prometheus/prometheus
Head and append path
Active contributors: jesusvazquez, codesome, krajorama, bwplotka
The Head is the in-memory portion of the TSDB. It holds every active series with its in-progress chunk plus a small ring of older mmapped chunks. New samples enter through an Appender, are buffered, and on Commit() are written to the WAL and inserted into the head.
Key types
| Type / Function | File | Role |
|---|---|---|
Head |
tsdb/head.go |
Top-level head structure: stripe-locked series map, WAL handle, exemplar store. |
stripeSeries |
tsdb/head.go |
Sharded map[storage.SeriesRef]*memSeries to reduce lock contention. |
memSeries |
tsdb/head.go |
Per-series state: labels, head chunk, mmapped chunks, last sample, last commit. |
headAppender |
tsdb/head_append.go |
V1 appender. Buffers AppendOps; Commit writes WAL + inserts. |
headAppenderV2 |
tsdb/head_append_v2.go |
V2 appender; one AppendEntry per call, supports ST/exemplars/metadata in one op. |
Head.gc() |
tsdb/head.go |
Periodically drops expired series and chunks. |
Head.Truncate(t) |
tsdb/head.go |
After a block is written, drops everything older than t. |
Head.WAL() |
tsdb/head.go |
Returns the underlying *wlog.WL (head + sample records). |
isolation |
tsdb/isolation.go |
Append/read isolation tracker; provides appendID + readSnapshot. |
Append path V1
sequenceDiagram
participant Scrape as scrapeLoop
participant App as headAppender
participant Stripe as stripeSeries
participant WAL
Scrape->>App: Appender(ctx)
Scrape->>App: Append(ref?, labels, t, v)
App->>Stripe: getOrCreate(labels)
Note right of App: Sample buffered in app.samples
Scrape->>App: Commit()
App->>App: Validate (out-of-order, dups, NaN handling)
App->>WAL: Log() series + samples in batches
App->>Stripe: memSeries.append(t, v)
App->>App: release isolation appendIDV1 is the default and the only path used by the older Storage.Appender(ctx) API. Notes:
- The
Append(ref, labels, t, v)returns aSeriesRef. Callers should store it and pass it back on subsequent appends to bypass the labels-lookup hot path. AddFastNoCheck(head_append.go) skips out-of-order checks for the common scrape case where samples are guaranteed monotonic per series.- Metadata (
MetricType,Help,Unit) is appended viaUpdateMetadata. - Native histograms and float histograms have their own append methods (
AppendHistogram,AppendHistogramCT).
Append path V2
V2 is the newer shape, defined in storage/interface_append.go::AppenderV2 and implemented in tsdb/head_append_v2.go. It is being rolled out as the canonical path; the migration tracker is issue #17632. Differences:
- A single
AppendEntrycarries timestamp, value, optional histogram, optional exemplars, optional start timestamp, optional metadata. - Always-on metadata: every entry can update the type/unit/help on the series.
- Exemplars are attached per sample, not separately appended.
- Out-of-order handling is consolidated.
scrape/scrape_append_v2.go is the scrape-side counterpart; remote write write_handler.go and the OTLP handler also implement V2.
Stripe locking
stripeSeries is an array of 16 (configurable via --storage.tsdb.stripe-size) sub-maps, each with its own RWMutex. Look up a series by hashing its labels modulo the stripe count. This caps contention to the worst-case-per-stripe rather than serializing all writes.
Isolation
tsdb/isolation.go implements lockless multi-version isolation:
- Each appender gets a monotonically increasing
appendID. - Each query takes a snapshot listing the open
appendIDs plus the high-water mark. - A read seeing a chunk produced by an in-progress append (whose
appendIDis in the open set) skips that chunk. appendIDandreadSnapshotare atomic operations; no per-series locks are needed for the isolation check.
You can disable isolation with -test.tsdb-isolation=false. CI runs both with and without it.
Out-of-order samples
When out_of_order_time_window > 0 (in tsdb config) the head accepts samples whose timestamp is within the window but older than head.maxt. These are stored in a separate OOO head (tsdb/ooo_head.go) and merged with the regular head on read (tsdb/ooo_head_read.go). At compaction time the OOO chunks are written to overlapping blocks; vertical compaction then merges them.
The OOO head has its own append path (Head.appendOOOHistogram, etc.) and its own metric set (prometheus_tsdb_ooo_*).
Chunk lifecycle in the head
A series' active chunk lives in memory. When it fills (default 120 samples) or its time range exceeds the chunk range:
- The chunk is finalized and added to
memSeries.mmappedChunks. - The bytes are appended to a
chunks_head/NNNNNNNsegment file (tsdb/chunks/). - The segment is mmapped and the in-memory representation drops the bytes — only an offset is retained.
- A new in-progress chunk is allocated for new samples.
The mmap budget is bounded by --storage.tsdb.head-mmap-budget (3.x). When exceeded, mmaps are unmapped on a least-recently-used basis.
Memory snapshot on shutdown
With --enable-feature=memory-snapshot-on-shutdown, Head.Snapshot() writes a serialized form of the in-memory state to chunks_head/snapshot.NNNNNNN/ so the next start can restore series and chunks without replaying the WAL. See docs/feature_flags.md for the rationale.
Entry points for modification
- Tweak the head append validation:
headAppender.appendable()and friends inhead_append.go. - Add a sample type: wire it through both V1 and V2, the WAL record (
tsdb/record/record.go), and the corresponding chunk encoding (tsdb/chunkenc/). - Adjust chunk-cut policy:
memSeries.appendPreprocessor()andchunkenc.MaxBytesPerXORChunk(or its equivalents).
See WAL for the durability layer, Blocks for what happens after compaction, and Storage for the interface contracts.
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.