electric-sql / electric-sql/electric-circuits

/v1/shape HTTP caching: cache-control tiers, ETag/304, cursor collapsing, 10MB chunking

Open
#10 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
Rust
Stars
30
Forks
6
PR merge metrics
No merged PRs in 30d

Description

Today every /v1/shape response is `cache-control: no-store` (`apps/engine/src/electric.rs:652,698`), the client's `cursor` is ignored, there is no ETag, and snapshot bodies are unbounded. Zero CDN/edge offload. Depends on #8 (handles/offsets must survive restarts, or cached URLs point at dead handles).

Adopt upstream sync-service's scheme (values encode real CDN tuning — copy verbatim, refs into `shapes/api/response.ex`):

- **Cache-control tiers:** snapshot `public, max-age=604800, s-maxage=3600, stale-while-revalidate=2629746`; catch-up `public, max-age=60, stale-while-revalidate=300` (configurable); live `public, max-age=5, stale-while-revalidate=5`; 409 must-refetch `public, max-age=1` (no handle) / `60` (with handle) + `must-revalidate`; other errors `no-store` + `surrogate-control: no-store`.
- **ETag + If-None-Match → 304:** etag `"::"`; empty live responses get a monotonic suffix so CDNs never serve them from cache (forces origin collapsing).
- **`electric-cursor` time-bucketing:** bucket = long-poll window, with jitter on rollover, so concurrent live pollers share a URL and CDNs collapse them to one origin request (upstream `utils.ex:59-93`).
- **Response chunking at 10 MB:** cap each response's read size and return `electric-offset` at the cut; client re-requests from there (fresh cacheable URL). Our DS offsets are byte-positioned so no storage change is needed.
- **Long-poll timeout → 200 + `[up-to-date]` control** (+ `electric-has-data: false`) instead of today's 204 — the 200 is the cacheable/collapsible response, and it matches upstream exactly (conformance win).
- `send_cache_headers` off-switch for deployments without a CDN.

**Non-goal here:** the extended API read path (durable-streams server) — separate issue, different surface; investigating whether the DS implementation supports per-stream/response cache headers.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Contributor guide

Open the contributing guide

Research direction

Read apps/engine/src/electric.rs at lines 652 and 698, then compare upstream shapes/api/response.ex and utils.ex:59-93; account for dependency #8 before changing cache behavior. Done means the shape endpoint implements the specified cache tiers, ETag/304 handling, cursor bucketing, 10 MB offsets, cacheable long-poll responses, and the send_cache_headers off-switch.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
api, backend, performance
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
42/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.