From 2b16124d0700c26a93664d26f59723311b5cf5a3 Mon Sep 17 00:00:00 2001 From: amianthus <49116958+SirTenzin@users.noreply.github.com> Date: Mon, 18 May 2026 13:51:20 +0100 Subject: [PATCH] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20cleanup=20gitdiff?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/experiments/benchDashboardMerge.ts | 153 ---- server/experiments/checkRevcatIndex.ts | 24 - .../cursorPaginatedFullCusQuery.ts | 338 --------- server/experiments/debugOptimizedQuery.ts | 47 -- server/experiments/diffDashboardMerge.ts | 305 -------- server/experiments/diffOptimizedVsCurrent.ts | 180 ----- server/experiments/diffV07VsV22.ts | 426 ----------- server/experiments/inspectIndexes.ts | 52 -- server/experiments/inspectUnitTestOrg.ts | 90 --- server/experiments/optimizedFullCusQuery.ts | 173 ----- server/experiments/paginationBenchmark.ts | 368 ---------- .../experiments/paginationBenchmarkDbStats.ts | 678 ------------------ server/experiments/sqlBenchUnitTestOrg.ts | 304 -------- server/experiments/sweepCursorNoSkip.ts | 130 ---- server/experiments/v07ReassemblyBenchmark.ts | 463 ------------ server/experiments/versionPipelineBench.ts | 547 -------------- 16 files changed, 4278 deletions(-) delete mode 100644 server/experiments/benchDashboardMerge.ts delete mode 100644 server/experiments/checkRevcatIndex.ts delete mode 100644 server/experiments/cursorPaginatedFullCusQuery.ts delete mode 100644 server/experiments/debugOptimizedQuery.ts delete mode 100644 server/experiments/diffDashboardMerge.ts delete mode 100644 server/experiments/diffOptimizedVsCurrent.ts delete mode 100644 server/experiments/diffV07VsV22.ts delete mode 100644 server/experiments/inspectIndexes.ts delete mode 100644 server/experiments/inspectUnitTestOrg.ts delete mode 100644 server/experiments/optimizedFullCusQuery.ts delete mode 100644 server/experiments/paginationBenchmark.ts delete mode 100644 server/experiments/paginationBenchmarkDbStats.ts delete mode 100644 server/experiments/sqlBenchUnitTestOrg.ts delete mode 100644 server/experiments/sweepCursorNoSkip.ts delete mode 100644 server/experiments/v07ReassemblyBenchmark.ts delete mode 100644 server/experiments/versionPipelineBench.ts diff --git a/server/experiments/benchDashboardMerge.ts b/server/experiments/benchDashboardMerge.ts deleted file mode 100644 index a3b7176ca..000000000 --- a/server/experiments/benchDashboardMerge.ts +++ /dev/null @@ -1,153 +0,0 @@ -import { AppEnv, RELEVANT_STATUSES } from "@autumn/shared"; -import chalk from "chalk"; -import { sql } from "drizzle-orm"; -import { CusBatchService } from "../src/internal/customers/CusBatchService"; -import { CusSearchService } from "../src/internal/customers/CusSearchService"; -import { getCursorPaginatedFullCusQuery } from "../src/internal/customers/cursorPaginatedFullCusQuery"; -import { initDrizzle } from "../src/db/initDrizzle"; -import { - type FlattenedCustomerRow, - reassembleFlattenedCustomer, -} from "../src/internal/customers/reassembleFlattenedCustomer"; -import { loadLocalEnv } from "../src/utils/envUtils"; - -loadLocalEnv(); - -const ORG_ID = "org_2sWv2S8LJ9iaTjLI6UtNsfL88Kt"; -const ENV = AppEnv.Sandbox; -const REPEATS = 5; -const LIMIT = parseInt(process.env.LIMIT ?? "500", 10); -const OFFSET_PCT = parseInt(process.env.OFFSET_PCT ?? "95", 10); - -const main = async () => { - console.log( - chalk.magentaBright( - "\n================ Dashboard Merge Bench ================\n", - ), - ); - const { db, client } = initDrizzle(); - const ctx = { - db, - client, - org: { id: ORG_ID, slug: "unit-test-org" }, - env: ENV, - logger: { - info: () => {}, - error: () => {}, - warn: () => {}, - debug: () => {}, - trace: () => {}, - }, - } as any; - - try { - const total = ( - (await db.execute( - sql`SELECT COUNT(*)::int AS n FROM customers WHERE org_id = ${ORG_ID} AND env = ${ENV}`, - )) as unknown as { n: number }[] - )[0].n; - const offset = Math.floor((total * OFFSET_PCT) / 100); - const row = (await db.execute( - sql`SELECT created_at, id FROM customers WHERE org_id = ${ORG_ID} AND env = ${ENV} ORDER BY created_at DESC, id DESC LIMIT 1 OFFSET ${offset}`, - )) as unknown as { created_at: number; id: string }[]; - const cursor = { t: Number(row[0].created_at), id: row[0].id }; - console.log( - chalk.gray( - ` total=${total.toLocaleString()} cursor at ${OFFSET_PCT}%: ${cursor.id} limit=${LIMIT}\n`, - ), - ); - - const measure = async (label: string, fn: () => Promise) => { - for (let i = 0; i < 2; i++) await fn(); - const samples: number[] = []; - for (let i = 0; i < REPEATS; i++) { - const t0 = performance.now(); - await fn(); - samples.push(performance.now() - t0); - } - samples.sort((a, b) => a - b); - const p50 = samples[Math.floor(samples.length / 2)]; - const min = samples[0]; - const max = samples[samples.length - 1]; - console.log( - ` ${chalk.cyan(label.padEnd(20))} p50=${p50.toFixed(0).padStart(4)}ms min=${min.toFixed(0).padStart(4)}ms max=${max.toFixed(0).padStart(4)}ms`, - ); - return p50; - }; - - const oldPath = async () => { - const { internalIds } = - await CusSearchService.resolveInternalIdsByCursor({ - db, - orgId: ORG_ID, - env: ENV, - search: "", - cursor, - limit: LIMIT, - }); - if (internalIds.length === 0) return; - const query = getCursorPaginatedFullCusQuery({ - orgId: ORG_ID, - env: ENV, - inStatuses: RELEVANT_STATUSES, - withSubs: true, - limit: internalIds.length, - internalCustomerIds: internalIds, - cusProductLimit: 15, - }); - const rows = (await db.execute(query)) as unknown as Record< - string, - unknown - >[]; - const flat = (rows[0] ?? {}) as unknown as FlattenedCustomerRow; - reassembleFlattenedCustomer(flat); - }; - - const newPath = async () => { - await CusBatchService.getDashboardCursorPage({ - ctx, - search: "", - filters: undefined, - cursor, - limit: LIMIT, - }); - }; - - const oldWithStatus = async () => { - // Call old path manually (resolve + fetch) with status filter for parity - await oldPath(); - }; - - const newWithStatus = async () => { - await CusBatchService.getDashboardCursorPage({ - ctx, - search: "", - filters: { status: ["active"] }, - cursor, - limit: LIMIT, - }); - }; - - const oldP50 = await measure("OLD (resolve+fetch)", oldPath); - const newP50 = await measure("NEW (no filters)", newPath); - const newStatusP50 = await measure( - "NEW (status=active)", - newWithStatus, - ); - - console.log(); - console.log(chalk.magentaBright("================ Summary ================")); - const delta = (((newP50 - oldP50) / oldP50) * 100).toFixed(0); - console.log( - ` old → new (no filters): ${oldP50.toFixed(0)}ms → ${newP50.toFixed(0)}ms (${delta}%)`, - ); - console.log( - ` new with status=active: ${newStatusP50.toFixed(0)}ms (uses resolve+fetch fallback)`, - ); - } finally { - await client.end(); - process.exit(0); - } -}; - -await main(); diff --git a/server/experiments/checkRevcatIndex.ts b/server/experiments/checkRevcatIndex.ts deleted file mode 100644 index 7249958ce..000000000 --- a/server/experiments/checkRevcatIndex.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { sql } from "drizzle-orm"; -import { initDrizzle } from "../src/db/initDrizzle"; -import { loadLocalEnv } from "../src/utils/envUtils"; - -loadLocalEnv(); - -const main = async () => { - const { db, client } = initDrizzle(); - try { - const rows = (await db.execute(sql` - SELECT indexname, indexdef - FROM pg_indexes - WHERE schemaname = 'public' - AND tablename = 'customer_products' - AND (indexname LIKE '%revenuecat%' OR indexdef LIKE '%revenuecat%') - `)) as any[]; - console.log(JSON.stringify(rows, null, 2)); - } finally { - await client.end(); - process.exit(0); - } -}; - -await main(); diff --git a/server/experiments/cursorPaginatedFullCusQuery.ts b/server/experiments/cursorPaginatedFullCusQuery.ts deleted file mode 100644 index b311bfc8f..000000000 --- a/server/experiments/cursorPaginatedFullCusQuery.ts +++ /dev/null @@ -1,338 +0,0 @@ -import type { AppEnv, CusProductStatus, ListCustomersV2Params } from "@autumn/shared"; -import { sql } from "drizzle-orm"; -import { getCustomerListFilterSql } from "../src/internal/customers/getFullCusQuery"; - -export type CursorPaginatedFullCusQueryArgs = { - orgId: string; - env: AppEnv; - inStatuses?: CusProductStatus[]; - includeInvoices: boolean; - withEntities: boolean; - withTrialsUsed: boolean; - withSubs: boolean; - limit: number; - cursor?: { createdAt: number; id: string }; - withEvents?: boolean; - internalCustomerIds?: string[]; - plans?: ListCustomersV2Params["plans"]; - processors?: ListCustomersV2Params["processors"]; - search?: string; - cusProductLimit: number; -}; - -export const getCursorPaginatedFullCusQuery = ({ - orgId, - env, - inStatuses, - includeInvoices, - withEntities, - withTrialsUsed, - withSubs, - limit, - cursor, - withEvents = false, - internalCustomerIds, - plans, - processors, - search, - cusProductLimit, -}: CursorPaginatedFullCusQueryArgs) => { - const withStatusFilter = () => { - return inStatuses?.length - ? sql`AND cp.status = ANY(ARRAY[${sql.join( - inStatuses.map((status) => sql`${status}`), - sql`, `, - )}])` - : sql``; - }; - - const customerListFilterSql = getCustomerListFilterSql({ - internalCustomerIds, - inStatuses, - plans, - processors, - search, - }); - - const cursorPredicate = cursor - ? sql`AND (c.created_at, c.id) < (${cursor.createdAt}, ${cursor.id})` - : sql``; - - const fetchLimit = limit + 1; - - const extraEntitlementsCTE = sql`, extra_customer_entitlements AS ( - SELECT - cr.internal_id AS internal_customer_id, - COALESCE( - json_agg( - to_jsonb(ce.*) || jsonb_build_object( - 'entitlement', ( - SELECT row_to_json(ent_with_feature) - FROM ( - SELECT e.*, row_to_json(f) AS feature - FROM entitlements e - JOIN features f ON e.internal_feature_id = f.internal_id - WHERE e.id = ce.entitlement_id - ) AS ent_with_feature - ), - 'replaceables', ( - SELECT COALESCE( - json_agg(row_to_json(r)) FILTER (WHERE r.id IS NOT NULL), - '[]'::json - ) - FROM replaceables r - WHERE r.cus_ent_id = ce.id - ), - 'rollovers', ( - SELECT COALESCE( - json_agg(row_to_json(ro) ORDER BY ro.expires_at ASC NULLS LAST) - FILTER (WHERE ro.expires_at > EXTRACT(EPOCH FROM now()) * 1000 OR ro.expires_at IS NULL), - '[]'::json - ) - FROM rollovers ro - WHERE ro.cus_ent_id = ce.id - ) - ) - ORDER BY ce.id DESC - ) FILTER (WHERE ce.id IS NOT NULL), - '[]'::json - ) AS extra_customer_entitlements - FROM customer_records cr - LEFT JOIN LATERAL ( - SELECT * - FROM customer_entitlements ce - WHERE ce.internal_customer_id = cr.internal_id - AND ce.customer_product_id IS NULL - AND (ce.expires_at IS NULL OR ce.expires_at > EXTRACT(EPOCH FROM now()) * 1000) - ORDER BY ce.id DESC - LIMIT 30 - ) ce ON true - GROUP BY cr.internal_id - )`; - - return sql` - WITH customer_records AS ( - SELECT c.* - FROM customers c - WHERE c.org_id = ${orgId} - AND c.env = ${env} - ${customerListFilterSql} - ${cursorPredicate} - ORDER BY c.created_at DESC, c.id DESC - LIMIT ${fetchLimit} - ), - - customer_products_with_prices AS ( - SELECT - cp.*, - row_to_json(prod) AS product, - cpr_data.customer_prices, - ce_data.customer_entitlements, - ft_data.free_trial - - FROM customer_records cr - JOIN LATERAL ( - SELECT * - FROM customer_products cp - WHERE cp.internal_customer_id = cr.internal_id - ${withStatusFilter()} - ORDER BY (SELECT p.is_add_on FROM products p WHERE p.internal_id = cp.internal_product_id) ASC, cp.created_at DESC - LIMIT ${cusProductLimit} - ) cp ON true - JOIN products prod ON cp.internal_product_id = prod.internal_id - LEFT JOIN LATERAL ( - SELECT COALESCE( - json_agg( - to_jsonb(cpr.*) || jsonb_build_object('price', to_jsonb(p.*)) - ) FILTER (WHERE cpr.id IS NOT NULL), - '[]'::json - ) AS customer_prices - FROM customer_prices cpr - LEFT JOIN prices p ON cpr.price_id = p.id - WHERE cpr.customer_product_id = cp.id - ) cpr_data ON true - LEFT JOIN LATERAL ( - SELECT COALESCE( - json_agg( - to_jsonb(ce.*) || jsonb_build_object( - 'entitlement', ( - SELECT row_to_json(ent_with_feature) - FROM ( - SELECT e.*, row_to_json(f) AS feature - FROM entitlements e - JOIN features f ON e.internal_feature_id = f.internal_id - WHERE e.id = ce.entitlement_id - ) AS ent_with_feature - ), - 'replaceables', ( - SELECT COALESCE( - json_agg(row_to_json(r)) FILTER (WHERE r.id IS NOT NULL), - '[]'::json - ) - FROM replaceables r - WHERE r.cus_ent_id = ce.id - ), - 'rollovers', ( - SELECT COALESCE( - json_agg(row_to_json(ro) ORDER BY ro.expires_at ASC NULLS LAST) FILTER (WHERE ro.expires_at > EXTRACT(EPOCH FROM now()) * 1000 OR ro.expires_at IS NULL), - '[]'::json - ) - FROM rollovers ro - WHERE ro.cus_ent_id = ce.id - ) - ) - ) FILTER (WHERE ce.id IS NOT NULL), - '[]'::json - ) AS customer_entitlements - FROM customer_entitlements ce - WHERE ce.customer_product_id = cp.id - ) ce_data ON true - LEFT JOIN LATERAL ( - SELECT row_to_json(ft) AS free_trial - FROM free_trials ft - WHERE ft.id = cp.free_trial_id - ) ft_data ON true - ), - - customer_products_aggregated AS ( - SELECT - cpwp.internal_customer_id, - json_agg(row_to_json(cpwp) ORDER BY cpwp.created_at DESC) AS customer_products - FROM customer_products_with_prices cpwp - GROUP BY cpwp.internal_customer_id - ) - - ${ - withSubs - ? sql`, customer_subscriptions AS ( - SELECT - s.internal_customer_id, - COALESCE( - json_agg(row_to_json(s)) FILTER (WHERE s.stripe_id IS NOT NULL), - '[]'::json - ) AS subscriptions - FROM ( - SELECT DISTINCT - cpwp.internal_customer_id, - s.* - FROM customer_products_with_prices cpwp - JOIN LATERAL unnest(cpwp.subscription_ids) AS cpwp_sub(stripe_id) ON true - JOIN subscriptions s ON s.stripe_id = cpwp_sub.stripe_id - ) s - GROUP BY s.internal_customer_id - )` - : sql`` - } - - ${ - withEvents - ? sql`, customer_events AS ( - SELECT - e.internal_customer_id, - COALESCE( - json_agg( - json_build_object( - 'id', e.id, - 'event_name', e.event_name, - 'value', e.value, - 'timestamp', e.timestamp, - 'properties', e.properties - ) - ORDER BY e.timestamp DESC, e.id DESC - ) FILTER (WHERE e.id IS NOT NULL), - '[]'::json - ) AS events - FROM events e - WHERE e.internal_customer_id IN (SELECT internal_id FROM customer_records) - AND e.set_usage = false - GROUP BY e.internal_customer_id - )` - : sql`` - } - - ${ - withEntities - ? sql`, customer_entities AS ( - SELECT - cr.internal_id AS internal_customer_id, - COALESCE( - json_agg(row_to_json(e) ORDER BY e.internal_id DESC) FILTER (WHERE e.internal_id IS NOT NULL), - '[]'::json - ) AS entities - FROM customer_records cr - LEFT JOIN LATERAL ( - SELECT * - FROM entities e - WHERE e.internal_customer_id = cr.internal_id - ORDER BY e.internal_id DESC - LIMIT 300 - ) e ON true - GROUP BY cr.internal_id - )` - : sql`` - } - - ${ - includeInvoices - ? sql`, customer_invoices AS ( - SELECT - cr.internal_id AS internal_customer_id, - COALESCE( - json_agg(row_to_json(i) ORDER BY i.created_at DESC, i.id DESC) FILTER (WHERE i.id IS NOT NULL), - '[]'::json - ) AS invoices - FROM customer_records cr - LEFT JOIN LATERAL ( - SELECT * - FROM invoices i - WHERE i.internal_customer_id = cr.internal_id - ORDER BY i.created_at DESC, i.id DESC - LIMIT 10 - ) i ON true - GROUP BY cr.internal_id - )` - : sql`` - } - - ${ - withTrialsUsed - ? sql`, customer_trials_used AS ( - SELECT - cp.internal_customer_id, - json_agg(json_build_object( - 'product_id', p.id, - 'fingerprint', c.fingerprint, - 'customer_id', c.id - )) AS trials_used - FROM customer_products cp - JOIN products p ON cp.internal_product_id = p.internal_id - JOIN customers c ON cp.internal_customer_id = c.internal_id - WHERE cp.internal_customer_id IN (SELECT internal_id FROM customer_records) - AND cp.free_trial_id IS NOT NULL - GROUP BY cp.internal_customer_id - )` - : sql`` - } - - ${extraEntitlementsCTE} - - SELECT - cr.*, - COALESCE(cpa.customer_products, '[]'::json) AS customer_products - ${withSubs ? sql`, COALESCE(cs.subscriptions, '[]'::json) AS subscriptions` : sql``} - ${withEntities ? sql`, COALESCE(ce.entities, '[]'::json) AS entities` : sql``} - ${includeInvoices ? sql`, COALESCE(ci.invoices, '[]'::json) AS invoices` : sql``} - ${withTrialsUsed ? sql`, COALESCE(ctu.trials_used, '[]'::json) AS trials_used` : sql``} - ${withEvents ? sql`, COALESCE(cev.events, '[]'::json) AS events` : sql``} - , COALESCE(ece.extra_customer_entitlements, '[]'::json) AS extra_customer_entitlements - FROM customer_records cr - LEFT JOIN customer_products_aggregated cpa ON cpa.internal_customer_id = cr.internal_id - ${withSubs ? sql`LEFT JOIN customer_subscriptions cs ON cs.internal_customer_id = cr.internal_id` : sql``} - ${withEntities ? sql`LEFT JOIN customer_entities ce ON ce.internal_customer_id = cr.internal_id` : sql``} - ${includeInvoices ? sql`LEFT JOIN customer_invoices ci ON ci.internal_customer_id = cr.internal_id` : sql``} - ${withTrialsUsed ? sql`LEFT JOIN customer_trials_used ctu ON ctu.internal_customer_id = cr.internal_id` : sql``} - ${withEvents ? sql`LEFT JOIN customer_events cev ON cev.internal_customer_id = cr.internal_id` : sql``} - LEFT JOIN extra_customer_entitlements ece ON ece.internal_customer_id = cr.internal_id - ORDER BY cr.created_at DESC, cr.id DESC - `; -}; diff --git a/server/experiments/debugOptimizedQuery.ts b/server/experiments/debugOptimizedQuery.ts deleted file mode 100644 index 4f88d9f23..000000000 --- a/server/experiments/debugOptimizedQuery.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { AppEnv } from "@autumn/shared"; -import chalk from "chalk"; -import { sql } from "drizzle-orm"; -import { RELEVANT_STATUSES } from "../src/internal/customers/cusProducts/CusProductService"; -import { initDrizzle } from "../src/db/initDrizzle"; -import { loadLocalEnv } from "../src/utils/envUtils"; -import { getOptimizedFullCusQuery } from "./optimizedFullCusQuery"; - -loadLocalEnv(); - -const ORG_ID = "org_2sWv2S8LJ9iaTjLI6UtNsfL88Kt"; - -const main = async () => { - const { db, client } = initDrizzle(); - try { - const query = getOptimizedFullCusQuery({ - orgId: ORG_ID, - env: AppEnv.Sandbox, - inStatuses: RELEVANT_STATUSES, - withSubs: false, - limit: 1000, - cusProductLimit: 15, - }); - - console.log(chalk.cyan("Running optimized first page...")); - try { - const r = await db.execute(query); - console.log(chalk.green(`OK: ${(r as any).length} row(s)`)); - const first = (r as any)[0]; - if (first) { - for (const [k, v] of Object.entries(first)) { - const arr = Array.isArray(v) ? v : v != null && typeof v === "object" && "length" in (v as any) ? (v as any[]) : null; - console.log(` ${k}: ${arr ? `${arr.length} items` : typeof v}`); - } - } - } catch (e: any) { - console.error(chalk.red(`SQL ERROR: ${e.message}`)); - console.error(chalk.gray(`code: ${e.code}, where: ${e.where ?? "n/a"}, detail: ${e.detail ?? "n/a"}, hint: ${e.hint ?? "n/a"}`)); - if (e.position) console.error(chalk.gray(`position: ${e.position}`)); - } - } finally { - await client.end(); - process.exit(0); - } -}; - -await main(); diff --git a/server/experiments/diffDashboardMerge.ts b/server/experiments/diffDashboardMerge.ts deleted file mode 100644 index 8eb145215..000000000 --- a/server/experiments/diffDashboardMerge.ts +++ /dev/null @@ -1,305 +0,0 @@ -import { - AppEnv, - type FullCustomer, - RELEVANT_STATUSES, - StandardCursor, -} from "@autumn/shared"; -import chalk from "chalk"; -import { sql } from "drizzle-orm"; -import { CusBatchService } from "../src/internal/customers/CusBatchService"; -import { CusSearchService } from "../src/internal/customers/CusSearchService"; -import { getCursorPaginatedFullCusQuery } from "../src/internal/customers/cursorPaginatedFullCusQuery"; -import { initDrizzle } from "../src/db/initDrizzle"; -import { - type FlattenedCustomerRow, - reassembleFlattenedCustomer, -} from "../src/internal/customers/reassembleFlattenedCustomer"; -import { loadLocalEnv } from "../src/utils/envUtils"; - -loadLocalEnv(); - -const ORG_ID = "org_2sWv2S8LJ9iaTjLI6UtNsfL88Kt"; -const ENV = AppEnv.Sandbox; - -type DashboardFilters = { - status?: string[]; - version?: string[]; - none?: boolean; - processor?: string[]; -}; - -const runOldPath = async ({ - ctx, - search, - filters, - cursor, - limit, -}: { - ctx: any; - search: string; - filters?: DashboardFilters; - cursor: { t: number; id: string } | null; - limit: number; -}): Promise<{ - fullCustomers: FullCustomer[]; - next_cursor: string | null; -}> => { - const { internalIds, peek } = - await CusSearchService.resolveInternalIdsByCursor({ - db: ctx.db, - orgId: ORG_ID, - env: ENV, - search, - filters, - cursor, - limit, - }); - if (internalIds.length === 0) { - return { fullCustomers: [], next_cursor: null }; - } - const query = getCursorPaginatedFullCusQuery({ - orgId: ORG_ID, - env: ENV, - inStatuses: RELEVANT_STATUSES, - withSubs: true, - limit: internalIds.length, - internalCustomerIds: internalIds, - cusProductLimit: 15, - }); - const rows = (await ctx.db.execute(query)) as unknown as Record< - string, - unknown - >[]; - const flat = (rows[0] ?? { - customers: [], - customer_products: [], - customer_entitlements: [], - extra_customer_entitlements: [], - customer_prices: [], - entitlements: [], - rollovers: [], - replaceables: [], - free_trials: [], - subscriptions: [], - }) as unknown as FlattenedCustomerRow; - const fullCustomers = reassembleFlattenedCustomer(flat); - const next_cursor = peek ? StandardCursor.encode(peek) : null; - return { fullCustomers, next_cursor }; -}; - -const minimalCtx = () => { - const { db, client } = initDrizzle(); - return { - db, - client, - org: { id: ORG_ID, slug: "unit-test-org" }, - env: ENV, - logger: { - info: () => {}, - error: () => {}, - warn: () => {}, - debug: () => {}, - trace: () => {}, - }, - } as any; -}; - -const sortById = ( - arr: T[], -): T[] => - [...arr].sort((a, b) => - (a.internal_id ?? a.id ?? "").localeCompare(b.internal_id ?? b.id ?? ""), - ); - -const sortDeep = (obj: unknown): unknown => { - if (Array.isArray(obj)) { - const sorted = obj.map(sortDeep); - if (sorted.length > 0 && typeof sorted[0] === "object" && sorted[0] !== null) { - (sorted as any[]).sort((a, b) => { - const ka = (a as any).id ?? (a as any).internal_id ?? ""; - const kb = (b as any).id ?? (b as any).internal_id ?? ""; - return String(ka).localeCompare(String(kb)); - }); - } - return sorted; - } - if (obj && typeof obj === "object") { - return Object.fromEntries( - Object.entries(obj as Record) - .map(([k, v]) => [k, sortDeep(v)] as const) - .sort(([a], [b]) => a.localeCompare(b)), - ); - } - return obj; -}; - -const diffCustomers = ( - a: FullCustomer[], - b: FullCustomer[], - label: string, -) => { - if (a.length !== b.length) { - console.log( - chalk.red( - ` ${label} ✗ length mismatch: old=${a.length} new=${b.length}`, - ), - ); - return false; - } - const sa = sortById(a); - const sb = sortById(b); - let mismatches = 0; - for (let i = 0; i < sa.length; i++) { - const ja = JSON.stringify(sortDeep(sa[i])); - const jb = JSON.stringify(sortDeep(sb[i])); - if (ja !== jb) { - if (mismatches < 2) { - console.log( - chalk.red( - ` ${label} ✗ customer #${i} (${(sa[i] as any).id}) differs`, - ), - ); - const keysA = Object.keys(sortDeep(sa[i]) as object); - for (const k of keysA) { - const va = JSON.stringify((sortDeep(sa[i]) as any)[k]); - const vb = JSON.stringify((sortDeep(sb[i]) as any)[k]); - if (va !== vb) { - console.log( - chalk.gray(` KEY ${k}\n old=${va?.slice(0, 160)}\n new=${vb?.slice(0, 160)}`), - ); - } - } - } - mismatches++; - } - } - if (mismatches > 0) { - console.log( - chalk.red(` ${label} ✗ ${mismatches}/${sa.length} customers differ`), - ); - return false; - } - console.log(chalk.green(` ${label} ✓ ${sa.length} customers identical`)); - return true; -}; - -type Case = { - name: string; - search?: string; - filters?: DashboardFilters; - cursor?: { t: number; id: string } | null; - limit?: number; -}; - -const main = async () => { - console.log( - chalk.magentaBright( - "\n================ Dashboard Merge Parity ================\n", - ), - ); - - const ctx = minimalCtx(); - - const total = ( - (await ctx.db.execute( - sql`SELECT COUNT(*)::int AS n FROM customers WHERE org_id = ${ORG_ID} AND env = ${ENV}`, - )) as unknown as { n: number }[] - )[0].n; - const deepOffset = Math.floor(total * 0.95); - const deepRow = (await ctx.db.execute( - sql`SELECT created_at, id FROM customers WHERE org_id = ${ORG_ID} AND env = ${ENV} ORDER BY created_at DESC, id DESC LIMIT 1 OFFSET ${deepOffset}`, - )) as unknown as { created_at: number; id: string }[]; - const deepCursor = { - t: Number(deepRow[0].created_at), - id: deepRow[0].id, - }; - console.log( - chalk.gray( - ` total=${total.toLocaleString()} deep_cursor=${deepCursor.id} (offset ~95%)\n`, - ), - ); - - const cases: Case[] = [ - { name: "baseline", limit: 200 }, - { name: "search 'bench_000450'", search: "bench_000450", limit: 100 }, - { name: "status=active", filters: { status: ["active"] }, limit: 200 }, - { name: "status=past_due", filters: { status: ["past_due"] }, limit: 200 }, - { name: "status=canceled", filters: { status: ["canceled"] }, limit: 200 }, - { - name: "status=free_trial", - filters: { status: ["free_trial"] }, - limit: 200, - }, - { name: "status=expired", filters: { status: ["expired"] }, limit: 200 }, - { - name: "status=active,past_due", - filters: { status: ["active", "past_due"] }, - limit: 200, - }, - { name: "none=true", filters: { none: true }, limit: 200 }, - { - name: "processor=stripe", - filters: { processor: ["stripe"] }, - limit: 200, - }, - { name: "deep cursor (95%)", cursor: deepCursor, limit: 200 }, - { - name: "deep cursor + status=active", - cursor: deepCursor, - filters: { status: ["active"] }, - limit: 200, - }, - { - name: "search no-match", - search: "no_such_customer_zzz", - limit: 50, - }, - ]; - - let pass = 0; - let fail = 0; - try { - for (const tc of cases) { - const args = { - ctx, - search: tc.search ?? "", - filters: tc.filters, - cursor: tc.cursor ?? null, - limit: tc.limit ?? 200, - }; - const oldRes = await runOldPath(args); - const newRes = await CusBatchService.getDashboardCursorPage(args); - const ok = diffCustomers( - oldRes.fullCustomers, - newRes.fullCustomers, - tc.name, - ); - const oldDecoded = StandardCursor.decode(oldRes.next_cursor ?? ""); - const newDecoded = StandardCursor.decode(newRes.next_cursor ?? ""); - const cursorMatches = - (oldDecoded?.t ?? null) === (newDecoded?.t ?? null) && - (oldDecoded?.id ?? null) === (newDecoded?.id ?? null); - if (!cursorMatches) { - console.log( - chalk.yellow( - ` cursor differs: old=${JSON.stringify(oldDecoded)} new=${JSON.stringify(newDecoded)}`, - ), - ); - } - if (ok && cursorMatches) pass++; - else fail++; - } - - console.log(); - console.log( - chalk.bold( - `Result: ${chalk.green(`${pass} pass`)} / ${chalk.red(`${fail} fail`)} of ${cases.length}`, - ), - ); - } finally { - await ctx.client.end(); - process.exit(fail === 0 ? 0 : 1); - } -}; - -await main(); diff --git a/server/experiments/diffOptimizedVsCurrent.ts b/server/experiments/diffOptimizedVsCurrent.ts deleted file mode 100644 index 9543fec39..000000000 --- a/server/experiments/diffOptimizedVsCurrent.ts +++ /dev/null @@ -1,180 +0,0 @@ -import { AppEnv } from "@autumn/shared"; -import type { FlattenedCustomerRow } from "../src/internal/customers/reassembleFlattenedCustomer/index.js"; -import chalk from "chalk"; -import { sql } from "drizzle-orm"; -import { RELEVANT_STATUSES } from "../src/internal/customers/cusProducts/CusProductService"; -import { initDrizzle } from "../src/db/initDrizzle"; -import { loadLocalEnv } from "../src/utils/envUtils"; -import { getCursorPaginatedFullCusQuery } from "../src/internal/customers/cursorPaginatedFullCusQuery"; -import { reassembleFlattenedCustomer } from "../src/internal/customers/reassembleFlattenedCustomer"; -import { getOptimizedFullCusQuery } from "./optimizedFullCusQuery"; - -loadLocalEnv(); - -const ORG_ID = "org_2sWv2S8LJ9iaTjLI6UtNsfL88Kt"; - -type Args = { - orgId: string; - env: AppEnv; - inStatuses: typeof RELEVANT_STATUSES; - withSubs: boolean; - limit: number; - cusProductLimit: number; -}; - -const sharedArgs: Args & { - cursor?: { v: 0; t: number; id: string }; -} = { - orgId: ORG_ID, - env: AppEnv.Sandbox, - inStatuses: RELEVANT_STATUSES, - withSubs: true, - limit: parseInt(process.env.LIMIT ?? "50", 10), - cusProductLimit: 15, -}; - -type AnyRecord = Record; - -const fetchFlat = async ( - db: ReturnType["db"], - q: any, -): Promise => { - const rows = await db.execute(q); - const flat = (rows as unknown as AnyRecord[])[0] as unknown as FlattenedCustomerRow; - return flat; -}; - -const sortById = (a: { id?: string }, b: { id?: string }) => - (a.id ?? "").localeCompare(b.id ?? ""); - -const sortDeep = (obj: unknown): unknown => { - if (Array.isArray(obj)) { - const sorted = obj.map(sortDeep); - if (sorted.length > 0 && typeof sorted[0] === "object" && sorted[0] !== null) { - (sorted as AnyRecord[]).sort((a, b) => { - const ka = (a as AnyRecord).id ?? (a as AnyRecord).internal_id ?? ""; - const kb = (b as AnyRecord).id ?? (b as AnyRecord).internal_id ?? ""; - return String(ka).localeCompare(String(kb)); - }); - } - return sorted; - } - if (obj && typeof obj === "object") { - const entries = Object.entries(obj as AnyRecord) - .map(([k, v]) => [k, sortDeep(v)] as const) - .sort(([a], [b]) => a.localeCompare(b)); - return Object.fromEntries(entries); - } - return obj; -}; - -const main = async () => { - const { db, client } = initDrizzle(); - try { - const offsetPct = parseInt(process.env.OFFSET_PCT ?? "0", 10); - if (offsetPct > 0) { - const total = (await db.execute( - sql`SELECT COUNT(*)::int AS n FROM customers WHERE org_id = ${ORG_ID} AND env = 'sandbox'`, - )) as unknown as { n: number }[]; - const offset = Math.floor((total[0].n * offsetPct) / 100); - const row = (await db.execute( - sql`SELECT created_at, id FROM customers WHERE org_id = ${ORG_ID} AND env = 'sandbox' ORDER BY created_at DESC, id DESC LIMIT 1 OFFSET ${offset}`, - )) as unknown as { created_at: number; id: string }[]; - sharedArgs.cursor = { v: 0, t: row[0].created_at, id: row[0].id }; - console.log(chalk.cyan(`Using deep cursor at ${offsetPct}%: ${row[0].id}`)); - } - - console.log(chalk.cyan(`Running CURRENT query (limit=${sharedArgs.limit}, cursor=${sharedArgs.cursor ? "yes" : "no"})...`)); - const flatCurrent = await fetchFlat( - db, - getCursorPaginatedFullCusQuery(sharedArgs), - ); - - console.log(chalk.cyan(`Running OPTIMIZED query...`)); - const flatOptim = await fetchFlat( - db, - getOptimizedFullCusQuery(sharedArgs), - ); - - // Compare top-level array lengths - const keys = [ - "customers", - "customer_products", - "customer_entitlements", - "extra_customer_entitlements", - "customer_prices", - "entitlements", - "rollovers", - "replaceables", - "free_trials", - "subscriptions", - ] as const; - - console.log(chalk.bold("\nTop-level counts:")); - let lenMismatch = false; - for (const k of keys) { - const a = (flatCurrent[k] as unknown[] | undefined)?.length ?? 0; - const b = (flatOptim[k] as unknown[] | undefined)?.length ?? 0; - const match = a === b; - if (!match) lenMismatch = true; - console.log( - ` ${k.padEnd(32)} current=${String(a).padStart(5)} optim=${String(b).padStart(5)} ${match ? chalk.green("✓") : chalk.red("✗")}`, - ); - } - - if (lenMismatch) { - console.log(chalk.red("\n❌ Length mismatch — aborting deep compare")); - process.exit(1); - } - - // Reassemble both and deep compare the FullCustomer arrays - const customersA = reassembleFlattenedCustomer(flatCurrent); - const customersB = reassembleFlattenedCustomer(flatOptim); - - console.log(chalk.cyan(`\nReassembled ${customersA.length} customers (both queries)`)); - - if (customersA.length !== customersB.length) { - console.log(chalk.red(`Mismatch in reassembled customer counts: ${customersA.length} vs ${customersB.length}`)); - process.exit(1); - } - - // Sort by id for stable diff (same cursor order should already be aligned) - customersA.sort((a: any, b: any) => (a.internal_id ?? "").localeCompare(b.internal_id ?? "")); - customersB.sort((a: any, b: any) => (a.internal_id ?? "").localeCompare(b.internal_id ?? "")); - - let diffs = 0; - for (let i = 0; i < customersA.length; i++) { - const a = sortDeep(customersA[i]); - const b = sortDeep(customersB[i]); - const ja = JSON.stringify(a); - const jb = JSON.stringify(b); - if (ja !== jb) { - diffs++; - if (diffs <= 3) { - console.log(chalk.red(`\nDIFF for customer #${i} (${(customersA[i] as any).id}):`)); - // find first differing key - for (const k of Object.keys(a as AnyRecord)) { - const va = JSON.stringify((a as AnyRecord)[k]); - const vb = JSON.stringify((b as AnyRecord)[k]); - if (va !== vb) { - console.log(` KEY ${k}:`); - console.log(chalk.gray(` current: ${va?.slice(0, 200)}`)); - console.log(chalk.gray(` optim: ${vb?.slice(0, 200)}`)); - } - } - } - } - } - - if (diffs === 0) { - console.log(chalk.green(`\n✅ All ${customersA.length} customers match byte-for-byte`)); - } else { - console.log(chalk.red(`\n❌ ${diffs}/${customersA.length} customers differ`)); - } - } finally { - await client.end(); - process.exit(0); - } -}; - -await main(); diff --git a/server/experiments/diffV07VsV22.ts b/server/experiments/diffV07VsV22.ts deleted file mode 100644 index 465a54ada..000000000 --- a/server/experiments/diffV07VsV22.ts +++ /dev/null @@ -1,426 +0,0 @@ -import { - type AppEnv, - type CusProductStatus, - type FullCustomer, - type ListCustomersV2Params, - RELEVANT_STATUSES, -} from "@autumn/shared"; -import chalk from "chalk"; -import { sql } from "drizzle-orm"; -import { initDrizzle } from "../src/db/initDrizzle"; -import { getCursorPaginatedFullCusQuery } from "../src/internal/customers/cursorPaginatedFullCusQuery"; -import { getPaginatedFullCusQuery } from "../src/internal/customers/getFullCusQuery"; -import { - type FlattenedCustomerRow, - reassembleFlattenedCustomer, -} from "../src/internal/customers/reassembleFlattenedCustomer"; -import { loadLocalEnv } from "../src/utils/envUtils"; - -loadLocalEnv(); - -const ORGS: { id: string; env: AppEnv; label: string }[] = [ - { id: "biu9vSF7vghBLSKW1UTDwxHBAivjnPaK", env: "live" as AppEnv, label: "firecrawl" }, - { id: "r7pwHiekGsqt32qGqcVku6thWFh5aHh8", env: "live" as AppEnv, label: "runable" }, -]; -const LIMIT = parseInt(process.env.DIFF_LIMIT ?? "50", 10); -const DEEP_OFFSET = parseInt(process.env.DIFF_DEEP_OFFSET ?? "500", 10); -const CUS_PRODUCT_LIMIT = 15; -const STATEMENT_TIMEOUT_MS = 30_000; - -type ResolvedParams = Partial & { - offset?: number; - cursor?: { t: number; id: string }; -}; - -type CaseContext = { db: DB; orgId: string; env: AppEnv }; -type Case = { - name: string; - build: (ctx: CaseContext) => Promise; -}; - -type DB = ReturnType["db"]; - -const runReadOnly = async (db: DB, fn: (tx: any) => Promise): Promise => { - return await db.transaction(async (tx) => { - await tx.execute( - sql.raw(`SET LOCAL statement_timeout = ${STATEMENT_TIMEOUT_MS}`), - ); - await tx.execute(sql.raw("SET LOCAL transaction_read_only = on")); - return fn(tx); - }); -}; - -const v22NormalizeTimestamp = (value: any): number => { - if (typeof value === "string") { - const parsed = parseInt(value, 10); - return Number.isNaN(parsed) ? Date.now() : parsed; - } - return typeof value === "number" ? value : Date.now(); -}; - -const v22Normalize = (raw: any): any => { - const out = { - ...raw, - created_at: v22NormalizeTimestamp(raw.created_at), - }; - if (raw.customer_products && Array.isArray(raw.customer_products)) { - out.customer_products = raw.customer_products.map((cp: any) => ({ - ...cp, - created_at: v22NormalizeTimestamp(cp.created_at), - starts_at: cp.starts_at - ? v22NormalizeTimestamp(cp.starts_at) - : v22NormalizeTimestamp(cp.created_at), - canceled_at: cp.canceled_at ? v22NormalizeTimestamp(cp.canceled_at) : null, - ended_at: cp.ended_at ? v22NormalizeTimestamp(cp.ended_at) : null, - trial_ends_at: cp.trial_ends_at - ? v22NormalizeTimestamp(cp.trial_ends_at) - : null, - quantity: cp.quantity ? parseInt(cp.quantity, 10) || 1 : 1, - options: cp.options || [], - collection_method: cp.collection_method || "charge_automatically", - subscription_ids: cp.subscription_ids || [], - scheduled_ids: cp.scheduled_ids || [], - customer_entitlements: (cp.customer_entitlements || []).map((ce: any) => ({ - ...ce, - created_at: v22NormalizeTimestamp(ce.created_at), - next_reset_at: ce.next_reset_at - ? v22NormalizeTimestamp(ce.next_reset_at) - : null, - balance: ce.balance ? parseFloat(ce.balance) || 0 : 0, - adjustment: ce.adjustment ? parseFloat(ce.adjustment) || 0 : 0, - })), - })); - } - return out; -}; - -const fetchV22 = async ( - db: DB, - orgId: string, - env: AppEnv, - params: ResolvedParams, -) => { - const { subscription_status, plans, search, processors, offset = 0 } = params; - const query = getPaginatedFullCusQuery({ - orgId, - env, - inStatuses: subscription_status - ? [subscription_status as unknown as CusProductStatus] - : RELEVANT_STATUSES, - includeInvoices: false, - withEntities: false, - withTrialsUsed: false, - withSubs: true, - limit: LIMIT, - offset, - search, - plans, - processors, - cusProductLimit: CUS_PRODUCT_LIMIT, - }); - const rows = await runReadOnly(db, (tx) => tx.execute(query)); - return (rows as any[]).map(v22Normalize) as FullCustomer[]; -}; - -const fetchV23 = async ( - db: DB, - orgId: string, - env: AppEnv, - params: ResolvedParams, -) => { - const { subscription_status, plans, search, processors, cursor } = params; - const query = getCursorPaginatedFullCusQuery({ - orgId, - env, - inStatuses: subscription_status - ? [subscription_status as unknown as CusProductStatus] - : RELEVANT_STATUSES, - withSubs: true, - limit: LIMIT, - search, - plans, - processors, - cursor: cursor ? { v: 0 as const, t: cursor.t, id: cursor.id } : undefined, - cusProductLimit: CUS_PRODUCT_LIMIT, - }); - const rows = (await runReadOnly(db, (tx) => tx.execute(query))) as any[]; - const flat = (rows[0] ?? {}) as unknown as FlattenedCustomerRow; - const all = reassembleFlattenedCustomer(flat); - return all.slice(0, LIMIT); -}; - -const resolveCursorAtOffset = async ({ - db, - orgId, - env, - offset, - inStatuses, - plans, - processors, - search, -}: { - db: DB; - orgId: string; - env: AppEnv; - offset: number; - inStatuses?: CusProductStatus[]; - plans?: ListCustomersV2Params["plans"]; - processors?: ListCustomersV2Params["processors"]; - search?: string; -}): Promise<{ t: number; id: string } | null> => { - const query = getPaginatedFullCusQuery({ - orgId, - env, - inStatuses: inStatuses ?? RELEVANT_STATUSES, - includeInvoices: false, - withEntities: false, - withTrialsUsed: false, - withSubs: false, - limit: 1, - offset: offset - 1, - search, - plans, - processors, - cusProductLimit: 1, - }); - const rows = (await runReadOnly(db, (tx) => tx.execute(query))) as any[]; - if (rows.length === 0) return null; - const row = rows[0] as { id: string; created_at: number | string }; - return { t: v22NormalizeTimestamp(row.created_at), id: row.id }; -}; - -const resolveFirstPlanId = async ({ - db, - orgId, - env, -}: { - db: DB; - orgId: string; - env: AppEnv; -}): Promise => { - const rows = (await runReadOnly( - db, - (tx) => tx.execute(sql` - SELECT p.id - FROM products p - JOIN customer_products cp ON cp.internal_product_id = p.internal_id - WHERE p.org_id = ${orgId} AND p.env = ${env} - GROUP BY p.id - ORDER BY COUNT(*) DESC - LIMIT 1 - `), - )) as any[]; - return rows[0]?.id ?? null; -}; - -const resolveSearchSubstring = async ({ - db, - orgId, - env, -}: { - db: DB; - orgId: string; - env: AppEnv; -}): Promise => { - const rows = (await runReadOnly( - db, - (tx) => tx.execute(sql` - SELECT c.email - FROM customers c - WHERE c.org_id = ${orgId} AND c.env = ${env} - AND c.email IS NOT NULL - AND length(c.email) >= 5 - LIMIT 1 - `), - )) as any[]; - const email = rows[0]?.email as string | undefined; - if (!email) return null; - const at = email.indexOf("@"); - if (at < 3) return email.slice(0, 3); - return email.slice(0, Math.min(at, 4)); -}; - -const CASES: Case[] = [ - { - name: "page 1 / no filters", - build: async () => ({}), - }, - { - name: "page 1 / subscription_status=active", - build: async () => ({ subscription_status: "active" }), - }, - { - name: "page 1 / processors=revenuecat", - build: async () => ({ processors: ["revenuecat"] }), - }, - { - name: "page 1 / search", - build: async (ctx) => { - const search = await resolveSearchSubstring(ctx); - if (!search) return null; - return { search }; - }, - }, - { - name: "page 1 / plans", - build: async (ctx) => { - const planId = await resolveFirstPlanId(ctx); - if (!planId) return null; - return { plans: [{ id: planId }] }; - }, - }, - { - name: `deep / offset=${DEEP_OFFSET} / no filters`, - build: async (ctx) => { - const cursor = await resolveCursorAtOffset({ - db: ctx.db, - orgId: ctx.orgId, - env: ctx.env, - offset: DEEP_OFFSET, - }); - if (!cursor) return null; - return { offset: DEEP_OFFSET, cursor }; - }, - }, -]; - -const findDiff = (a: any, b: any, path = "$"): string | null => { - if (a === b) return null; - if (a === null || b === null || a === undefined || b === undefined) { - return `${path}: ${JSON.stringify(a)} vs ${JSON.stringify(b)}`; - } - if (typeof a !== typeof b) { - return `${path}: type ${typeof a} vs ${typeof b}`; - } - if (Array.isArray(a) || Array.isArray(b)) { - if (!Array.isArray(a) || !Array.isArray(b)) { - return `${path}: array vs non-array`; - } - if (a.length !== b.length) { - return `${path}.length: ${a.length} vs ${b.length}`; - } - for (let i = 0; i < a.length; i++) { - const d = findDiff(a[i], b[i], `${path}[${i}]`); - if (d) return d; - } - return null; - } - if (typeof a === "object") { - const keys = new Set([...Object.keys(a), ...Object.keys(b)]); - for (const k of keys) { - const d = findDiff(a[k], b[k], `${path}.${k}`); - if (d) return d; - } - return null; - } - return `${path}: ${JSON.stringify(a)} vs ${JSON.stringify(b)}`; -}; - -const summarize = (label: string, list: FullCustomer[]) => { - const cps = list.reduce((n, c) => n + (c.customer_products?.length ?? 0), 0); - const subs = list.reduce( - (n, c) => n + (c.subscriptions?.length ?? 0), - 0, - ); - const extras = list.reduce( - (n, c) => n + (c.extra_customer_entitlements?.length ?? 0), - 0, - ); - return `${label}: ${list.length} cus / ${cps} cps / ${subs} subs / ${extras} loose_ces`; -}; - -const runOrg = async ({ - db, - orgId, - env, - label, -}: { - db: DB; - orgId: string; - env: AppEnv; - label: string; -}) => { - console.log( - chalk.magentaBright( - `\n========= ${label} (${orgId}) env=${env} limit=${LIMIT} =========\n`, - ), - ); - let total = 0; - let passes = 0; - for (const c of CASES) { - total++; - console.log(chalk.cyan(`\n--- ${c.name} ---`)); - const params = await c.build({ db, orgId, env }); - if (!params) { - console.log(chalk.yellow(" ⊘ skipped (no data to build params)")); - passes++; - continue; - } - console.log( - chalk.gray( - ` params: ${JSON.stringify(params, (_k, v) => (typeof v === "bigint" ? String(v) : v))}`, - ), - ); - const [v22, v23] = await Promise.all([ - fetchV22(db, orgId, env, params), - fetchV23(db, orgId, env, params), - ]); - console.log(chalk.gray(` ${summarize("V2.2", v22)}`)); - console.log(chalk.gray(` ${summarize("V2.3", v23)}`)); - const diff = findDiff(v22, v23); - if (diff) { - console.log(chalk.red(` ❌ DIFF at ${diff}`)); - const idx = diff.match(/\$\[(\d+)\]/); - if (idx) { - const i = parseInt(idx[1]!, 10); - console.log(chalk.gray(` --- V2.2 [${i}] ---`)); - console.log(chalk.gray(JSON.stringify(v22[i], null, 2).slice(0, 4000))); - console.log(chalk.gray(` --- V2.3 [${i}] ---`)); - console.log(chalk.gray(JSON.stringify(v23[i], null, 2).slice(0, 4000))); - } - } else { - console.log(chalk.green(` ✓ MATCH`)); - passes++; - } - } - console.log( - chalk.magentaBright(`\n ${label}: ${passes}/${total} cases match`), - ); - return { total, passes }; -}; - -const main = async () => { - const { db, client } = initDrizzle(); - try { - let grandTotal = 0; - let grandPasses = 0; - for (const org of ORGS) { - try { - const { total, passes } = await runOrg({ - db, - orgId: org.id, - env: org.env, - label: org.label, - }); - grandTotal += total; - grandPasses += passes; - } catch (err) { - console.error(chalk.red(`\n❌ Org ${org.label} failed:`), err); - } - } - console.log( - chalk.magentaBright( - `\n========= TOTAL: ${grandPasses}/${grandTotal} cases match =========\n`, - ), - ); - } catch (error) { - console.error(chalk.red("\n❌ Diff failed:")); - console.error(error); - process.exit(1); - } finally { - await client.end(); - process.exit(0); - } -}; - -await main(); diff --git a/server/experiments/inspectIndexes.ts b/server/experiments/inspectIndexes.ts deleted file mode 100644 index 049488da2..000000000 --- a/server/experiments/inspectIndexes.ts +++ /dev/null @@ -1,52 +0,0 @@ -import chalk from "chalk"; -import { sql } from "drizzle-orm"; -import { initDrizzle } from "../src/db/initDrizzle"; -import { loadLocalEnv } from "../src/utils/envUtils"; - -loadLocalEnv(); - -const main = async () => { - const { db, client } = initDrizzle(); - try { - const tables = [ - "customers", - "customer_products", - "customer_entitlements", - "customer_prices", - "subscriptions", - ]; - for (const t of tables) { - console.log(chalk.bold(`\n=== ${t} indexes ===`)); - const r = (await db.execute(sql.raw(` - SELECT indexname, indexdef - FROM pg_indexes - WHERE tablename = '${t}' AND schemaname = 'public' - ORDER BY indexname - `))) as unknown as { indexname: string; indexdef: string }[]; - for (const idx of r) { - console.log(` ${idx.indexname}`); - console.log(` ${idx.indexdef}`); - } - } - - // Get total rows in customer_products across all orgs - console.log(chalk.bold("\n=== Global customer_products stats ===")); - const stats = (await db.execute(sql.raw(` - SELECT - reltuples::bigint AS estimated_rows, - pg_size_pretty(pg_relation_size('customer_products')) AS table_size, - pg_size_pretty(pg_indexes_size('customer_products')) AS indexes_size - FROM pg_class WHERE relname = 'customer_products' - `))) as unknown as { estimated_rows: string; table_size: string; indexes_size: string }[]; - console.log(` estimated_rows=${stats[0].estimated_rows} table=${stats[0].table_size} indexes=${stats[0].indexes_size}`); - - // Actual count globally - const real = (await db.execute(sql.raw(`SELECT COUNT(*)::int AS n FROM customer_products`))) as unknown as { n: number }[]; - console.log(` actual_total_rows=${real[0].n.toLocaleString()}`); - } finally { - await client.end(); - process.exit(0); - } -}; - -await main(); diff --git a/server/experiments/inspectUnitTestOrg.ts b/server/experiments/inspectUnitTestOrg.ts deleted file mode 100644 index 0993585a5..000000000 --- a/server/experiments/inspectUnitTestOrg.ts +++ /dev/null @@ -1,90 +0,0 @@ -import chalk from "chalk"; -import { sql } from "drizzle-orm"; -import { initDrizzle } from "../src/db/initDrizzle"; -import { loadLocalEnv } from "../src/utils/envUtils"; - -loadLocalEnv(); - -const ORG_ID = "org_2sWv2S8LJ9iaTjLI6UtNsfL88Kt"; - -const main = async () => { - const { db, client } = initDrizzle(); - try { - const tables = [ - "customers", - "customer_products", - "customer_entitlements", - "customer_prices", - "subscriptions", - "entitlements", - "products", - "rollovers", - "replaceables", - "free_trials", - ]; - - console.log(chalk.bold(`\nRow counts for org=${ORG_ID}:\n`)); - for (const t of tables) { - const hasOrg = ["customers", "subscriptions", "entitlements", "products", "features"]; - const col = hasOrg.includes(t) - ? `WHERE org_id = '${ORG_ID}'` - : t === "customer_products" || - t === "customer_entitlements" || - t === "customer_prices" - ? `WHERE internal_customer_id IN (SELECT internal_id FROM customers WHERE org_id = '${ORG_ID}')` - : ""; - const start = performance.now(); - const r = (await db.execute( - sql.raw(`SELECT COUNT(*)::int AS n FROM ${t} ${col}`), - )) as unknown as { n: number }[]; - const n = r[0]?.n ?? 0; - console.log( - ` ${t.padEnd(28)} ${n.toLocaleString().padStart(10)} (${(performance.now() - start).toFixed(0)}ms)`, - ); - } - - // Sample a customer to see if it has any products - const sample = (await db.execute(sql.raw(` - SELECT c.internal_id, c.id, - (SELECT COUNT(*)::int FROM customer_products cp WHERE cp.internal_customer_id = c.internal_id) AS cp_count, - (SELECT COUNT(*)::int FROM customer_entitlements ce WHERE ce.internal_customer_id = c.internal_id) AS ce_count - FROM customers c - WHERE c.org_id = '${ORG_ID}' - ORDER BY c.created_at DESC - LIMIT 10 - `))) as unknown as Record[]; - console.log(chalk.bold(`\nFirst 10 customers by created_at DESC:\n`)); - for (const row of sample) { - console.log( - ` ${(row.id as string).padEnd(30)} cp=${row.cp_count} ce=${row.ce_count}`, - ); - } - - // Histogram: how many customers have N products? - const hist = (await db.execute(sql.raw(` - SELECT cp_bucket, COUNT(*)::int AS customer_count FROM ( - SELECT c.internal_id, - LEAST( - (SELECT COUNT(*)::int FROM customer_products cp WHERE cp.internal_customer_id = c.internal_id), - 10 - ) AS cp_bucket - FROM customers c - WHERE c.org_id = '${ORG_ID}' - ) t - GROUP BY cp_bucket - ORDER BY cp_bucket - `))) as unknown as { cp_bucket: number; customer_count: number }[]; - console.log(chalk.bold(`\nCustomer × #products histogram (capped at 10):\n`)); - for (const row of hist) { - const bar = "█".repeat(Math.min(60, Math.floor(row.customer_count / 5000))); - console.log( - ` ${row.cp_bucket.toString().padStart(2)}: ${row.customer_count.toLocaleString().padStart(8)} ${bar}`, - ); - } - } finally { - await client.end(); - process.exit(0); - } -}; - -await main(); diff --git a/server/experiments/optimizedFullCusQuery.ts b/server/experiments/optimizedFullCusQuery.ts deleted file mode 100644 index c8b07abcb..000000000 --- a/server/experiments/optimizedFullCusQuery.ts +++ /dev/null @@ -1,173 +0,0 @@ -import type { - AppEnv, - CusProductStatus, - ListCustomersV2Params, - StandardCursorFields, -} from "@autumn/shared"; -import { sql } from "drizzle-orm"; -import { getCustomerListFilterSql } from "../src/internal/customers/getFullCusQuery"; - -export type OptimizedQueryArgs = { - orgId: string; - env: AppEnv; - inStatuses?: CusProductStatus[]; - withSubs?: boolean; - limit: number; - cursor?: StandardCursorFields; - internalCustomerIds?: string[]; - plans?: ListCustomersV2Params["plans"]; - processors?: ListCustomersV2Params["processors"]; - search?: string; - cusProductLimit: number; -}; - -/** - * Set-based replacement for the per-customer LATERAL nested loops. - * Key change: each child fetch is a single hash/merge join over the cr CTE - * instead of 1001 sequential scans (original Seq Scan-per-loop pattern). - */ -export const getOptimizedFullCusQuery = ({ - orgId, - env, - inStatuses, - withSubs = true, - limit, - cursor, - internalCustomerIds, - plans, - processors, - search, - cusProductLimit, -}: OptimizedQueryArgs) => { - const cpStatusFilter = inStatuses?.length - ? sql`AND cp.status = ANY(ARRAY[${sql.join( - inStatuses.map((status) => sql`${status}`), - sql`, `, - )}])` - : sql``; - - const customerListFilterSql = getCustomerListFilterSql({ - internalCustomerIds, - inStatuses, - plans, - processors, - search, - }); - - const cursorPredicate = cursor - ? sql`AND (c.created_at, c.id) < (${cursor.t}, ${cursor.id})` - : sql``; - - const fetchLimit = limit + 1; - - const subscriptionsSelect = withSubs - ? sql`( - SELECT COALESCE(json_agg(row_to_json(s)), '[]'::json) - FROM ( - SELECT DISTINCT s.* - FROM cps_ranked cps - CROSS JOIN LATERAL unnest(cps.subscription_ids) AS sub_id_t(sub_id) - JOIN subscriptions s ON s.stripe_id = sub_id_t.sub_id - WHERE cps.subscription_ids IS NOT NULL - ) s - ) AS subscriptions` - : sql`'[]'::json AS subscriptions`; - - return sql` - WITH cr AS MATERIALIZED ( - SELECT - c.internal_id, - c.id, - c.created_at, - row_to_json(c) AS row_json - FROM customers c - WHERE c.org_id = ${orgId} - AND c.env = ${env} - ${customerListFilterSql} - ${cursorPredicate} - ORDER BY c.created_at DESC, c.id DESC - LIMIT ${fetchLimit} - ), - cp_ranked_ids AS MATERIALIZED ( - SELECT - cp.id, - cp.internal_customer_id, - cp.internal_product_id, - ROW_NUMBER() OVER ( - PARTITION BY cp.internal_customer_id - ORDER BY prod.is_add_on ASC, cp.created_at DESC - ) AS rn - FROM cr - JOIN customer_products cp ON cp.internal_customer_id = cr.internal_id - JOIN products prod ON prod.internal_id = cp.internal_product_id - WHERE TRUE ${cpStatusFilter} - ), - cps_ranked AS MATERIALIZED ( - SELECT - cp.id, - cp.internal_customer_id, - cp.internal_product_id, - cp.free_trial_id, - cp.subscription_ids, - (row_to_json(cp)::jsonb || jsonb_build_object('product', row_to_json(prod)))::json AS row_json - FROM cp_ranked_ids r - JOIN customer_products cp ON cp.id = r.id - JOIN products prod ON prod.internal_id = r.internal_product_id - WHERE r.rn <= ${cusProductLimit} - ), - ces_bound AS MATERIALIZED ( - -- Single hash join instead of 1001 LATERAL scans. - SELECT ce.id, ce.entitlement_id, row_to_json(ce) AS row_json - FROM cps_ranked - JOIN customer_entitlements ce ON ce.customer_product_id = cps_ranked.id - ), - ces_loose AS MATERIALIZED ( - -- LATERAL with LIMIT 30 stays; existing partial index is well-suited. - SELECT ce.id, ce.entitlement_id, row_to_json(ce) AS row_json - FROM cr - JOIN LATERAL ( - SELECT ce.* - FROM customer_entitlements ce - WHERE ce.internal_customer_id = cr.internal_id - AND ce.customer_product_id IS NULL - AND (ce.expires_at IS NULL OR ce.expires_at > EXTRACT(EPOCH FROM now()) * 1000) - ORDER BY ce.id DESC - LIMIT 30 - ) ce ON true - ), - ces_all AS MATERIALIZED ( - SELECT id, entitlement_id FROM ces_bound - UNION ALL - SELECT id, entitlement_id FROM ces_loose - ) - SELECT - (SELECT COALESCE(json_agg(row_json), '[]'::json) FROM cr) AS customers, - (SELECT COALESCE(json_agg(row_json), '[]'::json) FROM cps_ranked) AS customer_products, - (SELECT COALESCE(json_agg(row_json), '[]'::json) FROM ces_bound) AS customer_entitlements, - (SELECT COALESCE(json_agg(row_json ORDER BY id DESC), '[]'::json) FROM ces_loose) AS extra_customer_entitlements, - (SELECT COALESCE(json_agg(row_to_json(cpr)::jsonb || jsonb_build_object('price', row_to_json(p))), '[]'::json) - FROM cps_ranked - JOIN customer_prices cpr ON cpr.customer_product_id = cps_ranked.id - LEFT JOIN prices p ON p.id = cpr.price_id - ) AS customer_prices, - (SELECT COALESCE(json_agg(row_to_json(e)::jsonb || jsonb_build_object('feature', row_to_json(f))), '[]'::json) - FROM (SELECT DISTINCT entitlement_id FROM ces_all) ce - JOIN entitlements e ON e.id = ce.entitlement_id - JOIN features f ON f.internal_id = e.internal_feature_id - ) AS entitlements, - (SELECT COALESCE(json_agg(row_to_json(ro)), '[]'::json) - FROM ces_all - JOIN rollovers ro ON ro.cus_ent_id = ces_all.id - WHERE ro.expires_at IS NULL OR ro.expires_at > EXTRACT(EPOCH FROM now()) * 1000 - ) AS rollovers, - (SELECT COALESCE(json_agg(row_to_json(r)), '[]'::json) - FROM ces_all - JOIN replaceables r ON r.cus_ent_id = ces_all.id - ) AS replaceables, - (SELECT COALESCE(json_agg(row_to_json(ft)), '[]'::json) - FROM (SELECT DISTINCT free_trial_id FROM cps_ranked WHERE free_trial_id IS NOT NULL) cps - JOIN free_trials ft ON ft.id = cps.free_trial_id - ) AS free_trials, - ${subscriptionsSelect} - `; -}; diff --git a/server/experiments/paginationBenchmark.ts b/server/experiments/paginationBenchmark.ts deleted file mode 100644 index 4fee4962c..000000000 --- a/server/experiments/paginationBenchmark.ts +++ /dev/null @@ -1,368 +0,0 @@ -import { AppEnv } from "@autumn/shared"; -import chalk from "chalk"; -import { sql, type SQL } from "drizzle-orm"; -import { writeFileSync } from "node:fs"; -import { join } from "node:path"; -import { RELEVANT_STATUSES } from "../src/internal/customers/cusProducts/CusProductService"; -import { initDrizzle } from "../src/db/initDrizzle"; -import { loadLocalEnv } from "../src/utils/envUtils"; -import { getCursorPaginatedFullCusQuery } from "../src/internal/customers/cursorPaginatedFullCusQuery"; - -loadLocalEnv(); - -const ORG_ID = "r7pwHiekGsqt32qGqcVku6thWFh5aHh8"; -const ENV: AppEnv = AppEnv.Live; -const LABEL = "runable"; -const REPEATS = 5; -const STATEMENT_TIMEOUT_MS = 30_000; -const DEEP_OFFSET_PCT = 45; -const BASE_LIMIT = 1000; -const READ_ONLY = true; -const CUS_PRODUCT_LIMIT = 15; - -const RESULTS_DIR = join(import.meta.dir, "results"); - -type DB = ReturnType["db"]; - -const sharedFullCusOpts = { - inStatuses: RELEVANT_STATUSES, - includeInvoices: false, - withEntities: false, - withTrialsUsed: false, - withSubs: true, - cusProductLimit: CUS_PRODUCT_LIMIT, -}; - -type Cell = { - name: string; - build: () => SQL; - includeExplain?: boolean; -}; - -type CellResult = { - name: string; - medianMs: number; - p95Ms: number; - minMs: number; - maxMs: number; - rowCount: number; - samples: number[]; - explainPlan?: string; - error?: string; -}; - -const normalizeRows = (r: unknown): Record[] => { - if (Array.isArray(r)) return r as Record[]; - if (r && typeof r === "object" && "rows" in r) { - return (r as { rows: Record[] }).rows; - } - return []; -}; - -const runQueryInTxn = async ({ - db, - query, -}: { - db: DB; - query: SQL; -}): Promise[]> => { - return await db.transaction(async (tx) => { - await tx.execute(sql.raw(`SET LOCAL statement_timeout = ${STATEMENT_TIMEOUT_MS}`)); - if (READ_ONLY) { - await tx.execute(sql.raw("SET LOCAL transaction_read_only = on")); - } - return normalizeRows(await tx.execute(query)); - }); -}; - -const measureCell = async ({ - db, - cell, -}: { - db: DB; - cell: Cell; -}): Promise => { - const samples: number[] = []; - let rowCount = 0; - let error: string | undefined; - - for (let i = 0; i < REPEATS; i++) { - const query = cell.build(); - const startedAt = performance.now(); - try { - const result = await runQueryInTxn({ db, query }); - const elapsed = performance.now() - startedAt; - samples.push(elapsed); - rowCount = result.length; - } catch (err) { - const elapsed = performance.now() - startedAt; - samples.push(elapsed); - error = err instanceof Error ? err.message : String(err); - break; - } - } - - let explainPlan: string | undefined; - if (cell.includeExplain && !error) { - try { - const query = cell.build(); - const explainRows = await runQueryInTxn({ - db, - query: sql`EXPLAIN (ANALYZE, BUFFERS, FORMAT TEXT) ${query}`, - }); - explainPlan = explainRows.map((row) => row["QUERY PLAN"]).join("\n"); - } catch (err) { - explainPlan = `EXPLAIN failed: ${err instanceof Error ? err.message : String(err)}`; - } - } - - const sorted = [...samples].sort((a, b) => a - b); - const median = sorted[Math.floor(sorted.length / 2)] ?? 0; - const p95 = sorted[Math.min(sorted.length - 1, Math.floor(sorted.length * 0.95))] ?? 0; - const min = sorted[0] ?? 0; - const max = sorted[sorted.length - 1] ?? 0; - - return { - name: cell.name, - medianMs: median, - p95Ms: p95, - minMs: min, - maxMs: max, - rowCount, - samples, - explainPlan, - error, - }; -}; - -const revenuecatCountQuery = (): SQL => sql` - SELECT COUNT(*)::int AS total_count - FROM customers c - WHERE c.org_id = ${ORG_ID} - AND c.env = ${ENV} - AND EXISTS ( - SELECT 1 FROM customer_products cp - WHERE cp.internal_customer_id = c.internal_id - AND cp.processor->>'type' = 'revenuecat' - ) -`; - -const resolveRevenuecatCount = async ({ db }: { db: DB }) => { - const result = await runQueryInTxn({ db, query: revenuecatCountQuery() }); - return (result[0] as { total_count: number }).total_count; -}; - -const resolveRevenuecatDeepCursor = async ({ - db, - deepOffset, -}: { - db: DB; - deepOffset: number; -}): Promise<{ v: 0; t: number; id: string }> => { - const result = await runQueryInTxn({ - db, - query: sql` - SELECT c.created_at, c.id - FROM customers c - WHERE c.org_id = ${ORG_ID} - AND c.env = ${ENV} - AND EXISTS ( - SELECT 1 FROM customer_products cp - WHERE cp.internal_customer_id = c.internal_id - AND cp.processor->>'type' = 'revenuecat' - ) - ORDER BY c.created_at DESC, c.id DESC - LIMIT 1 OFFSET ${deepOffset} - `, - }); - - const row = result[0] as { created_at: number; id: string } | undefined; - if (!row) { - throw new Error( - `Could not resolve revenuecat deep cursor at offset ${deepOffset}. Not enough revenuecat customers.`, - ); - } - return { v: 0, t: row.created_at, id: row.id }; -}; - -const buildCells = ({ - deepCursor, -}: { - deepCursor: { v: 0; t: number; id: string }; -}): Cell[] => { - return [ - { - name: `10 cursor / revcat=true / first page / limit ${BASE_LIMIT}`, - build: () => - getCursorPaginatedFullCusQuery({ - orgId: ORG_ID, - env: ENV, - limit: BASE_LIMIT, - processors: ["revenuecat"], - ...sharedFullCusOpts, - }), - includeExplain: true, - }, - { - name: `11 cursor / revcat=true / deep / limit ${BASE_LIMIT}`, - build: () => - getCursorPaginatedFullCusQuery({ - orgId: ORG_ID, - env: ENV, - limit: BASE_LIMIT, - cursor: deepCursor, - processors: ["revenuecat"], - ...sharedFullCusOpts, - }), - includeExplain: true, - }, - ]; -}; - -const renderResults = ({ - totalCount, - deepOffset, - deepCursor, - results, -}: { - totalCount: number; - deepOffset: number; - deepCursor: { v: 0; t: number; id: string }; - results: CellResult[]; -}): string => { - const date = new Date().toISOString().slice(0, 10); - const lines: string[] = []; - - lines.push(`# Pagination Benchmark — ${date} — prod / ${LABEL}`); - lines.push(""); - lines.push("## Config"); - lines.push(""); - lines.push(`- org_id: \`${ORG_ID}\``); - lines.push(`- env: \`${ENV}\``); - lines.push(`- revenuecat_customers: \`${totalCount.toLocaleString()}\``); - lines.push( - `- deep_offset (${DEEP_OFFSET_PCT}% within revcat subset): \`${deepOffset.toLocaleString()}\``, - ); - lines.push( - `- deep_cursor (revcat-aware): \`{ t: ${deepCursor.t}, id: ${deepCursor.id} }\``, - ); - lines.push(`- limit: \`${BASE_LIMIT}\``); - lines.push(`- repeats per cell: ${REPEATS}`); - lines.push(`- statement_timeout_ms: ${STATEMENT_TIMEOUT_MS}`); - lines.push(`- read_only: ${READ_ONLY}`); - lines.push(`- focus: revenuecat filter (single SQL call, cursor + EXISTS combined)`); - lines.push(`- partial index in place: idx_customer_products_revenuecat_processor`); - lines.push(""); - - lines.push("## Results"); - lines.push(""); - lines.push("| # | Cell | Rows | median ms | p95 ms | min ms | max ms | Error |"); - lines.push("|---|------|------|-----------|--------|--------|--------|-------|"); - for (const r of results) { - const idx = r.name.split(" ")[0]; - const desc = r.name.split(" ").slice(1).join(" "); - const errCol = r.error ? `\`${r.error.slice(0, 80)}\`` : ""; - lines.push( - `| ${idx} | ${desc} | ${r.rowCount} | ${r.medianMs.toFixed(2)} | ${r.p95Ms.toFixed(2)} | ${r.minMs.toFixed(2)} | ${r.maxMs.toFixed(2)} | ${errCol} |`, - ); - } - lines.push(""); - - lines.push("## EXPLAIN ANALYZE"); - lines.push(""); - for (const r of results) { - if (!r.explainPlan) continue; - lines.push(`### ${r.name}`); - lines.push(""); - lines.push("```"); - lines.push(r.explainPlan); - lines.push("```"); - lines.push(""); - } - - return lines.join("\n"); -}; - -const main = async () => { - console.log( - chalk.magentaBright( - `\n================ Pagination Benchmark — prod / ${LABEL} ================\n`, - ), - ); - - const { db, client } = initDrizzle(); - - try { - console.log(chalk.cyan(`Org: ${ORG_ID} (${ENV})`)); - console.log(chalk.cyan("Resolving revenuecat customer count...")); - const totalCount = await resolveRevenuecatCount({ db }); - const deepOffset = Math.floor((totalCount * DEEP_OFFSET_PCT) / 100); - console.log( - chalk.gray( - ` revenuecat_customers = ${totalCount.toLocaleString()}, deep_offset (${DEEP_OFFSET_PCT}% within revcat) = ${deepOffset.toLocaleString()}`, - ), - ); - - console.log( - chalk.cyan(`Resolving revcat-aware deep cursor at offset ${deepOffset}...`), - ); - const deepCursor = await resolveRevenuecatDeepCursor({ db, deepOffset }); - console.log( - chalk.gray(` cursor = { t: ${deepCursor.t}, id: ${deepCursor.id} }`), - ); - console.log(); - - const cells = buildCells({ deepCursor }); - const results: CellResult[] = []; - - for (let i = 0; i < cells.length; i++) { - const cell = cells[i]; - process.stdout.write( - chalk.gray(`[${i + 1}/${cells.length}] ${cell.name} ... `), - ); - const result = await measureCell({ db, cell }); - results.push(result); - if (result.error) { - console.log(chalk.red(`error: ${result.error.slice(0, 80)}`)); - } else { - console.log( - chalk.green( - `median=${result.medianMs.toFixed(2)}ms p95=${result.p95Ms.toFixed(2)}ms rows=${result.rowCount}`, - ), - ); - } - } - - const report = renderResults({ - totalCount, - deepOffset, - deepCursor, - results, - }); - const date = new Date().toISOString().slice(0, 10); - const outPath = join(RESULTS_DIR, `${date}-prod-${LABEL}.md`); - writeFileSync(outPath, report); - - console.log(); - console.log(chalk.green(`✅ Report written to ${outPath}`)); - console.log( - chalk.magentaBright( - `\n================ Benchmark Complete ================\n`, - ), - ); - } catch (error) { - console.error(chalk.red("\n❌ Benchmark failed:")); - if (error instanceof Error) { - console.error(chalk.red(` ${error.message}`)); - console.error(chalk.gray(error.stack)); - } else { - console.error(chalk.red(` ${String(error)}`)); - } - process.exit(1); - } finally { - await client.end(); - process.exit(0); - } -}; - -await main(); diff --git a/server/experiments/paginationBenchmarkDbStats.ts b/server/experiments/paginationBenchmarkDbStats.ts deleted file mode 100644 index 927201503..000000000 --- a/server/experiments/paginationBenchmarkDbStats.ts +++ /dev/null @@ -1,678 +0,0 @@ -import { AppEnv, CusProductStatus, type ListCustomersV2Params } from "@autumn/shared"; -import chalk from "chalk"; -import { sql, type SQL } from "drizzle-orm"; -import { writeFileSync } from "node:fs"; -import { join } from "node:path"; -import { initDrizzle } from "../src/db/initDrizzle"; -import { getCursorPaginatedFullCusQuery } from "../src/internal/customers/cursorPaginatedFullCusQuery"; -import { getPaginatedFullCusQuery } from "../src/internal/customers/getFullCusQuery"; -import { RELEVANT_STATUSES } from "../src/internal/customers/cusProducts/CusProductService"; -import { loadLocalEnv } from "../src/utils/envUtils"; - -loadLocalEnv(); - -// Runs against whatever DATABASE_URL points at. Intentionally bypasses -// the prod-safety guard — laptop → prod is the entire point. - -const FIRECRAWL_ORG_ID = "biu9vSF7vghBLSKW1UTDwxHBAivjnPaK"; -const RUNABLE_ORG_ID = "r7pwHiekGsqt32qGqcVku6thWFh5aHh8"; -const ENV: AppEnv = AppEnv.Live; - -const BASE_LIMIT = 1000; -const REPEATS = 5; -const STATEMENT_TIMEOUT_MS = 60_000; -const READ_ONLY = true; -const CUS_PRODUCT_LIMIT = 15; -const DEEP_OFFSET_PCT = 45; -const FIRECRAWL_BASELINE_DEEP_OFFSET = 950_000; - -const PLAN_MASSIVE = "free"; -const PLAN_MID = "hobby"; -const PLAN_RARE = "scale_monthly"; -const SEARCH_GMAIL = "@gmail"; - -const RESULTS_DIR = join(import.meta.dir, "results"); - -type DB = ReturnType["db"]; -type Cursor = { v: 0; t: number; id: string }; - -type FilterOverrides = { - inStatuses?: CusProductStatus[]; - search?: string; - plans?: ListCustomersV2Params["plans"]; - processors?: ListCustomersV2Params["processors"]; - internalCustomerIds?: string[]; -}; - -type Scenario = { - key: string; - label: string; - org: "firecrawl" | "runable"; - orgId: string; - filter: FilterOverrides; - withDeep: boolean; - deepOffset?: number; - deepCursor?: Cursor; -}; - -type Cell = { - scenarioKey: string; - scenarioLabel: string; - org: "firecrawl" | "runable"; - queryShape: "offset" | "cursor"; - depth: "page1" | "deep"; - build: () => SQL; -}; - -type CellResult = Cell & { - medianMs: number; - p95Ms: number; - minMs: number; - maxMs: number; - rowCount: number; - samples: number[]; - error?: string; -}; - -const sharedFullCusOpts = { - includeInvoices: false, - withEntities: false, - withTrialsUsed: false, - withSubs: true, - cusProductLimit: CUS_PRODUCT_LIMIT, -}; - -const normalizeRows = (r: unknown): Record[] => { - if (Array.isArray(r)) return r as Record[]; - if (r && typeof r === "object" && "rows" in r) { - return (r as { rows: Record[] }).rows; - } - return []; -}; - -const runQueryInTxn = async ({ - db, - query, -}: { db: DB; query: SQL }): Promise[]> => { - return await db.transaction(async (tx) => { - await tx.execute( - sql.raw(`SET LOCAL statement_timeout = ${STATEMENT_TIMEOUT_MS}`), - ); - if (READ_ONLY) { - await tx.execute(sql.raw("SET LOCAL transaction_read_only = on")); - } - return normalizeRows(await tx.execute(query)); - }); -}; - -const measureCell = async ({ - db, - cell, -}: { db: DB; cell: Cell }): Promise => { - const samples: number[] = []; - let rowCount = 0; - let error: string | undefined; - - for (let i = 0; i < REPEATS; i++) { - const query = cell.build(); - const startedAt = performance.now(); - try { - const result = await runQueryInTxn({ db, query }); - const elapsed = performance.now() - startedAt; - samples.push(elapsed); - rowCount = result.length; - } catch (err) { - const elapsed = performance.now() - startedAt; - samples.push(elapsed); - error = err instanceof Error ? err.message : String(err); - break; - } - } - - const sorted = [...samples].sort((a, b) => a - b); - const median = sorted[Math.floor(sorted.length / 2)] ?? 0; - const p95 = - sorted[Math.min(sorted.length - 1, Math.floor(sorted.length * 0.95))] ?? 0; - const min = sorted[0] ?? 0; - const max = sorted[sorted.length - 1] ?? 0; - - return { - ...cell, - medianMs: median, - p95Ms: p95, - minMs: min, - maxMs: max, - rowCount, - samples, - error, - }; -}; - -// Per-filter predicate matching getCustomerListFilterSql, used by the -// resolver helpers (count + deep cursor) so deep cells are filter-aware. -const filterPredicate = (f: FilterOverrides): SQL => { - const parts: SQL[] = []; - - if (f.internalCustomerIds?.length) { - parts.push( - sql`AND c.internal_id IN (${sql.join( - f.internalCustomerIds.map((id) => sql`${id}`), - sql`, `, - )})`, - ); - } - - if (f.plans?.length) { - const planConditions = f.plans.map((plan) => { - if (plan.versions?.length) { - return sql`(p_filter.id = ${plan.id} AND p_filter.version IN (${sql.join( - plan.versions.map((v) => sql`${v}`), - sql`, `, - )}))`; - } - return sql`p_filter.id = ${plan.id}`; - }); - parts.push(sql`AND EXISTS ( - SELECT 1 - FROM customer_products cp_filter - JOIN products p_filter ON cp_filter.internal_product_id = p_filter.internal_id - WHERE cp_filter.internal_customer_id = c.internal_id - ${ - f.inStatuses?.length - ? sql`AND cp_filter.status = ANY(ARRAY[${sql.join( - f.inStatuses.map((s) => sql`${s}`), - sql`, `, - )}])` - : sql`` - } - AND (${sql.join(planConditions, sql` OR `)}) - )`); - } - - const trimmedSearch = f.search?.trim(); - if (trimmedSearch) { - const pattern = `%${trimmedSearch}%`; - parts.push(sql`AND ( - c.id ILIKE ${pattern} - OR c.name ILIKE ${pattern} - OR c.email ILIKE ${pattern} - )`); - } - - if (f.processors?.length) { - const procConditions = f.processors - .map((proc) => { - if (proc === "stripe") return sql`(c.processor->>'id' IS NOT NULL)`; - if (proc === "revenuecat") - return sql`EXISTS ( - SELECT 1 - FROM customer_products cp_processor - WHERE cp_processor.internal_customer_id = c.internal_id - AND cp_processor.processor->>'type' = 'revenuecat' - )`; - if (proc === "vercel") return sql`(c.processors->>'vercel' IS NOT NULL)`; - return null; - }) - .filter((c): c is SQL => c !== null); - - if (procConditions.length) { - parts.push(sql`AND (${sql.join(procConditions, sql` OR `)})`); - } - } - - return parts.length ? sql.join(parts, sql` `) : sql``; -}; - -const resolveCount = async ({ - db, - orgId, - filter, -}: { db: DB; orgId: string; filter: FilterOverrides }): Promise => { - const predicate = filterPredicate(filter); - const rows = await runQueryInTxn({ - db, - query: sql` - SELECT COUNT(*)::int AS total_count - FROM customers c - WHERE c.org_id = ${orgId} - AND c.env = ${ENV} - ${predicate} - `, - }); - return (rows[0] as { total_count: number }).total_count; -}; - -const resolveDeepCursor = async ({ - db, - orgId, - filter, - deepOffset, -}: { - db: DB; - orgId: string; - filter: FilterOverrides; - deepOffset: number; -}): Promise => { - const predicate = filterPredicate(filter); - const rows = await runQueryInTxn({ - db, - query: sql` - SELECT c.created_at, c.id - FROM customers c - WHERE c.org_id = ${orgId} - AND c.env = ${ENV} - ${predicate} - ORDER BY c.created_at DESC, c.id DESC - LIMIT 1 OFFSET ${deepOffset} - `, - }); - const row = rows[0] as { created_at: number; id: string } | undefined; - if (!row) { - throw new Error( - `No row at deep offset ${deepOffset} for filter ${JSON.stringify(filter)}`, - ); - } - return { v: 0, t: row.created_at, id: row.id }; -}; - -const resolveFirecrawlInternalIds = async ({ - db, -}: { db: DB }): Promise => { - const rows = await runQueryInTxn({ - db, - query: sql` - SELECT internal_id - FROM customers - WHERE org_id = ${FIRECRAWL_ORG_ID} - AND env = ${ENV} - ORDER BY created_at DESC, id DESC - LIMIT 10 - `, - }); - return rows.map((r) => (r as { internal_id: string }).internal_id); -}; - -const buildScenarios = ({ - firecrawlInternalIds, -}: { firecrawlInternalIds: string[] }): Scenario[] => { - const fc = ( - key: string, - label: string, - filter: FilterOverrides, - withDeep: boolean, - ): Scenario => ({ - key, - label, - org: "firecrawl", - orgId: FIRECRAWL_ORG_ID, - filter, - withDeep, - }); - - return [ - fc("01-baseline", "none (baseline)", {}, true), - fc("02-search-gmail", `search '${SEARCH_GMAIL}'`, { search: SEARCH_GMAIL }, true), - fc( - "03-status-active", - "inStatuses=['active']", - { inStatuses: [CusProductStatus.Active] }, - true, - ), - fc("04-plan-massive", `plans=['${PLAN_MASSIVE}']`, { plans: [{ id: PLAN_MASSIVE }] }, true), - fc("05-plan-mid", `plans=['${PLAN_MID}']`, { plans: [{ id: PLAN_MID }] }, true), - fc("06-plan-rare", `plans=['${PLAN_RARE}']`, { plans: [{ id: PLAN_RARE }] }, false), - fc("07-processor-stripe", "processors=['stripe']", { processors: ["stripe"] }, true), - fc( - "08-internal-ids", - "internalCustomerIds=[10]", - { internalCustomerIds: firecrawlInternalIds }, - false, - ), - { - key: "09-processor-revenuecat", - label: "processors=['revenuecat']", - org: "runable", - orgId: RUNABLE_ORG_ID, - filter: { processors: ["revenuecat"] }, - withDeep: false, - }, - ]; -}; - -const buildCellsForScenario = (s: Scenario): Cell[] => { - const inStatuses = s.filter.inStatuses ?? RELEVANT_STATUSES; - const filterArgs = { - inStatuses, - search: s.filter.search, - plans: s.filter.plans, - processors: s.filter.processors, - internalCustomerIds: s.filter.internalCustomerIds, - }; - - const cells: Cell[] = [ - { - scenarioKey: s.key, - scenarioLabel: s.label, - org: s.org, - queryShape: "offset", - depth: "page1", - build: () => - getPaginatedFullCusQuery({ - orgId: s.orgId, - env: ENV, - limit: BASE_LIMIT, - offset: 0, - ...sharedFullCusOpts, - ...filterArgs, - }), - }, - { - scenarioKey: s.key, - scenarioLabel: s.label, - org: s.org, - queryShape: "cursor", - depth: "page1", - build: () => - getCursorPaginatedFullCusQuery({ - orgId: s.orgId, - env: ENV, - limit: BASE_LIMIT, - ...sharedFullCusOpts, - ...filterArgs, - }), - }, - ]; - - if (s.withDeep && s.deepOffset !== undefined && s.deepCursor) { - const deepOffset = s.deepOffset; - const deepCursor = s.deepCursor; - cells.push( - { - scenarioKey: s.key, - scenarioLabel: s.label, - org: s.org, - queryShape: "offset", - depth: "deep", - build: () => - getPaginatedFullCusQuery({ - orgId: s.orgId, - env: ENV, - limit: BASE_LIMIT, - offset: deepOffset, - ...sharedFullCusOpts, - ...filterArgs, - }), - }, - { - scenarioKey: s.key, - scenarioLabel: s.label, - org: s.org, - queryShape: "cursor", - depth: "deep", - build: () => - getCursorPaginatedFullCusQuery({ - orgId: s.orgId, - env: ENV, - limit: BASE_LIMIT, - cursor: deepCursor, - ...sharedFullCusOpts, - ...filterArgs, - }), - }, - ); - } - - return cells; -}; - -const pairResults = ( - results: CellResult[], -): { - scenarioKey: string; - scenarioLabel: string; - org: string; - depth: "page1" | "deep"; - offset?: CellResult; - cursor?: CellResult; -}[] => { - const groups = new Map< - string, - { - scenarioKey: string; - scenarioLabel: string; - org: string; - depth: "page1" | "deep"; - offset?: CellResult; - cursor?: CellResult; - } - >(); - for (const r of results) { - const key = `${r.scenarioKey}|${r.depth}`; - const group = groups.get(key) ?? { - scenarioKey: r.scenarioKey, - scenarioLabel: r.scenarioLabel, - org: r.org, - depth: r.depth, - }; - if (r.queryShape === "offset") group.offset = r; - else group.cursor = r; - groups.set(key, group); - } - return Array.from(groups.values()); -}; - -const fmt = (n: number | undefined): string => - n === undefined ? "—" : n.toFixed(0); - -const renderReport = ({ - scenarios, - pairs, - totalWallMs, - dbHost, -}: { - scenarios: Scenario[]; - pairs: ReturnType; - totalWallMs: number; - dbHost: string; -}): string => { - const date = new Date().toISOString().slice(0, 10); - const lines: string[] = []; - - lines.push(`# Pagination Benchmark — offset vs cursor — ${date}`); - lines.push(""); - lines.push("## Config"); - lines.push(""); - lines.push(`- db host: \`${dbHost}\``); - lines.push(`- env: \`${ENV}\``); - lines.push(`- limit: ${BASE_LIMIT}`); - lines.push(`- repeats per cell: ${REPEATS}`); - lines.push(`- statement_timeout_ms: ${STATEMENT_TIMEOUT_MS}`); - lines.push(`- read_only: ${READ_ONLY}`); - lines.push(`- shared opts: withSubs=true, includeInvoices=false, withEntities=false, withTrialsUsed=false, cusProductLimit=${CUS_PRODUCT_LIMIT}`); - lines.push(`- timings are wall-clock from this laptop around drizzle execute (includes network round-trip + result transfer + deserialize)`); - lines.push(`- total benchmark wall time: ${(totalWallMs / 1000).toFixed(1)}s`); - lines.push(""); - - lines.push("## Scenarios resolved"); - lines.push(""); - lines.push("| # | Org | Filter | Filtered count | Deep offset | Deep cursor |"); - lines.push("|---|-----|--------|----------------|-------------|-------------|"); - for (const s of scenarios) { - const count = (s as Scenario & { resolvedCount?: number }).resolvedCount; - lines.push( - `| ${s.key} | ${s.org} | \`${s.label}\` | ${count?.toLocaleString() ?? "—"} | ${s.deepOffset?.toLocaleString() ?? "—"} | ${s.deepCursor ? `\`{ t: ${s.deepCursor.t}, id: ${s.deepCursor.id.slice(0, 8)}… }\`` : "—"} |`, - ); - } - lines.push(""); - - lines.push("## Results"); - lines.push(""); - lines.push( - "| # | Org | Filter | Depth | offset median ms | cursor median ms | Δ ms | offset p95 | cursor p95 | offset rows | cursor rows | offset error | cursor error |", - ); - lines.push( - "|---|-----|--------|-------|------------------|------------------|------|------------|------------|-------------|-------------|--------------|--------------|", - ); - for (const p of pairs) { - const off = p.offset; - const cur = p.cursor; - const delta = - off && cur && !off.error && !cur.error - ? cur.medianMs - off.medianMs - : undefined; - const deltaStr = - delta === undefined - ? "—" - : `${delta >= 0 ? "+" : ""}${delta.toFixed(0)}`; - lines.push( - `| ${p.scenarioKey} | ${p.org} | \`${p.scenarioLabel}\` | ${p.depth} | ${fmt(off?.medianMs)} | ${fmt(cur?.medianMs)} | ${deltaStr} | ${fmt(off?.p95Ms)} | ${fmt(cur?.p95Ms)} | ${off?.rowCount ?? "—"} | ${cur?.rowCount ?? "—"} | ${off?.error ? `\`${off.error.slice(0, 50)}\`` : ""} | ${cur?.error ? `\`${cur.error.slice(0, 50)}\`` : ""} |`, - ); - } - lines.push(""); - - lines.push("## Raw samples (per cell)"); - lines.push(""); - for (const p of pairs) { - lines.push(`### ${p.scenarioKey} / ${p.depth}`); - if (p.offset) { - lines.push( - `- offset: [${p.offset.samples.map((s) => s.toFixed(0)).join(", ")}]ms${p.offset.error ? ` (error: ${p.offset.error})` : ""}`, - ); - } - if (p.cursor) { - lines.push( - `- cursor: [${p.cursor.samples.map((s) => s.toFixed(0)).join(", ")}]ms${p.cursor.error ? ` (error: ${p.cursor.error})` : ""}`, - ); - } - lines.push(""); - } - - return lines.join("\n"); -}; - -const redactHost = (url: string | undefined): string => { - if (!url) return ""; - try { - const u = new URL(url); - return `${u.hostname}:${u.port || "5432"}`; - } catch { - return ""; - } -}; - -const main = async () => { - const startedAt = performance.now(); - console.log( - chalk.magentaBright( - "\n================ Pagination Benchmark — offset vs cursor (prod DB) ================\n", - ), - ); - - const dbHost = redactHost(process.env.DATABASE_URL); - console.log(chalk.cyan(`db host: ${dbHost}`)); - - const { db, client } = initDrizzle(); - - try { - console.log(chalk.cyan("Resolving Firecrawl internal_ids for IN-list cell...")); - const firecrawlInternalIds = await resolveFirecrawlInternalIds({ db }); - console.log(chalk.gray(` got ${firecrawlInternalIds.length} ids`)); - - const scenarios = buildScenarios({ firecrawlInternalIds }); - - // Resolve counts and deep cursors for each scenario before running cells. - for (const s of scenarios) { - process.stdout.write(chalk.gray(`Resolving count for ${s.key} (${s.label})... `)); - const count = await resolveCount({ db, orgId: s.orgId, filter: s.filter }); - (s as Scenario & { resolvedCount?: number }).resolvedCount = count; - console.log(chalk.gray(`${count.toLocaleString()} customers`)); - - if (s.withDeep) { - const deepOffset = - s.key === "01-baseline" - ? FIRECRAWL_BASELINE_DEEP_OFFSET - : Math.floor((count * DEEP_OFFSET_PCT) / 100); - s.deepOffset = deepOffset; - process.stdout.write( - chalk.gray(` resolving deep cursor at offset ${deepOffset}... `), - ); - try { - s.deepCursor = await resolveDeepCursor({ - db, - orgId: s.orgId, - filter: s.filter, - deepOffset, - }); - console.log( - chalk.gray( - `{ t: ${s.deepCursor.t}, id: ${s.deepCursor.id.slice(0, 8)}… }`, - ), - ); - } catch (err) { - console.log( - chalk.yellow( - `failed: ${err instanceof Error ? err.message : String(err)}`, - ), - ); - s.withDeep = false; - } - } - } - - const cells = scenarios.flatMap(buildCellsForScenario); - console.log(); - console.log(chalk.cyan(`Running ${cells.length} cells × ${REPEATS} repeats...`)); - console.log(); - - const results: CellResult[] = []; - for (let i = 0; i < cells.length; i++) { - const cell = cells[i]; - process.stdout.write( - chalk.gray( - `[${(i + 1).toString().padStart(2)}/${cells.length}] ${cell.scenarioKey} ${cell.queryShape} ${cell.depth} ... `, - ), - ); - const result = await measureCell({ db, cell }); - results.push(result); - if (result.error) { - console.log(chalk.red(`error: ${result.error.slice(0, 80)}`)); - } else { - console.log( - chalk.green( - `median=${result.medianMs.toFixed(0)}ms p95=${result.p95Ms.toFixed(0)}ms rows=${result.rowCount}`, - ), - ); - } - } - - const pairs = pairResults(results); - const totalWallMs = performance.now() - startedAt; - const report = renderReport({ scenarios, pairs, totalWallMs, dbHost }); - - const date = new Date().toISOString().slice(0, 10); - const outPath = join(RESULTS_DIR, `${date}-offset-vs-cursor.md`); - writeFileSync(outPath, report); - - console.log(); - console.log(chalk.green(`✅ Report written to ${outPath}`)); - console.log( - chalk.magentaBright( - `\n================ Benchmark Complete (${(totalWallMs / 1000).toFixed(1)}s) ================\n`, - ), - ); - } catch (error) { - console.error(chalk.red("\n❌ Benchmark failed:")); - if (error instanceof Error) { - console.error(chalk.red(` ${error.message}`)); - console.error(chalk.gray(error.stack)); - } else { - console.error(chalk.red(` ${String(error)}`)); - } - process.exit(1); - } finally { - await client.end(); - process.exit(0); - } -}; - -await main(); diff --git a/server/experiments/sqlBenchUnitTestOrg.ts b/server/experiments/sqlBenchUnitTestOrg.ts deleted file mode 100644 index 3210f5393..000000000 --- a/server/experiments/sqlBenchUnitTestOrg.ts +++ /dev/null @@ -1,304 +0,0 @@ -import { AppEnv } from "@autumn/shared"; -import chalk from "chalk"; -import { sql, type SQL } from "drizzle-orm"; -import { RELEVANT_STATUSES } from "../src/internal/customers/cusProducts/CusProductService"; -import { initDrizzle } from "../src/db/initDrizzle"; -import { loadLocalEnv } from "../src/utils/envUtils"; -import { getCursorPaginatedFullCusQuery } from "../src/internal/customers/cursorPaginatedFullCusQuery"; -import { getOptimizedFullCusQuery } from "./optimizedFullCusQuery"; - -loadLocalEnv(); - -const ORG_ID = "org_2sWv2S8LJ9iaTjLI6UtNsfL88Kt"; -const ENV: AppEnv = AppEnv.Sandbox; -const REPEATS = 5; -const STATEMENT_TIMEOUT_MS = 60_000; -const DEEP_OFFSET_PCT = parseInt(process.env.OFFSET_PCT ?? "60", 10); -const LIMIT = parseInt(process.env.LIMIT ?? "1000", 10); -const CUS_PRODUCT_LIMIT = 15; -const SHOW_EXPLAIN = process.env.EXPLAIN !== "false"; -const SKIP_CURRENT = process.env.SKIP_CURRENT === "true"; -const SKIP_OPTIMIZED = process.env.SKIP_OPTIMIZED === "true"; -const DASHBOARD_MODE = process.env.DASHBOARD === "true"; - -type DB = ReturnType["db"]; - -const normalizeRows = (r: unknown): Record[] => { - if (Array.isArray(r)) return r as Record[]; - if (r && typeof r === "object" && "rows" in r) { - return (r as { rows: Record[] }).rows; - } - return []; -}; - -const runInTxn = async ({ db, query }: { db: DB; query: SQL }) => { - return db.transaction(async (tx) => { - await tx.execute( - sql.raw(`SET LOCAL statement_timeout = ${STATEMENT_TIMEOUT_MS}`), - ); - await tx.execute(sql.raw("SET LOCAL transaction_read_only = on")); - return normalizeRows(await tx.execute(query)); - }); -}; - -const resolveTotalCount = async ({ db }: { db: DB }) => { - const result = await runInTxn({ - db, - query: sql` - SELECT COUNT(*)::int AS total_count - FROM customers c - WHERE c.org_id = ${ORG_ID} AND c.env = ${ENV} - `, - }); - return (result[0] as { total_count: number }).total_count; -}; - -const resolveDeepCursor = async ({ - db, - offset, -}: { - db: DB; - offset: number; -}) => { - const result = await runInTxn({ - db, - query: sql` - SELECT c.created_at, c.id - FROM customers c - WHERE c.org_id = ${ORG_ID} AND c.env = ${ENV} - ORDER BY c.created_at DESC, c.id DESC - LIMIT 1 OFFSET ${offset} - `, - }); - const row = result[0] as { created_at: number; id: string } | undefined; - if (!row) throw new Error(`No customer at offset ${offset}`); - return { v: 0 as const, t: row.created_at, id: row.id }; -}; - -const buildQuery = ( - cursor?: { v: 0; t: number; id: string }, - variant: "current" | "optimized" = "current", - internalCustomerIds?: string[], -): SQL => { - const builder = - variant === "optimized" - ? getOptimizedFullCusQuery - : getCursorPaginatedFullCusQuery; - return builder({ - orgId: ORG_ID, - env: ENV, - inStatuses: RELEVANT_STATUSES, - withSubs: true, - limit: internalCustomerIds?.length ?? LIMIT, - cursor, - internalCustomerIds, - cusProductLimit: CUS_PRODUCT_LIMIT, - }); -}; - -const resolveInternalIds = async ({ - db, - offset, - limit, -}: { - db: DB; - offset: number; - limit: number; -}): Promise => { - const result = await runInTxn({ - db, - query: sql` - SELECT c.internal_id - FROM customers c - WHERE c.org_id = ${ORG_ID} AND c.env = ${ENV} - ORDER BY c.created_at DESC, c.id DESC - LIMIT ${limit} OFFSET ${offset} - `, - }); - return (result as unknown as { internal_id: string }[]).map( - (r) => r.internal_id, - ); -}; - -const measureRun = async ({ - db, - cursor, - label, - variant = "current", - internalCustomerIds, -}: { - db: DB; - cursor?: { v: 0; t: number; id: string }; - label: string; - variant?: "current" | "optimized"; - internalCustomerIds?: string[]; -}) => { - const samples: number[] = []; - for (let i = 0; i < REPEATS; i++) { - const t0 = performance.now(); - await runInTxn({ db, query: buildQuery(cursor, variant, internalCustomerIds) }); - const ms = performance.now() - t0; - samples.push(ms); - process.stdout.write(chalk.gray(` run ${i + 1}/${REPEATS}: ${ms.toFixed(0)}ms\n`)); - } - const sorted = [...samples].sort((a, b) => a - b); - const median = sorted[Math.floor(sorted.length / 2)] ?? 0; - const p95 = sorted[Math.min(sorted.length - 1, Math.floor(sorted.length * 0.95))] ?? 0; - const min = sorted[0] ?? 0; - const max = sorted[sorted.length - 1] ?? 0; - console.log( - chalk.bold( - ` ${label}: median=${median.toFixed(0)}ms p95=${p95.toFixed(0)}ms min=${min.toFixed(0)}ms max=${max.toFixed(0)}ms`, - ), - ); - return { median, p95, min, max, samples }; -}; - -const runExplain = async ({ - db, - cursor, - variant = "current", - internalCustomerIds, -}: { - db: DB; - cursor?: { v: 0; t: number; id: string }; - variant?: "current" | "optimized"; - internalCustomerIds?: string[]; -}) => { - const explain = await runInTxn({ - db, - query: sql`EXPLAIN (ANALYZE, BUFFERS, FORMAT TEXT) ${buildQuery(cursor, variant, internalCustomerIds)}`, - }); - return explain.map((r) => r["QUERY PLAN"]).join("\n"); -}; - -const main = async () => { - console.log( - chalk.magentaBright( - `\n================ SQL Bench — unit-test-org ================\n`, - ), - ); - - const { db, client } = initDrizzle(); - - try { - console.log(chalk.cyan(`Org: ${ORG_ID} Env: ${ENV} Limit: ${LIMIT}`)); - console.log(chalk.cyan("Resolving total customer count...")); - const total = await resolveTotalCount({ db }); - const deepOffset = Math.floor((total * DEEP_OFFSET_PCT) / 100); - console.log( - chalk.gray( - ` total=${total.toLocaleString()} deep_offset(${DEEP_OFFSET_PCT}%)=${deepOffset.toLocaleString()}`, - ), - ); - - console.log(chalk.cyan(`Resolving deep cursor at offset ${deepOffset}...`)); - const deepCursor = await resolveDeepCursor({ db, offset: deepOffset }); - console.log( - chalk.gray(` cursor = { t: ${deepCursor.t}, id: ${deepCursor.id} }\n`), - ); - - let curFirst: { median: number } | null = null; - let curDeep: { median: number } | null = null; - let optFirst: { median: number } | null = null; - let optDeep: { median: number } | null = null; - - let dashIds: string[] | undefined; - if (DASHBOARD_MODE) { - const offset = Math.floor((total * DEEP_OFFSET_PCT) / 100); - console.log( - chalk.cyan(`Dashboard mode: resolving ${LIMIT} internal_ids at offset ${offset}...`), - ); - dashIds = await resolveInternalIds({ db, offset, limit: LIMIT }); - console.log(chalk.gray(` got ${dashIds.length} ids\n`)); - } - - if (!SKIP_CURRENT) { - console.log(chalk.bold("→ CURRENT first page:")); - curFirst = await measureRun({ db, label: "current first" }); - console.log(chalk.bold(`→ CURRENT deep page${DASHBOARD_MODE ? " (internalCustomerIds)" : ""}:`)); - curDeep = await measureRun({ - db, - cursor: DASHBOARD_MODE ? undefined : deepCursor, - internalCustomerIds: DASHBOARD_MODE ? dashIds : undefined, - label: "current deep", - }); - } else { - console.log(chalk.gray("(SKIP_CURRENT=true — skipping current variant)")); - } - - if (!SKIP_OPTIMIZED) { - console.log(); - console.log(chalk.bold("→ OPTIMIZED first page:")); - optFirst = await measureRun({ - db, - label: "optim first", - variant: "optimized", - }); - console.log(chalk.bold(`→ OPTIMIZED deep page${DASHBOARD_MODE ? " (internalCustomerIds)" : ""}:`)); - optDeep = await measureRun({ - db, - cursor: DASHBOARD_MODE ? undefined : deepCursor, - internalCustomerIds: DASHBOARD_MODE ? dashIds : undefined, - label: "optim deep", - variant: "optimized", - }); - } else { - console.log(chalk.gray("(SKIP_OPTIMIZED=true — skipping optimized variant)")); - } - - if (SHOW_EXPLAIN) { - if (!SKIP_CURRENT) { - console.log(); - console.log( - chalk.magenta("================ EXPLAIN: CURRENT deep ================"), - ); - console.log(await runExplain({ db, cursor: deepCursor })); - } - if (!SKIP_OPTIMIZED) { - console.log(); - console.log( - chalk.magenta( - "================ EXPLAIN: OPTIMIZED deep ================", - ), - ); - console.log( - await runExplain({ db, cursor: deepCursor, variant: "optimized" }), - ); - } - } - - console.log(); - console.log(chalk.magentaBright("================ Summary ================")); - const fmtDelta = (oldMs: number, newMs: number) => { - const delta = ((newMs - oldMs) / oldMs) * 100; - const arrow = delta < 0 ? "↓" : "↑"; - return chalk[delta < 0 ? "green" : "red"]( - `${arrow}${Math.abs(delta).toFixed(0)}%`, - ); - }; - if (curFirst && optFirst) { - console.log( - ` first page current=${curFirst.median.toFixed(0)}ms optimized=${optFirst.median.toFixed(0)}ms ${fmtDelta(curFirst.median, optFirst.median)}`, - ); - console.log( - ` deep page current=${curDeep!.median.toFixed(0)}ms optimized=${optDeep!.median.toFixed(0)}ms ${fmtDelta(curDeep!.median, optDeep!.median)}`, - ); - } else if (curFirst) { - console.log(` first page current=${curFirst.median.toFixed(0)}ms`); - console.log(` deep page current=${curDeep!.median.toFixed(0)}ms`); - } else if (optFirst) { - console.log(` first page optimized=${optFirst.median.toFixed(0)}ms`); - console.log(` deep page optimized=${optDeep!.median.toFixed(0)}ms`); - } - } catch (err) { - console.error(chalk.red(`\n❌ ${err instanceof Error ? err.message : err}`)); - if (err instanceof Error && err.stack) console.error(chalk.gray(err.stack)); - process.exit(1); - } finally { - await client.end(); - process.exit(0); - } -}; - -await main(); diff --git a/server/experiments/sweepCursorNoSkip.ts b/server/experiments/sweepCursorNoSkip.ts deleted file mode 100644 index 1f3e19337..000000000 --- a/server/experiments/sweepCursorNoSkip.ts +++ /dev/null @@ -1,130 +0,0 @@ -import { AppEnv } from "@autumn/shared"; -import chalk from "chalk"; -import { sql } from "drizzle-orm"; -import { CusBatchService } from "../src/internal/customers/CusBatchService"; -import { initDrizzle } from "../src/db/initDrizzle"; -import { loadLocalEnv } from "../src/utils/envUtils"; - -loadLocalEnv(); - -const ORG_ID = "org_2sWv2S8LJ9iaTjLI6UtNsfL88Kt"; -const ENV = AppEnv.Sandbox; -const PAGE_SIZE = parseInt(process.env.PAGE_SIZE ?? "100", 10); -const MAX_PAGES = parseInt(process.env.MAX_PAGES ?? "50", 10); - -const minimalCtx = () => { - const { db, client } = initDrizzle(); - return { - db, - client, - org: { id: ORG_ID, slug: "unit-test-org" }, - env: ENV, - logger: { - info: () => {}, - error: () => {}, - warn: () => {}, - debug: () => {}, - trace: () => {}, - }, - } as any; -}; - -const main = async () => { - console.log( - chalk.magentaBright( - "\n================ Dashboard Cursor No-Skip Sweep ================\n", - ), - ); - - const ctx = minimalCtx(); - - try { - const seen = new Set(); - const duplicates: string[] = []; - const order: string[] = []; - - let cursor: { t: number; id: string } | null = null; - let pageNum = 0; - while (pageNum < MAX_PAGES) { - const res: { fullCustomers: any[]; next_cursor: string | null } = - await CusBatchService.getDashboardCursorPage({ - ctx, - search: "", - filters: undefined, - cursor, - limit: PAGE_SIZE, - }); - pageNum++; - - for (const c of res.fullCustomers) { - if (seen.has(c.internal_id)) duplicates.push(c.internal_id); - seen.add(c.internal_id); - order.push(c.internal_id); - } - - console.log( - chalk.gray( - ` page ${pageNum.toString().padStart(3)} rows=${res.fullCustomers.length.toString().padStart(4)} total=${seen.size.toString().padStart(6)} next=${res.next_cursor ? "yes" : "no"}`, - ), - ); - - if (!res.next_cursor) break; - const decoded = JSON.parse( - Buffer.from(res.next_cursor, "base64").toString("utf8"), - ); - cursor = { t: decoded.t, id: decoded.id }; - } - - const expected = ( - (await ctx.db.execute( - sql` - SELECT c.internal_id - FROM customers c - WHERE c.org_id = ${ORG_ID} AND c.env = ${ENV} - ORDER BY c.created_at DESC, c.id DESC - LIMIT ${pageNum * PAGE_SIZE} - `, - )) as unknown as { internal_id: string }[] - ).map((r) => r.internal_id); - - console.log(); - console.log( - chalk.bold( - ` Walked ${pageNum} pages, saw ${seen.size} unique customers (expected first ${expected.length}).`, - ), - ); - - if (duplicates.length > 0) { - console.log( - chalk.red(` ❌ ${duplicates.length} duplicates returned across pages`), - ); - } else { - console.log(chalk.green(` ✓ no duplicates`)); - } - - const missing: string[] = []; - for (const id of expected) if (!seen.has(id)) missing.push(id); - if (missing.length > 0) { - console.log( - chalk.red( - ` ❌ ${missing.length} customers skipped! First 5: ${missing.slice(0, 5).join(", ")}`, - ), - ); - } else { - console.log(chalk.green(` ✓ no customers skipped`)); - } - - const sameOrder = order.every((id, i) => id === expected[i]); - console.log( - sameOrder - ? chalk.green(` ✓ pagination order matches direct query`) - : chalk.yellow(` ⚠ pagination order differs from direct query`), - ); - - process.exit(missing.length === 0 && duplicates.length === 0 ? 0 : 1); - } finally { - await ctx.client.end(); - } -}; - -await main(); diff --git a/server/experiments/v07ReassemblyBenchmark.ts b/server/experiments/v07ReassemblyBenchmark.ts deleted file mode 100644 index 5f21ee5f6..000000000 --- a/server/experiments/v07ReassemblyBenchmark.ts +++ /dev/null @@ -1,463 +0,0 @@ -import chalk from "chalk"; -import { sql, type SQL } from "drizzle-orm"; -import { writeFileSync } from "node:fs"; -import { join } from "node:path"; -import { initDrizzle } from "../src/db/initDrizzle"; -import { loadLocalEnv } from "../src/utils/envUtils"; - -loadLocalEnv(); - -const ORG_ID = "biu9vSF7vghBLSKW1UTDwxHBAivjnPaK"; -const ENV = "live"; -const LABEL = "firecrawl-deep"; -const LIMIT = 1000; -const FETCH_LIMIT = LIMIT + 1; -const SQL_REPEATS = 1; -const REASSEMBLY_REPEATS = 20; -const STATEMENT_TIMEOUT_MS = 30_000; -const DEEP_CURSOR = { - t: 1774237983361, - id: "772c9569-fc97-4c30-9fd8-c8a585b66755", -}; - -const RESULTS_DIR = join(import.meta.dir, "results"); - -type DB = ReturnType["db"]; - -const buildVariant07Sql = (): SQL => sql` -WITH cr AS MATERIALIZED ( - SELECT - c.internal_id, - c.id, - c.created_at, - row_to_json(c) AS row_json - FROM customers c - WHERE c.org_id = ${ORG_ID} - AND c.env = ${ENV} - AND (c.created_at, c.id) < (${DEEP_CURSOR.t}, ${DEEP_CURSOR.id}) - ORDER BY c.created_at DESC, c.id DESC - LIMIT ${FETCH_LIMIT} -), -cps_flat AS MATERIALIZED ( - SELECT - cp.id, - cp.internal_customer_id, - cp.internal_product_id, - cp.free_trial_id, - cp.subscription_ids, - (row_to_json(cp)::jsonb || jsonb_build_object('product', row_to_json(prod)))::json AS row_json - FROM cr - JOIN LATERAL ( - SELECT cp.* - FROM customer_products cp - WHERE cp.internal_customer_id = cr.internal_id - AND cp.status = ANY(ARRAY['active', 'past_due', 'scheduled']) - ORDER BY cp.created_at DESC - LIMIT 15 - ) cp ON true - JOIN products prod ON cp.internal_product_id = prod.internal_id -), -ces_combined AS MATERIALIZED ( - SELECT 'bound'::text AS kind, ce.id, ce.entitlement_id, row_to_json(ce) AS row_json - FROM cps_flat - JOIN LATERAL ( - SELECT ce.* - FROM customer_entitlements ce - WHERE ce.customer_product_id = cps_flat.id - ) ce ON true - UNION ALL - SELECT 'loose'::text AS kind, ce.id, ce.entitlement_id, row_to_json(ce) AS row_json - FROM cr - JOIN LATERAL ( - SELECT ce.* - FROM customer_entitlements ce - WHERE ce.internal_customer_id = cr.internal_id - AND ce.customer_product_id IS NULL - AND (ce.expires_at IS NULL OR ce.expires_at > EXTRACT(EPOCH FROM now()) * 1000) - ORDER BY ce.id DESC - LIMIT 30 - ) ce ON true -), -arrays AS MATERIALIZED ( - SELECT - (SELECT array_agg(id) FROM ces_combined) AS all_ce_ids, - (SELECT array_agg(DISTINCT entitlement_id) FROM ces_combined) AS distinct_entitlement_ids, - (SELECT array_agg(DISTINCT free_trial_id) FILTER (WHERE free_trial_id IS NOT NULL) FROM cps_flat) AS free_trial_ids -) -SELECT - (SELECT COALESCE(json_agg(row_json), '[]'::json) FROM cr) AS customers, - (SELECT COALESCE(json_agg(row_json), '[]'::json) FROM cps_flat) AS customer_products, - (SELECT COALESCE(json_agg(row_json), '[]'::json) FROM ces_combined WHERE kind = 'bound') AS customer_entitlements, - (SELECT COALESCE(json_agg(row_json), '[]'::json) FROM ces_combined WHERE kind = 'loose') AS extra_customer_entitlements, - (SELECT COALESCE(json_agg(row_to_json(cpr)::jsonb || jsonb_build_object('price', row_to_json(p))), '[]'::json) - FROM cps_flat cps - JOIN LATERAL (SELECT cpr.* FROM customer_prices cpr WHERE cpr.customer_product_id = cps.id) cpr ON true - LEFT JOIN LATERAL (SELECT p.* FROM prices p WHERE p.id = cpr.price_id) p ON true) AS customer_prices, - (SELECT COALESCE(json_agg(row_to_json(e)::jsonb || jsonb_build_object('feature', row_to_json(f))), '[]'::json) - FROM unnest((SELECT distinct_entitlement_ids FROM arrays)) AS u(entitlement_id) - JOIN LATERAL (SELECT e.* FROM entitlements e WHERE e.id = u.entitlement_id) e ON true - JOIN LATERAL (SELECT f.* FROM features f WHERE f.internal_id = e.internal_feature_id) f ON true) AS entitlements, - (SELECT COALESCE(json_agg(row_to_json(ro)), '[]'::json) - FROM unnest((SELECT all_ce_ids FROM arrays)) AS u(ce_id) - JOIN LATERAL (SELECT ro.* FROM rollovers ro WHERE ro.cus_ent_id = u.ce_id AND (ro.expires_at IS NULL OR ro.expires_at > EXTRACT(EPOCH FROM now()) * 1000)) ro ON true) AS rollovers, - (SELECT COALESCE(json_agg(row_to_json(r)), '[]'::json) - FROM unnest((SELECT all_ce_ids FROM arrays)) AS u(ce_id) - JOIN LATERAL (SELECT r.* FROM replaceables r WHERE r.cus_ent_id = u.ce_id) r ON true) AS replaceables, - (SELECT COALESCE(json_agg(row_to_json(ft)), '[]'::json) - FROM unnest((SELECT free_trial_ids FROM arrays)) AS u(ft_id) - JOIN LATERAL (SELECT ft.* FROM free_trials ft WHERE ft.id = u.ft_id) ft ON true) AS free_trials, - (SELECT COALESCE(json_agg(row_to_json(s)), '[]'::json) - FROM ( - SELECT DISTINCT s.* - FROM cps_flat cps - CROSS JOIN LATERAL unnest(cps.subscription_ids) AS sub_id_t(sub_id) - JOIN LATERAL (SELECT s.* FROM subscriptions s WHERE s.stripe_id = sub_id_t.sub_id) s ON true - WHERE cps.subscription_ids IS NOT NULL - ) s) AS subscriptions -`; - -type V07Row = { - customers: any[]; - customer_products: any[]; - customer_entitlements: any[]; - extra_customer_entitlements: any[]; - customer_prices: any[]; - entitlements: any[]; - rollovers: any[]; - replaceables: any[]; - free_trials: any[]; - subscriptions: any[]; -}; - -const reassemble = (flat: V07Row) => { - const { - customers, - customer_products, - customer_entitlements, - extra_customer_entitlements, - customer_prices, - entitlements, - rollovers, - replaceables, - free_trials, - subscriptions, - } = flat; - - const entById = new Map(); - for (const e of entitlements) entById.set(e.id, e); - - const ftById = new Map(); - for (const ft of free_trials) ftById.set(ft.id, ft); - - const subByStripeId = new Map(); - for (const s of subscriptions) subByStripeId.set(s.stripe_id, s); - - const rolloversByCeId = new Map(); - for (const ro of rollovers) { - const list = rolloversByCeId.get(ro.cus_ent_id); - if (list) list.push(ro); - else rolloversByCeId.set(ro.cus_ent_id, [ro]); - } - - const replaceablesByCeId = new Map(); - for (const r of replaceables) { - const list = replaceablesByCeId.get(r.cus_ent_id); - if (list) list.push(r); - else replaceablesByCeId.set(r.cus_ent_id, [r]); - } - - const cpricesByCpId = new Map(); - for (const cpr of customer_prices) { - const list = cpricesByCpId.get(cpr.customer_product_id); - if (list) list.push(cpr); - else cpricesByCpId.set(cpr.customer_product_id, [cpr]); - } - - const hydrateCe = (ce: any) => ({ - ...ce, - entitlement: entById.get(ce.entitlement_id) ?? null, - rollovers: rolloversByCeId.get(ce.id) ?? [], - replaceables: replaceablesByCeId.get(ce.id) ?? [], - }); - - const cesByCpId = new Map(); - for (const ce of customer_entitlements) { - const hydrated = hydrateCe(ce); - const list = cesByCpId.get(ce.customer_product_id); - if (list) list.push(hydrated); - else cesByCpId.set(ce.customer_product_id, [hydrated]); - } - - const looseCesByCusId = new Map(); - for (const ce of extra_customer_entitlements) { - const hydrated = hydrateCe(ce); - const list = looseCesByCusId.get(ce.internal_customer_id); - if (list) list.push(hydrated); - else looseCesByCusId.set(ce.internal_customer_id, [hydrated]); - } - - const cpsByCusId = new Map(); - for (const cp of customer_products) { - const hydrated = { - ...cp, - customer_prices: cpricesByCpId.get(cp.id) ?? [], - customer_entitlements: cesByCpId.get(cp.id) ?? [], - free_trial: cp.free_trial_id ? (ftById.get(cp.free_trial_id) ?? null) : null, - }; - const list = cpsByCusId.get(cp.internal_customer_id); - if (list) list.push(hydrated); - else cpsByCusId.set(cp.internal_customer_id, [hydrated]); - } - - const subsByCusId = new Map(); - for (const cp of customer_products) { - if (!cp.subscription_ids?.length) continue; - const existing = subsByCusId.get(cp.internal_customer_id) ?? []; - const seen = new Set(existing.map((s: any) => s.stripe_id)); - for (const subId of cp.subscription_ids) { - if (seen.has(subId)) continue; - const sub = subByStripeId.get(subId); - if (sub) { - existing.push(sub); - seen.add(subId); - } - } - if (existing.length) subsByCusId.set(cp.internal_customer_id, existing); - } - - const result: any[] = []; - for (const c of customers) { - result.push({ - ...c, - customer_products: cpsByCusId.get(c.internal_id) ?? [], - extra_customer_entitlements: looseCesByCusId.get(c.internal_id) ?? [], - subscriptions: subsByCusId.get(c.internal_id) ?? [], - }); - } - return result; -}; - -const runQueryInTxn = async ({ - db, - query, -}: { - db: DB; - query: SQL; -}): Promise[]> => { - return await db.transaction(async (tx) => { - await tx.execute( - sql.raw(`SET LOCAL statement_timeout = ${STATEMENT_TIMEOUT_MS}`), - ); - await tx.execute(sql.raw("SET LOCAL transaction_read_only = on")); - const result = await tx.execute(query); - return result as unknown as Record[]; - }); -}; - -const summarise = (samples: number[]) => { - const sorted = [...samples].sort((a, b) => a - b); - return { - min: sorted[0] ?? 0, - median: sorted[Math.floor(sorted.length / 2)] ?? 0, - p95: sorted[Math.min(sorted.length - 1, Math.floor(sorted.length * 0.95))] ?? 0, - max: sorted[sorted.length - 1] ?? 0, - samples: sorted, - }; -}; - -const main = async () => { - console.log( - chalk.magentaBright( - `\n================ Variant 07 + Reassembly Benchmark — ${LABEL} ================\n`, - ), - ); - - const { db, client } = initDrizzle(); - - try { - console.log(chalk.cyan(`Org: ${ORG_ID} (${ENV})`)); - console.log( - chalk.cyan( - `Deep cursor: t=${DEEP_CURSOR.t} id=${DEEP_CURSOR.id} limit=${LIMIT}\n`, - ), - ); - - // Step 1 — EXPLAIN ANALYZE pass for server-side SQL execution time - console.log(chalk.cyan("[1/3] EXPLAIN (ANALYZE, FORMAT JSON) — server-side ms")); - const explainSamples: number[] = []; - let lastExplainPlan: any = null; - for (let i = 0; i < SQL_REPEATS; i++) { - const explainRows = await runQueryInTxn({ - db, - query: sql`EXPLAIN (ANALYZE, BUFFERS, FORMAT JSON) ${buildVariant07Sql()}`, - }); - const plan = (explainRows[0] as { "QUERY PLAN": any })["QUERY PLAN"]; - const planObj = Array.isArray(plan) ? plan[0] : plan; - const execTime = planObj["Execution Time"] as number; - explainSamples.push(execTime); - lastExplainPlan = planObj; - process.stdout.write(chalk.gray(` run ${i + 1}: ${execTime.toFixed(2)}ms\n`)); - } - const sqlSummary = summarise(explainSamples); - - // Step 2 — actual query to capture rows for reassembly - console.log(chalk.cyan("\n[2/3] Executing variant 07 to capture row payload")); - const queryStart = performance.now(); - const rows = await runQueryInTxn({ db, query: buildVariant07Sql() }); - const queryWallMs = performance.now() - queryStart; - const flatRow = rows[0] as unknown as V07Row; - - const counts = { - customers: flatRow.customers?.length ?? 0, - customer_products: flatRow.customer_products?.length ?? 0, - customer_entitlements: flatRow.customer_entitlements?.length ?? 0, - extra_customer_entitlements: flatRow.extra_customer_entitlements?.length ?? 0, - customer_prices: flatRow.customer_prices?.length ?? 0, - entitlements: flatRow.entitlements?.length ?? 0, - rollovers: flatRow.rollovers?.length ?? 0, - replaceables: flatRow.replaceables?.length ?? 0, - free_trials: flatRow.free_trials?.length ?? 0, - subscriptions: flatRow.subscriptions?.length ?? 0, - }; - console.log( - chalk.gray(` client-side wall (incl. UK→US network): ${queryWallMs.toFixed(2)}ms`), - ); - console.log(chalk.gray(` row counts: ${JSON.stringify(counts)}`)); - - const payloadBytes = Buffer.byteLength(JSON.stringify(flatRow), "utf8"); - console.log( - chalk.gray(` payload size: ${(payloadBytes / 1024 / 1024).toFixed(2)} MB`), - ); - - // Step 3 — reassembly benchmark (pure JS, in-memory) - console.log( - chalk.cyan(`\n[3/3] Reassembly benchmark — ${REASSEMBLY_REPEATS} runs`), - ); - // Warm up the JIT - for (let i = 0; i < 3; i++) reassemble(flatRow); - const reassemblySamples: number[] = []; - let lastReassembledLen = 0; - for (let i = 0; i < REASSEMBLY_REPEATS; i++) { - const start = performance.now(); - const reassembled = reassemble(flatRow); - const elapsed = performance.now() - start; - reassemblySamples.push(elapsed); - lastReassembledLen = reassembled.length; - } - const reassemblySummary = summarise(reassemblySamples); - console.log( - chalk.gray( - ` reassembled ${lastReassembledLen} FullCustomers (${REASSEMBLY_REPEATS} runs)`, - ), - ); - - // Report - console.log(chalk.magentaBright("\n================ RESULTS ================\n")); - console.log(chalk.bold("SQL (server-side, EXPLAIN ANALYZE Execution Time):")); - console.log( - ` min=${sqlSummary.min.toFixed(2)}ms median=${sqlSummary.median.toFixed(2)}ms p95=${sqlSummary.p95.toFixed(2)}ms max=${sqlSummary.max.toFixed(2)}ms`, - ); - console.log(chalk.gray(` samples: ${sqlSummary.samples.map((s) => s.toFixed(1)).join(", ")}`)); - console.log(); - console.log(chalk.bold("Reassembly (JS, in-memory, network-free):")); - console.log( - ` min=${reassemblySummary.min.toFixed(2)}ms median=${reassemblySummary.median.toFixed(2)}ms p95=${reassemblySummary.p95.toFixed(2)}ms max=${reassemblySummary.max.toFixed(2)}ms`, - ); - console.log(chalk.gray(` samples: ${reassemblySummary.samples.map((s) => s.toFixed(2)).join(", ")}`)); - console.log(); - console.log(chalk.bold.green("Total (network-free) median:")); - console.log( - chalk.bold.green( - ` ${(sqlSummary.median + reassemblySummary.median).toFixed(2)}ms = ${sqlSummary.median.toFixed(2)}ms SQL + ${reassemblySummary.median.toFixed(2)}ms reassembly`, - ), - ); - console.log(); - console.log(chalk.bold("Reference (UK→US client wall, includes ~100ms network):")); - console.log(` one-shot db.execute: ${queryWallMs.toFixed(2)}ms`); - console.log(); - - const date = new Date().toISOString().slice(0, 10); - const reportPath = join(RESULTS_DIR, `${date}-v07-reassembly-${LABEL}.md`); - const fixturePath = join(RESULTS_DIR, `${date}-v07-fixture-${LABEL}.json`); - - const lines: string[] = []; - lines.push(`# Variant 07 + JS Reassembly — ${date} — ${LABEL}`); - lines.push(""); - lines.push("## Config"); - lines.push(`- org_id: \`${ORG_ID}\``); - lines.push(`- env: \`${ENV}\``); - lines.push(`- limit: ${LIMIT} (fetch ${FETCH_LIMIT})`); - lines.push(`- deep cursor: \`{ t: ${DEEP_CURSOR.t}, id: ${DEEP_CURSOR.id} }\``); - lines.push(`- SQL repeats: ${SQL_REPEATS}`); - lines.push(`- reassembly repeats: ${REASSEMBLY_REPEATS} (after 3 warmup runs)`); - lines.push(""); - lines.push("## SQL (server-side, EXPLAIN ANALYZE Execution Time, network-free)"); - lines.push(""); - lines.push(`| min | median | p95 | max |`); - lines.push(`|---|---|---|---|`); - lines.push( - `| ${sqlSummary.min.toFixed(2)}ms | ${sqlSummary.median.toFixed(2)}ms | ${sqlSummary.p95.toFixed(2)}ms | ${sqlSummary.max.toFixed(2)}ms |`, - ); - lines.push(""); - lines.push( - `Samples: ${sqlSummary.samples.map((s) => `${s.toFixed(1)}ms`).join(", ")}`, - ); - lines.push(""); - lines.push("## Reassembly (JS, in-memory)"); - lines.push(""); - lines.push(`| min | median | p95 | max |`); - lines.push(`|---|---|---|---|`); - lines.push( - `| ${reassemblySummary.min.toFixed(2)}ms | ${reassemblySummary.median.toFixed(2)}ms | ${reassemblySummary.p95.toFixed(2)}ms | ${reassemblySummary.max.toFixed(2)}ms |`, - ); - lines.push(""); - lines.push( - `Samples: ${reassemblySummary.samples.map((s) => `${s.toFixed(2)}ms`).join(", ")}`, - ); - lines.push(""); - lines.push("## Total network-free"); - lines.push(""); - lines.push( - `**${(sqlSummary.median + reassemblySummary.median).toFixed(2)}ms** = ${sqlSummary.median.toFixed(2)}ms SQL + ${reassemblySummary.median.toFixed(2)}ms reassembly`, - ); - lines.push(""); - lines.push("## Row counts"); - lines.push(""); - lines.push("```json"); - lines.push(JSON.stringify(counts, null, 2)); - lines.push("```"); - lines.push(""); - lines.push(`Payload size: ${(payloadBytes / 1024 / 1024).toFixed(2)} MB`); - lines.push(""); - lines.push("## Reference: UK→US client wall"); - lines.push(""); - lines.push(`One-shot \`db.execute\`: ${queryWallMs.toFixed(2)}ms`); - lines.push(""); - lines.push("## Last EXPLAIN plan (JSON)"); - lines.push(""); - lines.push("```json"); - lines.push(JSON.stringify(lastExplainPlan, null, 2)); - lines.push("```"); - - writeFileSync(reportPath, lines.join("\n")); - writeFileSync(fixturePath, JSON.stringify(flatRow)); - - console.log(chalk.green(`✅ Report written to ${reportPath}`)); - console.log(chalk.green(`✅ Fixture written to ${fixturePath}`)); - console.log( - chalk.magentaBright( - "\n================ Benchmark Complete ================\n", - ), - ); - } catch (error) { - console.error(chalk.red("\n❌ Benchmark failed:")); - console.error(error); - console.error(chalk.gray(JSON.stringify(error, Object.getOwnPropertyNames(error || {})))); - process.exit(1); - } finally { - await client.end(); - process.exit(0); - } -}; - -await main(); diff --git a/server/experiments/versionPipelineBench.ts b/server/experiments/versionPipelineBench.ts deleted file mode 100644 index 7b3adc356..000000000 --- a/server/experiments/versionPipelineBench.ts +++ /dev/null @@ -1,547 +0,0 @@ -import { - AffectedResource, - AllowanceType, - type ApiCustomerV5, - ApiVersion, - ApiVersionClass, - applyResponseVersionChanges, - AppEnv, - BillingInterval, - CollectionMethod, - type CustomerLegacyData, - CusProductStatus, - EntInterval, - FeatureType, - type FullCustomer, - type FullCustomerEntitlement, - type FullCusProduct -} from "@autumn/shared"; -import chalk from "chalk"; -import { getApiCustomerBase } from "../src/internal/customers/cusUtils/apiCusUtils/getApiCustomerBase"; - -const CUSTOMER_COUNT = 1000; -const ITERATIONS = 5; -const WARMUP_ITERATIONS = 2; - -const ORG_ID = "org_bench"; -const FEATURE_IDS = [ - "messages", - "credits", - "users", - "projects", - "api_calls", - "storage_gb", - "connectors", - "webhooks", - "exports", - "dashboards", -]; -const ADDON_FEATURE_IDS = [ - "extra_messages", - "extra_credits", - "premium_support", - "sso", - "audit_log", -]; -const LOOSE_FEATURE_IDS = [ - "loose_a", - "loose_b", - "loose_c", - "loose_d", - "loose_e", -]; - -const featureFromId = (id: string) => ({ - internal_id: `fe_${id}`, - org_id: ORG_ID, - created_at: 1000, - env: AppEnv.Sandbox, - id, - name: id, - type: FeatureType.Metered, - config: { usage_type: "single_use" }, - display: { singular: id, plural: `${id}s` }, - archived: false, - event_names: [id], -}); - -const allFeatures = [ - ...FEATURE_IDS.map(featureFromId), - ...ADDON_FEATURE_IDS.map(featureFromId), - ...LOOSE_FEATURE_IDS.map(featureFromId), -]; - -const makeEntitlement = ({ - cusEntId, - productInternalId, - productCusId, - featureId, - internalCustomerId, - customerId, -}: { - cusEntId: string; - productInternalId: string; - productCusId: string | null; - featureId: string; - internalCustomerId: string; - customerId: string; -}): FullCustomerEntitlement => ({ - id: cusEntId, - internal_customer_id: internalCustomerId, - internal_entity_id: null, - internal_feature_id: `fe_${featureId}`, - customer_id: customerId, - feature_id: featureId, - customer_product_id: productCusId, - entitlement_id: `ent_${featureId}_${productInternalId}`, - created_at: 1000, - unlimited: false, - balance: 100, - additional_balance: 0, - usage_allowed: null, - next_reset_at: 2000, - adjustment: 0, - expires_at: null, - cache_version: 0, - entities: null, - external_id: null, - entitlement: { - id: `ent_${featureId}_${productInternalId}`, - created_at: 1000, - internal_feature_id: `fe_${featureId}`, - internal_product_id: productInternalId, - is_custom: false, - allowance_type: AllowanceType.Fixed, - allowance: 100, - interval: EntInterval.Month, - interval_count: 1, - entity_feature_id: null, - feature_id: featureId, - usage_limit: null, - feature: featureFromId(featureId), - }, - replaceables: [], - rollovers: [], -}); - -const makeCusProduct = ({ - productCusId, - productInternalId, - productId, - productName, - isAddOn, - internalCustomerId, - customerId, - featureIds, - cusEntPrefix, - internalEntityId, - entityId, -}: { - productCusId: string; - productInternalId: string; - productId: string; - productName: string; - isAddOn: boolean; - internalCustomerId: string; - customerId: string; - featureIds: string[]; - cusEntPrefix: string; - internalEntityId?: string | null; - entityId?: string | null; -}): FullCusProduct => ({ - id: productCusId, - internal_product_id: productInternalId, - product_id: productId, - internal_customer_id: internalCustomerId, - customer_id: customerId, - internal_entity_id: internalEntityId ?? null, - entity_id: entityId ?? null, - created_at: 1500, - updated_at: null, - status: CusProductStatus.Active, - canceled: false, - starts_at: 1500, - access_starts_at: null, - trial_ends_at: null, - billing_cycle_anchor_resets_at: null, - canceled_at: null, - ended_at: null, - options: [], - free_trial_id: null, - collection_method: CollectionMethod.ChargeAutomatically, - subscription_ids: [`sub_${productCusId}`], - scheduled_ids: [], - processor: { type: "stripe" as any }, - quantity: 1, - api_semver: null, - is_custom: false, - billing_version: "v2" as any, - external_id: null, - stripe_checkout_session_id: null, - customer_prices: [ - { - id: `cus_price_${productCusId}`, - internal_customer_id: internalCustomerId, - customer_product_id: productCusId, - created_at: 1500, - price_id: `price_${productInternalId}`, - price: { - id: `price_${productInternalId}`, - internal_product_id: productInternalId, - org_id: ORG_ID, - created_at: 1500, - billing_type: null, - tier_behavior: null, - is_custom: false, - config: { - type: "fixed", - amount: 99, - interval: BillingInterval.Month, - } as any, - entitlement_id: null, - proration_config: null, - }, - }, - ], - customer_entitlements: featureIds.map((fid, i) => - makeEntitlement({ - cusEntId: `${cusEntPrefix}_${i}`, - productInternalId, - productCusId, - featureId: fid, - internalCustomerId, - customerId, - }), - ), - product: { - id: productId, - name: productName, - description: null, - is_add_on: isAddOn, - is_default: false, - version: 1, - group: "", - env: AppEnv.Sandbox, - internal_id: productInternalId, - org_id: ORG_ID, - created_at: 1000, - processor: { type: "stripe", id: `prod_${productId}` }, - base_variant_id: null, - archived: false, - config: { ignore_past_due: false }, - }, - free_trial: null, -}); - -const makeFullCustomer = (idx: number): FullCustomer => { - const internalId = `cus_bench_${idx.toString().padStart(8, "0")}`; - const customerId = `bench_${idx}`; - - const customerProducts: FullCusProduct[] = [ - makeCusProduct({ - productCusId: `cus_prod_main_${idx}`, - productInternalId: "prod_main", - productId: "pro", - productName: "Pro", - isAddOn: false, - internalCustomerId: internalId, - customerId, - featureIds: FEATURE_IDS, - cusEntPrefix: `cus_ent_main_${idx}`, - }), - makeCusProduct({ - productCusId: `cus_prod_addon_${idx}`, - productInternalId: "prod_addon", - productId: "pro_addon", - productName: "Pro Add-on", - isAddOn: true, - internalCustomerId: internalId, - customerId, - featureIds: ADDON_FEATURE_IDS, - cusEntPrefix: `cus_ent_addon_${idx}`, - }), - ]; - - const entities = Array.from({ length: 5 }, (_, i) => ({ - id: `ent_${idx}_${i}`, - org_id: ORG_ID, - created_at: 1100, - internal_id: `ent_int_${idx}_${i}`, - internal_customer_id: internalId, - env: AppEnv.Sandbox, - name: `Entity ${i}`, - deleted: false, - feature_id: "seats", - internal_feature_id: "fe_seats", - })); - - for (let i = 0; i < 3; i++) { - const ent = entities[i]!; - customerProducts.push( - makeCusProduct({ - productCusId: `cus_prod_main_${idx}_e${i}`, - productInternalId: "prod_main", - productId: "pro", - productName: "Pro (Entity-scoped)", - isAddOn: false, - internalCustomerId: internalId, - customerId, - featureIds: FEATURE_IDS, - cusEntPrefix: `cus_ent_main_${idx}_e${i}`, - internalEntityId: ent.internal_id, - entityId: ent.id, - }), - ); - } - - const looseEnts: FullCustomerEntitlement[] = LOOSE_FEATURE_IDS.map((fid, i) => - makeEntitlement({ - cusEntId: `cus_ent_loose_${idx}_${i}`, - productInternalId: "prod_loose", - productCusId: null, - featureId: fid, - internalCustomerId: internalId, - customerId, - }), - ); - - return { - id: customerId, - name: `Bench Customer ${idx}`, - email: `bench+${idx}@autumn.dev`, - fingerprint: null, - internal_id: internalId, - org_id: ORG_ID, - created_at: 1000, - env: AppEnv.Sandbox, - processor: { id: `cus_stripe_${idx}`, type: "stripe" }, - processors: null, - metadata: {}, - send_email_receipts: false, - auto_topups: null, - spend_limits: null, - usage_alerts: null, - overage_allowed: null, - config: null, - customer_products: customerProducts, - entities, - extra_customer_entitlements: looseEnts, - subscriptions: customerProducts - .filter((cp) => cp.subscription_ids?.length) - .flatMap((cp) => - (cp.subscription_ids ?? []).map((sid) => ({ - id: sid, - stripe_id: sid, - stripe_schedule_id: null, - created_at: 1500, - usage_features: [], - metadata: {}, - org_id: ORG_ID, - env: AppEnv.Sandbox, - current_period_start: 1500, - current_period_end: 4000, - })), - ), - } as unknown as FullCustomer; -}; - -const makeCtx = () => - ({ - org: { - id: ORG_ID, - slug: "bench", - created_at: 1000, - default_currency: "usd", - config: { in_statuses: [CusProductStatus.Active] }, - stripe_connected: false, - }, - env: AppEnv.Sandbox, - features: allFeatures, - logger: { - info: () => {}, - error: () => {}, - warn: () => {}, - debug: () => {}, - trace: () => {}, - child: () => makeCtx().logger, - }, - expand: [], - apiVersion: new ApiVersionClass(ApiVersion.V2_3), - }) as any; - -const summarize = (samples: number[]) => { - const sorted = [...samples].sort((a, b) => a - b); - return { - min: sorted[0] ?? 0, - p50: sorted[Math.floor(sorted.length / 2)] ?? 0, - p95: sorted[Math.min(sorted.length - 1, Math.floor(sorted.length * 0.95))] ?? 0, - max: sorted[sorted.length - 1] ?? 0, - mean: samples.reduce((a, b) => a + b, 0) / samples.length, - }; -}; - -const fmt = (n: number) => `${n.toFixed(2)}ms`.padStart(9, " "); - -const runSerial = async (customers: FullCustomer[], ctx: any) => { - const finals: ApiCustomerV5[] = []; - for (const fullCus of customers) { - const { apiCustomer: baseCustomer, legacyData } = await getApiCustomerBase({ - ctx, - fullCus, - withAutumnId: false, - }); - const versioned = applyResponseVersionChanges({ - input: baseCustomer, - legacyData, - targetVersion: ctx.apiVersion, - resource: AffectedResource.Customer, - ctx, - }); - finals.push(versioned); - } - return finals; -}; - -const runPromiseAll = async (customers: FullCustomer[], ctx: any) => { - const finals = await Promise.all( - customers.map(async (fullCus) => { - const { apiCustomer: baseCustomer, legacyData } = await getApiCustomerBase({ - ctx, - fullCus, - withAutumnId: false, - }); - return applyResponseVersionChanges({ - input: baseCustomer, - legacyData, - targetVersion: ctx.apiVersion, - resource: AffectedResource.Customer, - ctx, - }); - }), - ); - return finals; -}; - -// "sync-equivalent" path: keeps the async function calls but uses a tight -// promise-chained sequence with no extra microtask churn between customers. -// Since getApiCustomerBase is async, true sync isn't possible without rewriting it. -const runSyncEquivalent = (customers: FullCustomer[], ctx: any) => { - let p: Promise = Promise.resolve([] as ApiCustomerV5[]); - for (const fullCus of customers) { - p = p.then(async (acc) => { - const { apiCustomer: baseCustomer, legacyData } = await getApiCustomerBase({ - ctx, - fullCus, - withAutumnId: false, - }); - const versioned = applyResponseVersionChanges({ - input: baseCustomer, - legacyData, - targetVersion: ctx.apiVersion, - resource: AffectedResource.Customer, - ctx, - }); - acc.push(versioned); - return acc; - }); - } - return p; -}; - -const benchVariant = async ({ - name, - customers, - ctx, - run, -}: { - name: string; - customers: FullCustomer[]; - ctx: any; - run: (customers: FullCustomer[], ctx: any) => Promise; -}) => { - for (let i = 0; i < WARMUP_ITERATIONS; i++) await run(customers, ctx); - - const samples: number[] = []; - for (let i = 0; i < ITERATIONS; i++) { - const t0 = performance.now(); - const result = await run(customers, ctx); - const t1 = performance.now(); - if (result.length !== customers.length) { - throw new Error(`${name}: result length mismatch ${result.length} vs ${customers.length}`); - } - samples.push(t1 - t0); - } - - const s = summarize(samples); - const perCustomer = s.p50 / customers.length; - console.log( - ` ${chalk.cyan(name.padEnd(20))} | total p50 ${fmt(s.p50)} (per cus ${perCustomer.toFixed(3)}ms) | mean ${fmt(s.mean)} | p95 ${fmt(s.p95)} | min ${fmt(s.min)} | max ${fmt(s.max)}`, - ); - return s; -}; - -const main = async () => { - console.log( - chalk.magentaBright( - `\n================ Version Pipeline Bench ================`, - ), - ); - console.log( - chalk.gray( - ` customers per iter: ${CUSTOMER_COUNT.toLocaleString()}, iterations: ${ITERATIONS} (+${WARMUP_ITERATIONS} warmup)\n`, - ), - ); - - const ctx = makeCtx(); - const customers = Array.from({ length: CUSTOMER_COUNT }, (_, i) => - makeFullCustomer(i), - ); - console.log( - chalk.gray( - ` shape per customer: ${customers[0]!.customer_products.length} cps · ${customers[0]!.customer_products.reduce((n, cp) => n + cp.customer_entitlements.length, 0)} bound ces · ${customers[0]!.extra_customer_entitlements.length} loose ces · ${customers[0]!.entities.length} entities`, - ), - ); - console.log(); - - console.log(chalk.bold("Baseline (serial for-await):")); - const serial = await benchVariant({ name: "serial", customers, ctx, run: runSerial }); - - console.log(); - console.log(chalk.bold("Variant A (Promise.all):")); - const promiseAll = await benchVariant({ - name: "Promise.all", - customers, - ctx, - run: runPromiseAll, - }); - - console.log(); - console.log(chalk.bold("Variant B (sync-equivalent promise chain):")); - const syncish = await benchVariant({ - name: "sync-equivalent", - customers, - ctx, - run: runSyncEquivalent, - }); - - const fmtDelta = (other: number, baseline: number) => { - const delta = ((other - baseline) / baseline) * 100; - const arrow = delta < 0 ? "↓" : "↑"; - return `${arrow}${Math.abs(delta).toFixed(1)}%`; - }; - - console.log(); - console.log(chalk.magentaBright("================ Summary ================")); - console.log( - ` serial: ${fmt(serial.p50)} ${chalk.gray("(baseline)")}`, - ); - console.log( - ` Promise.all: ${fmt(promiseAll.p50)} (${fmtDelta(promiseAll.p50, serial.p50)})`, - ); - console.log( - ` sync-equivalent: ${fmt(syncish.p50)} (${fmtDelta(syncish.p50, serial.p50)})`, - ); -}; - -await main(); -process.exit(0); \ No newline at end of file