Skip to content

feat : support for out-of-orderness tolerance - #2245

Open
sherali42 wants to merge 5 commits into
filodb:developfrom
sherali42:out-of-orderness-rebased
Open

feat : support for out-of-orderness tolerance#2245
sherali42 wants to merge 5 commits into
filodb:developfrom
sherali42:out-of-orderness-rebased

Conversation

@sherali42

Copy link
Copy Markdown
Contributor

Pull Request checklist

  • The commit(s) message(s) follows the contribution guidelines ?
  • Tests for the changes have been added (for bug fixes / features) ?
  • Docs have been added / updated (for bug fixes / features) ?

Summary

Adds out-of-order (OOO) sample ingestion to FiloDB: time-bucketed,
per-partition aggregation that accepts samples whose timestamps fall behind
the latest seen timestamp by up to a configurable tolerance window. Standard
TimeSeriesPartition rejects such samples outright; this change replaces the
hard rejection with bounded in-memory aggregation, finalizing each bucket to a
single chunk row once an event-time watermark passes it.

Opt-in per schema. No on-disk format change. Cassandra, downsampling, query
planners, and persistence are unaffected. Full design in
docs/specs/ooo-sample-ingestion.md;
ADR in doc/adr-aggregating-buffers.md.

Motivation

Standard TimeSeriesPartition.ingest enforces strict in-order arrival —
anything older than the latest chunk's endTime is dropped and counted on
outOfOrderDropped. In production this loses real data:

  • Producer skew. Retries, batching, and clock drift push samples behind
    the in-order watermark by seconds to minutes.
  • Delta-temporality metrics. A dropped delta sample doesn't just leave a
    hole — it permanently distorts every downstream rate() / delta() that
    crosses the gap, because nothing else re-sums the missing delta.

Design (one paragraph)

AggregatingTimeSeriesPartition extends TimeSeriesPartition and is
selected at partition-creation time when the schema declares aggregators.
Incoming samples are aggregated into fixed-width event-time buckets keyed by
ceilToBucket(sampleTs, intervalMs). A watermark — the latest sample
timestamp on the partition — advances forward only; buckets whose
timestamps fall more than oooToleranceMs behind the watermark are
finalized and handed to the existing TimeSeriesPartition.ingest pipeline
as a single row. Active (not-yet-finalized) buckets are queryable via
AggregatingRangeVector, which merges in-memory bucket rows with finalized
chunk rows. Kafka commit offsets are held back to the earliest active
bucket so crash recovery never loses samples that were aggregated but not
yet flushed.

The model is purely event-time. No wall-clock dependency in tolerance
checks, finalization, or query visibility — replay is deterministic.

What's in the change

Per-schema config (additive)

Key Meaning
aggregators List of name(colId), e.g. ["dSum(1)", "hSum(3)"]
aggregation-interval Bucket width (HOCON duration, e.g. 1m)
aggregation-ooo-tolerance Acceptance window (HOCON duration, e.g. 2m)

A non-empty aggregators list is the only trigger. Schemas without
aggregators are completely unaffected. Canonical example schema
aggregating-delta-histogram-v2 is registered in filodb-defaults.conf.

Core components

  • AggregatingTimeSeriesPartition — routes ingest through
    BucketAggregationState; routes query through AggregatingRangeVector
  • BucketAggregationState — TreeMap of per-bucket aggregators with object
    pooling to limit allocation pressure
  • Aggregator trait + Sum/Min/Max/Last/First/Count/HistogramSum/
    HistogramLast with type-specialized add paths (no boxing on Double/Long)
  • BinaryHistogram.addValuesTo — in-place histogram accumulation without
    per-sample allocation (the only new public API on existing types)
  • AggregatingRangeVector + MergingRangeVectorCursor — merge finalized
    chunk rows with active in-memory bucket data, with a per-cursor scratch
    pool for histogram snapshots
  • Watermark-based Kafka offset hold in TimeSeriesShard.prepareFlushGroup
    prevents commit past samples still held in active buckets

Out of scope (intentionally)

  • Per-column tolerance windows (schema-wide only)
  • Late-arriving samples beyond the tolerance window (still rejected and
    counted on outOfOrderDropped)
  • Re-aggregating already-finalized buckets (silently dropped to avoid
    duplicates)

Tests

  • 6 new specs across core/src/test/scala/filodb.core/memstore/aggregation/
    and core/src/test/scala/filodb.core/{memstore,query}/:
    BucketAggregationStateSpec, AggregatorSpec, ColumnAggregatorSpec,
    AggregatingTimeSeriesPartitionSpec, AggregatingRangeVectorSpec,
    EventTimeWatermarkSpec
  • Schema-level config coverage in ColumnSpec and SchemasSpec
  • PeriodicSamplesMapperSpec updated for AggregatingRangeVector query path
  • OOOAggregationBenchmark (JMH) — scalar and histogram-typed scenarios for
    ingest, query, and finalization throughput / allocation
  • AggregationHotPathBenchmark — micro for the hot ingest path

Rollout / risk

  • Off by default — schemas opt in by declaring aggregators. Existing
    schemas behave identically.
  • No on-disk format change. No protocol change. No query-engine API change.
  • Crash recovery: the Kafka offset hold guarantees in-flight bucket samples
    are re-ingested on replay; aggregation is deterministic and idempotent
    under replay.

Test plan

  • Smoke a dev cluster with aggregating-delta-histogram-v2 schema and
    verify OOO samples (vs. previous outOfOrderDropped behavior)
  • Verify Kafka offset hold under graceful shutdown + restart
  • Compare ingest CPU / allocations against a non-aggregating schema
    (JMH locally; production canary in a low-traffic shard)
  • Confirm queries against a partition with active buckets return the
    in-memory data merged with finalized chunks

@sherali42
sherali42 force-pushed the out-of-orderness-rebased branch from 8af39d6 to 23ab46d Compare July 15, 2026 16:59
sherali42 and others added 3 commits July 17, 2026 09:50
Adds a time-bucketed aggregation layer for time series partitions to
accept samples within a configurable out-of-order tolerance window.
Samples are aggregated in memory by bucket and persisted to chunks once
the event-time watermark passes them.

Core components:
- AggregatingTimeSeriesPartition (extends TimeSeriesPartition) routes
  ingest through BucketAggregationState; query through AggregatingRangeVector
- BucketAggregationState manages per-bucket aggregators using a TreeMap
  with object pooling to limit allocation pressure
- Aggregator trait + Sum/Min/Max/Last/First/Count/HistogramSum/HistogramLast
  with type-specialized add paths (no boxing on Double/Long)
- BinaryHistogram.addValuesTo enables in-place histogram accumulation
  without per-sample allocation
- AggregatingRangeVector + MergingRangeVectorCursor merge finalized chunk
  rows with active in-memory bucket data, with a per-cursor scratch pool
  for histogram snapshots
- Watermark-based Kafka offset hold in TimeSeriesShard.prepareFlushGroup
  prevents commit past samples still held in active buckets, so crash
  recovery via Kafka replay is safe
- Schema-level config: aggregators (e.g. dSum(1), hSum(3)), aggregation-
  interval, aggregation-ooo-tolerance — single source of truth at the
  schema level rather than per-column

The model is purely event-time. No wall-clock dependency in tolerance
checks, finalization, or query visibility — see
docs/specs/ooo-sample-ingestion.md for the full semantic.

Co-Authored-By: Claude Opus 4.7 (1M context via Établi <noreply@anthropic.com>
OOOAggregationBenchmark with scalar and histogram-typed scenarios for
ingest, query, and finalization throughput / allocation. Test coverage
across BucketAggregationStateSpec, AggregatorSpec, AggregatingTimeSeries
PartitionSpec, AggregatingRangeVectorSpec, EventTimeWatermarkSpec.

Co-Authored-By: Claude Opus 4.7 (1M context via Établi <noreply@anthropic.com>
- docs/specs/ooo-sample-ingestion.md: canonical feature reference
- doc/adr-aggregating-buffers.md: design rationale (ADR)
- docs/plans/2026-04-30-agg-buffer-heap-optimization-tier1-tier2-design.md:
  heap optimization design (Tier 1 implemented, Tier 2 rejected after
  benchmarking)
- doc/ingestion.md: section on OOO ingestion behavior
- README.md: feature mention

Co-Authored-By: Claude Opus 4.7 (1M context via Établi <noreply@anthropic.com>
@sherali42
sherali42 force-pushed the out-of-orderness-rebased branch from b1a1e18 to 352606f Compare July 20, 2026 00:43
sherali42 and others added 2 commits July 19, 2026 20:56
…interpolation

Extends Histogram.quantile's evenDistribution mode to use the order-statistic
percentile convention (rank = (N-1)*q + 1) instead of the Prometheus q*N rank.
Within each bucket, samples are reconstructed as evenly spaced at k*width/(count+1),
and the quantile interpolates between the two samples straddling the rank — which may
live in different buckets, so interpolation can cross a bucket boundary. The top
fractional sample snaps toward `max` (only when it shares a bucket with its lower
neighbour or past the last observation). Scheme-aware bucket edges handle the
GeometricBuckets minusOne (integer power-of-2) case where bucket 0's lower edge is 1,
not 0. The default Prometheus linear/exponential path is unchanged.

Adds a value-pinned test with a 64-bucket Base2 exponential histogram covering
interior quantiles, cross-bucket-boundary interpolation (p25), tail max-snap, and an
assertion that the even and default paths diverge at the tail.

Applied via `git apply --3way` (clean, no conflicts). Only Histogram.scala and
HistogramTest.scala changed. `sbt core/compile` succeeds; all 27 HistogramTest cases
pass (1 pre-existing ignored).

scalastyle method.length / cyclomatic.complexity are suppressed around quantile as
the branch is now substantially longer.

Co-Authored-By: Claude Opus 4.8 (1M context) via Établi <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) via Établi <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant