Research cross system query dissection - kmosoti/FabricO11y GitHub Wiki
Historical report imported 2026-10-10 from
docs/research/cross-system-query-dissection.md. Original SHA-256:4ba4595200df35e1135aa43e27d82c185ee9096ffa15e5a939829985eba5e258. Measurements and outcomes retain their stated revisions. Links and local environment labels are normalized;$FABRIC_DATAis the mounted experiment drive. See evidence policy.
Status: selected implementation inspection, 2026-10-08; no upstream code or comparative workload executed. Tier 6 research, subordinate to the product contract, query contract and accepted ADRs. This extends the cross-system program with concrete sources; it does not adopt an engine, index or format.
The retained downloads live under
cross-system source evidence (local evidence omitted from stage).
Each successful source directory has a receipt.json, a complete regular-member
inventory and a selected-source.tar.gz with selected member SHA-256 values in
the receipt. The observations below come from reading selected archive members
in memory, without extracting or executing upstream files. Download/readback
verification is distinct from implementation correctness or performance.
The supplementary receipt (local evidence omitted from stage)
records bounded pinned-file retrievals with per-file command, exit, size and
SHA-256. Its selected archive prefixes each member with its source identity.
These supplements extend inspection at the same pins; the failed initial
ClickHouse archive download remains failed.
The useful question is how much verified work a candidate avoids over its actual lifetime, including construction, publication, candidate decoding, exact predicate verification, payload materialization and reclamation. An index can save row decoding while increasing integrity reads, request startup or maintenance costs. An optional filter is an overapproximation; Fabric still requires exact literal substring semantics, stable ordering, full paginated answers and the current snapshot/retention behavior. Raw custody and typed validation remain mandatory.
Dimensions that separate credible trajectories are predicate type, match density, clustering, payload width, query reuse before expiry, candidate representation, physical order and ownership lifetime. A dense negative workload is required even when a sparse positive case is attractive. These are different mechanisms and conditions, not seven engines competing on an unregistered synthetic score.
Pin c12cc2d54bc0ff75bf1b4a836b4c7e593600bc1a;
receipt (local evidence omitted from stage).
In src/indexer/segment_writer.rs, add_document feeds the inverted index,
fast fields and document store separately. Serialization writes those three
representations, then custom plugins; mem_usage sums their estimates. Fast
fields do not eliminate the postings or stored payload cost when all are enabled.
src/postings/block_segment_postings.rs decodes document IDs in compressed
blocks. Requested record options control whether frequency blocks are decoded.
seek first seeks a block, loads it, then seeks within the decoded block;
reset reuses the cursor for another term without reallocating that cursor,
but explicitly does not reset the positions list. A postings hit is an address
candidate, not the original Fabric log or a complete answer envelope.
Deduction: an unscored Fabric candidate path should avoid frequency/position
work unless its actual semantics require them. This does not establish that
Tantivy's ordinary token semantics implement arbitrary case-sensitive UTF-8
substrings. Supplementary src/tokenizer/simple_tokenizer.rs splits on
non-alphanumeric Unicode characters. ngram_tokenizer.rs uses
CodepointFrontiers to enumerate codepoint-boundary slices of configured length;
prefix-only mode stops after the leading edge. This is neither Fabric's existing
byte-trigram filter nor an exact substring decision. A sound adaptation needs
an explicit byte/codepoint mapping, non-prefix mode where required, short-needle
fallback and final exact substring verification. No equivalence test ran.
Pin d140cfc4bbcc37b73354a9d0e4f4b1a44639bb04;
receipt (local evidence omitted from stage).
quickwit/quickwit-indexing/src/actors/merge_executor.rs constructs a stacked
union directory with an output directory, shadow metadata and source directories.
The output uses sequential mmap advice and controlled I/O carrying the actor's
kill switch and progress state. It opens a Tantivy writer with one thread and
a 15,000,000-byte memory budget, applies delete queries when present, commits
them, then merges segment IDs. A single segment with no deletion skips merging.
quickwit/quickwit-indexing/src/split_store/split_store_quota.rs checks both cached
split count and cached bytes before accepting another split. Those counters
describe cache residency, not whole-process memory, mmap residency or the active
merge's input/output overlap. Supplementary
quickwit/quickwit-indexing/src/actors/publisher.rs acquires a publish lock and
sends staged IDs, replaced IDs, a checkpoint delta and a publish token together
in a metastore request. It suggests source truncation only after that request
succeeds. Invalid tokens terminate the pipeline; a dead publish lock prevents
another publication. This caller couples publication ownership and input
progress explicitly. The metastore transaction was not selected, so its atomicity
is not established by this file alone.
Deduction: avoiding redundant consolidation can dominate tuning a merger. A Fabric index prototype must count rebuild/merge work and coexisting old/new files. The observed actor controls and budget do not prove a global bound or give permission to change Fabric checkpoint/reclamation order.
Pin 42d15ef8e0b84f3364cf507b40ebc37487299747;
receipt (local evidence omitted from stage).
src/optimizer/row_group_pruner.cpp recognizes a limited ORDER BY over a table
scan. It rejects intervening additional limit operators and unsupported order
expressions. Intervening filters/aggregation/distinct invalidate its row-limit
bound; table filters specifically retain reordering while invalidating limit
and offset pruning. This is a concrete guard against treating the first physical
groups as enough qualifying rows. The selected file is an optimizer entrypoint,
not inspection of all zonemap execution. Supplementary
src/storage/table/row_group_reorderer.cpp tracks unresolved overlapping bounds:
overlap may guarantee only one additional qualifying tuple, while non-overlap
can admit a whole group's qualifying count. column_data.cpp::CheckZonemap
checks base and update statistics and returns no pruning when their conclusions
differ. Dynamic filters are not permanently marked checked. ZonemapScanEnd
only skips vectors entirely within rejected segments and refuses that skip when
updated rows may pass elsewhere. These safety cuts do not establish that a global
match fraction suffices for early stopping.
src/storage/standard_buffer_manager.cpp obtains an eviction reservation before
constructing a managed small buffer and transfers it to the block owner;
unsuccessful eviction throws an out-of-memory error. This is explicit accounting
for the managed buffer pool. It is not evidence that every process allocation or
file-cache charge belongs to that pool.
Deduction: Fabric's existing source-bound Walk is a relevant baseline. Adding postings before measuring how much candidate work remains could duplicate a mechanism that already avoids rows. Full drains can erase a first-page advantage.
Pin e4872ec1a2d0d040d9f05c370f238cd7f411328e;
receipt (local evidence omitted from stage).
src/index/src/bloom_filter/applier.rs maps candidate row ranges to unique sorted
segment IDs, loads filters and applies AND across predicates with OR within each
predicate's acceptable values. A matching filter produces whole segment ranges,
then merges adjacent ranges and intersects with the original ranges. It does
not return verified matching rows. An empty predicate list returns an empty
result at this API; a caller cannot silently use that as an identity/all-rows rule.
The file also handles a specific legacy missing all-null last segment; that
compatibility branch is not a general corruption fallback contract.
src/index/src/bloom_filter/creator.rs hashes elements, deduplicates current
segment hashes and finalizes on row boundaries. It deliberately inserts hashes
individually rather than reserving for an iterator containing duplicate tokens.
Its memory increments use new distinct hash count times size_of::<u64>();
that is narrower than actual HashSet capacity and allocator overhead.
src/index/src/inverted_index/create/sort/external_sort.rs stores copied values
with Roaring postings over segment indices. Repeated values reuse a posting;
sorting drains the HashMap into a Vec and sorts by value before external write.
account_memory checks a global threshold and an optional local threshold.
Crucially, dump_buffer takes the buffer and subtracts its global/current memory
accounting before sorted(values) and awaited write_all consume the still-live
postings. Observation: the ledger's scope ends before serialization ownership
does. This is not a measured upstream bug; it is a counterexample to inferring
live allocation from a sorter counter or copying its scope into a Fabric limit.
Pin b5a99aea3826ffc499827f2d0934ef80fc3d8b12;
receipt (local evidence omitted from stage).
The inspected tempodb/encoding/vparquet5/block_findtracebyid.go first reads a
trace-ID-sharded Bloom filter. A positive result proceeds to an optional ID index,
then opens Parquet and performs ID lookup. Disabled or missing ID index returns
an unlocated candidate and uses the fallback; malformed/read-failed index returns
an error. Bloom read/parse failures also return errors. Fabric's corrupt optional
index rule requires exact fallback, so these error paths cannot be copied as-is.
The fallback binary-searches row-group bounds using the first TraceID value in each sampled group; the code explicitly relies on ascending TraceID order. It clones sampled ID bytes to detach them from I/O buffers. Fabric spans are ordered by time, identity, sequence and index: this physical premise cannot be assumed for Fabric's current spans table. Search across retained segments must also include every relevant span, not just locate one trace-shaped object.
tempodb/encoding/vparquet5/block_search.go uses dedicated-column schema changes,
skips Parquet Bloom filters/page indexes on its general search open path and
records backend bytes read. Its general Search obtains row groups with a comment
that row-group predicate pushdown is not yet solved there. The ID-lookup mechanism
therefore is not evidence that a general substring/tag query uses the same index.
Supplementary index.go keeps the last ID added and appends it on a row-group
flush; Find binary-searches those group upper bounds, not a posting for every
span. create.go adds each trace ID to the index and Bloom filter alongside
Parquet output. Complete finalizes index boundaries, closes/flushes Parquet,
reads its footer to check magic/size, then hands metadata, Bloom and index to
writeBlockMeta. This reveals coexisting build work, but that helper and backend
durability were not selected. Build CPU, Bloom sizing and whole-path allocation
remain unmeasured; compact ID boundaries require the sorted physical layout.
Pin 0439d576fb3d36acba4444f5a5bc0d4173ecd932;
receipt (local evidence omitted from stage).
roaring/src/bitmap/container.rs stores a u16 container key and selects Array,
Bitmap or Run representations. Its array/bitmap cardinality threshold is 4096.
optimize counts runs and compares serialized representation sizes before
converting; the choice is not merely global bitmap density. Contiguous range
construction can start with a Run. Thus equal cardinalities with different
clustering and high-key spread can have different costs.
roaring/src/bitmap/ops.rs provides intersection cardinality without constructing
the resulting bitmap. Borrowed set operations can allocate a container vector
and clone retained containers, whereas owned operations can reuse ownership.
Cardinality-only queries cannot supply Fabric's actual page rows or identities.
The selected container code delegates lower-level storage operations; full
operation costs are not established by an API signature. Supplementary
roaring/src/bitmap/store/mod.rs dispatches cardinality intersections across
Array/Array, Bitmap/Bitmap, Array/Bitmap, Run/Array, Run/Bitmap and Run/Run.
Different layout pairs therefore have different kernels even at equal total
cardinality. Lower-level implementations and runtime costs remain outside this
selected-file inspection.
Pin 47907285810e618994aa703c31beb0edfc5d2271;
failed initial retrieval receipt (local evidence omitted from stage)
and the supplementary receipt above. The initial full-archive attempt remains
curl exit 63, exit code 1. Bounded supplementary retrieval succeeded for Bloom
.cpp/.h, Set, MinMax and MergeTreeDataSelectExecutor.cpp under
src/Storages/MergeTree/; guessed separate
MergeTreeIndexGranuleBloomFilter.cpp returned HTTP 404/exit 22 and is absent.
This is selected implementation coverage, not a complete repository inventory.
MergeTreeIndexSet.cpp stops further set accumulation after exceeding its
configured maximum distinct rows, serializes a zero-sized sentinel and returns
mayBeTrue for zero/oversized granules. A bounded index losing discriminatory
power deliberately becomes non-pruning instead of dropping evidence. For a
usable set, it checks the hyperrectangle first, then evaluates expressions on
stored values. Those expression costs count even when no rows are excluded.
MergeTreeIndexBloomFilter.cpp obtains distinct dictionary hashes for applicable
LowCardinality columns and accumulates hash sets. Its Boolean-mask condition
returns whether a granule can be true. It declines some indexed map comparisons
against default values because an absent map key can return that same default.
Candidate soundness depends on expression semantics, not just a mathematically
sound Bloom filter. This selected generic Bloom path does not demonstrate
arbitrary literal substring support.
MergeTreeIndexMinMax.cpp handles nullable extrema and relaxes conditions for
columns whose extrema hide NaNs. MergeTreeDataSelectExecutor.cpp orders useful
skip indexes per part by priority, coarser granularity and retained size:
min/max precedes ordinary indexes, with conditional vector-index priority ahead
of it. It caches those orders and records index-analysis statistics. This is a
cost/order heuristic, not a measured optimum or an instruction to add every
index. Accepted granules become merged mark ranges; exact row predicates still
belong downstream. Missing/corrupt-index behavior was not established for every
executor path and must not be assumed to match Fabric's exact fallback.
- Semantic applicability precedes candidate efficiency. Tantivy splits or slices text according to tokenizer rules; ClickHouse declines default-value map cases and relaxes NaN-sensitive bounds. Preserve Fabric's predicate before comparing candidate cost.
- Effective cardinality requires sound bounds and layout. DuckDB distinguishes overlapping bounds and updated statistics; Tempo stores sorted group boundaries. Neither licenses stopping after an estimated number of matches or using time-ordered addresses as ID-ordered bounds.
- An index may intentionally stop helping. ClickHouse saturated sets become non-pruning; Roaring selects different kernels by shape. A broader or high cardinality workload can favor exact scans/ranges.
- Maintenance ownership matters as much as lookup. Quickwit coordinates publication and checkpoint progress at its caller; Tempo builds Bloom/index state alongside Parquet. Greptime's observed accounting cut shows why build/serialization ownership overlap needs direct measurement.
These are hypotheses, not registered runs. The existing Q1 census is diagnostic; its finite first-page/whole-process measurements are not index amortization or independent full-chain latency measurements. Freeze each later workload, state preservation bound, command and decision rule before execution.
| Trajectory | H1 and causal mechanism | H0 / adverse case | Small discriminating test and contract guard |
|---|---|---|---|
| Predicate candidates followed by exact verification | A bounded trigram candidate representation avoids enough payload decode to beat existing Walk across repeated selective literal searches. Tantivy separates IDs from payload; Greptime returns candidate segment ranges. | Dense/common terms, needles shorter than the candidate unit, or required integrity work dominate; a token index misses a substring. | Compare existing filter/Walk with one scratch candidate on identical clustered and shuffled bodies, including punctuation, UTF-8 and broad matches. Count candidate rows, verified matches, false positives, hashed bytes and decode/output CPU. Grade every complete chain with the unchanged oracle; missing/corrupt candidates fall back exactly. |
| Build only where reuse pays | Query lifetime savings can exceed candidate construction and publication cost for repeated narrow queries before expiry. Quickwit merger skip and scoped quotas motivate charging avoided maintenance too. | Quiet/one-shot data, high cardinality or early retention makes the extra representation a net cost. | Include zero, one and repeated query schedules over the same fixture; charge build CPU, temporary/retained bytes, query CPU and overlap peaks separately. Estimate a workload-specific break-even count instead of promoting a universal default. No adaptive policy is implemented by this test. |
| Shape-sensitive candidate sets with explicit ownership | Sorted IDs/ranges outperform general bitmaps for sparse or clustered candidates, while Roaring wins another density/container regime; owned intersections reduce intermediate allocation. | Conversion and container overhead erase gains at small size; borrowed results clone more than expected; sparse wide results pin decoded frames. | Use equal-cardinality candidates with contiguous ranges, shuffled IDs and spread across container keys. Compare sorted IDs, ranges and scratch Roaring build/intersection/iteration including retained backing allocations. Exact row identity, ties and full output remain mandatory; cardinality equality alone is insufficient. |
| Trace lookup without changing primary order | A secondary ID-to-time-ordered-row candidate map reduces exact span lookup cost without changing Fabric's time ordering. Tempo demonstrates an ID-specialized path but relies on a different physical order. | Cross-segment trace scatter, full trace output, index build cost or snapshot mapping dominate; stale addresses omit spans after publication. | Start with isolated scratch ID candidates for a time-ordered fixture, including one trace spread across Batches/Segments and equal-time ties. Compare against current span filters/Walk, charge exact predicate and output work, and grade full chains. Publication, retry identity and retention Gone controls precede any persistent mapping proposal. |
The first three trajectories fit Q3 after Q1 attribution; the fourth depends on O2 snapshot/identity evidence. Do not nominate all four at once. The strongest current rejection is a blanket index or bitmap mandate: sources repeatedly separate predicate applicability, optional structures, physical order and owner scope. The next action is use measured Fabric costs to choose at most the existing bounded prototype allowance, retrieving deeper kernels only where that choice depends on them.
Seven systems have selected implementation observations, including ClickHouse's bounded retry. Its failed full-archive attempt and guessed-path 404 stay recorded. Requested supplementary query files were inspected at unchanged pins; coverage remains local, with unselected lower-level kernels, metastore and backend durability outside the claim. No upstream test, Fabric build, benchmark, documentation validator or speed comparison ran in this inspection. No scratch files or processes were created; selected members were read in memory. Repository documentation is the only new artifact. The coordinator owns further bounded retrieval, source-manifest reconciliation, validation and registration.