
On September 11, Elastic published a detailed account of how it built PromQL into Elasticsearch. Elasticsearch 9.5 makes its native Prometheus and PromQL support generally available, and Elastic reports that more than 80% of the queries in a corpus collected from public repositories run without modification.
That last sentence needs its full context. Elastic tested more than 2,000 real PromQL queries. The result is not a claim of complete language compatibility, and several important constructs remain unsupported. It is still a substantial result.
I read the post more carefully than most people will because I am four months into building a query engine for Arc's time-series data. Elastic's article is one of the most useful public descriptions I have seen of what implementing another query language on a columnar execution engine actually requires.
It also reinforces a pattern that has become difficult to ignore: storage architectures are converging, but compatibility and deployment surface increasingly decide which system a team can adopt.
What actually shipped
There are two distinct pieces.
First, Elasticsearch now accepts Prometheus remote write v1 directly. Snappy-compressed Protocol Buffer messages arrive at an Elasticsearch endpoint, labels become time-series dimensions, and the metric name and value are written into a time-series data stream. There is no sidecar translating the request into Elasticsearch bulk JSON.
Second, PromQL queries execute on the same compute engine that runs ES|QL. Elastic did not put a separate PromQL interpreter beside Elasticsearch or require a plugin. PromQL is another front end to the existing planner and distributed, vectorized execution pipeline.
That second part is the harder engineering claim, and it is the reason the article is worth reading.
The engineering is not trivial, and they show their work
PromQL and SQL disagree about time in ways that look cosmetic until you try to support both on one engine.
Elasticsearch normally groups timestamps with TBUCKET, which truncates timestamps to fixed boundaries. That operation is cheap, deterministic, and useful for reusing intermediate results.
Prometheus range queries use a different grid. Evaluation points are fixed steps anchored to the query range itself. Two queries can use the same step but produce different evaluation timestamps because their start times differ. To preserve that behavior, Elastic introduced TSTEP, an execution primitive whose grid comes from the query range and step rather than global clock boundaries.
Then there is the direction of a window. Evaluating:
avg_over_time(http_request_duration_seconds[5m])at time T means computing the average over (T - 5m, T]. It looks backward from the evaluation point. Elastic's existing window aggregation was forward-looking, so the team rewrote the path around a common backward-looking implementation that both ES|QL and PromQL can use.
That is the kind of detail that separates accepting PromQL syntax from reproducing PromQL semantics.
The grouping problem is even more interesting. SQL normally gives the planner a complete key:
STATS sum(x) BY cluster, namespacePromQL can express the inverse:
sum without(instance, pod) (http_requests_total)Now the query says which dimensions to exclude, not which ones to keep. The complete key can depend on the labels present in each series at execution time.
One implementation would discover every label used by the metric, subtract instance and pod, and rewrite the expression into an ordinary by(...) aggregation. Elastic rejected that approach because it adds a discovery phase and can force a wide, sparse dimension universe through planning and execution.
Instead, the team added dynamic grouping columns. Dimensions are loaded as series are read, and the exclusions are applied per series. The grouping schema no longer has to be known in full before the query runs.
Idiomatic PromQL makes the resulting keys wide. In a columnar engine, ten grouping labels would normally mean ten vectors being read, retained, hashed, and compared through each aggregation stage. Elastic added dimension packing, encoding the effective grouping key into one compact representation before aggregation.
The same article describes a TimeSeriesCollapse operator that reshapes columnar results into the series-oriented response expected by Prometheus clients without first building a large graph of boxed maps and lists in the HTTP layer.
None of this is glamorous. All of it is query-engine work. It is also reusable work: Elastic says the dimension-packing optimization benefits ES|QL time-series queries because both languages share the engine.
I am not writing this as a competitor looking for points to score. I am writing it as someone working on the same category of problem at a much smaller scale. Their implementation notes made several of my own design questions clearer, particularly around evaluation grids, window direction, and when a grouping key becomes knowable.
The compatibility number needs a footnote
Elastic's 80% figure is meaningful because it comes from more than 2,000 PromQL queries collected from public repositories and is backed by differential testing against Prometheus. It should not be read as “Elasticsearch implements 80% of the PromQL specification” or “80% of every company's dashboards will work.” The corpus has its own distribution of query shapes.
Elastic is explicit about the gaps. In 9.5, unsupported features include advanced vector matching with on(...) group_left, ranking functions such as topk and bottomk, label manipulation with label_replace and label_join, the absolute @ time modifier, and some compound expressions that mix offsets. The /api/v1/alerts and /api/v1/targets endpoints are also outside the current scope. Elastic maintains a function-by-function compatibility reference.
That distinction matters for migration claims. Elasticsearch exposes a Prometheus-compatible HTTP API, and Grafana can use it through its normal Prometheus data source. Existing dashboards can move without changes when their queries use the supported surface. Elastic's own Grafana setup guide tells users to review the limitations before moving dashboards.
So the accurate version is not “point every dashboard at Elasticsearch and everything works.” It is “the ingest protocol, query API, and most query shapes in Elastic's measured corpus are compatible, which removes a large share of migration work.” That is still a strong release.
Why this matters more than a storage announcement
In July, Elastic announced that Elasticsearch was becoming a columnar database. I wrote about what that architecture change means at the time. Columnar Mode ultimately shipped in 9.5 as a technical preview, while native Prometheus and PromQL support shipped as GA.
Columnar storage can improve the economics of a workload. It does not, by itself, move one dashboard, alert, or recording rule.
Prometheus compatibility attacks that adoption barrier directly. Remote write preserves the ingest protocol. The Prometheus query API gives compatible clients a familiar endpoint. PromQL preserves the language in which teams have already encoded operational knowledge.
This is why I read the release primarily as a migration feature. Elastic's stated motivation is that many teams already keep logs and traces in Elasticsearch while operating metrics in Prometheus or another metrics backend. Supporting the existing metrics interfaces gives those teams a path to consolidate without translating every query on day one.
The remaining 20% is not a rounding error, especially when it contains common dashboard functions such as topk. But migration cost is not binary. Removing most of the rewrite work changes which evaluations become practical.
The larger 2026 pattern
The pattern is broader than PromQL, but it needs to be described precisely.
Elastic adopted an interface created by the Prometheus ecosystem. InfluxDB 3.11 Enterprise added bulk Parquet import, and its storage tooling supports exporting compacted data as Parquet. QuestDB 10 introduced its own high-performance binary protocol, but its launch post explicitly says InfluxDB Line Protocol and PostgreSQL wire remain supported because that is how QuestDB fits into existing tooling. Amazon, meanwhile, signed an agreement to acquire DuckLabs, bringing the team behind DuckDB into a company with enormous distribution while the project and its IP remain with the independent DuckDB Foundation.
Those are not identical moves. QuestDB's QWP is a new native interface, while PromQL support is compatibility with an existing one. Parquet import is a data boundary, and the DuckLabs agreement is a distribution decision.
Together, however, they point in the same direction. A database cannot win only by having the right storage layout. It also has to meet users where their data, queries, dashboards, clients, and deployment environments already are.
Arc speaks InfluxDB Line Protocol for the same reason. I did not implement it because text line protocols are the final form of database ingestion. I implemented it because asking someone to replace Telegraf and rewrite an existing pipeline before evaluating a database is asking them not to evaluate it.
Architecture matters. Compatibility determines whether anyone gets far enough to experience it.
What is left when engines and interfaces converge
If serious analytical systems increasingly store columns, process vectors, and adopt neighboring interfaces, the interesting competition moves up a level.
Performance still matters. So do correctness, compression, and query coverage. But once a system is fast enough for a workload, deployment surface and operating cost become product features: where can it run, how many components does it require, what fails together, and who has to keep it healthy?
Elasticsearch and Arc make different choices here.
Elasticsearch is a JVM-based distributed system. It supports Linux AArch64, and it can run as a single node, so it would be inaccurate to claim that it cannot run on ARM or outside a large cluster. Elastic's own production guidance also says a one-node cluster is not resilient and is not recommended for production. A self-managed production deployment asks its operator to reason about nodes, shards, replicas, heap, cluster state, and recovery. Those capabilities are part of what lets Elasticsearch solve problems Arc does not attempt to solve.
Arc targets a different operating envelope: one static binary, local or object storage, open Parquet, and a deployment model intended to remain useful on a small Linux server or at the edge. That is not a claim that Elasticsearch is incapable of running there. It is a claim that the operational surface is different.
This is the part a compatibility release cannot erase. Two products may accept the same ingest protocol and query language while remaining very different things to deploy.
Where that leaves me
Elastic published a careful account of making a general-purpose distributed engine evaluate time-series semantics natively. I am doing a narrower version of the same work, with a fraction of the people, for a storage layout I control completely. The scales are different, but the engineering problems rhyme.
The post is useful because it does not pretend that parsing is the hard part. The hard part is preserving semantics while making them natural to the engine underneath: anchored evaluation grids, backward-looking windows, grouping keys discovered at runtime, compact aggregation state, and output shaped without throwing away the benefits of columnar execution.
The product lesson is just as clear. Being early to a correct architectural choice is worth something. It is worth less if adoption requires users to abandon the interfaces in which they already invested.
Elasticsearch 9.5 does not implement all of PromQL, and it does not make every Prometheus deployment an automatic migration. What it does is more concrete: it makes a large, measured subset of existing Prometheus workloads reachable through the same Elasticsearch engine that already stores many teams' logs and traces.
That is serious engineering, and it is a serious migration strategy.
Arc is an open source time-series database for telemetry you need to keep. It stores open Parquet, speaks standard SQL and familiar ingest protocols, and ships as one binary with no JVM required. Read the code on GitHub.