mirror of
https://github.com/ModernRelay/omnigraph.git
synced 2026-06-09 01:35:18 +02:00
MR-854 docs: describe the staged-only seal; fix stale Lance index URLs
- writes.md / invariants.md / AGENTS.md: the inline-commit residuals now
live on InlineCommitResidual behind db.storage_inline_residual(), so
acceptance §1 holds by construction rather than 'option (b)' per-method
enumeration. Drop the inaccurate 'until Lance exposes
Operation::Overwrite { fragments }' claim (that op exists; stage_overwrite
already builds it) and reframe overwrite_batch as a removable legacy
residual gated on the loader's bulk-overwrite concurrency.
- forbidden_apis.rs: rewrite the allow-list doc for the split surface.
- lance.md: the index spec pages moved from /format/table/index/ to
/format/index/ in Lance 6.x (the old paths 404). Fix all 13 URLs.
This commit is contained in:
parent
39a72b76bf
commit
ed00c05be0
5 changed files with 56 additions and 53 deletions
|
|
@ -236,7 +236,7 @@ omnigraph policy explain --actor act-alice --action change --branch main
|
|||
| Columnar storage on object store | ✅ Arrow/Lance | URI normalization, S3 env-var plumbing |
|
||||
| Per-dataset versioning + time travel | ✅ | `snapshot_at_version`, `entity_at`, snapshot-pinned reads across many tables |
|
||||
| Per-dataset branches | ✅ | **Graph-level** branches (atomic across all sub-tables), lazy fork, system branch filtering |
|
||||
| Atomic single-dataset commits | ✅ | **Multi-table publish via three layers**, NOT a single Lance primitive: (1) per-table Lance `commit_staged` for the data write, (2) `__manifest` row-level CAS via `ManifestBatchPublisher` for cross-table ordering, (3) the open-time recovery sweep for the residual gap between (1) and (2). All three layers ship; the five migrated writers (`MutationStaging::finalize`, `schema_apply`, `branch_merge`, `ensure_indices`, `optimize_all_tables`) write a `__recovery/{ulid}.json` sidecar before Phase B and delete it after Phase C. The next `Omnigraph::open` (gated on `OpenMode::ReadWrite`) runs the sweep in `db/manifest/recovery.rs`: classify, decide all-or-nothing per sidecar, roll forward via single `ManifestBatchPublisher::publish` or roll back via `Dataset::restore` followed by a manifest publish of the restored version (so both directions converge to `manifest == HEAD` — no residual drift), and record an audit row in `_graph_commit_recoveries.lance` (queryable via `omnigraph commit list --filter actor=omnigraph:recovery`). Continuous in-process recovery (no restart needed between Phase B failure and recovery) is the goal of a future background reconciler. Engine writes route through a sealed `TableStorage` trait exposing `stage_*` + `commit_staged` as the canonical staged-write surface; documented inline-commit residuals (`delete_where`, `create_vector_index`, plus legacy `append_batch` / `merge_insert_batches` / `overwrite_batch` / `create_*_index`) remain on the trait until upstream Lance ships a public two-phase API ([#6658](https://github.com/lance-format/lance/issues/6658), [#6666](https://github.com/lance-format/lance/issues/6666)) and the migration of every call site completes. |
|
||||
| Atomic single-dataset commits | ✅ | **Multi-table publish via three layers**, NOT a single Lance primitive: (1) per-table Lance `commit_staged` for the data write, (2) `__manifest` row-level CAS via `ManifestBatchPublisher` for cross-table ordering, (3) the open-time recovery sweep for the residual gap between (1) and (2). All three layers ship; the five migrated writers (`MutationStaging::finalize`, `schema_apply`, `branch_merge`, `ensure_indices`, `optimize_all_tables`) write a `__recovery/{ulid}.json` sidecar before Phase B and delete it after Phase C. The next `Omnigraph::open` (gated on `OpenMode::ReadWrite`) runs the sweep in `db/manifest/recovery.rs`: classify, decide all-or-nothing per sidecar, roll forward via single `ManifestBatchPublisher::publish` or roll back via `Dataset::restore` followed by a manifest publish of the restored version (so both directions converge to `manifest == HEAD` — no residual drift), and record an audit row in `_graph_commit_recoveries.lance` (queryable via `omnigraph commit list --filter actor=omnigraph:recovery`). Continuous in-process recovery (no restart needed between Phase B failure and recovery) is the goal of a future background reconciler. Engine writes route through a sealed `TableStorage` trait (`db.storage()`) exposing only `stage_*` + `commit_staged` + reads; the inline-commit residuals (`overwrite_batch`, `delete_where`, `create_vector_index`) are split onto a separate sealed `InlineCommitResidual` trait reached via `db.storage_inline_residual()` (MR-854), so the default surface cannot couple a write with a HEAD advance — §1 holds by construction. `delete_where` and `create_vector_index` stay inline until upstream Lance ships a public two-phase API ([#6658](https://github.com/lance-format/lance/issues/6658), [#6666](https://github.com/lance-format/lance/issues/6666)); `overwrite_batch` until the loader bulk-overwrite fast-path migrates to `stage_overwrite`. |
|
||||
| Compaction (`compact_files`) | ✅ | `omnigraph optimize` orchestrates over all node/edge tables, bounded concurrency; **publishes each compacted table's new version to `__manifest`** (so the manifest tracks the Lance HEAD — required for reads to observe compaction and for schema apply / strict writes to pass their HEAD-vs-manifest precondition), under the per-`(table, main)` write queue with `SidecarKind::Optimize` recovery coverage; **refuses on an unrecovered graph** (errors if a `__recovery` sidecar is pending — recovery may roll back a partial write, so optimize requires `manifest == HEAD` going in); **skips blob-bearing tables** (reported via `TableOptimizeStats.skipped`, not silent), gated on `LANCE_SUPPORTS_BLOB_COMPACTION` until the upstream blob-v2 compaction-decode bug is fixed (see [docs/dev/invariants.md](docs/dev/invariants.md) Known Gaps) |
|
||||
| Cleanup (`cleanup_old_versions`) | ✅ | `omnigraph cleanup` with `--keep` / `--older-than` policy |
|
||||
| BTREE / inverted (FTS) / vector indexes | ✅ | `ensure_indices` builds them on every relevant column; idempotent; lazy across branches |
|
||||
|
|
|
|||
|
|
@ -31,22 +31,20 @@
|
|||
//!
|
||||
//! ## Allow-list shape
|
||||
//!
|
||||
//! After MR-854 (MR-793 Phase 1b + Phase 9), every engine call site
|
||||
//! reaches the storage layer through `db.storage()` (returns
|
||||
//! `&dyn TableStorage`). The inherent inline-commit methods on
|
||||
//! `TableStore` (`append_batch`, `merge_insert_batch{,es}`,
|
||||
//! `overwrite_batch`, `create_{btree,inverted}_index`) are now
|
||||
//! `pub(crate)`, so the only direct users are `table_store.rs` itself
|
||||
//! (which IS the storage layer) and the bulk loader's
|
||||
//! `LoadMode::{Append, Overwrite, Merge}` concurrent fast-paths in
|
||||
//! `loader::write_batch_to_dataset` (the loader uses the trait surface
|
||||
//! for the staged-write path and falls back to the demoted inherent
|
||||
//! methods only for the concurrent fast-path, which has no two-phase
|
||||
//! shape in Lance v6.0.1). The remaining trait-surface residuals
|
||||
//! (`delete_where`, `create_vector_index`) are gated on the Lance v7.x
|
||||
//! bump (MR-A) and Lance #6666 respectively — see
|
||||
//! `docs/dev/lance.md` for the canonical tracking. The file-level
|
||||
//! allow-list below matches that boundary.
|
||||
//! After MR-854, `db.storage()` (`&dyn TableStorage`) exposes only staged
|
||||
//! primitives + reads. The inline-commit writes live on a separate
|
||||
//! `InlineCommitResidual` trait reached via
|
||||
//! `Omnigraph::storage_inline_residual()`, so the default storage surface
|
||||
//! cannot couple "write bytes" with "advance HEAD" — engine code that
|
||||
//! wants an inline residual must name the residual accessor explicitly.
|
||||
//! The only residuals are `overwrite_batch` (the loader bulk-overwrite
|
||||
//! fast-path), `delete_where` (Lance #6658 / v7.x), and
|
||||
//! `create_vector_index` (Lance #6666). The dead legacy methods
|
||||
//! (trait `append_batch` / `merge_insert_batches`, inherent
|
||||
//! `merge_insert_batch{,es}`, `create_{btree,inverted}_index`) were
|
||||
//! removed entirely. This guard's scope is unchanged: it catches direct
|
||||
//! `lance::*` inline-commit misuse outside the storage layer. The
|
||||
//! file-level allow-list below matches that boundary.
|
||||
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
|
|
|
|||
|
|
@ -102,7 +102,7 @@ Use it this way:
|
|||
| Branch delete | Manifest is the single authority, flipped atomically first; per-table forks + commit-graph branch are derived state, reclaimed best-effort (`force_delete_branch`) with the `cleanup` reconciler as the guaranteed backstop. Reusing a name whose reclaim failed before `cleanup` surfaces an actionable error | [branches-commits.md](../user/branches-commits.md), [maintenance.md](../user/maintenance.md) |
|
||||
| Schema validation | Type checks, required fields, defaults, edge endpoint checks, and edge cardinality are enforced on write paths | [schema-language.md](../user/schema-language.md), [execution.md](execution.md) |
|
||||
| Unique constraints | Intra-batch and write-path checks exist; full cross-version uniqueness is still a gap | [schema-language.md](../user/schema-language.md) |
|
||||
| Storage trait | `TableStorage` is the sealed staged-write surface; engine call sites all route through `db.storage()` (MR-854); inline-commit inherent methods are `pub(crate)`-demoted; capability/stat surfaces are roadmap | [writes.md](writes.md), [architecture.md](architecture.md) |
|
||||
| Storage trait | `TableStorage` (via `db.storage()`) is staged-only; the inline-commit residuals (`overwrite_batch`, `delete_where`, `create_vector_index`) are split onto a separate sealed `InlineCommitResidual` trait reached via `db.storage_inline_residual()` (MR-854), so §1 holds by construction; capability/stat surfaces are roadmap | [writes.md](writes.md), [architecture.md](architecture.md) |
|
||||
| Index lifecycle | `ensure_indices` is explicit today; reconciler-based convergence is roadmap | [indexes.md](../user/indexes.md), [maintenance.md](../user/maintenance.md) |
|
||||
| Traversal IDs | Runtime still builds `TypeIndex`; Lance stable row-id based graph IDs are roadmap | [architecture.md](architecture.md), [query-language.md](../user/query-language.md) |
|
||||
| Auth | Bearer token hashing and server-side actor resolution are implemented at the HTTP boundary | [server.md](../user/server.md), [policy.md](../user/policy.md) |
|
||||
|
|
@ -124,13 +124,15 @@ them explicit.
|
|||
renames. The current compiler still derives type IDs from `kind:name`; this
|
||||
must be fixed before relying on renamed IDs across accepted schemas.
|
||||
- **Storage abstraction:** `TableStorage` is present, sealed, and canonical for
|
||||
staged writes. MR-854 closed the call-site migration: every engine call site
|
||||
routes through `db.storage()` and the inherent inline-commit methods on
|
||||
`TableStore` (`append_batch`, `merge_insert_batch{,es}`, `overwrite_batch`,
|
||||
`create_{btree,inverted}_index`) are `pub(crate)`, surviving only as the bulk
|
||||
loader's `LoadMode::{Append, Overwrite, Merge}` concurrent fast-paths. The
|
||||
remaining trait-surface residuals are `delete_where` (gated on MR-A — Lance
|
||||
v7.x bump) and `create_vector_index` (gated on Lance #6666); see
|
||||
staged writes. MR-854 sealed it: `db.storage()` exposes only staged primitives
|
||||
+ reads, and the inline-commit residuals are split onto a separate sealed
|
||||
`InlineCommitResidual` trait reached via `db.storage_inline_residual()`, so a
|
||||
new writer cannot couple a write with a HEAD advance through the default
|
||||
surface. The dead legacy methods (`append_batch` on the trait,
|
||||
`merge_insert_batch{,es}`, `create_{btree,inverted}_index`) were removed. The
|
||||
remaining residuals are `overwrite_batch` (loader bulk-overwrite fast-path,
|
||||
removable once it migrates to `stage_overwrite`), `delete_where` (gated on
|
||||
MR-A — Lance v7.x bump), and `create_vector_index` (gated on Lance #6666); see
|
||||
[lance.md](lance.md) and [writes.md](writes.md). New write paths should use
|
||||
the staged shape unless a documented Lance blocker applies.
|
||||
- **Deletes and vector indexes:** `delete_where` and vector index creation still
|
||||
|
|
|
|||
|
|
@ -55,18 +55,18 @@ Adding/changing index types, fixing coverage, debugging FTS or vector recall, de
|
|||
|
||||
| Topic | URL |
|
||||
|---|---|
|
||||
| Index spec overview | https://lance.org/format/table/index/ |
|
||||
| BTREE scalar index | https://lance.org/format/table/index/scalar/btree/ |
|
||||
| Bitmap scalar index | https://lance.org/format/table/index/scalar/bitmap/ |
|
||||
| Bloom-filter scalar index | https://lance.org/format/table/index/scalar/bloom_filter/ |
|
||||
| Label-list scalar index | https://lance.org/format/table/index/scalar/label_list/ |
|
||||
| Zone-map scalar index | https://lance.org/format/table/index/scalar/zonemap/ |
|
||||
| R-Tree scalar index (spatial) | https://lance.org/format/table/index/scalar/rtree/ |
|
||||
| Full-text search (FTS) index | https://lance.org/format/table/index/scalar/fts/ |
|
||||
| N-gram scalar index | https://lance.org/format/table/index/scalar/ngram/ |
|
||||
| Vector index | https://lance.org/format/table/index/vector/ |
|
||||
| Fragment-reuse system index | https://lance.org/format/table/index/system/frag_reuse/ |
|
||||
| MemWAL system index | https://lance.org/format/table/index/system/mem_wal/ |
|
||||
| Index spec overview | https://lance.org/format/index/ |
|
||||
| BTREE scalar index | https://lance.org/format/index/scalar/btree/ |
|
||||
| Bitmap scalar index | https://lance.org/format/index/scalar/bitmap/ |
|
||||
| Bloom-filter scalar index | https://lance.org/format/index/scalar/bloom_filter/ |
|
||||
| Label-list scalar index | https://lance.org/format/index/scalar/label_list/ |
|
||||
| Zone-map scalar index | https://lance.org/format/index/scalar/zonemap/ |
|
||||
| R-Tree scalar index (spatial) | https://lance.org/format/index/scalar/rtree/ |
|
||||
| Full-text search (FTS) index | https://lance.org/format/index/scalar/fts/ |
|
||||
| N-gram scalar index | https://lance.org/format/index/scalar/ngram/ |
|
||||
| Vector index | https://lance.org/format/index/vector/ |
|
||||
| Fragment-reuse system index | https://lance.org/format/index/system/frag_reuse/ |
|
||||
| MemWAL system index | https://lance.org/format/index/system/mem_wal/ |
|
||||
| HNSW Rust example | https://lance.org/examples/rust/hnsw/ |
|
||||
| Distributed indexing | https://lance.org/guide/distributed_indexing/ |
|
||||
| Tokenizer (FTS, n-gram) | https://lance.org/guide/tokenizer/ |
|
||||
|
|
@ -125,7 +125,7 @@ Touching `omnigraph optimize` / `cleanup`, the underlying `compact_files` / `cle
|
|||
|---|---|
|
||||
| Read-and-write guide (covers `compact_files`, `cleanup_old_versions`) | https://lance.org/guide/read_and_write/ |
|
||||
| Performance (compaction tradeoffs) | https://lance.org/guide/performance/ |
|
||||
| Fragment-reuse index | https://lance.org/format/table/index/system/frag_reuse/ |
|
||||
| Fragment-reuse index | https://lance.org/format/index/system/frag_reuse/ |
|
||||
|
||||
### DataFusion integration
|
||||
|
||||
|
|
|
|||
|
|
@ -106,30 +106,33 @@ the same drift class. Closing it requires either upstream Lance
|
|||
multi-dataset commit OR the omnigraph-side recovery-on-open reconciler
|
||||
described in `.context/mr-793-design.md` §15 (deferred to MR-795).
|
||||
|
||||
### Inline-commit method residuals on `TableStorage` (MR-793 acceptance §1 option b)
|
||||
### Inline-commit residuals live on `InlineCommitResidual`, not `db.storage()` (MR-793 acceptance §1, by construction)
|
||||
|
||||
MR-793's acceptance criterion §1 ("`TableStore` public API has no method that performs a manifest commit as a side effect of writing") is met **per-method** by enumerating every inline-commit method that remains on the trait surface, naming why it cannot yet be removed, and keeping the residual comment at every call site:
|
||||
MR-793's acceptance criterion §1 ("`TableStore` (or successor) public API has no method that performs a manifest commit as a side effect of writing") holds **by construction** after MR-854. `db.storage()` (`&dyn TableStorage`) exposes only staged primitives + reads; the inline-commit writes Lance cannot yet stage live on a separate `InlineCommitResidual` trait reached via `Omnigraph::storage_inline_residual()`. A new engine writer cannot couple a write with a Lance HEAD advance through the default surface — it would have to name the residual accessor explicitly. The dead legacy methods (trait `append_batch` / `merge_insert_batches`, inherent `merge_insert_batch{,es}`, `create_{btree,inverted}_index`) were removed; appends/merges and scalar index builds all use the `stage_*` primitives.
|
||||
|
||||
| Method on `TableStorage` | Inline-commit reason | Closes when |
|
||||
Three methods remain on `InlineCommitResidual`, each named honestly at its call site:
|
||||
|
||||
| Residual method | Inline-commit reason | Closes when |
|
||||
|---|---|---|
|
||||
| `delete_where` | `DeleteBuilder::execute_uncommitted` is not in Lance v6.0.1 (closed upstream as [#6658](https://github.com/lance-format/lance/issues/6658) but first ships in `v7.0.0-beta.10`); see [docs/dev/lance.md](lance.md) | MR-A: Lance v7.x bump migrates `delete_where` to staged, retires the parse-time D₂ mutation rule, and extends recovery sidecar coverage |
|
||||
| `create_vector_index` | Vector indices take Lance's "segment commit path"; `build_index_metadata_from_segments` is `pub(crate)` (Lance [#6666](https://github.com/lance-format/lance/issues/6666) still open) | Lance #6666 lands and `stage_create_vector_index` joins the trait |
|
||||
| `create_vector_index` | Vector indices take Lance's "segment commit path"; `build_index_metadata_from_segments` is `pub(crate)` (Lance [#6666](https://github.com/lance-format/lance/issues/6666) still open) | Lance #6666 lands and `stage_create_vector_index` joins the staged surface |
|
||||
| `overwrite_batch` | Removable legacy: a `stage_overwrite` primitive exists (schema_apply uses it), but the loader's bulk `LoadMode::Overwrite` fast-path still uses this inline path for cross-table write concurrency (see below) | The loader's overwrite fast-path migrates to `stage_overwrite` + `commit_staged` + a recovery sidecar (tracked follow-up) |
|
||||
|
||||
MR-854 (Phase 1b + Phase 9) closed the remaining residuals on the engine surface: every `db.table_store.X(...)` call site was converted to `db.storage().X(...)` (trait dispatch through `&dyn TableStorage`), and the legacy inline-commit inherent methods on `TableStore` (`append_batch`, `merge_insert_batch`, `merge_insert_batches`, `overwrite_batch`, `create_btree_index`, `create_inverted_index`) were demoted from `pub` to `pub(crate)`. They survive only as the bulk loader's `LoadMode::{Append, Overwrite, Merge}` concurrent fast-paths (see "`LoadMode::Overwrite` residual" below) and as internal helpers for the staged primitives — no engine call site outside `table_store.rs` and `loader::write_batch_to_dataset` reaches them.
|
||||
|
||||
After **MR-A (Lance v7 bump) + Lance #6666 ship**, the trait surface exposes only staged-write primitives + `commit_staged`. Until then this matrix names the two remaining residuals explicitly, every call site carries a one-line residual comment, and no engine code outside `table_store.rs` is permitted to reach the inline-commit Lance APIs (enforced by the `tests/forbidden_apis.rs` guard).
|
||||
The `tests/forbidden_apis.rs` guard still catches direct `lance::*` inline-commit misuse outside the storage layer; the trait split makes the staged-only default a type-system guarantee on top of it.
|
||||
|
||||
### `LoadMode::Overwrite` residual
|
||||
|
||||
The bulk loader's Append and Merge modes use the staged-write path
|
||||
described above. `LoadMode::Overwrite` keeps the legacy inline-commit
|
||||
path: truncate-then-append doesn't fit the staged shape cleanly in
|
||||
Lance v6.0.1, and overwrite has no in-flight read-your-writes
|
||||
requirement (the prior data is being wiped). A mid-overwrite failure
|
||||
can leave Lance HEAD on a partially-truncated table; the next overwrite
|
||||
will replace it. Operator-driven (rare in agent workloads); document
|
||||
permanently until Lance exposes `Operation::Overwrite { fragments }` as
|
||||
a two-phase op.
|
||||
described above. `LoadMode::Overwrite` keeps an inline-commit fast-path
|
||||
(`InlineCommitResidual::overwrite_batch`): each table is overwritten and
|
||||
committed concurrently with the others, and a fresh overwrite wipes the
|
||||
prior data, so there is no in-flight read-your-writes requirement and no
|
||||
partial-drift correctness concern (a mid-overwrite failure leaves the old
|
||||
data; re-running the overwrite recovers). A `stage_overwrite` primitive
|
||||
already exists (schema_apply uses it), so this path can migrate to the
|
||||
staged + recovery-sidecar shape; preserving the cross-table write
|
||||
concurrency (`OMNIGRAPH_LOAD_CONCURRENCY`) is the open design point.
|
||||
Tracked as a follow-up.
|
||||
|
||||
### Open-time recovery sweep
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue