- A proxy for MariaDB and PostgreSQL
- Fast in-memory query result caching
- Provides single-flight for cold and warm cache
- Routes cacheable queries to read replicas
- Metrics with query source file + line number
- Database sharding based on database name
┌────────────────────────┐ ┌────────────────────────┐
│ Application Server 1 │ │ Application Server 2 │
│ ┌────────┐ ┌─────────┐ │ │ ┌────────┐ ┌─────────┐ │
│ │TQMemory|+|TQDBProxy| │ │ │TQMemory|+|TQDBProxy│ │
│ └────────┘ └────┬────┘ │ │ └────────┘ └────┬────┘ │
└─────────────────┼──────┘ └─────────────────┼──────┘
│ │
└──────────────┬──────────────┘
│
┌───────────────────────┴─────┐
┌────────▼────────┐ ┌────────▼─────────┐
│ 1 Primary │ │ N Read Replicas │─┐
│ (Writes/Reads) │ │ (Cachable Reads) │ │
└─────────────────┘ └─┬────────────────┘ │
└──────────────────┘
Add metadata comments to your SQL queries:
/* ttl:60 file:app.php line:42 */
SELECT * FROM `users` WHERE `active` = 1ttl: Cache duration in secondsfile: Source file of the query (for metrics)line: Line number of the query (for metrics)
- Fresh: Age between 0 and TTL = serve cache
- Stale: Age between TTL and 2 x TTL = refresh cache
- Miss: Age above 2 x TTL = cache evicted
NB: Ensure: TTL > refresh duration!
Cold Cache: The first request that gets a cache miss fetches the result from the database. Subsequent requests have to wait for the result.
Warm Cache: The first request that detects soft expiration fetches a new result from the database. Subsequent requests are served stale data.
- Prevents backend saturation during heavy traffic spikes.
- Ensures consistent low latency for hits.
- Fresh hit => serve cache
- Stale hit + in-flight => serve stale
- Stale hit + first access => refresh synchronously (single-flight)
- Miss + in-flight => wait for result
- Miss + first access => query backend (single-flight)
mariadb> SHOW TQDB STATUS; => SELECT * FROM pg_tqdb_status;
+---------------+---------+ variable_name | value
| Variable_name | Value | ---------------+---------
+---------------+---------+ Shard | main
| Shard | main | Backend | primary
| Backend | primary | (2 rows)
+---------------+---------+
Backend = primary, replicas[n], cache, cache (stale)
/* ttl:4 file:app.php line:42 */ SELECT SLEEP(2)
tqdbproxy_cache_hits_total{file="app.php",line="42"} 16 tqdbproxy_cache_misses_total{file="app.php",line="42"} 2 tqdbproxy_query_latency_seconds_sum{file="app.php",line="42",query_type="select"} 4.0013 tqdbproxy_query_latency_seconds_count{file="app.php",line="42",query_type="select"} 18 tqdbproxy_query_total{cached="false",file="app.php",line="42",query_type="select"} 2 tqdbproxy_query_total{cached="true",file="app.php",line="42",query_type="select"} 16
Use: curl localhost:9090/metrics -s | grep app.php
Replication & Sharding
[mariadb]
listen = :3307
default = main
[mariadb.main]
primary = 127.0.0.1:3306
replicas = 127.0.0.1:3307, 127.0.0.1:3308
[mariadb.shard1]
primary = 10.0.0.1:3309
databases = logs./tqdbproxy
mariadb -u tqdbproxy -ptqdbproxy -P 3307 tqdbproxy --comments
/* ttl:4 */ SELECT SLEEP(2); SHOW TQDB STATUS
NB: In MariaDB CLI you must use the "--comments" flag to preserve comments
- Most proxies use credentials in config file
- TQDBProxy forwards credentials as-is to backend
- MariaDB uses double SHA1 hashing with nonce (salt)
- Patched Go MySQL driver, to forward handshake
- See: https://github.com/mevdschee/go-sql-driver
- TQDBProxy shards at the database level (by database name).
- Some PostgreSQL proxies support schema level sharding.
- MariaDB may switch backends on
USE databaseor FQN query. - PostgreSQL routes to the correct shard at connection time.
- Databases are fast, NVMe drives have ridiculous performance
- Trade durability for performance first, if possible (periodic fsync)
- 1M QPS recipe: (smart) caches + (read) replicas + (database) shards
- Built in a few weeks (AI assisted)
- Work in progress (optimizing further)
- No production use (yet)
- No warranties (use at your own risk)
Repository: github.com/mevdschee/tqdbproxy
Blog post: tqdev.com/2026-tqdbproxy-mariadb-postgresql-proxy