Repository navigation
[0.23.0] - 2026-10-09
Changed
- The server role templates allow the key-epoch record only conditional
writes (issue #2462).deploy/iam/gateway.json,query.jsonand
maintain.jsonnow allow writing a tenant's key-epoch recordt/<hash>/enc
only as a create-if-absent or a compare-and-swap, not as an unconditional
overwrite. The code already wrote it only those ways, so nothing a server or
ravel-clidoes changes. Operators must re-apply the three templates. /metricssays why a flush was handed back (issue #2600).
ravel_ingest_rerouted_flushes_totalgains areasonlabel,
retired_indexorgeneration_mismatch, so a query that selects the family
without the label now gets two series per signal. Every hand-back is counted
under exactly one reason.ravel-cli load's stride cursors no longer each hold a whole batch of
decoded Arrow rows (issue #2613). Each stride cursor used to decode a
full--batch-rowsArrow batch of every column and keep the undealt
remainder, so K cursors held K whole batches at once: about 7 GB of a
21 GB live heap at 16 cursors and 500,000-row batches on ClickBench. A
cursor now decodesceil(--batch-rows / K)rows at a time, so the decoded
rows held across all cursors no longer scale with K; each cursor still
keeps its own Parquet reader and page-decode state. Batches are dealt
exactly as before: a differential test compares every batch's rows with a
copy of the previous dealer over uneven row groups,--batch-rowsnot
divisible by K, short and empty partitions,--skip-rowsand K = 1, plus
generated files, and the RLOG encodings match the previous dealer's at
(--batch-rows, K) = (1000, 4), (333, 5), (1000, 3) and (700, 1). In one
360 s measurement per version (ClickBenchhits.parquet,--batch-rows 500000 --read-cursors 16 --shards 4, memory store, a 16-vCPU 30 GB x86_64
host, jemalloc heap profiling on in both runs) peak RSS fell from 22.7 GB
to 15.5 GB. The after-run predates the batch-composition fix, which
changes only which rows a batch takes at a partition's end, not how many
rows a cursor holds.- The columnar log write path moves each batch into its shards instead of
copying it (issue #2624).LogIngestRouter::write_columnarused to clone
every cell, residual attribute list and stream blob of the batch into
per-shard copies while the batch itself stayed alive until the write
returned. The batch is now freed column by column as its rows are dealt,
and is gone before any shard is sent its part; the severity text, body and
trace and span ids are still copied. String and byte column dictionaries
are still dropped at the split, as before: carrying them would keep one
copy of each distinct value per shard, memory the ingest byte budget does
not count. Stored objects do not change: tests compare every per-shard
batch, dictionaries included, with the previous partition's over fixed and
generated input,
and every stored object the two partitions write, including two writes
merged into one flush and a shard's buffer handed back to new targets.
Peak RSS ofravel-cli loadon ClickBenchhits.parquetat--batch-rows 500000 --read-cursors 16 --shards 4fell from 15.48 GB to 11.37 GB. - Columnar log batches store attribute cells as typed columns (issue
#2625). AColumnarLogBatchdynamic column used to hold oneAttrValue
per cell; it now holds one dense payload per type: string and byte values
as offsets plus bytes, integers and floats as plain vectors, and bools as
a vector of flags.ravel-cli loadfills those columns straight from the
Arrow arrays, copying each string's bytes once, and no longer attaches a
string column dictionary to the batch. Stored objects do not change: the
row and columnar builders still write byte-identical RLOG objects, which
tests check over generated batches and a Parquet file holding every
type the loader maps, NaN payloads and -0.0 included. A string, byte,
body or severity text column that would passu32::MAXbytes in one
batch (the most its 32-bit offsets address, one byte short of 4 GiB) is
now a load error naming the column and suggesting a smaller
--batch-rows, where it used to panic.
Measured on ClickBenchhits.parquetat--batch-rows 500000 --read-cursors 16 --shards 4, stopped after 360 seconds, peak RSS over
the whole run was 11.02 GB, against 11.37 GB for the previous change, on
a different host (8 cores and 16.0 GB of memory, against 16 cores and
32.1 GB), so the comparison does not isolate this change. The run kept
every written object in the measured process, since the load wrote to the
in-memory object store (9.4 GB of object buffers in its last heap dump).
Of the four targets set before measuring, three were missed: 0.5 to 1 KB
of heap per batch row, under 1 GB held at batch build and at shard
partitioning, and a peak RSS of 5 to 7 GB. The fourth was met: at
--batch-rows 1000000the load ran its 360 seconds without running out
of memory, at a 13.33 GB peak. ravel-cli loadholds its log batches under one byte budget,
--load-memory-bytes(issue #2626). Each batch is charged before it
is built, at an estimate from the previous batch's bytes per row (zero
for the first), and corrected to its measured in-memory size once built,
so the first batch and any batch wider than its estimate are built partly
uncharged. The charge stays through the decode queue, the write window,
the shard buffers and the flush that writes its rows; when the budget is
full the decoder waits instead of failing. With the flag set, a load
whose first batch alone does not fit is refused before anything is
written, naming the batch's size, the budget and the floor. Unset, the
budget is host memory (or a lower cgroup limit) less an estimated floor
of 128 MiB plus 32 MiB per read cursor and 72 MiB per concurrent flush,
with a 4 GiB fallback, logged, where host memory cannot be read; a
derived budget below one batch admits one batch at a time with a
warning instead of refusing. The load summary prints the budget, its
source, the floor, the peak bytes charged, the largest batch and the
decoder's waits. Stored bytes are unchanged unless the budget binds
above--target-bytes 1, where the loader flushes the shard buffers
early when the decoder waits on the budget with nothing released:
smaller objects, never a hang.--target-bytesabove1lets shards
merge several batches into one object, up to--pipeline-depth
batches, since each Strict write waits for its own batch's flush. The
target counts estimated uncompressed content: on ClickBench
hits.parquet,--batch-rows 50000 --target-bytes 25000000stored
objects of 1.1 to 2.4 MB (10th to 90th percentile); the recipe for
larger objects from small batches is documented separately (issue #2627).
Measured with the
in-memory object store at--batch-rows 500000and a 6 GB budget, peak
RSS, which includes the store's held objects, was 12.02 GB against
11.85 GB with the host-derived budget on the same host. That run is no
evidence the budget bound: both runs read the same bytes, the sampled
heap outside the store's objects stayed under 6 GB (at most 5,430 MB),
and the peak charge was not read.ravel-cli loaddocuments a measured recipe for large log objects
(issues #2627 and #2651). The--target-bytesand--pipeline-depthhelp
and the ingest guide now give the measured settings for large stored
objects from small batches, in place of "pending": on ClickBench
--batch-rows 100000 --target-bytes 1850000000 --max-flush-delay 30s --pipeline-depth 32 --load-memory-bytes 6500000000stored 25.1 MB mean
objects in 896 s at a 7.03 GB peak loader RSS, and the guide gives the
depth and budget used on 16, 8 and 4 GB hosts with the object size each
stored (24.5, 23.9 and 17.1 MB median).
The earlier statement that
stored objects come out about 15 times below--target-byteswas wrong:
on that corpus they come out about 65 to 75 times below it, and the
ratio depends on the data. The help and guide also say that--pipeline-depth
must cover the batches one object needs on a shard and that
--max-flush-delaymust be long enough for one object to fill.- A failed logs load still prints the one-batch memory warning. When the
memory budget derived from host memory is smaller than one batch, the
loader admits one batch at a time and warns. That warning was printed only
when the load succeeded; it is now printed beside the error too. - A columnar hand-back no longer holds the buffered rows twice. When a
shard buffer holding row records receives a columnar batch, it converts
those records by moving them rather than copying them, and puts them back
unchanged if the conversion is refused.
Fixed
- Inputs a stale snapshot still names are now re-folded instead of held
until retention (issue #1763). When the maintain-role sweep holds a late
compaction's or erasure rewrite's superseded inputs because the catalog
snapshot still names them, in an hour outside the fold's reconcile window,
it now hands that hour to the scheduled fold of the same tenant and signal
in the same process. The hour stays queued through no-op, skipped and failed
folds, and the pair's next successful fold that advances the watermark
re-lists it so the inputs can be collected. Each fold takes at most
frontier_reconcile_max_hoursof a pair's oldest queued hours, the most it
reconciles, and leaves the rest queued for the next such fold. A maintain
process whose scheduled fold is disabled queues nothing. The queue holds up
to 256 tenant and signal pairs and evicts the earliest-inserted pair when
full, counted onravel_catalog_fold_refold_requests_dropped_total; an
evicted hour, or one lost when the process restarts, is sent again by the
next sweep pass that finds the hold. Issue #2606 extends the hand-off to a
hold found on any shard of the pair when more than one maintain process
runs. - SQL reads a segment whose declared-column statistics ignore resource and
scope values (issue #2159). SQL could answerMIN,MAX, a count or a
sum of a declared log column from.cstatcolumn statistics that ignored
values set on the resource or scope, so the answer could be wrong. This was
not limited to segments without a stamp: on a segment with a valid stamp,
SUM,COUNT(*) WHERE col <> kandGROUP BY colread the column
statistics without consulting the stamp, so they could be wrong there too.
A segment without a stamp is now read. Where a segment has a stamp,MIN,
MAXandCOUNTof the column come from the stamp, and the not-equal
count,GROUP BYcount,SUMandAVGcome from the column statistics
only when the stamp states the same minimum, maximum and NULL count;
otherwise the segment is read.ravel_sql::declared_stat_carrier_conflicts
and its warning are removed: the two carriers differ by design wherever a
value comes from the resource or scope, so the difference was not a defect. - A late flush no longer writes into an ingest hour another shard generation
owns (issue #2429). After a reshard, a flush routed under the earlier shard
count that opened in an hour the new generation owns alone wrote its rows at
the old count's indices, where a distributed pushdown, which splits an owned
hour by shard index, could split a series across two slice workers and
answer a count differently from a single node; the flush now hands its rows
to the owning generation's shards, counted on
ravel_ingest_rerouted_flushes_total, and the activation overlap hours still
write in place. A shard closes its mailbox before its shutdown flush and
writes every hand-back already queued, so rows handed to a shard set that is
shutting down are no longer lost. When such a hand-back finds its target
dead, condemned or closed, the rows are written in place at once; when it
finds the target's mailbox full on a hand-back to a larger shard set, they
are retried until the flush deferral cap, or until a drain's last pass, and
then written in place. Either way the write logs a WARN: readers find the
rows, but that hour can still split their series in a distributed query.
Rows written in place by a
binary without this fix stay split, and a rolling deploy keeps writing them
until the last process running such a binary exits. - Column-statistics decode failures are now on
/metrics(issue #2457).
ravel_catalog_column_stats_decode_refusals_totaland
ravel_catalog_column_stats_decode_panics_totalcount the column-statistics
objects a query could not decode and fell back from. The query guide and the
HTTP API reference no longer list a segment decode job as a cause of a 500 or
503, since no segment decode runs on the read CPU gate, and the
--max-queued-flusheshelp now calls the 2 h term the flush slack, not the
3 h read-side scan slack. - A query that races a compaction or retention delete retries on AWS S3
(issue #2462). On AWS S3 a query whose read of a segment raced a compaction
or retention delete failed with an access-denied error. The Query role
templatedeploy/iam/query.jsonnow carries the list grant on thel0/and
l1/data prefixes that makes S3 report the deleted object as missing, which
the query already retries once. Operators must re-apply
deploy/iam/query.json. - Every flush cadence check now refuses a server started from code (issue
#2465). An idle flush delay below the fast one, a fast delay whose strict
visibility budget reaches 3 s, and amin_flush_bytesat or above the ingest
target_byteswere refused only on the command line; a server started from
code now refuses each with the same message. A flush delay past about 292
years no longer wraps negative in the flush trigger age bound, the flush
deferral cap or the idle age threshold: those conversions saturate, so such
a delay leaves no flush deferral cap instead of a positive one. The shard
actors' own thresholds saturate as well (issue #2600, below). - A manifest key under an invalid table name no longer fails a tenant's
Parquet table listings (issue #2510). A.pqmkey the Query grant admits
undert/<tenant_hash>/pq/t/whose segment before/v/is not a valid
table name (upper case, reserved, or a path such asa/b) made
ravel-cli parquet lsandparquet sweepfail for the whole tenant; both
listings now skip it, count it and warn once per tenant, and
ravel-cli parquet repair --tenant <t> --stray --deleteremoves it. On S3
a key holding a control character, an empty segment or a.or..
segment still fails those listings and--strayitself, because the store
adapter cannot list it; delete that exact key with the Maintain credential
through an S3 tool.--straymarks and--deleteskips a key the store's
path encoding would send a delete of to a different key (one holding a
character such as~or%), which needs the same S3 tool, and a key under
a name reserved after tables could be created, such asl0, unless
--include-reserved-namesis passed; the command then prints what it
deleted, lists again and fails naming every such key still there. A key the
encoding changes is listed, and every key still there named, only when no
such key sits at a list page boundary, where the store adapter encodes the
page's continuation too and the listing repeats keys and fails, or skips
the keys after it.parquet repair --table <name> --deletemarks such a
key under the table's ownv/prefix the same way, deletes the deletable
flagged keys, and skips only the undeletable ones, leaving them in place
and exiting non-zero naming them, where it reported such a key deleted and
an empty segment could send the delete to a real version. ravel-cli parquet repair --delete-versionno longer refuses every
existing version on S3 (issue #2577). It confirmed the key by listing the
whole key as a prefix, which matches nothing once the S3 adapter appends
the path delimiter, so every version answered as not listed; it now lists
the table'sv/prefix starting just below the key.- One unwritable log object no longer stalls erasure or migration beyond its
own bucket (issue #2580). A log object whosestream_attrsblob the RLOG
writer refuses, in a bucket with no compaction record, used to fail that
bucket's erasure rewrite on every tick, and any failed bucket held back
completion of every pending erasure request for the tenant and signal. The
rewrite now reports the bucket as blocked by the object and writes nothing
to it, so every live object in it, healthy ones included, stays live: a
request whose window reaches any of them stays pending while the object
exists, and a request whose window reaches no live object of a blocked
bucket completes. Each such object is counted once per process on
the newravel_maintain_erasure_unwritable_objects_totaland warned once
with its key.ravel-cli maintain migrateno longer stops its walk at such
a bucket: it skips it, walks on, prints anunwritable_skippedline naming
the object, leaves the family's format floor unraised, and exits nonzero.
ravel_maintain_retention_held_out_of_window_objects_totaland
ravel_maintain_retention_held_by_lease_buckets_total, which were described
as metrics but never rendered, are now on/metrics. - The size limit on a log attribute value counts empty containers (issue
#2589). The limit did not count empty lists, empty maps or map entries with
empty keys, so a value made of them passed the limit at any size the request
body allowed. Each list, map and map entry now counts at least one byte, and
such an attribute past the limit is refused like any other oversized
attribute, asAttributeValueTooLong: dropped from a record, which is
kept, and on a resource or scope rejecting every record under it. - The shard actors' flush age thresholds and deadlines saturate instead of
wrapping (issue #2600). Each ofmax_flush_delay,put_retry_base_delay
andmax_flush_lifetimewas converted to nanoseconds with a cast: past about
292 years the count wrapped negative, and at or past 2^64 nanoseconds it was
truncated to an arbitrary value, not necessarily a negative one. All three
now saturate. Amax_flush_delaythat wrapped negative flushed a waiting
buffer on every tick; it now gives an age threshold no buffer reaches. A
max_flush_lifetimethat wrapped negative abandoned every flush as past its
lifetime; it is now an effectively unbounded lifetime. A
put_retry_base_delaythat wrapped negative widened the adaptive flush
delay's ceiling by the wrapped amount (one nanosecond at the largest value);
it now leaves no retry headroom under the visibility budget, so the ceiling
collapses to themax_flush_delayfloor. The server refuses such a flush
delay at startup and exposes neither of the other two as a flag, so only
code that builds anIngestConfigdirectly could reach these values. The
WARN for a flush written in place in an hour another shard generation owns
now logs once per tenant, cause (a hand-back target not live or, on a
drain, still full; or the retry run out to the flush deferral cap) and
ingest hour on each shard, rather than on every such flush. Each shard
remembers at most 4096 tenant and cause pairs, and past that a pair can
log again in the same hour. A flush of that tenant that opens in the scan set
or hands all its rows over re-arms it, and a shard that writes such a flush
in place with no rows handed over logs its next hand-back again. - A named-snapshot hold on any shard now reaches the pair's fold when more
than one maintain process runs (issue #2606). The maintain process that
owns shard 0 of a tenant and signal, the one that folds the pair, now runs
the sweep pass (superseded inputs, unreferenced parts, orphan collection)
for every shard of the pair, including the shards other maintain processes
own. Those processes keep running retention and compaction for their own
shards and no longer sweep them. A hold the superseded-input sweep finds on
any shard is therefore found in the process that folds the pair and handed
to its next fold in that process, instead of staying held until the fold's
frontier band reaches the hour. The sweep keeps its cadence, a pass scoped
to the head and tail hours on most ticks and a full pass every
--maintain-interior-reverify; for a shard it does not own, the sweeping
process lists that shard's hours once per tick to find them. A sweep of
such a shard that fails counts as a failed tick of the pair's shard 0 unit
onravel_maintain_units_stalled. No hold rule changes. ravel-serverstarts with default flags on hosts with 2 GiB of memory or
less (issue #2607). The overhead reserve taken from memory before the
memory budget is derived is now a quarter of the memory capped at 2 GiB,
raised to the memory the process holds outside the budget when that is
more, under a cgroup memory limit as well as without one. The memory held
outside the budget is a provisional 256 MiB baseline, plus the
--max-ingest-buffer-bytesceiling (512 MiB by default) in--mode all;
under--max-ingest-buffer-bytes 0the reserve is 2 GiB at every size,
because an unlimited buffer cannot be accounted. The fixed 2 GiB was more
than a t3a.small has (MemTotal1,912 MiB), so the budget derived to 0 and
startup refused with a message about--cache-max-bytes; that host now
reserves 768 MiB in--mode alland derives a budget between 1 GiB and
1,144 MiB, and reserves 478 MiB in--mode queryand--mode maintainfor
a budget between 1 GiB and 1,434 MiB. At an ingest ceiling of 1.75 GiB or
less, hosts and containers with aMemTotal(or cgroup memory limit) of
8 GiB or more derive the same figures as before; a nominal 8 GiB cloud
instance usually reports aMemTotalof about 7.6-7.8 GiB, which falls
just under that line, so its reserve drops by about 50-100 MiB rather than
staying at the fixed 2 GiB. A bounded ingest ceiling above 1.75 GiB in
--mode allnow raises the reserve above 2 GiB at every size, to the
ceiling plus 256 MiB: a 3 GiB ceiling reserves 3.25 GiB, so an 8 GiB host
derives a budget of at most 4,864 MiB instead of 6 GiB. That budget is
smaller, and it is the one that fits: the old budget plus the ceiling and
the baseline came to 9.25 GiB on an 8 GiB host. A derived budget below
256 MiB now refuses to start with its own message, namingMemTotalor
the cgroup limit, the reserve, the budget and--memory-budget-bytes, and
--max-ingest-buffer-byteswhen the ingest ceiling set the reserve;
--memory-budget-bytesand--disable-cachestill start there.
--mode allneeds a bounded ingest ceiling plus 512 MiB of memory to
start, 1 GiB at the default ceiling, andqueryandmaintain512 MiB.
Thememory_overhead_reserve_bytesstartup log line is printed only for a
derived budget; aflagorfallbackbudget subtracts no reserve and no
longer logs one.ravel-cli maintaintakes the same reserve at the
256 MiB baseline, which leaves its derived merge targets unchanged at the
default merge cursor budget.- A SQL
LIMIToverlogsis exact again (issue #2616).
SELECT body FROM logs LIMIT n,SELECT * FROM logs LIMIT nand any other
plain projection overlogswith aLIMITand noOFFSETreturned every
row of the segment the scan was reading, more thann, when the scan ran as
one partition. The logs scan now caps each partition at exactlynrows,
so those statements returnnrows.spans,alerts,auditand parquet
tables were not affected. ravel-serverenables jemalloc's background purge thread at startup
(issue #2633). Without it, jemalloc returns freed pages to the operating
system only during later allocator calls, so an idle server could keep them
resident. The startup log stamps the read-back state as
allocator_background_thread, and/metricsreports it as
ravel_process_allocator_background_thread. An_RJEM_MALLOC_CONFthat
setsbackground_threadis left as set, so
_RJEM_MALLOC_CONF=background_thread:falseturns the thread off. A failure
to enable it is a warning, not a startup failure.- A read-cache RAM entry no longer keeps a larger buffer alive than it is
charged for (issue #2633). The RAM tier charges an entry its length, but
stored the value it was given, so a value that was a slice of a larger
buffer (a response body that is a view into an HTTP read buffer) held that
whole buffer for as long as the entry stayed resident. Every RAM admission
(insert, theget_or_fetchadmission, and the tiered cache's disk
promotion and dual-tier admissions) now stores an exact-size copy unless the
value is already the only handle to an allocation of exactly its length,
and copies nothing on a hit.CacheMetricscounts the copies and the bytes
they copied (admission_copies,admission_copied_bytes); they are not
yet rendered on/metrics.
Added
- SQL accepts
POSITION(x IN y),SUBSTRING(x FROM n [FOR m]), and
unquotedsubstr(...)/substring(...)calls (issue #2583). Each
previously failed to plan; they now answer through the admittedstrpos
andsubstrfunctions. The conformance registry also records the
OVERLAY,STRUCT(...),{'a': 1}, struct field access, map subscript,
trace id hex literal,count(), andcount(*) OVER ()syntax that already
planned. /metricscounts the in-place writes after a generation mismatch
(issue #2600). The new counter
ravel_ingest_generation_mismatch_in_place_writes_totalcounts buffers
written in an ingest hour another shard generation owns, where a
distributed aggregate can differ from the single-node answer; alert on any
increase.- SQL supports
SELECT DISTINCT ON (...)when itsORDER BYfully
determines the row each group keeps (issue #2629). Every such statement
used to fail to plan with "There is no UDAF named first_value in the
registry". Every selected column must also be anORDER BYterm, as in
SELECT DISTINCT ON (series_id) series_id, ts, value FROM samples ORDER BY series_id, ts DESC, value; a statement whoseORDER BYleaves a selected
column out, or that has noORDER BY, is refused with an HTTP 400 naming
DISTINCT ON, because the row it kept would depend on storage order. The
result is the same at every partition count. Callingfirst_value
directly is still refused, in every spelling: a quoted"first_value"(...)
used to fail with a generic planning error and now gets the same
excluded-aggregate error as the unquoted name.
Security
- A Parquet footer can no longer abort the server through an allocation
sized from a count it declares (GHSA-p43g-f7vx-7qfr). Theparquet
decoder sizes a list, a schema group's children and each row group's
column chunks from counts in the footer before it reads what they
describe, and reads a field by its id whatever type the field's header
gives, so a footer of a few bytes in an external table's file could ask
for an allocation of many gigabytes; one the allocator refused aborted
the server for every tenant on it. Reaching it needed a caller with the
ddlcapability, an operator location grant and a server started with
--parquet-profiles, or write access to a granted bucket. Ravel now
walks the footer the way the decoder reads it before decoding, and
refuses as a corrupt file a footer longer than 64 MiB, a field whose
header type differs from the type the decoder reads for it, a collection
declaring more elements than the bytes after it could hold, a skipped
field the decoder would misread or cannot skip, a field the decoder
reads appearing twice in one struct, a schema whose
num_childrenvalues are negative, exceed the elements after them, do
not form exactly one tree or nest deeper than 64 levels, and more than
32,768 row groups. An embedded Arrow schema (theARROW:schema
key-value) is refused when it is longer than 4 MiB, and is otherwise
verified before anything decodes it with bounds tied to its length (an
apparent size of four times its length, at most one table per eight
bytes). Ravel's tighter check runs before the decoder's own verification,
whose defaults of 2 GiB and a million tables let a small schema whose
fields share one long name convert to gigabytes. Decoding a footer that
passes first reserves an estimate of what the decoder allocates,
converting its Arrow schema included, from the memory budget, and is
refused as memory exhausted when the budget cannot hold it. Creating a
table holds the reservation until the table's schema is built from the
decoded footer. A scan releases it once the footer cache has admitted the
decoded footer, charging it at least that estimate. A footer charged more
than the cache's whole bound (metadata_cache_bytes, 64 MiB in the
server) is read uncached, and the scan's reader holds its reservation for
as long as the scan holds the reader, so the metadata stays charged to
the budget. Each scan
open still converts the footer's Arrow schema without a reservation,
within the estimate. A footer that
panics the decoder, such as an INT96 column whose statistics are longer
than 12 bytes, is refused as a corrupt file.
What's Changed
- docs: park ADR-2467's routing and restate decisions 5 and 6 by @pmoust in #2597
- fix(ingest): hand back a late flush whose hour another generation owns by @pmoust in #2599
- fix(server): bind every startup cadence check to the library entry by @pmoust in #2603
- feat(server): export the hand-back reason and in-place mismatch writes by @pmoust in #2605
- refactor(logseg): encode the stream directory once, drop state early by @pmoust in #2601
- fix(ingest): saturate the shard actors' duration conversions by @pmoust in #2609
- docs: say SQL creates Parquet tables in the README by @pmoust in #2610
- fix(otlp): count containers in the log attribute size limit by @pmoust in #2611
- feat(server): hand snapshot-held hours from the sweep to the fold by @pmoust in #2612
- feat(sql): admit POSITION and SUBSTRING syntax and pin planner rewrite targets by @pmoust in #2618
- fix(bench): drop the D7 concurrency error-ratio ceiling by @pmoust in #2617
- perf(cli): bound stride cursors' Arrow read-ahead to one batch by @pmoust in #2621
- fix(sql): make a pushed LIMIT exact in the logs scan by @pmoust in #2620
- fix(server): scale the overhead reserve on small hosts by @pmoust in #2619
- docs: add ADR-2614 bound the loader's memory by bytes by @pmoust in #2623
- fix(iam): condition key-epoch writes, let query list data prefixes by @pmoust in #2631
- docs: plain-language pass over the reader lanes by @pmoust in #2587
- fix(sql): keep DISTINCT ON with tied ordering out of spill by @pmoust in #2630
- test(cli): pin the zstd-level test's clock and check the level directly by @pmoust in #2634
- fix(parquet): walk a footer the way the decoder reads it, bound it by @pmoust in #2638
- perf(ingest): move batches into shards instead of cloning them by @pmoust in #2636
- fix(pqtable): skip manifest keys under an invalid table name by @pmoust in #2641
- fix(sql): read declared columns a stamp does not cover by @pmoust in #2640
- fix(maintain): let one unwritable log object block only its own bucket by @pmoust in #2642
- test(sql): refresh the logs-scan fetch test header and prune assertion by @pmoust in #2643
- fix(bench): end the concurrency phase when the engine becomes unreachable by @pmoust in #2644
- perf(logseg): store columnar attribute cells as typed columns by @pmoust in #2645
- feat(sql): support DISTINCT ON with a total-order ORDER BY by @pmoust in #2648
- feat(cli): hold ravel-cli load's log batches under a byte budget by @pmoust in #2650
- docs(adr-2040): scope the dead-engine clause to its own violation by @pmoust in #2646
- fix(server): purge freed jemalloc pages on a background thread by @pmoust in #2652
- fix(cache): store an owned copy when an entry pins a larger buffer by @pmoust in #2653
- docs: correct ADR-2467's amendment and record its outcome by @pmoust in #2655
- docs: add ADR-2430 authenticated .pqm manifest by @pmoust in #2649
- docs: add ADR-2637 raw-key S3 listing by @pmoust in #2654
- fix(server): own the whole superseded sweep on the fold owner by @pmoust in #2647
- fix(guards): retry a failed query in the duplicate-dispatch guard by @pmoust in #2622
- docs: record the loader memory acceptance and the large-object recipe by @pmoust in #2656
- docs: point loader sizing figures at the R5 recipe by @pmoust in #2658
- chore(release): prepare 0.23.0 by @pmoust in #2659
Full Changelog: v0.22.0...v0.23.0
Downloads
The attached binaries were extracted unmodified from the signed container
images this release published, so each one is byte-identical to the file inside
the image you can pull. They are glibc-dynamic, built against Debian 12
(the runtime image is gcr.io/distroless/cc-debian12), so they need glibc 2.36
or newer. On an older distribution, run the container image instead:
docker pull ghcr.io/nofireai/ravel-server:0.23.0One binary per platform, named <binary>-<os>-<arch>:
ravel-server-linux-amd64,ravel-server-linux-arm64ravel-cli-linux-amd64,ravel-cli-linux-arm64ravel-operator-linux-amd64,ravel-operator-linux-arm64ravel-ingest-router-linux-amd64,ravel-ingest-router-linux-arm64
Verifying the downloads
SHA256SUMS covers every attached binary and symbol archive. Check the files
you downloaded, then verify SHA256SUMS itself against the signature and
certificate beside it. The signature is keyless, bound to this release
workflow's identity, so there is no key to obtain:
sha256sum --check --ignore-missing SHA256SUMS
cosign verify-blob \
--certificate SHA256SUMS.pem \
--signature SHA256SUMS.sig \
--certificate-identity 'https://github.com/NOFireAI/ravel/.github/workflows/publish-images.yml@refs/tags/v0.23.0' \
--certificate-oidc-issuer 'https://token.actions.githubusercontent.com' \
SHA256SUMSDebug symbols
The shipped binaries are stripped of debug info but keep their symbol table,
so a backtrace resolves function names with nothing extra installed. Line
numbers come from the matching symbols archive. Each binary records the
basename of its symbol file, so unpacking the archive next to the binary is
enough and no symbol-file step is needed:
tar -xzf ravel-debug-symbols-linux-amd64.tar.gz
gdb ./ravel-server-linux-amd64gdb looks for ravel-server.debug in the same directory (and in
.debug/), which is the name inside the archive. Do not rename the extracted
.debug files.