Self-hosted docker upgrade from 18.10 to 18.11 fails (PG::UndefinedTable: ERROR: relation "index_deployments_on_id_and_status_and_created_at" does not exist)
After upgrading the docker image tag, pulling the new image and restarting the containers, the database migrations fail with the following error:
PG::UndefinedTable: ERROR: relation "index_deployments_on_id_and_status_and_created_at" does not exist
On professional_teal_sparrow, the post_deploy_db_migration [job](https://console.gitlab-dedicated.com/tenants/8/amp_jobs/41531/logs) database migration failed with the error: `2026-05-26 06:08:13.313:PG::UndefinedTable: ERROR: relation "bigint_idx_89fb13150c9da167f173" does not exist`
@ktchernov was able to resolve the issue by accessing the rails console and adding the missing relation back.
slack:
<details>
```
failed again:
2026-05-26 06:08:13.305:main: -> 0.0000s
2026-05-26 06:08:13.305:main: -- quote_table_name("index_deployments_on_id_and_status_and_created_at")
2026-05-26 06:08:13.305:main: -> 0.0000s
2026-05-26 06:08:13.305:main: -- execute("ALTER INDEX \"bigint_idx_89fb13150c9da167f173\" RENAME TO \"index_deployments_on_id_and_status_and_created_at\"")
2026-05-26 06:08:13.312:main: == [advisory_lock_connection] object_id: 58580, pg_backend_pid: 13205
2026-05-26 06:08:13.312:bin/rails aborted!
2026-05-26 06:08:13.312:StandardError: An error has occurred, all later migrations canceled:
2026-05-26 06:08:13.312:
2026-05-26 06:08:13.312:PG::UndefinedTable: ERROR: relation "bigint_idx_89fb13150c9da167f173" does not exist
2026-05-26 06:08:13.312:/srv/gitlab/vendor/bundle/ruby/3.3.0/gems/activerecord-7.2.3/lib/active_record/connection_adapters/postgresql/database_statements.rb:56:in `exec'
2026-05-26 06:08:13.312:/srv/gitlab/vendor/bundle/ruby/3.3.0/gems/activerecord-7.2.3/lib/active_record/connection_adapters/postgresql/database_statements.rb:56:in `block (2 levels) in raw_execute'
2026-05-26 06:08:13.312:/srv/gitlab/vendor/bundle/ruby/3.3.0/gems/activerecord-7.2.3/lib/active_record/connection_adapters/abstract_adapter.rb:1024:in `block in with_raw_connection'
2026-05-26 06:08:13.312:/srv/gitlab/vendor/bundle/ruby/3.3.0/gems/activesupport-7.2.3/lib/active_support/concurrency/null_lock.rb:9:in `synchronize'
2026-05-26 06:08:13.312:/srv/gitlab/vendor/bundle/ruby/3.3.0/gems/activerecord-7.2.3/lib/active_record/connection_adapters/abstract_adapter.rb:993:in `with_raw_connection'Vamshidhar Poralla [11:12 PM]
can i get a output of the following sql
SELECT indexname FROM pg_indexes
WHERE tablename = 'deployments'
AND indexname LIKE '%status_and_created_at%'
OR indexname LIKE '%89fb13%';Kunal [11:14 PM]
give me a few mins please
Kunal [11:15 PM]
@gitlab-dedicated/ems permission to break glass
Kunal [11:18 PM]
gitlabhq_production=>
gitlabhq_production=> SELECT indexname FROM pg_indexes
gitlabhq_production-> WHERE tablename = 'deployments'
gitlabhq_production-> AND indexname LIKE '%status_and_created_at%'
gitlabhq_production-> OR indexname LIKE '%89fb13%';
indexname
-----------------------------------------------------------
index_deployments_on_project_id_and_status_and_created_at
index_deployments_on_id_and_status_and_created_at
(2 rows)
gitlabhq_production=>Vamshidhar Poralla [11:24 PM]
The migration's swap_indexes expects both to exist simultaneously. What it needs to do:
Rename index_deployments_on_id_and_status_and_created_at → temp_xxxx
Rename bigint_idx_89fb13150c9da167f173 → index_deployments_on_id_and_status_and_created_at
But since we have already effectively done step 2 manually, the end state is correct — the right index is in place with the right name.
Skipping is now actually valid for this specific index.
The cleanest path forward:
-- Give the migration what it needs for step 1 to succeed,
-- then step 2 will rename "bigint" index which doesn't exist...
-- so instead, just rename current index to the bigint name so swap can complete fully:
ALTER INDEX index_deployments_on_id_and_status_and_created_at
RENAME TO bigint_idx_89fb13150c9da167f173;This restores the exact state the migration expects — both indexes exist with their original names — and lets swap_indexes do both renames cleanly itself.
Then re-trigger the migration.Kunal [11:25 PM]
ok, running the following:
ALTER INDEX index_deployments_on_id_and_status_and_created_at
RENAME TO bigint_idx_89fb13150c9da167f173;Kunal [11:27 PM]
done, Is this what you expected?
gitlabhq_production=> ALTER INDEX index_deployments_on_id_and_status_and_created_at
gitlabhq_production-> RENAME TO bigint_idx_89fb13150c9da167f173;
ALTER INDEX
gitlabhq_production=> SELECT indexname FROM pg_indexes
WHERE tablename = 'deployments'
AND indexname LIKE '%status_and_created_at%'
OR indexname LIKE '%89fb13%';
indexname
-----------------------------------------------------------
index_deployments_on_project_id_and_status_and_created_at
bigint_idx_89fb13150c9da167f173
(2 rows)
gitlabhq_production=>(edited)
Vamshidhar Poralla [11:31 PM]
Create the missing original index:
CREATE INDEX CONCURRENTLY index_deployments_on_id_and_status_and_created_at
ON deployments (id, status, created_at);Once done you'll have both:
SELECT indexname FROM pg_indexes
WHERE tablename = 'deployments'
AND indexname IN (
'index_deployments_on_id_and_status_and_created_at',
'bigint_idx_89fb13150c9da167f173'
);
index_deployments_on_id_and_status_and_created_at ✓
bigint_idx_89fb13150c9da167f173 ✓
Which is exactly what swap_indexes expects. Then re-trigger the migration and it should complete cleanly.
Kunal [11:32 PM]
gitlabhq_production=> CREATE INDEX CONCURRENTLY index_deployments_on_id_and_status_and_created_at
gitlabhq_production-> ON deployments (id, status, created_at);
ERROR: canceling statement due to statement timeout
gitlabhq_production=>Kunal [11:32 PM]
this timed out, is it safe to retry?
Kunal [11:32 PM]
or should we look at something else before that
Vamshidhar Poralla [11:33 PM]
we need to set stattement timeout to false
SET statement_timeout = 0;
CREATE INDEX CONCURRENTLY index_deployments_on_id_and_status_and_created_at
ON deployments (id, status, created_at);Vamshidhar Poralla [11:33 PM]
This index creation may take several minutes depending on the table size.
Kunal [11:34 PM]
previous timeout was misleading, it did create index":
gitlabhq_production=> SET statement_timeout = 0;
SET
gitlabhq_production=> CREATE INDEX CONCURRENTLY index_deployments_on_id_and_status_and_created_at
gitlabhq_production-> ON deployments (id, status, created_at);
ERROR: relation "index_deployments_on_id_and_status_and_created_at" already exists
gitlabhq_production=> SELECT indexname FROM pg_indexes
gitlabhq_production-> WHERE tablename = 'deployments'
gitlabhq_production-> AND indexname IN (
gitlabhq_production(> 'index_deployments_on_id_and_status_and_created_at',
gitlabhq_production(> 'bigint_idx_89fb13150c9da167f173'
gitlabhq_production(> );
indexname
---------------------------------------------------
index_deployments_on_id_and_status_and_created_at
bigint_idx_89fb13150c9da167f173
(2 rows)
gitlabhq_production=>Vamshidhar Poralla [11:34 PM]
Please re-trigger the migrations now
Kunal [11:36 PM]
ok, running
Kunal [11:42 PM]
that went fine, its now running db:migrate:geo
Kunal [11:42 PM]
all done, db migration completed successfully
Vamshidhar Poralla [11:42 PM]
Thats cool
Vamshidhar Poralla [11:45 PM]
Great! To summarise what happened and why:
The customer's DB was missing index_deployments_on_id_and_status_and_created_at — it had been cleaned up ahead of schedule as part of their bigint migration progress
The swap_indexes logic needed both the original and bigint-named indexes to exist simultaneously to do its two-step rename
The back-and-forth renames were us trying to reconstruct that state without creating the missing index
The real fix was simply creating the original index.
```
</details>
- Inspections of two other tenants that have not yet been upgraded to 18.11.x show that they have the relation that professional_teal_sparrow was missing.
- [this MR](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/237974) will be in next week's maintenance and will allow this migration to continue without causing failure.
### Next Steps
- [x] Document mitigation
- [x] re-enable upgrade to 18.11 op APAC tenants
- [ ] after APAC tenants update, re-evaluate whether or not to enable upgrade to 18.11 on AMER-2 tenants
### Checking for missing indexes
- `Gitlab.com_except_jh?`
- `ActiveRecord::Base.connection.indexes('deployments').each { |i| puts "#{i.name} | columns: #{i.columns.join(', ')} | unique: #{i.unique}" }; nil`
- `ActiveRecord::Base.connection.columns('deployments').each { |c| puts "#{c.name} | #{c.type} | #{c.sql_type} | null: #{c.null} | default: #{c.default}" }; nil`
### Mitigation
- If the index `index_deployments_on_id_and_status_and_created_at` is missing
create it with
CREATE INDEX CONCURRENTLY index_deployments_on_id_and_status_and_created_at
ON deployments (id, status, created_at);
- re-run the migration
- If the bigint index `bigint_idx_89fb13150c9da167f173` is missing
create it with
CREATE INDEX CONCURRENTLY bigint_idx_89fb13150c9da167f173
ON deployments (id_convert_to_bigint, status, created_at);
- re-run the migration
- If you get any statement_timeout errors. run `SET statement_timeout = 0;` and re-run the commands above
#### Example:
```
gitlabhq_production=> SET statement_timeout = 0;
SET
gitlabhq_production=> CREATE INDEX CONCURRENTLY index_deployments_on_id_and_status_and_created_at
gitlabhq_production-> ON deployments (id, status, created_at);
ERROR: relation "index_deployments_on_id_and_status_and_created_at" already exists
gitlabhq_production=> SELECT indexname FROM pg_indexes
gitlabhq_production-> WHERE tablename = 'deployments'
gitlabhq_production-> AND indexname IN (
gitlabhq_production(> 'index_deployments_on_id_and_status_and_created_at',
gitlabhq_production(> 'bigint_idx_89fb13150c9da167f173'
gitlabhq_production(> );
indexname
---------------------------------------------------
index_deployments_on_id_and_status_and_created_at
bigint_idx_89fb13150c9da167f173
(2 rows)
gitlabhq_production=>
```
issue