10 KiB
10 KiB
P2: Performance, Quality & Maintainability Improvements
Priority: P2 — Performance, refactoring, and long-term health
Theme: Make the codebase faster, smaller, easier to reason about, and production-operable at scale.
Status: Not started
Depends on: P0 strongly recommended; P1 nice-to-have for some verification steps
Goal
Turn a clever but monolithic prototype into a high-quality, maintainable Go project that is pleasant to work on and easy to operate.
Overview
After the critical stability (P0) and safety (P1) work, the project still carries technical debt that affects:
- Runtime performance under sustained load (lock contention, unnecessary copies)
- Developer velocity (huge source file, magic numbers, copy-paste)
- Operational visibility (weak metrics, no benchmarks, incomplete CI)
- Correctness confidence (very low test coverage on the storage layer)
These items are important for long-term success but are not immediate crash or security risks.
Tasks
P2-01: Refactor the monolithic ServeHTTP and split steamcache/steamcache.go
- Description: The core request handler is a single ~500+ line function with many responsibilities (authz, rate limiting, coalescing, upstream fetch, cache write, metrics, adaptive recording). The file itself is 1724 lines.
- Impact:
- Extremely hard to test individual behaviors in isolation.
- High risk of regression when touching any part of request handling.
- New contributors are intimidated.
- Affected Files:
steamcache/steamcache.go(primary)- Potentially new files:
steamcache/handler.go,steamcache/coalescing.go,steamcache/upstream.go,steamcache/response.go, etc.
- Approach:
- Extract clear types for the request context (e.g.
requestContextholding clientIP, cacheKey, service, timing, etc.). - Break
ServeHTTPinto smaller focused methods:handleCacheHit,handleCoalesced,fetchAndCache,writeCacheEntry, etc. - Move pure helper logic (range parsing, response reconstruction, hash generation) into separate small files if they aren't already.
- Keep the
SteamCachestruct as the coordinator but reduce its god-object nature over time.
- Extract clear types for the request context (e.g.
- Acceptance Criteria:
- No single function in the package > 150 lines.
ServeHTTPitself becomes a thin dispatcher (< 80 lines).- All existing behavior (including edge cases around coalescing + errors + Ranges) still passes the test suite.
- New unit tests become feasible for the extracted pieces.
- Dependencies: None (can be done in parallel with other P2 work)
- Effort: Large (8-16 hours). Best done as a series of small, reviewable refactors rather than one giant PR.
P2-02: Reduce lock contention during eviction
- Description:
EvictLRU,EvictBySize, etc. take the globalmu.Lock()on the entireMemoryFS/DiskFSfor the duration of the scan + deletion loop. - Impact: Under cache pressure (very common when the disk cache fills), all other operations (Open, Stat, Create) serialize behind the eviction. This can cause request latency spikes even for hot memory-tier hits.
- Affected Files:
vfs/memory/memory.go(eviction methods)vfs/disk/disk.go(eviction methods)
- Approach options:
- Collect candidates under read lock, then do the actual deletes and size updates in a second phase or in small batches while briefly acquiring write locks.
- Move eviction into a dedicated background goroutine that the GC layer signals, using finer-grained coordination.
- Use a "generation" or "watermark" approach so readers can proceed while eviction cleans up.
- Acceptance Criteria:
- Benchmark or load test shows improved tail latencies for
Open/Statwhile eviction is running. - No data races introduced (race detector clean).
- Total size and LRU invariants remain correct after concurrent eviction.
- Benchmark or load test shows improved tail latencies for
- Dependencies: Good test coverage on the VFS layer (see P2-04)
- Effort: Medium-Large (4-8 hours + measurement)
P2-03: Dramatically improve test coverage on the VFS and storage layer
- Description: Most
vfs/*packages currently have 0% coverage. The critical storage, eviction, GC, and tiering logic is almost untested in isolation. - Impact:
- Very low confidence when changing eviction, promotion, or GC behavior.
- Hard to catch regressions in size accounting, LRU ordering, or sharded locking.
- Blocks safe execution of P2-02 and future performance work.
- Affected Areas (need new or expanded tests):
vfs/memory/*vfs/disk/*vfs/gc/*vfs/cache/*vfs/eviction/*vfs/locks/*andvfs/lru/*(at least basic)
- Approach:
- Write focused unit tests for each VFS implementation using
t.TempDirfor disk. - Add property-style or table-driven tests that verify size never exceeds capacity after many Create + Evict cycles.
- Test concurrent Create/Open/Delete/Delete under load (with
-race). - Test promotion, tier fallback, and lazy discovery paths.
- Add benchmarks (
BenchmarkMemoryFS_CreateOpen,BenchmarkEvictionUnderPressure, etc.).
- Write focused unit tests for each VFS implementation using
- Acceptance Criteria:
- Combined coverage for all
vfs/*packages ≥ 70% (statement). - At least one benchmark per major component that can be run in CI or locally.
- New tests catch at least one real bug during development (celebrated in commit message).
- Combined coverage for all
- Dependencies: None
- Effort: Large (12-20 hours spread over multiple sessions). High leverage.
P2-04: Clean up build, CI, linting, and repository hygiene
- Description:
- Makefile
run/run-debugtargets are hardcoded to a Windows binary path. dist/artifacts are committed even though.gitignorelists/dist/.- No golangci-lint, no
go vetin CI, no vulnerability scanning. - Test target exists but coverage reporting and gates are missing.
- Makefile
- Impact: Painful local development on non-Windows machines. Risk of shipping known-bad artifacts. Harder to maintain code quality over time.
- Affected Files:
Makefile.gitea/workflows/test-pr.yaml(and release workflow).gitignore(verify dist is truly ignored)- Possibly add
.golangci.yml
- Approach:
- Fix Makefile to use
go run .or build a platform-appropriate binary. - Add a
make linttarget and wire golangci-lint (with reasonable defaults + errcheck, gosec, etc.). - Update Gitea workflows to run
go vet, lint, and (optionally)govulncheck. - Remove any committed files under
dist/(or add them to.gitignoremore aggressively and git-rm them). - Consider adding a coverage report step (even if not enforcing a hard gate yet).
- Fix Makefile to use
- Acceptance Criteria:
make testandmake runwork cleanly on Linux and macOS.- CI runs lint + vet and fails the PR on new issues.
- Repository no longer contains built binaries in its tree.
go mod tidy+ build is reproducible.
- Dependencies: None
- Effort: Small-Medium (3-5 hours)
P2-05: Use the existing rich error types consistently and improve observability
- Description: A nice
steamcache/errorspackage with context, unwrap, retry classification, and client/server error helpers exists but is almost unused. Metrics are still very basic. - Impact:
- Lost opportunity for better structured logging and error handling.
- Harder to write generic retry / circuit-breaker logic later.
/metricsand logs give limited insight into what actually failed and why.
- Approach:
- Audit the top 10-15 error sites in
ServeHTTPand VFS layers. - Convert the most important ones to use
NewSteamCacheError*helpers. - Wire more structured fields into zerolog calls using the error types.
- Expand the metrics package (per-service error counts, upstream error breakdown, cache write failures, etc.).
- Consider exporting Prometheus-style metrics in addition to the current text format (optional).
- Audit the top 10-15 error sites in
- Acceptance Criteria:
- At least the major error categories (upstream fetch, cache corruption, rate limit, validation) use the custom error types.
/metricssurface becomes more useful (new counters for categories of errors).- No behavior change for clients (still get the same HTTP status codes).
- Dependencies: P0-04 (basic error counting)
- Effort: Medium (4-6 hours)
Definition of Done (P2 Milestone)
- Major refactoring (P2-01) landed in reviewable chunks;
ServeHTTPis no longer a monster function. - Eviction lock contention measurably reduced (P2-02).
- VFS/storage layer has ≥70% test coverage + benchmarks (P2-03).
- Build/CI hygiene is excellent: cross-platform make targets, lint in CI, clean repo (P2-04).
- Error handling and metrics are noticeably better (P2-05).
go test -race -shuffle=on ./...+go vet+ linter are all green.- A new contributor can run
make help,make test,make runon Linux/macOS without friction. - At least one performance or quality regression has been prevented by the new tests/benchmarks during the work.
Suggested Order of Execution (within P2)
- P2-04 (CI hygiene) — cheap wins, improves confidence for everything else.
- P2-03 (test coverage on VFS) — unblocks safe work on P2-02.
- P2-02 (eviction locking).
- P2-01 (big refactor) — do this when the test safety net is stronger.
- P2-05 (errors + observability) — can run in parallel with others.
Notes for Implementers
- P2 work has the highest risk of "refactoring for its own sake." Every change should be justified by either a concrete performance win, a maintainability win that reduces future bug rate, or enabling future features.
- Keep changes reviewable. Large refactors should be broken into multiple PRs with clear "no behavior change" invariants.
- The custom LRU list and sharded locking are clever — make sure any refactoring preserves their performance characteristics.
References
steamcache/steamcache.go:1724(file size)vfs/disk/disk.goandvfs/memory/memory.goeviction methods (global lock held).gitea/workflows/Makefilesteamcache/errors/errors.go(under-used)
After P2: The project should feel like a mature, professional Go service rather than a sophisticated prototype.