Skip to main content

Caching Strategies

Cachemate provides three distinct caching strategies tailored for different database querying patterns:

  1. Join Cache (withJoinCache / client.cache): Multi-table relational queries invalidated via table version vectors.
  2. Flat Cache (withFlatCache / client.flat): Isolated namespace caching for key-value records or unversioned entities.
  3. Rows Cache (withRowsCache / client.rows): Two-tier row-level caching for paginated collections, feeds, and filtered search.

1. Join Cache (withJoinCache)​

Join Cache is designed for SQL joins or aggregated documents that span multiple database tables.

import { withJoinCache, type JoinCacheParams, type JoinCacheContext } from 'cachemate';

Key Derivation Algorithm​

Key Format: [keyPrefix]:[sorted-table-tags]:[composite-versions]:[filters-hash]
Example: cm:o-u:12:4:b7a9f0c2e3d1
  1. Table Deduplication & Sorting: Tables in tables: ['users', 'orders'] are mapped to tags (u, o), deduplicated, and sorted alphabetically (o-u).
  2. Version Retrieval: Versions for each table (cm:ver:orders = 12, cm:ver:users = 4) are retrieved via mget().
  3. Filter Hashing: The filters parameter is converted to a deterministic string via getDeterministicString() (sorting object keys recursively) and hashed using SHA-1 (truncated to 12 hex characters).

Execution Flow​

client.cache(params)
│
▼
buildJoinKey() ──── (Unregistered table error) ────► [ Fallback to loader() ]
│
▼
readJsonCache()
│
┌────┴────────────────────────┐
│ Hit │ Miss
▼ ▼
Return parsed JSON Execute loader()
│
▼
writeJsonCache(key, data, ttl)
│
▼
Return fresh data

2. Flat Cache (withFlatCache)​

Flat Cache is designed for queries that do not require relational table invalidation, such as third-party API responses, computed tokens, or static configuration.

import { withFlatCache, type FlatCacheParams, type FlatCacheContext } from 'cachemate';

Key Derivation Algorithm​

Key Format: [keyPrefix]:[namespace]:[filters-hash]
Example: cm:exchange-rates:8f2a1b9c3e4d

Signature​

export interface FlatCacheParams<T> {
ns: string;
filters: unknown;
ttlSeconds?: number;
loader: () => Promise<T>;
}

3. Rows Cache (withRowsCache)​

For high-throughput lists (e.g. product catalogs, search results, timeline feeds), caching the entire list as a single JSON blob leads to severe duplication across pages.

Rows Cache implements a two-tier cache:

  1. Index Cache (List Key): Caches only the ordered array of row IDs (e.g. ['prod_1', 'prod_2', 'prod_3']) and optional query meta (pagination counts).
  2. Row Entities (Entity Keys): Caches each individual row entity by its ID (e.g. cm:row:products:prod_1).

Cache Hit Optimization​

  • When the list key hits, Cachemate issues an mget() for all entity IDs in a single round trip.
  • If all entity rows are present in cache, the response is reassembled instantly.
  • If any individual row is missing (e.g. partially evicted), Cachemate fetches only the missing IDs via getAllById(missingIds).

Non-Blocking Background Write​

When a cache miss occurs on getByAll():

  • Fresh results are returned immediately to the API caller.
  • Cache population runs asynchronously in the background (void (async () => { ... })()), auto-pipelining pool.set() commands to avoid blocking the HTTP response.

Signature​

export type RowsParams<T, M = unknown> = {
ttlSeconds?: number;
rowId: (row: T) => string;
getByAll: () => Promise<{ rows: T[]; meta?: M } | T[]>;
getAllById: (ids: string[]) => Promise<T[]>;
} & (
| { namespace: string; tables: string[]; filters: unknown; listKey?: never; rowKey?: never }
| { listKey: string; rowKey: (id: string) => string; namespace?: never; tables?: never; filters?: never }
);