perf(database): defer primary fallback for user lookups (#4359)

This commit is contained in:
Daniel Salazar authored and GitHub committed 2026-10-26 21:37:06 -07:00
1 parent 91554a137e
commit 2dfd93687f
4 files changed
+36 -10

No files matched your search

@@ -108,6 +108,25 @@ describe('AbstractDatabaseClient — unimplemented surface', () => {
});
describe('AbstractDatabaseClient — replica-aware reads', () => {
it('avoids a primary read when the replica has rows in sequential mode', async () => {
const client = new FixtureDatabaseClient([{ id: 1 }], [{ id: 2 }]);
expect(
await client.tryHardRead('SELECT 1', [], { race: false }),
).toEqual([{ id: 1 }]);
expect(client.preads).toEqual([]);
});
it.each([[], new Error('replica unavailable')])(
'falls back once in sequential mode',
async (replica) => {
const client = new FixtureDatabaseClient(replica, [{ id: 2 }]);
expect(
await client.tryHardRead('SELECT 1', [], { race: false }),
).toEqual([{ id: 2 }]);
expect(client.preads).toEqual(['SELECT 1']);
},
);
it('returns replica rows without waiting on the primary', async () => {
const client = new FixtureDatabaseClient([{ id: 1 }], [{ id: 2 }]);
await expect(client.tryHardRead('SELECT 1')).resolves.toEqual([
@@ -138,25 +138,27 @@ export class AbstractDatabaseClient extends PuterClient {
/**
* Like `read()`, but an empty or failed replica read falls back to the
* primary, so replication lag can't hide a row that was just written. The
* primary read starts alongside the replica one rather than after it.
* Without a replica this is a single `read()`.
* primary, so replication lag can't hide a row that was just written. By
* default the primary read starts alongside the replica one; `race: false`
* reads the primary only after the replica returns empty or fails. Without
* a replica this is a single `read()`.
*/
@Span('db.tryHardRead', (query: string) => ({ 'db.statement': query }))
async tryHardRead(
query: string,
params: unknown[] = [],
{ race = true }: { race?: boolean } = {},
): Promise<Record<string, unknown>[]> {
if (!this.hasReadReplica()) return this.read(query, params);
const primary = settle(this.pread(query, params));
const primary = race ? settle(this.pread(query, params)) : null;
try {
const rows = await this.replicaRead(query, params);
if (rows.length > 0) return rows;
} catch {
// replica failed — fall through to primary
}
const result = await primary;
const result = await (primary ?? settle(this.pread(query, params)));
if ('error' in result) throw result.error;
return result.value;
}
@@ -217,7 +217,8 @@ export class MySQLDatabaseClient extends AbstractDatabaseClient {
return this.configuration === Configuration.REPLICA;
}
// No failover: `tryHardRead` already has a primary read in flight.
// No failover: `tryHardRead` falls back to the primary on an empty result
// or error (raced alongside by default, after the replica with `race: false`).
protected override async replicaRead(
query: string,
params: unknown[],
+8 -4
View File
@@ -316,7 +316,9 @@ export class UserStore extends PuterStore {
const rows = (
primary
? await this.clients.db.pread(sql, chunk)
: await this.clients.db.tryHardRead(sql, chunk)
: await this.clients.db.tryHardRead(sql, chunk, {
race: false,
})
) as Array<Record<string, unknown>>;
for (const row of rows) users.push(this.#normalizeRow(row));
}
@@ -430,8 +432,8 @@ export class UserStore extends PuterStore {
// Replication-aware read: on `force` (or a tombstoned key), go
// straight to the primary (`pread`) to bypass replica lag for hot
// reads (e.g., immediately after a signup). Otherwise `tryHardRead`
// parallels primary + replica and prefers whichever returns rows.
// reads (e.g., immediately after a signup). Otherwise the primary is
// only read when the replica returns no match or fails.
// `id`, `uuid` and `username` are UNIQUE, so at most one row matches and
// the optimizer drops the ordering. `email` is not — multiple rows may
// legitimately hold the same address while unconfirmed, so without an
@@ -447,7 +449,9 @@ export class UserStore extends PuterStore {
async (primary) => {
const rows = primary
? await this.clients.db.pread(sql, [value])
: await this.clients.db.tryHardRead(sql, [value]);
: await this.clients.db.tryHardRead(sql, [value], {
race: false,
});
const row = rows[0];
if (!row) return null;
if (