* fix(mito2): reject non-flat batches in flat merge instead of panicking
- SortColumns::new becomes fallible try_new: batches missing the
flat-format internal columns (time index, __primary_key, __sequence)
at the fixed trailing positions now yield InvalidRecordBatch instead
of a downcast panic, completing the generic-schema gate that only
covered BatchBuilder output assembly. Document the flat-format input
contract on FlatMergeIterator/FlatMergeReader.
- Clarify why BatchBuilder's schema gate uses >= 3 columns when a real
flat-format schema always has at least 4.
- Add schema-structure tests: empty primary keys (tables without tags),
dictionary-encoded string tag columns with per-source dictionaries,
and graceful rejection of batches without internal columns.
Signed-off-by: Lei, HUANG <mrsatangel@gmail.com>
* test(mito2): scan tables with various schemas through flat merge
Add an engine-level test that writes, flushes and scans regions without
tags (empty primary key) and with multiple string tags (dictionary-encoded
in the flat input schema), so the flat merge reader merges an SST with
the memtable on real schemas instead of hand-built batches.
Signed-off-by: Lei, HUANG <mrsatangel@gmail.com>
* refactor(mito2): use winner_tree dependency
Signed-off-by: Lei, HUANG <ratuthomm@gmail.com>
* Update comments for FlatMergeIterator struct
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
---------
Signed-off-by: Lei, HUANG <mrsatangel@gmail.com>
Signed-off-by: Lei, HUANG <ratuthomm@gmail.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
* feat(mito2): adapt bulk memtable encode bytes threshold to write buffer size
The default encode_bytes_threshold is now max(64MB,
min(global_write_buffer_size / 32, 512MB)) instead of a fixed 64MB, so
it scales with the memtable budget. GREPTIME_BULK_ENCODE_BYTES_THRESHOLD
and the per-region option still override the default.
Signed-off-by: Lei, HUANG <ratuthomm@gmail.com>
* test(mito2): clarify binary units in bulk threshold test
The threshold test uses powers of 1024, so label its values as MiB and GiB instead of decimal MB and GB.
Signed-off-by: Lei, HUANG <ratuthomm@gmail.com>
* refactor(mito2): resolve bulk encode threshold in memtable provider
Signed-off-by: Lei, HUANG <ratuthomm@gmail.com>
---------
Signed-off-by: Lei, HUANG <ratuthomm@gmail.com>
* perf(mito2): optimize flat merge heap and primary-key interleave
Replace the per-row BinaryHeap pop/push cycle in FlatMerge with an
in-place root mutation plus a single sift-down repair on a custom
RootHeap, keeping the cold heap and direct-batch fast path unchanged.
Fallible or awaiting batch transitions move the hot node out of the
heap first, preserving error and cancellation semantics.
Exploit the globally sorted merge output to build the internal
Dictionary<UInt32, Binary> primary-key column with a one-pass ordered
gather: append a Binary value only when the PK changes and reuse the
current key for adjacent equal PKs, bypassing Arrow dictionary masks,
hash interning and key remapping. Non-PK columns still use Arrow
interleave.
Also cache the current primary-key byte range in RowCursor to avoid
repeated dictionary range decoding during comparisons, and add a
setup-free Criterion benchmark with exact output-row assertions.
32-way/1 row-per-series/40-tag improves 955.79ms -> 562.36ms (-41.2%);
0-tag -39.5%, 64 rows/series -79.8%, 8-way -56.1%, single-iterator
control +0.3%.
Signed-off-by: Lei, HUANG <ratuthomm@gmail.com>
* test(mito2): add rows-per-series sweep to flat merge bench
Add 32-way/40-tag shapes for 1, 10, 100, 1000 and 10000 rows per
series, and allow FLAT_MERGE_BENCH_SHAPE to match shape name prefixes
so the whole sweep can run in one invocation.
Signed-off-by: Lei, HUANG <ratuthomm@gmail.com>
* test(mito2): add oracle-based correctness tests for RootHeap
Drive RootHeap and a std BinaryHeap oracle with the same seeded op
sequence (push / pop / mutate-root + repair) and assert peek, len,
best_child and the full drain order after every operation. A second
run with a tiny value range makes duplicates dominate, covering the
equal-key branches of sift_up/sift_down and best_child.
Signed-off-by: Lei, HUANG <ratuthomm@gmail.com>
* perf(mito2): replace hot heap with a tournament tree in flat merge
Replace the hot RootHeap with a fixed-capacity tournament (winner) tree
over per-node slots: every internal node caches the champion of its
subtree, so advancing the winner only replays the ~log2(k) nodes on its
leaf-to-root path with one compare per level, instead of the heap's
two-compares-per-level sift that also re-compares the same node pairs
on every row.
Two fast paths keep dense shapes at O(1) per row:
- champion retention: after mutating the winner in place, skip the
replay entirely when it still beats the runner-up (its path caches
are unchanged by construction);
- a second-best slot cache, invalidated on any structural change, so
the retention check costs a single compare without walking the tree.
The cold heap, hot/cold overlap window, direct-batch fast path and the
remove-before-fallible-fetch batch transition semantics are unchanged.
Vs the RootHeap version: 1rps/32way/40tag -19.7%, 0tag -34.4%,
8way -15.9%, 64rps -30.5%, sweep 10/100/1000/10000rps -29~32%;
vs the original BinaryHeap baseline the main shape is -52.8%.
The single-iterator control is +8% (+50ns one-time construction
allocation, no merge work).
Signed-off-by: Lei, HUANG <ratuthomm@gmail.com>
* fix(mito2): support generic schemas in flat merge
Signed-off-by: Lei, HUANG <ratuthomm@gmail.com>
* fix(mito2): satisfy clippy in flat merge benchmark
Signed-off-by: Lei, HUANG <ratuthomm@gmail.com>
* perf(mito2): cache flat merge primary key index
Compute the internal primary-key column index once when constructing BatchBuilder and reuse it for every output batch. Preserve the column-name gate for generic schemas.
Signed-off-by: Lei, HUANG <ratuthomm@gmail.com>
* test(mito2): benchmark high-fan-in flat merges
Add sparse 64, 128, 256, and 512-way merge shapes while keeping the total input fixed at 3.2 million rows. Compared with the merge-base heap implementation, median time improves by 56.0%, 60.8%, 55.6%, and 56.1%, respectively.
Signed-off-by: Lei, HUANG <ratuthomm@gmail.com>
---------
Signed-off-by: Lei, HUANG <ratuthomm@gmail.com>
Extend `WriteCacheUploadStoreWrapper::wrap` with the `OperationType` of the upload so implementations can apply per-operation policies (e.g. throttling compaction uploads but not flush uploads). Flush and compaction paths forward their existing `SstWriteRequest::op_type`; `put_and_upload_sst` is flush-only and index rebuild uploads are reported as compaction uploads.
Files: `src/mito2/src/cache/write_cache.rs`, `src/mito2/src/sst/index.rs`.
Signed-off-by: Lei, HUANG <ratuthomm@gmail.com>
* feat(mito2): support exact sequence range reads
Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com>
* test(mito2): cover preserve row sequence table alter
Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com>
* fix(mito2): clear preserve_row_sequence marker on copy_region_from
copy_region_from copies source FileMeta into the target region, which has an
independent sequence domain. The physical per-row sequences in the copied
file belong to the source region only; trusting them in the target would let
an exact sequence-range request replay source-domain rows as if they were
target sequences. Clear the preserve_row_sequence marker on copied files so
the target fails closed with SequenceRangeUnsupported until the scan provably
cannot intersect the copied rows.
Add a regression test: copying from a preserve-enabled source into a
preserve-enabled target clears the marker, and an exact (2, 7] request on the
target returns SequenceRangeUnsupported instead of replaying source rows.
Fixes#8865
Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com>
* style: remove redundant doc comments for exact sequence range options
Approved comment-cleanup-only changes for #8865: drop outdated doc
summaries duplicated on the exact_sequence_range wrapper and the
preserve_row_sequence field, drop pure-restatement doc comments on the
SetRegionOption/UnsetRegionOption PreserveRowSequence variants, and
remove the four structural SQL comments from the alter_preserve_row_sequence
case. No behavior changes; .result regenerated by the sqlness runner.
Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com>
* fix(mito2): fail closed exact reads on copied files and extension ranges
Address review feedback on #8865:
- copy_region_from: clear the source-domain FileMeta::sequence along with
the preserve_row_sequence marker. An unmarked file retaining a stale
source-domain max sequence could be silently skipped by
files_allow_exact_sequence_range() as 'proven disjoint' in the target's
independent sequence domain, dropping rows on exact (C, H] reads. With
sequence=None the capability check fails closed (SequenceRangeUnsupported)
until the copied rows are provably disjoint.
- Engine/reader: reject exact sequence-range reads whenever a follower
region has an extension range provider attached. Extension streams are
returned without a row-level sequence filter, so exactness cannot be
proven; treat the capability as missing (fail closed) instead of emitting
out-of-range rows. The reader also fails closed as defense in depth.
Tests: extend copy_region_from regression to assert the copied file's
sequence hint is cleared; mito2 suite 1148/1148 passing.
Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com>
* style(mito2): use doc comments for test function descriptions
Elevate the block comments describing test functions (in scan_test and
copy_region_from_test) to /// doc comments, matching the convention used
elsewhere in the exact sequence range change. No logic change.
Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com>
* refactor(mito2): extract helpers and trim comment noise in exact sequence reads
PR finalization for #8865 (zero behavior change, full mito2 suite green):
- engine: extract validate_sequence_fences and
sequence_range_unsupported_reason, keeping error variants, check order
and reason strings identical; OSS binds the extension blocker to false.
- handle_copy_region: extract remap_copied_file_meta and
file_descriptors_for_meta; rename file_ids -> source_file_ids and
files_to_copy -> new_file_metas.
- compactor: rename max_input_sequence -> known_max_input_sequence,
document the None semantics (empty input vs unknown sequence).
- Remove restating/outdated comments (ScanInput::sequence_range doc
first line, outdated file-pruning note, options test restatements),
compress verbatim comments while keeping why/invariants/contracts.
Verified: cargo check -p mito2 (+ --features enterprise), cargo fmt,
git diff --check, mito2 suite 1148/1148 passing.
Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com>
* fix(mito2): reject foreign-region SSTs in exact sequence reads
Reading an SST whose FileMeta.region_id differs from the scanned region
means the region's sequence domain is broken (manifest corruption or a
repartition/copy path that leaked a source-domain file). Treat this as
an explicit RegionSequenceDomainBroken error instead of silently
ignoring the file's sequence or falling back to a full scan: the region
is unusable for exact sequence-range reads until the foreign lineage is
compacted away or repaired.
- files_allow_exact_sequence_range / exact_sequence_range now return
Result and propagate the error through engine fence validation and
scan construction (StatusCode::Internal, distinct from the
fallback-capable SequenceRangeUnsupported).
- Row-level flat-batch sequence filtering rejects foreign-region files
as defense in depth.
- Engine test asserts the broken-domain error rather than
Unsupported/fallback.
Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com>
* fix(mito2): never trust unmarked SSTs for exact-range disjoint skipping
An unmarked file's FileMeta.sequence may be synthesized by the
region-edit or repartition paths (committed+1 import barrier), not a
physical max of its rows. Treating it as a whole-file disjoint proof
could permanently skip rows that were never incrementally consumed
once the flow checkpoint passes that value.
Exact sequence-range capability now requires every SST in the region to
carry the preserve_row_sequence marker; any unmarked file disables
exactness (fallback), and the (C, H] file-selection skip also only
applies to marked files. Foreign-region files still raise
RegionSequenceDomainBroken as before.
Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com>
* fix(mito2): scope exact sequence-range capability to the time-selected read set
The exact capability check used to walk the entire SstVersion, so a
single unmarked or foreign-region SST anywhere in the region disabled
exact reads or raised RegionSequenceDomainBroken even when the
request's time range could never touch that file.
Both the engine fence and the scan builder now derive the read set with
shared time-pruning + exact-min/sst-min selection and validate
capability only over the files actually selected: a time-pruned file
cannot contribute a row to (C, H], so it cannot affect exactness. The
existing fail-loud semantics are unchanged for every selected file
(foreign region id -> RegionSequenceDomainBroken; unmarked -> exact
unavailable).
Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com>
* fix(mito2): wash untrusted sequences in compaction and restore barrier skipping
Compaction with any non-preserved input now writes a sequence-less
output: the physical __sequence column is zeroed (the flat format
requires the internal columns) and FileMeta.sequence records the
region-local admission barrier committed_sequence + 1 (falling back to
the flushed frontier). preserve_row_sequence stays false.
Exact sequence-range scans interpret an unmarked file's sequence as an
admission barrier: barrier <= C means flow has already consumed the
whole file, so it is skipped at file level; a missing or newer barrier
fails closed. Foreign-region files stay in the selected read set so the
capability fence still raises RegionSequenceDomainBroken.
This closes the recovery loop: after a region repartition, one
time-scoped fallback consumes the migrated rows, then compaction washes
the untrusted per-row sequences away and exact incremental reads resume
via file-level barrier skipping.
Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com>
* chore(mito2): drop restating comments in known_max_input_sequence tests
Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com>
* test(mito2): trim SQLness result EOF whitespace
Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com>
* refactor(mito2): reuse exact scan file selection
Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com>
* test(mito2): strengthen sequence scan coverage
Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com>
* style(mito2): trim ALTER option comments
Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com>
* test(mito2): remove no-op bulk compaction check
Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com>
* fix(mito2): preserve trusted row sequences when reading SSTs
Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com>
* fix(mito2): preserve target sequence domain for imported SSTs
Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com>
* test(mito2): add trailing blank line to SQLness result EOF
Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com>
* chore(mito2): trim exact sequence scan plumbing
Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com>
* refactor(mito2): fold exact SST selection checks
Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com>
* test(mito2): make legacy compaction rewrite deterministic
Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com>
* test(mito2): make PK compaction rewrite deterministic
Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com>
---------
Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com>
* feat: allow widening the time index column's timestamp unit via ALTER TABLE ... MODIFY COLUMN
Previously MODIFY COLUMN rejected the time index column outright. Now the
time index unit can be widened (Second -> Milli -> Micro -> Nano), which is
lossless for data that fits the target unit: historical data in old SSTs is
cast to the new unit on read by the existing schema-compat layer, and
compaction rewrites it lazily. Narrowing and non-timestamp targets remain
rejected; tag columns keep being rejected. Widening is rejected if any
SST's time range would overflow the target unit's i64 range (e.g.
millisecond -> nanosecond beyond year 2262), since the cast would silently
null those values.
Read-path correctness for old-unit SSTs (verified by new engine e2e tests
and sqlness WHERE queries):
- row-group min/max pruning: parquet statistics of a timestamp column are
raw integers in the file's unit; when the region metadata's type differs
(also the case for altered field columns), stats are now interpreted in
the file's type and converted to the expected type before pruning.
Without this, a new-unit predicate silently pruned whole row groups of
old-unit files (wrong results, rows missing).
- SST-level simple filters are skipped for columns whose file type differs
from the expected type; the predicate is applied by the query layer's
residual filter above the region scan. Also drop the stale
"timestamp columns cannot change type" debug_assert.
- retry idempotency: a same-type ModifyColumnType on the time index
validates as a no-op and `need_alter` returns false, so a retried alter
procedure (region already altered before the previous attempt failed)
converges instead of aborting forever.
- add TimeUnit ordering and ConcreteDataType::is_timestamp_unit_widening_to
- relax ModifyColumnType validation in store-api and table metadata
- tests: unit tests in datatypes/store-api/table; mito2 engine e2e tests
(flushed SST + new writes + reopen + retry + overflow + predicate scans,
cross-unit dedup, mixed-unit SSTs, compaction); sqlness cases incl.
partitioned table and WHERE filters over old-unit data
Signed-off-by: Ning Sun <sunning@greptime.com>
* fix: resolve parquet filter issue
* test: provide sqlness tests
* refactor: drop trivial test cases and shorten comments
Review pass over the branch's additions:
- datatypes: keep a representative subset of the widening-matrix asserts
- table: collapse the three single-branch rejection blocks into one loop
- mito2: drop the boundary gt_eq and post-compaction predicate asserts
(covered by the exact-filter regression test and sqlness); drop the
engine-level gt_eq/lt_eq casts (full operator matrix stays in the
cast_timestamp_unit unit tests)
- sqlness: drop a bare full scan already covered by the filter above it
- shorten function doc comments across datatypes/store-api/table/mito2/
recordbatch to the essential semantics
Signed-off-by: Ning Sun <sunning@greptime.com>
* fix: drop physical prefilter for columns whose file type differs
Follow-up to the review feedback on CompatBatch/prune reader/filter
handling for widened time index units.
Between/InList/IsNull predicates are prefiltered by PhysicalFilterContext,
which builds its physical expression against the FILE's schema while the
predicate literals are in the expected (post-alter) unit. Evaluating them
against an old-unit SST raised a cross-unit comparison error (Timestamp(ms)
>= Timestamp(µs)) that failed the whole scan. Physical prefilter predicates
are best-effort pruning hints (the query layer re-applies them above the
scan), so drop the prefilter when the column's file type differs from the
expected type, mirroring the simple-filter strategy.
Verified: Between and a non-rewritten (large) InList on old-unit data no
longer error and filter exactly end-to-end (sqlness), and no matching rows
are lost at the engine level (engine test).
Signed-off-by: Ning Sun <sunning@greptime.com>
* test: add direct unit tests for stats cast and prefilter drop
The two-step stats cast (reinterpret raw Int64 stats in the file's
timestamp type, then rescale to the expected type) and the physical
prefilter drop on file/expected type mismatch were only covered
end-to-end; add localized unit tests so a regression fails at the
exact site:
- stats.rs (previously no tests): RowGroupPruningStats min/max over a
hand-built RowGroupMetaData — passthrough with no expected metadata,
passthrough on same type, and rescale (1000ms -> 1_000_000us, not
1000us) on a widened expected unit
- reader.rs: PhysicalFilterContext::new_opt keeps a Between prefilter
when file and expected types match and drops it on unit mismatch
Signed-off-by: Ning Sun <sunning@greptime.com>
* fix: tolerate mixed time units range cache key coverage check
* docs: flag mixed-unit hazard in the (unwired) series index
The series index stores per-series min/max ts as raw Int64 in the unit
of the region metadata at write time, and the searcher builds its range
predicates from a single per-region metadata. After a time index unit
widen, files of one region would carry mixed units, so a per-file unit
(or an index rebuild on such alters) is required before this index is
wired into scans. Leave notes at both sites.
Signed-off-by: Ning Sun <sunning@greptime.com>
* test: cover mixed-unit compaction for sparse encoding and strict windows
Compaction-path audit follow-up. The compat cast and window math were
already covered for dense regions; add the two remaining e2e scenarios:
- sparse primary key encoding (used by metric-engine physical regions):
widening then compacting mixed-unit files rewrites the old-unit time
index correctly through the sparse compaction compat path
- strict-window manual compaction: each window output trims rows with a
predicate built in the region's new unit against an old-unit file;
every instant must survive exactly once (no loss, no cross-window
duplication), rescaled
Also documents the audit finding that Regular ranged (manual)
compaction never trims rows: TwcsPicker sets output_time_range to None
and the request time range only selects candidate windows.
Signed-off-by: Ning Sun <sunning@greptime.com>
* test: cover time index unit change in FlatCompatBatch directly
The compat layer's rescaling of a widened time index was only verified
end-to-end; add direct unit tests for both paths:
- dense: identical units skip compat entirely; a widened unit rescales
the time index column (1000ms -> 1_000_000us, not reinterpreted) while
other columns pass through and the output schema matches the expected
metadata
- compact sparse (the metric-engine compaction path): same rescaling
Signed-off-by: Ning Sun <sunning@greptime.com>
* refactor: move timestamp unit division into common-time
The exact unit division (UnitQuotient + div_mod_units) is time
semantics, not filter logic; move it next to TimeUnit in common-time
with a compact test covering representable/non-representable values,
negative (floor) instants, and quotient overflow. The ScalarValue
helpers stay in filter.rs since common-time has no datafusion
dependency.
Signed-off-by: Ning Sun <sunning@greptime.com>
* test: compat rescales a widened time index and fills an added column together
The realistic multi-alter sequence (widen at T1, add column at T2,
read a T0 SST) exercises cast and default-fill in the same
compute_index_and_fields pass; assert both in one output batch.
Signed-off-by: Ning Sun <sunning@greptime.com>
* fix: preflight time index widening overflow before any region alters
Address review feedback on the overflow guard:
- preflight: when the frontend operator receives a widening alter on the
time index, run an existence scan (ts outside the target unit's i64
range, LIMIT 1, via the query engine so it covers every region of the
table in both standalone and distributed modes) BEFORE any DDL task is
submitted. A region that fits can no longer commit the new schema
while another region rejects the alter with a non-retryable error.
File and row-group pruning keep the scan cheap when nothing overflows.
The per-region check in mito2 stays as the final guard for data
written after the preflight (the remaining race window); without a
validate-only wire field (region.proto lives in the external
greptime-proto repo) a fully atomic two-phase validate/commit is out
of scope here.
- fast path: cast_timestamp_unit returns the filter unchanged when the
literal is already in the target unit, skipping the div-mod rebuild.
Signed-off-by: Ning Sun <sunning@greptime.com>
* fix: address review comments
* fix: address auto review comments
* fix: remove time index widening overflow preflight
Overflow needs timestamps beyond the target unit's i64 range (~year
2262 for nanoseconds), which real workloads never write, so the two
existence scans before every widening alter are not worth the cost.
Region validation already rejects the alter when an SST's time range
overflows the target unit; it now logs the rejection (with the
offending file) and returns a deterministic client-facing message.
Signed-off-by: Ning Sun <sunning@greptime.com>
* test: make sqlness test stable
* fix: log instead of rejecting time index widening overflow
Overflowing values cast to NULL on read but do not otherwise affect
reads or writes, so the alter is allowed; the region-level check now
only logs (with the offending file) when an SST's time range exceeds
the target unit's i64 range.
Signed-off-by: Ning Sun <sunning@greptime.com>
---------
Signed-off-by: Ning Sun <sunning@greptime.com>
Add `WriteCacheUploadStoreWrapper` in `src/mito2/src/cache/write_cache.rs` and wire it through `src/mito2/src/worker.rs`.
Signed-off-by: Lei, HUANG <ratuthomm@gmail.com>
* refactor(mito2): prioritize file count in TWCS picker
Signed-off-by: Lei, HUANG <ratuthomm@gmail.com>
* test(mito2): migrate TWCS tests to async picker API
Signed-off-by: Lei, HUANG <ratuthomm@gmail.com>
* refactor(mito2): remove legacy reduce_runs and merge_seq_files pickers
Signed-off-by: Lei, HUANG <ratuthomm@gmail.com>
* feat(mito2): balance TWCS picks by file group
Signed-off-by: Lei, HUANG <ratuthomm@gmail.com>
* refactor(mito2): treat multi-SST groups as barriers in TWCS pick_count_first
Previously pick_count_first filtered out multi-SST file groups and could
pick singleton groups across them in one interval. Now multi-SST groups
split the candidates into independent segments, so a single pick never
crosses a multi-SST group.
Signed-off-by: Lei, HUANG <ratuthomm@gmail.com>
* refactor(mito2): remove redundant overlaps_files_left_behind check
The rebase onto main kept both the upstream fix (#8872) and the branch's
selected_overlaps_unselected check for the same deletion-marker problem.
The upstream check is fully subsumed: files_to_merge only differs from
the window files in append mode, where filter_deleted is already false,
and selected_overlaps_unselected treats partially-selected groups as
unselected, making it strictly stronger.
Signed-off-by: Lei, HUANG <ratuthomm@gmail.com>
* perf(mito2): pre-filter by selection time span in selected_overlaps_unselected
A window group outside the overall time span of the selected groups
cannot overlap any of them, so skip the precise overlap check (and the
per-group file id set lookup) for it. In typical TWCS windows the pick
is clustered in one segment, so most unselected groups are rejected in
O(1).
Signed-off-by: Lei, HUANG <ratuthomm@gmail.com>
* refactor(mito2): remove FileGroup abstraction from TWCS picker
Signed-off-by: Lei, HUANG <ratuthomm@gmail.com>
* feat(mito2): drain compactable backlog after successful compaction
Signed-off-by: Lei, HUANG <ratuthomm@gmail.com>
* refactor(mito2): score TWCS candidates by predicted progress
Redefine what a pick candidate is worth: an interval is only eligible if
compacting it makes progress on at least one axis — it reduces the
physical file count given the max_output_file_size split threshold
(predicted output = ceil(input bytes / threshold)), or it resolves at
least one overlap between sorted runs. A pure rewrite that achieves
neither (e.g. 32 large balanced files whose output would split back into
just as many SSTs) is skipped instead of burning I/O.
The candidate metrics are accumulated in a Candidate struct as the
interval expands, and the score ranks by predicted file reduction, then
overlap participants, then smaller input bytes. With no output size
limit the behavior degenerates to the previous count-first rule.
Signed-off-by: Lei, HUANG <ratuthomm@gmail.com>
* fix(mito2): make TWCS input limit configurable
Allow operators to tune the maximum SST inputs through a hidden environment variable while keeping 32 as the validated default.
Signed-off-by: Lei, HUANG <ratuthomm@gmail.com>
* fix(mito2): apply TWCS trigger at window level
Check the total physical SST count before candidate selection so trigger values above the per-task input limit still allow bounded compactions.
Signed-off-by: Lei, HUANG <ratuthomm@gmail.com>
---------
Signed-off-by: Lei, HUANG <ratuthomm@gmail.com>
* feat(mito2): add series index searcher
Signed-off-by: evenyag <realevenyag@gmail.com>
* refactor(mito2): use parquet push decoder for series index
Signed-off-by: evenyag <realevenyag@gmail.com>
* fix(mito2): handle evolved series index schemas
Signed-off-by: evenyag <realevenyag@gmail.com>
---------
Signed-off-by: evenyag <realevenyag@gmail.com>
`TwcsPicker::find_inputs` decides `filter_deleted` from the shape of the whole
time window, but the compaction inputs are only a subset of it: `reduce_runs`
and `merge_seq_files` narrow the selection down and the max input file num limit
narrows it further. When a deletion marker lands in the compacted set while the
file holding the row it masks stays behind, the marker is dropped from the
output and the old row becomes visible again.
Re-check the final selection against the rest of the window and stop filtering
deleted rows whenever something left behind still overlaps the inputs. The check
compares ranges inclusively, so it also covers files that share only a boundary
timestamp: run detection treats those as non-overlapping, which is how a single
timestamp delete file ends up in the same run as the file it deletes rows from.
Signed-off-by: jeremyhi <fengjiachun@gmail.com>
The committed-sequence watermark must never cover rows that are not yet
physically visible. Previously write_memtable() published next_sequence - 1
before bulk parts were installed, so a scan opening a snapshot could bind H
to invisible sequences and permanently miss rows after checkpoint advance.
Publish once, after both ordinary and bulk memtable writes complete, in
the single-region fast path, the multi-region spawned tasks, and WAL
replay; skip publication for contexts whose WAL entry could not be built.
Add a deterministic worker-level race test using a cfg(test) bulk-install
barrier proving the committed sequence stays put until installation.
Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com>
* fix(metric-engine): handle Utf8View tag/label columns without panicking
label_replace (planned as DataFusion regexp_replace) coerces to Utf8View,
so label columns materialize as StringViewArray; build_tag_arrays'
StringArray downcast then panicked ('tag column must be utf8') — e.g. for
OTLP/json2 ingest. TSID computation, sparse-PK encoding and tag
extraction now accept generic ArrayRef tag columns (Utf8/LargeUtf8/
Utf8View/Dictionary) via string_array_value_at_index, and build_tag_arrays
errors instead of panicking on non-string columns. The mito2 time-series
memtable string-field paths are hardened the same way.
Adds label_replace_with_utf8view_labels_does_not_panic (issue #8732).
Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com>
* refactor(metric-engine): add is_string_null_at helper for tag null checks
Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com>
* fix(datatypes): use is_none_or to satisfy clippy unnecessary-map-or
Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com>
* fix: reject oversized string batches before memtable append
Distinguish a full active string builder from a batch that cannot fit an
empty Arrow string builder at all. Scan every string field so a later
intrinsically oversized field cannot be skipped after an earlier field
requests a freeze. Return InvalidBatch instead of reaching Arrow's offset
overflow panic.
Also cover Utf8View tags with nulls through the metric-engine tag, TSID,
and sparse-primary-key path.
Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com>
---------
Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com>
When a bulk insert request carries a stale schema, the worker fills the
missing columns via BulkPart::fill_missing_columns before writing. The
method replaced the batch but kept raw_data (the original Arrow Flight
IPC bytes), while BulkWalEntry::try_from(&BulkPart) prefers raw_data, so
the memtable received the filled batch but the WAL recorded the pre-fill
bytes.
Replaying such an entry restores a batch that misses the filled columns:
- Bulk memtable (flat format): convert_bulk_part fails with
ColumnNotFound; the error is swallowed by the no-op write notifier and
the rows are silently lost after restart.
- Time series memtable: BulkPart::to_mutation builds rows shorter than
the declared schema and the region worker panics with index out of
bounds during replay, hanging the region open.
Fixes:
- fill_missing_columns clears raw_data so the WAL entry is re-encoded
from the filled batch.
- replay_memtable fills missing columns for replayed bulk parts of dense
regions, so entries already written by affected versions replay
correctly.
Signed-off-by: jeremyhi <fengjiachun@gmail.com>
When `add_wal_entry` fails for a region, the worker only sets the error
on the write context, which stays in `region_ctxs`. The region's entry
is not in the batch, so a successful `write_to_wal` returns no last
entry id for it and the success branch panics on
`response.last_entry_ids.get(region_id).unwrap()`, killing the region
worker. When the failed region is the only one in the batch, the batch
is empty and `append_batch` always returns an empty response, so the
panic is guaranteed.
No in-tree log store can fail to build an entry at runtime today (the
provider/log store combination is validated when the region opens), so
this is a latent panic rather than a reachable crash.
Skip contexts already marked as failed when updating next entry ids;
their waiters are already notified with the error. Extract the WAL
phase of `handle_write_requests` into `write_wal` and cover the
failure paths with unit tests.
Signed-off-by: jeremyhi <fengjiachun@gmail.com>
* fix(mito2): fail open when Bloom IN predicate has non-literal or unencodable members
collect_in_list previously filtered out non-literal and encoding-failed IN
members and could build a partial hard-pruning predicate, so scanning with
the Bloom filter enabled could prune rows that actually match the query
(false-negative results).
Now any non-literal member or any encoding failure disables Bloom pruning
for the whole IN expression (fail open), while independent AND
subpredicates such as `col = 42` are still extracted as before. Encoding
errors are logged and ignored, so queries never fail.
Adds builder unit tests covering pure-literal, pure-nonliteral, mixed
literal+null, all-null, mixed-nonliteral-with-AND, and encoding-failure
cases, plus an engine-level witness test asserting Bloom-on and Bloom-off
scans return identical rows for a mixed IN filter.
Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com>
* test(mito2): rename bloom filter tests to semantic names without bug-id prefixes
Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com>
---------
Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com>
* feat: support discarding unflushed region data
Signed-off-by: evenyag <realevenyag@gmail.com>
* fix(mito2): wake stalled writers after discard
Signed-off-by: evenyag <realevenyag@gmail.com>
* refactor(mito2): drop redundant manifest check for discarding unflushed data
Signed-off-by: evenyag <realevenyag@gmail.com>
---------
Signed-off-by: evenyag <realevenyag@gmail.com>
* fix(mito2): fence async index builds by schema generation
Signed-off-by: Dennis Zhuang <killme2008@gmail.com>
* fix(mito2): retry stale index builds after schema changes
Signed-off-by: Dennis Zhuang <killme2008@gmail.com>
---------
Signed-off-by: Dennis Zhuang <killme2008@gmail.com>
* refactor(mito2): split compaction module into scheduler/reader submodules
Extract the compaction scheduler lifecycle (scheduler, status, phases,
execution, SST reservations, pending requests) and its tests out of
compaction.rs into compaction/scheduler.rs and compaction/scheduler_test.rs.
Split the remaining helpers by responsibility:
- estimate_compaction_bytes/refresh_picker_output move to scheduler.rs,
the only call site
- get_expired_ssts moves to picker.rs, shared by the TWCS and window
pickers
- CompactionSstReaderBuilder/time_range_to_predicate/ts_to_lit move to
the new compaction/reader.rs
The root compaction.rs keeps the shared output types and
find_dynamic_options, and re-exports the moved types so existing call
paths stay unchanged. Pure code motion, no behavior change.
Signed-off-by: Lei, HUANG <ratuthomm@gmail.com>
* refactor(mito2): separate compaction scheduling from execution details
Turn compaction/scheduler.rs into a directory module to make the
scheduling flow easier to review:
- scheduler.rs keeps the pure scheduling core: the CompactionScheduler
state machine, scheduling entry points, termination chaining, DDL
coordination and region lifecycle events
- scheduler/planning.rs holds the execution-facing parts: background
planning dispatch, picker invocation, plan acceptance, remote/local
submission and memory estimation
- scheduler/state.rs holds the per-region lifecycle types:
CompactionStatus, ActiveCompaction, CompactionPhase, CompactingFiles,
LocalCompactionState, CompactionExecution and PendingCompaction
Child modules keep access to the scheduler's private methods, so the
split is pure code motion with minimal visibility changes (pub(super)
only where the parent module or tests reach into child items).
No behavior change.
Signed-off-by: Lei, HUANG <ratuthomm@gmail.com>
* refactor(mito2): use absolute scheduler imports
Signed-off-by: Lei, HUANG <ratuthomm@gmail.com>
* docs(mito2): document compaction scheduler modules
Signed-off-by: Lei, HUANG <ratuthomm@gmail.com>
---------
Signed-off-by: Lei, HUANG <ratuthomm@gmail.com>
* fix(mito2): make async index publication conditional
Check the captured SST generation and commit index metadata under the same manifest write lock. Revalidate the committed metadata before applying it to the in-memory version, and clean exact-version artifacts when either publication stage becomes stale.
Add deterministic compaction and overlapping-index tests covering reopen consistency, duplicate rows, cache cleanup, and both file purgers.
Signed-off-by: Dennis Zhuang <killme2008@gmail.com>
* refactor(mito2): centralize manifest update finalization
Share the locked update, lock release, follower check, and hook firing path between regular manifest updates and conditional index publication.
Signed-off-by: Dennis Zhuang <killme2008@gmail.com>
* fix(mito2): retain index build leases across reopen
Signed-off-by: Dennis Zhuang <killme2008@gmail.com>
* fix(mito2): avoid retiring scheduler on index failure
Signed-off-by: Dennis Zhuang <killme2008@gmail.com>
* fix(mito2): handle cross-region index publication
Signed-off-by: Dennis Zhuang <killme2008@gmail.com>
* fix(mito2): use physical region for index paths
Signed-off-by: Dennis Zhuang <killme2008@gmail.com>
* fix(cmd): update noop index builder
Signed-off-by: Dennis Zhuang <killme2008@gmail.com>
* fix(mito2): avoid reusing published index versions
Signed-off-by: Dennis Zhuang <killme2008@gmail.com>
* chore(mito2): log untracked index build stops
Signed-off-by: Dennis Zhuang <killme2008@gmail.com>
* fix(mito2): set compaction time range in index test
Signed-off-by: Dennis Zhuang <killme2008@gmail.com>
---------
Signed-off-by: Dennis Zhuang <killme2008@gmail.com>