@rocicorp/zero 0.25.9 → 0.25.10-canary.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/out/zero/package.json.js +1 -1
- package/out/zero/src/adapters/prisma.d.ts +2 -0
- package/out/zero/src/adapters/prisma.d.ts.map +1 -0
- package/out/zero/src/adapters/prisma.js +6 -0
- package/out/zero/src/adapters/prisma.js.map +1 -0
- package/out/zero-cache/src/custom/fetch.d.ts +1 -0
- package/out/zero-cache/src/custom/fetch.d.ts.map +1 -1
- package/out/zero-cache/src/custom/fetch.js +3 -0
- package/out/zero-cache/src/custom/fetch.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/schema/tables.d.ts +1 -0
- package/out/zero-cache/src/services/change-streamer/schema/tables.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/schema/tables.js +6 -5
- package/out/zero-cache/src/services/change-streamer/schema/tables.js.map +1 -1
- package/out/zero-cache/src/services/mutagen/pusher.d.ts +2 -2
- package/out/zero-cache/src/services/mutagen/pusher.d.ts.map +1 -1
- package/out/zero-cache/src/services/mutagen/pusher.js +11 -3
- package/out/zero-cache/src/services/mutagen/pusher.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/cvr-store.d.ts.map +1 -1
- package/out/zero-cache/src/services/view-syncer/cvr-store.js +19 -13
- package/out/zero-cache/src/services/view-syncer/cvr-store.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/row-record-cache.d.ts.map +1 -1
- package/out/zero-cache/src/services/view-syncer/row-record-cache.js +5 -1
- package/out/zero-cache/src/services/view-syncer/row-record-cache.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/view-syncer.d.ts +1 -0
- package/out/zero-cache/src/services/view-syncer/view-syncer.d.ts.map +1 -1
- package/out/zero-cache/src/services/view-syncer/view-syncer.js +6 -1
- package/out/zero-cache/src/services/view-syncer/view-syncer.js.map +1 -1
- package/out/zero-cache/src/workers/syncer-ws-message-handler.d.ts.map +1 -1
- package/out/zero-cache/src/workers/syncer-ws-message-handler.js +2 -1
- package/out/zero-cache/src/workers/syncer-ws-message-handler.js.map +1 -1
- package/out/zero-client/src/client/options.d.ts +10 -0
- package/out/zero-client/src/client/options.d.ts.map +1 -1
- package/out/zero-client/src/client/options.js.map +1 -1
- package/out/zero-client/src/client/version.js +1 -1
- package/out/zero-client/src/client/zero.d.ts +1 -1
- package/out/zero-client/src/client/zero.d.ts.map +1 -1
- package/out/zero-client/src/client/zero.js +8 -2
- package/out/zero-client/src/client/zero.js.map +1 -1
- package/out/zero-protocol/src/connect.d.ts +4 -0
- package/out/zero-protocol/src/connect.d.ts.map +1 -1
- package/out/zero-protocol/src/connect.js +3 -1
- package/out/zero-protocol/src/connect.js.map +1 -1
- package/out/zero-protocol/src/protocol-version.d.ts +1 -1
- package/out/zero-protocol/src/protocol-version.d.ts.map +1 -1
- package/out/zero-protocol/src/protocol-version.js +1 -1
- package/out/zero-protocol/src/protocol-version.js.map +1 -1
- package/out/zero-protocol/src/up.d.ts +2 -0
- package/out/zero-protocol/src/up.d.ts.map +1 -1
- package/out/zero-react/src/zero-provider.js +1 -1
- package/out/zero-react/src/zero-provider.js.map +1 -1
- package/out/zero-server/src/adapters/drizzle.d.ts +18 -13
- package/out/zero-server/src/adapters/drizzle.d.ts.map +1 -1
- package/out/zero-server/src/adapters/drizzle.js.map +1 -1
- package/out/zero-server/src/adapters/pg.d.ts +19 -13
- package/out/zero-server/src/adapters/pg.d.ts.map +1 -1
- package/out/zero-server/src/adapters/pg.js.map +1 -1
- package/out/zero-server/src/adapters/postgresjs.d.ts +19 -13
- package/out/zero-server/src/adapters/postgresjs.d.ts.map +1 -1
- package/out/zero-server/src/adapters/postgresjs.js.map +1 -1
- package/out/zero-server/src/adapters/prisma.d.ts +66 -0
- package/out/zero-server/src/adapters/prisma.d.ts.map +1 -0
- package/out/zero-server/src/adapters/prisma.js +63 -0
- package/out/zero-server/src/adapters/prisma.js.map +1 -0
- package/out/zero-solid/src/use-zero.js +1 -1
- package/out/zero-solid/src/use-zero.js.map +1 -1
- package/out/zql/src/ivm/push-accumulated.d.ts.map +1 -1
- package/out/zql/src/ivm/push-accumulated.js +17 -0
- package/out/zql/src/ivm/push-accumulated.js.map +1 -1
- package/package.json +5 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cvr-store.js","sources":["../../../../../../zero-cache/src/services/view-syncer/cvr-store.ts"],"sourcesContent":["import {trace} from '@opentelemetry/api';\nimport type {LogContext} from '@rocicorp/logger';\nimport type {MaybeRow, PendingQuery} from 'postgres';\nimport {startAsyncSpan} from '../../../../otel/src/span.ts';\nimport {version} from '../../../../otel/src/version.ts';\nimport {assert} from '../../../../shared/src/asserts.ts';\nimport {CustomKeyMap} from '../../../../shared/src/custom-key-map.ts';\nimport {CustomKeySet} from '../../../../shared/src/custom-key-set.ts';\nimport {\n deepEqual,\n type ReadonlyJSONValue,\n} from '../../../../shared/src/json.ts';\nimport {sleep} from '../../../../shared/src/sleep.ts';\nimport * as v from '../../../../shared/src/valita.ts';\nimport {astSchema} from '../../../../zero-protocol/src/ast.ts';\nimport {clientSchemaSchema} from '../../../../zero-protocol/src/client-schema.ts';\nimport {ErrorKind} from '../../../../zero-protocol/src/error-kind.ts';\nimport {ErrorOrigin} from '../../../../zero-protocol/src/error-origin.ts';\nimport type {InspectQueryRow} from '../../../../zero-protocol/src/inspect-down.ts';\nimport {clampTTL, DEFAULT_TTL_MS} from '../../../../zql/src/query/ttl.ts';\nimport * as Mode from '../../db/mode-enum.ts';\nimport {TransactionPool} from '../../db/transaction-pool.ts';\nimport {recordRowsSynced} from '../../server/anonymous-otel-start.ts';\nimport {ProtocolErrorWithLevel} from '../../types/error-with-level.ts';\nimport type {PostgresDB, PostgresTransaction} from '../../types/pg.ts';\nimport {rowIDString} from '../../types/row-key.ts';\nimport {cvrSchema, type ShardID, upstreamSchema} from '../../types/shards.ts';\nimport type {Patch, PatchToVersion} from './client-handler.ts';\nimport type {CVR, CVRSnapshot} from './cvr.ts';\nimport {RowRecordCache} from './row-record-cache.ts';\nimport {\n type ClientsRow,\n type DesiresRow,\n type InstancesRow,\n type QueriesRow,\n type RowsRow,\n} from './schema/cvr.ts';\nimport {\n type ClientQueryRecord,\n type ClientRecord,\n cmpVersions,\n type CustomQueryRecord,\n type CVRVersion,\n EMPTY_CVR_VERSION,\n type InternalQueryRecord,\n type NullableCVRVersion,\n type QueryPatch,\n type QueryRecord,\n queryRecordToQueryRow,\n type RowID,\n type RowRecord,\n versionFromString,\n versionString,\n} from './schema/types.ts';\nimport {\n type TTLClock,\n ttlClockAsNumber,\n ttlClockFromNumber,\n} from './ttl-clock.ts';\n\nexport type CVRFlushStats = {\n instances: number;\n queries: number;\n desires: number;\n clients: number;\n rows: number;\n rowsDeferred: number;\n statements: number;\n};\n\nconst tracer = trace.getTracer('cvr-store', version);\n\nfunction asQuery(row: QueriesRow): QueryRecord {\n const maybeVersion = (s: string | null) =>\n s === null ? undefined : versionFromString(s);\n\n if (row.clientAST === null) {\n // custom query\n assert(\n row.queryName !== null && row.queryArgs !== null,\n 'queryName and queryArgs must be set for custom queries',\n );\n return {\n type: 'custom',\n id: row.queryHash,\n name: row.queryName,\n args: row.queryArgs,\n patchVersion: maybeVersion(row.patchVersion),\n clientState: {},\n transformationHash: row.transformationHash ?? undefined,\n transformationVersion: maybeVersion(row.transformationVersion),\n } satisfies CustomQueryRecord;\n }\n\n const ast = astSchema.parse(row.clientAST);\n return row.internal\n ? ({\n type: 'internal',\n id: row.queryHash,\n ast,\n transformationHash: row.transformationHash ?? undefined,\n transformationVersion: maybeVersion(row.transformationVersion),\n } satisfies InternalQueryRecord)\n : ({\n type: 'client',\n id: row.queryHash,\n ast,\n patchVersion: maybeVersion(row.patchVersion),\n clientState: {},\n transformationHash: row.transformationHash ?? undefined,\n transformationVersion: maybeVersion(row.transformationVersion),\n } satisfies ClientQueryRecord);\n}\n\n// The time to wait between load attempts.\nconst LOAD_ATTEMPT_INTERVAL_MS = 500;\n// The maximum number of load() attempts if the rowsVersion is behind.\n// This currently results in a maximum catchup time of ~5 seconds, after\n// which we give up and consider the CVR invalid.\n//\n// TODO: Make this configurable with something like --max-catchup-wait-ms,\n// as it is technically application specific.\nconst MAX_LOAD_ATTEMPTS = 10;\n\nexport class CVRStore {\n readonly #schema: string;\n readonly #taskID: string;\n readonly #id: string;\n readonly #failService: (e: unknown) => void;\n readonly #db: PostgresDB;\n readonly #upstreamDb: PostgresDB | undefined;\n readonly #writes: Set<{\n stats: Partial<CVRFlushStats>;\n write: (\n tx: PostgresTransaction,\n lastConnectTime: number,\n ) => PendingQuery<MaybeRow[]>;\n }> = new Set();\n readonly #upstreamWrites: ((\n tx: PostgresTransaction,\n ) => PendingQuery<MaybeRow[]>)[] = [];\n readonly #pendingRowRecordUpdates = new CustomKeyMap<RowID, RowRecord | null>(\n rowIDString,\n );\n readonly #forceUpdates = new CustomKeySet<RowID>(rowIDString);\n readonly #rowCache: RowRecordCache;\n readonly #loadAttemptIntervalMs: number;\n readonly #maxLoadAttempts: number;\n readonly #upstreamSchemaName: string;\n #rowCount: number = 0;\n\n constructor(\n lc: LogContext,\n cvrDb: PostgresDB,\n // Optionally undefined to deal with custom upstreams.\n // This is temporary until we have a more principled protocol to deal with\n // custom upstreams and clearing their custom mutator responses.\n // An implementor could simply clear them after N minutes for the time being.\n upstreamDb: PostgresDB | undefined,\n shard: ShardID,\n taskID: string,\n cvrID: string,\n failService: (e: unknown) => void,\n loadAttemptIntervalMs = LOAD_ATTEMPT_INTERVAL_MS,\n maxLoadAttempts = MAX_LOAD_ATTEMPTS,\n deferredRowFlushThreshold = 100, // somewhat arbitrary\n setTimeoutFn = setTimeout,\n ) {\n this.#failService = failService;\n this.#db = cvrDb;\n this.#upstreamDb = upstreamDb;\n this.#schema = cvrSchema(shard);\n this.#taskID = taskID;\n this.#id = cvrID;\n this.#rowCache = new RowRecordCache(\n lc,\n cvrDb,\n shard,\n cvrID,\n failService,\n deferredRowFlushThreshold,\n setTimeoutFn,\n );\n this.#loadAttemptIntervalMs = loadAttemptIntervalMs;\n this.#maxLoadAttempts = maxLoadAttempts;\n this.#upstreamSchemaName = upstreamSchema(shard);\n }\n\n #cvr(table: string) {\n return this.#db(`${this.#schema}.${table}`);\n }\n\n load(lc: LogContext, lastConnectTime: number): Promise<CVR> {\n return startAsyncSpan(tracer, 'cvr.load', async () => {\n let err: RowsVersionBehindError | undefined;\n for (let i = 0; i < this.#maxLoadAttempts; i++) {\n if (i > 0) {\n await sleep(this.#loadAttemptIntervalMs);\n }\n const result = await this.#load(lc, lastConnectTime);\n if (result instanceof RowsVersionBehindError) {\n lc.info?.(`attempt ${i + 1}: ${String(result)}`);\n err = result;\n continue;\n }\n return result;\n }\n assert(err);\n throw new ClientNotFoundError(\n `max attempts exceeded waiting for CVR@${err.cvrVersion} to catch up from ${err.rowsVersion}`,\n );\n });\n }\n\n async #load(\n lc: LogContext,\n lastConnectTime: number,\n ): Promise<CVR | RowsVersionBehindError> {\n const start = Date.now();\n\n const id = this.#id;\n const cvr: CVR = {\n id,\n version: EMPTY_CVR_VERSION,\n lastActive: 0,\n ttlClock: ttlClockFromNumber(0), // TTL clock starts at 0, not Date.now()\n replicaVersion: null,\n clients: {},\n queries: {},\n clientSchema: null,\n profileID: null,\n };\n\n const [instance, clientsRows, queryRows, desiresRows] =\n await this.#db.begin(Mode.READONLY, tx => [\n tx<\n (Omit<InstancesRow, 'clientGroupID'> & {\n profileID: string | null;\n deleted: boolean;\n rowsVersion: string | null;\n })[]\n >`SELECT cvr.\"version\", \n \"lastActive\",\n \"ttlClock\",\n \"replicaVersion\", \n \"owner\", \n \"grantedAt\",\n \"clientSchema\", \n \"profileID\",\n \"deleted\",\n rows.\"version\" as \"rowsVersion\"\n FROM ${this.#cvr('instances')} AS cvr\n LEFT JOIN ${this.#cvr('rowsVersion')} AS rows \n ON cvr.\"clientGroupID\" = rows.\"clientGroupID\"\n WHERE cvr.\"clientGroupID\" = ${id}`,\n tx<Pick<ClientsRow, 'clientID'>[]>`SELECT \"clientID\" FROM ${this.#cvr(\n 'clients',\n )}\n WHERE \"clientGroupID\" = ${id}`,\n tx<QueriesRow[]>`SELECT * FROM ${this.#cvr('queries')} \n WHERE \"clientGroupID\" = ${id} AND deleted IS DISTINCT FROM true`,\n tx<DesiresRow[]>`SELECT \n \"clientGroupID\",\n \"clientID\",\n \"queryHash\",\n \"patchVersion\",\n \"deleted\",\n \"ttlMs\" AS \"ttl\",\n \"inactivatedAtMs\" AS \"inactivatedAt\"\n FROM ${this.#cvr('desires')}\n WHERE \"clientGroupID\" = ${id}`,\n ]);\n\n if (instance.length === 0) {\n // This is the first time we see this CVR.\n this.putInstance({\n version: cvr.version,\n lastActive: 0,\n ttlClock: ttlClockFromNumber(0), // TTL clock starts at 0 for new instances\n replicaVersion: null,\n clientSchema: null,\n profileID: null,\n });\n } else {\n assert(instance.length === 1);\n const {\n version,\n lastActive,\n ttlClock,\n replicaVersion,\n owner,\n grantedAt,\n rowsVersion,\n clientSchema,\n profileID,\n deleted,\n } = instance[0];\n\n if (deleted) {\n throw new ClientNotFoundError(\n 'Client has been purged due to inactivity',\n );\n }\n\n if (owner !== this.#taskID) {\n if ((grantedAt ?? 0) > lastConnectTime) {\n throw new OwnershipError(owner, grantedAt, lastConnectTime);\n } else {\n // Fire-and-forget an ownership change to signal the current owner.\n // Note that the query is structured such that it only succeeds in the\n // correct conditions (i.e. gated on `grantedAt`).\n void this.#db`\n UPDATE ${this.#cvr('instances')} \n SET \"owner\" = ${this.#taskID}, \n \"grantedAt\" = ${lastConnectTime}\n WHERE \"clientGroupID\" = ${this.#id} AND\n (\"grantedAt\" IS NULL OR\n \"grantedAt\" <= to_timestamp(${lastConnectTime / 1000}))\n `\n .execute()\n .catch(this.#failService);\n }\n }\n\n if (version !== (rowsVersion ?? EMPTY_CVR_VERSION.stateVersion)) {\n // This will cause the load() method to wait for row catchup and retry.\n // Assuming the ownership signal succeeds, the current owner will stop\n // modifying the CVR and flush its pending row changes.\n return new RowsVersionBehindError(version, rowsVersion);\n }\n\n cvr.version = versionFromString(version);\n cvr.lastActive = lastActive;\n cvr.ttlClock = ttlClock;\n cvr.replicaVersion = replicaVersion;\n cvr.profileID = profileID;\n\n try {\n cvr.clientSchema =\n clientSchema === null\n ? null\n : v.parse(clientSchema, clientSchemaSchema);\n } catch (e) {\n throw new InvalidClientSchemaError(e);\n }\n }\n\n for (const row of clientsRows) {\n cvr.clients[row.clientID] = {\n id: row.clientID,\n desiredQueryIDs: [],\n };\n }\n\n for (const row of queryRows) {\n const query = asQuery(row);\n cvr.queries[row.queryHash] = query;\n }\n\n for (const row of desiresRows) {\n const client = cvr.clients[row.clientID];\n // Note: row.inactivatedAt is mapped from inactivatedAtMs in the SQL query\n if (client) {\n if (!row.deleted && row.inactivatedAt === null) {\n client.desiredQueryIDs.push(row.queryHash);\n }\n } else {\n // This can happen if the client was deleted but the queries are still alive.\n lc.debug?.(`Client ${row.clientID} not found`);\n }\n\n const query = cvr.queries[row.queryHash];\n if (\n query &&\n query.type !== 'internal' &&\n (!row.deleted || row.inactivatedAt !== null)\n ) {\n query.clientState[row.clientID] = {\n inactivatedAt: row.inactivatedAt ?? undefined,\n ttl: clampTTL(row.ttl ?? DEFAULT_TTL_MS),\n version: versionFromString(row.patchVersion),\n };\n }\n }\n lc.debug?.(\n `loaded cvr@${versionString(cvr.version)} (${Date.now() - start} ms)`,\n );\n\n // why do we not sort `desiredQueryIDs` here?\n\n return cvr;\n }\n\n getRowRecords(): Promise<ReadonlyMap<RowID, RowRecord>> {\n return this.#rowCache.getRowRecords();\n }\n\n putRowRecord(row: RowRecord): void {\n this.#pendingRowRecordUpdates.set(row.id, row);\n }\n\n /**\n * Note: Removing a row from the CVR should be represented by a\n * {@link putRowRecord()} with `refCounts: null` in order to properly\n * produce the appropriate delete patch when catching up old clients.\n *\n * This `delRowRecord()` method, on the other hand, is only used for\n * \"canceling\" the put of a row that was not in the CVR in the first place.\n */\n delRowRecord(id: RowID): void {\n this.#pendingRowRecordUpdates.set(id, null);\n }\n\n /**\n * Overrides the default logic that removes no-op writes and forces\n * the updates for the given row `ids`. This has no effect if there\n * are no corresponding puts or dels for the associated row records.\n */\n forceUpdates(...ids: RowID[]) {\n for (const id of ids) {\n this.#forceUpdates.add(id);\n }\n }\n\n /**\n * Updates the `ttlClock` of the CVR instance. The ttlClock starts at 0 when\n * the CVR instance is first created and increments based on elapsed time\n * since the base time established by the ViewSyncerService.\n */\n async updateTTLClock(ttlClock: TTLClock, lastActive: number): Promise<void> {\n await this.#db`UPDATE ${this.#cvr('instances')}\n SET \"lastActive\" = ${lastActive},\n \"ttlClock\" = ${ttlClock}\n WHERE \"clientGroupID\" = ${this.#id}`.execute();\n }\n\n /**\n * @returns This returns the current `ttlClock` of the CVR instance. The ttlClock\n * represents elapsed time since the instance was created (starting from 0).\n * If the CVR has never been initialized for this client group, it returns\n * `undefined`.\n */\n async getTTLClock(): Promise<TTLClock | undefined> {\n const result = await this.#db<Pick<InstancesRow, 'ttlClock'>[]>`\n SELECT \"ttlClock\" FROM ${this.#cvr('instances')}\n WHERE \"clientGroupID\" = ${this.#id}`.values();\n if (result.length === 0) {\n // This can happen if the CVR has not been initialized yet.\n return undefined;\n }\n assert(result.length === 1);\n return result[0][0];\n }\n\n putInstance({\n version,\n replicaVersion,\n lastActive,\n clientSchema,\n profileID,\n ttlClock,\n }: Pick<\n CVRSnapshot,\n | 'version'\n | 'replicaVersion'\n | 'lastActive'\n | 'clientSchema'\n | 'profileID'\n | 'ttlClock'\n >): void {\n this.#writes.add({\n stats: {instances: 1},\n write: (tx, lastConnectTime) => {\n const change: InstancesRow = {\n clientGroupID: this.#id,\n version: versionString(version),\n lastActive,\n ttlClock,\n replicaVersion,\n owner: this.#taskID,\n grantedAt: lastConnectTime,\n clientSchema,\n profileID,\n };\n return tx`\n INSERT INTO ${this.#cvr('instances')} ${tx(change)} \n ON CONFLICT (\"clientGroupID\") DO UPDATE SET ${tx(change)}`;\n },\n });\n }\n\n markQueryAsDeleted(version: CVRVersion, queryPatch: QueryPatch): void {\n this.#writes.add({\n stats: {queries: 1},\n write: tx => tx`UPDATE ${this.#cvr('queries')} SET ${tx({\n patchVersion: versionString(version),\n deleted: true,\n transformationHash: null,\n transformationVersion: null,\n })}\n WHERE \"clientGroupID\" = ${this.#id} AND \"queryHash\" = ${queryPatch.id}`,\n });\n }\n\n putQuery(query: QueryRecord): void {\n const change: QueriesRow = queryRecordToQueryRow(this.#id, query);\n // ${JSON.stringify(change.queryArgs)}::text::json is used because postgres.js\n // gets confused if the input is `[boolean]` and throws an error saying a bool\n // cannot be converted to json.\n // https://github.com/porsager/postgres/issues/386\n this.#writes.add({\n stats: {queries: 1},\n write: tx => tx`INSERT INTO ${this.#cvr('queries')} (\n \"clientGroupID\",\n \"queryHash\",\n \"clientAST\",\n \"queryName\",\n \"queryArgs\",\n \"patchVersion\",\n \"transformationHash\",\n \"transformationVersion\",\n \"internal\",\n \"deleted\"\n ) VALUES (\n ${change.clientGroupID},\n ${change.queryHash},\n ${change.clientAST},\n ${change.queryName},\n ${change.queryArgs === undefined ? null : JSON.stringify(change.queryArgs)}::text::json,\n ${change.patchVersion},\n ${change.transformationHash ?? null},\n ${change.transformationVersion ?? null},\n ${change.internal},\n ${change.deleted ?? false}\n )\n ON CONFLICT (\"clientGroupID\", \"queryHash\")\n DO UPDATE SET \n \"clientAST\" = ${change.clientAST},\n \"queryName\" = ${change.queryName},\n \"queryArgs\" = ${change.queryArgs === undefined ? null : JSON.stringify(change.queryArgs)}::text::json,\n \"patchVersion\" = ${change.patchVersion},\n \"transformationHash\" = ${change.transformationHash ?? null},\n \"transformationVersion\" = ${change.transformationVersion ?? null},\n \"internal\" = ${change.internal},\n \"deleted\" = ${change.deleted ?? false}`,\n });\n }\n\n updateQuery(query: QueryRecord) {\n const maybeVersionString = (v: CVRVersion | undefined) =>\n v ? versionString(v) : null;\n\n const change: Pick<\n QueriesRow,\n | 'patchVersion'\n | 'transformationHash'\n | 'transformationVersion'\n | 'deleted'\n > = {\n patchVersion:\n query.type === 'internal'\n ? null\n : maybeVersionString(query.patchVersion),\n transformationHash: query.transformationHash ?? null,\n transformationVersion: maybeVersionString(query.transformationVersion),\n deleted: false,\n };\n\n this.#writes.add({\n stats: {queries: 1},\n write: tx => tx`UPDATE ${this.#cvr('queries')} SET ${tx(change)}\n WHERE \"clientGroupID\" = ${this.#id} AND \"queryHash\" = ${query.id}`,\n });\n }\n\n insertClient(client: ClientRecord): void {\n const change: ClientsRow = {\n clientGroupID: this.#id,\n clientID: client.id,\n };\n\n this.#writes.add({\n stats: {clients: 1},\n write: tx => tx`INSERT INTO ${this.#cvr('clients')} ${tx(change)}`,\n });\n }\n\n deleteClient(clientID: string) {\n this.#writes.add({\n stats: {clients: 1},\n write: sql =>\n sql`DELETE FROM ${this.#cvr('clients')} \n WHERE \"clientGroupID\" = ${this.#id} \n AND \"clientID\" = ${clientID}`,\n });\n this.#upstreamWrites.push(\n sql =>\n sql`DELETE FROM ${sql(this.#upstreamSchemaName)}.\"mutations\" \n WHERE \"clientGroupID\" = ${this.#id} \n AND \"clientID\" = ${clientID}`,\n );\n }\n\n putDesiredQuery(\n newVersion: CVRVersion,\n query: {id: string},\n client: {id: string},\n deleted: boolean,\n inactivatedAt: TTLClock | undefined,\n ttl: number,\n ): void {\n const change: DesiresRow = {\n clientGroupID: this.#id,\n clientID: client.id,\n deleted,\n inactivatedAt: inactivatedAt ?? null,\n patchVersion: versionString(newVersion),\n queryHash: query.id,\n\n // ttl is in ms in JavaScript\n ttl: ttl < 0 ? null : ttl,\n };\n\n // For backward compatibility during rollout, write to both old and new columns:\n // Old columns: inactivatedAt (TIMESTAMPTZ), ttl (INTERVAL) - need conversion ms->seconds\n // New columns: inactivatedAtMs (DOUBLE PRECISION), ttlMs (DOUBLE PRECISION) - store ms directly (1:1 with JS)\n const inactivatedAtTimestamp =\n inactivatedAt === undefined\n ? null\n : ttlClockFromNumber(ttlClockAsNumber(inactivatedAt) / 1000);\n const inactivatedAtMs = inactivatedAt ?? null;\n const ttlInterval = ttl < 0 ? null : ttl / 1000; // INTERVAL needs seconds\n const ttlMs = ttl < 0 ? null : ttl; // New column stores ms directly\n\n this.#writes.add({\n stats: {desires: 1},\n write: tx => tx`\n INSERT INTO ${this.#cvr('desires')} (\n \"clientGroupID\", \"clientID\", \"queryHash\", \"patchVersion\", \"deleted\",\n \"ttl\", \"ttlMs\", \"inactivatedAt\", \"inactivatedAtMs\"\n ) VALUES (\n ${change.clientGroupID}, ${change.clientID}, ${change.queryHash}, \n ${change.patchVersion}, ${change.deleted}, ${ttlInterval}, ${ttlMs},\n ${inactivatedAtTimestamp}, ${inactivatedAtMs}\n )\n ON CONFLICT (\"clientGroupID\", \"clientID\", \"queryHash\")\n DO UPDATE SET\n \"patchVersion\" = ${change.patchVersion},\n \"deleted\" = ${change.deleted},\n \"ttl\" = ${ttlInterval},\n \"ttlMs\" = ${ttlMs},\n \"inactivatedAt\" = ${inactivatedAtTimestamp},\n \"inactivatedAtMs\" = ${inactivatedAtMs}\n `,\n });\n }\n\n catchupRowPatches(\n lc: LogContext,\n afterVersion: NullableCVRVersion,\n upToCVR: CVRSnapshot,\n current: CVRVersion,\n excludeQueryHashes: string[] = [],\n ): AsyncGenerator<RowsRow[], void, undefined> {\n return this.#rowCache.catchupRowPatches(\n lc,\n afterVersion,\n upToCVR,\n current,\n excludeQueryHashes,\n );\n }\n\n async catchupConfigPatches(\n lc: LogContext,\n afterVersion: NullableCVRVersion,\n upToCVR: CVRSnapshot,\n current: CVRVersion,\n ): Promise<PatchToVersion[]> {\n if (cmpVersions(afterVersion, upToCVR.version) >= 0) {\n return [];\n }\n\n const startMs = Date.now();\n const start = afterVersion ? versionString(afterVersion) : '';\n const end = versionString(upToCVR.version);\n lc.debug?.(`scanning config patches for clients from ${start}`);\n\n const reader = new TransactionPool(lc, Mode.READONLY).run(this.#db);\n try {\n // Verify that we are reading the right version of the CVR.\n await reader.processReadTask(tx =>\n checkVersion(tx, this.#schema, this.#id, current),\n );\n\n const [allDesires, queryRows] = await reader.processReadTask(tx =>\n Promise.all([\n tx<DesiresRow[]>`\n SELECT * FROM ${this.#cvr('desires')}\n WHERE \"clientGroupID\" = ${this.#id}\n AND \"patchVersion\" > ${start}\n AND \"patchVersion\" <= ${end}`,\n tx<Pick<QueriesRow, 'deleted' | 'queryHash' | 'patchVersion'>[]>`\n SELECT deleted, \"queryHash\", \"patchVersion\" FROM ${this.#cvr('queries')}\n WHERE \"clientGroupID\" = ${this.#id}\n AND \"patchVersion\" > ${start}\n AND \"patchVersion\" <= ${end}`,\n ]),\n );\n\n const patches: PatchToVersion[] = [];\n for (const row of queryRows) {\n const {queryHash: id} = row;\n const patch: Patch = row.deleted\n ? {type: 'query', op: 'del', id}\n : {type: 'query', op: 'put', id};\n const v = row.patchVersion;\n assert(v);\n patches.push({patch, toVersion: versionFromString(v)});\n }\n for (const row of allDesires) {\n const {clientID, queryHash: id} = row;\n const patch: Patch = row.deleted\n ? {type: 'query', op: 'del', id, clientID}\n : {type: 'query', op: 'put', id, clientID};\n patches.push({patch, toVersion: versionFromString(row.patchVersion)});\n }\n\n lc.debug?.(\n `${patches.length} config patches (${Date.now() - startMs} ms)`,\n );\n return patches;\n } finally {\n reader.setDone();\n }\n }\n\n async #checkVersionAndOwnership(\n tx: PostgresTransaction,\n expectedCurrentVersion: CVRVersion,\n lastConnectTime: number,\n ): Promise<void> {\n const expected = versionString(expectedCurrentVersion);\n const result = await tx<\n Pick<InstancesRow, 'version' | 'owner' | 'grantedAt'>[]\n >`SELECT \"version\", \"owner\", \"grantedAt\" FROM ${this.#cvr('instances')}\n WHERE \"clientGroupID\" = ${this.#id}\n FOR UPDATE`.execute(); // Note: execute() immediately to send the query before others.\n const {version, owner, grantedAt} =\n result.length > 0\n ? result[0]\n : {\n version: EMPTY_CVR_VERSION.stateVersion,\n owner: null,\n grantedAt: null,\n };\n if (owner !== this.#taskID && (grantedAt ?? 0) > lastConnectTime) {\n throw new OwnershipError(owner, grantedAt, lastConnectTime);\n }\n if (version !== expected) {\n throw new ConcurrentModificationException(expected, version);\n }\n }\n\n async #flush(\n expectedCurrentVersion: CVRVersion,\n cvr: CVRSnapshot,\n lastConnectTime: number,\n ): Promise<CVRFlushStats | null> {\n const stats: CVRFlushStats = {\n instances: 0,\n queries: 0,\n desires: 0,\n clients: 0,\n rows: 0,\n rowsDeferred: 0,\n statements: 0,\n };\n if (this.#pendingRowRecordUpdates.size) {\n const existingRowRecords = await this.getRowRecords();\n this.#rowCount = existingRowRecords.size;\n for (const [id, row] of this.#pendingRowRecordUpdates.entries()) {\n if (this.#forceUpdates.has(id)) {\n continue;\n }\n const existing = existingRowRecords.get(id);\n if (\n // Don't delete or add an unreferenced row if it's not in the CVR.\n (existing === undefined && !row?.refCounts) ||\n // Don't write a row record that exactly matches what's in the CVR.\n deepEqual(\n (row ?? undefined) as ReadonlyJSONValue | undefined,\n existing as ReadonlyJSONValue | undefined,\n )\n ) {\n this.#pendingRowRecordUpdates.delete(id);\n }\n }\n }\n if (this.#pendingRowRecordUpdates.size === 0 && this.#writes.size === 0) {\n return null;\n }\n // Note: The CVR instance itself is only updated if there are material\n // changes (i.e. changes to the CVR contents) to flush.\n this.putInstance(cvr);\n\n const rowsFlushed = await this.#db.begin(Mode.READ_COMMITTED, async tx => {\n const pipelined: Promise<unknown>[] = [\n // #checkVersionAndOwnership() executes a `SELECT ... FOR UPDATE`\n // query to acquire a row-level lock so that version-updating\n // transactions are effectively serialized per cvr.instance.\n //\n // Note that `rowsVersion` updates, on the other hand, are not subject\n // to this lock and can thus commit / be-committed independently of\n // cvr.instances.\n this.#checkVersionAndOwnership(\n tx,\n expectedCurrentVersion,\n lastConnectTime,\n ),\n ];\n\n for (const write of this.#writes) {\n stats.instances += write.stats.instances ?? 0;\n stats.queries += write.stats.queries ?? 0;\n stats.desires += write.stats.desires ?? 0;\n stats.clients += write.stats.clients ?? 0;\n stats.rows += write.stats.rows ?? 0;\n\n pipelined.push(write.write(tx, lastConnectTime).execute());\n stats.statements++;\n }\n\n const rowUpdates = this.#rowCache.executeRowUpdates(\n tx,\n cvr.version,\n this.#pendingRowRecordUpdates,\n 'allow-defer',\n );\n pipelined.push(...rowUpdates);\n stats.statements += rowUpdates.length;\n\n // Make sure Errors thrown by pipelined statements\n // are propagated up the stack.\n await Promise.all(pipelined);\n\n if (rowUpdates.length === 0) {\n stats.rowsDeferred = this.#pendingRowRecordUpdates.size;\n return false;\n }\n stats.rows += this.#pendingRowRecordUpdates.size;\n return true;\n });\n\n this.#rowCount = await this.#rowCache.apply(\n this.#pendingRowRecordUpdates,\n cvr.version,\n rowsFlushed,\n );\n recordRowsSynced(this.#rowCount);\n\n if (this.#upstreamDb) {\n await this.#upstreamDb.begin(Mode.READ_COMMITTED, async tx => {\n await Promise.all(this.#upstreamWrites.map(write => write(tx)));\n });\n }\n\n return stats;\n }\n\n get rowCount(): number {\n return this.#rowCount;\n }\n\n async flush(\n lc: LogContext,\n expectedCurrentVersion: CVRVersion,\n cvr: CVRSnapshot,\n lastConnectTime: number,\n ): Promise<CVRFlushStats | null> {\n const start = performance.now();\n try {\n const stats = await this.#flush(\n expectedCurrentVersion,\n cvr,\n lastConnectTime,\n );\n if (stats) {\n const elapsed = performance.now() - start;\n lc.debug?.(\n `flushed cvr@${versionString(cvr.version)} ` +\n `${JSON.stringify(stats)} in (${elapsed} ms)`,\n );\n this.#rowCache.recordSyncFlushStats(stats, elapsed);\n }\n return stats;\n } catch (e) {\n // Clear cached state if an error (e.g. ConcurrentModificationException) is encountered.\n this.#rowCache.clear();\n throw e;\n } finally {\n this.#writes.clear();\n this.#upstreamWrites.length = 0;\n this.#pendingRowRecordUpdates.clear();\n this.#forceUpdates.clear();\n }\n }\n\n hasPendingUpdates(): boolean {\n return this.#rowCache.hasPendingUpdates();\n }\n\n /** Resolves when all pending updates are flushed. */\n flushed(lc: LogContext): Promise<void> {\n return this.#rowCache.flushed(lc);\n }\n\n async inspectQueries(\n lc: LogContext,\n ttlClock: TTLClock,\n clientID?: string,\n ): Promise<InspectQueryRow[]> {\n const db = this.#db;\n const clientGroupID = this.#id;\n\n const reader = new TransactionPool(lc, Mode.READONLY).run(db);\n try {\n return await reader.processReadTask(\n tx => tx<InspectQueryRow[]>`\n SELECT DISTINCT ON (d.\"clientID\", d.\"queryHash\")\n d.\"clientID\",\n d.\"queryHash\" AS \"queryID\",\n COALESCE(d.\"ttlMs\", ${DEFAULT_TTL_MS}) AS \"ttl\",\n d.\"inactivatedAtMs\" AS \"inactivatedAt\",\n (SELECT COUNT(*)::INT FROM ${this.#cvr('rows')} r \n WHERE r.\"clientGroupID\" = d.\"clientGroupID\" \n AND r.\"refCounts\" ? d.\"queryHash\") AS \"rowCount\",\n q.\"clientAST\" AS \"ast\",\n (q.\"patchVersion\" IS NOT NULL) AS \"got\",\n COALESCE(d.\"deleted\", FALSE) AS \"deleted\",\n q.\"queryName\" AS \"name\",\n q.\"queryArgs\" AS \"args\"\n FROM ${this.#cvr('desires')} d\n LEFT JOIN ${this.#cvr('queries')} q\n ON q.\"clientGroupID\" = d.\"clientGroupID\"\n AND q.\"queryHash\" = d.\"queryHash\"\n WHERE d.\"clientGroupID\" = ${clientGroupID}\n ${clientID ? tx`AND d.\"clientID\" = ${clientID}` : tx``}\n AND NOT (\n d.\"inactivatedAtMs\" IS NOT NULL \n AND d.\"ttlMs\" IS NOT NULL \n AND (d.\"inactivatedAtMs\" + d.\"ttlMs\") <= ${ttlClockAsNumber(ttlClock)}\n )\n ORDER BY d.\"clientID\", d.\"queryHash\"`,\n );\n } finally {\n reader.setDone();\n }\n }\n}\n\n/**\n * This is similar to {@link CVRStore.#checkVersionAndOwnership} except\n * that it only checks the version and is suitable for snapshot reads\n * (i.e. by doing a plain `SELECT` rather than a `SELECT ... FOR UPDATE`).\n */\nexport async function checkVersion(\n tx: PostgresTransaction,\n schema: string,\n clientGroupID: string,\n expectedCurrentVersion: CVRVersion,\n): Promise<void> {\n const expected = versionString(expectedCurrentVersion);\n const result = await tx<Pick<InstancesRow, 'version'>[]>`\n SELECT version FROM ${tx(schema)}.instances \n WHERE \"clientGroupID\" = ${clientGroupID}`;\n const {version} =\n result.length > 0 ? result[0] : {version: EMPTY_CVR_VERSION.stateVersion};\n if (version !== expected) {\n throw new ConcurrentModificationException(expected, version);\n }\n}\n\nexport class ClientNotFoundError extends ProtocolErrorWithLevel {\n constructor(message: string) {\n super({\n kind: ErrorKind.ClientNotFound,\n message,\n origin: ErrorOrigin.ZeroCache,\n });\n }\n}\n\nexport class ConcurrentModificationException extends ProtocolErrorWithLevel {\n readonly name = 'ConcurrentModificationException';\n\n constructor(expectedVersion: string, actualVersion: string) {\n super(\n {\n kind: ErrorKind.Internal,\n message: `CVR has been concurrently modified. Expected ${expectedVersion}, got ${actualVersion}`,\n origin: ErrorOrigin.ZeroCache,\n },\n 'warn',\n );\n }\n}\n\nexport class OwnershipError extends ProtocolErrorWithLevel {\n readonly name = 'OwnershipError';\n\n constructor(\n owner: string | null,\n grantedAt: number | null,\n lastConnectTime: number,\n ) {\n super(\n {\n kind: ErrorKind.Rehome,\n message:\n `CVR ownership was transferred to ${owner} at ` +\n `${new Date(grantedAt ?? 0).toISOString()} ` +\n `(last connect time: ${new Date(lastConnectTime).toISOString()})`,\n maxBackoffMs: 0,\n origin: ErrorOrigin.ZeroCache,\n },\n 'info',\n );\n }\n}\n\nexport class InvalidClientSchemaError extends ProtocolErrorWithLevel {\n readonly name = 'InvalidClientSchemaError';\n\n constructor(cause: unknown) {\n super(\n {\n kind: ErrorKind.SchemaVersionNotSupported,\n message: `Could not parse clientSchema stored in CVR: ${String(cause)}`,\n origin: ErrorOrigin.ZeroCache,\n },\n 'warn',\n {cause},\n );\n }\n}\n\nexport class RowsVersionBehindError extends Error {\n readonly name = 'RowsVersionBehindError';\n readonly cvrVersion: string;\n readonly rowsVersion: string | null;\n\n constructor(cvrVersion: string, rowsVersion: string | null) {\n super(`rowsVersion (${rowsVersion}) is behind CVR ${cvrVersion}`);\n this.cvrVersion = cvrVersion;\n this.rowsVersion = rowsVersion;\n }\n}\n"],"names":["Mode.READONLY","version","v.parse","v","Mode.READ_COMMITTED","ErrorKind.ClientNotFound","ErrorOrigin.ZeroCache","ErrorKind.Internal","ErrorKind.Rehome","ErrorKind.SchemaVersionNotSupported"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"cvr-store.js","sources":["../../../../../../zero-cache/src/services/view-syncer/cvr-store.ts"],"sourcesContent":["import {trace} from '@opentelemetry/api';\nimport type {LogContext} from '@rocicorp/logger';\nimport type {MaybeRow, PendingQuery} from 'postgres';\nimport {startAsyncSpan} from '../../../../otel/src/span.ts';\nimport {version} from '../../../../otel/src/version.ts';\nimport {assert} from '../../../../shared/src/asserts.ts';\nimport {CustomKeyMap} from '../../../../shared/src/custom-key-map.ts';\nimport {CustomKeySet} from '../../../../shared/src/custom-key-set.ts';\nimport {\n deepEqual,\n type ReadonlyJSONValue,\n} from '../../../../shared/src/json.ts';\nimport {sleep} from '../../../../shared/src/sleep.ts';\nimport * as v from '../../../../shared/src/valita.ts';\nimport {astSchema} from '../../../../zero-protocol/src/ast.ts';\nimport {clientSchemaSchema} from '../../../../zero-protocol/src/client-schema.ts';\nimport {ErrorKind} from '../../../../zero-protocol/src/error-kind.ts';\nimport {ErrorOrigin} from '../../../../zero-protocol/src/error-origin.ts';\nimport type {InspectQueryRow} from '../../../../zero-protocol/src/inspect-down.ts';\nimport {clampTTL, DEFAULT_TTL_MS} from '../../../../zql/src/query/ttl.ts';\nimport * as Mode from '../../db/mode-enum.ts';\nimport {TransactionPool} from '../../db/transaction-pool.ts';\nimport {recordRowsSynced} from '../../server/anonymous-otel-start.ts';\nimport {ProtocolErrorWithLevel} from '../../types/error-with-level.ts';\nimport type {PostgresDB, PostgresTransaction} from '../../types/pg.ts';\nimport {rowIDString} from '../../types/row-key.ts';\nimport {cvrSchema, type ShardID, upstreamSchema} from '../../types/shards.ts';\nimport type {Patch, PatchToVersion} from './client-handler.ts';\nimport type {CVR, CVRSnapshot} from './cvr.ts';\nimport {RowRecordCache} from './row-record-cache.ts';\nimport {\n type ClientsRow,\n type DesiresRow,\n type InstancesRow,\n type QueriesRow,\n type RowsRow,\n} from './schema/cvr.ts';\nimport {\n type ClientQueryRecord,\n type ClientRecord,\n cmpVersions,\n type CustomQueryRecord,\n type CVRVersion,\n EMPTY_CVR_VERSION,\n type InternalQueryRecord,\n type NullableCVRVersion,\n type QueryPatch,\n type QueryRecord,\n queryRecordToQueryRow,\n type RowID,\n type RowRecord,\n versionFromString,\n versionString,\n} from './schema/types.ts';\nimport {\n type TTLClock,\n ttlClockAsNumber,\n ttlClockFromNumber,\n} from './ttl-clock.ts';\n\nexport type CVRFlushStats = {\n instances: number;\n queries: number;\n desires: number;\n clients: number;\n rows: number;\n rowsDeferred: number;\n statements: number;\n};\n\nconst tracer = trace.getTracer('cvr-store', version);\n\nfunction asQuery(row: QueriesRow): QueryRecord {\n const maybeVersion = (s: string | null) =>\n s === null ? undefined : versionFromString(s);\n\n if (row.clientAST === null) {\n // custom query\n assert(\n row.queryName !== null && row.queryArgs !== null,\n 'queryName and queryArgs must be set for custom queries',\n );\n return {\n type: 'custom',\n id: row.queryHash,\n name: row.queryName,\n args: row.queryArgs,\n patchVersion: maybeVersion(row.patchVersion),\n clientState: {},\n transformationHash: row.transformationHash ?? undefined,\n transformationVersion: maybeVersion(row.transformationVersion),\n } satisfies CustomQueryRecord;\n }\n\n const ast = astSchema.parse(row.clientAST);\n return row.internal\n ? ({\n type: 'internal',\n id: row.queryHash,\n ast,\n transformationHash: row.transformationHash ?? undefined,\n transformationVersion: maybeVersion(row.transformationVersion),\n } satisfies InternalQueryRecord)\n : ({\n type: 'client',\n id: row.queryHash,\n ast,\n patchVersion: maybeVersion(row.patchVersion),\n clientState: {},\n transformationHash: row.transformationHash ?? undefined,\n transformationVersion: maybeVersion(row.transformationVersion),\n } satisfies ClientQueryRecord);\n}\n\n// The time to wait between load attempts.\nconst LOAD_ATTEMPT_INTERVAL_MS = 500;\n// The maximum number of load() attempts if the rowsVersion is behind.\n// This currently results in a maximum catchup time of ~5 seconds, after\n// which we give up and consider the CVR invalid.\n//\n// TODO: Make this configurable with something like --max-catchup-wait-ms,\n// as it is technically application specific.\nconst MAX_LOAD_ATTEMPTS = 10;\n\nexport class CVRStore {\n readonly #schema: string;\n readonly #taskID: string;\n readonly #id: string;\n readonly #failService: (e: unknown) => void;\n readonly #db: PostgresDB;\n readonly #upstreamDb: PostgresDB | undefined;\n readonly #writes: Set<{\n stats: Partial<CVRFlushStats>;\n write: (\n tx: PostgresTransaction,\n lastConnectTime: number,\n ) => PendingQuery<MaybeRow[]>;\n }> = new Set();\n readonly #upstreamWrites: ((\n tx: PostgresTransaction,\n ) => PendingQuery<MaybeRow[]>)[] = [];\n readonly #pendingRowRecordUpdates = new CustomKeyMap<RowID, RowRecord | null>(\n rowIDString,\n );\n readonly #forceUpdates = new CustomKeySet<RowID>(rowIDString);\n readonly #rowCache: RowRecordCache;\n readonly #loadAttemptIntervalMs: number;\n readonly #maxLoadAttempts: number;\n readonly #upstreamSchemaName: string;\n #rowCount: number = 0;\n\n constructor(\n lc: LogContext,\n cvrDb: PostgresDB,\n // Optionally undefined to deal with custom upstreams.\n // This is temporary until we have a more principled protocol to deal with\n // custom upstreams and clearing their custom mutator responses.\n // An implementor could simply clear them after N minutes for the time being.\n upstreamDb: PostgresDB | undefined,\n shard: ShardID,\n taskID: string,\n cvrID: string,\n failService: (e: unknown) => void,\n loadAttemptIntervalMs = LOAD_ATTEMPT_INTERVAL_MS,\n maxLoadAttempts = MAX_LOAD_ATTEMPTS,\n deferredRowFlushThreshold = 100, // somewhat arbitrary\n setTimeoutFn = setTimeout,\n ) {\n this.#failService = failService;\n this.#db = cvrDb;\n this.#upstreamDb = upstreamDb;\n this.#schema = cvrSchema(shard);\n this.#taskID = taskID;\n this.#id = cvrID;\n this.#rowCache = new RowRecordCache(\n lc,\n cvrDb,\n shard,\n cvrID,\n failService,\n deferredRowFlushThreshold,\n setTimeoutFn,\n );\n this.#loadAttemptIntervalMs = loadAttemptIntervalMs;\n this.#maxLoadAttempts = maxLoadAttempts;\n this.#upstreamSchemaName = upstreamSchema(shard);\n }\n\n #cvr(table: string) {\n return this.#db(`${this.#schema}.${table}`);\n }\n\n load(lc: LogContext, lastConnectTime: number): Promise<CVR> {\n return startAsyncSpan(tracer, 'cvr.load', async () => {\n let err: RowsVersionBehindError | undefined;\n for (let i = 0; i < this.#maxLoadAttempts; i++) {\n if (i > 0) {\n await sleep(this.#loadAttemptIntervalMs);\n }\n const result = await this.#load(lc, lastConnectTime);\n if (result instanceof RowsVersionBehindError) {\n lc.info?.(`attempt ${i + 1}: ${String(result)}`);\n err = result;\n continue;\n }\n return result;\n }\n assert(err);\n throw new ClientNotFoundError(\n `max attempts exceeded waiting for CVR@${err.cvrVersion} to catch up from ${err.rowsVersion}`,\n );\n });\n }\n\n async #load(\n lc: LogContext,\n lastConnectTime: number,\n ): Promise<CVR | RowsVersionBehindError> {\n const start = Date.now();\n\n const id = this.#id;\n const cvr: CVR = {\n id,\n version: EMPTY_CVR_VERSION,\n lastActive: 0,\n ttlClock: ttlClockFromNumber(0), // TTL clock starts at 0, not Date.now()\n replicaVersion: null,\n clients: {},\n queries: {},\n clientSchema: null,\n profileID: null,\n };\n\n const [instance, clientsRows, queryRows, desiresRows] =\n await this.#db.begin(Mode.READONLY, tx => {\n lc.debug?.(`CVR tx started after ${Date.now() - start} ms`);\n return [\n tx<\n (Omit<InstancesRow, 'clientGroupID'> & {\n profileID: string | null;\n deleted: boolean;\n rowsVersion: string | null;\n })[]\n >`SELECT cvr.\"version\",\n \"lastActive\",\n \"ttlClock\",\n \"replicaVersion\",\n \"owner\",\n \"grantedAt\",\n \"clientSchema\",\n \"profileID\",\n \"deleted\",\n rows.\"version\" as \"rowsVersion\"\n FROM ${this.#cvr('instances')} AS cvr\n LEFT JOIN ${this.#cvr('rowsVersion')} AS rows\n ON cvr.\"clientGroupID\" = rows.\"clientGroupID\"\n WHERE cvr.\"clientGroupID\" = ${id}`,\n tx<Pick<ClientsRow, 'clientID'>[]>`SELECT \"clientID\" FROM ${this.#cvr(\n 'clients',\n )}\n WHERE \"clientGroupID\" = ${id}`,\n tx<QueriesRow[]>`SELECT * FROM ${this.#cvr('queries')}\n WHERE \"clientGroupID\" = ${id} AND deleted IS DISTINCT FROM true`,\n tx<DesiresRow[]>`SELECT\n \"clientGroupID\",\n \"clientID\",\n \"queryHash\",\n \"patchVersion\",\n \"deleted\",\n \"ttlMs\" AS \"ttl\",\n \"inactivatedAtMs\" AS \"inactivatedAt\"\n FROM ${this.#cvr('desires')}\n WHERE \"clientGroupID\" = ${id}`,\n ];\n });\n lc.debug?.(\n `CVR tx completed after ${Date.now() - start} ms ` +\n `(${clientsRows.length} clients, ${queryRows.length} queries, ${desiresRows.length} desires)`,\n );\n\n if (instance.length === 0) {\n // This is the first time we see this CVR.\n this.putInstance({\n version: cvr.version,\n lastActive: 0,\n ttlClock: ttlClockFromNumber(0), // TTL clock starts at 0 for new instances\n replicaVersion: null,\n clientSchema: null,\n profileID: null,\n });\n } else {\n assert(instance.length === 1);\n const {\n version,\n lastActive,\n ttlClock,\n replicaVersion,\n owner,\n grantedAt,\n rowsVersion,\n clientSchema,\n profileID,\n deleted,\n } = instance[0];\n\n if (deleted) {\n throw new ClientNotFoundError(\n 'Client has been purged due to inactivity',\n );\n }\n\n if (owner !== this.#taskID) {\n if ((grantedAt ?? 0) > lastConnectTime) {\n throw new OwnershipError(owner, grantedAt, lastConnectTime);\n } else {\n // Fire-and-forget an ownership change to signal the current owner.\n // Note that the query is structured such that it only succeeds in the\n // correct conditions (i.e. gated on `grantedAt`).\n void this.#db`\n UPDATE ${this.#cvr('instances')} \n SET \"owner\" = ${this.#taskID}, \n \"grantedAt\" = ${lastConnectTime}\n WHERE \"clientGroupID\" = ${this.#id} AND\n (\"grantedAt\" IS NULL OR\n \"grantedAt\" <= to_timestamp(${lastConnectTime / 1000}))\n `\n .execute()\n .catch(this.#failService);\n }\n }\n\n if (version !== (rowsVersion ?? EMPTY_CVR_VERSION.stateVersion)) {\n // This will cause the load() method to wait for row catchup and retry.\n // Assuming the ownership signal succeeds, the current owner will stop\n // modifying the CVR and flush its pending row changes.\n return new RowsVersionBehindError(version, rowsVersion);\n }\n\n cvr.version = versionFromString(version);\n cvr.lastActive = lastActive;\n cvr.ttlClock = ttlClock;\n cvr.replicaVersion = replicaVersion;\n cvr.profileID = profileID;\n\n try {\n cvr.clientSchema =\n clientSchema === null\n ? null\n : v.parse(clientSchema, clientSchemaSchema);\n } catch (e) {\n throw new InvalidClientSchemaError(e);\n }\n }\n\n for (const row of clientsRows) {\n cvr.clients[row.clientID] = {\n id: row.clientID,\n desiredQueryIDs: [],\n };\n }\n\n for (const row of queryRows) {\n const query = asQuery(row);\n cvr.queries[row.queryHash] = query;\n }\n\n for (const row of desiresRows) {\n const client = cvr.clients[row.clientID];\n // Note: row.inactivatedAt is mapped from inactivatedAtMs in the SQL query\n if (client) {\n if (!row.deleted && row.inactivatedAt === null) {\n client.desiredQueryIDs.push(row.queryHash);\n }\n } else {\n // This can happen if the client was deleted but the queries are still alive.\n lc.debug?.(`Client ${row.clientID} not found`);\n }\n\n const query = cvr.queries[row.queryHash];\n if (\n query &&\n query.type !== 'internal' &&\n (!row.deleted || row.inactivatedAt !== null)\n ) {\n query.clientState[row.clientID] = {\n inactivatedAt: row.inactivatedAt ?? undefined,\n ttl: clampTTL(row.ttl ?? DEFAULT_TTL_MS),\n version: versionFromString(row.patchVersion),\n };\n }\n }\n lc.debug?.(\n `loaded cvr@${versionString(cvr.version)} (${Date.now() - start} ms)`,\n );\n\n // why do we not sort `desiredQueryIDs` here?\n\n return cvr;\n }\n\n getRowRecords(): Promise<ReadonlyMap<RowID, RowRecord>> {\n return this.#rowCache.getRowRecords();\n }\n\n putRowRecord(row: RowRecord): void {\n this.#pendingRowRecordUpdates.set(row.id, row);\n }\n\n /**\n * Note: Removing a row from the CVR should be represented by a\n * {@link putRowRecord()} with `refCounts: null` in order to properly\n * produce the appropriate delete patch when catching up old clients.\n *\n * This `delRowRecord()` method, on the other hand, is only used for\n * \"canceling\" the put of a row that was not in the CVR in the first place.\n */\n delRowRecord(id: RowID): void {\n this.#pendingRowRecordUpdates.set(id, null);\n }\n\n /**\n * Overrides the default logic that removes no-op writes and forces\n * the updates for the given row `ids`. This has no effect if there\n * are no corresponding puts or dels for the associated row records.\n */\n forceUpdates(...ids: RowID[]) {\n for (const id of ids) {\n this.#forceUpdates.add(id);\n }\n }\n\n /**\n * Updates the `ttlClock` of the CVR instance. The ttlClock starts at 0 when\n * the CVR instance is first created and increments based on elapsed time\n * since the base time established by the ViewSyncerService.\n */\n async updateTTLClock(ttlClock: TTLClock, lastActive: number): Promise<void> {\n await this.#db`UPDATE ${this.#cvr('instances')}\n SET \"lastActive\" = ${lastActive},\n \"ttlClock\" = ${ttlClock}\n WHERE \"clientGroupID\" = ${this.#id}`.execute();\n }\n\n /**\n * @returns This returns the current `ttlClock` of the CVR instance. The ttlClock\n * represents elapsed time since the instance was created (starting from 0).\n * If the CVR has never been initialized for this client group, it returns\n * `undefined`.\n */\n async getTTLClock(): Promise<TTLClock | undefined> {\n const result = await this.#db<Pick<InstancesRow, 'ttlClock'>[]>`\n SELECT \"ttlClock\" FROM ${this.#cvr('instances')}\n WHERE \"clientGroupID\" = ${this.#id}`.values();\n if (result.length === 0) {\n // This can happen if the CVR has not been initialized yet.\n return undefined;\n }\n assert(result.length === 1);\n return result[0][0];\n }\n\n putInstance({\n version,\n replicaVersion,\n lastActive,\n clientSchema,\n profileID,\n ttlClock,\n }: Pick<\n CVRSnapshot,\n | 'version'\n | 'replicaVersion'\n | 'lastActive'\n | 'clientSchema'\n | 'profileID'\n | 'ttlClock'\n >): void {\n this.#writes.add({\n stats: {instances: 1},\n write: (tx, lastConnectTime) => {\n const change: InstancesRow = {\n clientGroupID: this.#id,\n version: versionString(version),\n lastActive,\n ttlClock,\n replicaVersion,\n owner: this.#taskID,\n grantedAt: lastConnectTime,\n clientSchema,\n profileID,\n };\n return tx`\n INSERT INTO ${this.#cvr('instances')} ${tx(change)} \n ON CONFLICT (\"clientGroupID\") DO UPDATE SET ${tx(change)}`;\n },\n });\n }\n\n markQueryAsDeleted(version: CVRVersion, queryPatch: QueryPatch): void {\n this.#writes.add({\n stats: {queries: 1},\n write: tx => tx`UPDATE ${this.#cvr('queries')} SET ${tx({\n patchVersion: versionString(version),\n deleted: true,\n transformationHash: null,\n transformationVersion: null,\n })}\n WHERE \"clientGroupID\" = ${this.#id} AND \"queryHash\" = ${queryPatch.id}`,\n });\n }\n\n putQuery(query: QueryRecord): void {\n const change: QueriesRow = queryRecordToQueryRow(this.#id, query);\n // ${JSON.stringify(change.queryArgs)}::text::json is used because postgres.js\n // gets confused if the input is `[boolean]` and throws an error saying a bool\n // cannot be converted to json.\n // https://github.com/porsager/postgres/issues/386\n this.#writes.add({\n stats: {queries: 1},\n write: tx => tx`INSERT INTO ${this.#cvr('queries')} (\n \"clientGroupID\",\n \"queryHash\",\n \"clientAST\",\n \"queryName\",\n \"queryArgs\",\n \"patchVersion\",\n \"transformationHash\",\n \"transformationVersion\",\n \"internal\",\n \"deleted\"\n ) VALUES (\n ${change.clientGroupID},\n ${change.queryHash},\n ${change.clientAST},\n ${change.queryName},\n ${change.queryArgs === undefined ? null : JSON.stringify(change.queryArgs)}::text::json,\n ${change.patchVersion},\n ${change.transformationHash ?? null},\n ${change.transformationVersion ?? null},\n ${change.internal},\n ${change.deleted ?? false}\n )\n ON CONFLICT (\"clientGroupID\", \"queryHash\")\n DO UPDATE SET \n \"clientAST\" = ${change.clientAST},\n \"queryName\" = ${change.queryName},\n \"queryArgs\" = ${change.queryArgs === undefined ? null : JSON.stringify(change.queryArgs)}::text::json,\n \"patchVersion\" = ${change.patchVersion},\n \"transformationHash\" = ${change.transformationHash ?? null},\n \"transformationVersion\" = ${change.transformationVersion ?? null},\n \"internal\" = ${change.internal},\n \"deleted\" = ${change.deleted ?? false}`,\n });\n }\n\n updateQuery(query: QueryRecord) {\n const maybeVersionString = (v: CVRVersion | undefined) =>\n v ? versionString(v) : null;\n\n const change: Pick<\n QueriesRow,\n | 'patchVersion'\n | 'transformationHash'\n | 'transformationVersion'\n | 'deleted'\n > = {\n patchVersion:\n query.type === 'internal'\n ? null\n : maybeVersionString(query.patchVersion),\n transformationHash: query.transformationHash ?? null,\n transformationVersion: maybeVersionString(query.transformationVersion),\n deleted: false,\n };\n\n this.#writes.add({\n stats: {queries: 1},\n write: tx => tx`UPDATE ${this.#cvr('queries')} SET ${tx(change)}\n WHERE \"clientGroupID\" = ${this.#id} AND \"queryHash\" = ${query.id}`,\n });\n }\n\n insertClient(client: ClientRecord): void {\n const change: ClientsRow = {\n clientGroupID: this.#id,\n clientID: client.id,\n };\n\n this.#writes.add({\n stats: {clients: 1},\n write: tx => tx`INSERT INTO ${this.#cvr('clients')} ${tx(change)}`,\n });\n }\n\n deleteClient(clientID: string) {\n this.#writes.add({\n stats: {clients: 1},\n write: sql =>\n sql`DELETE FROM ${this.#cvr('clients')} \n WHERE \"clientGroupID\" = ${this.#id} \n AND \"clientID\" = ${clientID}`,\n });\n this.#upstreamWrites.push(\n sql =>\n sql`DELETE FROM ${sql(this.#upstreamSchemaName)}.\"mutations\" \n WHERE \"clientGroupID\" = ${this.#id} \n AND \"clientID\" = ${clientID}`,\n );\n }\n\n putDesiredQuery(\n newVersion: CVRVersion,\n query: {id: string},\n client: {id: string},\n deleted: boolean,\n inactivatedAt: TTLClock | undefined,\n ttl: number,\n ): void {\n const change: DesiresRow = {\n clientGroupID: this.#id,\n clientID: client.id,\n deleted,\n inactivatedAt: inactivatedAt ?? null,\n patchVersion: versionString(newVersion),\n queryHash: query.id,\n\n // ttl is in ms in JavaScript\n ttl: ttl < 0 ? null : ttl,\n };\n\n // For backward compatibility during rollout, write to both old and new columns:\n // Old columns: inactivatedAt (TIMESTAMPTZ), ttl (INTERVAL) - need conversion ms->seconds\n // New columns: inactivatedAtMs (DOUBLE PRECISION), ttlMs (DOUBLE PRECISION) - store ms directly (1:1 with JS)\n const inactivatedAtTimestamp =\n inactivatedAt === undefined\n ? null\n : ttlClockFromNumber(ttlClockAsNumber(inactivatedAt) / 1000);\n const inactivatedAtMs = inactivatedAt ?? null;\n const ttlInterval = ttl < 0 ? null : ttl / 1000; // INTERVAL needs seconds\n const ttlMs = ttl < 0 ? null : ttl; // New column stores ms directly\n\n this.#writes.add({\n stats: {desires: 1},\n write: tx => tx`\n INSERT INTO ${this.#cvr('desires')} (\n \"clientGroupID\", \"clientID\", \"queryHash\", \"patchVersion\", \"deleted\",\n \"ttl\", \"ttlMs\", \"inactivatedAt\", \"inactivatedAtMs\"\n ) VALUES (\n ${change.clientGroupID}, ${change.clientID}, ${change.queryHash}, \n ${change.patchVersion}, ${change.deleted}, ${ttlInterval}, ${ttlMs},\n ${inactivatedAtTimestamp}, ${inactivatedAtMs}\n )\n ON CONFLICT (\"clientGroupID\", \"clientID\", \"queryHash\")\n DO UPDATE SET\n \"patchVersion\" = ${change.patchVersion},\n \"deleted\" = ${change.deleted},\n \"ttl\" = ${ttlInterval},\n \"ttlMs\" = ${ttlMs},\n \"inactivatedAt\" = ${inactivatedAtTimestamp},\n \"inactivatedAtMs\" = ${inactivatedAtMs}\n `,\n });\n }\n\n catchupRowPatches(\n lc: LogContext,\n afterVersion: NullableCVRVersion,\n upToCVR: CVRSnapshot,\n current: CVRVersion,\n excludeQueryHashes: string[] = [],\n ): AsyncGenerator<RowsRow[], void, undefined> {\n return this.#rowCache.catchupRowPatches(\n lc,\n afterVersion,\n upToCVR,\n current,\n excludeQueryHashes,\n );\n }\n\n async catchupConfigPatches(\n lc: LogContext,\n afterVersion: NullableCVRVersion,\n upToCVR: CVRSnapshot,\n current: CVRVersion,\n ): Promise<PatchToVersion[]> {\n if (cmpVersions(afterVersion, upToCVR.version) >= 0) {\n return [];\n }\n\n const startMs = Date.now();\n const start = afterVersion ? versionString(afterVersion) : '';\n const end = versionString(upToCVR.version);\n lc.debug?.(`scanning config patches for clients from ${start}`);\n\n const reader = new TransactionPool(lc, Mode.READONLY).run(this.#db);\n try {\n // Verify that we are reading the right version of the CVR.\n await reader.processReadTask(tx =>\n checkVersion(tx, this.#schema, this.#id, current),\n );\n\n const [allDesires, queryRows] = await reader.processReadTask(tx =>\n Promise.all([\n tx<DesiresRow[]>`\n SELECT * FROM ${this.#cvr('desires')}\n WHERE \"clientGroupID\" = ${this.#id}\n AND \"patchVersion\" > ${start}\n AND \"patchVersion\" <= ${end}`,\n tx<Pick<QueriesRow, 'deleted' | 'queryHash' | 'patchVersion'>[]>`\n SELECT deleted, \"queryHash\", \"patchVersion\" FROM ${this.#cvr('queries')}\n WHERE \"clientGroupID\" = ${this.#id}\n AND \"patchVersion\" > ${start}\n AND \"patchVersion\" <= ${end}`,\n ]),\n );\n\n const patches: PatchToVersion[] = [];\n for (const row of queryRows) {\n const {queryHash: id} = row;\n const patch: Patch = row.deleted\n ? {type: 'query', op: 'del', id}\n : {type: 'query', op: 'put', id};\n const v = row.patchVersion;\n assert(v);\n patches.push({patch, toVersion: versionFromString(v)});\n }\n for (const row of allDesires) {\n const {clientID, queryHash: id} = row;\n const patch: Patch = row.deleted\n ? {type: 'query', op: 'del', id, clientID}\n : {type: 'query', op: 'put', id, clientID};\n patches.push({patch, toVersion: versionFromString(row.patchVersion)});\n }\n\n lc.debug?.(\n `${patches.length} config patches (${Date.now() - startMs} ms)`,\n );\n return patches;\n } finally {\n reader.setDone();\n }\n }\n\n async #checkVersionAndOwnership(\n tx: PostgresTransaction,\n expectedCurrentVersion: CVRVersion,\n lastConnectTime: number,\n ): Promise<void> {\n const expected = versionString(expectedCurrentVersion);\n const result = await tx<\n Pick<InstancesRow, 'version' | 'owner' | 'grantedAt'>[]\n >`SELECT \"version\", \"owner\", \"grantedAt\" FROM ${this.#cvr('instances')}\n WHERE \"clientGroupID\" = ${this.#id}\n FOR UPDATE NOWAIT`.execute(); // Note: execute() immediately to send the query before others.\n const {version, owner, grantedAt} =\n result.length > 0\n ? result[0]\n : {\n version: EMPTY_CVR_VERSION.stateVersion,\n owner: null,\n grantedAt: null,\n };\n if (owner !== this.#taskID && (grantedAt ?? 0) > lastConnectTime) {\n throw new OwnershipError(owner, grantedAt, lastConnectTime);\n }\n if (version !== expected) {\n throw new ConcurrentModificationException(expected, version);\n }\n }\n\n async #flush(\n expectedCurrentVersion: CVRVersion,\n cvr: CVRSnapshot,\n lastConnectTime: number,\n ): Promise<CVRFlushStats | null> {\n const stats: CVRFlushStats = {\n instances: 0,\n queries: 0,\n desires: 0,\n clients: 0,\n rows: 0,\n rowsDeferred: 0,\n statements: 0,\n };\n if (this.#pendingRowRecordUpdates.size) {\n const existingRowRecords = await this.getRowRecords();\n this.#rowCount = existingRowRecords.size;\n for (const [id, row] of this.#pendingRowRecordUpdates.entries()) {\n if (this.#forceUpdates.has(id)) {\n continue;\n }\n const existing = existingRowRecords.get(id);\n if (\n // Don't delete or add an unreferenced row if it's not in the CVR.\n (existing === undefined && !row?.refCounts) ||\n // Don't write a row record that exactly matches what's in the CVR.\n deepEqual(\n (row ?? undefined) as ReadonlyJSONValue | undefined,\n existing as ReadonlyJSONValue | undefined,\n )\n ) {\n this.#pendingRowRecordUpdates.delete(id);\n }\n }\n }\n if (this.#pendingRowRecordUpdates.size === 0 && this.#writes.size === 0) {\n return null;\n }\n // Note: The CVR instance itself is only updated if there are material\n // changes (i.e. changes to the CVR contents) to flush.\n this.putInstance(cvr);\n\n const rowsFlushed = await this.#db.begin(Mode.READ_COMMITTED, async tx => {\n const pipelined: Promise<unknown>[] = [\n // #checkVersionAndOwnership() executes a `SELECT ... FOR UPDATE`\n // query to acquire a row-level lock so that version-updating\n // transactions are effectively serialized per cvr.instance.\n //\n // Note that `rowsVersion` updates, on the other hand, are not subject\n // to this lock and can thus commit / be-committed independently of\n // cvr.instances.\n this.#checkVersionAndOwnership(\n tx,\n expectedCurrentVersion,\n lastConnectTime,\n ),\n ];\n\n for (const write of this.#writes) {\n stats.instances += write.stats.instances ?? 0;\n stats.queries += write.stats.queries ?? 0;\n stats.desires += write.stats.desires ?? 0;\n stats.clients += write.stats.clients ?? 0;\n stats.rows += write.stats.rows ?? 0;\n\n pipelined.push(write.write(tx, lastConnectTime).execute());\n stats.statements++;\n }\n\n const rowUpdates = this.#rowCache.executeRowUpdates(\n tx,\n cvr.version,\n this.#pendingRowRecordUpdates,\n 'allow-defer',\n );\n pipelined.push(...rowUpdates);\n stats.statements += rowUpdates.length;\n\n // Make sure Errors thrown by pipelined statements\n // are propagated up the stack.\n await Promise.all(pipelined);\n\n if (rowUpdates.length === 0) {\n stats.rowsDeferred = this.#pendingRowRecordUpdates.size;\n return false;\n }\n stats.rows += this.#pendingRowRecordUpdates.size;\n return true;\n });\n\n this.#rowCount = await this.#rowCache.apply(\n this.#pendingRowRecordUpdates,\n cvr.version,\n rowsFlushed,\n );\n recordRowsSynced(this.#rowCount);\n\n if (this.#upstreamDb) {\n await this.#upstreamDb.begin(Mode.READ_COMMITTED, async tx => {\n await Promise.all(this.#upstreamWrites.map(write => write(tx)));\n });\n }\n\n return stats;\n }\n\n get rowCount(): number {\n return this.#rowCount;\n }\n\n async flush(\n lc: LogContext,\n expectedCurrentVersion: CVRVersion,\n cvr: CVRSnapshot,\n lastConnectTime: number,\n ): Promise<CVRFlushStats | null> {\n const start = performance.now();\n try {\n const stats = await this.#flush(\n expectedCurrentVersion,\n cvr,\n lastConnectTime,\n );\n if (stats) {\n const elapsed = performance.now() - start;\n lc.debug?.(\n `flushed cvr@${versionString(cvr.version)} ` +\n `${JSON.stringify(stats)} in (${elapsed} ms)`,\n );\n this.#rowCache.recordSyncFlushStats(stats, elapsed);\n }\n return stats;\n } catch (e) {\n // Clear cached state if an error (e.g. ConcurrentModificationException) is encountered.\n this.#rowCache.clear();\n throw e;\n } finally {\n this.#writes.clear();\n this.#upstreamWrites.length = 0;\n this.#pendingRowRecordUpdates.clear();\n this.#forceUpdates.clear();\n }\n }\n\n hasPendingUpdates(): boolean {\n return this.#rowCache.hasPendingUpdates();\n }\n\n /** Resolves when all pending updates are flushed. */\n flushed(lc: LogContext): Promise<void> {\n return this.#rowCache.flushed(lc);\n }\n\n async inspectQueries(\n lc: LogContext,\n ttlClock: TTLClock,\n clientID?: string,\n ): Promise<InspectQueryRow[]> {\n const db = this.#db;\n const clientGroupID = this.#id;\n\n const reader = new TransactionPool(lc, Mode.READONLY).run(db);\n try {\n return await reader.processReadTask(\n tx => tx<InspectQueryRow[]>`\n SELECT DISTINCT ON (d.\"clientID\", d.\"queryHash\")\n d.\"clientID\",\n d.\"queryHash\" AS \"queryID\",\n COALESCE(d.\"ttlMs\", ${DEFAULT_TTL_MS}) AS \"ttl\",\n d.\"inactivatedAtMs\" AS \"inactivatedAt\",\n (SELECT COUNT(*)::INT FROM ${this.#cvr('rows')} r \n WHERE r.\"clientGroupID\" = d.\"clientGroupID\" \n AND r.\"refCounts\" ? d.\"queryHash\") AS \"rowCount\",\n q.\"clientAST\" AS \"ast\",\n (q.\"patchVersion\" IS NOT NULL) AS \"got\",\n COALESCE(d.\"deleted\", FALSE) AS \"deleted\",\n q.\"queryName\" AS \"name\",\n q.\"queryArgs\" AS \"args\"\n FROM ${this.#cvr('desires')} d\n LEFT JOIN ${this.#cvr('queries')} q\n ON q.\"clientGroupID\" = d.\"clientGroupID\"\n AND q.\"queryHash\" = d.\"queryHash\"\n WHERE d.\"clientGroupID\" = ${clientGroupID}\n ${clientID ? tx`AND d.\"clientID\" = ${clientID}` : tx``}\n AND NOT (\n d.\"inactivatedAtMs\" IS NOT NULL \n AND d.\"ttlMs\" IS NOT NULL \n AND (d.\"inactivatedAtMs\" + d.\"ttlMs\") <= ${ttlClockAsNumber(ttlClock)}\n )\n ORDER BY d.\"clientID\", d.\"queryHash\"`,\n );\n } finally {\n reader.setDone();\n }\n }\n}\n\n/**\n * This is similar to {@link CVRStore.#checkVersionAndOwnership} except\n * that it only checks the version and is suitable for snapshot reads\n * (i.e. by doing a plain `SELECT` rather than a `SELECT ... FOR UPDATE`).\n */\nexport async function checkVersion(\n tx: PostgresTransaction,\n schema: string,\n clientGroupID: string,\n expectedCurrentVersion: CVRVersion,\n): Promise<void> {\n const expected = versionString(expectedCurrentVersion);\n const result = await tx<Pick<InstancesRow, 'version'>[]>`\n SELECT version FROM ${tx(schema)}.instances \n WHERE \"clientGroupID\" = ${clientGroupID}`;\n const {version} =\n result.length > 0 ? result[0] : {version: EMPTY_CVR_VERSION.stateVersion};\n if (version !== expected) {\n throw new ConcurrentModificationException(expected, version);\n }\n}\n\nexport class ClientNotFoundError extends ProtocolErrorWithLevel {\n constructor(message: string) {\n super({\n kind: ErrorKind.ClientNotFound,\n message,\n origin: ErrorOrigin.ZeroCache,\n });\n }\n}\n\nexport class ConcurrentModificationException extends ProtocolErrorWithLevel {\n readonly name = 'ConcurrentModificationException';\n\n constructor(expectedVersion: string, actualVersion: string) {\n super(\n {\n kind: ErrorKind.Internal,\n message: `CVR has been concurrently modified. Expected ${expectedVersion}, got ${actualVersion}`,\n origin: ErrorOrigin.ZeroCache,\n },\n 'warn',\n );\n }\n}\n\nexport class OwnershipError extends ProtocolErrorWithLevel {\n readonly name = 'OwnershipError';\n\n constructor(\n owner: string | null,\n grantedAt: number | null,\n lastConnectTime: number,\n ) {\n super(\n {\n kind: ErrorKind.Rehome,\n message:\n `CVR ownership was transferred to ${owner} at ` +\n `${new Date(grantedAt ?? 0).toISOString()} ` +\n `(last connect time: ${new Date(lastConnectTime).toISOString()})`,\n maxBackoffMs: 0,\n origin: ErrorOrigin.ZeroCache,\n },\n 'info',\n );\n }\n}\n\nexport class InvalidClientSchemaError extends ProtocolErrorWithLevel {\n readonly name = 'InvalidClientSchemaError';\n\n constructor(cause: unknown) {\n super(\n {\n kind: ErrorKind.SchemaVersionNotSupported,\n message: `Could not parse clientSchema stored in CVR: ${String(cause)}`,\n origin: ErrorOrigin.ZeroCache,\n },\n 'warn',\n {cause},\n );\n }\n}\n\nexport class RowsVersionBehindError extends Error {\n readonly name = 'RowsVersionBehindError';\n readonly cvrVersion: string;\n readonly rowsVersion: string | null;\n\n constructor(cvrVersion: string, rowsVersion: string | null) {\n super(`rowsVersion (${rowsVersion}) is behind CVR ${cvrVersion}`);\n this.cvrVersion = cvrVersion;\n this.rowsVersion = rowsVersion;\n }\n}\n"],"names":["Mode.READONLY","version","v.parse","v","Mode.READ_COMMITTED","ErrorKind.ClientNotFound","ErrorOrigin.ZeroCache","ErrorKind.Internal","ErrorKind.Rehome","ErrorKind.SchemaVersionNotSupported"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"row-record-cache.d.ts","sourceRoot":"","sources":["../../../../../../zero-cache/src/services/view-syncer/row-record-cache.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAEjD,OAAO,KAAK,EAAC,YAAY,EAAE,GAAG,EAAC,MAAM,UAAU,CAAC;AAUhD,OAAO,EAEL,KAAK,UAAU,EACf,KAAK,mBAAmB,EACzB,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EAAY,KAAK,OAAO,EAAC,MAAM,uBAAuB,CAAC;AAC9D,OAAO,EAAe,KAAK,aAAa,EAAC,MAAM,gBAAgB,CAAC;AAChE,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,UAAU,CAAC;AAC1C,OAAO,EAEL,KAAK,OAAO,EAEb,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAEL,KAAK,UAAU,EACf,KAAK,kBAAkB,EACvB,KAAK,KAAK,EACV,KAAK,SAAS,EAGf,MAAM,mBAAmB,CAAC;AAI3B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiDG;AACH,qBAAa,cAAc;;gBAgCvB,EAAE,EAAE,UAAU,EACd,EAAE,EAAE,UAAU,EACd,KAAK,EAAE,OAAO,EACd,KAAK,EAAE,MAAM,EACb,WAAW,EAAE,CAAC,CAAC,EAAE,OAAO,KAAK,IAAI,EACjC,yBAAyB,SAAM,EAC/B,YAAY,oBAAa;IAW3B,oBAAoB,CAAC,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM;
|
|
1
|
+
{"version":3,"file":"row-record-cache.d.ts","sourceRoot":"","sources":["../../../../../../zero-cache/src/services/view-syncer/row-record-cache.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAEjD,OAAO,KAAK,EAAC,YAAY,EAAE,GAAG,EAAC,MAAM,UAAU,CAAC;AAUhD,OAAO,EAEL,KAAK,UAAU,EACf,KAAK,mBAAmB,EACzB,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EAAY,KAAK,OAAO,EAAC,MAAM,uBAAuB,CAAC;AAC9D,OAAO,EAAe,KAAK,aAAa,EAAC,MAAM,gBAAgB,CAAC;AAChE,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,UAAU,CAAC;AAC1C,OAAO,EAEL,KAAK,OAAO,EAEb,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAEL,KAAK,UAAU,EACf,KAAK,kBAAkB,EACvB,KAAK,KAAK,EACV,KAAK,SAAS,EAGf,MAAM,mBAAmB,CAAC;AAI3B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiDG;AACH,qBAAa,cAAc;;gBAgCvB,EAAE,EAAE,UAAU,EACd,EAAE,EAAE,UAAU,EACd,KAAK,EAAE,OAAO,EACd,KAAK,EAAE,MAAM,EACb,WAAW,EAAE,CAAC,CAAC,EAAE,OAAO,KAAK,IAAI,EACjC,yBAAyB,SAAM,EAC/B,YAAY,oBAAa;IAW3B,oBAAoB,CAAC,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM;IAgD5D,aAAa,IAAI,OAAO,CAAC,WAAW,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC;IAIvD;;;;;;;;;;;;;;OAcG;IACG,KAAK,CACT,UAAU,EAAE,GAAG,CAAC,KAAK,EAAE,SAAS,GAAG,IAAI,CAAC,EACxC,WAAW,EAAE,UAAU,EACvB,OAAO,EAAE,OAAO,GACf,OAAO,CAAC,MAAM,CAAC;IAmElB,iBAAiB;IAIjB;;;OAGG;IACH,OAAO,CAAC,EAAE,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAQtC,KAAK;IAOE,iBAAiB,CACtB,EAAE,EAAE,UAAU,EACd,YAAY,EAAE,kBAAkB,EAChC,OAAO,EAAE,WAAW,EACpB,OAAO,EAAE,UAAU,EACnB,kBAAkB,GAAE,MAAM,EAAO,GAChC,cAAc,CAAC,OAAO,EAAE,EAAE,IAAI,EAAE,SAAS,CAAC;IAmD7C,iBAAiB,CACf,EAAE,EAAE,mBAAmB,EACvB,OAAO,EAAE,UAAU,EACnB,UAAU,EAAE,GAAG,CAAC,KAAK,EAAE,SAAS,GAAG,IAAI,CAAC,EACxC,IAAI,EAAE,aAAa,GAAG,OAAO,GAC5B,YAAY,CAAC,GAAG,EAAE,CAAC,EAAE;CAiEzB"}
|
|
@@ -68,17 +68,21 @@ class RowRecordCache {
|
|
|
68
68
|
if (this.#cache) {
|
|
69
69
|
return this.#cache;
|
|
70
70
|
}
|
|
71
|
+
const start = Date.now();
|
|
71
72
|
const r = resolver();
|
|
72
73
|
this.#cache = r.promise;
|
|
73
74
|
const cache = new CustomKeyMap(rowIDString);
|
|
74
75
|
for await (const rows of this.#db`
|
|
75
|
-
SELECT * FROM ${this.#cvr(`rows`)}
|
|
76
|
+
SELECT * FROM ${this.#cvr(`rows`)}
|
|
76
77
|
WHERE "clientGroupID" = ${this.#cvrID} AND "refCounts" IS NOT NULL`.cursor(5e3)) {
|
|
77
78
|
for (const row of rows) {
|
|
78
79
|
const rowRecord = rowsRowToRowRecord(row);
|
|
79
80
|
cache.set(rowRecord.id, rowRecord);
|
|
80
81
|
}
|
|
81
82
|
}
|
|
83
|
+
this.#lc.debug?.(
|
|
84
|
+
`Loaded ${cache.size} row records in ${Date.now() - start} ms`
|
|
85
|
+
);
|
|
82
86
|
r.resolve(cache);
|
|
83
87
|
return this.#cache;
|
|
84
88
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"row-record-cache.js","sources":["../../../../../../zero-cache/src/services/view-syncer/row-record-cache.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport {type Resolver, resolver} from '@rocicorp/resolver';\nimport type {PendingQuery, Row} from 'postgres';\nimport {CustomKeyMap} from '../../../../shared/src/custom-key-map.ts';\nimport {must} from '../../../../shared/src/must.ts';\nimport {promiseVoid} from '../../../../shared/src/resolved-promises.ts';\nimport * as Mode from '../../db/mode-enum.ts';\nimport {TransactionPool} from '../../db/transaction-pool.ts';\nimport {\n getOrCreateCounter,\n getOrCreateHistogram,\n} from '../../observability/metrics.ts';\nimport {\n disableStatementTimeout,\n type PostgresDB,\n type PostgresTransaction,\n} from '../../types/pg.ts';\nimport {rowIDString} from '../../types/row-key.ts';\nimport {cvrSchema, type ShardID} from '../../types/shards.ts';\nimport {checkVersion, type CVRFlushStats} from './cvr-store.ts';\nimport type {CVRSnapshot} from './cvr.ts';\nimport {\n rowRecordToRowsRow,\n type RowsRow,\n rowsRowToRowRecord,\n} from './schema/cvr.ts';\nimport {\n cmpVersions,\n type CVRVersion,\n type NullableCVRVersion,\n type RowID,\n type RowRecord,\n versionString,\n versionToNullableCookie,\n} from './schema/types.ts';\n\nconst FLUSH_TYPE_ATTRIBUTE = 'flush.type';\n\n/**\n * The RowRecordCache is an in-memory cache of the `cvr.rows` tables that\n * operates as both a write-through and write-back cache.\n *\n * For \"small\" CVR updates (i.e. zero or small numbers of rows) the\n * RowRecordCache operates as write-through, executing commits in\n * {@link executeRowUpdates()} before they are {@link apply}-ed to the\n * in-memory state.\n *\n * For \"large\" CVR updates (i.e. with many rows), the cache switches to a\n * write-back mode of operation, in which {@link executeRowUpdates()} is a\n * no-op, and {@link apply()} initiates a background task to flush the pending\n * row changes to the store. This allows the client poke to be completed and\n * committed on the client without waiting for the heavyweight operation of\n * committing the row records to the CVR store.\n *\n * Note that when the cache is in write-back mode, all updates become\n * write-back (i.e. asynchronously flushed) until the pending update queue is\n * fully flushed. This is required because updates must be applied in version\n * order. As with all pending work systems in zero-cache, multiple pending\n * updates are coalesced to reduce buildup of work.\n *\n * ### High level consistency\n *\n * Note that the above caching scheme only applies to the row data in `cvr.rows`\n * and corresponding `cvr.rowsVersion` tables. CVR metadata and query\n * information, on the other hand, are always committed before completing the\n * client poke. In this manner, the difference between the `version` column in\n * `cvr.instances` and the analogous column in `cvr.rowsVersion` determines\n * whether the data in the store is consistent, or whether it is awaiting a\n * pending update.\n *\n * The logic in {@link CVRStore#load()} takes this into account by loading both\n * the `cvr.instances` version and the `cvr.rowsVersion` version and checking\n * if they are in sync, waiting for a configurable delay until they are.\n *\n * ### Eventual conversion\n *\n * In the event of a continual stream of mutations (e.g. an animation-style\n * app), it is conceivable that the row record data be continually behind\n * the CVR metadata. In order to effect eventual convergence, a new view-syncer\n * signals the current view-syncer to stop updating by writing new `owner`\n * information to the `cvr.instances` row. This effectively stops the mutation\n * processing (in {@link CVRStore.#checkVersionAndOwnership}) so that the row\n * data can eventually catch up, allowing the new view-syncer to take over.\n *\n * Of course, there is the pathological situation in which a view-syncer\n * process crashes before the pending row updates are flushed. In this case,\n * the wait timeout will elapse and the CVR considered invalid.\n */\nexport class RowRecordCache {\n // The state in the #cache is always in sync with the CVR metadata\n // (i.e. cvr.instances). It may contain information that has not yet\n // been flushed to cvr.rows.\n #cache: Promise<CustomKeyMap<RowID, RowRecord>> | undefined;\n readonly #lc: LogContext;\n readonly #db: PostgresDB;\n readonly #schema: string;\n readonly #cvrID: string;\n readonly #failService: (e: unknown) => void;\n readonly #deferredRowFlushThreshold: number;\n readonly #setTimeout: typeof setTimeout;\n\n // Write-back cache state.\n readonly #pending = new CustomKeyMap<RowID, RowRecord | null>(rowIDString);\n #pendingRowsVersion: CVRVersion | null = null;\n #flushedRowsVersion: CVRVersion | null = null;\n #flushing: Resolver<void> | null = null;\n\n readonly #cvrFlushTime = getOrCreateHistogram('sync', 'cvr.flush-time', {\n description:\n 'Time to flush a CVR transaction. This includes both synchronous ' +\n 'and asynchronous flushes, distinguished by the flush.type attribute',\n unit: 's',\n });\n readonly #cvrRowsFlushed = getOrCreateCounter(\n 'sync',\n 'cvr.rows-flushed',\n 'Number of (changed) rows flushed to a CVR',\n );\n\n constructor(\n lc: LogContext,\n db: PostgresDB,\n shard: ShardID,\n cvrID: string,\n failService: (e: unknown) => void,\n deferredRowFlushThreshold = 100,\n setTimeoutFn = setTimeout,\n ) {\n this.#lc = lc;\n this.#db = db;\n this.#schema = cvrSchema(shard);\n this.#cvrID = cvrID;\n this.#failService = failService;\n this.#deferredRowFlushThreshold = deferredRowFlushThreshold;\n this.#setTimeout = setTimeoutFn;\n }\n\n recordSyncFlushStats(stats: CVRFlushStats, elapsedMs: number) {\n this.#cvrFlushTime.record(elapsedMs / 1000, {\n [FLUSH_TYPE_ATTRIBUTE]: 'sync',\n });\n if (stats.rowsDeferred === 0) {\n this.#cvrRowsFlushed.add(stats.rows);\n }\n }\n\n #recordAsyncFlushStats(rows: number, elapsedMs: number) {\n this.#cvrFlushTime.record(elapsedMs / 1000, {\n [FLUSH_TYPE_ATTRIBUTE]: 'async',\n });\n this.#cvrRowsFlushed.add(rows);\n }\n\n #cvr(table: string) {\n return this.#db(`${this.#schema}.${table}`);\n }\n\n async #ensureLoaded(): Promise<CustomKeyMap<RowID, RowRecord>> {\n if (this.#cache) {\n return this.#cache;\n }\n const r = resolver<CustomKeyMap<RowID, RowRecord>>();\n // Set this.#cache immediately (before await) so that only one db\n // query is made even if there are multiple callers.\n this.#cache = r.promise;\n\n const cache: CustomKeyMap<RowID, RowRecord> = new CustomKeyMap(rowIDString);\n for await (const rows of this.#db<RowsRow[]>`\n SELECT * FROM ${this.#cvr(`rows`)} \n WHERE \"clientGroupID\" = ${this.#cvrID} AND \"refCounts\" IS NOT NULL`\n // TODO(arv): Arbitrary page size\n .cursor(5000)) {\n for (const row of rows) {\n const rowRecord = rowsRowToRowRecord(row);\n cache.set(rowRecord.id, rowRecord);\n }\n }\n r.resolve(cache);\n return this.#cache;\n }\n\n getRowRecords(): Promise<ReadonlyMap<RowID, RowRecord>> {\n return this.#ensureLoaded();\n }\n\n /**\n * Applies the `rowRecords` corresponding to the `rowsVersion`\n * to the cache, indicating whether the corresponding updates\n * (generated by {@link executeRowUpdates}) were `flushed`.\n *\n * If `flushed` is false, the RowRecordCache will flush the records\n * asynchronously.\n *\n * Note that `apply()` indicates that the CVR metadata associated with\n * the `rowRecords` was successfully committed, which essentially means\n * that this process has the unconditional right (and responsibility) of\n * following up with a flush of the `rowRecords`. In particular, the\n * commit of row records are not conditioned on the version or ownership\n * columns of the `cvr.instances` row.\n */\n async apply(\n rowRecords: Map<RowID, RowRecord | null>,\n rowsVersion: CVRVersion,\n flushed: boolean,\n ): Promise<number> {\n const cache = await this.#ensureLoaded();\n for (const [id, row] of rowRecords.entries()) {\n if (row === null || row.refCounts === null) {\n cache.delete(id);\n } else {\n cache.set(id, row);\n }\n if (!flushed) {\n this.#pending.set(id, row);\n }\n }\n this.#pendingRowsVersion = rowsVersion;\n // Initiate a flush if not already flushing.\n if (!flushed && this.#flushing === null) {\n this.#flushing = resolver();\n this.#setTimeout(() => this.#flush(), 0);\n }\n return cache.size;\n }\n\n async #flush() {\n const flushing = must(this.#flushing);\n try {\n while (this.#pendingRowsVersion !== this.#flushedRowsVersion) {\n const start = performance.now();\n\n const {rows, rowsVersion} = await this.#db.begin(\n Mode.READ_COMMITTED,\n tx => {\n disableStatementTimeout(tx);\n\n // Note: This code block is synchronous, guaranteeing that the\n // #pendingRowsVersion is consistent with the #pending rows.\n const rows = this.#pending.size;\n const rowsVersion = must(this.#pendingRowsVersion);\n // Awaiting all of the individual statements incurs too much\n // overhead. Instead, just catch and log exception(s); the outer\n // transaction will properly fail.\n void Promise.all(\n this.executeRowUpdates(tx, rowsVersion, this.#pending, 'force'),\n ).catch(e => this.#lc.error?.(`error flushing cvr rows`, e));\n\n this.#pending.clear();\n return {rows, rowsVersion};\n },\n );\n const elapsed = performance.now() - start;\n this.#lc.debug?.(\n `flushed ${rows} rows@${versionString(rowsVersion)} (${elapsed} ms)`,\n );\n this.#recordAsyncFlushStats(rows, elapsed);\n this.#flushedRowsVersion = rowsVersion;\n // Note: apply() may have called while the transaction was committing,\n // which will result in looping to commit the next #pendingRowsVersion.\n }\n this.#lc.debug?.(\n `up to date rows@${versionToNullableCookie(this.#flushedRowsVersion)}`,\n );\n flushing.resolve();\n this.#flushing = null;\n } catch (e) {\n flushing.reject(e);\n this.#failService(e);\n }\n }\n\n hasPendingUpdates() {\n return this.#flushing !== null;\n }\n\n /**\n * Returns a promise that resolves when all outstanding row-records\n * have been committed.\n */\n flushed(lc: LogContext): Promise<void> {\n if (this.#flushing) {\n lc.debug?.('awaiting pending row flush');\n return this.#flushing.promise;\n }\n return promiseVoid;\n }\n\n clear() {\n // Note: Only the #cache is cleared. #pending updates, on the other hand,\n // comprise canonical (i.e. already flushed) data and must be flushed\n // even if the snapshot of the present state (the #cache) is cleared.\n this.#cache = undefined;\n }\n\n async *catchupRowPatches(\n lc: LogContext,\n afterVersion: NullableCVRVersion,\n upToCVR: CVRSnapshot,\n current: CVRVersion,\n excludeQueryHashes: string[] = [],\n ): AsyncGenerator<RowsRow[], void, undefined> {\n if (cmpVersions(afterVersion, upToCVR.version) >= 0) {\n return;\n }\n\n const startMs = Date.now();\n const start = afterVersion ? versionString(afterVersion) : '';\n const end = versionString(upToCVR.version);\n lc.debug?.(`scanning row patches for clients from ${start}`);\n\n // Before accessing the CVR db, pending row records must be flushed.\n // Note that because catchupRowPatches() is called from within the\n // view syncer lock, this flush is guaranteed to complete since no\n // new CVR updates can happen while the lock is held.\n await this.flushed(lc);\n const flushMs = Date.now() - startMs;\n\n const reader = new TransactionPool(lc, Mode.READONLY).run(this.#db);\n try {\n // Verify that we are reading the right version of the CVR.\n await reader.processReadTask(tx =>\n checkVersion(tx, this.#schema, this.#cvrID, current),\n );\n\n const {query} = await reader.processReadTask(tx => {\n const query =\n excludeQueryHashes.length === 0\n ? tx<RowsRow[]>`SELECT * FROM ${this.#cvr('rows')}\n WHERE \"clientGroupID\" = ${this.#cvrID}\n AND \"patchVersion\" > ${start}\n AND \"patchVersion\" <= ${end}`\n : // Exclude rows that were already sent as part of query hydration.\n tx<RowsRow[]>`SELECT * FROM ${this.#cvr('rows')}\n WHERE \"clientGroupID\" = ${this.#cvrID}\n AND \"patchVersion\" > ${start}\n AND \"patchVersion\" <= ${end}\n AND (\"refCounts\" IS NULL OR NOT \"refCounts\" ?| ${excludeQueryHashes})`;\n return {query};\n });\n\n yield* query.cursor(10000);\n } finally {\n reader.setDone();\n }\n\n const totalMs = Date.now() - startMs;\n lc.debug?.(\n `finished row catchup (flush: ${flushMs} ms, total: ${totalMs} ms)`,\n );\n }\n\n executeRowUpdates(\n tx: PostgresTransaction,\n version: CVRVersion,\n rowUpdates: Map<RowID, RowRecord | null>,\n mode: 'allow-defer' | 'force',\n ): PendingQuery<Row[]>[] {\n if (\n mode === 'allow-defer' &&\n // defer if pending rows are being flushed\n (this.#flushing !== null ||\n // or if the new batch is above the limit.\n rowUpdates.size > this.#deferredRowFlushThreshold)\n ) {\n return [];\n }\n const rowsVersion = {\n clientGroupID: this.#cvrID,\n version: versionString(version),\n };\n const pending: PendingQuery<Row[]>[] = [\n tx`INSERT INTO ${this.#cvr('rowsVersion')} ${tx(rowsVersion)}\n ON CONFLICT (\"clientGroupID\") \n DO UPDATE SET ${tx(rowsVersion)}`.execute(),\n ];\n\n const rowRecordRows: RowsRow[] = [];\n for (const [id, row] of rowUpdates.entries()) {\n if (row === null) {\n pending.push(\n tx`\n DELETE FROM ${this.#cvr('rows')}\n WHERE \"clientGroupID\" = ${this.#cvrID}\n AND \"schema\" = ${id.schema}\n AND \"table\" = ${id.table}\n AND \"rowKey\" = ${id.rowKey}\n `.execute(),\n );\n } else {\n rowRecordRows.push(rowRecordToRowsRow(this.#cvrID, row));\n }\n }\n if (rowRecordRows.length) {\n pending.push(\n tx`\n INSERT INTO ${this.#cvr('rows')}(\n \"clientGroupID\", \"schema\", \"table\", \"rowKey\", \"rowVersion\", \"patchVersion\", \"refCounts\"\n ) SELECT\n \"clientGroupID\", \"schema\", \"table\", \"rowKey\", \"rowVersion\", \"patchVersion\", \"refCounts\"\n FROM json_to_recordset(${rowRecordRows}) AS x(\n \"clientGroupID\" TEXT,\n \"schema\" TEXT,\n \"table\" TEXT,\n \"rowKey\" JSONB,\n \"rowVersion\" TEXT,\n \"patchVersion\" TEXT,\n \"refCounts\" JSONB\n ) ON CONFLICT (\"clientGroupID\", \"schema\", \"table\", \"rowKey\")\n DO UPDATE SET \"rowVersion\" = excluded.\"rowVersion\",\n \"patchVersion\" = excluded.\"patchVersion\",\n \"refCounts\" = excluded.\"refCounts\"\n `.execute(),\n );\n this.#lc.debug?.(\n `flushing ${rowUpdates.size} rows (${rowRecordRows.length} inserts, ${\n rowUpdates.size - rowRecordRows.length\n } deletes)`,\n );\n }\n return pending;\n }\n}\n"],"names":["Mode.READ_COMMITTED","rows","rowsVersion","Mode.READONLY","query"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"row-record-cache.js","sources":["../../../../../../zero-cache/src/services/view-syncer/row-record-cache.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport {type Resolver, resolver} from '@rocicorp/resolver';\nimport type {PendingQuery, Row} from 'postgres';\nimport {CustomKeyMap} from '../../../../shared/src/custom-key-map.ts';\nimport {must} from '../../../../shared/src/must.ts';\nimport {promiseVoid} from '../../../../shared/src/resolved-promises.ts';\nimport * as Mode from '../../db/mode-enum.ts';\nimport {TransactionPool} from '../../db/transaction-pool.ts';\nimport {\n getOrCreateCounter,\n getOrCreateHistogram,\n} from '../../observability/metrics.ts';\nimport {\n disableStatementTimeout,\n type PostgresDB,\n type PostgresTransaction,\n} from '../../types/pg.ts';\nimport {rowIDString} from '../../types/row-key.ts';\nimport {cvrSchema, type ShardID} from '../../types/shards.ts';\nimport {checkVersion, type CVRFlushStats} from './cvr-store.ts';\nimport type {CVRSnapshot} from './cvr.ts';\nimport {\n rowRecordToRowsRow,\n type RowsRow,\n rowsRowToRowRecord,\n} from './schema/cvr.ts';\nimport {\n cmpVersions,\n type CVRVersion,\n type NullableCVRVersion,\n type RowID,\n type RowRecord,\n versionString,\n versionToNullableCookie,\n} from './schema/types.ts';\n\nconst FLUSH_TYPE_ATTRIBUTE = 'flush.type';\n\n/**\n * The RowRecordCache is an in-memory cache of the `cvr.rows` tables that\n * operates as both a write-through and write-back cache.\n *\n * For \"small\" CVR updates (i.e. zero or small numbers of rows) the\n * RowRecordCache operates as write-through, executing commits in\n * {@link executeRowUpdates()} before they are {@link apply}-ed to the\n * in-memory state.\n *\n * For \"large\" CVR updates (i.e. with many rows), the cache switches to a\n * write-back mode of operation, in which {@link executeRowUpdates()} is a\n * no-op, and {@link apply()} initiates a background task to flush the pending\n * row changes to the store. This allows the client poke to be completed and\n * committed on the client without waiting for the heavyweight operation of\n * committing the row records to the CVR store.\n *\n * Note that when the cache is in write-back mode, all updates become\n * write-back (i.e. asynchronously flushed) until the pending update queue is\n * fully flushed. This is required because updates must be applied in version\n * order. As with all pending work systems in zero-cache, multiple pending\n * updates are coalesced to reduce buildup of work.\n *\n * ### High level consistency\n *\n * Note that the above caching scheme only applies to the row data in `cvr.rows`\n * and corresponding `cvr.rowsVersion` tables. CVR metadata and query\n * information, on the other hand, are always committed before completing the\n * client poke. In this manner, the difference between the `version` column in\n * `cvr.instances` and the analogous column in `cvr.rowsVersion` determines\n * whether the data in the store is consistent, or whether it is awaiting a\n * pending update.\n *\n * The logic in {@link CVRStore#load()} takes this into account by loading both\n * the `cvr.instances` version and the `cvr.rowsVersion` version and checking\n * if they are in sync, waiting for a configurable delay until they are.\n *\n * ### Eventual conversion\n *\n * In the event of a continual stream of mutations (e.g. an animation-style\n * app), it is conceivable that the row record data be continually behind\n * the CVR metadata. In order to effect eventual convergence, a new view-syncer\n * signals the current view-syncer to stop updating by writing new `owner`\n * information to the `cvr.instances` row. This effectively stops the mutation\n * processing (in {@link CVRStore.#checkVersionAndOwnership}) so that the row\n * data can eventually catch up, allowing the new view-syncer to take over.\n *\n * Of course, there is the pathological situation in which a view-syncer\n * process crashes before the pending row updates are flushed. In this case,\n * the wait timeout will elapse and the CVR considered invalid.\n */\nexport class RowRecordCache {\n // The state in the #cache is always in sync with the CVR metadata\n // (i.e. cvr.instances). It may contain information that has not yet\n // been flushed to cvr.rows.\n #cache: Promise<CustomKeyMap<RowID, RowRecord>> | undefined;\n readonly #lc: LogContext;\n readonly #db: PostgresDB;\n readonly #schema: string;\n readonly #cvrID: string;\n readonly #failService: (e: unknown) => void;\n readonly #deferredRowFlushThreshold: number;\n readonly #setTimeout: typeof setTimeout;\n\n // Write-back cache state.\n readonly #pending = new CustomKeyMap<RowID, RowRecord | null>(rowIDString);\n #pendingRowsVersion: CVRVersion | null = null;\n #flushedRowsVersion: CVRVersion | null = null;\n #flushing: Resolver<void> | null = null;\n\n readonly #cvrFlushTime = getOrCreateHistogram('sync', 'cvr.flush-time', {\n description:\n 'Time to flush a CVR transaction. This includes both synchronous ' +\n 'and asynchronous flushes, distinguished by the flush.type attribute',\n unit: 's',\n });\n readonly #cvrRowsFlushed = getOrCreateCounter(\n 'sync',\n 'cvr.rows-flushed',\n 'Number of (changed) rows flushed to a CVR',\n );\n\n constructor(\n lc: LogContext,\n db: PostgresDB,\n shard: ShardID,\n cvrID: string,\n failService: (e: unknown) => void,\n deferredRowFlushThreshold = 100,\n setTimeoutFn = setTimeout,\n ) {\n this.#lc = lc;\n this.#db = db;\n this.#schema = cvrSchema(shard);\n this.#cvrID = cvrID;\n this.#failService = failService;\n this.#deferredRowFlushThreshold = deferredRowFlushThreshold;\n this.#setTimeout = setTimeoutFn;\n }\n\n recordSyncFlushStats(stats: CVRFlushStats, elapsedMs: number) {\n this.#cvrFlushTime.record(elapsedMs / 1000, {\n [FLUSH_TYPE_ATTRIBUTE]: 'sync',\n });\n if (stats.rowsDeferred === 0) {\n this.#cvrRowsFlushed.add(stats.rows);\n }\n }\n\n #recordAsyncFlushStats(rows: number, elapsedMs: number) {\n this.#cvrFlushTime.record(elapsedMs / 1000, {\n [FLUSH_TYPE_ATTRIBUTE]: 'async',\n });\n this.#cvrRowsFlushed.add(rows);\n }\n\n #cvr(table: string) {\n return this.#db(`${this.#schema}.${table}`);\n }\n\n async #ensureLoaded(): Promise<CustomKeyMap<RowID, RowRecord>> {\n if (this.#cache) {\n return this.#cache;\n }\n const start = Date.now();\n const r = resolver<CustomKeyMap<RowID, RowRecord>>();\n // Set this.#cache immediately (before await) so that only one db\n // query is made even if there are multiple callers.\n this.#cache = r.promise;\n\n const cache: CustomKeyMap<RowID, RowRecord> = new CustomKeyMap(rowIDString);\n for await (const rows of this.#db<RowsRow[]>`\n SELECT * FROM ${this.#cvr(`rows`)}\n WHERE \"clientGroupID\" = ${this.#cvrID} AND \"refCounts\" IS NOT NULL`\n // TODO(arv): Arbitrary page size\n .cursor(5000)) {\n for (const row of rows) {\n const rowRecord = rowsRowToRowRecord(row);\n cache.set(rowRecord.id, rowRecord);\n }\n }\n this.#lc.debug?.(\n `Loaded ${cache.size} row records in ${Date.now() - start} ms`,\n );\n r.resolve(cache);\n return this.#cache;\n }\n\n getRowRecords(): Promise<ReadonlyMap<RowID, RowRecord>> {\n return this.#ensureLoaded();\n }\n\n /**\n * Applies the `rowRecords` corresponding to the `rowsVersion`\n * to the cache, indicating whether the corresponding updates\n * (generated by {@link executeRowUpdates}) were `flushed`.\n *\n * If `flushed` is false, the RowRecordCache will flush the records\n * asynchronously.\n *\n * Note that `apply()` indicates that the CVR metadata associated with\n * the `rowRecords` was successfully committed, which essentially means\n * that this process has the unconditional right (and responsibility) of\n * following up with a flush of the `rowRecords`. In particular, the\n * commit of row records are not conditioned on the version or ownership\n * columns of the `cvr.instances` row.\n */\n async apply(\n rowRecords: Map<RowID, RowRecord | null>,\n rowsVersion: CVRVersion,\n flushed: boolean,\n ): Promise<number> {\n const cache = await this.#ensureLoaded();\n for (const [id, row] of rowRecords.entries()) {\n if (row === null || row.refCounts === null) {\n cache.delete(id);\n } else {\n cache.set(id, row);\n }\n if (!flushed) {\n this.#pending.set(id, row);\n }\n }\n this.#pendingRowsVersion = rowsVersion;\n // Initiate a flush if not already flushing.\n if (!flushed && this.#flushing === null) {\n this.#flushing = resolver();\n this.#setTimeout(() => this.#flush(), 0);\n }\n return cache.size;\n }\n\n async #flush() {\n const flushing = must(this.#flushing);\n try {\n while (this.#pendingRowsVersion !== this.#flushedRowsVersion) {\n const start = performance.now();\n\n const {rows, rowsVersion} = await this.#db.begin(\n Mode.READ_COMMITTED,\n tx => {\n disableStatementTimeout(tx);\n\n // Note: This code block is synchronous, guaranteeing that the\n // #pendingRowsVersion is consistent with the #pending rows.\n const rows = this.#pending.size;\n const rowsVersion = must(this.#pendingRowsVersion);\n // Awaiting all of the individual statements incurs too much\n // overhead. Instead, just catch and log exception(s); the outer\n // transaction will properly fail.\n void Promise.all(\n this.executeRowUpdates(tx, rowsVersion, this.#pending, 'force'),\n ).catch(e => this.#lc.error?.(`error flushing cvr rows`, e));\n\n this.#pending.clear();\n return {rows, rowsVersion};\n },\n );\n const elapsed = performance.now() - start;\n this.#lc.debug?.(\n `flushed ${rows} rows@${versionString(rowsVersion)} (${elapsed} ms)`,\n );\n this.#recordAsyncFlushStats(rows, elapsed);\n this.#flushedRowsVersion = rowsVersion;\n // Note: apply() may have called while the transaction was committing,\n // which will result in looping to commit the next #pendingRowsVersion.\n }\n this.#lc.debug?.(\n `up to date rows@${versionToNullableCookie(this.#flushedRowsVersion)}`,\n );\n flushing.resolve();\n this.#flushing = null;\n } catch (e) {\n flushing.reject(e);\n this.#failService(e);\n }\n }\n\n hasPendingUpdates() {\n return this.#flushing !== null;\n }\n\n /**\n * Returns a promise that resolves when all outstanding row-records\n * have been committed.\n */\n flushed(lc: LogContext): Promise<void> {\n if (this.#flushing) {\n lc.debug?.('awaiting pending row flush');\n return this.#flushing.promise;\n }\n return promiseVoid;\n }\n\n clear() {\n // Note: Only the #cache is cleared. #pending updates, on the other hand,\n // comprise canonical (i.e. already flushed) data and must be flushed\n // even if the snapshot of the present state (the #cache) is cleared.\n this.#cache = undefined;\n }\n\n async *catchupRowPatches(\n lc: LogContext,\n afterVersion: NullableCVRVersion,\n upToCVR: CVRSnapshot,\n current: CVRVersion,\n excludeQueryHashes: string[] = [],\n ): AsyncGenerator<RowsRow[], void, undefined> {\n if (cmpVersions(afterVersion, upToCVR.version) >= 0) {\n return;\n }\n\n const startMs = Date.now();\n const start = afterVersion ? versionString(afterVersion) : '';\n const end = versionString(upToCVR.version);\n lc.debug?.(`scanning row patches for clients from ${start}`);\n\n // Before accessing the CVR db, pending row records must be flushed.\n // Note that because catchupRowPatches() is called from within the\n // view syncer lock, this flush is guaranteed to complete since no\n // new CVR updates can happen while the lock is held.\n await this.flushed(lc);\n const flushMs = Date.now() - startMs;\n\n const reader = new TransactionPool(lc, Mode.READONLY).run(this.#db);\n try {\n // Verify that we are reading the right version of the CVR.\n await reader.processReadTask(tx =>\n checkVersion(tx, this.#schema, this.#cvrID, current),\n );\n\n const {query} = await reader.processReadTask(tx => {\n const query =\n excludeQueryHashes.length === 0\n ? tx<RowsRow[]>`SELECT * FROM ${this.#cvr('rows')}\n WHERE \"clientGroupID\" = ${this.#cvrID}\n AND \"patchVersion\" > ${start}\n AND \"patchVersion\" <= ${end}`\n : // Exclude rows that were already sent as part of query hydration.\n tx<RowsRow[]>`SELECT * FROM ${this.#cvr('rows')}\n WHERE \"clientGroupID\" = ${this.#cvrID}\n AND \"patchVersion\" > ${start}\n AND \"patchVersion\" <= ${end}\n AND (\"refCounts\" IS NULL OR NOT \"refCounts\" ?| ${excludeQueryHashes})`;\n return {query};\n });\n\n yield* query.cursor(10000);\n } finally {\n reader.setDone();\n }\n\n const totalMs = Date.now() - startMs;\n lc.debug?.(\n `finished row catchup (flush: ${flushMs} ms, total: ${totalMs} ms)`,\n );\n }\n\n executeRowUpdates(\n tx: PostgresTransaction,\n version: CVRVersion,\n rowUpdates: Map<RowID, RowRecord | null>,\n mode: 'allow-defer' | 'force',\n ): PendingQuery<Row[]>[] {\n if (\n mode === 'allow-defer' &&\n // defer if pending rows are being flushed\n (this.#flushing !== null ||\n // or if the new batch is above the limit.\n rowUpdates.size > this.#deferredRowFlushThreshold)\n ) {\n return [];\n }\n const rowsVersion = {\n clientGroupID: this.#cvrID,\n version: versionString(version),\n };\n const pending: PendingQuery<Row[]>[] = [\n tx`INSERT INTO ${this.#cvr('rowsVersion')} ${tx(rowsVersion)}\n ON CONFLICT (\"clientGroupID\") \n DO UPDATE SET ${tx(rowsVersion)}`.execute(),\n ];\n\n const rowRecordRows: RowsRow[] = [];\n for (const [id, row] of rowUpdates.entries()) {\n if (row === null) {\n pending.push(\n tx`\n DELETE FROM ${this.#cvr('rows')}\n WHERE \"clientGroupID\" = ${this.#cvrID}\n AND \"schema\" = ${id.schema}\n AND \"table\" = ${id.table}\n AND \"rowKey\" = ${id.rowKey}\n `.execute(),\n );\n } else {\n rowRecordRows.push(rowRecordToRowsRow(this.#cvrID, row));\n }\n }\n if (rowRecordRows.length) {\n pending.push(\n tx`\n INSERT INTO ${this.#cvr('rows')}(\n \"clientGroupID\", \"schema\", \"table\", \"rowKey\", \"rowVersion\", \"patchVersion\", \"refCounts\"\n ) SELECT\n \"clientGroupID\", \"schema\", \"table\", \"rowKey\", \"rowVersion\", \"patchVersion\", \"refCounts\"\n FROM json_to_recordset(${rowRecordRows}) AS x(\n \"clientGroupID\" TEXT,\n \"schema\" TEXT,\n \"table\" TEXT,\n \"rowKey\" JSONB,\n \"rowVersion\" TEXT,\n \"patchVersion\" TEXT,\n \"refCounts\" JSONB\n ) ON CONFLICT (\"clientGroupID\", \"schema\", \"table\", \"rowKey\")\n DO UPDATE SET \"rowVersion\" = excluded.\"rowVersion\",\n \"patchVersion\" = excluded.\"patchVersion\",\n \"refCounts\" = excluded.\"refCounts\"\n `.execute(),\n );\n this.#lc.debug?.(\n `flushing ${rowUpdates.size} rows (${rowRecordRows.length} inserts, ${\n rowUpdates.size - rowRecordRows.length\n } deletes)`,\n );\n }\n return pending;\n }\n}\n"],"names":["Mode.READ_COMMITTED","rows","rowsVersion","Mode.READONLY","query"],"mappings":"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"}
|
|
@@ -55,6 +55,7 @@ export declare class ViewSyncerService implements ViewSyncer, ActivityBasedServi
|
|
|
55
55
|
#private;
|
|
56
56
|
readonly id: string;
|
|
57
57
|
userQueryURL?: string | undefined;
|
|
58
|
+
userQueryHeaders?: Record<string, string> | undefined;
|
|
58
59
|
constructor(config: NormalizedZeroConfig, lc: LogContext, shard: ShardID, taskID: string, clientGroupID: string, cvrDb: PostgresDB, upstreamDb: PostgresDB | undefined, pipelineDriver: PipelineDriver, versionChanges: Subscription<ReplicaState>, drainCoordinator: DrainCoordinator, slowHydrateThreshold: number, inspectorDelegate: InspectorDelegate, customQueryTransformer: CustomQueryTransformer | undefined, keepaliveMs?: number, setTimeoutFn?: SetTimeout);
|
|
59
60
|
readyState(): Promise<'initialized' | 'draining'>;
|
|
60
61
|
run(): Promise<void>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"view-syncer.d.ts","sourceRoot":"","sources":["../../../../../../zero-cache/src/services/view-syncer/view-syncer.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"view-syncer.d.ts","sourceRoot":"","sources":["../../../../../../zero-cache/src/services/view-syncer/view-syncer.ts"],"names":[],"mappings":"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"}
|
|
@@ -58,6 +58,7 @@ class ViewSyncerService {
|
|
|
58
58
|
#slowHydrateThreshold;
|
|
59
59
|
#queryConfig;
|
|
60
60
|
userQueryURL;
|
|
61
|
+
userQueryHeaders;
|
|
61
62
|
// The ViewSyncerService is only started in response to a connection,
|
|
62
63
|
// so #lastConnectTime is always initialized to now(). This is necessary
|
|
63
64
|
// to handle race conditions in which, e.g. the replica is ready and the
|
|
@@ -185,6 +186,7 @@ class ViewSyncerService {
|
|
|
185
186
|
#getHeaderOptions(forwardCookie) {
|
|
186
187
|
return {
|
|
187
188
|
apiKey: this.#queryConfig.apiKey,
|
|
189
|
+
customHeaders: this.userQueryHeaders,
|
|
188
190
|
token: this.#authData?.raw,
|
|
189
191
|
cookie: forwardCookie ? this.#httpCookie : void 0
|
|
190
192
|
};
|
|
@@ -388,9 +390,10 @@ class ViewSyncerService {
|
|
|
388
390
|
`Picked auth token: ${JSON.stringify(this.#authData?.decoded)}`
|
|
389
391
|
);
|
|
390
392
|
this.#httpCookie = httpCookie;
|
|
391
|
-
const [, { userQueryURL }] = initConnectionMessage;
|
|
393
|
+
const [, { userQueryURL, userQueryHeaders }] = initConnectionMessage;
|
|
392
394
|
if (this.userQueryURL === void 0) {
|
|
393
395
|
this.userQueryURL = userQueryURL;
|
|
396
|
+
this.userQueryHeaders = userQueryHeaders;
|
|
394
397
|
} else {
|
|
395
398
|
if (this.userQueryURL !== userQueryURL) {
|
|
396
399
|
this.#lc.warn?.(
|
|
@@ -1233,6 +1236,8 @@ class ViewSyncerService {
|
|
|
1233
1236
|
cvr,
|
|
1234
1237
|
current
|
|
1235
1238
|
);
|
|
1239
|
+
configPatches.catch(() => {
|
|
1240
|
+
});
|
|
1236
1241
|
let rowPatchCount = 0;
|
|
1237
1242
|
for await (const rows of rowPatches) {
|
|
1238
1243
|
for (const row of rows) {
|