Fast reads, no lost updates
Reads dominate, so put your LRU cache (System design) in front of the DynamoDB table (AWS). Two dangers: serving stale data after a write, and two writers overwriting each other. Write-through updates the cache on every write; optimistic locking gives each item a version, and a write that expected an older version is rejected.
The repository
function createCachedRepo({ table, cache }) {
let dbReads = 0;
let dbWrites = 0;
const read = (id) => {
dbReads++;
return table.get(id);
};
const write = (item) => {
dbWrites++;
table.put(item);
cache.set(item.id, item); // write-through: the cache never holds an old version
return item;
};
return {
create(item) {
return write({ ...item, version: 1 });
},
get(id) {
const cached = cache.get(id);
if (cached !== undefined) return cached;
const item = read(id) ?? null;
if (item) cache.set(id, item);
return item;
},
update(id, changes, expectedVersion) {
const current = read(id);
if (!current) throw new Error("Not found");
if (expectedVersion !== undefined && current.version !== expectedVersion) throw new Error("Version conflict"); // optimistic locking
return write({ ...current, ...changes, id, version: current.version + 1 });
},
stats: () => ({ dbReads, dbWrites, ...cache.stats() }),
};
}