Skip to content
lake
Browse this documentation section

Architecture

System design for lake. goal.md says why; this file says how. Agent entry points (AGENT.md, CLAUDE.md) are catalogs — the substance lives here.

Design ethos

Stateless fan-out in front, bounded stateful authority behind. The read flood (fleet nodes requesting episode data, DDoS-like) lands on a stateless query layer that scales horizontally and reads storage directly. The metadata authority — which tables exist, where, what version — is a small stateful tier the query layer shields behind a cache. Compute and storage are disaggregated: throughput scales by adding query nodes, not by growing a central store.

At a glance

Open the source-controlled architecture overview when a rendered systems diagram is more useful than a text walkthrough. The diagram deliberately separates control-plane metadata from object bytes; it is an implementation map, not an aspirational product diagram.

flowchart LR
    client["Fleet node / SDK"] -->|"Flight SQL"| query["Query replicas\nlake-query\nstateless"]
    query -->|"cache miss / bounded refresh"| meta["Metasrv\nstateful authority"]
    meta -->|"registry + operation records"| kv["DynamoDB / RocksDB\nHA KV"]
    query -->|"direct snapshot scan"| dataset["Lance datasets\nobject storage"]
    query -->|"exact table/snapshot lookup"| iceberg_catalog["Iceberg REST catalog\nexternal authority"]
    query -->|"direct Parquet/manifest scan"| iceberg_data["Iceberg table files\nobject storage"]
    sdk["SDK FILE upload / direct read"] -. "large-object bytes" .-> objects["managed object stage\nobject storage"]
    query -->|"metadata-only append proxy"| meta
    meta -->|"commit / version pointer"| dataset

Two rules make this diagram useful in practice:

PlaneCarriesDoes not carryScaling boundary
SQL/controlFlight SQL, catalog generations, schemas, table versions, DataLocation metadatavideo/model bytes, storage credentials, mutable file listsQuery cache shields Metasrv from read fan-out
object dataimmutable table files and managed large objectsregistry CAS, append coordination, user SQLSDK and Query stream directly to object storage

The external-Iceberg connector follows the same boundary: its catalog remains the authority for Iceberg metadata and snapshots, while Lake Query caches and reads it as a distinct, read-only SQL catalog. It does not put external Iceberg metadata in Lake’s registry. See Iceberg federation and the focused federation topology. The topology makes one extra property explicit: Flight tickets pin the external snapshot selected at planning, but object bytes continue to travel directly between Query and the Iceberg table’s object storage.

Pick the table authority before writing SQL

If you needAddress it asAuthority and byte path
Lake-owned writes, per-table versions, and video/model artifacts represented by FILElake.<namespace>.<table>Metasrv coordinates the native table version; the SDK streams object bytes to or from the Lake-managed stage and stores only immutable DataLocation metadata in the row.
SQL scans of an already-managed Iceberg tableiceberg.<namespace>.<table>The external REST catalog owns snapshots, commits, and GC; Query pins one external snapshot and reads Parquet/manifests directly with its workload identity.

Neither choice crosses the authority boundary: Lake does not import or commit Iceberg state, and the Iceberg catalog does not become an alternate commit path for a native Lake table. In particular, neither Metasrv nor a Flight request proxies multi-gigabyte object bytes.

Planned robot-training data model

This section distinguishes implemented contracts from the target model. Today Lake provides the underlying immutable DataLocation, exact per-table versions, managed-object reads, SQL primitives, and the format-neutral EpisodeManifestV1 plus EpisodeBundleV1/ArtifactRefV1 contracts with canonical JSON, exact Artifact binding, validated Arrow encoding, and a public bounded exact-schema Arrow append path. Episode-specific ingestion conveniences, format adapters, DatasetRevision and TrainingView APIs, Python readers, Materializations, and derived-Layer append are planned work.

In the target model, Lake is authoritative for Dataset membership, DatasetRevision identity, access, retention, TrainingView selection, and provenance. Rerun is a first-class visualization, temporal-query, and training-runtime adapter; its catalog is never an independent source of truth. The full terminology, current capability boundary, and delivery sequence live in robot-training-lakehouse.md.

flowchart LR
    source["External: robot / simulator"] --> adapters["Planned: format adapters\nRRD / MCAP / LeRobot"]
    adapters --> manifest["Current contract: EpisodeManifest v1\ncanonical metadata + bindings"]
    manifest --> objects["Current primitive: immutable FILE Artifacts\nmanaged object storage"]
    manifest --> episodes["Current contract + public Arrow append\nEpisode + ArtifactRef rows"]
    objects --> revision["Planned: DatasetRevision\nover current exact table versions"]
    episodes --> revision
    revision --> view["Planned: TrainingView"]
    view --> rerun["External: Rerun Viewer"]
    view --> training["Planned: Python training readers"]
    view --> materialized["Planned: rebuildable Materializations"]
    training --> layers["Planned: immutable derived Layers"]
    layers --> objects
    layers --> episodes

The logical and physical identities are deliberately separate:

Format adapters run at ingestion and client/runtime seams. They extract filterable Episode metadata and build immutable manifests before append. They may interpret RRD timelines, MCAP topics, or LeRobot shard offsets on reads, but no format type enters lake-engine, lake-metasrv, or DataLocation.

Robot-training reads therefore have two levels: Lake SQL selects across Episodes at an exact DatasetRevision, then a format-aware runtime aligns and decodes data within each Episode. Query and Metasrv continue to carry metadata only; Recording bytes and byte-range reads go directly between clients and object storage. No per-sample training request may reach Metasrv.

Three tiers

Fleet nodes / users            millions of reads, DDoS-like fan-out
    │  Flight SQL, load-balanced

Query layer   (lake-query)     STATELESS — fan out freely
    - accept SQL, plan + execute via DataFusion
    - read data files directly from object storage (disaggregated storage)
    - cache registrations (db→table→location+version) with TTL;
      cache immutable providers by exact generation with bounded capacity
    │  cache miss / refresh / writes

Metadata layer (lake-metasrv)  STATEFUL — bounded, leader-elected
    - authority for the db→table registry and current versions
    - serialize writes / durable idempotent commit coordination
    - background coordination (GC, compaction scheduling)


Metastore     (lake-meta)      HA KV: DynamoDB (prod) / RocksDB (dev)
    - registry pointers + compact operation records (durable, HA)

Storage engine (lake-engine + lake-engine-lance)
    - per-table datasets on object storage (immutable, cacheable)

The query layer is both the compute fan-out and the cache shield. Because the registry is small (~10⁴ tables) it fits in memory on every query node, so catalog reads are served locally and the metadata authority sees only cache-miss and write traffic. That is why the metadata tier being hard to fan out is acceptable — it is not on the hot read path.

Production Query does not program against the registry KV interface. Its read-only CatalogSource is an authenticated Metasrv Flight client exposing only delegated point resolution and a versioned conditional directory snapshot—no CAS, raw key, prefix scan, or delete. catalog_snapshot accepts the last opaque generation and returns not_modified or one canonical directory assembled between matching generation reads. Metasrv retries a racing generation at most three times, scans in 64-entry pages, accounts each entry before retaining it, and caps entries, individual schema IPC, generation tokens, and the serialized response. One process admits at most one full snapshot construction and retains that admission until the response is dropped. Only QueryService, MetadataPeer, and Admin identities may read the full directory. User principals remain namespace-scoped.

Each query replica also keeps a capacity-bounded DataFusion provider cache. The key includes table name, engine, physical location, incarnation, and registry version. Concurrent SQL planning for one generation coalesces into one engine open/provider build. An append changes the version; drop/recreate changes the incarnation, so neither can reuse a stale provider. Old immutable providers remain safe for in-flight readers and disappear through normal eviction. Missing or failed loads are never cached.

Catalog listing refresh is startup-strict and runtime-available. A replica must connect to Metasrv and synchronously publish its first complete catalog generation before it is ready. After that, an expired request-path check serves the immutable last-good generation immediately and admits at most one tracked background revalidation. A failed scan records bounded process-local health without replacing the snapshot; a later successful scan atomically publishes the replacement and clears failure state. Query shutdown aborts and joins the tracked request-triggered task, while the scheduled server refresher is owned directly by the server cancellation token. All fallible address/security/TLS configuration and the first catalog connection fail before Query binds. Warm directory checks usually return not_modified; warm registration and listing hits perform no metadata RPC. The Lance engine may still use physical manifest KV while opening object-store datasets; separating that storage metadata into a least-privilege table/credential is distinct from catalog authority and is not implied by this boundary. All of that setup precedes task creation; a drop guard also cancels and aborts both refresh paths if the serve future itself is cancelled.

Steady refresh is generation-gated after an explicit mixed-writer rollout finalization. Registry create/delete atomically move an opaque directory generation; append version changes do not. An unchanged generation costs one conditional RPC whose Metasrv handler performs one generation point read and skips the tbl/ scan. A changed generation is read again after the paged scan, and a moving candidate is discarded with bounded retry while the last-good snapshot remains published. Before the monotonic authority marker exists, the remote action fails closed; only the explicitly local development adapter retains legacy full-scan compatibility. See Catalog directory generations.

The published listing is an immutable Arc<CatalogGeneration> containing both namespace/table names and their registry schemas. DataFusion sync listing and each Flight discovery request clone only that Arc, then read names and schemas from the pinned allocation. Refresh builds a private replacement and swaps one pointer after the complete scan, so an in-flight response cannot combine two generations and request startup does not deep-clone the full catalog. Flight table discovery applies catalog/schema/table/type filters before schema resolution and row allocation, so filtered requests pay for matching rows. Schema and table discovery share the replica Query admission semaphore and hold a permit until the Flight stream completes or is dropped. Responses are built lazily in fixed-size batches (256 rows by default); the first match after the configured 10,000-row default maximum terminates the stream with ResourceExhausted.

Why the tiers scale differently

Mapping to GreptimeDB

lake reuses GreptimeDB’s frontend/metasrv split and drops its datanode tier:

GreptimeDBlakeproperty
frontend (stateless SQL, catalog cache)lake-queryfan out freely
metasrv (leader-elected authority, DDL coordination)lake-metasrvstateful, bounded
datanode (owns region storage)nonestorage is shared object store; query reads it directly

Adopted: the KvBackend trait shape (src/common/meta/src/kv_backend.rs), the KvBackendCatalogManager + moka cache pattern (src/catalog/src/kvbackend/), lease-in-KV leader election (src/common/meta/src/election/). Rejected: etcd as the backend (we lean on DynamoDB’s managed HA instead), and the datanode tier (disaggregated storage removes it).

Storage engine abstraction

Lake must be able to swap Lance for a self-built engine, so no crate above lake-engine-lance may name a lance:: type — the same confinement rule RocksDB has inside lake-meta. The engine trait exposes only what the catalog and metadata layers call:

#[async_trait]
pub trait TableEngine: Send + Sync {
    async fn create(&self, loc: &TableLocation, schema: SchemaRef) -> Result<TableHandleRef>;
    async fn open(&self, loc: &TableLocation) -> Result<Option<TableHandleRef>>;
    async fn maintain(&self, loc: &TableLocation, version: Version) -> Result<Option<Version>>;
}

#[async_trait]
pub trait TableHandle: Send + Sync {
    fn schema(&self) -> SchemaRef;
    fn current_version(&self) -> Version;
    /// A DataFusion table at a specific snapshot — this is how the query
    /// layer reads.
    async fn table_provider(&self, version: Version) -> Result<Arc<dyn TableProvider>>;
    /// Append one identified operation, producing one immutable version.
    async fn append(
        &self,
        operation: &AppendOperation,
        batches: SendableRecordBatchStream,
    ) -> Result<Version>;
    /// Discover an earlier commit after a lost response or failover.
    async fn reconcile_append(&self, operation: &AppendOperation) -> Result<Option<Version>>;
}

Lance-on-S3 commit arbitration stores one mutable current manifest pointer per physical dataset plus immutable historical records. The current pointer is a single O(1) metastore read. To advance it, the adapter archives the exact prior pointer and CASes current from exact old bytes to the new staging manifest; Lance has already durably written staging before that call. A legacy dataset without the fixed pointer performs one history scan and CAS-installs the maximum record. External history retention is separate from latest resolution: cleanup may delete a record only after Lance’s tag/branch-aware cleanup proves the corresponding manifest object obsolete (#42).

Drop never returns the fixed key to an absent state: it CASes current to deleting, clears immutable history, then CASes to a durable deleted marker. Recreate replaces only deleted. This tombstone prevents a migration that read legacy history before drop from winning an ABA None CAS afterward. Every history-key creation is a guarded transaction whose guard is the exact fixed-pointer bytes observed by that writer, so a writer paused before drop cannot add history after the deletion fence is installed. The pointer and both deletion states carry a UUIDv7 dataset incarnation. Advance and finalize preserve it; recreate generates a new one. Consequently, even recreating the same base URI with identical version and path values cannot make an old writer’s exact guard match again. Immutable history records carry the same incarnation. Legacy path-only history is accepted and upgraded on first archive/finalize. Finalizer retries converge only when both target path and incarnation match, never merely by path.

External manifest history reclamation runs only after Lance’s cleanup succeeds; Lance therefore remains authoritative for tags, referenced branches, and the retention window. The CLI validates one immutable Lance maintenance policy before opening storage: keep 10 recent versions by default, configurable within 1..=10000 through LAKE_LANCE_RETAIN_VERSIONS. The same policy is threaded through local and object-store engine construction. Maintenance then scans a durable, incarnation-bound page of at most 256 external records and HEADs each exact stored manifest path. It deletes only confirmed-absent paths, using a guarded transaction over exact latest and history bytes, then advances the cursor under the same latest guard. Existing objects—including tagged or branch-retained manifests—keep their records. A crash before cursor advancement replays idempotently, while drop/recreate makes stale work fail closed and clears the old cursor.

The fixed pointer is a commit-protocol boundary. A pre-pointer binary can write a newer per-version record without advancing it, so commit-capable binaries on both sides must not run concurrently. Deployments drain writes, upgrade every metadata node that may lead, then resume. Dataset data itself needs no offline migration; the first post-upgrade open installs its pointer lazily.

Version is an opaque engine-defined identifier; the registry stores it but does not interpret it. The Lance impl (lake-engine-lance) maps append and versioning onto Lance’s own commit + ExternalManifestStore (which is itself a put-if-not-exists KV — see below). A self-built engine implements the same trait over its own format, using lake-meta’s CAS directly.

Metadata: two levels, not one

There are three distinct pieces of metadata, owned by different layers:

  1. Registry (lake’s, in lake-meta): which tables exist and where — tbl/<namespace>/<name> → { incarnation_id, location, current_version, engine, schema_ipc? }. The incarnation changes on every successful create, so retained operation records cannot cross a drop/recreate boundary. The optional opaque Arrow IPC schema keeps old JSON readable while allowing Query to answer schema-inclusive Flight SQL discovery locally. Tiny (~10⁴ entries), fully cacheable, the metadata layer is its authority.
  2. Operation coordination (lake’s, in lake-meta): compact CAS records keyed by tenant, table, and UUIDv7 operation identity. They contain only a payload digest, base/result versions, state, and timestamps. Arrow rows, object bytes, credentials, and signed URLs are forbidden.
  3. Drop coordination (lake’s, in lake-meta): immutable tombstones keyed drop/<namespace>/<name>. A tombstone retains the exact old incarnation and registration needed to conditionally detach the registry and resume idempotent engine cleanup after a crash.
  4. Per-table manifest (the engine’s): the file list, schema, and version history of one table. For Lance this is the Lance dataset manifest; lake does not reimplement it.

The registry is key/value prefix-scannable with pagination. Metasrv can list a single tbl/<namespace>/… prefix. Each Query replica scans the whole tbl/ family on initial warm and directory DDL, while stable refreshes use the opaque directory-generation point read after rollout finalization. Discovery reads the immutable process-local generation and performs no request-path authority lookup.

Server-authoritative table placement

Remote DDL carries only a table identifier and Arrow schema. The metadata server derives TableLocation from a trusted TablePlacement: a local table root in development, or an S3 bucket plus optional key prefix in production. Namespace and table names must be safe single path segments before the engine or registry is touched. Legacy location-bearing requests fail closed; the server never ignores and never consumes a caller-selected URI.

The policy lives in lake-metasrv, above the engine boundary. Engines still receive an ordinary storage-neutral TableLocation, so placement authority does not couple the metadata or query tiers to Lance. Each HA replica must use the same placement configuration; only the elected leader materializes a new table.

Commit protocol

Writes go through the metadata layer’s leader to serialize per-table commits, then delegate the data commit to the engine. One logical append is identified by (authenticated tenant, table, UUIDv7 operation ID) and a verified SHA-256 digest of its ordered Flight control payload:

  1. The SDK uploads object bytes directly to managed storage, encodes only DataLocation rows, and generates the operation ID once. Ambiguous Flight failures reuse the same encoded messages, identity, and digest for a 30-second bounded window, longer than the 10-second metadata lease. If that window expires ambiguously, the error returns a PendingAppend; callers can resume it throughout operation retention with the same identity and without uploading the object again. When SDK checkpointing is configured, that exact pending identity and payload are fsynced before the first RPC and can be discovered and resumed by a replacement process.
  2. Metasrv authenticates the tenant, verifies the digest, claims a durable per-table fence, and CAS-creates a compact reserved operation record.
  3. The engine writes the new immutable version. Lance disables automatic append rebase and stores tenant, operation ID, digest, and reference-stage identity in transaction properties. Object-reference chunks are staged before the manifest is visible. A freshly reserved operation takes a no-eager-scan engine path; full transaction history is consulted only for replay/recovery or commit collision, not for every healthy append.
  4. Metasrv records engine_committed, CAS-advances the registry pointer only after reference lineage is complete, and records the terminal version.
  5. A replay or replacement leader loads the durable record and reconciles Lance transaction history. An identical replay returns the original version; a changed digest conflicts; corrupt or missing recovery evidence fails closed.

Reference staging is an operation-derived, durable recovery journal. It remains for the full lifetime of the coordinator’s append-operation record, even after final sidecars are complete, so every legally admitted exact replay can finish reconciliation without racing terminal deletion. The leader’s bounded operation GC holds the same table lock used by append, asks the engine to expire the exact stage, and only then deletes the operation record. Cleanup failure therefore leaves durable identity for a later retry instead of creating an untraceable orphan. Missing or recreated tables skip exact-stage cleanup because their old dataset is governed by the durable drop lifecycle. Within the stage itself, chunk zero is both the only chunk-count header and the publication marker. Persisters materialize non-header chunks first and publish chunk zero last. Expiry cleanup withdraws chunk zero first, then streams a bounded drain of the operation-dedicated prefix; a missing or lazily vanished header uses the same drain rather than assuming no chunks remain. The table lock prevents a legally admitted persister from recreating chunks during expiry.

This changes the staging publication protocol. Commit-capable binaries using the old header-first publisher must not run concurrently with the new cleanup: deployments drain writers, upgrade every Query/Metasrv node that may append, then resume, matching the external-manifest pointer upgrade boundary above.

Terminal coordination records have a configurable retention horizon (seven days by default). Leader-only cleanup drains consecutive metastore pages under finite per-tick page and wall-clock budgets, advances its cursor only after a complete page, stops at end-of-scan without wrapping, and resumes its process-local cursor on the next tick. Pending records are reconciled before deletion. IDs older than retention fail closed, and timestamps more than five minutes in the future are rejected. A FILE Flight control stream is capped at 64 MiB because multi-GB video/model bytes belong in object storage, not in Query or Metasrv memory.

Readers (through the query layer’s cache) never observe a half-written version: the pointer only ever advances to a fully-written one. Consistency is snapshot-by-version with at-most-one-commit staleness on cache-served reads — acceptable for training/eval, see goal.md.

Flight SQL preserves that exact snapshot across its two protocol phases. GetFlightInfo resolves all physical SQL references through the bounded registration cache, captures table name + engine + unique location + incarnation + version, and plans through a request-local catalog containing only those providers. The encrypted ticket carries the canonical bounded snapshot set. DoGet reconstructs the same catalog directly from the claims, so it neither re-resolves current registry pointers nor falls forward when a historical engine snapshot has been reclaimed. Unique per-incarnation object locations are the storage fence for drop/recreate; the incarnation remains in the claim and provider-cache key.

SQL over object storage

The public query protocol keeps arbitrary SQL execution read-only. Query nodes resolve the exact registry version and stream its files directly from S3; SQL text cannot register arbitrary object-store locations. The one typed write surface is a Flight DoPut command for already-uploaded SQL FILE rows: the SDK sends DataLocation Arrow values to query, query proxies the stream without persisting it, and the metadata leader performs the idempotent append protocol above. Query forwards tenant scope derived from its authenticated principal; a caller-supplied tenant string is never trusted. The original object bytes never enter query or metadata.

Each Metasrv process admits FILE appends before polling their first Flight message. A request reserves one concurrency slot plus its configured worst-case control-stream bytes, then holds both through follower forwarding or local digest validation, decode, engine commit, and response construction. Defaults are 8 concurrent appends, 100 ms queue wait, 64 MiB per stream, and 256 MiB process-wide buffered control metadata. Reserving the whole per-stream maximum avoids incremental weighted-permit deadlocks; saturation fails with ResourceExhausted. These budgets cover only small DataLocation/scalar control rows—multi-GB objects continue directly between the SDK and storage.

After a query node receives the metadata leader’s append acknowledgement, it advances that table’s local registration epoch and evicts the prior entry. An older in-flight cache fill is confined to its old epoch and cannot repopulate the post-ack lookup generation. The same SDK Flight connection therefore observes its own write immediately; independent query nodes retain the normal bounded-staleness window until their cache refreshes.

Interactive results stream over DoGet. The durable result tier accepts standard PollFlightInfo, persists encrypted snapshot-pinned jobs in a dedicated CAS store, materializes bounded Arrow IPC parts below a tenant/query-scoped result prefix, and publishes only an immutable manifest. Completed part capabilities are redeemed through DoGet; the SDK offers both the query_async convenience path and a versioned, bounded, redacted AsyncQueryHandle for explicit submit/poll/resume/cancel across caller and Query-replica restarts. Initial retries carry one 128-bit submission id and converge through the same state-record CAS. The complete API and security boundary are in docs/design/sql-api-over-s3.md.

When a deployment opts into global async execution limits, every replica keeps its local fair scheduler but reserves one exact opaque token in the dedicated async state store before claiming a job. The compact lease index is the only cross-replica authority: it is bounded, expires crashed owners, and counts a domain-separated tenant digest rather than a raw identity. A saturated lease leaves the job queued; it does not create a global queue or a terminal failure.

flowchart LR
    scan["Replica-local bounded scan"] --> local["Local tenant-fair scheduler"]
    local --> reserve["CAS reserve exact execution lease"]
    reserve -->|"capacity"| claim["CAS claim durable job"]
    reserve -->|"saturated"| pending["Job remains queued"]
    claim --> execute["Direct object-storage scan"]
    execute --> renew["Renew job + execution leases"]
    renew --> release["Release exact execution token"]
    reserve <--> state["Dedicated async state store\nbounded lease index"]

Result-part encoding and redemption are streaming and backpressured. A blocking Arrow IPC writer emits fixed chunks into a bounded async upload channel and enforces the encoded limit before an object can be published. On download, a fixed-window verified reader feeds Arrow’s incremental StreamDecoder, whose record batches enter a second bounded channel consumed directly by the Flight encoder. Before Arrow sees a complete message, a framing validator caps metadata at 1 MiB, caps declared bodies at the part limit, and rejects compressed IPC so an untrusted decompressed-length prefix cannot allocate outside the encoded window. The client-visible stream owns both tasks and shares its admission permit with the blocking decoder; EOF, error, deadline, cancellation, and drop have one lifecycle boundary, and admission is released only after that decoder actually exits. This bounds duplicate serialization buffers; an individual Arrow batch can still be as large as the external 64 MiB part limit.

Protocol conformance is intentionally not circular: a pinned official ADBC Flight SQL client black-box tests interactive typed streaming, bearer metadata, and read-only errors against a real listener. Rust’s upstream Arrow Flight types separately exercise polling, endpoint redemption, and cancellation, which are below the ordinary ADBC DB-API surface.

Crate map

CrateOwnsTier
lake-commonshared newtypes plus format-neutral Episode/ArtifactRef v1 values
lake-flightshared Flight TLS, bearer authentication, exposure policy, and secure Channel constructiontransport
lake-objectsSQL FILE representation, Episode/ArtifactRef v1 Arrow encoding, direct object I/Ostorage
lake-metaMetaStore (KvBackend) trait; RocksMeta (dev), DynamoMeta (prod)metastore
lake-engineTableEngine / TableHandle traits + shared typesstorage
lake-engine-lanceLance impl and ExternalManifestStore adapter; the ONLY crate that names lance::storage
lake-catalogdb→table registry logic, DataFusion CatalogProvider, moka cachequery + metadata
lake-querystateless query-layer server (Flight SQL, DataFusion execution)query
lake-metasrvstateful metadata-layer server (registry and table-placement authority, write coordination, leader election)metadata
lake-clithin clap binary: subcommands to run each server + client
lake-sdkRust streaming SQL query, parameterized FILE INSERT, DataLocation decoding, and direct readerclient

Conventions: thin libs (lib.rs is module docs + re-exports; logic in sub-files), async-first (engine, metastore, catalog, servers are async; sync bridges only at framework boundaries, each with a ponytail: note), per-crate snafu errors ({CrateName}Error + Result<T>), anyhow only in lake-cli. Each crate carries an AGENT.md card. Workspace lints/deps live in the root Cargo.toml.

schema is not a crate: it is Arrow SchemaRef, owned by the engine and surfaced through lake-catalog.

HA

No self-built consensus: read HA comes from stateless replicas, write HA from lease-election over an already-HA KV.

Production Metasrv wraps its raw metastore in a lease-fenced view after election starts. Each registry, append-operation, and maintenance CAS/delete loads the latest exact lease bytes immediately before publication and executes through guarded_mutate; election renew/resign continues on the raw store. Within one process, the metastore transaction takes a shared publication barrier while renewal/resign holds its exclusive side from durable lease CAS through local guard publication. This closes the exact-bytes rotation window without holding the barrier across long engine work. Long engine operations can therefore span same-holder renewals, while a takeover changes the guard and rejects a paused former leader. If the old leader already committed an engine version, the successor reconciles that immutable commit before publishing it.

Destructive table drop is a persisted idempotent procedure because object deletion cannot share the KV transaction. Metasrv first guarded-CAS creates an immutable incarnation tombstone, guarded-deletes the exact registry pointer, idempotently removes the old engine location, and finally guarded-deletes the exact tombstone. Leader maintenance resumes a cursor-paged bounded set of unfinished tombstones. Remote creates use <root>/<namespace>/<table>/<uuid>.lance (or the equivalent S3 prefix), so an old leader that continues object deletion after takeover can touch only the old physical generation. A replacement incarnation therefore remains safe even though object-store deletion itself is not lease-transactional.

Deliberate simplifications (ponytail markers)

Grep for ponytail: in code for shortcuts with known ceilings. Current design-level ones:

Phasing

Invariant across all phases: fleet reads go through the stateless query layer, never directly at the metadata authority.