Skip to content

CTM-661: add db indexes for retrieveSnapshot() - #2126

Merged
davidangb merged 1 commit into
developfrom
da_dbindexes
Sep 8, 2026
Merged

CTM-661: add db indexes for retrieveSnapshot()#2126
davidangb merged 1 commit into
developfrom
da_dbindexes

Conversation

@davidangb

@davidangb davidangb commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Jira ticket: https://broadworkbench.atlassian.net/browse/CTM-661

Addresses

TDR's Postgres instance saw its CPU pegged at 100% for ~2 hours. This has happened multiple times in the past week.
Screenshot 03-09-2026 at 15 36

Summary of changes

This PR adds indexes to four columns used by GET /api/repository/v1/snapshots/{id}, to retrieve a single snapshot.

Details

The most recent CPU spike was triggered by a surge in traffic from azul-ucsc-0-hammerbox@platform-anvil-prod.iam.gserviceaccount.com.
Screenshot 03-09-2026 at 15 59

Claude quantified this traffic as:

50,911 executions of the per-snapshot queries, over only 275 distinct snapshot IDs (~185× re-fetch each)
steady ~390 req/min, starting 20:20, stopping dead at 22:33

This increased traffic to GET /api/repository/v1/snapshots/{id} in turn caused the db to struggle because the SQL powering this API is inefficient. Two separate queries - SnapshotTableDao.sqlSelectTable and SnapshotMapTablesDao.retrieveMapTables - each perform full table scans on two columns each. The size of the tables in question is not large, but under this kind of load they struggled mightily.

Running explain against the two queries proves this out - look for the two ""Node Type"": ""Seq Scan"" callouts in each of these:

SnapshotTableDao.sqlSelectTable
"QUERY PLAN"
"[
  {
    ""Plan"": {
      ""Node Type"": ""Sort"",
      ""Parallel Aware"": false,
      ""Async Capable"": false,
      ""Startup Cost"": 1542.01,
      ""Total Cost"": 1542.12,
      ""Plan Rows"": 43,
      ""Plan Width"": 124,
      ""Actual Startup Time"": 14.640,
      ""Actual Total Time"": 14.665,
      ""Actual Rows"": 171,
      ""Actual Loops"": 1,
      ""Output"": [""t.id"", ""t.name"", ""t.row_count"", ""t.primary_key"", ""c.id"", ""c.name"", ""c.type"", ""c.array_of"", ""c.required"", ""t.ctid"", ""c.ordinal""],
      ""Sort Key"": [""t.ctid"", ""c.ordinal""],
      ""Sort Method"": ""quicksort"",
      ""Sort Space Used"": 48,
      ""Sort Space Type"": ""Memory"",
      ""Shared Hit Blocks"": 873,
      ""Shared Read Blocks"": 0,
      ""Shared Dirtied Blocks"": 0,
      ""Shared Written Blocks"": 0,
      ""Local Hit Blocks"": 0,
      ""Local Read Blocks"": 0,
      ""Local Dirtied Blocks"": 0,
      ""Local Written Blocks"": 0,
      ""Temp Read Blocks"": 0,
      ""Temp Written Blocks"": 0,
      ""I/O Read Time"": 0.000,
      ""I/O Write Time"": 0.000,
      ""Temp I/O Read Time"": 0.000,
      ""Temp I/O Write Time"": 0.000,
      ""Plans"": [
        {
          ""Node Type"": ""Hash Join"",
          ""Parent Relationship"": ""Outer"",
          ""Parallel Aware"": false,
          ""Async Capable"": false,
          ""Join Type"": ""Inner"",
          ""Startup Cost"": 215.10,
          ""Total Cost"": 1540.85,
          ""Plan Rows"": 43,
          ""Plan Width"": 124,
          ""Actual Startup Time"": 2.067,
          ""Actual Total Time"": 14.418,
          ""Actual Rows"": 171,
          ""Actual Loops"": 1,
          ""Output"": [""t.id"", ""t.name"", ""t.row_count"", ""t.primary_key"", ""c.id"", ""c.name"", ""c.type"", ""c.array_of"", ""c.required"", ""t.ctid"", ""c.ordinal""],
          ""Inner Unique"": true,
          ""Hash Cond"": ""(c.table_id = t.id)"",
          ""Shared Hit Blocks"": 865,
          ""Shared Read Blocks"": 0,
          ""Shared Dirtied Blocks"": 0,
          ""Shared Written Blocks"": 0,
          ""Local Hit Blocks"": 0,
          ""Local Read Blocks"": 0,
          ""Local Dirtied Blocks"": 0,
          ""Local Written Blocks"": 0,
          ""Temp Read Blocks"": 0,
          ""Temp Written Blocks"": 0,
          ""I/O Read Time"": 0.000,
          ""I/O Write Time"": 0.000,
          ""Temp I/O Read Time"": 0.000,
          ""Temp I/O Write Time"": 0.000,
          ""Plans"": [
            {
-              ""Node Type"": ""Seq Scan"",
              ""Parent Relationship"": ""Outer"",
              ""Parallel Aware"": false,
              ""Async Capable"": false,
              ""Relation Name"": ""snapshot_column"",
              ""Schema"": ""public"",
              ""Alias"": ""c"",
              ""Startup Cost"": 0.00,
              ""Total Cost"": 1199.72,
              ""Plan Rows"": 47972,
              ""Plan Width"": 61,
              ""Actual Startup Time"": 0.009,
              ""Actual Total Time"": 9.593,
              ""Actual Rows"": 49377,
              ""Actual Loops"": 1,
              ""Output"": [""c.id"", ""c.name"", ""c.type"", ""c.array_of"", ""c.required"", ""c.ordinal"", ""c.table_id""],
              ""Shared Hit Blocks"": 720,
              ""Shared Read Blocks"": 0,
              ""Shared Dirtied Blocks"": 0,
              ""Shared Written Blocks"": 0,
              ""Local Hit Blocks"": 0,
              ""Local Read Blocks"": 0,
              ""Local Dirtied Blocks"": 0,
              ""Local Written Blocks"": 0,
              ""Temp Read Blocks"": 0,
              ""Temp Written Blocks"": 0,
              ""I/O Read Time"": 0.000,
              ""I/O Write Time"": 0.000,
              ""Temp I/O Read Time"": 0.000,
              ""Temp I/O Write Time"": 0.000
            },
            {
              ""Node Type"": ""Hash"",
              ""Parent Relationship"": ""Inner"",
              ""Parallel Aware"": false,
              ""Async Capable"": false,
              ""Startup Cost"": 215.04,
              ""Total Cost"": 215.04,
              ""Plan Rows"": 5,
              ""Plan Width"": 79,
              ""Actual Startup Time"": 0.753,
              ""Actual Total Time"": 0.755,
              ""Actual Rows"": 18,
              ""Actual Loops"": 1,
              ""Output"": [""t.id"", ""t.name"", ""t.row_count"", ""t.primary_key"", ""t.ctid""],
              ""Hash Buckets"": 1024,
              ""Original Hash Buckets"": 1024,
              ""Hash Batches"": 1,
              ""Original Hash Batches"": 1,
              ""Peak Memory Usage"": 10,
              ""Shared Hit Blocks"": 145,
              ""Shared Read Blocks"": 0,
              ""Shared Dirtied Blocks"": 0,
              ""Shared Written Blocks"": 0,
              ""Local Hit Blocks"": 0,
              ""Local Read Blocks"": 0,
              ""Local Dirtied Blocks"": 0,
              ""Local Written Blocks"": 0,
              ""Temp Read Blocks"": 0,
              ""Temp Written Blocks"": 0,
              ""I/O Read Time"": 0.000,
              ""I/O Write Time"": 0.000,
              ""Temp I/O Read Time"": 0.000,
              ""Temp I/O Write Time"": 0.000,
              ""Plans"": [
                {
-                  ""Node Type"": ""Seq Scan"",
                  ""Parent Relationship"": ""Outer"",
                  ""Parallel Aware"": false,
                  ""Async Capable"": false,
                  ""Relation Name"": ""snapshot_table"",
                  ""Schema"": ""public"",
                  ""Alias"": ""t"",
                  ""Startup Cost"": 0.00,
                  ""Total Cost"": 215.04,
                  ""Plan Rows"": 5,
                  ""Plan Width"": 79,
                  ""Actual Startup Time"": 0.138,
                  ""Actual Total Time"": 0.739,
                  ""Actual Rows"": 18,
                  ""Actual Loops"": 1,
                  ""Output"": [""t.id"", ""t.name"", ""t.row_count"", ""t.primary_key"", ""t.ctid""],
                  ""Filter"": ""(t.parent_id = 'e40bea8e-be23-441d-9a8b-a35abb213d2c'::uuid)"",
                  ""Rows Removed by Filter"": 5610,
                  ""Shared Hit Blocks"": 145,
                  ""Shared Read Blocks"": 0,
                  ""Shared Dirtied Blocks"": 0,
                  ""Shared Written Blocks"": 0,
                  ""Local Hit Blocks"": 0,
                  ""Local Read Blocks"": 0,
                  ""Local Dirtied Blocks"": 0,
                  ""Local Written Blocks"": 0,
                  ""Temp Read Blocks"": 0,
                  ""Temp Written Blocks"": 0,
                  ""I/O Read Time"": 0.000,
                  ""I/O Write Time"": 0.000,
                  ""Temp I/O Read Time"": 0.000,
                  ""Temp I/O Write Time"": 0.000
                }
              ]
            }
          ]
        }
      ]
    },
    ""Query Identifier"": -5805976077017921812,
    ""Planning"": {
      ""Shared Hit Blocks"": 302,
      ""Shared Read Blocks"": 0,
      ""Shared Dirtied Blocks"": 0,
      ""Shared Written Blocks"": 0,
      ""Local Hit Blocks"": 0,
      ""Local Read Blocks"": 0,
      ""Local Dirtied Blocks"": 0,
      ""Local Written Blocks"": 0,
      ""Temp Read Blocks"": 0,
      ""Temp Written Blocks"": 0,
      ""I/O Read Time"": 0.000,
      ""I/O Write Time"": 0.000,
      ""Temp I/O Read Time"": 0.000,
      ""Temp I/O Write Time"": 0.000
    },
    ""Planning Time"": 1.357,
    ""Triggers"": [
    ],
    ""Execution Time"": 14.919
  }
]"
SnapshotMapTablesDao.retrieveMapTables
"QUERY PLAN"
"[
  {
    ""Plan"": {
      ""Node Type"": ""Hash Join"",
      ""Parallel Aware"": false,
      ""Async Capable"": false,
      ""Join Type"": ""Inner"",
      ""Startup Cost"": 176.81,
      ""Total Cost"": 1457.32,
      ""Plan Rows"": 43,
      ""Plan Width"": 96,
      ""Actual Startup Time"": 2.166,
      ""Actual Total Time"": 14.619,
      ""Actual Rows"": 171,
      ""Actual Loops"": 1,
      ""Output"": [""smt.id"", ""smt.from_table_id"", ""smt.to_table_id"", ""smc.id"", ""smc.from_column_id"", ""smc.to_column_id""],
      ""Inner Unique"": true,
      ""Hash Cond"": ""(smc.map_table_id = smt.id)"",
      ""Shared Hit Blocks"": 778,
      ""Shared Read Blocks"": 0,
      ""Shared Dirtied Blocks"": 0,
      ""Shared Written Blocks"": 0,
      ""Local Hit Blocks"": 0,
      ""Local Read Blocks"": 0,
      ""Local Dirtied Blocks"": 0,
      ""Local Written Blocks"": 0,
      ""Temp Read Blocks"": 0,
      ""Temp Written Blocks"": 0,
      ""I/O Read Time"": 0.000,
      ""I/O Write Time"": 0.000,
      ""Temp I/O Read Time"": 0.000,
      ""Temp I/O Write Time"": 0.000,
      ""Plans"": [
        {
-          ""Node Type"": ""Seq Scan"",
          ""Parent Relationship"": ""Outer"",
          ""Parallel Aware"": false,
          ""Async Capable"": false,
          ""Relation Name"": ""snapshot_map_column"",
          ""Schema"": ""public"",
          ""Alias"": ""smc"",
          ""Startup Cost"": 0.00,
          ""Total Cost"": 1153.70,
          ""Plan Rows"": 48270,
          ""Plan Width"": 64,
          ""Actual Startup Time"": 0.009,
          ""Actual Total Time"": 5.756,
          ""Actual Rows"": 49341,
          ""Actual Loops"": 1,
          ""Output"": [""smc.id"", ""smc.map_table_id"", ""smc.from_column_id"", ""smc.to_column_id""],
          ""Shared Hit Blocks"": 671,
          ""Shared Read Blocks"": 0,
          ""Shared Dirtied Blocks"": 0,
          ""Shared Written Blocks"": 0,
          ""Local Hit Blocks"": 0,
          ""Local Read Blocks"": 0,
          ""Local Dirtied Blocks"": 0,
          ""Local Written Blocks"": 0,
          ""Temp Read Blocks"": 0,
          ""Temp Written Blocks"": 0,
          ""I/O Read Time"": 0.000,
          ""I/O Write Time"": 0.000,
          ""Temp I/O Read Time"": 0.000,
          ""Temp I/O Write Time"": 0.000
        },
        {
          ""Node Type"": ""Hash"",
          ""Parent Relationship"": ""Inner"",
          ""Parallel Aware"": false,
          ""Async Capable"": false,
          ""Startup Cost"": 176.75,
          ""Total Cost"": 176.75,
          ""Plan Rows"": 5,
          ""Plan Width"": 48,
          ""Actual Startup Time"": 0.589,
          ""Actual Total Time"": 0.590,
          ""Actual Rows"": 18,
          ""Actual Loops"": 1,
          ""Output"": [""smt.id"", ""smt.from_table_id"", ""smt.to_table_id""],
          ""Hash Buckets"": 1024,
          ""Original Hash Buckets"": 1024,
          ""Hash Batches"": 1,
          ""Original Hash Batches"": 1,
          ""Peak Memory Usage"": 10,
          ""Shared Hit Blocks"": 107,
          ""Shared Read Blocks"": 0,
          ""Shared Dirtied Blocks"": 0,
          ""Shared Written Blocks"": 0,
          ""Local Hit Blocks"": 0,
          ""Local Read Blocks"": 0,
          ""Local Dirtied Blocks"": 0,
          ""Local Written Blocks"": 0,
          ""Temp Read Blocks"": 0,
          ""Temp Written Blocks"": 0,
          ""I/O Read Time"": 0.000,
          ""I/O Write Time"": 0.000,
          ""Temp I/O Read Time"": 0.000,
          ""Temp I/O Write Time"": 0.000,
          ""Plans"": [
            {
-              ""Node Type"": ""Seq Scan"",
              ""Parent Relationship"": ""Outer"",
              ""Parallel Aware"": false,
              ""Async Capable"": false,
              ""Relation Name"": ""snapshot_map_table"",
              ""Schema"": ""public"",
              ""Alias"": ""smt"",
              ""Startup Cost"": 0.00,
              ""Total Cost"": 176.75,
              ""Plan Rows"": 5,
              ""Plan Width"": 48,
              ""Actual Startup Time"": 0.057,
              ""Actual Total Time"": 0.577,
              ""Actual Rows"": 18,
              ""Actual Loops"": 1,
              ""Output"": [""smt.id"", ""smt.from_table_id"", ""smt.to_table_id""],
              ""Filter"": ""(smt.source_id = 'ec740600-3e27-4613-bbe2-1c647bbdfe19'::uuid)"",
              ""Rows Removed by Filter"": 5604,
              ""Shared Hit Blocks"": 107,
              ""Shared Read Blocks"": 0,
              ""Shared Dirtied Blocks"": 0,
              ""Shared Written Blocks"": 0,
              ""Local Hit Blocks"": 0,
              ""Local Read Blocks"": 0,
              ""Local Dirtied Blocks"": 0,
              ""Local Written Blocks"": 0,
              ""Temp Read Blocks"": 0,
              ""Temp Written Blocks"": 0,
              ""I/O Read Time"": 0.000,
              ""I/O Write Time"": 0.000,
              ""Temp I/O Read Time"": 0.000,
              ""Temp I/O Write Time"": 0.000
            }
          ]
        }
      ]
    },
    ""Query Identifier"": 971855624158779861,
    ""Planning"": {
      ""Shared Hit Blocks"": 273,
      ""Shared Read Blocks"": 0,
      ""Shared Dirtied Blocks"": 0,
      ""Shared Written Blocks"": 0,
      ""Local Hit Blocks"": 0,
      ""Local Read Blocks"": 0,
      ""Local Dirtied Blocks"": 0,
      ""Local Written Blocks"": 0,
      ""Temp Read Blocks"": 0,
      ""Temp Written Blocks"": 0,
      ""I/O Read Time"": 0.000,
      ""I/O Write Time"": 0.000,
      ""Temp I/O Read Time"": 0.000,
      ""Temp I/O Write Time"": 0.000
    },
    ""Planning Time"": 1.889,
    ""Triggers"": [
    ],
    ""Execution Time"": 14.765
  }
]"

We should probably figure out why azul-ucsc-0-hammerbox@platform-anvil-prod.iam.gserviceaccount.com needs to call retrieveSnapshot this many times, including repeated queries to the same snapshot, but we should also fix our db.

@davidangb davidangb changed the title add db indexes for retrieveSnapshot() CTM-661: add db indexes for retrieveSnapshot() Sep 3, 2026
@sonarqubecloud

sonarqubecloud Bot commented Sep 3, 2026

Copy link
Copy Markdown

@aednichols

Copy link
Copy Markdown
Contributor

The analysis makes sense to me – indexes can make a huge difference; but I'm not really familiar with Postgres query plans.

Practical question, how long will this changeset take to apply on the production instance?

@davidangb
davidangb marked this pull request as ready for review September 4, 2026 13:15
@davidangb
davidangb requested a review from a team as a code owner September 4, 2026 13:15
@davidangb
davidangb requested review from snf2ye and removed request for a team September 4, 2026 13:15
@davidangb

Copy link
Copy Markdown
Contributor Author

Practical question, how long will this changeset take to apply on the production instance?

@aednichols I don't know for sure, but my best guess is seconds or tens of seconds.

--------- new index snapshot_table_parent_id_idx on snapshot_table.parent_id
select count(1) from snapshot_table;
-- 75,195
select count(distinct(parent_id)) from snapshot_table;
-- 30,795

--------- new index snapshot_column_table_id_idx on snapshot_column.table_id
select count(1) from snapshot_column;
-- 1,866,360
select count(distinct(table_id)) from snapshot_column;
-- 75,195

--------- new index snapshot_map_table_source_id_idx on snapshot_map_table.source_id
select count(1) from snapshot_map_table;
-- 75,149
select count(distinct(source_id)) from snapshot_map_table;
-- 30,759

--------- new index snapshot_map_column_map_table_id_idx on snapshot_map_column.map_table_id
select count(1) from snapshot_map_column;
-- 1,864,637
select count(distinct(map_table_id)) from snapshot_map_column;
-- 75,149

At these scales, Postgres should be pretty efficient. 1.8M rows is not trivial but also is not particularly scary.

@davidangb
davidangb requested a review from aednichols September 4, 2026 14:52

@snf2ye snf2ye left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Than you, this looks great. The google query insights shows that the sequential scans the snapshot_column and snapshot_map_column tables were big factors in the load on the db during one of the recent spikes. The join between snapshot_table and snapshot_column and then also between snapshot_map_table and snapshot_map_column were also pretty big.

As far as applying these changes, we have done this sort of change before to help with the load on the DB and I don't think it has caused an issue before (see #2030 and #1861)

@davidangb
davidangb merged commit b3008d1 into develop Sep 8, 2026
18 checks passed
@davidangb
davidangb deleted the da_dbindexes branch September 8, 2026 14:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants