Skip to content

feat: expose a native Thanos StoreAPI to serve long-term metrics from PostgreSQL#43

Merged
TeddyAndrieux merged 12 commits into
mainfrom
feat/thanos-store-api
Jun 18, 2026
Merged

feat: expose a native Thanos StoreAPI to serve long-term metrics from PostgreSQL#43
TeddyAndrieux merged 12 commits into
mainfrom
feat/thanos-store-api

Conversation

@TeddyAndrieux

Copy link
Copy Markdown
Contributor

What

The adapter now exposes a native Thanos StoreAPI (gRPC) so a Thanos Query can
federate the PostgreSQL-stored long-term metrics directly, without a Prometheus
remote_read passthrough behind a sidecar.

A Thanos sidecar only advertises its Prometheus' local TSDB time range, so it
cannot surface the history a Prometheus serves through remote_read to this
adapter. Exposing the StoreAPI from the adapter makes the PostgreSQL data a
first-class Thanos store, queryable for the whole range it actually holds.
See DESIGN.md for the full reasoning.

Changes

  • Thanos StoreAPI (gRPC) - new pkg/presentation/storeapi implementing
    storepb.StoreServer (Series / LabelNames / LabelValues) and
    infopb.InfoServer (Info). Samples are streamed as XOR chunks; series are
    sorted; external labels are configured via STORE_API_EXTERNAL_LABELS and
    applied to every series (with precedence on name collision), exactly like a
    sidecar applies a Prometheus' external labels. Matchers on an external label
    are validated and stripped before hitting SQL.
  • gRPC server wiring - runs alongside the HTTP server (GRPC_ADDR, default
    :10901), with graceful shutdown and the standard grpc_health_v1 health
    service for Kubernetes probes.
  • errors - migrate from github.com/pkg/errors to
    github.com/scality/go-errors (package-level ErrXxx sentinels + Wrap).
  • Repo alignment with scality/file-reflector - build/CI workflows, Dockerfile
    (distroless, statically linked, non-root), .golangci.yaml (v2), renovate,
    CODEOWNERS, ignore files.
  • Docs - add DESIGN.md, CONTRIBUTING.md, CLAUDE.md, and rework the
    README.md.
  • Debug logging - log the queries received (StoreAPI requests + remote_read)
    and the SQL executed against PostgreSQL, at debug level.

Testing

  • go test ./... - green
  • golangci-lint run - 0 issues
  • Verified end-to-end against a Thanos Query frontend: the adapter store
    registers and advertises its oldest sample, and a metric returns its full
    history through Thanos (external-label filtering included).

Issue: ARTESCA-17670

Introduce configuration for the upcoming Thanos StoreAPI gRPC server:
- GRPC_ADDR (default :10901) for the gRPC listen address
- STORE_API_EXTERNAL_LABELS for the external labels advertised to Thanos
  via the StoreAPI Info call (used for deduplication)

Issue: ARTESCA-17670
Add a new storeapi package with the conversion that maps a Thanos StoreAPI
SeriesRequest (matchers + time range) to a prompb.Query, so the existing SQL
query builder can be reused unchanged for the StoreAPI Series path.

This introduces the github.com/thanos-io/thanos/pkg/store/storepb dependency
(thanos v0.41.0, compatible with the current prometheus/prometheus v0.312.0).

Issue: ARTESCA-17670
@TeddyAndrieux
TeddyAndrieux requested a review from a team as a code owner June 18, 2026 04:51
Comment thread pkg/presentation/storeapi/series.go
Comment thread cmd/main.go
Add BuildSeries, which groups database rows into Thanos StoreAPI series:
- merges the metric name, stored labels and configured external labels into a
  sorted label set (external labels take precedence on collision, like the
  Thanos sidecar, so the StoreAPI output matches the sidecar path)
- sorts series by label set as required by the StoreAPI
- encodes samples into XOR chunks (120 samples per chunk)

Issue: ARTESCA-17670
Add the read queries the Thanos StoreAPI needs:
- MinSampleTimestamp: oldest metric_time, used to advertise the store's min
  time (with a boolean for the empty-database case)
- LabelNames / LabelValues: distinct label names and values (supersets,
  matchers/time not applied, which the StoreAPI permits)

Issue: ARTESCA-17670
Add the gRPC server backing the StoreAPI, implementing both storepb.StoreServer
(Series, LabelNames, LabelValues) and infopb.InfoServer (Info):
- Info advertises the configured external labels and a time range whose min is
  the oldest sample in the database (MaxInt64 when empty, so Thanos skips it)
- Series converts the request to SQL, queries the database, builds XOR-chunked
  series and streams them
- LabelNames/LabelValues expose the stored labels plus the external labels

Issue: ARTESCA-17670
@TeddyAndrieux
TeddyAndrieux force-pushed the feat/thanos-store-api branch from 1b69af4 to b4fb47c Compare June 18, 2026 09:24
Wire the Thanos Store/Info gRPC server into the DI container and start it from
main on GRPC_ADDR (default :10901), next to the existing HTTP server.

Issue: ARTESCA-17670
Describe the gRPC StoreAPI exposed by the adapter: the GRPC_ADDR and
STORE_API_EXTERNAL_LABELS settings, how to register it as a Thanos Query
endpoint, the external-labels behaviour, and a Kubernetes Service example.

Issue: ARTESCA-17670
Adopt github.com/scality/go-errors (the error library used by
scality/file-reflector) for consistency across Scality Go repositories.

Each failure now wraps a package-level ErrXxx sentinel - the error category,
so errors.Is keeps matching through the chain - via
errors.Wrap(ErrXxx, errors.CausedBy(rawErr), errors.WithProperty(...)),
with go-errors imported unaliased as errors. Sentinel titles preserve the
previous messages. Two use-case tests that built fake errors switch to the
standard-library errors package.
…flector

Mirror scality/file-reflector's documentation set for consistency across
Scality Go repositories:
- DESIGN.md: architecture (layers, ingestion pipeline, read paths, storage
  model) and the design decisions (PostgreSQL store, native Thanos StoreAPI,
  external labels, XOR chunks, single-replica rule)
- CONTRIBUTING.md: layering, coding conventions (slog, go-errors, testify),
  commit and PR workflow
- CLAUDE.md: short working guide importing the doc set
- README.md: reworked to file-reflector's structure (badges, how it works,
  setup, configuration table, container image, development), fixing stale
  environment variables (HTTP_ADDR, POSTGRESQL_DATABASE_NAME,
  LOGGER_LOG_LEVEL) and Docker build args
Adopt the same build/CI conventions as scality/file-reflector for consistency
across Scality Go repositories:
- workflows: replace the previous set with build / pre-merge / post-merge /
  promote / release / review (reusable build via workflow_call, GHCR images,
  git-describe tags); drop the file-reflector-specific capability and mockery
  jobs
- Dockerfile: golang:1.26 + distroless/static:nonroot (USER 65532), VERSION
  build-arg, build ./cmd; drop the file-reflector-specific setcap step
- add .dockerignore, align .golangci.yaml (v2) and .gitignore
- move CODEOWNERS under .github/, switch renovate to .github/renovate.json5
- fix the lint issues the new golangci config surfaces (errcheck, wsl_v5)
Give debug-level visibility into the queries flowing through the adapter, on
both edges:

- the StoreAPI server logs every request it receives (Series, LabelNames,
  LabelValues) with the raw matchers - including external-label matchers
  before they are stripped - and the requested time range;
- the remote-read use case logs each received Prometheus query;
- the PostgreSQL client logs every statement it runs - reads, label inserts,
  label name/value lookups, the startup metric recovery, and value COPYs -
  with its arguments.

All of these use log/slog at debug level and stay silent at the default info
level.
The empty-string __name__ equality branch built
"(l.metric_name IS NULL OR name = '')", but `name` is not a column of either
queried table (it is `metric_name` in `metric_labels`), so PostgreSQL rejects
the query with `column "name" does not exist` whenever a `__name__=""` matcher
reaches the builder. Qualify it as `l.metric_name`. This branch is now
reachable from Thanos Query through the StoreAPI, not only via remote_read.
@TeddyAndrieux
TeddyAndrieux force-pushed the feat/thanos-store-api branch from b4fb47c to ce0a29a Compare June 18, 2026 09:55
@TeddyAndrieux
TeddyAndrieux merged commit c07acff into main Jun 18, 2026
5 checks passed
@TeddyAndrieux
TeddyAndrieux deleted the feat/thanos-store-api branch June 18, 2026 11:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants