{
  "version": 1,
  "source": "public/hero/optimize/slow_order_by.md",
  "note": "Supplemental data for chatbot-announcement.mdx. Each block is referenced by key from the MDX. `mcp-call` blocks render as ExoBench chat tool cards (ChatbotMcpCard); `chart` blocks render as marketing charts. Sourced verbatim from the hero demo the post is based on.",
  "blocks": {
    "baseline": {
      "type": "mcp-call",
      "step": "#1 of 3",
      "name": "benchmarkSql",
      "input": {
        "schemaTemplate": "CREATE TABLE orders (\n  id            bigserial PRIMARY KEY,\n  user_id       bigint NOT NULL,\n  customer_name text NOT NULL,\n  status        text NOT NULL,\n  total         numeric(10,2) NOT NULL,\n  created_at    timestamptz NOT NULL DEFAULT now()\n);\n\nINSERT INTO orders (user_id, customer_name, status, total, created_at)\nSELECT\n  (random()*100000)::bigint + 1,\n  'cust_' || (random()*100000)::int,\n  CASE\n    WHEN random() < 0.10 THEN 'pending'\n    WHEN random() < 0.55 THEN 'completed'\n    WHEN random() < 0.80 THEN 'shipped'\n    ELSE 'cancelled'\n  END,\n  (random()*1000)::numeric(10,2),\n  NOW() - (random() * 365 * interval '1 day')\nFROM generate_series(1, ${SCALE}) i;\n\nVACUUM ANALYZE;",
        "query": "EXPLAIN ANALYZE SELECT id, customer_name, total, created_at FROM orders WHERE status = 'pending' ORDER BY created_at DESC;",
        "scalePoints": [
          {
            "SCALE": 100000
          },
          {
            "SCALE": 1000000
          },
          {
            "SCALE": 3000000
          }
        ],
        "version": "1.2.0",
        "warmCache": "true"
      },
      "result": "{\"timestamp\":\"2026-06-25T03:04:48.897880989Z\",\"databaseType\":\"postgres\",\"sql\":\"EXPLAIN ANALYZE SELECT id, customer_name, total, created_at FROM orders WHERE status = 'pending' ORDER BY created_at DESC;\",\"warmCacheIterations\":5,\"scalePointCount\":3,\"totalComputeSeconds\":21.253,\"results\":[{\"kind\":\"postgres_explain_analyze\",\"scalePoint\":{\"SCALE\":100000},\"output\":\"QUERY PLAN\\nSort  (cost=2949.29..2974.42 rows=10053 width=32) (actual time=9.091..9.809 rows=9905 loops=1)\\n  Sort Key: created_at DESC\\n  Sort Method: quicksort  Memory: 925kB\\n  ->  Seq Scan on orders  (cost=0.00..2281.00 rows=10053 width=32) (actual time=0.010..7.183 rows=9905 loops=1)\\n        Filter: (status = 'pending'::text)\\n        Rows Removed by Filter: 90095\\nPlanning Time: 0.061 ms\\nExecution Time: 10.152 ms\",\"runtimeDurationMs\":15,\"rowsAreCapped\":false,\"computeSeconds\":3.088},{\"kind\":\"postgres_explain_analyze\",\"scalePoint\":{\"SCALE\":1000000},\"output\":\"QUERY PLAN\\nGather Merge  (cost=19889.64..30095.42 rows=87472 width=32) (actual time=37.281..59.169 rows=99971 loops=1)\\n  Workers Planned: 2\\n  Workers Launched: 2\\n  ->  Sort  (cost=18889.62..18998.96 rows=43736 width=32) (actual time=34.695..38.876 rows=33324 loops=3)\\n        Sort Key: created_at DESC\\n        Sort Method: quicksort  Memory: 3287kB\\n        Worker 0:  Sort Method: quicksort  Memory: 2512kB\\n        Worker 1:  Sort Method: quicksort  Memory: 2420kB\\n        ->  Parallel Seq Scan on orders  (cost=0.00..15518.33 rows=43736 width=32) (actual time=0.009..26.553 rows=33324 loops=3)\\n              Filter: (status = 'pending'::text)\\n              Rows Removed by Filter: 300010\\nPlanning Time: 0.086 ms\\nExecution Time: 62.944 ms\",\"runtimeDurationMs\":66,\"rowsAreCapped\":false,\"computeSeconds\":4.825},{\"kind\":\"postgres_explain_analyze\",\"scalePoint\":{\"SCALE\":3000000},\"output\":\"QUERY PLAN\\nGather Merge  (cost=61195.32..90500.07 rows=251166 width=32) (actual time=114.061..173.780 rows=300858 loops=1)\\n  Workers Planned: 2\\n  Workers Launched: 2\\n  ->  Sort  (cost=60195.30..60509.26 rows=125583 width=32) (actual time=110.212..121.060 rows=100286 loops=3)\\n        Sort Key: created_at DESC\\n        Sort Method: external merge  Disk: 5032kB\\n        Worker 0:  Sort Method: external merge  Disk: 4816kB\\n        Worker 1:  Sort Method: external merge  Disk: 4856kB\\n        ->  Parallel Seq Scan on orders  (cost=0.00..46553.00 rows=125583 width=32) (actual time=0.016..76.795 rows=100286 loops=3)\\n              Filter: (status = 'pending'::text)\\n              Rows Removed by Filter: 899714\\nPlanning Time: 0.066 ms\\nExecution Time: 185.338 ms\",\"runtimeDurationMs\":188,\"rowsAreCapped\":false,\"computeSeconds\":13.34}],\"workerId\":\"f028acc6-e598-4087-884f-a8a013380ce8\",\"correlationId\":\"dba17cc9-10a1-4352-9b96-1fb8ba932ef2\",\"correlationIdInstruction\":\"Echo correlationId on subsequent ExoBench tool calls. Tracking only — it does NOT preserve DB state.\",\"version\":\"1.2.0\"}",
      "isError": false,
      "caption": "Baseline: primary key only, no index on status or created_at",
      "planCaptions": {
        "100K": "Baseline with no index on status or created_at. PostgreSQL runs a Seq Scan on orders, throws away 90,095 rows in the Filter, then quicksorts 9,905 surviving rows by created_at DESC in 925kB of work_mem. Execution Time 10.152 ms at 100,000 rows.",
        "1M": "At 1,000,000 rows the unindexed ORDER BY goes parallel: Gather Merge over 2 launched workers, each running a Parallel Seq Scan that removes 300,010 rows by filter and a quicksort in about 3MB. Execution Time 62.944 ms.",
        "3M": "At 3,000,000 rows the per-worker sort no longer fits in work_mem, so quicksort becomes Sort Method external merge, spilling 5,032kB to disk under a 2-worker Gather Merge. Execution Time 185.338 ms, 18x the 100,000-row number."
      }
    },
    "partialIndex": {
      "type": "mcp-call",
      "step": "#2 of 3",
      "name": "benchmarkSql",
      "input": {
        "schemaTemplate": "CREATE TABLE orders (\n  id            bigserial PRIMARY KEY,\n  user_id       bigint NOT NULL,\n  customer_name text NOT NULL,\n  status        text NOT NULL,\n  total         numeric(10,2) NOT NULL,\n  created_at    timestamptz NOT NULL DEFAULT now()\n);\n\nINSERT INTO orders (user_id, customer_name, status, total, created_at)\nSELECT\n  (random()*100000)::bigint + 1,\n  'cust_' || (random()*100000)::int,\n  CASE\n    WHEN random() < 0.10 THEN 'pending'\n    WHEN random() < 0.55 THEN 'completed'\n    WHEN random() < 0.80 THEN 'shipped'\n    ELSE 'cancelled'\n  END,\n  (random()*1000)::numeric(10,2),\n  NOW() - (random() * 365 * interval '1 day')\nFROM generate_series(1, ${SCALE}) i;\n\nCREATE INDEX idx_pending_created ON orders (created_at DESC) WHERE status = 'pending';\nVACUUM ANALYZE;",
        "query": "EXPLAIN ANALYZE SELECT id, customer_name, total, created_at FROM orders WHERE status = 'pending' ORDER BY created_at DESC;",
        "scalePoints": [
          {
            "SCALE": 100000
          },
          {
            "SCALE": 1000000
          },
          {
            "SCALE": 3000000
          }
        ],
        "version": "1.2.0",
        "warmCache": "true",
        "correlationId": "dba17cc9-10a1-4352-9b96-1fb8ba932ef2"
      },
      "result": "{\"timestamp\":\"2026-06-25T03:05:26.214687643Z\",\"databaseType\":\"postgres\",\"sql\":\"EXPLAIN ANALYZE SELECT id, customer_name, total, created_at FROM orders WHERE status = 'pending' ORDER BY created_at DESC;\",\"warmCacheIterations\":5,\"scalePointCount\":3,\"totalComputeSeconds\":19.258,\"results\":[{\"kind\":\"postgres_explain_analyze\",\"scalePoint\":{\"SCALE\":100000},\"output\":\"QUERY PLAN\\nSort  (cost=1981.93..2006.63 rows=9877 width=32) (actual time=5.445..6.292 rows=10102 loops=1)\\n  Sort Key: created_at DESC\\n  Sort Method: quicksort  Memory: 935kB\\n  ->  Bitmap Heap Scan on orders  (cost=172.14..1326.60 rows=9877 width=32) (actual time=0.469..3.404 rows=10102 loops=1)\\n        Recheck Cond: (status = 'pending'::text)\\n        Heap Blocks: exact=1031\\n        ->  Bitmap Index Scan on idx_pending_created  (cost=0.00..169.67 rows=9877 width=0) (actual time=0.372..0.372 rows=10102 loops=1)\\nPlanning Time: 0.083 ms\\nExecution Time: 6.677 ms\",\"runtimeDurationMs\":10,\"rowsAreCapped\":false,\"computeSeconds\":0.908},{\"kind\":\"postgres_explain_analyze\",\"scalePoint\":{\"SCALE\":1000000},\"output\":\"QUERY PLAN\\nSort  (cost=23783.79..24031.87 rows=99233 width=32) (actual time=68.744..78.463 rows=99817 loops=1)\\n  Sort Key: created_at DESC\\n  Sort Method: external merge  Disk: 4880kB\\n  ->  Bitmap Heap Scan on orders  (cost=1621.27..13171.68 rows=99233 width=32) (actual time=6.435..34.971 rows=99817 loops=1)\\n        Recheck Cond: (status = 'pending'::text)\\n        Heap Blocks: exact=10309\\n        ->  Bitmap Index Scan on idx_pending_created  (cost=0.00..1596.46 rows=99233 width=0) (actual time=5.245..5.246 rows=99817 loops=1)\\nPlanning Time: 0.086 ms\\nExecution Time: 82.992 ms\",\"runtimeDurationMs\":86,\"rowsAreCapped\":false,\"computeSeconds\":4.762},{\"kind\":\"postgres_explain_analyze\",\"scalePoint\":{\"SCALE\":3000000},\"output\":\"QUERY PLAN\\nSort  (cost=72449.80..73171.55 rows=288700 width=32) (actual time=195.434..232.272 rows=300203 loops=1)\\n  Sort Key: created_at DESC\\n  Sort Method: external merge  Disk: 14656kB\\n  ->  Bitmap Heap Scan on orders  (cost=4820.10..39356.85 rows=288700 width=32) (actual time=20.224..104.989 rows=300203 loops=1)\\n        Recheck Cond: (status = 'pending'::text)\\n        Heap Blocks: exact=30927\\n        ->  Bitmap Index Scan on idx_pending_created  (cost=0.00..4747.92 rows=288700 width=0) (actual time=15.301..15.301 rows=300203 loops=1)\\nPlanning Time: 0.079 ms\\nExecution Time: 244.986 ms\",\"runtimeDurationMs\":248,\"rowsAreCapped\":false,\"computeSeconds\":13.588}],\"workerId\":\"cb7acb98-19d5-495f-8de2-2aa8414a5ab4\",\"correlationId\":\"89c9580b-b60f-4aaf-bc81-a7ce25f5b403\",\"correlationIdInstruction\":\"Echo correlationId on subsequent ExoBench tool calls. Tracking only — it does NOT preserve DB state.\",\"version\":\"1.2.0\"}",
      "isError": false,
      "caption": "Plain partial index on created_at DESC WHERE status = 'pending'",
      "planCaptions": {
        "100K": "Partial index idx_pending_created on (created_at DESC) WHERE status = 'pending'. At 100,000 rows the planner picks Bitmap Index Scan plus Bitmap Heap Scan over 1,031 heap blocks, still followed by a 935kB quicksort. Execution Time 6.677 ms against 10.152 ms for the Seq Scan baseline.",
        "1M": "The trap: a bitmap scan returns rows in heap order, not index order, so ORDER BY created_at DESC still needs a Sort. At 1,000,000 rows that sort spills, Sort Method external merge Disk 4,880kB over 10,309 heap blocks. Execution Time 82.992 ms, slower than the 62.944 ms baseline.",
        "3M": "At 3,000,000 rows the partial index costs 244.986 ms against 185.338 ms for having no index at all. Bitmap Heap Scan touches 30,927 heap blocks, the sort spills 14,656kB to disk, and the single-process bitmap plan gives up the 2 parallel workers the Seq Scan plan used."
      }
    },
    "coveringIndex": {
      "type": "mcp-call",
      "step": "#3 of 3",
      "name": "benchmarkSql",
      "input": {
        "schemaTemplate": "CREATE TABLE orders (\n  id            bigserial PRIMARY KEY,\n  user_id       bigint NOT NULL,\n  customer_name text NOT NULL,\n  status        text NOT NULL,\n  total         numeric(10,2) NOT NULL,\n  created_at    timestamptz NOT NULL DEFAULT now()\n);\n\nINSERT INTO orders (user_id, customer_name, status, total, created_at)\nSELECT\n  (random()*100000)::bigint + 1,\n  'cust_' || (random()*100000)::int,\n  CASE\n    WHEN random() < 0.10 THEN 'pending'\n    WHEN random() < 0.55 THEN 'completed'\n    WHEN random() < 0.80 THEN 'shipped'\n    ELSE 'cancelled'\n  END,\n  (random()*1000)::numeric(10,2),\n  NOW() - (random() * 365 * interval '1 day')\nFROM generate_series(1, ${SCALE}) i;\n\nCREATE INDEX idx_pending_cov ON orders (created_at DESC) INCLUDE (id, customer_name, total) WHERE status = 'pending';\nVACUUM ANALYZE;",
        "query": "EXPLAIN ANALYZE SELECT id, customer_name, total, created_at FROM orders WHERE status = 'pending' ORDER BY created_at DESC;",
        "scalePoints": [
          {
            "SCALE": 100000
          },
          {
            "SCALE": 1000000
          },
          {
            "SCALE": 3000000
          }
        ],
        "version": "1.2.0",
        "warmCache": "true",
        "correlationId": "dba17cc9-10a1-4352-9b96-1fb8ba932ef2"
      },
      "result": "{\"timestamp\":\"2026-06-25T03:06:00.665893162Z\",\"databaseType\":\"postgres\",\"sql\":\"EXPLAIN ANALYZE SELECT id, customer_name, total, created_at FROM orders WHERE status = 'pending' ORDER BY created_at DESC;\",\"warmCacheIterations\":5,\"scalePointCount\":3,\"totalComputeSeconds\":17.835,\"results\":[{\"kind\":\"postgres_explain_analyze\",\"scalePoint\":{\"SCALE\":100000},\"output\":\"QUERY PLAN\\nIndex Only Scan using idx_pending_cov on orders  (cost=0.29..440.34 rows=9870 width=32) (actual time=0.009..1.148 rows=9996 loops=1)\\n  Heap Fetches: 0\\nPlanning Time: 0.032 ms\\nExecution Time: 1.483 ms\",\"runtimeDurationMs\":3,\"rowsAreCapped\":false,\"computeSeconds\":0.766},{\"kind\":\"postgres_explain_analyze\",\"scalePoint\":{\"SCALE\":1000000},\"output\":\"QUERY PLAN\\nIndex Only Scan using idx_pending_cov on orders  (cost=0.42..4308.42 rows=96267 width=32) (actual time=0.020..11.518 rows=100196 loops=1)\\n  Heap Fetches: 0\\nPlanning Time: 0.068 ms\\nExecution Time: 14.864 ms\",\"runtimeDurationMs\":18,\"rowsAreCapped\":false,\"computeSeconds\":4.331},{\"kind\":\"postgres_explain_analyze\",\"scalePoint\":{\"SCALE\":3000000},\"output\":\"QUERY PLAN\\nIndex Only Scan using idx_pending_cov on orders  (cost=0.42..13240.92 rows=312300 width=32) (actual time=0.023..34.749 rows=300067 loops=1)\\n  Heap Fetches: 0\\nPlanning Time: 0.068 ms\\nExecution Time: 44.760 ms\",\"runtimeDurationMs\":47,\"rowsAreCapped\":false,\"computeSeconds\":12.738}],\"workerId\":\"d3f28f7d-d098-4f1b-b70b-09f4b4563519\",\"correlationId\":\"1d468a13-1db4-4d21-8f42-44b53dfdff0a\",\"correlationIdInstruction\":\"Echo correlationId on subsequent ExoBench tool calls. Tracking only — it does NOT preserve DB state.\",\"version\":\"1.2.0\"}",
      "isError": false,
      "caption": "Covering partial index with INCLUDE (id, customer_name, total)",
      "planCaptions": {
        "100K": "Covering partial index idx_pending_cov on (created_at DESC) INCLUDE (id, customer_name, total) WHERE status = 'pending'. The plan collapses to a single Index Only Scan with Heap Fetches 0 and no Sort node at all. Execution Time 1.483 ms at 100,000 rows.",
        "1M": "Index Only Scan with Heap Fetches 0 at 1,000,000 rows, Execution Time 14.864 ms. The index already stores the rows in created_at DESC order and carries every selected column, so PostgreSQL skips both the heap and the sort.",
        "3M": "At 3,000,000 rows the covering index holds 44.760 ms against 185.338 ms with no index and 244.986 ms with the plain partial index. Still one Index Only Scan, Heap Fetches 0, no external merge, no parallel workers needed."
      }
    },
    "resultsMatrix": {
      "type": "chart",
      "chartType": "multi-line",
      "scales": [
        "100K",
        "1M",
        "3M"
      ],
      "xLabel": "Rows",
      "yLabel": "Execution time (ms)",
      "yUnit": "ms",
      "maxY": 260,
      "caption": "One query, three index candidates, three scales. The obvious partial index is the trap; the covering index is the flat line.",
      "summaryCaption": "Baseline climbs from 10.2ms to 185.3ms. The obvious partial index is WORSE at scale (83ms, 245ms). The covering index stays flat and fast (1.5ms, 14.9ms, 44.8ms).",
      "dataAsProse": "Baseline (PK only): 10.2ms at 100K, 62.9ms at 1M, 185.3ms at 3M. Plain partial index: 6.7ms at 100K, 83.0ms at 1M, 245.0ms at 3M — slower than baseline at 1M and 3M. Covering partial index: 1.5ms at 100K, 14.9ms at 1M, 44.8ms at 3M — flat and fastest at every scale.",
      "series": [
        {
          "label": "Baseline (PK only)",
          "data": [
            10.2,
            62.9,
            185.3
          ],
          "color": "#E24B4A",
          "fill": true
        },
        {
          "label": "Plain partial index",
          "data": [
            6.7,
            83,
            245
          ],
          "color": "#BA7517",
          "style": "dashed",
          "pointStyle": "rect"
        },
        {
          "label": "Covering partial index",
          "data": [
            1.5,
            14.9,
            44.8
          ],
          "color": "#1D9E75",
          "pointStyle": "rectRot"
        }
      ]
    }
  }
}
