Compatibility

PgCache is designed to be transparent to client applications. This page covers supported PostgreSQL versions, query patterns, client libraries, and known limitations.

PostgreSQL Version Support

PostgreSQL VersionStatus
18Supported (primary target)
17Supported
16Supported
15 and belowNot supported (requires logical replication protocol version 4)

The origin database must have wal_level = logical enabled for CDC replication.

Note: On PostgreSQL 18+, search_path changes are observed via the native ParameterStatus notifications. On PostgreSQL 16 and 17, pgcache piggybacks SHOW search_path onto detected mutations (SET/RESET search_path, DISCARD ALL, transaction boundaries) to track the current value. While the new value is being resolved, cacheable queries on the affected connection are forwarded to the origin.

Limitation: User-defined functions can change search_path internally (for example, a PL/pgSQL function calling SET LOCAL search_path or set_config()). These changes are not visible to the proxy and will not be reflected in pgcache’s table resolution. If your application relies on functions that mutate search_path, set it explicitly at the session level instead.

Supported Query Patterns

PgCache caches queries that it can safely evaluate and invalidate. All other queries are transparently forwarded to the origin.

Cached

PatternExample
Single table SELECTSELECT * FROM users WHERE active = true
INNER JOINSELECT u.id, u.name, o.total FROM users u INNER JOIN orders o ON u.id = o.user_id
LEFT / RIGHT JOINSELECT u.name, o.total FROM users u LEFT JOIN orders o ON u.id = o.user_id
USING / NATURAL JOINSELECT * FROM users JOIN orders USING (user_id), SELECT * FROM users NATURAL JOIN orders
CROSS JOINSELECT * FROM colors CROSS JOIN sizes
Equality comparisonsWHERE status = 'active'
Inequality comparisonsWHERE age > 18, WHERE price <= 100
AND / OR conditionsWHERE active = true AND role = 'admin'
IN / NOT INWHERE id IN (1, 2, 3)
IS NULL / IS NOT NULLWHERE deleted_at IS NULL
NOT expressionsWHERE NOT archived
GROUP BY / HAVINGSELECT status, COUNT(*) FROM orders GROUP BY status
ORDER BYSELECT * FROM products ORDER BY price ASC, including NULLS FIRST / NULLS LAST
Aggregate functionsSELECT COUNT(*), SUM(total) FROM orders
FILTER on aggregatesSELECT COUNT(*) FILTER (WHERE status = 'paid') FROM orders (also inside OVER (...))
Window functionsSELECT id, name, ROW_NUMBER() OVER (PARTITION BY dept ORDER BY salary DESC) FROM employees
CASE expressionsSELECT CASE WHEN active THEN 'yes' ELSE 'no' END FROM users
Arithmetic expressionsSELECT price * quantity AS total FROM line_items
TypecastsSELECT col::int FROM t, WHERE created_at = '2026-01-01'::date
Uncorrelated subqueriesWHERE id IN (SELECT user_id FROM orders), derived tables, scalar subqueries
CTEsWITH active AS (SELECT * FROM users WHERE active) SELECT * FROM active
Set operationsSELECT id FROM users UNION SELECT id FROM admins
Functions in SELECTSELECT COALESCE(name, 'unknown') FROM users
Immutable functions in WHEREWHERE lower(name) = 'foo', WHERE abs(balance) > 100
LIMIT / OFFSETSELECT * FROM products ORDER BY price LIMIT 10 OFFSET 20
LIKE / ILIKEWHERE name LIKE 'foo%', WHERE name ILIKE '%bar%'
BETWEENWHERE price BETWEEN 10 AND 100, including SYMMETRIC
ANY / ALLWHERE id = ANY(ARRAY[1, 2, 3]), WHERE score > ALL(ARRAY[80, 90])
Mixed wildcards and columnsSELECT *, id, name FROM users
EXISTS / NOT EXISTSWHERE EXISTS (SELECT 1 FROM orders WHERE orders.user_id = users.id)
Correlated subqueriesWHERE id IN (SELECT ...), NOT IN (SELECT ...), scalar subqueries, EXISTS/NOT EXISTS — decorrelated into equivalent JOINs
IS [NOT] TRUE/FALSE/UNKNOWNWHERE active IS TRUE, WHERE flag IS NOT FALSE
Extended query protocolParameterized queries via Parse/Bind/Execute

Not Cached (Forwarded to Origin)

PatternReason
FULL JOINNot yet supported for caching
LATERAL subqueriesNot supported
RECURSIVE CTEsNot supported
Non-equality correlated predicatesCorrelated subqueries using <, >, or OR-connected correlation are not decorrelated
Non-immutable functions outside SELECT listStable or volatile function calls — e.g. now(), random(), nextval() — in WHERE, JOIN, or other clauses (they are allowed in the SELECT list)
Locking clausesFOR UPDATE, FOR SHARE, etc. are always forwarded to the origin
ViewsQueries against views are forwarded to the origin
Extension and composite typesTables with columns of an extension type (e.g. pgvector’s vector, PostGIS geometry) or a composite type are not cached — these types are not reproduced in the cache database, so such queries are forwarded to the origin
COPYCOPY … TO/COPY … FROM is forwarded to the origin and never cached. A COPY … FROM write still propagates to cached tables through CDC like any other write
Non-SELECT statementsINSERT, UPDATE, DELETE, DDL are always forwarded

Queries that aren’t cached still work correctly — they pass through to the origin with no functional difference.

Client Library Compatibility

PgCache speaks the PostgreSQL wire protocol. Any client library that connects to PostgreSQL will work with PgCache without modifications.

LanguageLibraries
Pythonpsycopg2, psycopg3, asyncpg, SQLAlchemy
JavaScript/TypeScriptnode-postgres (pg), Prisma, Drizzle, Knex
JavaJDBC, HikariCP, jOOQ
Gopgx, database/sql with pq driver
Rusttokio-postgres, sqlx, diesel
Rubypg gem, ActiveRecord
.NETNpgsql, Entity Framework

ORMs and query builders work as expected — PgCache analyzes the SQL they generate the same way it handles hand-written queries.

TLS Support

ConnectionConfiguration
Client to PgCachetls_cert and tls_key in config (PEM format)
PgCache to originssl_mode = "require" or "verify-full" in [origin] section
PgCache to replicationssl_mode = "require" or "verify-full" in [replication] section

require encrypts the connection without verifying the server certificate (suitable for self-signed certs). verify-full additionally verifies the certificate against trusted public CAs.

For Docker deployments, TLS certificates can be passed as base64-encoded environment variables (PGCACHE_TLS_CERT, PGCACHE_TLS_KEY).

Note: Channel binding must be disabled for client connections to PgCache. Set channel_binding=disable in your client connection string or library configuration.

Connection Pooler Compatibility

PgCache works alongside connection poolers like PgBouncer. A common pattern is:

  • Application connects to PgCache (caching proxy)
  • PgCache connects to PgBouncer for regular queries (via [origin])
  • PgCache connects directly to PostgreSQL for CDC replication (via [replication])

This is configured using the [replication] section to override the origin connection for logical replication, which requires a direct PostgreSQL connection.

[origin]
host = "pgbouncer.example.com"
port = 6432
user = "app_user"
database = "myapp"

[replication]
host = "postgres-direct.example.com"
port = 5432

Known Limitations

  • Row-Level Security (RLS): RLS is not yet supported. Until support lands, queries against tables with RLS enabled should be excluded from caching at the application or configuration level — pgcache does not currently detect RLS-enabled tables on its own, so cached results would not respect per-role policies.
  • Read-after-write: Inside a transaction every query is forwarded to the origin, so a session always sees its own writes. Outside a transaction, cached reads reflect a write only after logical replication catches up (typical replication lag). See How Caching Works → Consistency Guarantees for the full model. A stronger read-after-write guarantee is in active development.
  • Schema changes (DDL): PgCache tracks row changes, not DDL. Most schema changes are handled transparently, but dropping a table, or attaching or detaching a partition that already holds rows, is not observed via logical replication and can serve stale results for the affected tables.
  • Cache reset on startup: The cache database is fully reset each time PgCache starts. This ensures a clean state but means the cache is cold after a restart.
  • Single-node: PgCache runs as a single process with an embedded cache. There is no distributed cache or multi-node support.
  • COPY protocol: The PostgreSQL COPY protocol is not proxied through PgCache.
  • LISTEN/NOTIFY: PostgreSQL asynchronous notifications are not proxied.
  • Replication management: PgCache creates and manages its own publication and replication slot on the origin. If PgCache is down for an extended period, WAL can accumulate on the origin. Monitor pg_replication_slots on the origin.