feat: improve workspace list perf

This commit is contained in:
DarkSky
2025-12-31 22:59:52 +08:00
parent bc03fab649
commit 20c4951847
4 changed files with 43 additions and 4 deletions
@@ -0,0 +1,36 @@
-- Add persisted size column for snapshots (nullable for backwards compatibility)
ALTER TABLE "snapshots" ADD COLUMN IF NOT EXISTS "size" BIGINT;
-- Ensure size is populated on insert/update even for older app versions
CREATE OR REPLACE FUNCTION snapshots_set_size() RETURNS TRIGGER AS $$
BEGIN
NEW."size" := COALESCE(NEW."size", octet_length(NEW."blob"));
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
DROP TRIGGER IF EXISTS snapshots_set_size_before_write ON "snapshots";
CREATE TRIGGER snapshots_set_size_before_write
BEFORE INSERT OR UPDATE OF "blob" ON "snapshots"
FOR EACH ROW
EXECUTE FUNCTION snapshots_set_size();
-- Backfill existing rows
UPDATE "snapshots"
SET "size" = octet_length("blob")
WHERE "size" IS NULL;
-- Support faster admin aggregates
CREATE INDEX IF NOT EXISTS "idx_wur_owner" ON "workspace_user_permissions" ("workspace_id")
WHERE "type" = 99 AND "status" = 'Accepted'::"WorkspaceMemberStatus";
CREATE INDEX IF NOT EXISTS "idx_wur_workspace" ON "workspace_user_permissions" ("workspace_id");
CREATE INDEX IF NOT EXISTS "idx_blobs_active" ON "blobs" ("workspace_id")
WHERE "deleted_at" IS NULL AND "status" = 'completed';
CREATE INDEX IF NOT EXISTS "idx_workspace_pages_public" ON "workspace_pages" ("workspace_id")
WHERE "public" = TRUE;
CREATE INDEX IF NOT EXISTS "idx_workspace_features_activated" ON "workspace_features" ("workspace_id")
WHERE "activated" = TRUE;
+2
View File
@@ -273,6 +273,8 @@ model Snapshot {
workspaceId String @map("workspace_id") @db.VarChar
id String @default(uuid()) @map("guid") @db.VarChar
blob Bytes @db.ByteA
// persisted size of blob to avoid summing over bytea
size BigInt? @map("size") @db.BigInt
state Bytes? @db.ByteA
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3)
// the `updated_at` field will not record the time of record changed,
+4 -3
View File
@@ -149,6 +149,7 @@ export class DocModel extends BaseModel {
async upsert(doc: Doc) {
const { spaceId, docId, blob, timestamp, editorId } = doc;
const updatedAt = new Date(timestamp);
const size = blob.byteLength ?? blob.length;
// CONCERNS:
// i. Because we save the real user's last seen action time as `updatedAt`,
// it's possible to simply compare the `updatedAt` to determine if the snapshot is older than the one we are going to save.
@@ -158,10 +159,10 @@ export class DocModel extends BaseModel {
// where: { workspaceId_id: {}, updatedAt: { lt: updatedAt } }
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
const result: { updatedAt: Date }[] = await this.db.$queryRaw`
INSERT INTO "snapshots" ("workspace_id", "guid", "blob", "created_at", "updated_at", "created_by", "updated_by")
VALUES (${spaceId}, ${docId}, ${blob}, DEFAULT, ${updatedAt}, ${editorId}, ${editorId})
INSERT INTO "snapshots" ("workspace_id", "guid", "blob", "size", "created_at", "updated_at", "created_by", "updated_by")
VALUES (${spaceId}, ${docId}, ${blob}, ${size}, DEFAULT, ${updatedAt}, ${editorId}, ${editorId})
ON CONFLICT ("workspace_id", "guid")
DO UPDATE SET "blob" = ${blob}, "updated_at" = ${updatedAt}, "updated_by" = ${editorId}
DO UPDATE SET "blob" = ${blob}, "size" = ${size}, "updated_at" = ${updatedAt}, "updated_by" = ${editorId}
WHERE "snapshots"."workspace_id" = ${spaceId} AND "snapshots"."guid" = ${docId} AND "snapshots"."updated_at" <= ${updatedAt}
RETURNING "snapshots"."workspace_id" as "workspaceId", "snapshots"."guid" as "id", "snapshots"."updated_at" as "updatedAt"
`;
@@ -211,7 +211,7 @@ export class WorkspaceModel extends BaseModel {
),
snapshot_stats AS (
SELECT workspace_id,
SUM(octet_length(blob)) AS snapshot_size,
SUM(COALESCE(size, octet_length(blob))) AS snapshot_size,
COUNT(*) AS snapshot_count
FROM snapshots
GROUP BY workspace_id