ADR-001: Tightened Overlay Index Artifact Contract & Compound Puffin BundlesΒΆ
Status: Proposed / Under Implementation
Date: 2026-10-03
Authors: BenoStreamDB Core Team
Target Version: v0.13.0+
Companion Documents:
docs/ARCHITECTURE.md(Lakehouse Storage Architecture)docs/ICEBERG_COMPATIBILITY.md(Iceberg Specification Conformance)docs/CONCURRENCY.md(Multi-Writer Transaction Model)docs/BENCHMARKING_PLAN.md(Production Verification Framework)
1. Executive SummaryΒΆ
BenoStreamDB pairs authoritative, open Apache Iceberg table storage with derived, advisory overlay indexes (Vector HNSW-TurboQuant, BM25 lexical, Roaring Bitmaps, and Bloom filters). Treating overlay indexes as disposable accelerators ensures 100% open-format compatibility and zero lock-in: if an index is lost or corrupted, the table degrades gracefully to raw Parquet scans.
However, in large-scale deployments, loose sidecar management presents two structural risks:
Object Count Explosion ($O(M \times N)$): Multiple index files per Parquet segment can multiply S3/GCS object counts by 5xβ10x, leading to cloud storage rate limiting, high
PUT/LISTcosts, and slow metadata planning.Silent Index Drift: If an external engine (Apache Spark, Trino, Snowflake, or PyIceberg) mutates or compacts a Parquet file out-of-band, an index bound only by file name can return corrupted vector candidates or misaligned row offsets.
This document specifies the Tightened Index Artifact Contract:
Cryptographic & Structural Lineage Binding: Explicitly binding every index artifact to the data fileβs SHA-256 checksum, snapshot ID, and exact record count.
Compound Index Bundles (Apache Iceberg Puffin): Consolidating multiple index structures into single Puffin (
.puffin) sidecars queried via HTTP byte-range offsets, reducing object overhead by 70%β85%.Detached Overlay Catalogs: Formalizing the architecture for indexing external, read-only Iceberg tables without write permissions to the upstream storage or catalog.
2. Problem Statement & MotivationΒΆ
2.1 The Disposable Accelerator InvariantΒΆ
BenoStreamDB establishes the core invariant: $$\text{Correctness}(\text{Table}) \equiv \text{Correctness}(\text{Parquet Data}) \quad \forall \text{ queries}$$
Indexes do not store authoritative data. They are derived mathematical projections (nearest-neighbor graphs, inverted postings, bitmap filters) that map queries to row offsets within specific Parquet files. If an index is deleted, query results remain identicalβonly execution latency changes.
2.2 The Scaling VulnerabilitiesΒΆ
In production lakehouses, this model encounters two concrete challenges:
Vulnerability A: Object Count ExplosionΒΆ
In a streaming table with 50,000 Parquet segments:
Raw Parquet files: 50,000 objects
Inverted Bitmap indexes (
.inv.parquet): 50,000 objectsBM25 full-text indexes (
.bm25.parquet): 50,000 objectsVector HNSW-TQ8 graph + codebooks (
.hnsw.graph,.centroids.parquet): 100,000 objectsTotal Objects: 250,000+ objects on S3/GCS.
This explosion increases cloud API billing, increases S3 503 SlowDown throttling (as demonstrated in Β§7.4 benchmarks), and severely degrades partition-level LIST performance.
Vulnerability B: Silent Index Drift & Out-of-Band MutationsΒΆ
Because Iceberg tables are open, external engines often interact with the data:
Apache Spark runs
rewriteDataFiles(bin-packing compaction) or Trino executesOPTIMIZE.Old Parquet files are unlinked, and new Parquet files are written.
If an index sidecar is bound merely by path conventions or if a file is overwritten in-place, the index can point to row offsets that no longer correspond to the original data, producing silent data corruption.
3. The Tightened Index Artifact SpecificationΒΆ
3.1 Metadata Lineage Contract (IndexFile)ΒΆ
Every index entry recorded in an Iceberg manifest or secondary overlay catalog MUST contain strict cryptographic and positional bindings to its source data file.
Rust Data Structure Definition (src/core/manifest/types.rs)ΒΆ
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct IndexFile {
/// Format version of the serialized index structure (e.g., 1 for legacy, 2 for tightened/Puffin)
#[serde(default = "default_index_format_version")]
pub format_version: u32,
/// Unique identifier for this index instance
pub index_id: String,
/// High-level index category: "vector", "lexical", "scalar", "composite", "bloom"
pub index_category: String,
/// Concrete algorithm implementation: "hnsw_turboquant8", "hnsw_f32", "bm25_v1", "roaring_bitmap_v1"
pub algorithm: String,
/// Target column name within the Parquet schema
pub column_name: String,
// -----------------------------------------------------------------------
// Strict Lineage Bindings (Data Integrity Guards)
// -----------------------------------------------------------------------
/// Exact Iceberg Snapshot ID active when this index was constructed
pub source_snapshot_id: i64,
/// Cryptographic SHA-256 hash of the target Parquet data file
pub source_data_checksum: String,
/// Exact record count of the target Parquet data file
/// Enforces a strict 1-to-1 bijection between index point IDs and Parquet row offsets
pub source_record_count: i64,
/// Cryptographic SHA-256 hash of the index artifact itself (detects storage corruption / bitrot)
#[serde(default, skip_serializing_if = "Option::is_none")]
pub index_checksum: Option<String>,
// -----------------------------------------------------------------------
// Physical Storage Location & Puffin Bundle Offsets
// -----------------------------------------------------------------------
/// Physical URI or relative storage path of the index artifact (or Puffin bundle)
pub file_path: String,
/// Puffin blob type if packed into an Iceberg Puffin container
/// e.g., "org.apache.iceberg.vector.hnsw-tq8", "org.apache.iceberg.lexical.bm25"
#[serde(default, skip_serializing_if = "Option::is_none")]
pub blob_type: Option<String>,
/// Byte offset within the container file where the index payload begins
#[serde(default, skip_serializing_if = "Option::is_none")]
pub blob_offset: Option<i64>,
/// Length in bytes of the index payload
#[serde(default, skip_serializing_if = "Option::is_none")]
pub blob_length: Option<i64>,
/// Compression algorithm applied to the index blob ("zstd", "lz4", "none")
#[serde(default = "default_compression")]
pub compression: String,
}
fn default_index_format_version() -> u32 { 2 }
fn default_compression() -> String { "zstd".to_string() }
3.2 Verification Protocol on Read PathΒΆ
Before any query engine evaluates an index overlay during scan planning:
Query Planner
β
βΌ
Fetch ManifestEntry βββΊ Read Data File Metadata (checksum, record_count, snapshot)
β
βΌ
Inspect IndexFile βββββΊ Compare:
1. index.source_data_checksum == data_file.file_checksum
2. index.source_record_count == data_file.record_count
3. index.source_snapshot_id <= active_snapshot_id
β
βββββββββββββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββββ
β (Match: 100% Consistent) β (Mismatch: Stale / Modified) β
βΌ βΌ βΌ
Execute Accelerated Scan Degrade to Parquet Scan Trigger Background
(HNSW / BM25 / Roaring) (Pushdown Filter / Brute-Force) Re-Indexing Job
Invalidation & Fallback InvariantsΒΆ
Zero False Positives / Corruption: If
source_data_checksum != data_file.file_checksum, the reader MUST immediately reject the index. Under no circumstances may an index be evaluated against a data file whose content hash differs.Graceful Degradation: When an index is rejected, the query MUST NOT panic or fail; it degrades to a standard DataFusion Parquet filter scan.
Telemetry & Self-Healing: The engine logs an advisory warning with the segment ID and queues an asynchronous background index repair (
recover_indexes_async).
4. Compound Index Bundles (Apache Iceberg Puffin Specification)ΒΆ
To resolve the object explosion problem, BenoStreamDB adopts Apache Iceberg Puffin as the standard compound container format for secondary index artifacts.
4.1 Physical Puffin File LayoutΒΆ
A single .puffin file is written alongside each Parquet data file (or partition group), encapsulating all declared indexes in contiguous byte blobs:
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Puffin File Magic: 0x50 0x55 0x46 0x31 β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β Blob 0: Vector HNSW Graph & TQ8 Codebook β
β - Type: "org.apache.iceberg.vector.hnsw-tq8" β
β - Offset: 4, Length: 2,411,520 bytes β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β Blob 1: BM25 Lexical Postings & Term Frequencies β
β - Type: "org.apache.iceberg.lexical.bm25" β
β - Offset: 2,411,524, Length: 812,040 bytes β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β Blob 2: Roaring Bitmap Scalar Index β
β - Type: "org.apache.iceberg.index.roaring-bitmap" β
β - Offset: 3,223,564, Length: 45,120 bytes β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β Puffin File Footer (JSON Metadata: Blob Offsets, Types, Hashes) β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β Footer Length (4 bytes) + Magic: 0x50 0x55 0x46 0x31 β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
4.2 Object Count & I/O ReductionΒΆ
Dimension |
Legacy Loose Sidecars |
Tightened Puffin Bundles |
Efficiency Gain |
|---|---|---|---|
Objects per Segment (4 indexes) |
4β5 files |
1 file ( |
75%β80% fewer objects |
Total Objects (50k segments) |
~250,000 objects |
50,000 objects |
200,000 fewer objects |
S3 PUT API Cost ($0.005/1k) |
$1.25 per ingestion batch |
$0.25 per ingestion batch |
5x cost reduction |
Range Read Acceleration |
Multiple distinct HTTP connections |
Single pooled connection with HTTP |
Eliminates cold connection overhead |
5. Detached Overlay Catalogs for External Iceberg TablesΒΆ
One of the most compelling applications of the tightened overlay design is accelerating third-party Iceberg tables without write permissions.
5.1 Use Case & ScenarioΒΆ
An enterprise maintains an authoritative 50 TB Iceberg data lake managed by Databricks, Snowflake, AWS EMR, or Google Cloud BigLake.
An AI / Search application requires sub-millisecond vector similarity search (HNSW-TurboQuant) and hybrid BM25 search over these tables.
Constraint: The AI application team cannot modify the upstream Iceberg table, cannot alter table properties, and has read-only S3 IAM permissions on the data bucket.
5.2 Architecture: Detached Overlay NamespaceΒΆ
Upstream Authoritative Lake (Read-Only) BenoStreamDB Acceleration Tier (Read-Write)
ββββββββββββββββββββββββββββββββββββββββ βββββββββββββββββββββββββββββββββββββββββββββ
β s3://enterprise-data/warehouse/ β β s3://ai-team-overlays/indexes/ β
β βββ metadata/ β β βββ detached_catalog.json β
β β βββ v104.metadata.json βββββββΌββSyncβββΌβββΊβ (Tracks upstream snapshot ID) β
β βββ data/ β β βββ overlays/ β
β βββ part-0001.parquet ββββββββΌββScanβββΌβββΊβ βββ part-0001.puffin (Vector/BM25)β
β βββ part-0002.parquet ββββββββΌβββββββββΌβββΊβ βββ part-0002.puffin (Vector/BM25)β
β βββ part-0003.parquet (New) β β β βββ (Pending background build) β
ββββββββββββββββββββββββββββββββββββββββ βββββββββββββββββββββββββββββββββββββββββββββ
5.3 Synchronization ProtocolΒΆ
Catalog Registration: BenoStreamDB mounts the external table via its metadata URI (
s3://enterprise-data/warehouse/metadata/v104.metadata.json) with an external overlay storage prefix (s3://ai-team-overlays/indexes/).Snapshot Pinning: The detached catalog records
upstream_snapshot_id = 104.Out-of-Band Upstream Mutation Handling:
If the upstream team appends
part-0003.parquetand advances tov105:Query requests for
part-0001andpart-0002immediately resolve via the existing.puffinoverlays.Query requests for
part-0003seamlessly execute as DataFusion brute-force scans.An asynchronous worker builds
part-0003.puffininto the detached overlay prefix and updates the detached manifest atomically.
If the upstream team runs compaction (replacing
part-0001withpart-0001-compacted):The cryptographic checksum check (
source_data_checksum) fails forpart-0001.The query engine drops the stale index without returning stale rows, preserving 100% query correctness.
6. Merge-On-Read (MOR) Lifecycle for Secondary IndexesΒΆ
To achieve sub-millisecond management latencies and eliminate cloud storage rate-limiting, BenoStreamDB applies Merge-On-Read (MOR) principles directly to secondary index lifecycles, replacing legacy Copy-On-Write (COW) behaviors.
6.1 drop_index: Instantaneous $O(1)$ Metadata Tombstoning vs. Synchronous Physical DeletionΒΆ
Problem in COW Index DropsΒΆ
In large production tables (e.g., 50,000 segments), dropping a column index under a Copy-On-Write model requires:
Scanning all manifest entries across all snapshot manifests.
Synchronously issuing tens of thousands of cloud storage
DELETEcalls (.centroids,.hnsw.*,.inv.parquet,.doclen, etc.).Risking S3 503 SlowDown rate-limiting, high latency (tens of seconds to minutes), and partial network failures that leave orphaned metadata or inconsistent states.
The MOR Solution: Metadata TombstoningΒΆ
$O(1)$ Manifest Commit: When
table.drop_index(column)is invoked, the engine writes a new atomic manifest snapshot that purges the target index pointers fromManifestEntry.index_filesand clears the columnβs index spec inSchema.fields.Sub-5ms Execution Latency: The API call completes immediately without blocking on cloud storage object deletion calls.
Scan Invariant: Query planning immediately ignores unreferenced or tombstoned indexes. There is zero risk of query race conditions or
NoSuchKeyerrors because readers resolve against the new snapshot manifest.Deferred Asynchronous Vacuum: Physical index files (
.puffin,.hnsw.*,.inv.parquet) are safely unlinked and garbage-collected in the background during periodic table maintenance (table.vacuum()/expire_snapshots()).
6.2 add_index: Instant Schema Activation with Hybrid Merge-On-Read ScansΒΆ
Problem in COW Index BuildsΒΆ
Calling add_index(col, algo) on a table with hundreds of millions of rows historically triggered a blocking full-table backfill across all historical segments, consuming heavy compute and delaying query readiness.
The MOR Solution: Hybrid Index-Scan PlanningΒΆ
Immediate Schema Registration:
add_indexcommits the schema update in milliseconds. Incoming flushes immediately build the index on newly written segments.Hybrid Candidate Evaluation:
For segments that carry the index: The query engine executes fast index traversal (Vector HNSW or Lexical BM25).
For legacy or unindexed segments: The query engine seamlessly falls back to DataFusion Parquet scans (brute-force vector distance or predicate evaluation).
The Query Coordinator merges top-$K$ candidate lists across both paths.
Incremental Background Backfill: A background worker incrementally indexes historical segments over time without locking or blocking query availability.
6.3 Delta Indexing & Positional Deletion MasksΒΆ
When data rows are deleted or updated:
Immutable Base Index: The primary Puffin bundle (
part-0001.puffin) remains read-only.Roaring Delete Mask: Deletions are recorded in a lightweight Roaring Bitmap. During vector nearest-neighbor graph walks or inverted index posting lookups, candidate row IDs are checked against the delete mask in $O(1)$ time.
Delta Ingestion Buffer: Newly appended records accumulate in an in-memory Flat/IVF buffer or small delta-Puffin container.
Compaction Consolidation: When the table runs compaction or maintenance, the base index, delete mask, and delta index are consolidated into a fresh, canonical Puffin container.
6.4 Operational Comparison: Copy-On-Write (COW) vs. Merge-On-Read (MOR)ΒΆ
Dimension |
Copy-On-Write (COW) Indexes |
Merge-On-Read (MOR) Indexes |
Architectural Benefit |
|---|---|---|---|
|
$O(N)$ synchronous S3 deletions (10s β 5 mins) |
$O(1)$ atomic manifest commit (< 5 ms) |
>10,000x faster index drop |
|
50,000β250,000 |
1 manifest |
Zero S3 503 SlowDown throttling |
|
Blocked until all segments backfilled (hours) |
Immediate query availability via hybrid plan |
Zero query downtime |
Data Ingestion Overhead |
Full re-indexing on every append / mutation |
Immutable base + delta buffer / delete mask |
Consistent sub-second write latencies |
Consistency / Isolation |
Subject to partial S3 delete failures |
Snapshot isolation with atomic manifest commits |
ACID guarantees; zero orphaned pointers |
Cleanup Mechanism |
Immediate inline deletion |
Decoupled background garbage collection ( |
Separates control plane from data cleanup |
6.5 API & Garbage Collection Lifecycle ContractΒΆ
// Proposed Index Lifecycle API in Table Trait
pub trait IndexLifecycle {
/// Instant O(1) drop via metadata tombstoning (MOR mode - default)
/// Removes index references from active manifest without blocking on storage I/O
async fn drop_index(&self, column_name: &str) -> Result<()>;
/// Optional synchronous purge (COW mode) for explicit compliance / storage reclamation
async fn drop_index_purge(&self, column_name: &str) -> Result<u64>;
/// Background garbage collector: deletes physical index artifacts unreferenced by any active snapshot
async fn vacuum_indexes(&self, retain_snapshots: usize) -> Result<VacuumReport>;
}
Garbage Collection & Vacuum AlgorithmΒΆ
During table.vacuum_indexes(retain_snapshots):
Enumerate all
index_filesacross all active and retained snapshots ($\le \text{retain_snapshots}$).Form the set of live index file URIs: $\mathcal{U}{\text{live}} = \bigcup{S \in \text{RetainedSnapshots}} \text{IndexURIs}(S)$.
Scan table storage prefix for candidate index artifacts (
.puffin,.hnsw.*,.inv.parquet, etc.).Any physical index artifact $F \notin \mathcal{U}{\text{live}}$ is safe for asynchronous unlinking: $$\text{ArtifactsToPurge} = \mathcal{U}{\text{storage}} \setminus \mathcal{U}_{\text{live}}$$
6.6 Hybrid Query Planner State MachineΒΆ
When a query arrives for column $C$ with index type $T$:
Incoming Query (Vector / Lexical Search on column C)
β
βΌ
Load Active Snapshot Manifest
β
βΌ
Partition Manifest Segments into Disjoint Sets:
βββ S_indexed = { s β Segments | β idx β s.index_files: idx.column == C }
βββ S_unindexed = { s β Segments | s β S_indexed }
β
ββββββββββββββ΄βββββββββββββ
βΌ βΌ
Accelerated Scan Fallback Scan
(Puffin Range Read (DataFusion Parquet
+ HNSW / BM25) Brute-Force Scan)
β β
Top-K Candidates Top-K Candidates
(Score, RowLocation) (Score, RowLocation)
ββββββββββββββ¬βββββββββββββ
βΌ
Global Top-K Reduction
β
βΌ
Return Result Batches
This state machine guarantees that adding an index can never return incomplete resultsβit simply speeds up segments as they are indexed, achieving true progressive index acceleration without downtime.
7. Migration & Compatibility PlanΒΆ
7.1 Phased Implementation RoadmapΒΆ
Phase 1: Metadata Schema Enrichment (v0.12.x) β β Complete:
Updated
IndexFileinsrc/core/manifest/types.rswithsource_data_checksum,source_snapshot_id, andsource_record_count.Maintained backward compatibility with legacy deserialization using
#[serde(default)].
Phase 2: Validation Enforcement in Scan Engine (v0.12.0) β β Complete:
Added strict checksum and record-count validation in
src/core/reader/scan.rsandsrc/core/reader/filter.rs.Rejected mismatched indexes with telemetry logging and automated degradation to Parquet scan.
Implemented
SegmentConfig::is_index_valid()three-check validation (checksum, record count, snapshot ID).
Phase 3: Puffin Compound Bundler & MOR Add/Drop (v0.12.0) β β Complete:
Implemented
PuffinIndexWriterinsrc/core/puffin.rsto emit compound.puffincontainers during segment flushes.Puffin is now the default and only index storage format. Every secondary index β lexical (BM25 + doclen), scalar inverted (Int32/Int64/Float/Date/Bool), vector (HNSW-IVF centroids/graph/mapping), and graph (CSR offsets/edges/dict) β is packed into a single
{segment}.puffinbundle. The legacy loose-sidecar writer path has been removed.Implemented byte-range
PuffinIndexReaderinsrc/core/reader/scan.rsandsrc/core/reader/filter.rs, plus Puffin-aware loaders for vector (HnswIvfIndex::load_from_puffin), graph (MmapCsrGraph::from_bytes), and preload.Implemented instant $O(1)$
drop_indexmetadata pruning with atomic manifest commits and cache invalidation.Implemented hybrid MOR index query planning in
keyword_search_indexandphrase_search_index.Validated with unit tests (
test_puffin_write_read,test_puffin_compound_index_writer_reader) and integration test (test_puffin_hybrid_segment_writer_and_reader).
Phase 4: Detached Overlay Catalog API (v0.13.0+) β β Complete:
Implemented
Table::mount_external_iceberg(table_uri, overlay_storage_uri)insrc/core/table/detached.rs, with the Python bindingTable.mount_external_iceberg(...).Persisted the
DetachedOverlayCatalogsidecar (detached_catalog.json) recordingupstream_snapshot_id,upstream_schema_id, and the last-synced overlay manifest version.Implemented
Table::sync_detached_overlay_async()(incremental snapshot reconciliation) and the background workerTable::start_detached_sync_worker(interval).Validated with unit tests (
catalog_round_trips_through_json,parses_benostream_and_iceberg_metadata_versions) and integration tests (test_mount_detached_overlay_catalog,test_sync_on_non_overlay_table_errors).
7.2 Backward Compatibility GuaranteesΒΆ
Concern |
Guarantee |
|---|---|
Legacy IndexFile (format_version=1) |
Fully deserializable via |
Mixed Puffin + Loose Sidecars |
The scan engine supports both: if |
External Engines |
Puffin sidecars are invisible to Spark/Trino/DuckDB. These engines read raw Parquet data files unmodified. |
Downgrade Path |
Removing |
8. Alternatives ConsideredΒΆ
Alternative |
Pros |
Cons |
Reason for Rejection |
|---|---|---|---|
Embedding Indexes Inside Parquet Files (Native Metadata) |
Single physical file; zero sidecars. |
Requires custom Parquet writer; breaks standard external tools (Spark, DuckDB); cannot index third-party read-only tables. |
Violates 100% open Iceberg compatibility and zero-lock-in philosophy. |
Global Monolithic Index Object |
Exactly 1 object for the entire table. |
Massive re-indexing cost on every append; impossible to scale with streaming writes; high memory footprint during query. |
Incompatible with high-frequency streaming ingestion. |
Loose Sidecars (Current Status Quo) |
Simple 1-file-per-index layout; trivial debugging. |
Exploding object counts ($O(M \times N)$); high S3 API costs; vulnerability to silent drift if unvalidated. |
Unsustainable at >10k segments. |
Tightened Puffin Bundles with MOR Lifecycle (Adopted) |
Reduces object count by 75%+; standard Iceberg format; supports HTTP range requests; strict checksum lineage; instant $O(1)$ add/drop via MOR metadata tombstones. |
Requires hybrid query planning (indexed + unindexed segments). |
Selected as the optimal production architecture. |
9. Observability & TelemetryΒΆ
The tightened index overlay contract exposes structured observability at every decision point in the index lifecycle.
9.1 Index Validation EventsΒΆ
When SegmentConfig::is_index_valid() rejects an index during scan planning, the engine emits structured tracing::warn! events:
Index {file_path} rejected: checksum mismatch (expected {data_checksum}, got {index_checksum})
Index {file_path} rejected: record count mismatch (expected {data_records}, got {index_records})
Index {file_path} rejected: from future snapshot {index_snapshot} (active {active_snapshot})
Each rejection triggers automatic graceful degradation to Parquet brute-force scan. These events should be monitored with alerting thresholds:
Sustained rejection rate > 5%: Indicates upstream compaction activity or index build failures requiring
recover_indexes_async().Future snapshot warnings: May indicate clock skew or concurrent writer races.
9.2 Prometheus MetricsΒΆ
Metric |
Type |
Description |
|---|---|---|
|
Counter |
Total bytes read from storage (Parquet + index artifacts). |
|
Counter |
Cache hits per cache tier ( |
|
Counter |
Cache misses triggering storage reads. |
|
Gauge |
Number of segments resolved via index (vs. brute-force) per query. |
|
Gauge |
Number of segments that fell back to Parquet scan. |
|
Counter |
Physical index files deleted during |
9.3 Cache Invalidation ContractΒΆ
After drop_index, all four in-memory cache tiers are explicitly invalidated for the dropped columnβs artifacts:
// src/core/table/index_config.rs β drop_index
INVERTED_INDEX_CACHE.invalidate(&key).await;
HNSW_IVF_CACHE.invalidate(&key).await;
INDEX_CACHE.invalidate(&key).await;
ANALYZER_META_CACHE.invalidate(&key).await;
This prevents stale cache entries from serving results for dropped indexes during the window between manifest commit and physical file deletion.
10. Security ConsiderationsΒΆ
10.1 Index Integrity via Cryptographic BindingΒΆ
The index_checksum field (SHA-256 of the index artifact) provides tamper detection:
If a malicious actor or bitrot event corrupts a
.puffinor.hnsw.graphfile, the checksum verification fails at read time and the index is rejected.This is a detection mechanism, not a prevention mechanism. Write-path integrity depends on the underlying object storeβs own durability guarantees (S3 CRC32c, GCS MD5, Azure Blob ETag).
10.2 Detached Overlay Security ModelΒΆ
When overlays are deployed against read-only upstream tables (Β§5):
The acceleration tier has no write access to the authoritative data lake.
Index overlays cannot alter, inject, or mask authoritative Parquet data.
If the overlay storage is compromised, the worst-case impact is query performance degradation (indexes rejected β Parquet scan fallback), not data corruption.
10.3 Multi-Tenant IsolationΒΆ
In multi-tenant deployments where multiple teams share the same underlying Iceberg table:
Each tenantβs detached overlay catalog is scoped to its own storage prefix.
Cross-tenant index poisoning is prevented by the
source_data_checksumbinding: an index built against tenant Aβs overlay prefix will be rejected if presented against tenant Bβs data files.
Appendix A: Implementation Cross-Reference MapΒΆ
This appendix maps ADR-001 specification elements to their concrete implementations in the BenoStreamDB codebase.
A.1 Core Data StructuresΒΆ
Specification Element |
Source File |
Symbol |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
A.2 Write Path (Index Construction)ΒΆ
Operation |
Source File |
Function |
|---|---|---|
Inverted index build (tokenize β postings) |
|
|
Vector HNSW-TQ8 index build |
|
|
Puffin bundle emission |
|
|
Puffin writer internals |
|
|
Manifest lineage binding |
|
|
A.3 Read Path (Index Query)ΒΆ
Operation |
Source File |
Function |
|---|---|---|
Lineage validation (3-check gate) |
|
|
BM25 keyword search (byte-range Puffin) |
|
|
Phrase search (positional postings) |
|
|
Scalar Roaring Bitmap filter |
|
|
HNSW vector search |
|
|
A.4 Index Lifecycle ManagementΒΆ
Operation |
Source File |
Function |
|---|---|---|
Add index (schema + backfill) |
|
|
Drop index (MOR tombstone + cleanup) |
|
|
Vacuum (orphan purge) |
|
|
Self-healing index recovery |
|
|
Reindex inverted column (upgrade) |
|
|
Background backfill |
|
|
Mount detached overlay |
|
|
Sync detached overlay |
|
|
Detached sync worker |
|
|
Detached catalog sidecar |
|
|
A.5 Cache TiersΒΆ
Cache |
Stored Type |
Source File |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
A.6 Test CoverageΒΆ
Test |
Source File |
What it validates |
|---|---|---|
Puffin write/read round-trip |
|
|
Puffin compound multi-blob |
|
|
End-to-end Puffin segment + BM25 query |
|
|
Add/drop/re-add lifecycle |
|
|
A.7 Python API SurfaceΒΆ
Python Method |
Rust Binding |
Behavior |
|---|---|---|
|
|
Schema commit + async backfill |
|
|
MOR tombstone + best-effort cleanup |
|
|
Alias for |
|
|
Orphan artifact purge |
|
|
Mount read-only upstream + detached overlay |
|
|
Incremental upstream snapshot sync |
|
|
Background upstream snapshot watcher |
|
|
Detached catalog metadata (or |