Spike valve: what a fingerprint flood returns on a 2-container host
Self-hosted error flood soft sample: still HTTP 202 with a counter bump and body drop, then 403 ingest_cap_exceeded at the hourly project cap.
On this pageShowHide
When the same fingerprint floods a 2-container Epure host, ingest still returns HTTP 202, bumps the issue counter, and drops excess bodies. After the hourly project cap, clients see 403 ingest_cap_exceeded.
Epure is a self-hosted exception tracker (Apache 2.0) that accepts Envelope + DSN traffic from official Sentry SDKs. The documented Docker Compose v2 stack is two services: epure and postgres (postgres:16-alpine). This ops note covers the sentry spike fingerprint rate limit path: how a self-hosted error flood soft sample behaves, why SaaS Spike Protection is missing on Sentry self-host, and how soft sample differs from a hard 429.
Authoritative defaults live in Concepts and API Errors. Sibling context: Sentry SDK compatible and Epure vs Sentry.
SaaS Spike Protection vs self-host
Sentry SaaS Spike Protection is documented as a billing-coupled, per-project spike control on cloud quotas. Sentry's self-hosted develop docs state Spike Protection is not available because it is tightly coupled with Billing Quotas.
Treat those pages as third-party / second-hand: Sentry Spike Protection and Sentry self-hosted. They explain why "sentry self-hosted spike protection missing" shows up in ops searches. They do not describe Epure's in-process fingerprint valve.
Per-fingerprint spike valve
Epure's spike valve limits raw events to about 100 per minute per fingerprint. When saturated, the client still receives HTTP 202 Accepted; the issue counter increments; excess event bodies are not stored.
Fingerprint
A fingerprint is the grouping key for an issue. By default Epure uses a SHA-256 of the normalized exception type plus the top in-app stack frame (file + function + line). A custom fingerprint array on the event overrides that rule. See Concepts.
Spike valve
The spike valve is an in-process per-fingerprint soft limiter on the Rust binary. Default: about 100 raw events per minute per fingerprint (API Errors). Soft sample means the client still gets HTTP 202, the issue event_count keeps rising, and excess duplicate bodies are not stored. Under flood, operators keep the event count without proportional row growth.
Spike valve
- Default
- ~100 raw events / minute / fingerprint
- Client sees
- Still HTTP 202
- Operator sees
- Counter up; excess bodies not stored
Project ingest cap
- Default
- 5000 events / hour / project
- Client sees
- 403 ingest_cap_exceeded
- Operator sees
- Cap checked before enqueue; may alert kind: ingest_cap_hit
Body size
- Default
- 2 × 1024 × 1024 bytes
- Client sees
- 413 payload_too_large
- Operator sees
- Separate from spike valve
| Dimension | Default | Client sees | Operator sees |
|---|---|---|---|
| Spike valve | ~100 raw events / minute / fingerprint | Still HTTP 202 | Counter up; excess bodies not stored |
| Project ingest cap | 5000 events / hour / project | 403 ingest_cap_exceeded | Cap checked before enqueue; may alert kind: ingest_cap_hit |
| Body size | 2 × 1024 × 1024 bytes | 413 payload_too_large | Separate from spike valve |
First-party defaults from Epure Concepts and API Errors docs as of 2026-09. No Redis-backed global rate limiter in the documented install.
There is no Redis-backed global rate limiter in the documented install. Valves run in-process beside PostgreSQL 16 on the two-container host (Installation).
Hourly project cap (403)
Separately from the spike valve, Epure enforces a project ingest cap of 5000 events per hour by default. Crossing that cap returns HTTP 403 with JSON error value ingest_cap_exceeded.
The cap is checked before enqueue. Operators may see an alert with kind: ingest_cap_hit. Fix path from the docs: wait for the hour window or raise the cap in project settings (API Errors).
- Do not conflate with
dsn_revoked(also 403): rotate or recreate the key and update the DSN. - Do not conflate with
project_mismatch(403): URL{project_id}must match the DSN project UUID. - Do not conflate with
payload_too_large(413): shrink the store/envelope body under the 2 MB router cap.
Soft sample vs hard reject
Under fingerprint flood, Epure soft-samples: HTTP 202, counter bump, body drop. Telebugs Ingest Protection (second-hand, 1.18.0 changelog dated 2026-06-29) can hard-reject with HTTP 429 Too Many Requests before writing. Epure's hard stop for the project hour is 403 ingest_cap_exceeded.
Epure spike valve
- Behavior (as documented)
- Still HTTP 202; counter up; excess bodies dropped (~100 raw/min/fingerprint)
- Contrast for operators
- Client success under flood; storage stays bounded per fingerprint
Epure project ingest cap
- Behavior (as documented)
- HTTP 403 ingest_cap_exceeded at 5000 events/hour/project
- Contrast for operators
- Hard stop for the project hour; raise cap or wait
Sentry SaaS Spike Protection (third-party)
- Behavior (as documented)
- Billing-coupled dynamic per-project spike threshold; drops events to protect quota
- Contrast for operators
- Cloud quota framing; absent from Sentry self-hosted (Billing Quotas coupling)
Telebugs Ingest Protection 1.18.0 (third-party, 2026-06-29)
- Behavior (as documented)
- Global accepted-errors/min + queue/disk guardrails → HTTP 429 before writing
- Contrast for operators
- Hard reject vs Epure soft 202 + body drop
| Dimension | Behavior (as documented) | Contrast for operators |
|---|---|---|
| Epure spike valve | Still HTTP 202; counter up; excess bodies dropped (~100 raw/min/fingerprint) | Client success under flood; storage stays bounded per fingerprint |
| Epure project ingest cap | HTTP 403 ingest_cap_exceeded at 5000 events/hour/project | Hard stop for the project hour; raise cap or wait |
| Sentry SaaS Spike Protection (third-party) | Billing-coupled dynamic per-project spike threshold; drops events to protect quota | Cloud quota framing; absent from Sentry self-hosted (Billing Quotas coupling) |
| Telebugs Ingest Protection 1.18.0 (third-party, 2026-06-29) | Global accepted-errors/min + queue/disk guardrails → HTTP 429 before writing | Hard reject vs Epure soft 202 + body drop |
Cite Telebugs from their changelog (https://telebugs.com/changelog). That entry does not publish numeric RPM defaults to quote here.
Cite Telebugs from their changelog. That entry does not publish numeric RPM defaults to quote here.
Validate with documented curls
Validate ingest with the documented store or envelope curls against the Quickstart seed project. Expect HTTP 202 and a JSON body with an event id. There is no public flood script or measured counter-vs-body table in docs this week.
Compose up and health
From Quickstart / Installation:
git clone https://github.com/epure-sh/epure.git && cd epure && docker compose up
Then: curl -sS http://localhost:8080/health → expected {"status":"ok"}.
Store path (seed defaults)
Documented store POST (seed project + key from Quickstart). Expect HTTP/1.1 202 Accepted and JSON with "id": "<event-uuid>".
curl -sS -D - -o /tmp/epure-ingest.json -X POST "http://localhost:8080/api/550e8400-e29b-41d4-a716-446655440000/store/" -H "X-Sentry-Auth: Sentry sentry_version=7, sentry_key=a1b2c3d4e5f6g7h8i9j0, sentry_secret=supersecretdevkey" -H "Content-Type: application/json" -d '{"platform":"javascript","exception":{"values":[{"type":"Error","value":"Epure try-it"}]}}'
Envelope fixture
From repo root, Quickstart documents: curl -sS -D - -o /tmp/epure-envelope.json -X POST "http://localhost:8080/api/550e8400-e29b-41d4-a716-446655440000/envelope/" -H "X-Sentry-Auth: Sentry sentry_version=7, sentry_key=a1b2c3d4e5f6g7h8i9j0, sentry_secret=supersecretdevkey" -H "Content-Type: application/x-sentry-envelope" --data-binary @fixtures/sentry/browser/envelope.txt
When showing JS/Node SDKs, pin @sentry/browser@7.120.0 / @sentry/node@7.120.0 as Quickstart does. Same fingerprint twice should land on one issue with occurrence count up (Verify). Under same-fingerprint flood, keep expecting 202; treat rising event_count with fewer stored bodies as the valve working. Debug with docker compose logs epure.
Failure modes under flood
A flood that still returns HTTP 202 with a rising issue count and few stored bodies is expected spike-valve behavior, not a failed verify. The hard stop operators wait for is 403 ingest_cap_exceeded.
Connection refused on 8080
- Error / cause
- Port / compose down
- Fix
- docker compose ps; set EPURE_PORT if needed
401
- Error / cause
- invalid_dsn
- Fix
- Copy key from Settings → SDK connection
403
- Error / cause
- dsn_revoked
- Fix
- Create/rotate key; update dsn
403 (project)
- Error / cause
- project_mismatch
- Fix
- URL {project_id} must match DSN project UUID
403 (cap)
- Error / cause
- ingest_cap_exceeded
- Fix
- Wait for hour window or raise project cap
202, empty Issues
- Error / cause
- Async queue / env chip
- Fix
- Wait ~1s; match environment tag; docker compose logs epure
202, high count, few bodies
- Error / cause
- Spike valve saturated
- Fix
- Expected; slow unique exceptions or wait ~1 min; hourly cap is the 403 path
413
- Error / cause
- payload_too_large
- Fix
- Shrink body under 2 MB router cap
| Dimension | Error / cause | Fix |
|---|---|---|
| Connection refused on 8080 | Port / compose down | docker compose ps; set EPURE_PORT if needed |
| 401 | invalid_dsn | Copy key from Settings → SDK connection |
| 403 | dsn_revoked | Create/rotate key; update dsn |
| 403 (project) | project_mismatch | URL {project_id} must match DSN project UUID |
| 403 (cap) | ingest_cap_exceeded | Wait for hour window or raise project cap |
| 202, empty Issues | Async queue / env chip | Wait ~1s; match environment tag; docker compose logs epure |
| 202, high count, few bodies | Spike valve saturated | Expected; slow unique exceptions or wait ~1 min; hourly cap is the 403 path |
| 413 | payload_too_large | Shrink body under 2 MB router cap |
Limitations
Epure is exception-only on a two-container Compose stack. Spike and project valves are in-process on the Rust binary. Documented Compose installs epure and postgres only.
- No public measured flood retention table ships in the docs: treat valve behavior as semantics (202 + counter bump + body drop), not as a bodies-kept ratio.
- Idle RAM and TTFI figures are omitted here (dated sources conflict; remeasure before claiming).
- Tracing, session replay, profiling, and generic logs are out of scope for Epure features.
- Mobile symbolication (iOS/Android) is not shipped.
- This is not protocol parity with full Sentry Cloud.
- SaaS Spike Protection and Telebugs 429 rows above are second-hand citations only.
Use this guide if
- You already point official Sentry SDKs at a 2-container Epure host.
- You need the soft-sample story: still 202, counter up, excess bodies dropped.
- You want the hard stop called out as 403 ingest_cap_exceeded at 5000/hour/project.
- You refuse a Redis rate-limiter service in the error stack.
Read something else if
- You still need the DSN-only swap walkthrough: Sentry SDK compatible.
- You are sizing a small VPS: Self-hosted under 512 MB.
- You want a roster of backends: Sentry alternatives.
- Session replay or APM is the daily debug loop: stay on Sentry Cloud.
Defaults: Concepts, API Errors. Verify path: Verify. Clone: GitHub.