From 3698373e47e8f9e1078d1268a4eac2a90e8b41c0 Mon Sep 17 00:00:00 2001 From: John Yeo Date: Mon, 23 Mar 2026 21:08:51 +0000 Subject: [PATCH] chore: index experiments --- server/experiments/checkIndexes.ts | 46 +++++++++++ server/experiments/explainGetByStripeId.ts | 91 ++++++++++++++++++++++ 2 files changed, 137 insertions(+) create mode 100644 server/experiments/checkIndexes.ts create mode 100644 server/experiments/explainGetByStripeId.ts diff --git a/server/experiments/checkIndexes.ts b/server/experiments/checkIndexes.ts new file mode 100644 index 000000000..14d5d7e90 --- /dev/null +++ b/server/experiments/checkIndexes.ts @@ -0,0 +1,46 @@ +import { sql } from "drizzle-orm"; +import { initDrizzle } from "./experimentEnv"; + +// Run with `bun run experiments/checkIndexes.ts` + +const main = async () => { + const { db } = initDrizzle(); + + console.log("=== INDEX VALIDITY CHECK ===\n"); + + const results = await db.execute(sql` + SELECT c.relname, i.indisvalid, i.indisready, i.indislive, + pg_size_pretty(pg_relation_size(c.oid)) as size, + am.amname as type + FROM pg_index i + JOIN pg_class c ON c.oid = i.indexrelid + JOIN pg_am am ON am.oid = c.relam + WHERE c.relname IN ( + 'idx_referral_codes_internal_customer_id', + 'idx_referral_codes_internal_customer_id_v2', + 'idx_reward_redemptions_referral_code_id', + 'idx_reward_redemptions_referral_code_id_v2', + 'idx_customers_processor_gin', + 'idx_customers_processor_gin_v2', + 'idx_customers_processor_id', + 'idx_customers_processor_id_v2' + ) + ORDER BY c.relname + `); + + if (results.length === 0) { + console.log("No matching indexes found.\n"); + } + + for (const row of results) { + const r = row as Record; + const valid = r.indisvalid ? "VALID" : "INVALID"; + console.log( + `${valid} | ${r.relname} | type: ${r.type} | size: ${r.size}`, + ); + } + + process.exit(0); +}; + +await main(); diff --git a/server/experiments/explainGetByStripeId.ts b/server/experiments/explainGetByStripeId.ts new file mode 100644 index 000000000..773d9f259 --- /dev/null +++ b/server/experiments/explainGetByStripeId.ts @@ -0,0 +1,91 @@ +import { AppEnv, customers } from "@autumn/shared"; +import { and, eq, sql } from "drizzle-orm"; +import { initDrizzle, prodTestCustomerId, prodTestOrgId } from "./experimentEnv"; + +// Run with `bun run experiments/explainGetByStripeId.ts` + +const main = async () => { + const orgId = prodTestOrgId; + const env = AppEnv.Live; + + const { db } = initDrizzle(); + + // Grab a real stripe ID from the test customer + const testCus = await db.query.customers.findFirst({ + where: and( + eq(customers.id, prodTestCustomerId), + eq(customers.org_id, orgId), + eq(customers.env, env), + ), + }); + + const stripeId = testCus?.processor?.id; + if (!stripeId) { + console.error("Test customer has no processor.id"); + process.exit(1); + } + + console.log(`Using stripeId: ${stripeId}`); + console.log(`Org: ${orgId} | Env: ${env}\n`); + + // ── Check index validity ───────────────────────────────────────── + console.log("=== INDEX VALIDITY ===\n"); + const idxValid = await db.execute(sql` + SELECT c.relname, i.indisvalid, i.indisready, i.indislive, + pg_size_pretty(pg_relation_size(c.oid)) as size + FROM pg_index i + JOIN pg_class c ON c.oid = i.indexrelid + WHERE c.relname LIKE '%processor%' + `); + for (const row of idxValid) { + console.log(row); + } + + // ── Query with org/env (matches CusService.getByStripeId) ──────── + const withOrgEnv = sql` + SELECT * FROM customers + WHERE processor->>'id' = ${stripeId} + AND org_id = ${orgId} + AND env = ${env} + LIMIT 1 + `; + + console.log("\n=== QUERY: processor->>'id' + org/env ===\n"); + + const start1 = performance.now(); + const result1 = await db.execute(withOrgEnv); + const elapsed1 = performance.now() - start1; + console.log(`Rows: ${result1.length} | Time: ${elapsed1.toFixed(2)}ms\n`); + + const explain1 = await db.execute( + sql`EXPLAIN (ANALYZE, BUFFERS, FORMAT TEXT) ${withOrgEnv}`, + ); + for (const row of explain1) { + console.log((row as Record)["QUERY PLAN"]); + } + + // ── Query without org/env (isolate expression index usage) ─────── + const withoutOrgEnv = sql` + SELECT * FROM customers + WHERE processor->>'id' = ${stripeId} + LIMIT 1 + `; + + console.log("\n=== QUERY: processor->>'id' only ===\n"); + + const start2 = performance.now(); + const result2 = await db.execute(withoutOrgEnv); + const elapsed2 = performance.now() - start2; + console.log(`Rows: ${result2.length} | Time: ${elapsed2.toFixed(2)}ms\n`); + + const explain2 = await db.execute( + sql`EXPLAIN (ANALYZE, BUFFERS, FORMAT TEXT) ${withoutOrgEnv}`, + ); + for (const row of explain2) { + console.log((row as Record)["QUERY PLAN"]); + } + + process.exit(0); +}; + +await main();