Prometheus — Interfaces#
Interface catalog#
storage.Storage#
- Package:
github.com/prometheus/prometheus/storage - File:
storage/interface.go:82 - Methods: Embeds
SampleAndChunkQueryable(→Queryable+ChunkQueryable),Appendable,AppendableV2, plusStartTime() (int64, error)andClose() error - Purpose: Top-level contract for a complete time-series storage backend. Combines the full read path (samples and chunks) with both write paths (v1 and v2 appenders) and lifecycle methods.
- Implementations:
tsdb.DB(local TSDB),remote.Storage(remote read/write),storage.fanoutStorage(fanout multiplexer), agent WAL storage - Design quality: Good compositional design — Storage is a convenience “god interface” for wiring, but its constituent pieces (
Queryable,Appendable) are small and used independently. The presence of bothAppendableandAppendableV2reflects an in-progress migration; this is a transient design smell that is explicitly documented as ETA Q2 2026.
storage.Queryable#
- Package:
github.com/prometheus/prometheus/storage - File:
storage/interface.go:108 - Methods:
Querier(mint, maxt int64) (Querier, error) - Purpose: Single-method factory interface for opening a time-bounded read view over a storage. This is the primary seam between the PromQL engine and any storage backend. Any project (Thanos, Cortex, Mimir) can plug in its own backend by implementing this one method.
- Implementations:
tsdb.DB,fanoutStorage,remote.Storage,storage.QueryableFunc(adapter for plain functions),MockQueryable(testing) - Design quality: Exemplary ISP application. One method, one responsibility. The
QueryableFuncadapter (following thehttp.HandlerFuncpattern) shows the interface is designed for easy ad-hoc implementation. TheSelectHintsstruct passed deeper intoQuerier.Select()carries optional optimization hints, keeping the core interface clean.
storage.Appender / AppenderV2#
- Package:
github.com/prometheus/prometheus/storage - File:
storage/interface.go:291,storage/interface_append.go:156 - Methods (Appender v1): Embeds
AppenderTransaction(Commit() error,Rollback() error),ExemplarAppender,HistogramAppender,MetadataUpdater,StartTimestampAppender; plusAppend(ref SeriesRef, l labels.Labels, t int64, v float64) (SeriesRef, error)andSetOptions(*AppendOptions) - Methods (AppenderV2): Embeds
AppenderTransaction; singleAppend(ref SeriesRef, ls labels.Labels, st, t int64, v float64, h *histogram.Histogram, fh *histogram.FloatHistogram, opts AppendV2Options) (SeriesRef, error) - Purpose: Batched, transactional write interface for ingesting samples (floats, histograms, exemplars, metadata).
AppenderV2unifies all sample types into a singleAppendcall with an options struct, replacing the fragmented v1 interface that had separate methods for each type. - Implementations:
tsdb.headAppender,remote.sampleAndMetadataQueue,fanoutAppender, various test fakes - Design quality: The migration from v1 to v2 is an interesting case study in interface evolution. V1 grew organically — a separate interface was added each time a new data type (exemplars, histograms, metadata, start timestamps) was introduced, leading to a wide composite interface. V2 collapses these into a single method with a struct for optional data. The
SeriesRefcaching pattern (return a ref on first append; pass it back to skip label lookups on subsequent appends) is a clever performance optimization built into the contract.
storage.Querier / storage.SeriesSet / storage.Series#
- Package:
github.com/prometheus/prometheus/storage - File:
storage/interface.go:123,storage/interface.go:414,storage/interface.go:483 - Methods (Querier): Embeds
LabelQuerier(LabelValues,LabelNames,Close);Select(ctx context.Context, sortSeries bool, hints *SelectHints, matchers ...*labels.Matcher) SeriesSet - Methods (SeriesSet):
Next() bool,At() Series,Err() error,Warnings() annotations.Annotations - Methods (Series): Embeds
Labels(Labels() labels.Labels) andSampleIterable(Iterator(chunkenc.Iterator) chunkenc.Iterator) - Purpose: Three-tier iterator hierarchy for reading samples:
Querieropens a scan,SeriesSetiterates over matched series,Seriesiterates over samples within a series. The chain mirrors the physical layout of TSDB (label index → posting list → chunk iterator). - Implementations:
tsdb.blockQuerier,tsdb.headQuerier,storage.mergeQuerier(for fan-in across blocks), remote read querier - Design quality: Clean iterator protocol following the Go standard. The
Warnings()method onSeriesSetis notable — it allows non-fatal advisory messages (e.g., “metric X is stale”) to propagate alongside results without contaminating the error channel. Thechunkenc.Iteratorpassed toSeries.Iterator()is a re-use parameter — callers can pass back the previous iterator to avoid allocation; implementations may or may not honor it. This contract is documented at the interface level.
discovery.Discoverer#
- Package:
github.com/prometheus/prometheus/discovery - File:
discovery/discovery.go:35 - Methods:
Run(ctx context.Context, up chan<- []*targetgroup.Group) - Purpose: The extension point for all 30+ service discovery mechanisms. Each provider (Kubernetes, Consul, EC2, DNS, etc.) is a
Discoverer. The contract is minimal: run until context cancels, push target group updates to the provided channel. - Implementations: One implementation per provider:
kubernetes.Discovery,consul.Discovery,ec2.Discovery,dns.Discovery,file.Discovery,staticDiscoverer, etc. - Design quality: Near-perfect ISP. A single method makes it trivially easy to implement new providers. The asymmetric channel direction (
chan<-) makes ownership explicit — providers push, the manager pulls. The note that implementations must NOT close the channel on return is an important contract detail documented in the interface comment.
discovery.Config#
- Package:
github.com/prometheus/prometheus/discovery - File:
discovery/discovery.go:89 - Methods:
Name() string,NewDiscoverer(DiscovererOptions) (Discoverer, error),NewDiscovererMetrics(prometheus.Registerer, RefreshMetricsInstantiator) DiscovererMetrics - Purpose: Factory interface for discovery providers. Each SD mechanism provides a
Configthat knows its own name, how to construct aDiscovererfrom options, and how to register its Prometheus metrics. Thediscoverypackage uses reflection-based YAML (de)serialization to build aConfigsslice from unmarshaled config files without knowing the concrete types at compile time. - Implementations: One
Configimplementation per provider, registered viadiscovery.RegisterConfig() - Design quality: The three-method design bundles construction, naming, and observability. The reflection-based YAML dispatch (
Configs.UnmarshalYAML) is the most unusual piece — it usesreflect.StructOfto dynamically build a struct type covering all registered configs, enabling discovery plugins to be added without touching the YAML unmarshaling logic.
promql.QueryEngine#
- Package:
github.com/prometheus/prometheus/promql - File:
promql/engine.go:125 - Methods:
NewInstantQuery(ctx context.Context, q storage.Queryable, opts QueryOpts, qs string, ts time.Time) (Query, error),NewRangeQuery(ctx context.Context, q storage.Queryable, opts QueryOpts, qs string, start, end time.Time, interval time.Duration) (Query, error) - Purpose: Abstracts the PromQL execution engine. Introduced after the
*promql.Engineconcrete type already existed, primarily so it can be replaced, wrapped, or mocked. The concreteEnginestruct satisfies this interface. Thanos’s streaming engine and the community-developed FrostDB engine both implement it. - Implementations:
*promql.Engine(built-in), Thanos streaming engine, test fakes - Design quality: Two methods covering the two query modes (instant and range) is the right decomposition. The
QueryOptsparameter carries per-query tuning (lookback delta, per-step stats) as an interface rather than a struct, allowing downstream implementations to define their own options without breaking the signature. The returnedQueryinterface (not a*Result) enables lazy evaluation and cancellation.
promql.Query#
- Package:
github.com/prometheus/prometheus/promql - File:
promql/engine.go:143 - Methods:
Exec(ctx context.Context) *Result,Close(),Statement() parser.Statement,Stats() *stats.Statistics,Cancel(),String() string - Purpose: Represents a prepared but not yet executed PromQL query. Separates query preparation from evaluation; the caller controls when execution happens and can cancel or inspect it.
- Implementations:
*query(unexported concrete type returned byEngine.NewInstantQuery/NewRangeQuery) - Design quality: Well-designed for observability and resource management.
Stats()exposes timing breakdown per evaluation phase.Close()reclaims pooled result slices. TheCancel()method provides explicit cooperative cancellation on top ofcontextcancellation.
rules.Rule#
- Package:
github.com/prometheus/prometheus/rules - File:
rules/rule.go:38 - Methods:
Name() string,Labels() labels.Labels,Eval(ctx, queryOffset, evaluationTime, queryFunc, externalURL, limit) (Vector, error),String() string,Query() parser.Expr,SetLastError/LastError(),SetHealth/Health(),SetEvaluationDuration/GetEvaluationDuration(),SetEvaluationTimestamp/GetEvaluationTimestamp(),SetDependentRules/NoDependentRules/DependentRules(),SetDependencyRules/NoDependencyRules/DependencyRules() - Purpose: Common contract for
RecordingRuleandAlertingRule. Therules.Manageroperates on[]Ruleslices without knowing which type it holds. The dependency-tracking methods (SetDependentRules,NoDependentRules) support the concurrent rule evaluation optimization — rules with no dependents can execute in parallel. - Implementations:
*RecordingRule,*AlertingRule - Design quality: The interface has grown large (15+ methods) as features were added. The setter/getter pairs for health, duration, and timestamp are essentially mutable state accessors; this is more struct-like than idiomatic interface design. The dependency tracking methods are notably specific to one optimization — they arguably belong on a separate interface. This is the weakest interface design in the codebase, but it reflects the single-implementation reality (there are only ever two Rule types).
tsdb/chunkenc.Chunk / Iterator / Appender#
- Package:
github.com/prometheus/prometheus/tsdb/chunkenc - File:
tsdb/chunkenc/chunk.go:72,108,128 - Methods (Chunk):
Bytes() []byte,Encoding() Encoding,Appender() (Appender, error),NumSamples() int,Compact(),Reset(stream []byte), plusIterable(→Iterator(Iterator) Iterator) - Methods (Appender):
Append(st, t int64, v float64),AppendHistogram(prev, st, t, h, appendOnly) (Chunk, bool, Appender, error),AppendFloatHistogram(...) - Methods (Iterator):
Next() ValueType,Seek(t int64) ValueType,At() (int64, float64),AtHistogram(*Histogram) (int64, *Histogram),AtFloatHistogram(*FloatHistogram) (int64, *FloatHistogram),AtT() int64,Err() error - Purpose: Codec abstraction for compressed time-series chunks.
Chunkis the container;Appenderwrites into it;Iteratorreads from it. Encoding types (XOR for floats, delta/gorilla variants for histograms) are hidden behind these interfaces. - Implementations:
XORChunk(Gorilla float encoding),HistogramChunk,FloatHistogramChunk - Design quality: Clean separation of read and write paths. The
AppendHistogramreturn signature is unusual — it may return a newChunkwhen the current one overflows or needs recoding, making chunk splitting explicit and caller-controlled. TheIteratorre-use parameter (pass back previous iterator) is a deliberate allocation-reduction pattern.
Interface patterns#
Size distribution: Most interfaces are small (1–3 methods).
storage.Storageandrules.Ruleare the outliers at 8+ effective methods, and both are justified:Storageis a composition interface for wiring,Ruleis genuinely fat but has only two concrete implementations. Average across all architecturally significant interfaces is ~3 methods.Embedding: Used pervasively and thoughtfully.
StorageembedsSampleAndChunkQueryable,Appendable,AppendableV2SampleAndChunkQueryableembedsQueryableandChunkQueryableQuerierembedsLabelQuerierSeriesembedsLabelsandSampleIterableRefreshMetricsManagerembedsDiscovererMetricsandRefreshMetricsInstantiator- This “interface pyramid” pattern allows components to declare exactly the capability they need (e.g., the PromQL engine needs
Queryable, notStorage).
Implicit satisfaction: Interfaces are consistently defined at the consumer side.
storage.Queryableis defined in thestoragepackage and consumed bypromql.web/api/v1defines its ownTargetRetriever,RulesRetriever,AlertmanagerRetrieverinterfaces for the parts ofscrape.Manager,rules.Manager, andnotifier.Managerit needs — classic dependency inversion.discovery.Configis defined indiscoveryand implemented by each provider package.Stdlib interfaces used:
io.Closer— embedded byLabelQuerier,QueryTracker,QueryLoggerslog.Handler— embedded byQueryLogger- The standard
Next() bool / At() T / Err() erroriterator protocol (used bySeriesSet,ChunkSeriesSet,tsdb/chunks.Iterator,tsdb/index.Postings) is consistent withbufio.Scanneranddatabase/sql.Rowsconventions
Adapter functions:
storage.QueryableFunc(func →Queryable) andstorage.AppenderV2AsLimitedV1(v2 → v1 compatibility shim) follow thehttp.HandlerFuncpattern, reducing ceremony for simple implementations.
Key abstractions#
storage.Queryable— The single most architecturally important interface. Decouples the PromQL engine from all storage implementations. One method, swappable backend. This is why Thanos/Cortex/Mimir could reuse Prometheus’s query engine.storage.Appendable/storage.AppendableV2— The write-side counterpart toQueryable. Everything that produces metrics (scrape engine, rule evaluator, OTLP receiver, remote write receiver) writes throughAppendable, allowing fanout, filtering, and backend swapping without producer changes. The v1→v2 migration is visible here as an in-progress API evolution with explicit deprecation warnings.discovery.Discoverer— The plugin interface that enabled 30+ discovery providers to be added over Prometheus’s lifetime without modifying core code. One method, context-driven, channel-based output. Textbook extensibility design.promql.QueryEngine— Added retroactively to allow the concrete*Engineto be replaced. Enables Thanos’s distributed query engine, FrostDB, and other alternative engines to slot into the same Prometheus web handler without any changes to the API layer.tsdb/chunkenc.Chunk+Iterator— The low-level codec abstraction. Isolates all encoding complexity (Gorilla XOR, histogram delta coding) behind stable read/write contracts. TheIteratorre-use parameter is a performance contract encoded into the interface signature itself.
Interface-driven extensibility#
Prometheus uses interfaces for three distinct extensibility patterns:
Backend swapping (storage seam): storage.Queryable and storage.Appendable are the primary extension points. The entire storage stack — TSDB, remote storage, agent WAL — is wired together in main.go via these two interfaces. External projects swap the backend by providing their own implementations.
Plugin registration (service discovery): discovery.Discoverer + discovery.Config form a factory plugin system. New SD providers implement Config, register via discovery.RegisterConfig(), and are automatically available in YAML configuration. The reflection-based YAML dispatch means zero changes to the config unmarshaling logic per new provider.
Engine replacement (query engine): promql.QueryEngine allows replacing the built-in evaluation engine. This was added specifically to support Thanos’s streaming engine and alternative implementations. The web API layer (web/api/v1) depends on QueryEngine, not *Engine, making the substitution seamless.
Consumer-side interfaces (web API isolation): web/api/v1 defines narrow interfaces (TargetRetriever, RulesRetriever, ScrapePoolsRetriever, TSDBAdminStats) for the specific methods it needs from each manager. This keeps the HTTP handler testable with lightweight fakes rather than fully instantiated subsystems, and makes the dependency graph explicit.