perf(db): index thread reply keyset pagination

Co-authored-by: npub1t2tgm7d8f995uqvmnm8h88sg3wnpp9a5xysjf6dg3tjmgt3ltulqdp8ehr <5a968df9a7494b4e019b9ecf739e088ba61097b4312124e9a88ae5b42e3f5f3e@sprout-oss.stage.blox.sqprod.co>
Signed-off-by: npub1t2tgm7d8f995uqvmnm8h88sg3wnpp9a5xysjf6dg3tjmgt3ltulqdp8ehr <5a968df9a7494b4e019b9ecf739e088ba61097b4312124e9a88ae5b42e3f5f3e@sprout-oss.stage.blox.sqprod.co>
Co-authored-by: coder 0 <d97ebdbb198c7237c94f84ea8bb8a73583ea067407eebd0062abbb3962527fb1@buzz.block.builderlab.xyz>
Signed-off-by: coder 0 <d97ebdbb198c7237c94f84ea8bb8a73583ea067407eebd0062abbb3962527fb1@buzz.block.builderlab.xyz>
This commit is contained in:
npub1t2tgm7d8f995uqvmnm8h88sg3wnpp9a5xysjf6dg3tjmgt3ltulqdp8ehr
2026-07-12 21:47:18 -04:00
committed by coder 0
parent 17d2147eca
commit 173b2d21dd
3 changed files with 43 additions and 1 deletions

View File

@@ -625,7 +625,7 @@ mod tests {
let mut migrations: Vec<_> = MIGRATOR.iter().collect();
migrations.sort_by_key(|migration| migration.version);
assert_eq!(migrations.len(), 31);
assert_eq!(migrations.len(), 32);
assert_eq!(migrations[0].version, 1);
assert_eq!(&*migrations[0].description, "initial schema");
assert!(migrations[0]
@@ -1057,6 +1057,25 @@ mod tests {
.as_str()
.contains("error_code"));
assert!(include_str!("../../../schema/schema.sql").contains("error_code TEXT"));
// Thread subtree pagination uses the full tenant/root/keyset tuple. Keep
// the covering index additive so brownfield migration checksums remain
// stable.
assert_eq!(migrations[31].version, 32);
assert!(migrations[31]
.sql
.as_str()
.contains("CREATE INDEX IF NOT EXISTS idx_thread_metadata_root_keyset"));
assert!(migrations[31]
.sql
.as_str()
.contains("(community_id, root_event_id, event_created_at, event_id)"));
assert!(!migrations[0]
.sql
.as_str()
.contains("idx_thread_metadata_root_keyset"));
assert!(include_str!("../../../schema/schema.sql")
.contains("CREATE INDEX idx_thread_metadata_root_keyset"));
}
#[test]

View File

@@ -0,0 +1,21 @@
-- ── Thread reply keyset pagination index ─────────────────────────────────────
-- Thread subtree reads filter by tenant and root, then page in ascending
-- (event_created_at, event_id) order. Include the keyset columns so PostgreSQL
-- can satisfy both the filter and ordering from one index scan.
--
-- Lock note: built without CONCURRENTLY because sqlx runs each migration inside
-- a transaction and CREATE INDEX CONCURRENTLY cannot run in one. This takes a
-- SHARE lock on `thread_metadata` (blocking writes, not reads) for the duration
-- of the build. `thread_metadata` is on the event insertion path and may be
-- large on brownfield databases, so operators should pre-build it by hand before
-- deploying this migration:
--
-- CREATE INDEX CONCURRENTLY idx_thread_metadata_root_keyset
-- ON thread_metadata (community_id, root_event_id, event_created_at, event_id);
--
-- IF NOT EXISTS then makes this migration a no-op on that database.
--
-- Additive migration: previously applied files must not change checksum.
CREATE INDEX IF NOT EXISTS idx_thread_metadata_root_keyset
ON thread_metadata (community_id, root_event_id, event_created_at, event_id);

View File

@@ -531,6 +531,8 @@ CREATE TABLE thread_metadata (
CREATE INDEX idx_thread_metadata_parent ON thread_metadata (community_id, parent_event_id);
CREATE INDEX idx_thread_metadata_root ON thread_metadata (community_id, root_event_id);
CREATE INDEX idx_thread_metadata_root_keyset
ON thread_metadata (community_id, root_event_id, event_created_at, event_id);
CREATE INDEX idx_thread_metadata_channel_depth
ON thread_metadata (community_id, channel_id, depth, event_created_at);
CREATE INDEX idx_thread_metadata_event_id ON thread_metadata (community_id, event_id);