The Database Bottleneck
In application performance tuning, 90% of latency bottlenecks can be traced back to the database. While PostgreSQL is an extremely robust and relational data store, executing complex JOIN operations on high-traffic tables can exhaust pool connections and slow down the entire system.
Let's inspect how to build an elegant caching layer that ensures sub-millisecond query execution speeds without risking stale cache anomalies.
Design Pattern: The Cache-Aside Strategy
For general reading operations, the Cache-Aside pattern remains the standard. The application attempts to query the fast cache (Redis) first. If a miss occurs, it queries PostgreSQL, populates the cache with a defined Time-To-Live (TTL), and returns the results:
export async function getCachedData<T>(
key: string,
queryFn: () => Promise<T>,
ttlSeconds = 300
): Promise<T> {
const cachedValue = await redis.get(key);
if (cachedValue) {
return JSON.parse(cachedValue) as T;
}
const freshData = await queryFn();
await redis.set(key, JSON.stringify(freshData), { EX: ttlSeconds });
return freshData;
}Handling Write-Heavy Collections
When updates are frequent, simple expiration TTLs are not enough. We implement a **Write-Through** strategy: when updating a database record, the application also forces a cache update in the same transactional context.
Optimal Connection Pooling
To prevent PostgreSQL from crashing under high concurrency, always wrap database initializations using a single global client pooling mechanism:
import { Pool } from "pg";
let pgPool: Pool | null = null;
export function getDatabasePool(): Pool {
if (!pgPool) {
pgPool = new Pool({
connectionString: process.env.DATABASE_URL,
max: 20, // Max concurrent client pools
idleTimeoutMillis: 30000,
});
}
return pgPool;
}Verification and Results
By combining Write-Through cache layers with query pooling, database latency can be reduced from an average of 120ms per complex query down to less than 4ms, making your full-stack applications run at lightspeed.