chore: capture post-P0/P1 state for clean P2 start (working tree was dirty at task begin)
This commit is contained in:
+175
@@ -0,0 +1,175 @@
|
||||
# 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**:
|
||||
1. Extract clear types for the request context (e.g. `requestContext` holding clientIP, cacheKey, service, timing, etc.).
|
||||
2. Break `ServeHTTP` into smaller focused methods: `handleCacheHit`, `handleCoalesced`, `fetchAndCache`, `writeCacheEntry`, etc.
|
||||
3. Move pure helper logic (range parsing, response reconstruction, hash generation) into separate small files if they aren't already.
|
||||
4. Keep the `SteamCache` struct as the coordinator but reduce its god-object nature over time.
|
||||
- **Acceptance Criteria**:
|
||||
- No single function in the package > 150 lines.
|
||||
- `ServeHTTP` itself 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 global `mu.Lock()` on the entire `MemoryFS`/`DiskFS` for 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:
|
||||
1. 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.
|
||||
2. Move eviction into a dedicated background goroutine that the GC layer signals, using finer-grained coordination.
|
||||
3. 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`/`Stat` while eviction is running.
|
||||
- No data races introduced (race detector clean).
|
||||
- Total size and LRU invariants remain correct after concurrent eviction.
|
||||
- **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/*` and `vfs/lru/*` (at least basic)
|
||||
- **Approach**:
|
||||
1. Write focused unit tests for each VFS implementation using `t.TempDir` for disk.
|
||||
2. Add property-style or table-driven tests that verify size never exceeds capacity after many Create + Evict cycles.
|
||||
3. Test concurrent Create/Open/Delete/Delete under load (with `-race`).
|
||||
4. Test promotion, tier fallback, and lazy discovery paths.
|
||||
5. Add benchmarks (`BenchmarkMemoryFS_CreateOpen`, `BenchmarkEvictionUnderPressure`, etc.).
|
||||
- **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).
|
||||
- **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-debug` targets are hardcoded to a Windows binary path.
|
||||
- `dist/` artifacts are committed even though `.gitignore` lists `/dist/`.
|
||||
- No golangci-lint, no `go vet` in CI, no vulnerability scanning.
|
||||
- Test target exists but coverage reporting and gates are missing.
|
||||
- **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**:
|
||||
1. Fix Makefile to use `go run .` or build a platform-appropriate binary.
|
||||
2. Add a `make lint` target and wire golangci-lint (with reasonable defaults + errcheck, gosec, etc.).
|
||||
3. Update Gitea workflows to run `go vet`, lint, and (optionally) `govulncheck`.
|
||||
4. Remove any committed files under `dist/` (or add them to `.gitignore` more aggressively and git-rm them).
|
||||
5. Consider adding a coverage report step (even if not enforcing a hard gate yet).
|
||||
- **Acceptance Criteria**:
|
||||
- `make test` and `make run` work 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/errors` package 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.
|
||||
- `/metrics` and logs give limited insight into what actually failed and why.
|
||||
- **Approach**:
|
||||
1. Audit the top 10-15 error sites in `ServeHTTP` and VFS layers.
|
||||
2. Convert the most important ones to use `NewSteamCacheError*` helpers.
|
||||
3. Wire more structured fields into zerolog calls using the error types.
|
||||
4. Expand the metrics package (per-service error counts, upstream error breakdown, cache write failures, etc.).
|
||||
5. Consider exporting Prometheus-style metrics in addition to the current text format (optional).
|
||||
- **Acceptance Criteria**:
|
||||
- At least the major error categories (upstream fetch, cache corruption, rate limit, validation) use the custom error types.
|
||||
- `/metrics` surface 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; `ServeHTTP` is 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 run` on 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)
|
||||
|
||||
1. P2-04 (CI hygiene) — cheap wins, improves confidence for everything else.
|
||||
2. P2-03 (test coverage on VFS) — unblocks safe work on P2-02.
|
||||
3. P2-02 (eviction locking).
|
||||
4. P2-01 (big refactor) — do this when the test safety net is stronger.
|
||||
5. 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.go` and `vfs/memory/memory.go` eviction methods (global lock held)
|
||||
- `.gitea/workflows/`
|
||||
- `Makefile`
|
||||
- `steamcache/errors/errors.go` (under-used)
|
||||
|
||||
---
|
||||
|
||||
**After P2**: The project should feel like a mature, professional Go service rather than a sophisticated prototype.
|
||||
Reference in New Issue
Block a user