Skip to content

Add velocity-backend-jdbi: the Postgres tumbling reference backend - #8

Merged
wolpert merged 1 commit into
mainfrom
velocity-backend-jdbi
Jul 19, 2026
Merged

Add velocity-backend-jdbi: the Postgres tumbling reference backend#8
wolpert merged 1 commit into
mainfrom
velocity-backend-jdbi

Conversation

@wolpert

@wolpert wolpert commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

Summary

velocity-backend-jdbi — the first real velocity-spi backend (the v1 Postgres/JDBI tumbling
reference, P10/§8), proven against a real Postgres via Testcontainers. This is the template every
future backend copies.

What's in it

  • JdbiVelocityBackend implements CountStore/SumStore/DistinctStore + TumblingSupport +
    SeedSupport. Exact, ATOMIC read-your-write: each apply is one transaction whose per-bucket
    upsert (INSERT ... ON CONFLICT ... DO UPDATE ... RETURNING) returns the post-write value, so
    concurrent applies never lose an increment (NFR-6, acceptance Bump actions/setup-java from 5.5.0 to 5.6.0 in the gh-actions group #1).
  • Backend owns bucketing + the clock (ADR 0003 / FR-3): a tumbling window resolves to the aligned
    current bucket floor(nowMs/Dms)·Dmsbyte-identical to velocity-testkit's WindowMath, so a
    JDBI value equals the in-memory reference. Storage identity is (namespace, subject, aggregation, window, bucket)not feature name (a QueryTuple carries none), matching the in-memory
    StoreKey.
  • Three per-aggregation tables (counts / sums NUMERIC(38,0) cents / distinct_members BYTEA, exact).
    Idempotent migrate(). Exact-only distinct (HLL degradation is a documented follow-up). An
    unsupported (sliding) window → Failure(UNSUPPORTED_WINDOW) on both paths, never a silent 0;
    HLL seed rejected. No Dagger/Dropwizard/Jackson.

Tests (real Postgres via Testcontainers)

33 tests, 0 skipped, LINE 100% / BRANCH 97.4% (gate 80/70). Includes:

  • the tumbling-compatible shared TCK scenarios — TumblingScenarios (bucket reset),
    SeedSupportScenarios (seeded≡recorded, acceptance #16; HLL/unsupported rejects),
    CapabilityConformanceScenarios (declaration↔impl agreement, unsupported→Failure, distinctHllSliding=false);
  • a 16-thread concurrency test on real Postgres asserting the final bucket == exactly 800 (atomic
    upsert — acceptance Bump actions/setup-java from 5.5.0 to 5.6.0 in the gh-actions group #1);
  • bespoke tumbling Count/Sum/Distinct/Purge tests (see the TCK note below).

Review

change-validator agent: VERDICT APPROVE. It confirmed the round-trip fix (identical key on both
paths; bucketing byte-identical to WindowMath), the concurrency test's exact atomicity, tumbling-only
contract fidelity, no forbidden imports — and diffed the bespoke tests method-by-method against the
shared scenarios, confirming they assert the same facets over tumbling and are not weaker
.

⚠️ Follow-up before velocity-backend-redis (tracked)

The window-agnostic TCK scenarios (Count/Sum/Distinct/PurgeStoreScenarios) currently hardcode a
sliding window
, so a tumbling-only backend can't run them as-authored — which is why this PR uses
bespoke tumbling tests for those four aggregations. The fix is to parameterize/split those scenarios
by window
in velocity-testkit so every backend runs the shared conformance scenarios (this also
subsumes two minor coverage nits the reviewer noted: SUM/DISTINCT cross-namespace isolation and the
query-side unsupported-window path are only exercised via the COUNT store today, though the key/guard
code is shared). CountStoreScenarios genuinely mixes three windows, so this is a deliberate TCK
refactor — do it before Redis so Redis inherits the shared harness.

./gradlew :velocity-backend-jdbi:buildBUILD SUCCESSFUL (Testcontainers postgres:16-alpine).

🤖 Generated with Claude Code

The first real velocity-spi backend (v1 tumbling reference, P10/§8) — Postgres via
JDBI, validated against a real Postgres with Testcontainers.

- JdbiVelocityBackend implements CountStore/SumStore/DistinctStore + TumblingSupport
  + SeedSupport. Exact, ATOMIC read-your-write: each apply is one transaction whose
  per-bucket upsert (INSERT ... ON CONFLICT ... DO UPDATE ... RETURNING) returns the
  post-write value, so concurrent applies never lose an increment (NFR-6, acc. #1).
- Backend owns bucketing + the clock (ADR 0003/FR-3): a tumbling window resolves to
  the aligned current bucket floor(nowMs/Dms)*Dms — byte-identical to velocity-testkit
  WindowMath, so a JDBI value equals the in-memory reference. Storage identity is
  (namespace, subject, aggregation[dimension + type-via-table], window, bucket) — NOT
  feature name (a QueryTuple carries none), matching InMemory's StoreKey.
- Three per-aggregation tables (counts / sums NUMERIC(38,0) cents / distinct_members
  BYTEA, exact). Idempotent CREATE ... IF NOT EXISTS migrate(). Exact-only distinct
  (HLL degradation is a documented follow-up). Unsupported (sliding) window ->
  Failure(UNSUPPORTED_WINDOW) on apply and query, never a silent 0. HLL seed rejected.
- No Dagger/Dropwizard/Jackson (a backend depends only on velocity-spi + JDBI/Postgres/
  Hikari/slf4j).

Tests (real Postgres via Testcontainers, Docker required): 33 tests, 0 skipped.
Wires the tumbling-compatible SHARED TCK scenarios (TumblingScenarios,
SeedSupportScenarios, CapabilityConformanceScenarios) + a 16-thread concurrency test
asserting exact atomicity (acc. #1) + bespoke tumbling Count/Sum/Distinct/Purge tests.
Coverage LINE 100% / BRANCH 97.4% (gate 80/70).

Validated by the change-validator agent: VERDICT APPROVE (round-trip fix confirmed,
concurrency atomic, bespoke tests NOT weaker than the shared scenarios).

FOLLOW-UP (before velocity-backend-redis): parameterize the window-agnostic TCK
scenarios (Count/Sum/Distinct/Purge currently hardcode a sliding window) so every
backend runs the SHARED scenarios instead of bespoke ones — see PR body.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@wolpert
wolpert merged commit 8546dee into main Jul 19, 2026
3 checks passed
@wolpert
wolpert deleted the velocity-backend-jdbi branch July 19, 2026 01:29
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