feat(npm): bufferedUpdate goroutine helper for interim buffered columns

Related issue: #142 (closed)

📦 What

Implements Step 24 of the npm local plan: the interim bufferedUpdate goroutine helper that mutating npm handlers will call to bump npm_packages.* and repositories.* counter columns off the request path.

bufferedUpdate(ctx, label, fn) runs fn in a fire-and-forget goroutine with defer recover(), so a failed or panicking counter UPDATE is logged and metered but never surfaces to the client — honouring S11's "a failed emission must not fail the request" contract. Each outcome is metered on gitlab_artifact_registry_npm_buffered_counter_updates_total{column, result} (result enum: ok, panic).

This is a deliberately throwaway helper (TODO(buffered)): it has no caller yet and will be replaced by the cross-format buffered-write subsystem (S12 OQ-16 / S23 storage accounting). Backpressure, in-flight bounds, shutdown drain, rate-limiting, and persistent buffering are out of scope per the plan.

Spec coverage

Spec: S11 — Interim implementation of buffered columns

Behaviour Test
Happy path: result=ok, column side-effect observable after the goroutine completes TestBufferedUpdate_HappyPath
Failed fn (non-panic error) logged but not surfaced; counts result=ok TestBufferedUpdate_FnError
Worker panic recovered, process stays up, result=panic increments TestBufferedUpdate_PanicSafety
column argument partitions the metric into distinct series TestBufferedUpdate_DistinctColumnsDistinctSeries
Detached worker runs on a live context even when the request ctx is canceled TestBufferedUpdate_DetachesRequestCancellation
Metric reaches the scrape path once registered with the LabKit registry TestRegisterMetrics_ExposedViaGatherer

🔍 Reviewer notes

Two files fall outside Step 24's declared Files: list — both intentional, surfaced by validation for conscious sign-off:

  • go.mod: promotes github.com/prometheus/client_golang from indirect to direct (required — buffered.go imports prometheus and the test imports prometheus/testutil) and pulls in github.com/kylelemons/godebug as an indirect transitive of prometheus/testutil. Mechanical fallout of the new import, not a scope leak.
  • docs/plans/2026-05-11-npm-local.md: drops the stray npm_buffered_counter_update_duration_seconds from Step 22's metrics catalog — a drift correction (this helper emits no duration histogram; latency instrumentation belongs to the future cross-format subsystem). Ships here under the validate-step drift carve-out.

The metric registers against the server's LabKit registry via RegisterMetrics(reg) (the composition root wires it when the first caller lands), not the global default registry that LabKit never scrapes.

🧪 Testing

  • go test -race ./internal/format/npm/... — pass (6 tests)
  • golangci-lint run — 0 issues
  • go vet, gofmt -l, full pre-commit hook chain — clean
Edited by David Fernandez

Merge request reports

Loading
Loading