m3db/m3: a self-hosted TSDB, aggregator and Prometheus sidecar in one Go monorepo
M3 monorepo - Distributed TSDB, Aggregator and Query Engine, Prometheus Sidecar, Graphite Compatible, Metrics Platform
At a glance
- What is it?
- M3 bundles a distributed time series database, a query engine, a metrics aggregator and a Prometheus sidecar into a single Go repository. It is aimed at teams that already run Prometheus and have outgrown a single instance.
- Who is it for?
- Adopt m3db/m3 if you already operate Prometheus at a scale where a single server is the constraint, and you have people who can run etcd and a multi-node cluster. Do not adopt it if you want a managed metrics backend or a single-binary replacement for Prometheus on one host; the quickstart container is a local evaluation path, not a production topology.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 5 days ago.
- What is it written in?
- Mainly Go, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 30, 2026, and from our analysis. They are not legal advice.
Editorial analysis
What M3 is for, and who ends up running it
M3 is not one program. The repository describes a distributed TSDB, a query engine, a Prometheus sidecar, a metrics aggregator, and Graphite-compatible storage and query. Each of those is a service you can run separately, and the monorepo holds them together so they share protobuf definitions, generated mocks and build tooling. That layout is the first thing to understand: cloning github.com/m3db/m3 gets you the whole platform, not a single daemon.
The intended user is a platform or observability team that already collects metrics with Prometheus and has hit the point where one Prometheus server is the bottleneck. The Prometheus sidecar integration is the documented path for that case. A second audience is teams with existing Graphite instrumentation that want a different storage and query backend without rewriting dashboards, which is what the Graphite integration addresses. A third is anyone who needs long retention on time series and is willing to operate a cluster to get it.
If you are a single developer who wants a metrics store for a side project, this is the wrong shape of tool. The quickstart runs one container, but the architecture it demonstrates is a cluster: the create call in the README takes a placement type, and a namespace needs to be marked ready before writes land. Those two steps exist because data is sharded and replicated across nodes, and the API makes you acknowledge that up front.
How the pieces fit: placement, namespace, shard
The mechanism visible in the README is a two-step provisioning model. You POST to /api/v1/database/create with a body containing type, namespaceName and retentionTime. That call creates both the placement (where shards live) and a namespace (how long data is kept). Only then do you POST to /api/v1/services/m3db/namespace/ready with the namespace name. Until that second call, the namespace exists but does not accept writes, which is why the quickstart separates them.
Writes go to /api/v1/json/write as a JSON object with tags, a timestamp and a value. The tags map is where the metric identity lives: the README example uses __name__ for the metric name alongside city and checkout labels. Reads go to /api/v1/query_range with query, start, end and step parameters, and the README shows a filter expression such as third_avenue > 6000 working through the same endpoint. That is a PromQL-shaped interface, which is consistent with the query engine and Prometheus sidecar framing.
The aggregator sits on the ingest side rather than the query side. The Makefile exposes an aggregator_client variable defaulting to m3msg, which tells you the aggregator consumes from the project's own message queue rather than from a remote-write stream by default. The go.mod lists etcd-adjacent and consensus-related dependencies, and docker-compose.yml sets up host.docker.internal specifically so tests can reach an etcd container on the host. That confirms etcd is part of the operational surface for a real cluster, and it is not something the README quickstart asks you to run.
Installing M3 with Docker and writing your first metric
The README says the simplest and quickest way to try M3 is Docker, and points at the quickstart section for other options. The image it uses is quay.io/m3db/m3dbnode:v1.0.0, with port 7201 for the API and 7203 exposed alongside it, and a host volume mounted at /var/lib/m3db.
Start the container:
docker run -p 7201:7201 -p 7203:7203 --name m3db -v $(pwd)/m3db_data:/var/lib/m3db quay.io/m3db/m3dbnode:v1.0.0Next, create the placement and namespace. The retentionTime here is 12h, which is short enough for a local experiment:
curl -X POST http://localhost:7201/api/v1/database/create -d '{
"type": "local",
"namespaceName": "default",
"retentionTime": "12h"
}' | jq .Mark the namespace ready. If you skip this, writes have nowhere to go:
curl -X POST http://localhost:7201/api/v1/services/m3db/namespace/ready -d '{
"name": "default"
}' | jq .Write a point. The README's example metric is third_avenue with city and checkout tags:
curl -X POST http://localhost:7201/api/v1/json/write -d '{
"tags": {"__name__": "third_avenue", "city": "new_york", "checkout": "1"},
"timestamp": '"$(date "+%s")"',
"value": 3347.26
}'Query it back over a range. The README gives separate invocations for Linux and macOS/BSD because of the date flag syntax:
curl -X "POST" -G "http://localhost:7201/api/v1/query_range" \
-d "query=third_avenue" \
-d "start=$(date "+%s" -d "45 seconds ago")" \
-d "end=$(date +%s)" \
-d "step=5s" | jq .The README notes that jq is used only to format API output and is not required. What you should see is a JSON series for third_avenue containing the point you just wrote, provided the write timestamp falls inside the 45 second window.
The release cadence does not match the commit activity
The most concrete limitation is not architectural, it is about versions. The newest release listed in the repository is v1.5.0, tagged 2022-04-07. Before that, v1.4.2 on 2022-01-19 and v1.4.1 on 2021-11-20. The last push to master was on 2026-09-22. Commits are landing; tagged releases have not followed for years. That gap matters if your organisation pins deployments to release tags, because the quickstart image named in the README is v1.0.0, an even older tag.
A second limitation is that the README quickstart is explicitly a simplified version of the full guide and uses type: local, a single-node placement. Nothing in the README documents how to grow that local placement into a replicated multi-node one, what happens to existing shards when you do, or how to roll back a namespace or placement change. The README is silent on rollback entirely. If you need a documented upgrade path between cluster topologies, you will be reading the site documentation and the specs directory, not the README.
A third is the dependency surface. The Dockerfile builds from golang:1.26.0-bookworm and installs docker.io and docker-compose inside the image, and docker-compose.yml mounts /var/run/docker.sock so the build container can start further containers. That is a CI design, and it means the project's own test setup assumes Docker-in-Docker. Running the test suite the way the maintainers do is not a lightweight operation.
M3 against Prometheus alone, and against Thanos
The honest comparison is with running Prometheus by itself. Prometheus gives you scraping, a local TSDB, PromQL and alerting in one binary with a config file. M3 splits those responsibilities: the sidecar integration exists so Prometheus keeps scraping while M3 handles storage and query. You gain horizontal scale and longer retention; you take on a cluster, a placement model, namespaces and, for a real deployment, etcd. The README's own quickstart is the evidence for the cost: four API calls before a single metric is written.
Against remote-write-only long-term storage designs, the difference is where query happens. M3 ships its own query engine and serves /api/v1/query_range directly, so queries do not have to be proxied back to a Prometheus instance. It also ships a Graphite-compatible storage and query path, which most Prometheus-shaped long-term stores do not offer at all. If your organisation has Graphite dashboards it cannot migrate, that compatibility is the deciding factor and nothing else in this comparison matters.
The trade-off is coupling. Choosing M3 means adopting its placement and namespace vocabulary, its aggregator and its message queue, and its release process. A design that only adds remote storage behind an existing Prometheus keeps your operational vocabulary intact. M3 asks you to learn a new one.
Licence, upgrade cost and what the repository actually tells you
The project is released under the Apache License, Version 2.0, stated at the bottom of the README and present as the LICENSE file at the repository root. NOTICES.txt and a FOSSA status badge indicate third-party dependency scanning is part of the project's process. Apache-2.0 is permissive and includes a patent grant, which is generally the least complicated option for internal deployment and for redistribution, but the aggregator, query engine and sidecar are all one licence here rather than a split open-core model. That is worth confirming against the actual LICENSE and NOTICES.txt files rather than taking this article's word for it, and it is not legal advice.
Upgrade cost is where the release gap bites. Because v1.5.0 dates from 2022-04-07 and master has moved on to 2026-09-22, building from master and deploying a tagged release are materially different propositions. The go.mod declares go 1.26.0, so building from source requires a recent Go toolchain. If you build from source, the Makefile pulls submodules on demand: the first target runs git submodule update --init --recursive, which means a plain clone without --recursive will fetch them during the build. The Makefile also exposes a genny_target defaulting to genny-all, and generated code lives under generated/mocks, generated/proto, generated/assets and generated/thrift/rpc, so a source build involves code generation, not just compilation.
The practical implication: pin to a tag and you inherit 2022-era code; track master and you own an unreleased state. There is no third option documented in the README.
Editorial conclusion
Adopt m3db/m3 if you already operate Prometheus at a scale where a single server is the constraint, and you have people who can run etcd and a multi-node cluster. Do not adopt it if you want a managed metrics backend or a single-binary replacement for Prometheus on one host; the quickstart container is a local evaluation path, not a production topology. Before committing, verify that the release train matches your expectations: the newest release in the repository is v1.5.0 from 2022-04-07, while the last push to master was on 2026-09-22, so the tag history and the commit history tell different stories. Also confirm which of the monorepo's components (dbnode, aggregator, query, coordinator) you actually need, because the README describes them as separate services rather than one binary.
Frequently asked questions
Does m3db/m3 need etcd to run?
The README quickstart does not mention etcd, but docker-compose.yml is configured with host.docker.internal specifically so a test process can reach an etcd container published on the host, which indicates etcd is part of the cluster test setup. The README does not document etcd as a requirement for the single-container local example.
What ports does the m3db/m3 Docker quickstart expose?
The README command publishes 7201 and 7203, and mounts a host directory at /var/lib/m3db for data. The API calls in the quickstart all go to http://localhost:7201.
Why do I have to mark an m3db/m3 namespace ready after creating it?
The README splits provisioning into a create call and a ready call, and only the ready call makes the namespace usable. The README does not explain what the namespace does between those two states.
Is m3db/m3 actively released?
The most recent release listed in the repository is v1.5.0 from 2022-04-07, while the last push to master was on 2026-09-22. The README documents no release schedule, so the gap between tags and commits is something to verify against the CHANGELOG before you depend on it.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/m3db-m3)