chore: merge golang-conventions and prometheus-conventions into sources/

Absorbed content from rodin/golang-conventions and
rodin/prometheus-conventions into a sources/ directory.
Reference material — descriptive, not prescriptive.

Part of taxonomy cleanup (elixir-patterns issue #4).
This commit is contained in:
Rodin
2026-05-07 18:02:04 -07:00
parent 0de5f54365
commit 65a433d0c6
4 changed files with 831 additions and 0 deletions
+15
View File
@@ -0,0 +1,15 @@
# Sources
Reference material extracted from specific projects. Study for ideas, don't copy blindly.
These are **descriptive** — they document what a project does and why.
The `patterns/` directory is **prescriptive** — it tells you what to do.
Patterns that prove broadly applicable get promoted from here into `patterns/`.
The rest stays as reference for understanding how mature projects solve specific problems.
## Files
- `golang.md` — conventions from the golang/go source
- `golang-analysis.md` — deeper analysis of golang/go source architecture
- `prometheus.md` — patterns from prometheus/prometheus (TSDB, storage, metrics)
+364
View File
@@ -0,0 +1,364 @@
# Go Language Source: Architectural Conventions
How does the Go team build Go itself? What does the language source
reveal about conventions, governance, and infrastructure decisions?
**Repo:** [golang/go](https://github.com/golang/go)
---
## 1. Repo Shape
| Metric | Value |
|--------|-------|
| Size | 632M |
| Go files | 11,245 |
| Assembly files (runtime) | 200 |
| Commits | 66,142 |
| Contributors | 2,842 |
| Test files | 1,811 |
| Non-test files | 6,065 |
| Test ratio | 1:3.3 |
| TODOs (non-test) | 3,428 (all owner-attributed) |
### Organizational Philosophy
```
src/
├── cmd/ # The toolchain (compile, link, go, gofmt, vet)
│ └── compile/
│ └── internal/ssa/ # 417,686 lines of SSA compiler
├── internal/ # 61 hidden packages (API firewall)
├── runtime/ # 129,141 lines (scheduler, GC, memory)
├── encoding/ # Serialization (json, xml, gob)
├── net/ # Networking
├── io/ # Stream interfaces
└── testing/ # Test framework
```
The Go source has three layers:
1. **Flat stdlib** — user-visible packages (`fmt`, `io`, `net`)
2. **`internal/`** — shared infrastructure hidden from users (61 packages)
3. **`cmd/`** — the toolchain itself (compiler, linker, build tool)
---
## 2. What the Codebase Values
### By import frequency
| Package | Imports | Role |
|---------|---------|------|
| `fmt` | 2,031 | Formatting (the universal tool) |
| `testing` | 1,658 | Tests are first-class citizens |
| `strings` | 1,454 | String manipulation |
| `os` | 1,306 | System interaction |
| `unsafe` | 1,304 | Low-level memory access |
| `runtime` | 970 | Runtime introspection |
| `io` | 924 | Stream abstraction |
**The surprise:** `unsafe` (1,304 imports) ranks 5th — nearly tied with
`os`. The language that preaches memory safety uses `unsafe` extensively
in its own implementation. This is the "know the rules so you know
where they don't apply" principle.
### By size (what consumes the most lines)
| Component | Lines | Nature |
|-----------|-------|--------|
| SSA compiler | 417,686 | Mostly generated rewrite rules |
| Runtime | 129,141 | Scheduler + GC + memory |
| `testing/` | 4,770 | Testing framework |
| `internal/poll` | 5,087 | OS I/O multiplexing |
| `internal/fuzz` | 4,220 | Fuzzing infrastructure |
| `encoding/json/v2` | 6,387 | The JSON rewrite (experiment) |
---
## 3. The Bootstrap Problem
**How does Go compile itself?**
Go has been self-hosting since May 2015 (Russ Cox, commit `0f4132c907`:
"all: build and use go tool compile, go tool link"). The bootstrap
chain:
1. A pre-built Go 1.4 binary compiles the bootstrap tools
2. Those tools compile the current Go toolchain
3. The new toolchain recompiles itself for correctness
**The `cmd/dist` tool** orchestrates this multi-stage build. Unlike
Elixir (which keeps 33 Erlang files permanently), Go eliminated its
non-Go dependencies entirely. The only external requirement is a
previous Go binary.
**Convention:** Self-hosting means the compiler, linker, and runtime
are all written in Go. The runtime includes 200 assembly files for
architecture-specific operations (scheduler context switches, atomic
operations, system calls).
---
## 4. TODO Culture: Owned Accountability
```go
// TODO(gri) — 320 occurrences (Robert Griesemer)
// TODO(mdempsky) — 198 occurrences (Matthew Dempsky)
// TODO(adonovan) — 170 occurrences (Alan Donovan)
// TODO(mknyszek) — 98 occurrences (Michael Knyszek)
// TODO(rsc) — 96 occurrences (Russ Cox)
```
**3,428 TODOs** in non-test code. Every TODO has an owner. The top 5
TODO authors are all core team members (compiler, runtime, and tools
leads).
**Convention:** `// TODO(username): description`
**Growth pattern:** linkname directives — 43 touches in 2019, 48 in
2022, 92 in 2024, 72 in 2025. The codebase is actively evolving its
relationship with internal/external boundaries.
**Contrast with Elixir:** Go TODOs are permanent documentation of known
limitations. Elixir TODOs are time-bombs with version deadlines. Go
accepts technical debt as a layer; Elixir refuses to let it accumulate.
---
## 5. Unique Patterns
### 5.1 `internal/` as API Firewall (61 packages)
Go's most distinctive structural pattern. The `internal/` directory is
enforced by the compiler — code outside the tree cannot import these
packages.
**Key internal packages:**
- `internal/godebug` — runtime feature flags (79 settings)
- `internal/goexperiment` — compile-time experiment guards
- `internal/singleflight` — dedup concurrent function calls
- `internal/bisect` — binary search for debugging (Russ Cox, 2023)
- `internal/poll` — OS I/O polling (5,087 lines)
- `internal/fuzz` — fuzzing coordinator (4,220 lines)
- `internal/coverage` — code coverage (3,821 lines)
**Convention:** Code shared between stdlib packages but not suitable for
public API goes in `internal/`. This is Go's answer to "how do you
share utilities without committing to backward compatibility."
### 5.2 GODEBUG: Runtime Feature Flags
Introduced 2021 (Brad Fitzpatrick), formalized as a compatibility
mechanism by Russ Cox in 2022 (proposal #56986: "extended backwards
compatibility for Go", 70 comments).
```go
var http2server = godebug.New("http2server")
func ServeConn(c net.Conn) {
if http2server.Value() == "0" {
// disable HTTP/2
}
}
```
**79 godebug settings** across the codebase. Each time a non-default
setting causes a behavior change, code calls `IncNonDefault()` to
increment a counter readable via `runtime/metrics`.
**Convention:** When a behavior change might break existing programs,
add a GODEBUG setting. The old behavior remains accessible via
`GODEBUG=setting=old_value`. New Go versions automatically use the
old behavior when building code that declared an older `go` directive
in `go.mod`.
**This is the Go compatibility promise made machine-enforceable.**
### 5.3 GOEXPERIMENT: Compile-Time Feature Gates
```go
// In internal/goexperiment/flags.go:
// GOEXPERIMENT=jsonv2 enables the new JSON API
```
**Convention:** Major API additions ship behind experiment flags.
`encoding/json/v2` (6,387 lines, Apr 2025) exists in the tree but is
only visible when `GOEXPERIMENT=jsonv2` is set. This allows the code
to be developed in-tree, tested by adventurous users, and refined
before the compatibility promise applies.
### 5.4 Compiler Directives as Hidden Language
```go
//go:linkname localFunction remote/package.Function
//go:nosplit
//go:nowritebarrier
//go:noescape
//go:systemstack
```
**1,711 `go:linkname` directives** and **2,428 runtime compiler
directives** in non-test code. These are effectively a hidden language
within Go — they bypass the type system, calling conventions, and
garbage collector safety for performance-critical paths.
**Convention:** Directives are ONLY acceptable in the runtime and
compiler. The Go team is actively trying to reduce `go:linkname` usage
(92 touches in 2024 — many are removals). Third-party packages that use
`go:linkname` to access internals are explicitly unsupported.
### 5.5 Generated Code: The SSA Compiler
The SSA (Static Single Assignment) compiler backend is 417,686 lines,
but the largest files are generated:
```
opGen.go — 97,135 lines (generated)
rewriteAMD64.go — 79,703 lines (generated)
rewritegeneric.go — 38,337 lines (generated)
rewriteARM64.go — 26,203 lines (generated)
```
**Convention:** Generated files are checked into the repo (not
generated at build time). They contain a `// Code generated` header.
The generators live alongside their output. This means `git blame`
works on generated code — you can trace when a rewrite rule was added.
### 5.6 The Runtime: 129K Lines of Go + Assembly
```
proc.go — 8,156 lines (THE goroutine scheduler)
malloc.go — 2,501 lines (memory allocator)
mgc.go — 2,315 lines (garbage collector)
mheap.go — 3,030 lines (heap management)
panic.go — 1,788 lines (panic/recover machinery)
```
The scheduler (`proc.go`) documents its own design at the top:
> "The main concepts are: G - goroutine. M - worker thread, or machine.
> P - processor, a resource that is required to execute Go code."
**Convention:** The runtime combines Go and assembly (200 `.s` files).
Architecture-specific operations (context switches, atomic ops, system
calls) are in assembly. Everything else is in Go, using compiler
directives to bypass safety checks where needed.
---
## 6. PR Discussion Patterns
Go uses GitHub issues for proposals, not PRs for discussion. The key
governance mechanism is the **proposal process** with designated
reviewers.
### json/v2 (Issue #71497, 201 comments, Jan 2025)
"The largest major revision of a standard Go package to date."
**Key design decision:** Split into `encoding/json/v2` (semantic) and
`encoding/jsontext` (syntactic). The syntactic layer has no reflection
dependency — it's a pure JSON tokenizer.
**Ship strategy:** Land behind `GOEXPERIMENT=jsonv2`, iterate with
community feedback, then graduate. The code lives in-tree but doesn't
count as a compatibility commitment until the experiment flag is
removed.
**External validation:** Built as `github.com/go-json-experiment/json`
first, iterated for years, then proposed for stdlib. The implementation
preceded the proposal.
### GODEBUG (Issue #56986, 70 comments, Nov 2022)
Russ Cox's proposal for machine-enforced backward compatibility.
**The problem:** Sort algorithm changes, bug fixes, and behavior
improvements can break programs that depend on old behavior.
**The solution:** `go.mod`'s `go` directive becomes a compatibility
declaration. Programs built with Go 1.22 but declaring `go 1.20`
automatically get Go 1.20 behavior for any setting that changed between
1.20 and 1.22.
**Lesson:** The Go team solved "how do you improve a language without
breaking users" with a general mechanism rather than case-by-case
migration. Each new GODEBUG setting is a structured backward
compatibility opt-out.
### Generics (Issue #15292, 874 comments, 2016-2021)
5 years of discussion. The most debated language change in Go's history.
**Lesson:** Committee-driven projects can take years on foundational
decisions because consensus requires addressing every edge case. Compare
to Elixir's formatter (1 hour, zero comments) — the BDFL model moves
faster but accepts more single-point-of-failure risk.
### slog (Issue #56345, 841 comments, 2022-2023)
Structured logging took 10 months and 841 comments to land.
**Lesson:** Even when the need is clear and the solution is well-known,
Go's process requires exhaustive discussion. The result is usually
better (slog is well-designed), but the cost is measured in months.
---
## 7. Cross-Ecosystem Comparisons
| Aspect | Go | Elixir |
|--------|-----|--------|
| TODOs | 3,428, owner-attributed, permanent | 127, version-gated, deadlines |
| Self-hosting | Complete since 2015 | 33 Erlang files permanently |
| Feature gates | GOEXPERIMENT (compile-time) | None (ship or don't) |
| Compat mechanism | GODEBUG (79 settings) | Deprecation → removal on version |
| Governance | Committee (proposals, 874-comment threads) | BDFL (José, 1-hour merges) |
| Internal boundary | `internal/` (compiler-enforced, 61 packages) | OTP applications (convention-enforced) |
| Generated code | Checked in (97K-line files) | Compile-time (no artifacts) |
| Assembly | 200 .s files in runtime | None (delegates to Erlang/BEAM) |
| Biggest file | 97,135 lines (generated) | 7,102 lines (Kernel) |
---
## 8. What This Teaches
1. **`internal/` solves "shared but not public" at the language level.**
61 packages that other ecosystems have to solve with conventions,
Go solves with compiler enforcement. This is why Go projects
rarely have "utils" packages that leak abstraction — the pattern
exists in the language itself.
2. **GODEBUG is the most sophisticated backward compatibility mechanism
in any language runtime.** It makes the compatibility promise
*machine-verifiable* rather than *socially-enforced*. Programs don't
just get old behavior by default — they get it because their `go.mod`
declares what era they belong to.
3. **GOEXPERIMENT enables fearless iteration in the stdlib.** json/v2
can exist in-tree, be tested, be refined — all without triggering
the compatibility promise. This is "feature flags for a language."
4. **3,428 TODOs is honest, not sloppy.** Each one has an owner. They
document known limitations rather than hiding them. Go prefers
"explicitly imperfect" over "implicitly broken."
5. **Compiler directives are a hidden language.** 4,139 directives
(linkname + runtime pragmas) bypass Go's safety model. The Go team
accepts that the runtime needs a different language than users —
but actively restricts this power from escaping to third-party code.
6. **Generated code checked in > generated at build time** for
archaeology. `git blame` on `rewriteAMD64.go` tells you when a
codegen rule was added and why. Build-time generation loses this
history.
7. **Committee governance produces better designs but 10x slower.** Go's
slog (841 comments, 10 months) vs Elixir's formatter (0 comments,
1 hour). The designs are comparable in quality — the process cost
is where they differ.
8. **`unsafe` in Go's own source (1,304 imports) proves that safety
rules are for users, not for runtime implementors.** The people who
wrote the safety rules know exactly where they don't apply.
<!-- PATTERN_COMPLETE -->
+270
View File
@@ -0,0 +1,270 @@
# Go Language Source: Convention Reference
Quick-reference for conventions extracted from the golang/go source
code. Each entry: pattern name, location, example, when to use,
when NOT to use, origin.
---
## Owner-Attributed TODOs
**Location:** Throughout `src/`
```go
// TODO(gri): consider using a different approach here
// TODO(rsc): this should be cleaned up in the next release
```
**When to use:** Known limitations that a specific person should
address. The owner tag creates accountability without creating an
issue (issues are for user-visible problems; TODOs are for internal
engineering debt).
**When NOT to use:** User-visible bugs (file an issue instead).
Aspirational improvements with no clear owner. Anything that should
block a release.
**Origin:** Convention since Go's earliest commits. The Go team
averages 3,428 TODOs across the codebase — this is a conscious
engineering culture, not neglect.
---
## `internal/` Packages
**Location:** `src/internal/` (61 packages)
```go
// internal/singleflight — dedup concurrent calls
// internal/godebug — runtime feature flags
// internal/poll — OS I/O polling
// internal/bisect — binary search debugging
```
**When to use:** Code shared between stdlib packages that should NOT
become public API. Utility code that isn't stable enough for the
compatibility promise. Implementation details that users shouldn't
depend on.
**When NOT to use:** Code that external packages need. Code that's
stable enough for public API (promote it). One-off helpers that only
one package uses (keep them package-private).
**Origin:** `src/internal/` existed since Go moved sources from
`src/pkg` to `src` (2014). The compiler enforces the import restriction
— no code outside the tree can import internal packages.
---
## GODEBUG: Runtime Feature Flags
**Location:** `internal/godebug/godebug.go` (316 lines)
```go
var http2server = godebug.New("http2server")
func ServeConn(c net.Conn) {
if http2server.Value() == "0" {
// user opted out of HTTP/2
}
// IncNonDefault must be called each time non-default behavior fires
http2server.IncNonDefault()
}
```
**When to use:** Behavior changes that might break existing programs.
The old behavior becomes accessible via `GODEBUG=setting=value`. Tied
to `go.mod`'s `go` directive for automatic version-based defaults.
**When NOT to use:** Bug fixes that no reasonable program depends on.
New features (use GOEXPERIMENT instead). Performance optimizations that
don't change observable behavior.
**Origin:** Brad Fitzpatrick added the package in Aug 2021. Russ Cox
formalized it as the backward compatibility mechanism in proposal
#56986 (Nov 2022, 70 comments). 79 settings now exist.
---
## GOEXPERIMENT: Compile-Time Feature Gates
**Location:** `internal/goexperiment/flags.go` (136 lines)
```go
// Set via: GOEXPERIMENT=jsonv2 go build
// Check via build tag: //go:build goexperiment.jsonv2
```
**When to use:** Major new APIs or behavior changes that need real-world
testing before committing to the compatibility promise. The code lives
in-tree but isn't visible without the flag.
**When NOT to use:** Small features that can ship directly. Bug fixes.
Internal refactoring. Anything that doesn't need user feedback before
committing.
**Origin:** The GOEXPERIMENT mechanism predates its formalization — used
for fieldtrack, regabi, unified. json/v2 (Apr 2025) is the highest-
profile use: 6,387 lines shipped behind a flag.
---
## Compiler Directives
**Location:** Throughout `runtime/` and `cmd/`
```go
//go:linkname localName remote/pkg.ExportedName
//go:nosplit
//go:nowritebarrier
//go:noescape
//go:systemstack
```
**When to use:** ONLY in the runtime and compiler. `linkname` accesses
unexported symbols across packages. `nosplit` prevents stack growth
checks. `nowritebarrier` asserts no GC barriers. `systemstack` forces
execution on the system stack.
**When NOT to use:** In application code. In stdlib packages outside
runtime. In third-party packages (explicitly unsupported — the Go team
actively removes `go:linkname` targets that external packages depend
on).
**Origin:** 1,711 `go:linkname` and 2,428 other runtime directives.
The Go team is actively trying to reduce linkname usage (92 touches
in 2024, many removals).
---
## Generated Code (Checked In)
**Location:** `cmd/compile/internal/ssa/`
```go
// Code generated by ssa/gen/*.go; DO NOT EDIT.
// opGen.go — 97,135 lines
// rewriteAMD64.go — 79,703 lines
// rewritegeneric.go — 38,337 lines
```
**When to use:** When the generated output needs `git blame` history.
When generators should be auditable alongside their output. When build
reproducibility matters (no external tool dependency).
**When NOT to use:** When the generated output is large AND changes
frequently (diff noise). When the generator is trivial (just use
`go generate` at build time).
**Origin:** SSA compiler introduced on dev.ssa branch (2015). The
rewrite rules are generated from a DSL but checked in so reviewers
can see exactly what changed.
---
## Assembly in Runtime
**Location:** `runtime/*.s` (200 files)
```asm
// runtime/asm_amd64.s
TEXT runtime·gogo(SB), NOSPLIT, $0-8
MOVQ buf+0(FP), BX // gobuf
MOVQ gobuf_g(BX), DX
...
```
**When to use:** Context switches, atomic operations, system calls,
and anything that needs direct control over registers/stack. Each
architecture has its own set of assembly files.
**When NOT to use:** Anything that can be written in Go with acceptable
performance. The Go team actively converts assembly to Go where
possible (e.g., crypto packages moving from asm to Go+intrinsics).
**Origin:** Runtime has always included assembly — Go's scheduler and
GC require direct hardware control that no high-level language can
provide.
---
## Proposal Process (Committee Governance)
**Location:** GitHub issues, not PRs
```
Issue #71497 (json/v2): 201 comments, Jan 2025
Issue #56986 (GODEBUG): 70 comments, Nov 2022
Issue #56345 (slog): 841 comments, 10 months
Issue #15292 (generics): 874 comments, 5 years
```
**When to use:** Any user-visible API addition or behavior change.
The proposal process ensures all edge cases are considered before
committing to the compatibility promise.
**When NOT to use:** Internal refactoring, performance improvements
that don't change API, bug fixes. These go through normal code review
(Gerrit/GitHub).
**Origin:** The Go proposal process evolved from informal (early Go)
to formalized (2015+). Russ Cox and the Go team manage a proposal
review meeting that triages and decides.
---
## The Scheduler as Documentation
**Location:** `runtime/proc.go` (8,156 lines)
```go
// Goroutine scheduler
// The scheduler's job is to distribute ready-to-run goroutines over
// worker threads.
//
// The main concepts are:
// G - goroutine.
// M - worker thread, or machine.
// P - processor, a resource that is required to execute Go code.
```
**When to use:** Complex algorithms should document their model at the
top of the file. The Go runtime uses extensive comments to explain
the scheduler's invariants, the GC's phases, and the memory
allocator's structure.
**When NOT to use:** Simple code that's self-documenting. Comments
that restate what the code does rather than WHY it does it.
**Origin:** The G-M-P model comment dates to the scheduler rewrite
(2014). The convention of architectural documentation at file-top
extends throughout the runtime.
---
## json/v2: Experiment-to-Stdlib Pipeline
**Location:** `encoding/json/v2/` (6,387 lines, 13 files)
```go
// encoding/json/v2 — semantic layer (uses reflection)
// encoding/jsontext — syntactic layer (no reflection dependency)
```
**When to use:** When building a major stdlib revision. The pattern:
1. Build as external module (`go-json-experiment/json`)
2. Iterate with real users for years
3. Propose for stdlib with working implementation
4. Ship behind GOEXPERIMENT flag
5. Graduate when stable
**When NOT to use:** Small additions that don't need years of
iteration. Features that can be backward-compatible additions to
existing packages.
**Origin:** `go-json-experiment/json` existed since 2022. Proposed as
#71497 (Jan 2025, 201 comments). Landed behind flag Apr 2025 (commit
`0e17905793` by Damien Neil).
<!-- PATTERN_COMPLETE -->
+182
View File
@@ -0,0 +1,182 @@
# Patterns Extracted from prometheus/prometheus
## Pattern: Atomic File Operations with Suffix Convention
**Source:** `tsdb/db.go`
**Category:** storage
**What:** Use directory suffixes (`.tmp-for-creation`,
`.tmp-for-deletion`) to make multi-step file operations
crash-safe. On startup, clean up any dirs with these
suffixes (they represent incomplete operations).
**Why:** Database storage needs atomicity. If the process
crashes between creating a block and finalizing it, you
need to know the block is incomplete. The suffix convention
makes incomplete state visible at the filesystem level
without requiring a separate journal.
**Example:**
```go
const (
tmpForDeletionBlockDirSuffix = ".tmp-for-deletion"
tmpForCreationBlockDirSuffix = ".tmp-for-creation"
)
// On startup: remove any .tmp-* dirs (incomplete ops)
// On create: write to dir.tmp-for-creation, then rename
// On delete: rename to dir.tmp-for-deletion, then remove
```
**When to use:** Any system that manages files/directories
and needs crash consistency without a full WAL. Simpler
than a write-ahead log for coarse-grained operations.
**When NOT to use:** When you already have a WAL or
transaction log. Or for fine-grained operations where
rename semantics are insufficient.
---
## Pattern: DefaultOptions() Function
**Source:** `tsdb/db.go`
**Category:** configuration
**What:** Provide a `DefaultOptions()` function returning a
fully-populated config struct. Users copy and override only
what they need. No nil-means-default ambiguity.
**Why:** Large config structs (20+ fields) are unwieldy.
By providing sane defaults as a function (not a
package-level var), you avoid mutation bugs and make it
clear what "normal" looks like. Users only specify
deviations.
**Example:**
```go
func DefaultOptions() *Options {
return &Options{
WALSegmentSize: wlog.DefaultSegmentSize,
RetentionDuration: int64(15*24*time.Hour / ...),
MinBlockDuration: DefaultBlockDuration,
MaxBlockDuration: DefaultBlockDuration,
SamplesPerChunk: DefaultSamplesPerChunk,
// ... 20 more fields with sane defaults
}
}
// Usage:
opts := tsdb.DefaultOptions()
opts.RetentionDuration = 30 * 24 * time.Hour
db, err := tsdb.Open(dir, nil, nil, opts, nil)
```
**When to use:** Config structs with many fields where most
users want defaults. Especially when zero-value semantics
would be confusing (e.g., 0 retention = infinite? or off?).
**When NOT to use:** Small configs (3-4 fields) where
struct literal with zero-means-default is clear enough.
---
## Pattern: Scrape Loop with Aligned Timestamps
**Source:** `scrape/scrape.go`
**Category:** concurrency
**What:** Periodic scrape loops that align timestamps to
intervals with a small tolerance, enabling better storage
compression downstream.
**Why:** Time-series databases compress better when
timestamps are regular. A 2ms tolerance on alignment
means scraped data aligns to the expected grid while
accommodating real-world jitter.
**Example:**
```go
var ScrapeTimestampTolerance = 2 * time.Millisecond
var AlignScrapeTimestamps = true
// In scrape loop: if scrape finishes within tolerance
// of expected timestamp, snap to the grid
```
**When to use:** Any periodic data collection where
downstream storage benefits from timestamp regularity.
Metrics, heartbeats, polling loops.
**When NOT to use:** Event-driven data where timestamps
must reflect actual occurrence time. Audit logs, user
actions, financial transactions.
---
## Pattern: Sentinel Errors with Interface Check
**Source:** `tsdb/db.go`
**Category:** error-handling
**What:** Define package-level sentinel errors with
`errors.New()` and use compile-time interface assertions
to verify implementations satisfy storage interfaces.
**Why:** `ErrNotReady` as a sentinel lets callers use
`errors.Is` for retry logic. The pattern ensures error
identity is stable across versions (not string-matched).
**Example:**
```go
var ErrNotReady = errors.New("TSDB not ready")
// Callers can reliably detect this:
if errors.Is(err, tsdb.ErrNotReady) {
// Retry later — DB is still initializing
}
```
**When to use:** Any error that callers need to handle
programmatically (retry, fallback, special UI). Make it a
named sentinel, not a string comparison.
**When NOT to use:** Errors that are always terminal or
always logged-and-discarded. Not every error needs a name.
---
## Pattern: Compile-Time Interface Satisfaction
**Source:** `scrape/scrape.go`
**Category:** organization
**What:** Use `var _ Interface = (*Type)(nil)` to verify at
compile time that a type satisfies an interface, even if
the type is only used dynamically.
**Why:** Without this, you discover missing methods only
when the type is actually used — which might be in a
rarely-exercised code path or only in production. The
compile-time check catches it immediately.
**Example:**
```go
var _ FailureLogger = (*logging.JSONFileLogger)(nil)
// Fails at compile time if JSONFileLogger doesn't
// implement FailureLogger
```
**When to use:** Any type that implements an interface
consumed dynamically (registered in a map, stored as
interface value, passed to framework code).
**When NOT to use:** Types whose interface satisfaction is
already enforced by direct usage in the same package.
<!-- PATTERN_COMPLETE -->