Polish bundle: validation, BufWriter WAL, durability mode, spec-aligned routes - #6
Merged
Conversation
…ed routes Closes a long tail of small review items in a single PR. #14 Event validation. handle_ingest now runs each event through validate_event before WAL append: rejects empty event_id, account_id, product_id, meter_id; non-positive timestamp_ms; >16 dimensions (spec §21); Correction/Retraction without correction_ref. Rejected events never reach the WAL or dedupe. IngestBatchResponse.rejected is finally a real count, surfacing collector bugs that previously went silent. 5 unit tests cover the rules. #17 Buffered WAL. Wal.file becomes BufWriter<File>. writeln! coalesces in userspace; sync() flushes then fsyncs; rotate flushes + fsyncs the sealed file before swapping. Throughput win for small-batch ingest with no change to durability semantics in Strict mode. #18 Durability mode. New Config.durability_mode: DurabilityMode enum (Strict | Fast). Strict (default) is the previous flush+fsync behavior. Fast does flush-only — bytes reach the page cache but no disk round-trip — for at-least-once upstream pipelines that tolerate losing the tail batch on host crash. Balanced (group commit) is documented as future work. #19 Endpoint paths aligned with spec. - POST /v1/usage/batch (was /v1/ingest) - GET /v1/accounts/:account_id/usage with query params from/to/group_by/ product_id/meter_id/model_id/source (§12.2 monthly usage path) - GET /v1/accounts/:account_id/usage/events for raw audit (§12.3) - POST /v1/query/json and /v1/query/sql kept for flexible callers #20 ingested_at_ms consistency. The ingest handler now overwrites event.ingested_at_ms with server-side now_ms() before hashing and WAL persistence, so a client with a wrong clock can't poison the dedupe TTL eviction. Replay still uses the persisted (server-stamped) value, so live and replayed entries are consistent. #21 evict_expired simplification. The "Only remove if entry hasn't been replaced" branch was unreachable since cache entries are never updated — drop both unconditionally when past the cutoff. #22 Drop unused parking_lot from Cargo.toml. #23 Zero clones on the accepted ingest path. wal.append_batch now takes IntoIterator<Item = &UsageEvent> so the handler can stream refs from new_events.iter() without building an intermediate Vec of clones. Memtable insertion moves the event by value. #16 Manifest fsync portability. Switch from fs::write + reopened-RO fsync (Linux-only contract) to the standard recipe: open + write + sync_all on a single fd, drop, rename, parent dir fsync. README updated. 37/37 tests pass (was 32; +5 validation unit tests). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
3 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Knocks out the long tail of small review items in a single PR. After this, the codebase is in a clean state where the only remaining moves are performance work (per-column encodings, block metadata) or testing infrastructure (proptest invariants).
What's in
handle_ingestruns each event throughvalidate_eventbefore WAL append: rejects empty IDs, non-positive timestamp, >16 dimensions (spec §21), Correction/Retraction withoutcorrection_ref. Rejected events never reach the WAL or dedupe.IngestBatchResponse.rejectedis finally a real count. 5 unit tests cover the rules.Wal.fileis nowBufWriter<File>;writeln!coalesces in userspace,sync()flushes then fsyncs,rotate()flushes + fsyncs the sealed file before swapping.DurabilityMode::{Strict, Fast}inConfig. Strict = flush + fsync (default, billing-safe). Fast = flush only (page cache; for at-least-once upstream retry pipelines). Balanced (group commit) is documented as future work.POST /v1/usage/batch(was/v1/ingest) — spec §9.1GET /v1/accounts/{account_id}/usage?from&to&group_by&product_id&meter_id&model_id&source— spec §12.2GET /v1/accounts/{account_id}/usage/events?from&to&meter_id&product_id— spec §12.3 raw auditPOST /v1/query/jsonand/v1/query/sqlretained for flexible callersingested_at_msconsistency. Ingest handler stampsevent.ingested_at_ms = now_ms()server-side before hashing and WAL persistence — a client with a wrong clock can't poison the dedupe TTL. Replay still uses the persisted server-stamped value, so live and replayed entries are consistent.evict_expiredsimplification. The dead defensive branch ("only remove if entry hasn't been replaced") was unreachable since cache entries are never updated.parking_lotdependency.wal.append_batchtakesIntoIterator<Item = &UsageEvent>, so the handler streams refs fromnew_events.iter()without building an intermediateVecof clones. The memtable still owns its events (move, not clone).fs::write+ reopened-ROfsync(Linux-only contract) to the standard atomic-write recipe: open + write +sync_allon a single fd, drop, rename, parent dir fsync.Test plan
cargo build --all-targetsclean with-D warningscargo test --all-targets— 37 tests pass (was 32; +5 validation unit tests)Still on the backlog
DurabilityMode::Balanced(group commit)🤖 Generated with Claude Code