Changelog¶
The authoritative changelog is CHANGELOG.md in the source repository.
This page includes runtime changes through Siglake merge commit ca8d598 from
2026-09-08. It also includes a 2026-09-09 correction to the Jaeger name-list
cache limits. The overview reflects the 2026-09-11 durability and OTLP/gRPC
defaults from merge commits b23bffa and 94ea42e.
0.1.0: initial public release¶
Siglake is a horizontally-scalable, OTLP-native log analytics platform on Parquet v2, Apache Iceberg and DataFusion.
Timestamp handling, Jaeger reads, and what to do on upgrade¶
0.1.0 is the first public release, so there is no earlier release line to upgrade from. Five changes inside the line need an action if you are moving a pre-release install onto the release binary.
| Change in the 0.1.0 line | What to do on upgrade |
|---|---|
The 2026-09-06 timestamp contract: tables are Iceberg format version 2, events.timestamp is a microsecond timestamptz, and the required timestamp_ns long holds the OTLP nanosecond verbatim. |
Recreate any warehouse written before that change. Those warehouses are format version 3 and cannot be migrated. Where a reader needs nanosecond exactness, convert timestamp_ns with that engine's own function: see Reconstruct nanosecond timestamps from timestamp_ns. |
| Additive schema changes are gated: a binary newer than the table refuses writes to columns the table lacks. | Run siglake migrate-schema --all-tables --all-namespaces before or with the new binary. Each control plane runs it on a trigger: the chart renders the pre-upgrade hook while schemaMigration.enabled is true, its default, and the operator renders the Job when you raise the monotonic spec.schemaVersion counter; an unset counter, or 0, renders no Job. Run the command yourself wherever neither trigger fires. |
| The 2026-09-08 bounded Jaeger reads, with the 2026-09-09 name-list cache correction: the shim derives trace, span-row, Arrow-byte and distinct-name ceilings from each request's admission reservation. | Re-check every Grafana Jaeger panel and API client. A search limit above the derived trace ceiling now returns 400 before planning, and a result that crosses another ceiling is refused whole with 413. Narrow the time range, service, operation, tags or limit on the panels that refuse. |
Acknowledgement calls fsync(2) by default, covering segment bytes and the directory entries that name the segment. |
Expect acknowledgement latency to follow the WAL volume's flush latency. Send ?commit=auto per request where a client prefers the older write(2) boundary and can lose unflushed rows on a node crash. |
The ingester listens for OTLP/gRPC logs and traces on 0.0.0.0:4317 by default. |
Allow or block port 4317 deliberately in your NetworkPolicy and Service. Set ingester.otlpGrpc.enabled: false if you do not want the listener; see Configure OTLP over gRPC. |
Rolling ingesters and drains across a version change also takes one retention setting, applied before the first new binary starts. See Consumed-proof rolling upgrades.
2026-09-09: Jaeger name-list cache correction¶
Later 0.1.0 changes replaced the cache limits in the 2026-09-08 entry. A complete Jaeger name list is cached when its retained arena and stored lookup string together fit the 512 KiB per-entry cap. The shim's derived name ceiling supplies the row bound. The SQL cache's 128-row entry cap does not apply to these lists. See the Jaeger shim reference.
2026-09-08: bounded Jaeger reads¶
The Jaeger HTTP shim now derives trace, span-row, accumulated Arrow-byte, and
distinct-name ceilings from each request's admission reservation. An excessive
search limit is rejected with 400 before admission or planning. A result
that crosses another ceiling is refused whole with 413, no data, and no
Retry-After. The four refusal causes are exposed as jaeger_* values on
siglake_query_breaker_trips_total. See the
Jaeger shim reference. Shipped in
Siglake merge commit d4b60e1.
The span plan now fetches at most one row beyond its row ceiling. A refused
request no longer materializes the full match before returning 413.
2026-09-08: release hardening¶
- An unknown field in a SQL request's
limitsobject now returns422before planning, execution or batch enqueue. Unknown top-level keys remain allowed, and known limits above their tier ceiling remain clamped. The row-limit field ismax_rows_returned, not the response fieldmax_rows. See the SQL query reference. - Delete sweeps now report claimed tasks that remain non-terminal for more than
twice
SIGLAKE_DRAIN_WATCHDOG_SECS. Thesiglake_compactor_delete_tasks_stalled_total{state}counter drives theSiglakeDeleteTaskStalledalert. The dashboard-onlysiglake_compactor_delete_tasks_nonterminal{state}gauge records the last complete observation. See Monitor Siglake. - The Jaeger service and operation name routes now cache complete lists by table snapshot. Lists above 128 rows or 4 MiB remain uncached. See the Jaeger shim reference.
2026-09-06: portable timestamp contract¶
Siglake tables now use Iceberg format version 2. events.timestamp is a
microsecond timestamptz (Parquet INT64 TIMESTAMP(MICROS, UTC)), and the
required timestamp_ns long sibling preserves the OTLP nanosecond verbatim.
Trino 483, Spark 3.5.9 with Iceberg 1.11.0, DuckDB 1.5.5 with core iceberg
extension 45163a28, and PyIceberg 0.12.0 all read a fresh 2,000-row fixture
and agreed on its exact timestamp_ns bounds. See
External query engines.
Release overview¶
Ingest accepts OTLP/HTTP logs over
POST /v1/logs and an
Elasticsearch-compatible _bulk surface with per-index document mappings. The
ingester also listens for OTLP/gRPC logs and traces on 0.0.0.0:4317 by
default. The WAL force-seals on graceful shutdown. Backpressure lanes and
per-tenant rate budgets bound the accept path. Auth is
Authorization: Bearer <token> or OIDC.
Durability rests on the WAL. A request is acked once its rows are in the WAL.
By default, the ingester calls fsync(2) before it acknowledges the request.
The sync covers segment bytes and the directory entries that name the segment.
A seal publishes the sealed name durably before unlinking the active copy. The
power-loss guarantee assumes ext4 or xfs on a node-attached volume. A network
filesystem provides whatever its fsync(2) and rename semantics guarantee.
Send ?commit=auto to accept an acknowledgement after write(2) instead. A
node-level crash or power loss can discard rows that the kernel has not
flushed in that mode. Sealed WAL segments are mirrored to object storage by
default, but the asynchronous upload is outside the acknowledgement path.
Neither mode waits for the rows to become queryable; that follows the
drain's cadence (measured p50 ~5.5 s).
Multi-tenancy is single-tenant by default. Every
ingest request routes to default. An X-Scope-OrgID naming another tenant is
refused with 403 over HTTP or PermissionDenied over OpenTelemetry Protocol
(OTLP)/gRPC. Set ingester.oidc.tenantClaim to route by a verified JSON Web
Token (JWT) claim, or ingester.trustScopeHeader to trust the header. The
claim option requires a usable claim, and a present header may only agree with
it. Optional --allowed-tenants and --max-tenants bound what a client can
create. The query server applies the claim rule through its own
--oidc-tenant-claim: a claim that is missing, blank, non-string, over 128
characters or outside [A-Za-z0-9_-] is a 403 before routing rather than a
fall back to the default namespace, and identifiers are validated rather than
repaired, so acme.corp cannot reach acmecorp's data.
Storage is Iceberg tables on any object store:
physically time-ordered Parquet with per-file group-count footers (typed
columns included), token and trigram bloom filters, and continuous leveled
compaction with overlap-depth convergence. The compactor's
siglake_table_live_data_files gauge is read from the snapshot summary each
cycle, so it stays exact on tables too large to walk inside the sampler's
per-table budget. The level and depth gauges can lag, and
siglake_table_gauges_sampled_at_seconds says when they were last walked.
Group-count delta writes get four attempts with retry and failure metrics. An
exhausted write leaves a durable marker for the maintenance compactor to
rebuild automatically. siglake_group_count_auto_rebuilds_total{table,outcome}
records the result, and SiglakeGroupCountDeltaLost fires only when that
rebuild fails or remains incomplete. As the operator fallback,
siglake rebuild-group-counts repairs the aggregate from committed files
without double-folding late deltas; its --admit-typed-columns adds the typed
columns a table created before typed side aggregates never carried, so no
rewrite is needed for those.
Query is DataFusion SQL (/api/v1/sql) with a
transparent distributed coordinator. Ordered-scan early stop in both time
directions (reversed tail-chunk decode). Zero-scan fast paths for counts,
group-bys, histograms, distinct counts, and dimensional or typed filters served
from footers and snapshot-keyed side aggregates. Snapshot-keyed result and
decoded-chunk caches, invalidated by commit and never by TTL. Per-request scan
and cost attribution (stats.scan). Bounded query spill uses
SIGLAKE_QUERY_SPILL_DIR and SIGLAKE_QUERY_SPILL_MAX_BYTES, the chart's
query.spill.* ladder, and a matching query-pod ephemeral-storage limit.
Memory-pool or spill-cap refusals return 503 plus Retry-After, including
refusals forwarded from workers, and increment
siglake_query_breaker_trips_total{breaker="pool_exhausted"}.
Freshness comes from sealed-WAL buffer serving. Records are queryable in seconds, before commit, and are folded exactly into every fast path.
Attribute capture is lossless. OTLP resource and log attributes stay queryable
via attr_get(). Hot keys auto-promote to typed columns with backfill and
query rewrite (opt-in).
Streaming consumers read the WAL directly.
siglake_wal::consumer::SegmentConsumer is a supported interface for reading
WAL segments as they seal, with a durable cursor, at-least-once delivery,
retention that waits for slow consumers (bounded, so a stuck consumer degrades
to "you missed some" rather than filling the disk), and CRC integrity. See
docs/CONSUMING_SEGMENTS.md in the source repository. The four-tier semantic
detection pipeline that shipped inside Siglake through 2026-08-29 was moved out
to run entirely on top of this interface and is maintained as its reference
consumer.
Schema evolution is additive and gated. A table records the schema version it
is at. When the running binary is newer, the columns the table lacks cannot be
written, so the write is refused, naming the column and the remedy, rather than
silently dropped. Run siglake migrate-schema --all-tables --all-namespaces;
it is additive-only and idempotent, and --all-namespaces matters on any
multi-tenant install because events exists once per tenant namespace. Each
control plane runs it on a trigger. The chart renders the pre-upgrade hook
while schemaMigration.enabled is true, its default. The operator renders
the Job when you raise the monotonic spec.schemaVersion counter, and holds
the workload rollout until the Job finishes; the counter says "run a
migration" rather than naming a target version, and leaving it unset or at 0
renders no Job. See Schema
migrations. Anywhere neither
trigger fires, run the command yourself before the new binary serves writes.
The operational surface is Helm charts and a
Kubernetes operator (SiglakeCluster CRD), with
per-tier spec.resources.<tier>, the chart's 4Gi query-pod default,
schema-migration jobs, and offline Helm-release adoption. Invalid
configurations set InvalidSpec with one of ten reasons rather than being
partly honored; the former QueryAutoscalingIgnored condition is removed. A
Terraform/EKS reference deployment covers BYOC installs. The release also
carries Prometheus metrics throughout, Prometheus alert rules for the
silent-loss counters, audit logging, and retention, GC and delete sweeps.
Interoperability rests on the open format. The warehouse is plain
Iceberg-on-Parquet at format version 2, with no v3-only type in any schema.
Event time is timestamp, a microsecond timestamptz (Parquet INT64
TIMESTAMP(MICROS, UTC)) that every Iceberg reader maps. events also carries
timestamp_ns, a required long holding the OTLP time_unix_nano value
verbatim, so nanosecond exactness is available externally and Siglake's own
scans lose nothing. Each engine reconstructs the nanosecond timestamp with its
own conversion, listed under Reconstruct nanosecond timestamps from
timestamp_ns.
Trino, Spark, DuckDB and PyIceberg all read the warehouse without Siglake in
the path;
the versions tested and the measured output are in
External query engines .
Warehouses written before this change are format version 3 and must be
recreated, not migrated (docs/DESIGN_time_ordered_storage.md, "Timestamp
contract").
See the Performance section of README.md for measured comparisons against
Quickwit, Elasticsearch, ClickHouse, and a vanilla-Parquet DuckDB baseline.
Before 0.1.0¶
The project was named knulps until 2026-06-12. Pre-rename docs are kept in the internal history, and older git history uses the old name.
Development was phased: the storage engine, ingest, and query (phases 1 to 4);
scale-out, multi-tenancy, and hardening; the former in-tree detector pipeline
(phase 5, retired in source commit e2a6ab2 on 2026-08-29); and the 2026-06/07
performance arc. See About.