Sayonora
← Back to the Warp overview — Warp is the intelligent database gateway. This page is the full technical detail behind that: every outcome, use case, benchmark, and honestly-scoped capability, organized by what it does — Connect, Protect, Control, Accelerate, Observe.
Warp: full capabilities
On this page
- Outcomes
- Use cases
- Admin console
- Mach vs. round trip
- Sharding
- Native-backend mode
- SQS
- InfluxDB
- GraphDB (Neo4j Bolt+Cypher)
- Rollups
- Observability
- AI features beyond MCP
- Multi-AZ deployment
- Security
- Planned/unplanned outages
- Error handling
- See it happen
- Try Warp in 60 seconds
- Warp vs. alternatives
- Compatibility, honestly scoped
Outcomes, by pillar#
Everything below rolls up into the same five things Warp does — Connect, Protect, Control, Accelerate, Observe — grouped here instead of as one flat list, so it's clear which outcome each capability is actually buying you.
Connect
Simple architecture
One gateway for all protocols and data sources.
Governed AI access
An MCP frontend gives AI agents real database access — Postgres by default, or Oracle/MySQL/SQL Server directly in native-backend mode — through the same SQL firewall, ACL, and QoS controls as every other client (Postgres mode only — see "Native-backend mode" below for what native MCP mode carries over) — not a separate, unguarded path to your data.
Easy to test
Write tests against each protocol's own real client library — psql, mongosh, boto3, python-oracledb, opensearch-py, and more — no Warp-specific SDK to learn first.
Protect
Strong security
Centralized policies, auth, and SQL firewall — see "Security" below for the full, honestly-scoped detail.
Control
Scale elastically
Add shards, regions, tenants without re-architecting.
Planned & unplanned downtime management
A scheduled drain waits for a clean cutover; an unrelated backend failure triggers automatic failover in seconds — the two cases are handled deliberately differently, across every wire protocol.
Accelerate
Max performance
Smart routing, QoS control, Mach, and rollups.
Lower costs
Better resource utilization and reduced complexity.
Observe
Operational excellence
Deep observability, audit, and real-time insights.
Native error translation
Every real Postgres failure comes back as that protocol's own genuine error — ORA-03113, a MongoDB codeName, a DynamoDB exception name — so each client's own retry/reconnect logic keys off exactly what it expects.
Use cases#
- Governed access for AI agentsAn MCP frontend lets AI agents and modern service architectures query Postgres directly — through the same SQL firewall, ACL, and QoS controls as every other client, not a separate, unguarded path to your data.
WARP_MCP_BACKEND=oracle/mysql/sqlserverpoints the same tools at a real Oracle/MySQL/SQL Server backend instead, the same "keep the database you have" tradeoff native-backend mode makes for every other protocol. - A single, secure gateway across multiple data sourcesOne admin surface — SQL firewall, ACL, routing, QoS, live metrics — in front of traffic that used to be spread across five different databases' own tooling, each with its own access model.
- Connection pooling for Postgres, Oracle, MySQL, or SQL ServerEvery backend — whichever of the four it is — is fronted by a bounded connection pool: thousands of client connections into Warp can share a small, fixed-size pool of real backend connections, the number configured independently of client concurrency, the same problem PgBouncer solves for Postgres specifically. Built into the same gateway that's already translating and firewalling the traffic, and the one thing native-backend mode (see below) keeps even though it bypasses everything else in the pipeline.
- Horizontal sharding, by real bound valuesRoute by schema, predicate, or a real bound parameter value — decoded from each protocol's own wire format across Postgres, Oracle, SQL Server, and MySQL, not just SQL text — with genuine cross-shard merge (not concatenation) for aggregates, sorted/paginated hits, and DynamoDB/MongoDB/SQS/OpenSearch sharding alongside SQL. See "Sharding" below.
- Distributed transactions (XA)Coordinate a transaction across multiple backend databases from a client that only knows how to talk to one, for the cases a single-database commit isn't enough.
- Query result caching (Mach)Warp's embedded distributed cache, opt-in per table, serves repeat exact-key reads without a round trip to Postgres. Invalidation is automatic and table-level — any write against a cached table (whether it binds parameters or sends literal values, the invalidation match works either way) evicts every cached entry for that table — no application-side caching code to write or invalidate.
- Pre-aggregated rollups for analytics queriesDefine a GROUP BY/aggregate summary once — Warp keeps a real materialized table fresh on a schedule and rewrites matching client queries to read from it automatically, via a real SQL query planner, not a hand-maintained second copy your application has to know about.
- Time-series writes without running InfluxDBA real line-protocol
/writeendpoint and a bounded InfluxQL/query(WHERE, GROUP BY time(), mean/sum/count/min/max) land on Postgres — a real TimescaleDB hypertable when the backend has the extension, a plain indexed table otherwise. See "InfluxDB" below. - Property-graph queries without running Neo4jReal Bolt+Cypher —
CREATE,MATCH ... WHERE ... RETURN, bounded variable-length paths — against a real Postgres nodes/edges schema, spoken to the officialneo4jdriver over the real Bolt wire protocol. See "GraphDB" below. - Keeping the database engine you already haveNative-backend mode proxies straight to a real Oracle, MySQL, or SQL Server backend of your own — no dialect translation, no Postgres migration — with Warp's connection pooling and connection ACL still in front of it (the SQL firewall, QoS, and caching are pipeline stages this mode bypasses). See "Native-backend mode" below.
- Standardizing on one databaseDifferent teams' applications speak different protocols against different databases. Warp lets every one of them land on the same Postgres, without every team rewriting its data layer first.
- Permanent compatibility shimSome client code isn't worth touching — an old MongoDB driver, a legacy ORM tied to a specific dialect. Run Warp indefinitely and let it keep speaking that protocol forever while everything actually lives in Postgres.
- Mid-migration bridgeRun Warp while Ferry (or another migration tool) moves schema and data behind the scenes — old client code keeps working unmodified throughout the cutover, no coordinated "flag day" rewrite required.
Admin console#
Warp ships its own admin app — live traffic metrics across every protocol (gRPC and MCP shown separately, not merged into one label), per-tool MCP call counts and error rates, cache-hit-vs-Postgres RTT for every protocol including OpenSearch, and a sidebar that groups controls the same way this page does: Security (SQL firewall, ACL, OAuth) as its own labeled section, Router Rules for sharding/routing targets under Traffic, deployment topology, backends, queues, and LLM configuration for dialect translation — all in one place, not scattered across each protocol's own tooling.
Click through it yourself: Open the interactive demo → No signup, no backend required — synthetic data, real layout.
Mach hit vs. Postgres round trip, per protocol#
Shared across protocols, not per-protocol silos — in two tiers. PostgreSQL, MySQL,
Oracle, SQL Server, gRPC, and MCP all read and populate the same distributed cache for
arbitrary opted-in SELECT results — a result cached via one protocol is
served to another asking for the same row, with no duplicate cache and no
protocol-specific staleness window. A second, narrower tier now covers exact primary-key
point lookups only (GetItem, find by _id, SQL
WHERE pk = ?) — and this one does bring DynamoDB and MongoDB in, each
sharing its cache entries with any SQL client reading the same physical Postgres table
(though not directly with each other, since they store data under different physical
tables even when the logical table/collection name matches). See
the DynamoDB/MongoDB demo for exactly what
that looks like.
How a cross-protocol hit actually happens
A sophisticated question to ask here: how does a request from one protocol resolve to the
same cache entry as an equivalent request from another? Warp doesn't parse two
differently-worded queries and recognize they mean the same thing — it's simpler and more
literal than that, on purpose. Every cache entry's key is built from: the backend the
request targets, the SQL text after dialect translation (the same stage that turns
an Oracle or MySQL query into the Postgres SQL that actually runs), the bind parameter
values, and the caller's access context. Two requests share an entry when all four match —
which happens routinely for the common case (an app issuing the same shaped query,
SELECT ... WHERE id = ?, from more than one protocol) since dialect
translation is a no-op for plain SQL with nothing protocol-specific to rewrite, but it
isn't a semantic-equivalence engine: the same logical read phrased with different
whitespace, casing, or column order — or via a query shape one protocol's own dialect
translator rewrites and another's doesn't touch — won't share an entry. Only SELECT
statements against tables explicitly opted in (a name/pattern list, not "every table") are
eligible at all; anything else always goes to Postgres. Every write against an opted-in
table evicts every cached entry for that table by name, regardless of whether the write
used bind parameters or literal values — a simple, coarse invalidation, not row-level
tracking, so a write to one row briefly costs a cache miss on unrelated rows in the same
table too. Entries expire after a configurable TTL (30 seconds by default) even without a
write. There's no cross-instance consistency protocol beyond that: every Warp instance
in the cluster reads and writes the same distributed cache directly, so a write anywhere is
visible everywhere on its very next read, bounded by ordinary distributed-cache propagation
time, not a separate sync step.
See benchmark methodology and full numbers →
Mach is Warp's embedded, distributed query result cache — the same admin console tracks this per wire protocol: how long a cache hit takes against a real Postgres read or write. Measured on a local loopback deployment (client, Warp, and Postgres all on one machine), 30+ samples per cell — every protocol's connections were opened together and warmed with an equal number of throwaway calls each before any sample was taken, then measured in round-robin order (one call per protocol, repeated) rather than one protocol's whole run before the next. An earlier pass here that measured each protocol in its own sequential block produced skewed numbers — whichever protocol ran first absorbed the JVM's own warm-up cost — so it's corrected below. The point isn't the absolute millisecond figures, which shrink further over a real network, it's that the cache consistently wins on every protocol Warp speaks, without any application-side caching code, and that a real Postgres round trip costs about the same regardless of which wire protocol asked for it.
| Protocol | Cache hit | Postgres read | Postgres write | Cache speedup |
|---|---|---|---|---|
| Postgres (pgwire) | 0.08 ms | 0.53 ms | 0.83 ms | 6.7× |
| SQL Server (mssqlwire) | 0.08 ms | 0.60 ms | 0.93 ms | 8.0× |
| MySQL (mywire) | 0.08 ms | 0.74 ms | 0.73 ms | 9.3× |
| Oracle (orawire) | 0.08 ms | 0.57 ms | 0.60 ms | 7.6× |
| MongoDB (mongowire) | 0.10 ms | 0.61 ms | 1.48 ms | 5.9× |
| DynamoDB (dynamowire) | 0.07 ms | 0.60 ms | 0.95 ms | 9.0× |
| OpenSearch (oswire) | — | 1.24 ms | 1.13 ms | — |
Workload, exactly: for the four SQL protocols, SELECT * FROM t against
a cached row (cache hit), the same query against an uncached row (Postgres read), and
UPDATE t SET id = id (Postgres write) — issued through each protocol's real
driver (psycopg2, python-tds/pymssql, python-oracledb, the MySQL CLI), not a synthetic
benchmark harness. For MongoDB, findOne by _id (mongowire's
cache only covers exact-_id lookups) against a cached vs. uncached document,
and insertOne for the write. For DynamoDB, GetItem by primary
key (same exact-key-only caching) against a cached vs. uncached item, and
PutItem for the write. For OpenSearch, a term _search
and an index (upsert) call via the real opensearch-py client —
oswire has no result cache at all (see the rollups/caching architecture above; every
_search/_doc call hits Postgres directly), so its cache-hit and
speedup cells are genuinely blank, not omitted data.
Sharding: by real bound values, across every protocol that has one#
See sharding detail →
Warp routes by a real bound parameter value — not just SQL text — across
Postgres, Oracle, SQL Server, and MySQL wire protocols, decoding each protocol's
own native binary bind-parameter encoding (Postgres extended-query, Oracle,
SQL Server sp_executesql, MySQL COM_STMT_EXECUTE) so a client's
PreparedStatement value — tenant_id, customer_id,
whatever the shard key is — reaches the router correctly, whichever of the four protocols
the client happens to speak. A client that sends the same value as a plain SQL literal
(psql, simple-query mode, an ORM that doesn't bind parameters) still routes correctly too,
via literal-value matching — the feature degrades to "still correct," not "silently
wrong." Hash, consistent-hash (150 virtual nodes per backend, minimizing what actually
moves when the shard set changes), range, and list strategies are all real, configured via
the same WARP_ROUTER_VALUE_SHARD_RULES knob regardless of which protocol
or bind style the traffic arrives as.
A cross-shard SQL query — COUNT/SUM/AVG/
MIN/MAX, with or without GROUP BY, and
ORDER BY/LIMIT/OFFSET — is genuinely merged across
shards, not concatenated: AVG is a real weighted average (not an
average-of-averages), and a global LIMIT caps the merged result, not each
shard's own contribution. A query shape outside that set is refused with a clear error
rather than silently mis-merged.
Sharding isn't SQL-only. DynamoDB (dynamowire) hashes by the real DynamoDB
partition key. MongoDB (mongowire) hashes by _id for a
{_id: ...}-shaped query; anything broader scatter-gathers. SQS
(sqswire) hashes by queue name (one queue lives entirely on one backend — there's no
per-message key to shard by independently). OpenSearch (oswire) hashes documents
by doc_id, and a structured _search — hits, pagination, and
terms/metric aggregations including a real weighted avg, even
nested inside a terms bucket — merges across shards the same way SQL's does.
k-NN (vector) and hybrid search on a sharded collection are refused with a clear error
rather than quietly searching only one shard — a real, disclosed boundary, not a silent
gap.
What sharding doesn't do: there's no elastic/online resharding anywhere — changing the shard set doesn't migrate data, queues, or documents already placed under the old set. Consistent hashing minimizes what would need to move; nothing moves it automatically today.
Native-backend mode: proxy straight to Oracle, MySQL, or SQL Server#
See native-backend detail →
By default, orawire/mywire/mssqlwire all translate the client's SQL into Postgres dialect
and run it against real Postgres — that's the whole point of the shared pipeline described
under "Architecture" above. Setting WARP_ORACLE_BACKEND_MODE=native,
WARP_MYWIRE_BACKEND=mysql, or WARP_MSSQLWIRE_BACKEND=sqlserver
switches that one protocol to the opposite mode instead: no translation at all, statements
proxy straight through to a real Oracle, MySQL, or SQL Server backend of your own,
configured via that protocol's own WARP_ORACLE_HOST/WARP_MYSQL_HOST/
WARP_MSSQL_HOST family of settings. A client that already speaks Oracle,
MySQL, or SQL Server keeps talking to the exact same engine it always has, with nothing
about the SQL itself rewritten in transit.
MCP gets the same toggle: WARP_MCP_BACKEND=oracle,
=mysql, or =sqlserver (default postgres) points
execute_sql, list_tables, and describe_table at a
real Oracle/MySQL/SQL Server connection of Warp's own the same way. Oracle needs its own
WARP_ORACLE_USER/WARP_ORACLE_PASSWORD here specifically — unlike
orawire's native mode, which reuses whatever credentials the client itself logged in with,
MCP has no client login step to source a per-caller Oracle identity from, so it needs one
configured. Three MCP tools stay Postgres-only regardless of the toggle —
document_schema, explain_query, and
query_natural_language all hardcode Postgres-specific SQL (a literal
EXPLAIN (FORMAT JSON ...), or a schema-drafting prompt written assuming
Postgres) — tools/list doesn't even advertise them in native mode, and
calling one anyway returns a clear "not supported" error rather than silently running SQL
that's simply wrong for the configured backend. Tools registered via
WARP_MCP_TOOLS (real Postgres functions/procedures turned into MCP tools) are
Postgres-only for the same reason and aren't introspected at all in native mode.
What native mode doesn't do: it bypasses the shared eight-stage pipeline entirely for
every statement, not just the dialect-translation stage — so the SQL firewall and QoS
admission control (both pipeline stages) don't apply to native-mode traffic, any more than
Mach caching, rollups, or cross-backend value-sharding do. What still applies is whatever
happens before a statement ever reaches that pipeline: connection ACL (CIDR allow/deny,
enforced at TCP accept time, protocol-agnostic) and the connection pool itself — a real
WARP_POOL_MAX_SIZE-bounded pool (default 30) of actual backend connections
that a much larger number of client connections into Warp share, the client-facing and
backend-facing connection counts deliberately decoupled, exactly as they are for every
other protocol. Native mode is a real, direct, pooled proxy to your existing database —
not the fully-governed path dialect-translation mode (the default) gives every other
protocol; pick it when keeping the current engine matters more than SQL firewall/QoS/
caching coverage, and the default mode when it doesn't.
SQS: enqueue vs. dequeue#
sqswire has no result cache — a queue's whole point is that every message is a real state
change, nothing is safely repeatable from a cache — so instead of the cache-hit breakdown
above, it reports the two halves of the queue lifecycle separately. Workload:
SendMessage (enqueue) and ReceiveMessage (dequeue) via boto3's
SQS client, 40 samples each, warmed and round-robined the same way as the table above.
sqswire runs the AWS-style JSON-over-HTTP API real SQS also uses, not a raw binary wire
protocol like the other six — the modest premium over the table above (roughly 1.5–2x a
Postgres round trip via pgwire, not the much larger gap an earlier, less careful
measurement pass here showed) is HTTP request/response framing and JSON parsing on every
call, not the queue logic itself: SendMessage and ReceiveMessage are each a single SQL
statement against Postgres, same as the tables above.
| Protocol | Enqueue (SendMessage) | Dequeue (ReceiveMessage) |
|---|---|---|
| Amazon SQS (sqswire) | 1.18 ms | 1.25 ms |
InfluxDB: line-protocol writes, a real InfluxQL subset#
influxwire speaks InfluxDB's real v1 wire format -- POST /write with a genuine
line-protocol body, GET/POST /query for a real, bounded InfluxQL subset
(WHERE, GROUP BY time(), mean/sum/count/min/max) --
against a plain Postgres table, one per measurement, tags and fields stored as
jsonb. Like oswire and sqswire, influxwire has no result cache of its own
(a time-series write is a new point, not a repeatable read to serve from cache) and sits
outside the shared pipeline Mach's other six protocols share -- see
Mach for that scope.
TimescaleDB, detected not required. influxwire checks whether the target Postgres
backend has the TimescaleDB extension installed and takes a genuinely different code path
per result: a real hypertable (create_hypertable(...)) when it's there, a
plain indexed table otherwise -- verified live against both a stock postgres:16
backend and a real timescale/timescaledb container, confirmed via
timescaledb_information.hypertables/.chunks that the hypertable
path really does create real chunks, not just log a claim that it did. Every other
operation (write, InfluxQL translation, GROUP BY time() bucketing via
Postgres's own native date_bin()) is identical code on both paths -- plain
Postgres works today, TimescaleDB is where real write/retention volume would actually need
to go.
| Protocol | Write (1 point) | Query (SELECT ... LIMIT 1) |
|---|---|---|
| InfluxDB (influxwire) | 2.25 ms | 1.32 ms |
Median of 30 samples, warmed first, via the real official influxdb (v1)
Python client -- a lighter-weight methodology than the round-robin table above (single
protocol, no cross-protocol warm-up interleaving needed since there's only one to measure
here), reported honestly as such rather than dressed up to look like the same rigor.
SELECT * FROM <measurement> LIMIT 1 against a single-row measurement is
the workload -- not a GROUP BY/aggregate query, which costs more (a real, unavoidable
consequence of actually computing an aggregate over matching rows, not a wire-protocol
overhead). Not yet implemented: a dedicated cache-hit-vs-round-trip breakdown the way the
six cached protocols above get, since influxwire has no cache to hit.
Why this is higher than the round-robin table's own Postgres numbers (0.53/0.83 ms):
InfluxDB's real wire protocol is HTTP -- every /write and /query
is its own independent, stateless HTTP request, with no persistent client session to hold a
backend connection open across requests the way a pgwire client's session does. Borrowing a
pooled Postgres connection once per request (plus real HTTP request parsing) is a genuine,
unavoidable cost of speaking a stateless request/response protocol, not an inefficiency in
the translation itself -- see boltwire's own numbers below for the contrast a persistent,
session-based wire protocol makes.
GraphDB: real Neo4j Bolt+Cypher, on Postgres#
boltwire speaks Neo4j's real Bolt wire protocol -- the same binary handshake, PackStream
serialization, and message framing (HELLO/RUN/PULL/
RECORD) the official neo4j driver (Python and Java both verified
live) speaks to a real Neo4j server -- against a real Postgres schema underneath: one shared
nodes table (labels text[], properties jsonb) and one
shared edges table, not a table per label the way influxwire is a table per
measurement, since any node relating to any other regardless of label is the whole point of
a property graph. A bounded Cypher subset is implemented, not the full language: CREATE
for a node or a single node-edge-node pattern, and MATCH ... WHERE ... RETURN
including bounded variable-length paths ([*1..3], translated to a real Postgres
WITH RECURSIVE query with cycle detection) -- an unbounded [*] path is
rejected at parse time rather than accepted and left to run away against a large graph.
Like influxwire, boltwire has no result cache of its own and sits outside Mach's shared
pipeline -- see Mach for
that scope.
| Protocol | Write (CREATE one node) |
Query (MATCH ... RETURN one node) |
|---|---|---|
| GraphDB (boltwire) | 1.10 ms | 0.73 ms |
Median of 30 samples, warmed first, via the real official neo4j Python driver --
same lighter-weight single-protocol methodology as influxwire's own table above, reported
honestly as such. Unlike influxwire's HTTP request/response model, Bolt is a persistent TCP
session -- so boltwire holds one pooled Postgres connection per Bolt session, reused across
every query the client sends, instead of borrowing and returning a fresh one per request.
(An earlier version of this page reported 2.07/1.08 ms: it was still doing the latter, plus
wrapping even a single-node CREATE in an explicit transaction with nothing to
roll back that mattered -- both real, fixed inefficiencies, not measurement noise, which is
why these numbers now land close to the round-robin table's own Postgres baseline
(0.53/0.83 ms) above rather than the 2x+ gap influxwire's own genuinely-stateless HTTP
model still has.) Not yet implemented: MERGE/SET, returning a
created or matched relationship as a real Bolt Relationship struct (edges are
written and queryable, just not yet returned as their own wire object), chained multi-hop
patterns beyond one relationship, OR in WHERE, and a
cache-hit-vs-round-trip breakdown, for the same reason influxwire doesn't have one.
Rollups: pre-aggregated tables, kept fresh automatically#
See rollup configuration and detail →
A rollup is a real Postgres table Warp creates and keeps current for you — the
materialized result of a GROUP BY/aggregate query you define once, not a view
and not something your application maintains by hand. Define it in YAML (a
WARP_ROLLUP_DEFINITIONS_FILE, or the same field via the admin API):
rollups:
- name: daily_order_totals
backend: primary
source_table: orders
group_by:
- customer_id
- order_date
aggregations:
- "SUM(amount) AS total_amount"
- "COUNT(*) AS order_count"
refresh_interval_minutes: 15
max_staleness_minutes: 30
Every 15 minutes, Warp runs exactly the SQL you'd write by hand to keep this current —
DROP TABLE IF EXISTS warp_rollup_daily_order_totals; then
CREATE TABLE warp_rollup_daily_order_totals AS SELECT customer_id, order_date,
SUM(amount) AS total_amount, COUNT(*) AS order_count FROM orders GROUP BY customer_id,
order_date; — against the real orders table, on the backend named
primary. max_staleness_minutes is the cutoff past which Warp
stops trusting that table for acceleration until the next refresh succeeds.
Acceleration is automatic and always on for a fresh, matching rollup — there's no
separate switch to flip. Warp embeds a genuine SQL parser, validator, and
relational query planner inside its own pipeline. When a client sends an aggregate query
against orders, Warp parses it, checks whether a fresh rollup's source
table is mentioned, and rewrites the query to read from
warp_rollup_daily_order_totals instead whenever its
materialized-view matcher can prove that substitution is valid for that specific query
shape — defining the rollup above is turning rewriting on for it, nothing else to
configure. Your application still just queries orders — it never has to know
the rollup exists. If the rollup is stale, doesn't apply, or the rewrite can't be proven
safe for that query, Warp falls straight through to the real table unchanged — a
rollup can only ever make a matching query faster, never make an unmatched one wrong.
Which protocols this applies to: rollup acceleration lives in the same shared
pipeline as the SQL firewall and dialect translation, so it covers every protocol that
sends a SQL statement through that pipeline — pgwire, mywire, mssqlwire, orawire,
gRPC, and MCP's execute_sql tool. mongowire, dynamowire, and sqswire
don't build SQL text at all — a Mongo find or a DynamoDB GetItem
goes straight from that protocol's own store layer to Postgres, so there's no SQL
statement for a rollup to match against and no rewriting happens for those three today.
Observability: CloudWatch, Datadog, New Relic, Grafana, and more#
See export surfaces and per-platform detail →
Warp exposes two real, independent export surfaces — a Prometheus-format
/metrics endpoint and a periodic OTLP metrics push, over either gRPC
(:4317, the default) or HTTP (:4318, set
WARP_OTEL_PROTOCOL=http) — and how each platform gets that data differs
by platform. Some scrape /metrics directly, some accept an OTLP push with no
extra hop, and some (CloudWatch, Azure Monitor, GCP Cloud Monitoring, AppDynamics) need an
OpenTelemetry Collector in between to translate into their native API. OTLP/HTTP exists
for the networks gRPC doesn't reach cleanly — corporate proxies and L7 load balancers that
only forward plain HTTP/HTTPS. This is metrics only today — no traces or logs — and
Warp doesn't set a service.name resource attribute, so a receiving
platform identifies its data by the warp_* metric-name prefix, not a
service tag.
| Env var | Default | What it does |
|---|---|---|
| WARP_OTEL_PROTOCOL | grpc | grpc or http |
| WARP_OTEL_ENDPOINT | http://localhost:4317 | Collector/backend endpoint (defaults to :4318 when protocol=http) |
| WARP_OTEL_EXPORT_INTERVAL_MS | 5000 | Push interval |
| WARP_OTEL_HEADERS | — | Comma-separated key=value request headers (e.g. an API key) |
| WARP_METRICS_PORT | 19090 | Prometheus /metrics scrape port |
| WARP_ADMIN_TOKEN | — | Bearer auth on /metrics, if set |
Self-hosted Grafana
Point a Prometheus server (or Grafana Agent/Alloy) at /metrics and add it as a Grafana data source — no collector, no OTLP.
Grafana Cloud
Either scrape /metrics and remote_write it in, or push OTLP straight to Grafana Cloud's own OTLP gateway.
Datadog
The Datadog Agent can scrape /metrics as a Prometheus check, or take a native OTLP push on recent Agent versions — either way, no collector required.
New Relic
Push OTLP straight to otlp.nr-data.net:4317 with your API key in WARP_OTEL_HEADERS. No collector hop.
AWS CloudWatch
No native OTLP receiver — run an OpenTelemetry Collector (e.g. ADOT) with the awsemf exporter in between.
Azure Monitor / App Insights
Same shape — a Collector with the azuremonitor exporter and your connection string.
GCP Cloud Monitoring
A Collector with the googlecloud (or googlemanagedprometheus) exporter forwards into Cloud Monitoring.
AppDynamics
Historically agent/controller-based rather than an open metrics receiver — route through a Collector into Cisco Cloud Observability.
AI features beyond MCP#
MCP is one way an AI agent reaches Warp — a client speaking a protocol. These ten are
different: Warp itself calling an LLM, at specific points in its own pipeline (five in
the shared admin/pipeline surface, five as their own MCP tools), to do something a fixed
rule can't. All ten share the same design discipline: a deterministic decision stays
deterministic — whether to retry a failed query, whether a rate is anomalous, whether a
statement is a genuine read — the LLM only phrases, drafts, or judges, it never makes the
call alone; and nothing an LLM produces is ever applied or executed without a check
— a draft is plain JSON an admin reviews and applies through an endpoint that already
existed, a narration always comes with the real fact it describes, and anything that
actually runs still passes through Warp's own firewall/QoS/pipeline like every other
statement. All ten read one shared LLM provider config
(WARP_LLM_PROVIDER/API_KEY/BASE_URL/MODEL,
hot-reloadable via PUT /api/llm-config with no restart) — configure it once,
every feature below picks it up.
Query repair
When Postgres genuinely rejects a statement with a narrow, deliberately-chosen set of
SQLSTATEs (syntax error, undefined function, datatype mismatch, feature not supported —
never "this table doesn't exist," which no LLM can fix), the LLM gets one shot at
rewriting it, retried exactly once. Any second failure surfaces the ORIGINAL Postgres
error, not a confusing second one. Set WARP_QUERY_REPAIR_ENABLED=true to
enable — off by default since rewriting a rejected statement is more invasive than
translating a known dialect gap.
Natural-language firewall rules
POST /api/firewall-rules/draft turns "block any DELETE against orders
without a WHERE clause" into a structured rule — action, priority, statement type,
table/SQL pattern — validated against the exact same grammar the firewall itself uses
(a real regex compile-check catches an LLM's invalid syntax before it's ever shown).
Nothing is inserted; an admin reviews it and submits it through the existing
POST /api/firewall-rules.
Traffic anomaly detection
A background scan compares each protocol's traffic rate against its own recent
baseline (an exponential moving average) and flags a real threshold breach — 3× baseline
by default, with a minimum-rate floor so near-zero traffic isn't flagged on noise. The
LLM's only job is turning that into one plain-English sentence; with no LLM configured,
the raw numeric anomaly is still recorded. Set
WARP_ANOMALY_SCAN_INTERVAL_MINUTES=<n> to enable, read back via
GET /api/anomalies.
QoS tuning suggestions
POST /api/qos-suggestions/draft proposes ONE targeted rate-limit change —
the default limit or a single workload class — based on current config and recent
per-backend load. Returns ready-to-paste fields for the existing
PUT /api/config, never writes anything itself. Honestly scoped: the evidence
is per-backend load, not per-workload-class throughput, since that's the only traffic
signal actually tracked today.
MCP audit + summarization
Every MCP tool call now lands a real audit event — tool, arguments, success/failure,
latency — the same audit trail every other protocol's login/query activity already
reaches. POST /api/mcp-audit/summarize turns a client's recent tool calls
into 2-4 plain-English sentences, called to flag failures and writes, never to invent an
action that didn't happen.
Natural-language querying (with a judge)
The MCP query_natural_language tool drafts a read-only SQL SELECT
from a plain-English question (grounded in a real schema summary, not guessed blind), then
a SECOND, independent LLM call judges that draft against the schema and the question and
can correct it — visibly, not silently. The judged SQL runs through the exact same
firewall/QoS/cache pipeline every other tool uses. One thing is deterministic, not
LLM-decided: a plain read-only check refuses anything that isn't a real
SELECT/WITH before it's ever executed, regardless of what either
LLM call said.
Rollup (pre-aggregation) suggestions
POST /api/rollup-suggestions/draft looks at recent expensive/frequent SQL
and proposes ONE new RollupStage pre-aggregation definition — validated by
literally running it through the real parser the runtime itself uses, not a second copy
of that grammar. Returns a ready-to-paste YAML document for the existing
PUT /api/config; nothing is ever written automatically.
Table-sharding suggestions
POST /api/router-suggestions/draft proposes ONE new per-table hash-sharding
rule based on real per-backend load. An LLM can hallucinate a plausible-sounding backend
name even when given the real list — so every proposed backend is checked in code against
the actual configured registry before the draft is ever shown, not trusted from the
model's own output.
EXPLAIN plan narration
The MCP explain_query tool runs a real Postgres EXPLAIN (optionally
ANALYZE) and, with an LLM configured, explains the plan in plain English —
sequential scans, missing indexes, expensive sorts. The raw plan is always returned either
way. Since ANALYZE genuinely executes the statement, the same read-only check
query_natural_language uses gates this tool too.
Schema documentation
The MCP document_schema tool lists every real table/column and foreign-key
relationship, then — with an LLM configured — writes a short plain-English data dictionary
on top: what each table likely represents and how it relates to others, based only on
what's actually in the schema. The raw listing always comes back regardless of whether an
LLM is reachable.
Every one of these ten was built with a real, no-mock integration test — a real Warp subprocess, a real disposable Postgres, and a real local HTTP server standing in for the LLM endpoint (there's no free, deterministic, offline real LLM to point a CI run at) — the same discipline as everything else on this page.
Multi-AZ deployment#
Security#
Every layer below is real, running code — not roadmap. Each item states its actual scope plainly, including the parts that are opt-in or narrower than the feature name might suggest; a security page that only lists what's strong isn't one you should trust.
- SQL firewallAllow/deny rules matched on statement type, table (glob), and a SQL regex, stored in Postgres and pushed live to every node over LISTEN/NOTIFY — no restart to change a rule. Stacked-query injection (
SELECT ...; DROP TABLE ...) is blocked unconditionally, independent of any rule. Rules are process-wide today, not yet per-tenant or per-role, and the only actions are allow/deny — no rate-limit or log-only action. - Connection ACLCIDR allow/deny lists, fail-closed once any rule is configured, enforced at accept time on every TCP wire protocol, every HTTP endpoint, and gRPC. PROXY protocol v2 preserves the real client IP behind a load balancer. Configure
WARP_ACL_TRUSTED_PROXIESfor HTTP endpoints — without it, the firstX-Forwarded-Forhop is trusted, which a client in front of an untrusted proxy could spoof. - QoS admission controlA real token-bucket limiter — rate, burst, and max-wait, configurable per workload class (query/write/ddl/txn) — plus shedding when a backend's connection pool is saturated. Today the limiter is per-workload-class and process-wide, not yet per-tenant or per-client.
- AuthenticationSet
WARP_AUTH_MODE=postgres_rolesand Warp verifies real Postgres roles — SCRAM-SHA-256 and md5, read live frompg_authid— for the Postgres and SQL Server wire protocols (needs a backend role grantedSELECT ON pg_authidto read role hashes — deliberately not a superuser, since a superuser bypasses row-level security unconditionally, which matters if you're relying on RLS below). The Oracle wire protocol authenticates differently — its O5LOGON handshake needs a real plaintext password server-side to verify the client's own encrypted challenge response, so it can't be satisfied from Postgres's hashed role verifiers the way SCRAM can; setWARP_AUTH_CREDENTIALS(auser=pass;user2=pass2list) to give it real, distinguishable per-caller identities instead. Without either, pgwire/mssqlwire/orawire all fall back to one shared username/password, and MySQL only supports that shared credential today. The admin API and MCP endpoint separately support OIDC/JWT bearer tokens with live JWKS rotation, off by default until an issuer is configured. - TLS everywhereIn-band TLS for Postgres, MySQL, and SQL Server wire protocols (standard client
sslmode/encryption settings just work), a dedicated TCPS port for Oracle, TLS for gRPC, and mutual TLS between cluster nodes for cache traffic — all from one PKCS12 keystore. Client-certificate authentication isn't implemented yet; TLS today verifies the server to the client, not the client to the server. - Secrets managementA backend password can be a
vault:orcyberark:reference instead of a literal — resolved fresh from HashiCorp Vault (KV v1/v2) or CyberArk CCP on every connection, so a rotated secret takes effect without a restart. Credential fields in the config table can additionally be encrypted at rest with AES-256-GCM — setSAYONORA_ENCRYPTION_KEY; without it, those fields are stored as plain text, and Warp logs a warning at startup saying so. - Admin API authenticationThe config/backends/queues/topology API accepts either of two auth paths, simultaneously — a shared bearer token (
WARP_ADMIN_TOKEN, constant-time compare) that always grants full read+write access, the simplest option for a single operator, CI, or a quick trial; or real SSO — Okta, Entra ID, or any OIDC-compliant IdP, via the sameWARP_OAUTH_ISSUERconfig as every other OIDC-backed surface on this page. With SSO, a caller's IdP group or app-role claim (WARP_OAUTH_ROLES_CLAIM) is checked againstWARP_OAUTH_ADMIN_ROLES/WARP_OAUTH_VIEWER_ROLESto grant either read-only viewer access or full admin — an SSO caller with neither role name is denied, not silently downgraded to viewer. Every mutating call made through either path is recorded to the audit log, attributed to the real SSO identity when there is one, or plainly to"shared-admin-token"when there isn't. Turning SSO on doesn't retire the shared token — both stay live together, and either one alone fails closed the same way the single-token setup always did.
See RLS detail and requirements →
Row-level security, for real: under WARP_AUTH_MODE=postgres_roles
(Postgres, SQL Server) or WARP_AUTH_CREDENTIALS (Oracle), every login sets
warp.user_id as a session GUC on the backend connection before each
statement — filtering is enforced by your own Postgres RLS policies
(USING (owner_user = current_setting('warp.user_id'))-style), not by
Warp rewriting SQL. This holds even fronted by the Oracle wire protocol: orawire's SQL
translation always executes against Postgres, so the same Postgres RLS mechanism applies —
no separate Oracle VPD/SYS_CONTEXT setup needed, since there's no real Oracle
database in this path for VPD to run against. One requirement RLS makes non-negotiable:
Warp's own backend role must not be a superuser or the table owner — either bypasses
every RLS policy unconditionally, no matter what the session GUC says. Grant it
SELECT on pg_authid and on the tables it serves instead. Every
login — success or failure, real identity or shared credential — is recorded to a
tamper-evident audit log, readable live via the bearer-token-authenticated
GET /api/audit admin route. Column-masking and attribute-based row filtering
beyond native RLS (the AccessControlStage engine) are real, reviewed code, not
yet wired to a config surface — out of scope until an identity-to-attribute mapping exists.
MySQL and the DynamoDB/MongoDB/SQS/OpenSearch wire protocols don't yet propagate identity
into RLS at all — every session there still shares one backend credential.
Planned and unplanned outages#
A Postgres backend going down for a patch shouldn't take your application down with it — whether you chose the timing or not. Warp tells the two cases apart deliberately: a planned switchover can afford to wait for a clean cutover; an unplanned failure can't wait for anything.
- Planned switchover
POST /api/backends/{name}/drainstops routing new statements to a backend in favor of a configured fallback (a same-region replica or another region's backend — the mechanism doesn't distinguish the two), then waits (bounded bygraceMs) for its connection pool to empty before closing it. It also waits for the fallback to reach real zero replication lag before reporting success — a planned window has no outage forcing an immediate cutover, so it actually waits for a clean one. Refuses (409) to drain a backend with any unresolved in-doubt XA transaction against it, and fans the call out to every other node in the cluster (not just whichever one received the HTTP request) so a switchover is genuinely cluster-wide, not single-node. - Unplanned failoverA background prober re-checks every backend's connectivity (
WARP_BACKEND_HEALTH_CHECK_SECONDS, default 15s) and automatically redirects new statements to a backend's fallback the moment it stops responding — no operator, no admin call. SetWARP_FAILOVER_MAX_LAG_SECONDSto name the replication lag (the data loss) you're willing to accept from an outage nobody scheduled; exceeding it doesn't block the failover — refusing to redirect would only trade a bounded loss for a total outage — but it does log loudly, exactly what a real alert should page on. - Crash-safe recovery, even after a switchoverAn in-doubt two-phase-commit branch records the exact backend (jdbc URL, user, password) it was prepared against, not just its name — so if that name gets repointed to a different physical target between the crash and the next restart (a switchover, a credential rotation, a config edit), startup recovery still reconnects to where the branch actually lives, not wherever the name currently resolves to.
Every wire protocol participates — Postgres, SQL Server, MySQL, Oracle, MongoDB, DynamoDB, SQS, and OpenSearch alike. Drain/undrain, fallback redirection, and auto-failover all resolve backends through the same registry lookup every protocol's store now shares. A backend that starts serving traffic for the first time after a switchover — a fallback that's a genuinely separate Postgres, not necessarily a replica — gets its schema/catalog tables created there automatically the first time routing actually lands on it, not never.
Error handling: real native errors, not a generic wrapper#
Underneath every protocol Warp speaks, the real failure is a Postgres SQLSTATE — a
unique-constraint violation, a missing table, a dead connection. A driver's own retry and
reconnect logic (Oracle drivers checking for ORA-03113 specifically to decide
whether to transparently reconnect, MongoDB's retryable-reads keying off a real
codeName, the AWS SDK unmarshalling a real exception class) only works if
Warp sends the exact native error that driver expects — not a generic
internal error that leaves that logic with nothing to act on. Every wire
protocol translates the real Postgres SQLSTATE into that protocol's own genuine error
shape, verified against each vendor's own documentation or a real client library actually
parsing the response, not guessed.
- Oracle, MySQL, SQL ServerReal
ORA-NNNNNnumbers, MySQLerrnos, and SQL Server error numbers, each paired with that vendor's own real message wording (not Postgres's) and the originating SQLSTATE. - MongoDBReal
code+codeNameon command errors and on per-documentwriteErrors— e.g.11000 DuplicateKey,112 WriteConflict,91 ShutdownInProgress— verified against a realmongodb-driver-syncclient. - DynamoDBReal AWS exception names in the real DynamoDB JSON envelope —
ConditionalCheckFailedException,ResourceNotFoundException,TransactionConflictException— verified against a real AWS SDK v2 client. - SQSReal AWS error codes in the real SQS JSON error envelope —
QueueDoesNotExist,AccessDenied— verified against a real AWS SDK v2 client. - OpenSearchReal
error.type/error.reason/error.root_cause—index_not_found_exception,version_conflict_engine_exception,mapper_parsing_exception— verified against a realopensearch-javaclient. - Connection loss, specificallyWarp distinguishes three real Postgres SQLSTATEs that all mean "the backend is unreachable" — an already-open connection dying vs. a new connection attempt failing to establish — and maps every one of them to the native "backend gone" error each protocol's own client actually expects, confirmed against a real live outage, not assumed.
Where a vendor genuinely has no dedicated code for a condition (SQL Server has no numbered error for a mid-session transport failure; SQS has no code distinct from a generic backend failure for a duplicate/throttling condition), that gap is documented plainly rather than papered over with an invented code. Full per-protocol mapping tables, the canonical- condition comparison, and the connection-loss detail live in ERRORS.md.
See it happen#
Pick a protocol — same demo, different client, same PostgreSQL on the other end.
$ sql app/password@//localhost:11521/postgres
SQLcl: Release 23.4 Production
SQL> SELECT * FROM orders;
ORDER_ID CUSTOMER_ID AMOUNT
-------- ----------- -------
1001 42 129.99
1002 57 89.50
2 rows selected.
SQL>
$ mysql -h 127.0.0.1 -P 13306 -u root -p mysql> SELECT * FROM orders; +----------+-------------+--------+ | order_id | customer_id | amount | +----------+-------------+--------+ | 1001 | 42 | 129.99 | | 1002 | 57 | 89.50 | +----------+-------------+--------+ 2 rows in set mysql>
$ mongosh mongodb://localhost:27017
test> db.orders.find()
[
{ _id: 1001, customer_id: 42, amount: 129.99 },
{ _id: 1002, customer_id: 57, amount: 89.50 }
]
test>
$ curl -s localhost:9200/orders/_search
{
"hits": {
"total": { "value": 2 },
"hits": [
{ "_source": { "order_id": 1001, "amount": 129.99 } },
{ "_source": { "order_id": 1002, "amount": 89.50 } }
]
}
}
Real client. Real wire protocol. PostgreSQL database. No client rewrite. Table/document contents above are illustrative — the protocol and path are exactly real. (SQLcl, JDBC, and psql are what's most thoroughly verified today; see Warp capabilities for exact client-by-client coverage.)
Try Warp in 60 seconds#
What do you use? Point it at a Postgres you already have:
1. Start Warp
docker run -p 15432:15432 \
-e WARP_HOST=<host> -e WARP_PASSWORD=<password> \
ghcr.io/polygres26/warp:latest2. Connect
psql -h localhost -p 15432 -U postgres3. Done — your client → Warp → PostgreSQL. No rewrite.
1. Start Warp
docker run -p 11521:11521 \
-e WARP_HOST=<host> -e WARP_PASSWORD=<password> \
ghcr.io/polygres26/warp:latest2. Connect
sql app/password@//localhost:11521/postgres3. Done — Oracle client → OraWire → Warp → PostgreSQL. No driver change.
1. Start Warp
docker run -p 13306:13306 \
-e WARP_HOST=<host> -e WARP_PASSWORD=<password> \
ghcr.io/polygres26/warp:latest2. Connect
mysql -h 127.0.0.1 -P 13306 -u root -p3. Done — mysql CLI → MyWire → Warp → PostgreSQL. No driver change.
1. Start Warp
docker run -p 27017:27017 \
-e WARP_HOST=<host> -e WARP_PASSWORD=<password> \
ghcr.io/polygres26/warp:latest2. Connect
mongosh mongodb://localhost:270173. Done — mongosh → MongoWire → Warp → PostgreSQL. No driver change.
1. Start Warp
docker run -p 9200:9200 \
-e WARP_HOST=<host> -e WARP_PASSWORD=<password> \
ghcr.io/polygres26/warp:latest2. Connect
curl -s localhost:9200/_search3. Done — REST client → OSWire → Warp → PostgreSQL. No driver change.
See every protocol at once (SQL Server, DynamoDB, SQS included) →
docker run \
-p 19090:19090 -p 15432:15432 -p 13306:13306 -p 11521:11521 \
-p 14333:14333 -p 27017:27017 -p 18000:18000 -p 9324:9324 \
-e WARP_HOST=<host> \
-e WARP_PASSWORD=<password> \
ghcr.io/polygres26/warp:latest
psql -h localhost -p 15432 -U postgres # Postgres wire
mysql -h 127.0.0.1 -P 13306 -u root -p # MySQL wire
sqlcmd -S localhost,14333 -U sa -P '<password>' # SQL Server wire
sql app/password@//localhost:11521/postgres # Oracle wire (SQLcl)
mongosh mongodb://localhost:27017 # MongoDB wire
curl -s localhost:9200/_search # OpenSearch wire
aws dynamodb scan --table-name orders \ # DynamoDB wire
--endpoint-url http://localhost:18000
aws sqs receive-message \ # SQS wire
--queue-url http://localhost:9324/queue/orders \
--endpoint-url http://localhost:9324
That works as-is against an existing on-prem Postgres. For a specific managed provider instead — Supabase, Amazon RDS, Google Cloud SQL, Azure Database for PostgreSQL, or Oracle Cloud Infrastructure's Database with PostgreSQL — see CONNECTING.md.
Different protocols. Same security policy.#
Oracle application? Same policy. MySQL application? Same policy. MCP agent? Same policy. Security follows the request, not the protocol.
SQL Firewall · ACL · Authentication · RLS · TLS · Secrets · Audit
Full, honestly-scoped security detail →
Stop one workload from taking down Postgres.#
QoS for Postgres, at the gateway. Prioritize, throttle, bound concurrency, and shed load — before Postgres saturates, not after it falls over. A real token-bucket admission controller, per workload class (query/write/ddl/txn). See Security for the full scope.
Cache once. Access across protocols.#
A result cached through one Warp protocol can be served through another — across Warp instances — without another Postgres round trip. One distributed cache, not a per-protocol silo. Confirmed live: a row read via a Postgres client, then asked for again through a MySQL client, comes back as a cache hit — zero Postgres round trips on the second read.
Cross-Protocol · Distributed · Automatically Invalidated · No Application Cache Code
Explore Warp in depth — detailed architecture, comparison →
Built for enterprise Postgres#
Without Warp vs. with it#
Without Warp
- Different database infrastructure per protocol
- A separate connection proxy
- Different security controls per system
- Different observability per system
- Separate migration tooling
- A separate, unguarded AI access path
With Warp
One gateway → the database you already run, or Postgres.
- Fewer systems to operate — one gateway instead of five databases' own tooling.
- One place to govern workloads — the same firewall, QoS, and audit path for every protocol.
- Standardize on Postgres, or keep your current engine — without rewriting every application first, either way.
Warp vs. alternatives#
| Capability | Direct to Postgres | Traditional proxy | Warp |
|---|---|---|---|
| Postgres wire protocol | ✓ | ✓ | ✓ |
| Foreign DB protocols (Oracle, MySQL, SQL Server, MongoDB…) | — | — | ✓ |
| Live dialect / semantic translation | — | — | ✓, every call |
| SQL firewall | DB-side only | Some | ✓ |
| Workload QoS / admission control | DB-side only | Limited | ✓ |
| Intelligent routing | App/DB-side | Some | ✓ |
| Sharding | App/DB-side | Some | ✓ |
| Cross-protocol distributed caching | Separate system | — | ✓ |
| AI / MCP access, same governed path | Separate path | — | ✓ |
| Unified observability | Multiple layers | Proxy layer only | ✓ |
Compatibility, honestly scoped#
Every row below is verified against that protocol's real client library, not a synthetic harness — this table states only what's actually confirmed today.
See the full per-protocol compatibility table →
| Protocol | Client tested against | Bound parameters decoded | Native error translation | RLS identity |
|---|---|---|---|---|
| PostgreSQL | psycopg2 | ✓ | ✓ (native SQLSTATE) | ✓ |
| Oracle | python-oracledb, SQLcl, JDBC | ✓ | ✓ (ORA-NNNNN) | ✓ |
| SQL Server | python-tds / pymssql, JDBC | ✓ | ✓ | ✓ |
| MySQL | MySQL CLI, JDBC | ✓ | ✓ (errno) | — |
| MongoDB | mongodb-driver-sync, mongosh | Hash by _id | ✓ (code/codeName) | — |
| DynamoDB | AWS SDK v2 | Hash by partition key | ✓ (AWS exception names) | — |
| Amazon SQS | boto3 | Hash by queue name | ✓ (AWS error codes) | — |
| OpenSearch | opensearch-py | Hash by doc_id | ✓ (error.type/reason) | — |
"—" means not yet implemented for that protocol, stated plainly rather than omitted — see Security for exactly which protocols propagate a real per-caller identity today, and full capabilities / ERRORS.md for the complete per-protocol detail including known limitations.
Built for production#
- HA & FailoverMulti-AZ Warp deployment, planned drain/switchover and automatic unplanned failover, both tested across every wire protocol. See Multi-AZ deployment and Planned and unplanned outages.
- SecuritySQL firewall, connection ACL, authentication, TLS, row-level security identity propagation, Vault/CyberArk secrets, admin SSO. See Security for the exact, honestly-scoped detail.
- Workload protectionQoS admission control, connection pooling, and load shedding before a backend saturates — see "Stop one workload from taking down Postgres" above.
- ObservabilityPrometheus
/metricsand OTLP export, reaching Grafana, Datadog, New Relic, CloudWatch, Azure Monitor, and GCP Cloud Monitoring. See Observability. - DeploymentDocker, Kubernetes, AWS, Azure, GCP, OCI, or on-prem — your infrastructure, your network, your database.
- SupportCommunity support on Developer; enterprise support on Enterprise — see Pricing below.