Skip to content

RunAI Backup, Restore and Migration of Postgres

Backup

Verify PostgreSQL pod is running

kubectl get pods -n runai | grep postgresql
# Expected: postgresql-master pod in Running state

Create backup directory

mkdir -p ~/backup_workloads_$(date +%Y%m%d)
cd ~/backup_workloads_$(date +%Y%m%d)

Backup the workloads schema

kubectl exec -n runai deploy/postgresql-master -- \
 pg_dump -U postgres -d backend -n workloads \
 > backup_workloads_CRITICAL_$(date +%Y%m%d_%H%M%S).sql

Verify backup was created

ls -lh backup_workloads_CRITICAL_*.sql

Expected output:
-rw-r--r-- 1 user user 15M Feb 20 14:30 backup_workloads_CRITICAL_20260220_143045.sq

Warning

Keep the backup files safe! During the rollback, if something goes wrong, this will be used for the restore.

Check the Current Migration State of RunAI

kubectl exec -n runai deploy/postgresql-master -- \
 psql -U postgres -d backend -c \
 "SET search_path TO workloads; SELECT version, dirty FROM schema_migrations;"

Expected output (current state):
 version | dirty
---------+-------
 2220006 | f

Or

 version | dirty
---------+-------
 2220009 | t

Check which tables exist within Postgres

kubectl exec -n runai runai-backend-postgresql-0 -- \
 psql -U postgres -d backend -c \
 "SET search_path TO workloads; SELECT tablename FROM pg_tables WHERE schemaname = 'workloads' ORDER BY tablename;"
Key tables to look for: - workloads, pods, events — should exist (from v2.16) - revisions — exists if v2.21 migrations ran (02210002) - crud_templates — exists if v2.22 migrations ran (02220001) - priorities — exists if 02220008 ran - workload_types — exists if 02220009 ran - applications — exists if v2.23 migrations ran (02230008)

Check tenant_id column types

kubectl exec -n runai runai-backend-postgresql-0 -- \
psql -U postgres -d backend << 'EOF'
SET search_path TO workloads;
SELECT table_name, column_name, data_type
FROM information_schema.columns
WHERE table_schema = 'workloads'
    AND column_name = 'tenant_id'
ORDER BY table_name;
EOF

If migrations are incomplete (stuck at v2.20), you'll only see:

  table_name               | column_name | data_type
----------------------------+-------------+-----------
 events                    | tenant_id  | bigint
 pods                      | tenant_id  | bigint
 workloads                 | tenant_id  | bigint
 workloads_phase_history   | tenant_id  | bigint
If all migrations are complete:
  table_name                      | column_name | data_type

------------------------------------+-------------+-------------------
 events                            | tenant_id  | bigint
 external_workloads_type_config    | tenant_id  | character varying
 pods                              | tenant_id  | bigint
 priorities                        | tenant_id  | text
 workload_type_resource_interfaces | tenant_id  | character varying
 workload_type_statuses            | tenant_id  | character varying
 workloads                         | tenant_id  | bigint
 workloads_categories              | tenant_id  | bigint
 workloads_phase_history           | tenant_id  | bigint

Fix the migration statd

Case A: If dirty = f (clean state, eg. version 2200006)

The migration state is clean. Simply restarting the workload-manager service will trigger migrations to run from the current version up to v2.23. Skip to Step (5).

Case B: If dirty = t (dirty state)

You need to fix the dirty flag first, follow the steps below:

Set the dirty flag to false:

# Replace <VERSION> with the version number shown in Step (1)_
kubectl exec -n runai runai-backend-postgresql-0 -- \
 psql -U postgres -d backend -c \
 "SET search_path TO workloads; UPDATE schema_migrations SET dirty = 'f' WHERE dirty = 't';"

Then verify the change:

kubectl exec -n runai runai-backend-postgresql-0 -- \
psql -U postgres -d backend -c \
"SET search_path TO workloads; SELECT version, dirty FROM schema_migrations;"


## Expected output:
version | dirty
---------+-------
<VERSION> | f

Identify the workload-manager pod name

kubectl get pods -n runai | grep -iE "workload-manager|workload"

Restart the workload-manager service

# Use the label from step (5). Common labels:
kubectl delete pod -n runai -l app=workload-manager
# OR if the label is different:
# kubectl delete pod -n runai <pod-name>

Watch the migration run:

kubectl logs -n runai -l app=workload-manager -f --tail=100

Scenario A - Migration ran successfully.

{"level":"info","message":"Start migration of schema workloads with command"}
{"level":"info","message":"connecting to postgres db:backend, schema: workloads..."}
{"level":"info","message":"Migrating up"}
{"level":"info","message":"Migrations ended successfully"}
Skip to Verify key tables were created.

Scenario B - Pod crashes with dirty database error:

{"level":"fatal","message":"failed running migrations: Dirty database version XXXXXXX. Fix and force version."}
# This is expected. Note the version number from the log, then fix it:
kubectl exec -n runai runai-backend-postgresql-0 -- \
 psql -U postgres -d backend -c \
 "SET search_path TO workloads; UPDATE schema_migrations SET version = XXXXXXX, dirty = 'f';"
Then, restart the pod again:
kubectl delete pod -n runai -l app=workload-manager
# Repeat Step (7) until migrations complete successfully.

Verify the final migration state:

kubectl exec -n runai runai-backend-postgresql-0 -- \
psql -U postgres -d backend -c \
"SET search_path TO workloads; SELECT version, dirty FROM schema_migrations;"



# Expected output:
 version | dirty
---------+-------
 2230018 | f

Verify key tables were created

kubectl exec -n runai runai-backend-postgresql-0 -- \
psql -U postgres -d backend `<<` 'EOF'
SET search_path TO workloads;

# Check critical tables exist
SELECT tablename FROM pg_tables
WHERE schemaname = 'workloads'
AND tablename IN ('priorities', 'workload_types', 'crud_templates',
'applications', 'revisions')
ORDER BY tablename;



# Check priorities were seeded
SELECT name, value FROM priorities WHERE source = 'Run:ai' ORDER BY value;

# Check workload types were seeded
SELECT name FROM workload_types ORDER BY name;



# Check views exist
SELECT viewname FROM pg_views
WHERE schemaname = 'workloads'
AND viewname IN ('timeout_events_of_workload_view', 'workloads_phases_view', 'external_workloads_type_config_view')
ORDER BY viewname;
EOF

# Expected output (newer tables should have text/varchar):
   tablename
-----------------
 applications
 crud_templates
 priorities
 revisions
 workload_types
(5 rows)

   name    | value
-------------+-------
 very-low   |   25
 low        |   40
 medium-low |   65
 medium     |   80
 medium-high |   90
 high       |  125
 very-high  |  150
(7 rows)


        name
-----------------------
 AmlJob
 CronJob
 Deployment
 DevWorkspace
 ... (24 rows)


             viewname
-------------------------------------
 external_workloads_type_config_view
 timeout_events_of_workload_view
 workloads_phases_view
(3 rows)

Verify workload-manager logs are clean

kubectl logs -n runai -l app=workload-manager --tail=100 | grep -iE "error|failed|fatal"

# Expected Output:
No migration-related errors.

Check PostgreSQL logs are improved

kubectl logs -n runai runai-backend-postgresql-0 --tail=200 | grep -iE "duplicate key|foreign key"

# Expected output:
No more duplicate key or foreign key errors. These should stop once the schema is consistent.

Test in UI

  1. Logout from Run:ai UI
  2. Clear browser cache (Ctrl+Shift+Delete or Cmd+Shift+Delete)
  3. Login again
  4. Navigate to Workloads
  5. Workloads list should load without errors
  6. Workload status/phase should be displayed correctly

  7. Check that workload events and timeout events are visible

  8. Verify new workload submissions work correctly