feat: support manifest version hint#5997
feat: support manifest version hint#5997jackye1995 wants to merge 21 commits intolance-format:mainfrom
Conversation
- Benchmark tests performance degradation with many small fragments - Measures write (commit) and load (manifest open) latencies - Outputs CSV time series data for graphing - Calculates linear regression to show per-fragment overhead - Supports S3, local disk via DATASET_PREFIX env var - Configurable via NUM_ITERATIONS and ROWS_PER_FRAGMENT Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>
Add a version hint file (`latest_version_hint.bin`) that encodes the latest version number via its file size. This enables O(1) latest version lookup via HEAD request instead of O(n) listing. Key changes: - Write version hint after each successful commit (optimistic, non-blocking) - Race hint-based lookup vs listing, use whichever completes first - HEAD request (~10ms) is much faster than LIST (~200ms+) at scale - Works on all object stores (S3 Standard, S3 Express, GCS, Azure) The optimization can be disabled via environment variable: LANCE_USE_VERSION_HINT=0 Performance improvement: - S3 Express: ~10ms (HEAD) vs ~200ms+ (LIST at 5000 versions) - S3 Standard: ~20ms (HEAD + probe) vs ~200ms+ (LIST at 5000 versions) Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>
- Add LANCE_VERSION_HINT_FORMAT env var: "file_size" (default) or "json"
- Add LANCE_VERSION_HINT_WRITE_MODE env var: "async" (default) or "sync"
- Default async mode uses fire-and-forget pattern to avoid commit latency
- JSON format stores version as {"version": N} for human readability
- File-size format uses file size = version number for O(1) HEAD lookup
Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)
Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
…bing Add list_manifest_locations_since method that uses version hint to avoid O(n) manifest listing on non-lexically ordered stores (e.g., S3 Express). Instead of listing all manifests, the optimization: 1. Reads the version hint to get approximate latest version 2. Probes upward from hint to find true latest (sequential HEADs) 3. Parallel HEADs for versions between since_version and hint 4. Returns all found manifests in descending order This changes commit-time complexity from O(n) to O(k) where k is the number of new versions since the read version. The feature is gated by LANCE_USE_VERSION_HINT env var and supports both file_size and json formats via LANCE_VERSION_HINT_FORMAT. Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>
When list_manifests_since_version_with_hint found that hint_version <= since_version, it called probe_versions_upward(since_version + 1). If no version existed at since_version + 1, probe_versions_upward returned None, which caused the function to fall back to full O(n) listing instead of returning an empty list (the fast path). This fix properly handles the None case by returning an empty list, achieving O(1) performance when there are no new transactions since the read version. Also: - Remove slope calculation from manifest_commit benchmark (just report averages) - Add test for version hint optimization with non-lexical stores Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>
…D timing - Revert stagger start approach (didn't help with contention) - Change default hint format from file_size to JSON - Add more precise HEAD request timing in debug logs
Add environment variable LANCE_HINT_ONLY to enable hint-only mode that bypasses tokio::select! racing with listing. This helps isolate whether the slow load path hint reads are caused by connection contention from racing, or by something else. When LANCE_HINT_ONLY=1, the load path will: 1. Only use hint+HEAD approach 2. Fall back to listing only if hint fails (no racing) This is for debugging/benchmarking purposes to investigate why load path hint reads are ~50ms while commit path hint reads are ~5ms. Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>
Add WARM_SESSION=true environment variable to test load latency with warm connections by reusing the same session. This helps compare: - Cold connection performance (WARM_SESSION=false, default) - Warm connection performance (WARM_SESSION=true) This helps identify whether slow load latency is due to: - TCP/TLS connection establishment overhead (cold connection) - tokio::select! racing contention - Other factors Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>
Add environment variable LANCE_CONNECTION_WARMUP=1 to enable connection warmup before hint/listing race. This makes a cheap HEAD request first to establish TCP/TLS connection, then subsequent operations use warm connections. Without warmup (cold session): ~95-100ms load latency With warmup (cold session): ~50-60ms load latency (estimated) With warm session: ~14ms load latency (optimal) The warmup helps cold starts (serverless, CLI tools) by reducing the impact of TCP/TLS connection establishment overhead. Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>
- Use checkout_latest() instead of load() for load measurements - Share ObjectStoreRegistry to reuse warm TCP/TLS connections - Use zero cache size session to avoid manifest caching - Remove unused env vars (WARM_SESSION, SHARED_REGISTRY, DIRECT_CHECKOUT) - Clean up debug statements from commit.rs - Remove connection warmup feature (it just moves latency, doesn't help) This approach properly isolates storage read latency from cold start overhead. Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>
- Auto-detect S3 Express buckets by --x-s3 suffix - Pass s3_express=true storage option for S3 Express buckets Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>
S3 Express buckets don't support GetBucketLocation API, so we need to pass the region explicitly from AWS_DEFAULT_REGION or AWS_REGION. Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>
- Remove LANCE_HINT_ONLY env var and sequential hint+HEAD code path - Remove storage options handling from benchmark (auto-detected) - Add ENABLE_CACHE config (default: false) for benchmark - Use single shared session for both commit and load - Keep LANCE_USE_VERSION_HINT to enable/disable version hint feature Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>
Code Review for PR #5997: feat: support manifest version hintSummaryThis PR adds a version hint optimization for faster manifest lookup on non-lexically ordered object stores (e.g., S3 Express). The approach uses a JSON hint file + HEAD-based probing instead of full directory listing. P1 Issues1. Inefficiency when hint file doesn't exist In Consider restructuring to avoid this: // Start list, check hint, if hint fails continue with existing list
let list_fut = resolve_version_from_listing(object_store, base);
tokio::pin!(list_fut);
tokio::select! {
biased;
hint_result = read_version_hint_and_probe(object_store, base) => {
if let Some(location) = hint_result {
return Ok(location);
}
list_fut.await // Continue with same future
}
list_result = &mut list_fut => list_result
}2. The pattern at line 152-156 spawns a task then immediately blocks on it: let handle = tokio::spawn(async move { ... });
if sync_write {
let _ = tokio::task::block_in_place(|| tokio::runtime::Handle::current().block_on(handle));
}
Minor Observations
🤖 Generated with Claude Code |
Based on benchmarking result in #5947 (comment)
Currently I have only kept JSON format manifest hint support. The exact format to choose requires some further discussions.