Skip to content

bug(pipes): mutation pipe results are cached and coalesced — the write silently drops on repeat calls #386

Closed

Description

@taitelee

Summary

/v1/pipes/{name} runs its SQL through the pipe cache + singleflight with no mutation guard, even though the shared executor deliberately supports mutation-verb SQL. A pipe that writes returns a cached [] on repeat calls and never re-executes.

Detail

  • internal/api/clickhouse_exec.go:26-31 dispatches mutation-verb SQL through conn.Exec, so an INSERT/ALTER pipe does execute the first time.
  • internal/api/pipes.go:176-221 caches the result under queryCacheKey(sql, params) with cache.QueryTimeToTTL (floor 10s, up to 1h) and wraps execution in singleflight — with no isMutation(sql) check on either the Put (store) or execute path.

Scenario: an admin authors INSERT INTO audit_log VALUES ({{msg}}, now()) with allowed_roles: ["writer"]. First call inserts and caches []. Every identical call within the TTL returns 200 + X-Cache: HIT and never reaches ClickHouse; concurrent identical calls are collapsed by singleflight to a single insert.

Impact

Silent data loss on mutation pipes. Docs frame pipes as the cached read path but never forbid mutation SQL, and the exec layer explicitly supports it.

Fix direction

Either reject mutation SQL at pipes.Store.Put, or bypass cache + singleflight when isMutation(sql).

Found in a repo-wide audit; verified by code trace. Distinct from #321/#365.

Activity

  1. added
    bugSomething isn't working
    area/cacheLocal / shared / tiered caching
    on Jul 8, 2026
  2. coderabbitai commented on Jul 8, 2026

    @coderabbitai
    🔗 Related PRs

    #164 - refactor(ingest)!: insert-only pipeline; mutations via /v1/query [closed]
    #177 - feat: caching for local table/scope invalidation [closed]
    #330 - fix(pipes): escape non-scalar param values [closed]


    🧪 Issue enrichment is currently in open beta.

    You can configure auto-planning by selecting labels in the issue_enrichment configuration.

    To disable automatic issue enrichment, add the following to your .coderabbit.yaml:

    issue_enrichment:
      auto_enrich:
        enabled: false

    💬 Have feedback or questions? Drop into our discord!

  3. EricAndrechek commented on Sep 25, 2026

    @EricAndrechek
    Member

    #630 (E4, draft) widens the blast radius. With cache.backend: redis every instance shares the pipe result cache. So a repeated identical call to a pipe that writes is answered from the cache, and the write is skipped, on every instance until the entry's TTL, not just on the instance that cached it.

    Pipe entries have no table dependencies, so ingest never invalidates them. Only the TTL (10 s to 1 h) or InvalidateTenant clears them. This should be fixed before any real deployment sets backend: redis. #634 (draft) is the fix. The risk is documented in the "Multiple instances and the shared cache" section of deployment.md (#630).

  4. moved this from Backlog to In progress in WaveHouse Task Boardon Sep 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area/cacheLocal / shared / tiered cachingarea/pipesNamed query pipesbugSomething isn't working

    Type

    No type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions