Skip to content

Compress /api/v1 responses with gzip - #95

Merged
LarsLaskowski merged 1 commit into
mainfrom
claude/issue-23-v3vlqq
Aug 19, 2026
Merged

Compress /api/v1 responses with gzip#95
LarsLaskowski merged 1 commit into
mainfrom
claude/issue-23-v3vlqq

Conversation

@LarsLaskowski

@LarsLaskowski LarsLaskowski commented Aug 19, 2026

Copy link
Copy Markdown
Owner

📖 Description

GET /api/v1/metrics/history returns highly repetitive JSON (a fixed set of
time series, each point ~50 bytes) that can reach several hundred KB per poll
on a Pi with multiple disks/interfaces at the default 60-minute/5-second
history window. writeJSON never compressed responses, so this payload was
re-sent in full, uncompressed, on every poll — noticeable on a Pi Zero over
Wi-Fi both in transfer time and CPU.

This adds a withGzip middleware (internal/httpapi/middleware.go) using
only the standard library:

  • If the request's Accept-Encoding doesn't contain gzip, the request
    passes through unchanged (identity response, unmodified for existing
    clients).
  • Otherwise it sets Content-Encoding: gzip and Vary: Accept-Encoding,
    drops any Content-Length, and wraps the ResponseWriter so Write goes
    through a pooled *gzip.Writer at gzip.BestSpeed (a sync.Pool avoids
    per-request writer allocation; WriteHeader is forwarded automatically via
    the embedded http.ResponseWriter).

withGzip wraps the four /api/v1/... routes in server.go
(/api/v1/metrics, /api/v1/metrics/history, /api/v1/alerts,
/api/v1/config). /healthz is left uncompressed (2-byte body, not worth
it).

🎫 Issues

Closes #23

👩‍💻 Reviewer Notes

Smoke-tested locally:

$ curl -s -o /dev/null -w '%{size_download}\n' http://localhost:8080/api/v1/metrics/history
907
$ curl -sH 'Accept-Encoding: gzip' -o /dev/null -w '%{size_download}\n' http://localhost:8080/api/v1/metrics/history
279

(compression ratio grows with history size — the fixture above only had a
handful of history points; a full 60-minute window compresses closer to the
~10x mentioned in the issue, since it's far more repetitive.)

📑 Test Plan

  • New test TestHandleHistory_GzipWhenAccepted in middleware_test.go:
    builds a 500-point history fixture, requests it both without and with
    Accept-Encoding: gzip, and asserts the gzip response carries
    Content-Encoding: gzip / Vary: Accept-Encoding, is smaller than the
    identity response, and gunzips to byte-equivalent JSON.
  • go build ./..., go vet ./..., go test ./... -race -cover, and
    golangci-lint run all pass locally.

✅ Checklist

General

  • I have added/updated tests for my changes (go test ./... -race -cover passes locally).
  • go vet ./... and golangci-lint run are clean.
  • I have tested my changes.
  • I have read the CONTRIBUTING documentation and followed the project's code style guidelines.
  • I have updated ARCHITECTURE.md if this changes a documented design decision. (not applicable — this is an internal HTTP-layer optimization, not a documented architectural decision)

REST API / configuration / packaging

  • I have updated docs/API.md to reflect a REST API change. (new "Compression" section)
  • No breaking change to /api/v1/... response shapes, or a new API version (/api/v2/...) was introduced instead. (response bodies are byte-identical after decompression — only the wire encoding changes, and only for clients that opt in via Accept-Encoding: gzip)

⏭ Next Steps

None.

History/metrics payloads are repetitive JSON that can reach several
hundred KB per poll (720 points per series across all series on a Pi
with disks/interfaces). Add a withGzip middleware that compresses
responses for clients sending Accept-Encoding: gzip, using a pooled
gzip.Writer at BestSpeed to keep the CPU cost low on constrained Pi
hardware. Clients without that header keep receiving identity
responses, so no existing /api/v1 consumer breaks.

Closes #23
@sonarqubecloud

Copy link
Copy Markdown

@LarsLaskowski
LarsLaskowski merged commit fd73a9a into main Aug 19, 2026
5 checks passed
@LarsLaskowski
LarsLaskowski deleted the claude/issue-23-v3vlqq branch August 19, 2026 16:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Performance: JSON API responses are served uncompressed (history payload is large on slow links)

2 participants