Commit Graph
1143 Commits
Author SHA1 Message Date
Aum 6f53d57d15 fix: avoid exact scan row counts for SSTs with unknown counts (#9759)
fix: avoid exact scan row counts for legacy SSTs

Treat selected SSTs with unknown row counts as non-exact before checking region partition visibility. Preserve exact statistics for known counts and cover legacy metadata mixed with known-count files.

Fixes GreptimeTeam/greptimedb#9718

Signed-off-by: Aumnertic <hznuzc@gmail.com>
2026-10-20 11:25:25 +00:00
dennis zhuang 977e76d0a4 fix(mito2): apply SST internal column encodings to bulk-encoded parts (#9735)
* fix(mito2): apply SST internal column encodings to bulk-encoded parts

Bulk memtable parts are encoded to parquet and flushed as SSTs as-is, but the
bulk encoder did not apply the time index / __sequence DELTA_BINARY_PACKED
encoding (with dictionary disabled) and the uncompressed op_type setting that
the regular SST writer uses. Share these settings through one helper.

Signed-off-by: Dennis Zhuang <killme2008@gmail.com>

* chore(mito2): drop the answered bulk encoder compression TODO

Signed-off-by: Dennis Zhuang <killme2008@gmail.com>

---------

Signed-off-by: Dennis Zhuang <killme2008@gmail.com>
2026-10-20 07:46:56 +00:00
Yingwen 3e2751d71d feat: improve SQL analyze scan diagnostics (#9565)
* feat: improve SQL analyze scan diagnostics

Signed-off-by: evenyag <realevenyag@gmail.com>

* test: redact dynamic filters in sqlness results

Signed-off-by: evenyag <realevenyag@gmail.com>

* test: stabilize analyze snapshots for merge queue

Signed-off-by: evenyag <realevenyag@gmail.com>

* test: remove obsolete scan_details assertion

Signed-off-by: evenyag <realevenyag@gmail.com>

* test: refresh explain snapshots after partition filter changes

Signed-off-by: evenyag <realevenyag@gmail.com>

---------

Signed-off-by: evenyag <realevenyag@gmail.com>
2026-10-20 05:06:32 +00:00
dennis zhuang 4645ab9ed7 perf(mito2): gather last rows with one take per input batch (#9737)
* perf(mito2): gather last rows with one take per input batch

FlatLastTimestampSelector buffered a single-row slice per series and
concatenated up to 8192 of them. Concatenating slices with dictionary columns
merges the dictionaries once per slice, which made high-cardinality instant
PromQL queries using the LastRow scan path (#9034) slower. Select the rows of
series that end inside a batch with one take instead.

Signed-off-by: Dennis Zhuang <killme2008@gmail.com>

* test(mito2): cover duplicate last timestamps in the take path

Signed-off-by: Dennis Zhuang <killme2008@gmail.com>

* fix(mito2): group last rows by primary key bytes

Flat batches may assign different dictionary keys to the same primary key, for
example when binary keys are wrapped with identity keys. Merge adjacent ranges
with equal key bytes before selecting last rows.

Signed-off-by: Dennis Zhuang <killme2008@gmail.com>

---------

Signed-off-by: Dennis Zhuang <killme2008@gmail.com>
2026-10-20 03:41:18 +00:00
jeremyhi 93c3b116da fix(mito2): re-check a filled write as sent after a schema change (#9701)
* fix(mito2): re-check a filled write as sent after a schema change

The engine fills the columns a write leaves out with their defaults when it
accepts the write, appending them to the rows without adding them to
`name_to_index` and `has_null`. If the region schema changes before the
worker handles the write, the worker checks the request again and
`check_schema` looks up a filled column in `name_to_index`, which panics and
stops the region worker. If the filled column was dropped, the write fails
with an unknown column that the client never sent.

Remove the filled columns before checking, so the request is checked as sent
and filled again for the current schema. Nothing is filled yet when the
engine checks the request, so that check does no extra work.

Signed-off-by: jeremyhi <fengjiachun@gmail.com>

* test(mito2): rename put_request helper to build_put_request

Signed-off-by: jeremyhi <fengjiachun@gmail.com>

---------

Signed-off-by: jeremyhi <fengjiachun@gmail.com>
2026-10-20 02:30:10 +00:00
jeremyhi 7397ab6b23 perf(datanode): send the mito puts of one request to each worker at once (#9692)
* perf(datanode): send the mito puts of one request to each worker at once

The region server handled the puts of a request to several regions as
separate requests. A Mito worker woke on the first of them and wrote it
alone; in the durable object store WAL mode its append then blocked the
worker until the next seal, so the rest of the request went into the next
object, and clients writing a partitioned table locked into one phase
that wastes a tick.

When every put of a request goes to a ready mito region, the region server
now hands them to `MitoEngine::put_regions_batch`, which submits the puts
of each worker in one message, so the worker writes its regions of the
request in one batch and one WAL append. Requests to one region and to
other engines are handled as before.

Signed-off-by: jeremyhi <jiachun_feng@proton.me>

* perf(datanode): await each region of a batched mito put on its own task

The batched path waited for the regions of a request in order and sent the
worker messages one after another, so an error or a full worker queue of
one region held back the others, and its metrics counted the request as a
whole.

`MitoEngine::put_regions_batch` now sends the worker messages concurrently
on their own task and returns each region's result. The region server
awaits and accounts every region on its own ingest task, as the per-region
path does: the first error returns at once, and each region records its
latency, changed rows or failure and is named in its error. A request with
several puts to one region keeps the per-region path.

Signed-off-by: jeremyhi <jiachun_feng@proton.me>

* perf(datanode): spawn the accounting of batched mito puts from the batch task

The request spawned the per-region accounting tasks only after the batch
task returned, so a request dropped in between left accepted writes
unaccounted. The batch task now spawns them and the request only joins
their handles.

Also state in the architecture invariants that the region server's batched
puts to the metric and mito engines are the one request dispatch that
does not go through the RegionEngine trait.

Signed-off-by: jeremyhi <jiachun_feng@proton.me>

* perf(datanode): batch mito puts through the RegionEngine trait

Add `RegionEngine::handle_batch_put_requests`, which returns each region's
response of the puts of one request. The default handles each put with
`handle_request`, so other engines keep their behaviour; Mito overrides it
with the one message per worker submission. The region server calls the
trait method instead of downcasting to `MitoEngine`, and the architecture
invariants no longer need an exception.

Signed-off-by: jeremyhi <jiachun_feng@proton.me>

* perf(datanode): trace each region of a batched mito put

Attach the per-region `RegionServer::handle_region_request` span of the
per-region path to each region's task of a batched mito put, so the
region ids stay in distributed traces.

Signed-off-by: jeremyhi <jiachun_feng@proton.me>

* perf(datanode): let a batch put response answer for several regions

`RegionEngine::handle_batch_put_requests` now returns each response with
the regions it answers for. The default and Mito answer each region on
its own; an engine that writes several puts together, such as the metric
engine's batched puts, can answer them with one response. The region
server accounts a response for each of its regions.

Signed-off-by: jeremyhi <jiachun_feng@proton.me>

* perf(datanode): batch any request with several mito puts

Take the batch path for a request with at least two puts to ready mito
regions, also when they all target one region: a request of many puts to
one region otherwise still sends one worker message per put. Each put
keeps its own response, affected rows and error.

Signed-off-by: jeremyhi <jiachun_feng@proton.me>

* perf(datanode): account batched mito puts in one task

Await the responses of a batched mito put in the batch task with a
FuturesUnordered instead of one spawned task per put. The task accounts
each response as it completes, hands the first error back to the caller
at once and keeps accounting the rest, so a request of many puts no
longer spawns a task per put.

Signed-off-by: jeremyhi <jiachun_feng@proton.me>

* refactor(mito2): name the batched worker message BatchWrite

Signed-off-by: jeremyhi <jiachun_feng@proton.me>

* refactor(mito2): submit batched writes on the ingest runtime

Grouping and submitting the writes of a request is part of ingestion, so
the task runs on the ingest runtime and takes the write task class when
the workload scheduler is enabled.

Signed-off-by: jeremyhi <jiachun_feng@proton.me>

* refactor(datanode): share ingest result accounting across put paths

The per-region path, the metric batch path and the mito batch path each
recorded the changed rows or the failures of an ingest response. They now
call one helper, so a metric or label change happens in one place.

A batch put response lists the region of each put it answers for, so the
failure count stays per put when two puts go to one region; the trait
documentation now says so.

Signed-off-by: jeremyhi <jiachun_feng@proton.me>

* fix(mito2): give batched puts of a stopped worker the worker's error

A batched write message that could not reach a stopped worker was
dropped, so its puts failed with "Failed to recv result" while a put
submitted on its own fails with "Worker <id> is stopped". The worker now
returns the message it could not take, and each of its puts receives the
WorkerStopped error.

Signed-off-by: jeremyhi <jiachun_feng@proton.me>

* refactor(datanode): look up the batch latency histogram once

The label of the request latency histogram is the same for every put of
a batch, so the batch task looks it up once instead of per observation.

Signed-off-by: jeremyhi <jiachun_feng@proton.me>

---------

Signed-off-by: jeremyhi <jiachun_feng@proton.me>
2026-10-19 14:57:15 +00:00
dennis zhuang e0032534d2 fix(mito2): build indexes for encoded bulk parts flushed in sync mode (#9722)
* fix(mito2): build indexes for encoded bulk parts flushed in sync mode

Signed-off-by: Dennis Zhuang <killme2008@gmail.com>

* fix(mito2): skip reading the SST when an index build has nothing to create

Signed-off-by: Dennis Zhuang <killme2008@gmail.com>

---------

Signed-off-by: Dennis Zhuang <killme2008@gmail.com>
2026-10-19 13:35:07 +00:00
jeremyhi 660c4efe1f fix(mito2): fence writes before a graceful downgrade (#9716)
* test(mito2): cover graceful role changes racing with writes

A graceful role change flips the region state in a background task while
the worker keeps handling writes. Add bulk and row write tests that check a
write handled after the role change request is either rejected or covered
by the returned last_entry_id.

Add tests for DowngradingLeader while an ALTER keeps the region in
Altering, alone and in the downgrade sequence the datanode runs
(DowngradingLeader, Flush, Follower) with a migration candidate on its own
local WAL. Add a test that the downgrade sequence covers a write in flight
when a lease renewal sets Downgrading.

Signed-off-by: jeremyhi <fengjiachun@gmail.com>

* fix(mito2): fence writes before a graceful downgrade

set_region_role_state_gracefully hands the role change to a background task
that waits for the manifest lock. The worker keeps handling writes until the
task flips the state, so a write could be acknowledged with an entry id above
the returned last_entry_id. Set Downgrading in the worker before spawning the
task for Follower and DowngradingLeader requests, as lease renewal already
does, so every write the worker handles afterwards is rejected.

DowngradingLeader also did nothing and returned success when the region was
in another leader state such as Altering. The region became writable again
when the operation finished, and the datanode then flushed and turned it
into a follower in one step. Return a retryable error instead, so metasrv
retries the downgrade.

Signed-off-by: jeremyhi <fengjiachun@gmail.com>

* test(mito2): assert the retryable downgrade error during ALTER

Check that DowngradingLeader during an ALTER returns a retryable error, and
release the paused ALTER on that path. The local WAL test now retries the
downgrade after the ALTER, as metasrv does.

Signed-off-by: jeremyhi <fengjiachun@gmail.com>

* test(mito2): write before the retried downgrade in the local WAL test

Rows written before an ALTER were already flushed, so the candidate check
did not depend on the downgrade's final flush. Write rows after the ALTER
and before the downgrade is retried, which only that flush can carry to the
candidate.

Signed-off-by: jeremyhi <fengjiachun@gmail.com>

---------

Signed-off-by: jeremyhi <fengjiachun@gmail.com>
2026-10-19 10:36:35 +00:00
Yingwen b3dc5674e9 feat(mito2)!: account SST decoding against shared scan memory limits (#9101)
* feat(mito2): add shared estimated SST scan memory budget

Signed-off-by: evenyag <realevenyag@gmail.com>

* feat: unify scan memory limits with configurable accounting modes

Signed-off-by: evenyag <realevenyag@gmail.com>

* refactor(mito2): reuse query runtime memory pool for series merges

Signed-off-by: evenyag <realevenyag@gmail.com>

* test(mito2): wait for cancelled scan reservations before exhaustion check

Signed-off-by: evenyag <realevenyag@gmail.com>

* docs(mito2): explain parallel scan cancellation cleanup

Signed-off-by: evenyag <realevenyag@gmail.com>

* fix(mito2): deprecate waiting for scan memory quota

Signed-off-by: evenyag <realevenyag@gmail.com>

* refactor(recordbatch): remove scan memory wait handling

Signed-off-by: evenyag <realevenyag@gmail.com>

* fix(table): initialize memory pool in scan test fixtures

Signed-off-by: evenyag <realevenyag@gmail.com>

---------

Signed-off-by: evenyag <realevenyag@gmail.com>
2026-10-19 10:09:30 +00:00
Lei, HUANG 52554db52f perf(mito2): keep short series unsplit in compaction merges (#9706)
* perf(mito2): keep short series unsplit in compaction merges

Compaction writes every merged batch to the output SST. Splitting short
series before the merge made it emit one small batch per series and
input file, which the SST writer and indexer then handled one by one.
Compaction now splits only when series are long enough to fill most of
a batch.

With 16 L0 files of 10k series x 30 rows, one output went from 160,560
merged batches to 592, and merging plus writing took 1.66s instead of
2.67s.

Signed-off-by: Lei, HUANG <ratuthomm@gmail.com>

* test(mito2): benchmark compacting L0 files into one output

The benchmark writes 16 L0 files through the engine, each holding every
series over its own time span as consecutive flushes do, and times a
strict window compaction that merges them into one output. Every
iteration compacts a fresh copy of the same files.

With 10k series of 30 rows per file, keeping short series unsplit in
compaction merges cut the time from 830ms to 520ms. With 60 series of
5,000 rows per file, which compaction still splits, both took 266ms.

Signed-off-by: Lei, HUANG <ratuthomm@gmail.com>

---------

Signed-off-by: Lei, HUANG <ratuthomm@gmail.com>
2026-10-19 08:44:15 +00:00
jeremyhi 791f429246 fix(mito2): validate bulk insert batches against the region schema (#9698)
* fix(mito2): validate bulk insert batches against the region schema

Bulk inserts were only checked for the time index column, unlike row
writes, which WriteRequest::check_schema checks against the region
schema. A batch with columns of another type, nulls in the time index or
in non-nullable columns, duplicate or unknown columns reached the region
worker: a time index that is not a timestamp or holds only nulls made
the worker fail, and other mismatches failed after the WAL append or
were stored as written, depending on the SST format.

Move the column checks of check_schema into a core shared by row writes
and bulk inserts, and check bulk insert batches at the same points as
rows: when the engine builds the worker request, and again in the worker
if the region schema changed since. check_schema keeps its behavior.
Bulk insert columns must have the column's Arrow type, except that tags
may use any string encoding, as the memtables already convert them.
Sparse batches must carry one encoded primary key column of binary
without nulls. A batch that misses columns gets them filled by the
worker even if it claims to match the current schema.

The metric engine checks logical bulk inserts against the logical region
before rewriting them into the physical region, as the rewrite copies
columns without checking them and would encode a duplicated tag into the
primary key twice.

Signed-off-by: jeremyhi <fengjiachun@gmail.com>

* fix(mito2): report an error when a write is interrupted by a panic

WriteNotify answers its waiter when it is dropped and reported success
unless an error had been set, also when it was dropped while unwinding
from a panic in the middle of the write. Report an error in that case.

Signed-off-by: jeremyhi <fengjiachun@gmail.com>

* fix(mito2): tolerate a poisoned series lock when dropping a series map

SeriesMap::drop reads every series to update the memtable metrics and
unwrapped the lock, so dropping the map while unwinding from a panic
that poisoned a series lock panicked again. Read through the poisoned
lock instead.

Signed-off-by: jeremyhi <fengjiachun@gmail.com>

---------

Signed-off-by: jeremyhi <fengjiachun@gmail.com>
2026-10-19 07:34:50 +00:00
dennis zhuang 672e94f61d feat: add column encoding option and use it for trace span ids (#9629)
* feat: add column encoding option and set it for trace span ids

COMPRESSION WITH accepts encoding = 'plain' | 'delta_binary_packed'.
Either disables the dictionary for the column; delta_binary_packed is
limited to integer and timestamp columns.

OTLP trace v1/v2 tables now create span_id and parent_span_id with plain
encoding and 64KiB pages, and timestamp_end with delta_binary_packed.

Signed-off-by: Dennis Zhuang <killme2008@gmail.com>

* test: add compatibility cases for column encoding options

Signed-off-by: Dennis Zhuang <killme2008@gmail.com>

* test: move column encoding downgrade checks into downgrade_compatibility

Signed-off-by: Dennis Zhuang <killme2008@gmail.com>

---------

Signed-off-by: Dennis Zhuang <killme2008@gmail.com>
2026-10-16 09:40:54 +00:00
jeremyhi 88bdab0f66 fix(mito2): keep processing bulk requests after one fails to encode (#9638)
* fix(mito2): keep processing bulk requests after one fails to encode

When `push_bulk` fails to build the WAL entry of a bulk request, it answers
that request with the error. `process_bulk_requests` then returned from the
loop, so the remaining requests of the drained batch were dropped without an
answer and their callers only saw a receive error. Skip the failed request
and continue with the rest of the batch instead.

Signed-off-by: jeremyhi <fengjiachun@gmail.com>

* test(mito2): keep bulk install barriers independent per target

The bulk install test barrier was a single process-wide slot, so tests
that arm it while running in the same process replaced each other's
barrier. Keep one barrier per armed target instead.

Signed-off-by: jeremyhi <fengjiachun@gmail.com>

---------

Signed-off-by: jeremyhi <fengjiachun@gmail.com>
2026-10-16 09:13:57 +00:00
Lei, HUANG 9089ea7a4b fix(mito): avoid pinning region version when rebuilding indexes (#9597)
refactor(mito): avoid pinning region version when rebuilding indexes

handle_rebuild_index kept the whole region version alive across the
manifest read and schedule_build awaits, which pins memtables and the
SST version even though index build tasks only need the region metadata,
index options and the candidate SST file handles.

Extract those inputs into a small IndexBuildSnapshot and drop the version
before awaiting.

Signed-off-by: Lei, HUANG <ratuthomm@gmail.com>
2026-10-16 07:50:40 +00:00
jeremyhi deae75ffee fix(log-store): refuse to open object store WAL regions whose history is missing (#9561)
* fix(log-store): refuse to open object store WAL regions whose history is missing

A region persists only its WAL prefix, not the object store that holds it.
After `[wal].storage_provider`, or the bucket or root of the provider it
names, changes, or after the objects under the prefix are deleted, every
region reopened against a prefix without its history, replayed nothing and
silently lost its acknowledged writes that were not flushed.

Add `LogStore::check_region_history` and `LogStore::start_region_history`,
whose defaults accept and do nothing, so Raft Engine and Kafka are
unchanged. The object store WAL reports the history of a region as missing
when recovery found no object under the prefix, or when the region's
watermark names an object above the newest object holding entries that
recovery indexed, unless the catalog holds the region's entries through the
watermark or the region's history starts in the store. Both rest on
invariants of the prefix: an opened prefix is never empty, and a persisted
watermark names an object at or below the newest object holding entries,
which collection never deletes. The segment of release records counts as
entries, since an object holding only release records can be the one
collection keeps; start objects hold no segment and do not move the bound.

When a store that found its prefix empty refuses a region, the actor gives
up its start object, so the next start refuses the region again. It agrees
only while nothing was handed an id, no batch, append, release or create
waits, no failed release is kept, no other object was listed, the catalog
holds only the start object and no region's history started in the store,
deciding under the lock region registration takes. It then records a
terminal error, deletes the object as one of its tracked deletes, which
`stop` waits for, and answers every refusal once that delete settled.

Mito runs the check before it replays an existing region on open, on
catch-up and when a create finds the region present, with the larger of
the flushed and truncated entry ids, and records the history of a region
it creates; regions that skip the WAL and noop regions are not checked. A
missing history fails the open with an error naming the region, the
prefix, the configured storage and how to move the WAL by copying its
objects. The new `[wal].accept_missing_history` option, off by default,
opens such a region without its unflushed writes instead, with a warning
and the counter
`greptime_logstore_object_store_wal_missing_history_accepted_total`.

Signed-off-by: jeremyhi <fengjiachun@gmail.com>

* docs(rfc): describe the object store WAL history check

Describe the open-time history check, the prefix invariants it relies on,
how release records bound the history, the start object withdrawal of a
refused open and the window that remains, the accept_missing_history
option and how to move a WAL by copying its objects, and update the
metrics, Mito integration, evidence, compatibility and cluster alignment
sections.

Signed-off-by: jeremyhi <fengjiachun@gmail.com>

* fix(log-store): unindex the start object a refused open withdrew

A withdrawal that deleted the start object left it in the catalog and in the indexed object and byte gauges for the rest of the process. Remove it from both once the delete succeeded, and keep it when the delete failed.

Signed-off-by: jeremyhi <fengjiachun@gmail.com>

* docs(rfc): separate the withdrawal windows of start objects and entries

An append or a release admitted before a refusal leaves an object holding entries, which may have collected the start object, so the next start also passes watermarks at or below that object.

Signed-off-by: jeremyhi <fengjiachun@gmail.com>

* fix(log-store): keep a successor's chain complete across a late withdrawal

A start object withdrawn after another instance recovered it was linked by that instance's start object, which carried reclaim boundary 0, so once the delete landed the next open found no complete chain. A start object now reclaims every object below it when the recovered chain holds no segment, so such a delete stays below its boundary.

Signed-off-by: jeremyhi <fengjiachun@gmail.com>

* fix(mito2): record a created region's WAL history before its manifest

A create recorded the region's WAL history after it persisted the manifest, so a withdrawal in between failed the create but left a manifest that the next start refused as a region without its history. Record the history first, so such a create fails before it writes anything.

Signed-off-by: jeremyhi <fengjiachun@gmail.com>

* docs(rfc): describe the start boundary of a chain without segments and the create order

Signed-off-by: jeremyhi <fengjiachun@gmail.com>

---------

Signed-off-by: jeremyhi <fengjiachun@gmail.com>
2026-10-15 12:38:01 +00:00
Yingwen ce66d8d4ac fix: avoid pinning memtables during series-index reconciliation (#9588)
Signed-off-by: evenyag <realevenyag@gmail.com>
2026-10-15 03:51:05 +00:00
jeremyhi 60655a933b fix(log-store): persist object store WAL region releases (#9579)
* fix(log-store): persist object store WAL region releases

A region's release lived only in memory: a restart after DROP TABLE and
before the next object never re-established the dropped region's
watermark, so its segments counted as live and pinned garbage collection
of the whole prefix.

A release now writes a release record (region id and the entry id the
region is released through) into an object sealed at once, and returns
once that object is durable and indexed, in both acknowledgement modes.
Recovery seeds the obsolete watermarks from the release records of the
indexed objects before it computes any reclaim boundary. obsolete_all
releases in full; the new LogStore::release_region, a no-op by default,
releases a dropped region through the largest entry id the store handed
out for it, and Mito calls it after obsolete when it drops a region.

Objects with release records use format version 2, the version 1 layout
with the records at the end of the footer; every other object is still
written as version 1. A counter of release records made durable is added.

Signed-off-by: jeremyhi <fengjiachun@gmail.com>

* docs(rfc): describe persisted object store WAL region releases

Signed-off-by: jeremyhi <fengjiachun@gmail.com>

* fix(log-store): carry a failed region release into the next object

A release whose batch failed left its record only in an orphan object,
so a drop that failed there could still leave the region unreleased
after a restart. The store now keeps the release records of failed
batches and seals them with its next batch, on a timer tick when no
entry is admitted, so the release becomes durable once a create
succeeds again.

The version 1 and version 2 object fixtures are frozen under
tests/compatibility/wal/object_store and loaded by the format tests,
and the RFC no longer says that state is persisted only in headers.

Signed-off-by: jeremyhi <fengjiachun@gmail.com>

* fix(mito2): complete a region drop whose WAL release fails

A drop request is not sent again once the region was removed, so a drop
whose WAL release failed used to leave the region half dropped. The
drop now completes, cleaning up the region's runtime state and
scheduling its files for deletion, and reports the release failure.

In the durable mode the object store WAL now creates one more object at
stop with the release records of failed and dropped batches, so a
graceful stop does not lose a release that failed. The release record
counter is described as counting the records of indexed objects.

Signed-off-by: jeremyhi <fengjiachun@gmail.com>

* docs(log-store): describe the stop-time release create of the WAL actor

Signed-off-by: jeremyhi <fengjiachun@gmail.com>

* docs(rfc): state the accepted window of unreleased dropped regions

Signed-off-by: jeremyhi <fengjiachun@gmail.com>

* docs(log-store): describe release waiters in the WAL actor's outcome table

Signed-off-by: jeremyhi <fengjiachun@gmail.com>

* fix(log-store): fail region releases while the object store WAL is failing

A failing store keeps its backlog and rejects appends until a create
succeeds, so a release would otherwise wait for an upload that is not
coming. A release that arrives in that state now fails at once with the
retryable error, and one whose batch is still pending when the store
enters it fails as well. Neither poisons the store: the record of the
first waits for the next sealed batch, like that of a batch that failed,
and the record of the second stays in the backlog, so both become durable
once a create succeeds.

Rejected releases count in rejected_appends_total.

Signed-off-by: jeremyhi <fengjiachun@gmail.com>

* docs(rfc): describe region releases while the object store WAL is failing

Signed-off-by: jeremyhi <fengjiachun@gmail.com>

* docs(rfc): place the drop crash window in the drop procedure

The crash window between the deletion of a table's metadata and a
durable release record is a gap of the drop procedure, not of the WAL.
Describe it as far as the code shows, name the remedy that belongs in
the procedure, cleaning up offline a region a resumed region drop finds
not open, and list it under future work.

Signed-off-by: jeremyhi <fengjiachun@gmail.com>

* fix(log-store): keep rejected release records coalesced while failing

A release rejected while the store is failing kept its record for the
next sealed batch, but every timer tick then sealed that record alone
behind the refused backlog, so a release repeated across ticks grew the
backlog without bound. A failing store now keeps such records coalesced
and seals them on the first tick after a create succeeds.

The fault-matrix case of a release while the backlog is refused now runs
with a 10 ms flush interval, repeats the release, and checks that the
backlog does not grow and that recovery reads the record back.

Signed-off-by: jeremyhi <fengjiachun@gmail.com>

* docs(rfc): keep rejected release records unsealed while failing

Signed-off-by: jeremyhi <fengjiachun@gmail.com>

* docs(rfc): cite the drop procedure issue for the crash window

Signed-off-by: jeremyhi <fengjiachun@gmail.com>

* fix(log-store): let a stop seal rejected release records of a failing store

Keeping the records of rejected releases out of sealed batches while the
store is failing also kept them out of the seal a stop requests, so a
stop whose last attempt of the backlog succeeded dropped them and the
released region pinned collection after the restart. A stop now seals
them with the backlog.

A fault-matrix case rejects a release while the store is failing, stops,
lets both creates succeed, and checks the record after a reopen.

Signed-off-by: jeremyhi <fengjiachun@gmail.com>

* docs(rfc): describe the stop of a failing store with rejected releases

Signed-off-by: jeremyhi <fengjiachun@gmail.com>

* fix(log-store): keep release records readable by older object store WAL readers

Release records were written as a version 2 footer, which a build without
it refuses to read, so rolling a node back after a drop failed to open
the whole WAL prefix. They are now the single entry of a segment of the
reserved region u64::MAX at the first position of a version 1 object:
an older reader indexes that segment as a region it never opens, and
recovery reads the segment back with one range read per object that
holds one. The segment never holds the reclaim boundary and names no
namespace.

A durable stop whose last create of release records fails now reports
the error instead of returning Ok and losing the records silently.

The tests are trimmed: the release unit tests and fault-matrix cases
that repeated each other are merged, and the Mito drop tests share one
parameterized case.

Signed-off-by: jeremyhi <fengjiachun@gmail.com>

* docs(rfc): describe release records as a segment of a reserved region

Signed-off-by: jeremyhi <fengjiachun@gmail.com>

* fix(log-store): report release records a poisoning stop drops

A durable stop whose last create of release records met a conflict
poisoned the store and returned Ok, losing the records silently. Stop
now reports the error whenever release records are dropped after it
began, by a rollback or by poisoning. The test of a failed release
covers that conflict, and seals its record on a tick after a one-shot
create failure, so the next tick cannot fail too.

The documentation now says that recovery reads the segments of release
records, and that one that does not decode fails recovery.

Signed-off-by: jeremyhi <fengjiachun@gmail.com>

* docs(rfc): describe how recovery reads segments of release records

Signed-off-by: jeremyhi <fengjiachun@gmail.com>

* docs(rfc): say that stop reports release records a conflict drops

Signed-off-by: jeremyhi <fengjiachun@gmail.com>

* test(log-store): restore the transiently failing release create in enqueued mode

The fault matrix again covers a release whose create fails transiently in
the enqueued mode: the release waits for the repeat under the same
sequence and returns once it succeeds. The encode_batch doc now says the
release segment is the last segment of the object.

Signed-off-by: jeremyhi <fengjiachun@gmail.com>

* docs(rfc): place the release segment and count the restored case

Signed-off-by: jeremyhi <fengjiachun@gmail.com>

* fix(mito2): do not fail a region drop on a failed WAL release

Returning the release error from the drop left a metric physical region
half dropped: the metric engine stops after the data region and never
drops the metadata region or forgets the physical region, and a retried
drop finds the data region gone and counts it as done. The release error
gave the caller nothing to retry, since the region is already removed
and the log store keeps the record for its next object. The drop now
logs the error and succeeds.

A metric engine test drops a physical region while the WAL releases of
both mito regions fail, and checks that both regions and the physical
region state are gone.

Signed-off-by: jeremyhi <fengjiachun@gmail.com>

* docs(rfc): a failed release no longer fails the drop

Signed-off-by: jeremyhi <fengjiachun@gmail.com>

* fix(mito2): do not fail a region drop on a failed WAL obsolete

A store that the release of another region poisoned fails obsolete as
well, which left the metric engine with its metadata region undropped.

Signed-off-by: jeremyhi <fengjiachun@gmail.com>

* test(metric-engine): drop a physical region whose WAL obsolete or release fails

Cover a failed data release, two failed releases and a conflicting
release that poisons the store, after the regions wrote to the WAL, and
check that a store opened later keeps no segment of either region.

Signed-off-by: jeremyhi <fengjiachun@gmail.com>

* docs(rfc): a failed obsolete does not fail the drop either

Signed-off-by: jeremyhi <fengjiachun@gmail.com>

* fix(mito2): fail the drop on an obsolete error of other WAL providers

Only the object store WAL is shared by the regions of a store that the
release of one can poison; Raft Engine and Kafka keep failing the drop
when obsolete fails.

Signed-off-by: jeremyhi <fengjiachun@gmail.com>

* docs(rfc): a stopped or poisoned store keeps no failed release

Signed-off-by: jeremyhi <fengjiachun@gmail.com>

---------

Signed-off-by: jeremyhi <fengjiachun@gmail.com>
2026-10-15 03:27:43 +00:00
Lei, HUANG 3b442adba2 feat(mito): decouple compaction unit execution and publication (#9131)
* feat(mito): decouple compaction unit execution and publication

Execute shared-input dependency groups as independent local compaction units. Publish and acknowledge each unit separately, retain successful sibling results, and release each unit's input leases after worker apply.

Preserve picker priority, panic notifications, snapshot-free execution context, and indexed input leases. Include regression coverage for independent visibility, purge, cancellation, partial failure, and restart recovery.

Signed-off-by: Lei, HUANG <ratuthomm@gmail.com>

* refactor(mito): use crate visibility for compaction unit items

Replace the newly introduced pub(super) declarations with pub(crate), and make CompactingFiles::try_reserve private since it is only used within its module, per docs/style-guide.md.

Signed-off-by: Lei, HUANG <ratuthomm@gmail.com>

* refactor(mito): simplify compaction unit state and test fixtures

Keep local input leases in the task and derive running units from tracked and pending work. Replace the single-implementation task trait with an inherent async method and align exposed state type visibility.

Use snapshot-free region fixtures and named test scenarios, and cover independent input purging across three units with bounded parallelism.

Signed-off-by: Lei, HUANG <ratuthomm@gmail.com>

* refactor(mito): align compaction task and test module names

Rename CompactionTaskImpl to CompactionTask now that the CompactionTask
trait has been removed and the Impl suffix no longer marks a trait
implementation. Rename the state test module from lease_tests to tests
to follow the test module naming convention.

Signed-off-by: Lei, HUANG <ratuthomm@gmail.com>

* refactor(mito): align compaction task debug label

The Debug impl reported "TwcsCompactionTask" while the type is now
CompactionTask. Report the type name for consistency.

Signed-off-by: Lei, HUANG <ratuthomm@gmail.com>

* refactor(mito): label compaction elapsed metric per published unit

Record greptime_mito_compaction_total_elapsed once per published unit and
label it by type: a local dependency unit or a remote job. Update the
metric docs and group the Grafana compaction OPS/latency panels by type.

Signed-off-by: Lei, HUANG <ratuthomm@gmail.com>

* docs(mito): clarify compacting file lease sharing

`CompactingFiles::for_inputs` clones the shared per-file leases instead of
transferring them. Document that the original collection keeps its leases,
so an input is released only after every owner drops its lease.

Signed-off-by: Lei, HUANG <ratuthomm@gmail.com>

* docs(mito): document local compaction unit map key

Document that `LocalCompaction::units` is keyed by each unit's execution
plan ID, which matches `CompactionExecution::plan_id()` and the `plan_id`
carried by unit notifications.

Signed-off-by: Lei, HUANG <ratuthomm@gmail.com>

* refactor(mito): rename submit_prepared_compaction

The function submits remote jobs but only prepares local unit tasks; the
caller dispatches them after installing the returned phase. Rename it to
`prepare_or_submit_compaction` and document that ordering.

Signed-off-by: Lei, HUANG <ratuthomm@gmail.com>

---------

Signed-off-by: Lei, HUANG <ratuthomm@gmail.com>
2026-10-14 12:21:32 +00:00
dennis zhuang 5c4d9eb4f8 fix: align fulltext bloom tokens with matches_term and use them for LIKE (#9470)
* fix: align fulltext bloom tokens with matches_term and use them for LIKE

The bloom fulltext index stored tokens that did not follow
matches_term's word boundaries (`_`, ASCII next to Han, Han substrings),
so pruning dropped rows matches_term accepts.

Tokens and query probes now share the character classes of
matches_term, Han runs are indexed as bigrams, and LIKE patterns use the
same probes. The bloom blob is written under a new key; blobs from older
versions are no longer probed. Tantivy no longer serves matches_term.

Signed-off-by: Dennis Zhuang <killme2008@gmail.com>

* chore: drop unused lazy_static from index and avoid typo-flagged test tokens

Signed-off-by: Dennis Zhuang <killme2008@gmail.com>

* docs: link like_probes to tokenize instead of the downstream ScriptTokenizer

Signed-off-by: Dennis Zhuang <killme2008@gmail.com>

* refactor: name probe extraction with verbs and document CharClass

Signed-off-by: Dennis Zhuang <killme2008@gmail.com>

* feat: rebuild legacy fulltext bloom indexes and probe lower(col)

The manifest records the fulltext bloom version per column, so ADMIN
BUILD_INDEX now rebuilds SSTs whose bloom index predates the current
tokens, and ssts_index_meta shows the version.

Case-insensitive bloom indexes also store the tokens of the whole
lowercased text for rows where it can differ (U+0130, U+212A, sigma),
so matches_term(lower(col), ..) and lower(col) LIKE .. use the index.

Signed-off-by: Dennis Zhuang <killme2008@gmail.com>

* test: cover legacy bloom index rewrite by compaction in the compat case

Signed-off-by: Dennis Zhuang <killme2008@gmail.com>

* chore: log outdated fulltext bloom indexes in the consistency check

Signed-off-by: Dennis Zhuang <killme2008@gmail.com>

* docs: explain the unrecorded fulltext bloom version

Signed-off-by: Dennis Zhuang <killme2008@gmail.com>

---------

Signed-off-by: Dennis Zhuang <killme2008@gmail.com>
2026-10-14 08:07:24 +00:00
jeremyhi a08e5bcceb fix(log-store): keep the enqueued backlog on permanent object store errors (#9558)
* fix(log-store): keep the enqueued backlog on permanent object store errors

On object storage the WAL's object store sits behind OpenDAL's retry
layer, which marks every error it returns as persistent. The enqueued
mode classified create failures by that status, so a 403 or a missing
bucket was repeated every 100 ms on every create in flight, logged
nothing, and made appends hang once the sealed batches were full.

Object store errors are now classified by kind: denied access, an
invalid configuration (how OpenDAL reports a missing S3 bucket) and a
write to a missing location are non-retriable. In the enqueued mode such
an error makes the store failing instead of poisoning it: the
acknowledged backlog is kept under its sequences and bytes, its lowest
pending batch is attempted alone with a delay that doubles from 100 ms
to 30 s, appends are rejected at once with a retryable error that
carries the last object store error, and the first create that succeeds
ends the state. Stop uploads the backlog at once and reports a failure
as before. The state is logged on entry, at most once a minute while it
lasts and on exit, and exported as the `failing` gauge and the
`rejected_appends_total` counter. Conflicts, encoding and catalog errors
still poison the store; the durable mode is unchanged.

An append held back because the sealed batches are full is now counted
and timed by the stall metrics in both modes.

Signed-off-by: jeremyhi <fengjiachun@gmail.com>

* docs(rfc): describe how the enqueued object store WAL handles refused creates

Describe the enqueued failure contract as the store now implements it:
non-retriable object store errors are told apart by kind because
OpenDAL's retry layer under the store marks every error persistent, a
failing store keeps its backlog and attempts it with backoff while it
rejects appends, and stop uploads the backlog at once. Add the failure
matrix row, the failing gauge and the rejected appends counter, the
sealed-batch admission limit and the stall metrics that now count it,
and the fault matrix case counts.

Signed-off-by: jeremyhi <fengjiachun@gmail.com>

* fix(log-store): time the failing attempts where they start and fix two statements

The test of the attempts of a failing store now records when each
refused write starts and checks the gaps between them, instead of
sampling a counter after a sleep of the test task. The delay comment and
the RFC describe 30 seconds as the longest delay between two attempts,
not as a bound on when appends resume, and the RFC states that the next
append is held once seven sealed batches wait.

Signed-off-by: jeremyhi <fengjiachun@gmail.com>

* test(log-store): drop the wait_until helper that main now defines

Signed-off-by: jeremyhi <fengjiachun@gmail.com>

* refactor(log-store): name errors that need an operator and trim the RFC contract

Rename is_non_retriable to requires_intervention: such a create is still
repeated, only slowly, until someone fixes the credentials, configuration
or bucket. Name the backoff test after what it asserts, cut the test of
the lowest pending batch to two failed rounds since the cadence is
covered by the backoff test, and shorten the RFC failure contract to
the contract and its rationale, leaving error mappings, request counts
and logging cadence to the code comments.

Signed-off-by: jeremyhi <fengjiachun@gmail.com>

* docs(log-store): tie the slow repeat to errors requiring intervention

Signed-off-by: jeremyhi <fengjiachun@gmail.com>

---------

Signed-off-by: jeremyhi <fengjiachun@gmail.com>
2026-10-14 06:10:26 +00:00
Weny Xu f690bbb204 refactor!: remove datanode remote WAL index (#9563)
* refactor: remove datanode remote WAL index

Signed-off-by: WenyXu <wenymedia@gmail.com>

* fix(log-store): update new WAL replay tests after index removal

Signed-off-by: WenyXu <wenymedia@gmail.com>

---------

Signed-off-by: WenyXu <wenymedia@gmail.com>
2026-10-13 15:31:04 +00:00
Lei, HUANG 4bd36cfd20 feat: support compaction options in HTTP hints (#9560)
* feat: support compaction options in HTTP hints

Signed-off-by: Lei, HUANG <ratuthomm@gmail.com>

* refactor: share TWCS compaction option prefix

Use the store-api constant for both HTTP hint inference and Mito region option validation so the two paths cannot drift.

Signed-off-by: Lei, HUANG <ratuthomm@gmail.com>

---------

Signed-off-by: Lei, HUANG <ratuthomm@gmail.com>
2026-10-13 12:28:36 +00:00
dennis zhuang 2a70194930 feat: add per-column COMPRESSION option for SST columns (#9426)
* feat: add per-column COMPRESSION option for SST columns

CREATE TABLE accepts COMPRESSION WITH (type = 'zstd', level = N,
page_size = '<size>') on a column. The options are stored in the column
metadata, carried through gRPC column options, shown by SHOW CREATE
TABLE, and applied by the mito SST writer and bulk part encoder as
parquet column properties. Unset fields keep the SST defaults. Nested
types are rejected.

A page size below the dictionary page limit also lowers the column's
dictionary page limit: parquet sizes write batches by the dictionary
budget and keeps that size after falling back to plain encoding, which
would otherwise ignore the smaller data page limit.

Signed-off-by: Dennis Zhuang <killme2008@gmail.com>

* fix: validate column compression options before persistence

Signed-off-by: Dennis Zhuang <killme2008@gmail.com>

* fix: reject compression overrides on metric logical tables

Signed-off-by: Dennis Zhuang <killme2008@gmail.com>

---------

Signed-off-by: Dennis Zhuang <killme2008@gmail.com>
2026-10-13 08:03:49 +00:00
Lei, HUANG 18634e17bb refactor(mito): retain region compaction and DDL lifecycle state (#9498)
* refactor(mito): retain region compaction and DDL lifecycle state

Signed-off-by: Lei, HUANG <ratuthomm@gmail.com>

* fix(mito): finalize opening batch in the worker on registration

Clean up the `opening_regions` entry and reply to its waiters in the same
serialized worker step that registers an opened region, instead of waiting
for the background opener to resume after the registration acknowledgement.

Previously the entry was only removed by the background opener, so a close
followed by a reopen could join the stale opening batch and receive a false
success after the region no longer existed. Replying from the worker closes
that window because registration and waiter cleanup are now atomic with
respect to the worker loop.

Add a test-only `EventListener::on_region_open_registered` hook and a
regression test that holds the first background opener to verify a reopen
does not wait for the previous open task. On failure the region is still
stopped and waiters receive `RegionClosed`.

Signed-off-by: Lei, HUANG <ratuthomm@gmail.com>

* fix(mito): unregister drop runtime state before WAL cleanup

The region leaves the region map before its WAL entries are obsoleted. A
failure in `wal.obsolete(...).await?` returned early and skipped
`cleanup_dropped_region_runtime_state`, so the resident compaction, flush,
and index scheduler entries were never unregistered. Since the region is no
longer in the map, a later close cannot clean them up either, leaking the
entries for the rest of the worker's lifetime.

Move the runtime-state cleanup to immediately after the region leaves the
map, before the fallible WAL cleanup. The schedulers only touch their own
state and do not depend on the region map or WAL, so the reordering is safe
and leaves the success path unchanged.

Signed-off-by: Lei, HUANG <ratuthomm@gmail.com>

* refactor(mito): tighten visibility of new compaction state items

Keep `ActiveCompaction::idle` private (it is only used inside `state.rs`)
and expose the other items added by the resident-region refactor as
`pub(crate)` instead of `pub(super)`, per the style guide. Also drop a
stale comment in `on_compaction_finished`: DDL handoff now happens in
`take_ready_ddls` before this block, which only runs when no DDL is
queued.

Signed-off-by: Lei, HUANG <ratuthomm@gmail.com>

* fix(mito): skip compaction scheduling while a region is truncating

`handle_flush_finished` dispatches queued DDLs before scheduling
compaction. A truncate queued behind a committing flush marks the region
as `Truncating` synchronously, but `schedule_compaction` only skipped
staging states, so it could schedule a new automatic compaction while the
truncate was still running.

Add `MitoRegion::is_truncating` and skip compaction scheduling for a
truncating region. Add a regression test that queues a full truncate
behind a committing flush and asserts no compaction is scheduled.

Signed-off-by: Lei, HUANG <ratuthomm@gmail.com>

* fix(mito): log DDL-fenced compaction rejection

The refactor dropped the log emitted when a region was skipped because of
pending DDL requests. Automatic triggers carry no waiter, so the rejection
became silent and could not be told apart from a scheduling interval that
had not elapsed. Log the queued and executing DDL state before cancelling.

Signed-off-by: Lei, HUANG <ratuthomm@gmail.com>

* docs(mito): document DDL reply fence invariants

Explain why fence_ddl_replies installs executing_ddl before handing off
the batch and why CompactionDdlComplete carries both a scheduler-wide
generation and a per-slot request_id. The generation fences replies from
superseded batches or region incarnations, while request_id makes
completion idempotent so only the last distinct reply releases the fence.

Signed-off-by: Lei, HUANG <ratuthomm@gmail.com>

* refactor(mito): rename DDL completion request_id to request_index

The value is the DDL's index within the handed-off batch, not a globally
stable request identifier; request_id suggested otherwise. Rename it to
request_index across the scheduler, worker dispatch, and tests.

Signed-off-by: Lei, HUANG <ratuthomm@gmail.com>

---------

Signed-off-by: Lei, HUANG <ratuthomm@gmail.com>
2026-10-13 05:17:41 +00:00
Weny Xu 287ced2de2 fix(mito2): ignore missing inverted indexes in old SSTs (#9545)
Signed-off-by: WenyXu <wenymedia@gmail.com>
2026-10-13 03:18:37 +00:00
Weny Xu 684c8ff291 feat(client): support region request reuse and route refresh hints (#9540)
* feat(client): support shared region requests without deep cloning

Signed-off-by: WenyXu <wenymedia@gmail.com>

* feat: propagate route refresh retry hints

Signed-off-by: WenyXu <wenymedia@gmail.com>

* style: preserve local error variant imports in retry hints

Signed-off-by: WenyXu <wenymedia@gmail.com>

* fix(mito2): distinguish staging rejection from write backpressure

Signed-off-by: WenyXu <wenymedia@gmail.com>

* fix(meta): preserve legacy retry hints in instruction replies

Signed-off-by: WenyXu <wenymedia@gmail.com>

* test(client): inline retry hint compatibility cases

Signed-off-by: WenyXu <wenymedia@gmail.com>

* test(datanode): align batch-open retry hint assertion

Signed-off-by: WenyXu <wenymedia@gmail.com>

* style: use imported types in region request tests

Signed-off-by: WenyXu <wenymedia@gmail.com>

---------

Signed-off-by: WenyXu <wenymedia@gmail.com>
2026-10-13 02:29:54 +00:00
Yingwen 674ba72669 fix(mito): preserve version ordering in bulk merges and flushes (#9517)
* fix(mito): preserve delete barriers in flat last-non-null dedup

Signed-off-by: evenyag <realevenyag@gmail.com>

* fix(mito): respect sequence ranges in bulk merges and flushes

Signed-off-by: evenyag <realevenyag@gmail.com>

* perf(mito): avoid merge selection on the bulk write path

Signed-off-by: evenyag <realevenyag@gmail.com>

---------

Signed-off-by: evenyag <realevenyag@gmail.com>
2026-10-12 13:16:52 +00:00
jeremyhi 18726c7602 fix: bound metric snapshot memory and reduce export overhead (#9505)
* fix(mito): bound retained scan memory

Compact unused tag dictionary values before merge retention and avoid splitting
a single scan source into redundant batches. Include regression coverage.

Signed-off-by: jeremyhi <fengjiachun@gmail.com>

* perf(snapshot): bound writes and reduce logical export overhead

Bound metadata writes to 8 MiB and abort failed writers. Preserve compatible
string dictionaries, bound conversion work, combine Parquet row groups and
filter single-table exports before routing. Include resource-boundary and
packed-restore regression coverage.

Signed-off-by: jeremyhi <fengjiachun@gmail.com>

* docs: clarify metric snapshot acceptance criteria

Specify populated scale fixtures, controlled S3 request delay, native macOS
validation, paging exclusions and alternating comparison rounds.

Signed-off-by: jeremyhi <fengjiachun@gmail.com>

* fix(mito): split batches only for merged readers

Signed-off-by: jeremyhi <fengjiachun@gmail.com>

* test(snapshot): cover supported types in both layouts

Signed-off-by: jeremyhi <fengjiachun@gmail.com>

* docs: consolidate metric snapshot experiment conclusions

Signed-off-by: jeremyhi <fengjiachun@gmail.com>

* fix(mito): honor series statistics when splitting L0 scans

Apply the existing series-density heuristic to L0 files with known statistics and include them in the batch-size estimate. Preserve the fallback for older files without statistics.

Cover short-series overlapping L0 files with an ordered latest-value regression.

Signed-off-by: jeremyhi <fengjiachun@gmail.com>

* docs(mito): explain memory risk of splitting short L0 series

Signed-off-by: jeremyhi <fengjiachun@gmail.com>

* refactor: address snapshot export review feedback

Signed-off-by: jeremyhi <fengjiachun@gmail.com>

* fix(cli): accept source errors in lazy snapshot contexts

Signed-off-by: jeremyhi <fengjiachun@gmail.com>

---------

Signed-off-by: jeremyhi <fengjiachun@gmail.com>
2026-10-12 06:17:03 +00:00
Weny Xu a026b3b280 fix(mito): flush memtables containing skip-WAL writes on region close (#9440)
* fix(mito): flush unlogged writes when closing regions

Signed-off-by: WenyXu <wenymedia@gmail.com>

* fix(mito): align skip-WAL naming and metric recovery tests

Signed-off-by: WenyXu <wenymedia@gmail.com>

---------

Signed-off-by: WenyXu <wenymedia@gmail.com>
2026-10-09 12:21:38 +00:00
jeremyhi e4872ec1a2 feat(log-store): collect object store WAL objects below a persisted reclaim boundary (#9471)
* feat(log-store): collect object store WAL objects below a persisted reclaim boundary

Every object header carries a reclaim boundary: every object below it was
fully flushed when the object was sealed. The writer computes the boundary
from the catalog and the obsolete watermarks after every obsolete and every
indexed object, and stamps it on every sealed object and on the start
object. The boundary of the last indexed object permits deletion: every
present object below it, indexed, listed by recovery off the chain, or
left by a failed batch, is deleted in the background, at most four at a
time.

Recovery judges every candidate with its own boundary: a link to a
predecessor below it holds whether or not the predecessor is present, and
the chain stops there. A read that listed an object before it was
collected skips it instead of failing.

The header grows from 46 to 54 bytes; the format version stays 1.

Signed-off-by: jeremyhi <fengjiachun@gmail.com>

* perf(log-store): scan the reclaim boundary from the previous boundary

The boundary was recomputed by scanning every region's indexed objects
from the first one, so the flushed objects of busy regions were scanned
again on every indexed object while another region held the boundary.
Scan the objects in sequence order from the previous boundary instead
and stop at the first one holding a live segment. Watermarks never move
down and a region first appears above the boundary, so the result is
the same and an object is not scanned again once the boundary passed it.

Signed-off-by: jeremyhi <fengjiachun@gmail.com>

* test(log-store): name the parked operation helpers after both their uses

Signed-off-by: jeremyhi <fengjiachun@gmail.com>

---------

Signed-off-by: jeremyhi <fengjiachun@gmail.com>
2026-10-08 13:16:14 +00:00
Lei, HUANG 5126623e63 fix: align TWCS assignment with half-open time windows (#9464)
Signed-off-by: Lei, HUANG <ratuthomm@gmail.com>
2026-10-08 12:34:20 +00:00
fys c46fbde9ac fix(json2): preserve metadata when adding columns via alter table (#8696)
* fix(json2): support adding columns via alter table

Signed-off-by: fys <fengys1996@gmail.com>

* fix(json2): scope missing-column null handling to JSON2

---------

Signed-off-by: fys <fengys1996@gmail.com>
2026-10-08 09:23:25 +00:00
Ning Sun 7f0f592b6b chore: update rust toolchain to 1.98.1 (#9429) 2026-10-08 09:00:10 +00:00
dennis zhuang 526dd48bda fix(promql): restore exact filtering for nullable label matchers (#9418)
* fix(promql): restore exact filtering for nullable label matchers

Signed-off-by: Dennis Zhuang <killme2008@gmail.com>

* test: verify compound filter semantics and last-row selection

Signed-off-by: Dennis Zhuang <killme2008@gmail.com>

* test: consolidate label filter regression coverage

Signed-off-by: Dennis Zhuang <killme2008@gmail.com>

* refactor: separate filter evaluation and timestamp adaptation

Signed-off-by: Dennis Zhuang <killme2008@gmail.com>

* refactor: inline predicate and timestamp tests

Signed-off-by: Dennis Zhuang <killme2008@gmail.com>

* refactor: keep direct IN predicates outside simple filters

Signed-off-by: Dennis Zhuang <killme2008@gmail.com>

* fix: coerce partition bounds before simple filtering

Signed-off-by: Dennis Zhuang <killme2008@gmail.com>

* test: cover partition bound overflow after timestamp widening

Signed-off-by: Dennis Zhuang <killme2008@gmail.com>

---------

Signed-off-by: Dennis Zhuang <killme2008@gmail.com>
2026-10-08 07:43:25 +00:00
jeremyhi f9ab3c296b feat(log-store): skip corrupted object store WAL segments per region (#9425)
* feat(log-store): skip corrupted object store WAL segments per region

A segment that does not decode, because its checksum does not match or
its content disagrees with its footer entry, is fetched once more. If it
still does not decode, the read follows `on_corrupted_segment`: `skip`
records a WAL hole for the region with the object key, sequence and the
footer's entry id range, increments
`greptime_logstore_object_store_wal_skipped_segments_total`, logs a
warning and continues with the next object; `fail` fails the read. A
failed fetch fails the read in both modes. Holes are kept in memory per
region and can be listed from the store.

The standalone test builder allocates WAL options through
`build_standalone_wal_provider`, and integration tests run a standalone
instance on the object store WAL across restarts, on S3 when the
`GT_S3_*` variables are set. The standalone shutdown runs every step and
returns the first error.

Signed-off-by: jeremyhi <fengjiachun@gmail.com>

* test(cmd): cover the best-effort standalone stop

Build a standalone instance whose leader services fail to stop and whose
datanode servers fail afterwards, and check that `Instance::stop` still
stops the object store WAL and returns the leader services error.

Rename `skip_segment` to `record_skipped_segment` and the file store
integration test to `test_standalone_object_store_wal_survives_restarts_on_file`.

Signed-off-by: jeremyhi <fengjiachun@gmail.com>

---------

Signed-off-by: jeremyhi <fengjiachun@gmail.com>
2026-10-08 06:33:40 +00:00
Yingwen 3a4c24a863 fix: temporarily disable v2 series scan by default (#9436)
* fix: disable v2 series scan by default temporarily

Signed-off-by: evenyag <realevenyag@gmail.com>

* test: explicitly enable v2 series scan in sqlness configs

Signed-off-by: evenyag <realevenyag@gmail.com>

* test: regenerate sqlness results for legacy series scan default

Signed-off-by: evenyag <realevenyag@gmail.com>

---------

Signed-off-by: evenyag <realevenyag@gmail.com>
2026-10-02 12:09:16 +00:00
dennis zhuang a314ac4281 fix(mito2): truncate parquet column index min/max for SST writes (#9420)
* fix(mito2): truncate parquet column index min/max for SST writes

The SST writer and the bulk part encoder disabled column index truncation
together with statistics truncation in #6977. Mito never reads the column
index, but every page of a large string or binary column still stored its
full min and max values, uncompressed. Truncate the column index to the
parquet default of 64 bytes and keep chunk statistics untruncated, since
primary key pruning decodes them.

Signed-off-by: Dennis Zhuang <killme2008@gmail.com>

* docs(mito2): note that the column index must not be used to decode primary keys

Signed-off-by: Dennis Zhuang <killme2008@gmail.com>

---------

Signed-off-by: Dennis Zhuang <killme2008@gmail.com>
2026-09-30 08:40:27 +00:00
jeremyhi 8553db3f27 feat(mito): wait for WAL durability before publishing a manifest watermark (#9410)
* feat(mito): wait for WAL durability before publishing a manifest watermark

A log store that acknowledges appends before their entries are durable can
hand out entry ids that a crash loses. Before a flush, a full truncate or a
discard of unflushed data records an entry id in the manifest, Mito now waits
on `LogStore::wait_durable` for that id. The flush waits after its SSTs are
written and observes its cancellation, so a drop or truncate queued behind it
is not held up by the upload.

Add engine tests of the object store WAL across restarts and of the barrier,
and a log store testing hook that stores an object and then reports its create
as failed.

Signed-off-by: jeremyhi <fengjiachun@gmail.com>

* test(mito): synchronize the WAL barrier tests on store signals

The barrier tests waited with fixed sleeps, which do not establish that a
flush or truncate has reached its durability wait, and the lost backlog test
could stop the store before the seal was handled. The object store WAL gets
testing hooks that observe parked creates and durability waits, and one that
ends the actor as a crash would; the tests wait on them, crash the store and
the engine before a reopen until nothing holds the old store, and cover a
truncate or discard of unflushed data that completes once its entry is
durable.

Signed-off-by: jeremyhi <fengjiachun@gmail.com>

* test(mito): rename the recovery state and the crash test in the WAL recovery tests

Signed-off-by: jeremyhi <fengjiachun@gmail.com>

* test(mito): drop a comment that restates the empty recovery state

Signed-off-by: jeremyhi <fengjiachun@gmail.com>

---------

Signed-off-by: jeremyhi <fengjiachun@gmail.com>
2026-09-30 05:20:59 +00:00
jeremyhi a99cb56d71 feat(datanode): wire the object store WAL into the datanode and standalone (#9386)
* feat(datanode): wire the object store WAL into the datanode and standalone

A datanode configured with `provider = "experimental_object_store"` now
builds an `ObjectStoreLogStore` instead of failing with the placeholder
"not supported yet" error. The builder resolves `storage_provider`
against the object store manager (empty selects the default store, an
unknown name is rejected as an invalid `storage_provider`), opens the
store under the node id and the standalone generation, stops it if a
later build step fails, and hands it to `Datanode`, whose shutdown stops
it after the region server. Datanode shutdown is now best-effort: every
step runs and the first error is returned. A datanode with a metasrv
client still rejects the provider.

The standalone bootstrap builds `WalProvider::ObjectStore` with the same
derived node prefix before the metasrv WAL conversion, so the WAL
options it allocates match the prefix the store runs under.

`log-store` exports `ObjectStoreLogStore` and its testing hooks. Mito's
test utilities can build an engine on the object store WAL over an
in-memory object store, and new engine tests cover create, reopen,
replay isolation between regions, batch open, drop and offline cleanup,
the latest entry id after replay, and the rejection of object store WAL
options on a Raft Engine log store.

Signed-off-by: jeremyhi <fengjiachun@gmail.com>

* fix(datanode): stop every region engine before the object store WAL

The region server returned at the first engine whose stop failed, so the
engines after it in map order, Mito included, were never stopped before
the datanode stopped the object store WAL. It now attempts every engine,
returns the first error and logs the later ones.

`ObjectStoreLogStore` is also exported from the `log-store` crate root,
and its callers import it from there.

Signed-off-by: jeremyhi <fengjiachun@gmail.com>

---------

Signed-off-by: jeremyhi <fengjiachun@gmail.com>
2026-09-29 13:17:42 +00:00
dennis zhuang 079ffec116 chore: remove dead code left behind by removed features (#9377)
* chore: remove dead code left behind by removed features

Signed-off-by: Dennis Zhuang <killme2008@gmail.com>

* chore: remove unused RouteInfoCorrupted error

Signed-off-by: Dennis Zhuang <killme2008@gmail.com>

---------

Signed-off-by: Dennis Zhuang <killme2008@gmail.com>
2026-09-28 12:35:47 +00:00
75bd8e9ce6 feat: add HDFS object storage backend (#8701)
* feat: add HDFS object storage backend

Signed-off-by: Minghan2005 <cambrianocean@gmail.com>

* fix: make HDFS storage operations durable

Gate the native HDFS backend behind an explicit feature. Publish writes through same-directory temporary files and atomic HDFS Rename2 replacement, and provide streaming copy fallback for COPY_REGION. Add regression coverage for interrupted writes and the region-copy path.

Signed-off-by: Minghan2005 <cambrianocean@gmail.com>

* ci: run HDFS object store tests

Signed-off-by: jeremyhi <fengjiachun@gmail.com>

* docs: note HDFS temporary file cleanup follow-up

Signed-off-by: jeremyhi <fengjiachun@gmail.com>

* feat: enable HDFS object storage by default

Signed-off-by: jeremyhi <fengjiachun@gmail.com>

* docs: remove redundant HDFS build feature notes

Signed-off-by: jeremyhi <fengjiachun@gmail.com>

---------

Signed-off-by: Minghan2005 <cambrianocean@gmail.com>
Signed-off-by: jeremyhi <fengjiachun@gmail.com>
Co-authored-by: Minghan2005 <cambrianocean@gmail.com>
Co-authored-by: jeremyhi <fengjiachun@gmail.com>
2026-09-28 08:39:48 +00:00
dennis zhuang ffdd6d09a6 perf(index): cut allocations when building bloom and inverted indexes (#9359)
* perf(index): build bloom filters from element hashes without per-token allocation

Signed-off-by: Dennis Zhuang <killme2008@gmail.com>

* perf(index): speed up inverted index building with hashed buffers and sync pushes

Signed-off-by: Dennis Zhuang <killme2008@gmail.com>

* chore(index): use BuildHasher::hash_one for element hashes

Signed-off-by: Dennis Zhuang <killme2008@gmail.com>

* chore(index): require callers to act on spill requests

Signed-off-by: Dennis Zhuang <killme2008@gmail.com>

* fix(index): insert bloom hashes one by one to keep segment set capacity bounded

Signed-off-by: Dennis Zhuang <killme2008@gmail.com>

* test(index): add index build and bloom search benchmarks

Signed-off-by: Dennis Zhuang <killme2008@gmail.com>

* test(index): keep applier setup out of the bloom search benchmark

Signed-off-by: Dennis Zhuang <killme2008@gmail.com>

* fix(index): skip empty spills and keep the old inverted sort memory estimate

Signed-off-by: Dennis Zhuang <killme2008@gmail.com>

* fix(index): stream fulltext token hashes and test spill dispatch

Signed-off-by: Dennis Zhuang <killme2008@gmail.com>

* docs(index): note non-ASCII tokens still allocate in analyze_text_hashes

Signed-off-by: Dennis Zhuang <killme2008@gmail.com>

* docs(index): narrow the allocation note to case-insensitive non-ASCII tokens

Signed-off-by: Dennis Zhuang <killme2008@gmail.com>

---------

Signed-off-by: Dennis Zhuang <killme2008@gmail.com>
2026-09-28 08:22:42 +00:00
dennis zhuang bc5ad4b416 perf(mito2): batch index page loads and share cached bloom metadata (#9360)
* perf(mito2): load missing index pages of all ranges in one read

Signed-off-by: Dennis Zhuang <killme2008@gmail.com>

* refactor(mito2): drop unused IndexCacheMetrics::merge

Signed-off-by: Dennis Zhuang <killme2008@gmail.com>

* perf(mito2): share cached bloom filter metadata instead of cloning it

Signed-off-by: Dennis Zhuang <killme2008@gmail.com>

* fix(mito2): only merge adjacent ranges when batch reading index pages

Signed-off-by: Dennis Zhuang <killme2008@gmail.com>

* fix(mito2): skip empty ranges when batch loading index pages

Signed-off-by: Dennis Zhuang <killme2008@gmail.com>

* perf(mito2): fetch index ranges concurrently

Signed-off-by: Dennis Zhuang <killme2008@gmail.com>

* test(index): return Arc<BloomFilterMeta> from RecordingReader

Signed-off-by: Dennis Zhuang <killme2008@gmail.com>

---------

Signed-off-by: Dennis Zhuang <killme2008@gmail.com>
2026-09-28 07:57:04 +00:00
dennis zhuang 0a03e64d8c perf(index): batch bloom filter searches across row groups (#9361)
* perf(index): search bloom filters of all row groups in one read

Signed-off-by: Dennis Zhuang <killme2008@gmail.com>

* perf(index): bound the filter bytes of each batched bloom search

Signed-off-by: Dennis Zhuang <killme2008@gmail.com>

* perf(index): count batch filter bytes exactly as the search reads them

Signed-off-by: Dennis Zhuang <killme2008@gmail.com>

* chore(index): allow single-range groups in the budget test

Signed-off-by: Dennis Zhuang <killme2008@gmail.com>

* test(index): cover filters shared across segments in the budget test

Signed-off-by: Dennis Zhuang <killme2008@gmail.com>

---------

Signed-off-by: Dennis Zhuang <killme2008@gmail.com>
2026-09-28 06:22:15 +00:00
dennis zhuang 411c0f173e fix(mito2): stop parallel flat scan tasks once the receiver is dropped (#9368)
* fix(mito2): stop parallel flat scan tasks once the receiver is dropped

spawn_flat_scan_task ignored send errors, so after a query was cancelled the
detached task kept reading its source to EOF. Break out of the loop when the
receiver is gone.

Signed-off-by: Dennis Zhuang <killme2008@gmail.com>

* chore(mito2): log when a parallel scan task stops on a dropped receiver

Signed-off-by: Dennis Zhuang <killme2008@gmail.com>

* chore(mito2): include the send error in the parallel scan stop log

Signed-off-by: Dennis Zhuang <killme2008@gmail.com>

---------

Signed-off-by: Dennis Zhuang <killme2008@gmail.com>
2026-09-28 02:50:35 +00:00
Weny Xu 5ffd01a70a fix: preserve primary key order when syncing columns (#9189)
* fix: preserve primary key order when syncing columns

Signed-off-by: WenyXu <wenymedia@gmail.com>

* fix: reject invalid SyncColumns metadata

Signed-off-by: WenyXu <wenymedia@gmail.com>

---------

Signed-off-by: WenyXu <wenymedia@gmail.com>
2026-09-27 02:15:47 +00:00
Yingwen 3c2aac0a55 feat: add manual series index reconciliation (#9323)
* feat(mito): support manual series index reconciliation

Signed-off-by: evenyag <realevenyag@gmail.com>

* feat(storage): route series index build requests

Signed-off-by: evenyag <realevenyag@gmail.com>

* feat(admin): add BUILD_SERIES_INDEX

Signed-off-by: evenyag <realevenyag@gmail.com>

* test: specify compaction type in series index fixtures

Signed-off-by: evenyag <realevenyag@gmail.com>

* test: correct series index SQL fixtures and error assertions

Signed-off-by: evenyag <realevenyag@gmail.com>

* test(compat): preserve legacy index rebuild across upgrades

Signed-off-by: evenyag <realevenyag@gmail.com>

* test(sql): cover series index admin validation

Signed-off-by: evenyag <realevenyag@gmail.com>

* refactor(mito): bound series index maintenance queue

Signed-off-by: evenyag <realevenyag@gmail.com>

* docs: remove series index how-to guide

Signed-off-by: evenyag <realevenyag@gmail.com>

* test: remove index build upgrade compatibility case

Signed-off-by: evenyag <realevenyag@gmail.com>

* fix: address series index reconciliation review feedback

Signed-off-by: evenyag <realevenyag@gmail.com>

* fix: bound manual series index reconciliation admission

Signed-off-by: evenyag <realevenyag@gmail.com>

* chore: update greptime-proto to merged index build options

Signed-off-by: evenyag <realevenyag@gmail.com>

---------

Signed-off-by: evenyag <realevenyag@gmail.com>
2026-09-24 09:39:21 +00:00
Ning Sun 20619273ee chore(toolchain): switch to stable Rust 1.96.1 and remove all nightly feature gates (#9303)
* chore(toolchain): switch to stable Rust 1.96.1 and remove all nightly feature gates

Move the workspace from the pinned nightly-2026-03-21 to stable
1.96.1 and drop all 23 '#![feature]' gates across 13 crates,
rewriting the still-unstable API usages with stable equivalents:

- try_blocks: closures / an async block (table, query, common-function, servers)
- duration_constructors: Duration::from_secs(n * 86400) / (n * 60)
- iterator_try_collect: collect::<Result<Vec<_>, _>>()
- box_patterns: as_deref() + matches! chains (sql)
- error_iter: error_chain_root() source-chain walker (common-error);
  sources() includes the error itself, so the walker never panics
- int_roundings: div_floor -> div_euclid (equal for positive divisors)
- iter_partition_in_place: stable sort_by_key partition helper (index)
- hash_set_entry: HashSet::insert bool / contains+insert
- trait_alias: lifetime-parameterized dyn FnOnce type aliases (puffin)
- string_from_utf8_lossy_owned: from_utf8_lossy(&v).into_owned()
- never_type: Infallible (common-recordbatch)
- debug_closure_helpers: closure-backed DebugFmt newtype (mito2)
- binary_heap_pop_if: peek().is_some_and() + pop()
- exclusive_wrapper: drop Exclusive; C: Send + Unpin already in bounds
- stmt_expr_attributes: stale gate, no usages

Also fix release-dev-builder-images.yaml, which parsed
rust-toolchain.toml with a date-only regex and would produce empty
image versions with a stable channel; it now extracts the full
channel token. Dev-builder images verified against stable 1.96.1
(image build, default-toolchain behavior, binstall/nextest, riscv64
and android targets, in-image cargo check).

Validated on 1.96.1: cargo check --workspace --all-targets, clippy
--workspace --all-targets --all-features -D warnings, cargo fmt
--check, and nextest on all 13 affected crates (4586 passed).

Part of #9289. Depends on #9298 (fuzz nightly quarantine) merging
first.

Signed-off-by: Ning Sun <sunning@greptime.com>

* chore: update flake checksum

* chore: use wild for linker in flake

---------

Signed-off-by: Ning Sun <sunning@greptime.com>
2026-09-24 08:37:43 +00:00
dennis zhuang b9f991502c refactor: remove experimental vector index (#9345)
Signed-off-by: Dennis Zhuang <killme2008@gmail.com>
2026-09-24 05:38:54 +00:00
Yingwen c7fa48ef95 feat(mito): limit approximate series index disk usage (#9313)
* feat(mito): limit series index disk usage

Signed-off-by: evenyag <realevenyag@gmail.com>

* refactor(mito): enforce series index quota during reconciliation

Signed-off-by: evenyag <realevenyag@gmail.com>

* refactor(mito): remove series index disk budget layer

Signed-off-by: evenyag <realevenyag@gmail.com>

* refactor(mito): simplify series index limit to estimated usage

Signed-off-by: evenyag <realevenyag@gmail.com>

* refactor(mito): trust index catalogs when loading snapshots

Signed-off-by: evenyag <realevenyag@gmail.com>

* refactor(mito): minimize series index disk limit changes

Signed-off-by: evenyag <realevenyag@gmail.com>

* fix(mito2): keep series index cleanup running at capacity

Signed-off-by: evenyag <realevenyag@gmail.com>

* fix(mito2): avoid no-op index clones and stabilize capacity tests

Signed-off-by: evenyag <realevenyag@gmail.com>

* fix: update config API expectation and stabilize index build tests

Signed-off-by: evenyag <realevenyag@gmail.com>

---------

Signed-off-by: evenyag <realevenyag@gmail.com>
2026-09-23 14:18:42 +00:00