Artifact 8fb89ae70aafb5fab9e1bfe62463870f387bb441e4e60af3e89138173e1aec3b:
- File
cmd/quarry/analytics_duck.go
— part of check-in
[564f272d3c]
at
2026-08-25 19:24:56
on branch ops-history
— ops: history, not just a snapshot
The rings die with the process, and this fleet restarts constantly — a 4.24s render and a 546 load average both left no trace. obs.Sink had one implementation and it did nothing; this is the durable one.
DuckDB because the workload is columnar: append-only, time-ordered, aggregate-queried, never point-looked-up by a page. RFC 0001 defers the lake to 'when hydration and analytics — the things columnar is for — exist', and reserves the operational surface for SQLite. This is that analytics workload, not a second operational store. Wide flat tables, no keys and no indexes, which is also what a Parquet COPY TO wants when the lake arrives.
Isolated behind a build constraint, in its own package, imported only by the composition root. Both halves of the reason were measured rather than assumed: CGO_ENABLED=0 fails on go-duckdb's bindings, and GOOS=js reports 'build constraints exclude all Go files'. Leaving the import off the request path is not enough — any reachable import breaks a wasm build — so cmd/quarry has two files, one per side of the tag. Verified: GOOS=js GOARCH=wasm builds cmd/quarry clean at 76MB, without DuckDB.
Buffered, never a write per request, and flushed on a timer so a quiet forge still lands rows rather than holding them in memory for days.
Failure is not fatal. The first run failed on 'at' being reserved in DuckDB; the server started anyway, the panels worked, and the log said what was wrong. Analytics is derived data about a forge that works without it.
Costs, measured: +462MB module cache, binary 57MB -> 104MB, cold build +10s, and CGO_ENABLED=0 no longer builds the server. (user: dmestas, size: 1324) [annotate] [blame] [check-ins using] [more...]
//go:build !js && !wasip1 package main import ( "fmt" "log" "path/filepath" "time" "craftdesign.group/quarry/internal/obs" "craftdesign.group/quarry/internal/obsduck" ) // openAnalytics wires the durable observation store, returning a stop // function. Failure is NOT fatal: analytics is derived data about a // forge that works without it, and a forge that refuses to start // because its metrics store is unhappy has the priority backwards. // // Two files provide this — one behind !js, one behind js — so that the // wasm build the CGO-free rule exists for never reaches DuckDB. It is // not enough to leave the import out of the request path: go-duckdb's // own build constraints exclude wasm, so any reachable import fails the // build with "build constraints exclude all Go files". func openAnalytics(dir string) func() { if dir == "" { return func() {} } path := filepath.Join(dir, "ops.duckdb") sink, err := obsduck.Open(path, 0) if err != nil { log.Printf("analytics: %v; the live panels still work", err) return func() {} } obs.Default.SetSink(sink) stop := make(chan struct{}) go sink.FlushEvery(30*time.Second, stop) fmt.Println("quarry: analytics ->", path) return func() { close(stop) if err := sink.Close(); err != nil { log.Printf("analytics: close: %v", err) } } }