Skip to content

feat(ingest): implement per-table cache invalidation on Bento batch flush #85

Description

@taitelee

Problem

This resolves #73 (perf(cache): query cache returns stale data after writes).

Following our architecture review, we explicitly rejected the idea of writing a mini query-engine to merge NATS WAL buffers with live queries. We are adopting an "acceptable delay" paradigm for structured reads, but enforcing strict Read-Your-Writes guarantees.

Currently, the API cache relies on a static TTL. If Bento flushes a write batch at second 1, the user still sees stale data until the TTL expires, leading to test collisions and administrative confusion. Additionally, because cache keys are SHA256 hashes of SQL strings, prefix-based invalidation was non-viable.

Proposed Solution

This PR implements the two-pronged approach established in the architecture review (Option B + Option C):

  1. Option C: Raw SQL Cache Bypass:

    • Caching has been completely stripped from QueryHandler.Handle (/v1/query).
    • Admin raw-SQL queries (used heavily in E2E tests and debugging) will now always hit ClickHouse directly, guaranteeing immediate consistency without cache thrash.
  2. Option B: Per-Table Tagging and Invalidation:

    • Modified the cache.Cache interface to accept tags []string during Set(), and replaced prefix invalidation with InvalidateByTags(ctx, tags).
    • LocalCache now maintains a secondary tagsMap (mapping a tag to its generated SHA256 cache keys).
    • In pipes.go, structured queries use a naive regex parser to extract FROM and JOIN table names, attaching them as tags to the cache entry.
    • In bento.go, the ingest worker now accepts the TieredCache. Upon a successful 200 OK from ClickHouse, Bento immediately drops all cache entries tagged with the inserted table_name.

Acceptance Criteria

  • Raw SQL queries (/v1/query) bypass the cache entirely (returning X-Cache: BYPASS).
  • Cache interface and LocalCache support tagging and tag-based invalidation.
  • Bento clickhouseOutput invalidates cache tags synchronously only upon successful ClickHouse insert.
  • Structured queries parsing correctly assigns table names as cache tags.

Additional Context

  • The naive SQL parser is a stop-gap for v1. It may over-match (false positives -> safe cache invalidation) but aims to avoid false negatives.
  • If high-write tables experience cache thrash, we can tune this later with time-bucketed aggregation strategies.

Activity

  1. self-assigned this
    on Apr 29, 2026
  2. moved this from Backlog to Ready in WaveHouse Task Boardon Apr 29, 2026
  3. moved this from Ready to In progress in WaveHouse Task Boardon May 4, 2026
  4. moved this from In progress to Ready in WaveHouse Task Boardon May 11, 2026
  5. moved this from Ready to In progress in WaveHouse Task Boardon May 12, 2026
  6. moved this from In progress to In review in WaveHouse Task Boardon May 24, 2026
  7. added a commit that references this issue on May 24, 2026
    65cb8ad
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area/cacheLocal / shared / tiered cachingarea/ingestIngest pipeline (Bento, batching, DLQ)breaking-changeBreaking change to public API, CLI, or configenhancementNew feature or request

Type

No type

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions