* feat(datanode): execute nested MergeScan plans on the datanode
Give a datanode the ability to decode and execute a plan whose MergeScan
carries a nested plan (e.g. an inner MergeScan over a build table), so a
distributed sub-plan pushed to a datanode can itself fan out to other
datanodes. This is the execution/codec capability only; it is not wired
to any user-facing switch and produces no plan by itself.
Codec (src/query/src/query_engine/default_serializer.rs):
- MergeScanAwareSerializer keeps the request's SessionState and the
engine catalog manager, and decodes a MergeScan payload's tables by
(catalog, schema, table) name through the engine catalog rather than
the region-aware request catalog list (which binds every table to the
request's region and breaks on mismatched columns).
Datanode (src/datanode):
- New DatanodeRegionQueryHandler (region_query.rs) serves region queries
for the MergeScan nodes of a received plan, resolving region leaders
via PartitionRuleManager and dispatching to peer datanodes.
- Wire KvBackendCatalogManager + PartitionRuleManager + NodeClients into
the datanode's catalog/cache registry (with_dist_planner stays false:
a datanode never rewrites its own plan).
Bug fixes uncovered while wiring this up:
- cache registry: register the table/table_info/table_name/table_route/
partition_info caches the datanode's catalog manager and partition
rule manager require (previously missing -> panic on lookup).
- default_serializer: decode_sub_plan bridge no longer drives a future
on a current-thread runtime from within another executor (spawn a
dedicated thread + runtime when not on a multi-thread runtime).
Tested by query_engine::default_serializer unit tests: nested MergeScan
round-trip and engine-catalog resolution with mismatched column names.
No user-facing behavior change.
Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com>
* fix(query): keep GreptimeDB function bindings after rebuilding decode state
SessionStateBuilder::new_from_existing collects the function registry
(HashMap) into a Vec and build() re-registers each entry; register_udf
registers aliases before the function's own name and the Vec order is
unstable. Rebuilding the session state to install MergeScanAwareSerializer
after the GreptimeDB functions were registered could therefore rebind a
name/alias (e.g. date_format, an alias of DataFusion's to_char) back to
the built-in function. This path runs for every decoded plan, not only
nested MergeScan ones.
Extract the GreptimeDB function registration into register_greptime_functions
and re-apply it after the final build of both the top-level decode state and
the MergeScan payload state. Add regression tests asserting date_format stays
bound to the GreptimeDB implementation on both paths.
Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com>
* fix(datanode): address review on nested MergeScan capability
Three follow-ups from review of the nested MergeScan execution capability:
1. Layer the datanode caches (P2b). table_cache derives from
table_info_cache + table_name_cache, and partition_info_cache derives
from table_route_cache, but they were all registered in a single flat
CacheRegistry whose invalidate() runs concurrently with no ordering.
A derived cache could be invalidated and refilled from a base cache
that had not been invalidated yet, leaving stale metadata. Build the
base caches and the derived caches as separate layers of a
LayeredCacheRegistry (reusing the existing layering mechanism) so base
caches finish invalidating before derived ones. Both the production
path (cmd) and the test scaffolding now build the same layered
registry; the flat build_datanode_cache_registry is removed.
2. Don't let a nested MergeScan's internal region query compete for the
concurrency permit (P2a). An outer query holds its permit until its
stream is fully consumed, and a nested MergeScan makes the datanode
issue region queries to peer datanodes as execution stages of that
same query. If such an inner stage also took a permit, the outer query
waiting on it would deadlock against its own held permit (or time out
when max_concurrent_queries > 0). Mark inner DN->DN region queries as
internal stages: QueryRequest gains an flag carried over
the wire in the request header's query_context extensions (reserved
key, not injectable via hints), and the region server skips acquiring
a concurrency permit for internal-stage requests on both acquire
sites. Outer queries still acquire normally.
3. Add an end-to-end capability test
(tests-integration/.../nested_merge_scan_capability_test.rs): hand-build
Join(<local probe region>, MergeScan(build regions on both datanodes))
with mismatched column names and duplicate build keys, ship it to a
datanode over a real TCP Flight endpoint, consume the stream fully and
compare the row multiset against the frontend's reference join result,
assert a real cross-datanode DoGet via an RPC counter, and assert an
inner region failure fails the whole query. Also migrate the
with_real_datanode_grpc_addr + DatanodeRpcStats test scaffolding.
Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com>
* fix(query): adapt date_format regression test to the updated DataFusion fork
ScalarUDFImpl's trait bounds changed so its impl can no longer be
downcast via as_any(); assert the function equals the GreptimeDB
implementation instead, which to_char can never satisfy.
Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com>
* test(datanode): cover the internal stage under the concurrency limit
Add an end-to-end regression test for the nested MergeScan capability
under max_concurrent_queries = 1: both datanodes' only permit is held by
the outer queries' streams while their inner MergeScans fetch the build
regions from the same datanodes over real Flight connections. The result
consumption is bounded by a timeout, so a broken link anywhere in the
chain (marking the inner request internal, carrying the marker in the
request header, honoring it at the receiving acquire sites) fails the
test instead of deadlocking.
Also add a minimal with_datanode_options_override hook to the cluster
builder, and fix the test module doc: the outer request is handed to
RegionServer::handle_remote_read in process; only the inner region
queries go over the wire.
Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com>
* test(datanode): cover cold route loads under unrelated invalidations
The table route cache uses InitStrategy::VersionChecked, whose version
counter belongs to the whole CacheContainer rather than to a single
table: any TableId invalidation bumps the shared version, so a cold
load of one table's route retries when an unrelated table is
invalidated concurrently. Document this as a known limitation on the
region query handler, and pin the behavior with an integration test:
run the nested plans while a background task invalidates an unrelated
table id on every datanode, assert the results still match the expected
multiset (retries only add work, they never return a stale route), and
report the observed load amplification via the cache miss counter
(2 loads without invalidations, 8 over three runs with 24 unrelated
invalidations in this run). Also expose the datanode cache registries
on the test cluster so a test can drive the local invalidation path
that the heartbeat handler drives after a metasrv broadcast.
Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com>
* docs(query): trim nested plan decoder commentary
Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com>
* refactor: remove redundant nested scan support plumbing
Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com>
* fix(query): decode nested MergeScan payloads asynchronously
Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com>
* test(query): avoid unnecessary extension name allocation
Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com>
* fix(query): retain extension decoding for manual session states
Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com>
* refactor(datanode): keep nested scan query admission unchanged
Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com>
* fix(datanode): honor query handler factories and trim nested scan fixtures
Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com>
* fix(datanode): retain required cache error import
Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com>
* refactor(query): reuse registered state for nested scan decoding
Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com>
* test(query): verify payload information schema table functions
Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com>
* test(query): retain nested scan runtime and cold-route guards
Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com>
* refactor(query): address nested scan review feedback
Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com>
* fix(session): preserve read preference across region query hops
Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com>
* test(session): verify strict context rejection at Flow ingress
Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com>
* test(query): remove redundant serializer error case
Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com>
---------
Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com>
* feat(function): add mergeable binary average states
Signed-off-by: discord9 <discord9@163.com>
* feat(function): expose avg_calc as an OSS scalar
Signed-off-by: discord9 <discord9@163.com>
* fix(function): borrow invalid AVG scalar argument type
Signed-off-by: discord9 <discord9@163.com>
* test(function): verify public AVG state SQL finalization
Signed-off-by: discord9 <discord9@163.com>
* fix(function): return canonical AVG1 state for empty window frames
DataFusion's plain-aggregate window executor bypasses the accumulator
and calls default_value() directly when a window frame contains no
rows. create_udaf's SimpleAggregateUDF derives default_value from the
return type, which yields SQL NULL for Binary output instead of the
canonical AVG1 empty state, so empty frames and frames over only NULL
inputs became observable differently (IS NULL, direct state saves,
state comparisons).
Register avg_state/avg_merge through a small AggregateUDFImpl (AvgUdaf)
that keeps the existing accumulator and declares the canonical AVG1
empty state as default_value, restoring the documented contract. Add a
unit test asserting default_value equals the empty accumulator's
evaluate() and an sqlness case covering the empty-frame window.
Signed-off-by: discord9 <discord9@outlook.com>
---------
Signed-off-by: discord9 <discord9@163.com>
Signed-off-by: discord9 <discord9@outlook.com>
Co-authored-by: discord9 <discord9@outlook.com>
* feat(mito2): support independent TWCS trigger_file_num for active and inactive windows
Split the single TWCS trigger_file_num into per-window-state thresholds:
the active window keeps the existing trigger (default 4, legacy
compaction.twcs.trigger_file_num stays a compatible alias), while
inactive windows use a new trigger (default 2). Inactive windows
additionally fall back from balanced L0-only/L1-only candidates to a
progress-making unbalanced mixed candidate so historical windows can
converge; the active window retains the row/byte balance guards.
Signed-off-by: Lei, HUANG <ratuthomm@gmail.com>
* fix(mito2): bound inactive TWCS window convergence by rewrite budget
Inactive windows that cannot compact within one level previously either
stayed stuck (a threshold-qualified but unbalanced level returned no
candidate without trying any fallback) or fell back to a mixed merge
with no balance checks at all, which could rewrite a huge compacted file
to absorb tiny fresh files.
Inactive windows now converge progressively: threshold-qualified
balanced picks, sub-threshold balanced single-level picks, a mixed merge
whose total rewrite must fit in the output file budget, and finally an
L0-only merge without balance checks. Windows that qualify for none of
these are left uncompacted, bounding write amplification.
Signed-off-by: Lei, HUANG <ratuthomm@gmail.com>
* test(mito2): cover TWCS window trigger options in alter_table_options sqlness case
Exercise SET/UNSET of compaction.twcs.active_window.trigger_file_num and
compaction.twcs.inactive_window.trigger_file_num end to end, including
that setting the canonical active key removes the legacy
compaction.twcs.trigger_file_num alias from the table options.
Signed-off-by: Lei, HUANG <ratuthomm@gmail.com>
* fix(mito2): derive TWCS active window from the max-sequence file
The active window was determined by the max event-time window among
level-0 files. Between an L0 compaction removing its inputs and the next
flush landing, level 0 is empty, so the active window transiently became
None and every window fell back to the inactive rules - triggering
full-window convergence merges during ongoing ingestion whose outputs
are then superseded by new data.
Flush and compaction outputs both inherit the max input sequence, so the
file with the highest sequence across all levels always tracks the most
recent write. Use its window as the active window, falling back to the
previous L0-based rule when no file carries a sequence (legacy files).
The new helper deliberately computes window keys with the
assign_to_windows convention (truncate to seconds, then align up),
because the result is compared against window keys produced there; the
older ceil-based helper is kept unchanged for the legacy fallback path.
Signed-off-by: Lei, HUANG <ratuthomm@gmail.com>
* feat(mito2): add active-window L1 compaction safety trigger
Signed-off-by: Lei, HUANG <ratuthomm@gmail.com>
* test(compat): cover TWCS active window options
Signed-off-by: Lei, HUANG <ratuthomm@gmail.com>
* fix(mito2): align TWCS window trigger validation
Signed-off-by: Lei, HUANG <ratuthomm@gmail.com>
* fix(mito2): resolve database TWCS trigger aliases
Signed-off-by: Lei, HUANG <ratuthomm@gmail.com>
* fix(mito2): prioritize newer compaction windows
Signed-off-by: Lei, HUANG <ratuthomm@gmail.com>
* fix(mito2): repick serial compaction outputs
Signed-off-by: Lei, HUANG <ratuthomm@gmail.com>
* feat(mito2): configure inactive-window L1 trigger
Signed-off-by: Lei, HUANG <ratuthomm@gmail.com>
* feat(mito2): prioritize TWCS compaction candidates
Signed-off-by: Lei, HUANG <ratuthomm@gmail.com>
* fix(meta): preserve TWCS trigger downgrade compatibility
Signed-off-by: Lei, HUANG <ratuthomm@gmail.com>
* fix(sql): distinguish invalid database option values
Separate database option key and value validation so recognized keys report the invalid value and its constraint. Add parser coverage for invalid, valid, and unknown options.
Signed-off-by: Lei, HUANG <ratuthomm@gmail.com>
* docs(meta): explain TWCS legacy key compatibility
Signed-off-by: Lei, HUANG <ratuthomm@gmail.com>
* refactor(mito2): clarify active window trigger field
Signed-off-by: Lei, HUANG <ratuthomm@gmail.com>
* fix(options): normalize TWCS trigger aliases
Signed-off-by: Lei, HUANG <ratuthomm@gmail.com>
* fix(mito2): ignore ineligible files for active window
Signed-off-by: Lei, HUANG <ratuthomm@gmail.com>
* fix(mito2): mark explicit TWCS options as overrides
Signed-off-by: Lei, HUANG <ratuthomm@gmail.com>
* fix(mito2): normalize zero compaction output size
Signed-off-by: Lei, HUANG <ratuthomm@gmail.com>
* fix(options): validate database TWCS trigger values
Signed-off-by: Lei, HUANG <ratuthomm@gmail.com>
* style(store-api): collapse TWCS alias conflict condition
Signed-off-by: Lei, HUANG <ratuthomm@gmail.com>
---------
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>
* ci: automate compatibility version window
Signed-off-by: discord9 <discord9@163.com>
* ci: address compat version window review feedback
Address all four review comments on the compat version window
automation:
- Keep the PR window to the sliding window only (latest patch of the two
newest stable minor lines). Exact =vX.Y.Z anchors from case.toml are no
longer unioned into from_versions; they are validated by the new
--check-anchors mode and exercised by nightly runs via --nightly-window.
- Add --published-only: the window is computed over stable git tags that
have a published, non-draft GitHub release carrying the sqlness compat
artifacts (greptime-linux-amd64 tar.gz and sha256sum), so failed releases
cannot land in the window.
- Run the updater Python tests plus the window/anchor consistency check in
PR and merge-group CI (new compat-updater-check job in integration.yml).
- Regenerate tests/compatibility/ci.toml to the current sliding window
[v1.0.2, v1.1.4].
Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com>
---------
Signed-off-by: discord9 <discord9@163.com>
Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com>