Skip to content

Drop legacy submit_message & groups columns from jobs#4984

Open
tgucks wants to merge 3 commits into
masterfrom
job-metadata-locked-migration
Open

Drop legacy submit_message & groups columns from jobs#4984
tgucks wants to merge 3 commits into
masterfrom
job-metadata-locked-migration

Conversation

@tgucks

@tgucks tgucks commented Jun 26, 2026

Copy link
Copy Markdown
Collaborator

What type of PR is this?

Feature/Refactor

What this PR does / why we need it

This PR removes the config flags for the migration of the columns groups and submit_message from the scheduler.jobs table to the job_metadata table.

Special notes for your reviewer

Migration 039 Lock-Hold Benchmark

Migration 039 backfills job_metadata from jobs and drops the legacy submit_message/groups columns, all under an ACCESS EXCLUSIVE lock. This measures how long that lock is held.

Environment: Postgres 17.10 (Docker on macOS). submit_message = 2 KB, groups = 64 B, scheduling_info = 512 B. Numbers are pessimistic vs. production (slower virtualized disk; 2 KB payloads exceed the real ~500 B compressed size and force TOAST writes).

What's timed: the full migration block — INSERT … SELECT + 2× DROP COLUMN + COMMIT. The INSERT is >99.9% of the window; the DROP COLUMNs are catalog-only and stay ~1-6 ms at every scale.

Lock-hold time (legacy phase, worst case)

Rows Lock hold Plan
10 ~9 ms trivial
100K 2.02 s Seq Scan
200K 4.20 s Seq Scan
500K 9.91 s Seq Scan
1M 24.49 s Seq Scan

Scaling is linear (~2 µs/row): 2.02 → 4.20 → 9.91 → 24.49 s.

By migration phase

Only the legacy phase copies rows (the worst case measured above). In dualWrite and cutover, the migration inserts 0 rows (already present, or submit_message is NULL) and completes in roughly the seq-scan time - sub-second to ~1 s even at 1M.

Answering the original combos

  • Few jobs (10): ~9 ms lock window, every phase. Negligible.
  • A lot of jobs (1M): ~24.5 s, likely well under that in production.

@greptile-apps

greptile-apps Bot commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR completes the migration of submit_message and groups from the scheduler.jobs table to the job_metadata table by removing all three migration-phase config flags (legacy, dualWrite, cutover) and hardwiring the final "cutover" behaviour throughout the scheduler and scheduleringester. A new SQL migration (039) backfills job_metadata from jobs and then drops the two legacy columns.

  • Config/struct cleanup: JobMetadataMigrationPhase is removed from both scheduler.Configuration and scheduleringester.Configuration, the PostgresJobRepository constructor loses its phase parameter, and SchedulerDb loses its migrationPhase field; all call sites are updated.
  • Ingester write path: InsertJobs now maps job IDs to *JobInsertion (a new wrapper carrying a *schedulerdb.Job and a JobInsertionMetadata), and WriteDbOp unconditionally upserts job_metadata before jobs in every transaction.
  • Lease-fetch read path: FetchJobRunLeases switches from a phase-conditional COALESCE + LEFT JOIN to a bare INNER JOIN job_metadata, meaning any job without a job_metadata row is silently excluded from lease results — safe only after migration 039 has successfully backfilled every active row.

Confidence Score: 4/5

Safe to merge once the deployment team has confirmed that all active jobs have a corresponding job_metadata row before running migration 039, since the new INNER JOIN in FetchJobRunLeases will silently drop any leases for jobs that are missing a metadata row.

The code changes themselves are mechanically correct — the phase-conditional logic is cleanly removed, tests are updated consistently, and the NOT NULL constraint on job_metadata.submit_message is properly handled in every code path. The residual risk is operational: migration 039 backfills only WHERE submit_message IS NOT NULL, and the scheduler immediately starts using an INNER JOIN after deployment. A job that slipped through without a metadata row would lose its leases silently. That operational window is short but real.

internal/scheduler/database/migrations/039_backfill_and_drop_job_metadata_columns.sql and internal/scheduler/database/job_repository.go deserve a careful second look around deployment ordering — the INNER JOIN goes live the moment the binary rolls, before migration 039 has run.

Important Files Changed

Filename Overview
internal/scheduler/database/migrations/039_backfill_and_drop_job_metadata_columns.sql New migration that backfills job_metadata from jobs and drops submit_message/groups columns; holds ACCESS EXCLUSIVE lock for entire backfill duration (already flagged in previous threads)
internal/scheduleringester/schedulerdb.go Removes migrationPhase; unconditionally upserts job_metadata before jobs. No FK constraint exists on job_metadata(job_id) so ordering is safe.
internal/scheduler/database/job_repository.go FetchJobRunLeases now uses INNER JOIN job_metadata; jobs without a metadata row are silently excluded from lease results (deployment risk if migration 039 misses any rows).
internal/scheduleringester/dbops.go InsertJobs type changed from map[string]*Job to map[string]*JobInsertion; Job and metadata separated cleanly; all callers updated.
internal/scheduleringester/schedulerdb_test.go TestWriteOps uses addDefaultValues to prevent NOT NULL constraint violations; TestStore explicitly verifies job_metadata writes via SelectJobMetadata. TestPoolFiltering explicitly initialises SubmitMessage/Groups.
internal/scheduler/database/db_pruner_test.go Removes SubmitMessage field from test helper (correct); but TestPruner_DeletesJobMetadataRows was deleted with migration test file, leaving pruner's job_metadata cleanup logic untested.
internal/scheduler/database/models.go Groups and SubmitMessage fields removed from Job struct; consistent with migration 039 dropping those columns.
internal/scheduler/database/query.sql.go All sqlc-generated queries updated to remove groups/submit_message from explicit column lists; new SelectJobMetadata query added for test verification.
internal/scheduler/database/job_repository_test.go TestFetchJobRunLeases correctly seeds job_metadata via new upsertJobMetadata helper and verifies leases pull groups/submit_message from job_metadata.
internal/scheduleringester/instructions.go handleSubmitJob now places groups/submit_message in JobInsertionMetadata rather than on the Job struct; clean separation of concerns.
internal/scheduler/configuration/configuration.go JobMetadataMigrationPhase field removed from Configuration; validation and import cleaned up.

Reviews (3): Last reviewed commit: "Merge branch 'master' into job-metadata-..." | Re-trigger Greptile

Comment on lines +5 to +10
INSERT INTO job_metadata (job_id, submit_message, groups)
SELECT j.job_id, j.submit_message, j.groups
FROM jobs j
WHERE j.submit_message IS NOT NULL
AND NOT EXISTS (SELECT 1 FROM job_metadata jm WHERE jm.job_id = j.job_id)
ON CONFLICT (job_id) DO NOTHING;

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.

P1 Backfill skips jobs with NULL submit_message, which become permanently invisible after the column drop

The condition WHERE j.submit_message IS NOT NULL means any jobs row where submit_message is NULL and no corresponding job_metadata row already exists will never be backfilled. After the columns are dropped, FetchJobRunLeases uses an INNER JOIN with job_metadata, so those jobs will be silently excluded from every lease-fetch result — their runs will never be dispatched to an executor.

In practice this should be safe if the deployment always ran in legacy or dualWrite mode (both of which populate submit_message in jobs), but it leaves no safety net for any data anomalies. Consider adding a pre-migration assertion that SELECT COUNT(*) FROM jobs WHERE submit_message IS NULL AND NOT EXISTS (SELECT 1 FROM job_metadata jm WHERE jm.job_id = jobs.job_id) AND terminated = false returns zero before proceeding.

Comment on lines 102 to 118
case InsertJobs:
if s.migrationPhase.WritesJobMetadata() {
specs := make([]any, 0, len(o))
for _, v := range o {
specs = append(specs, schedulerdb.JobMetadatum{
JobID: v.JobID,
SubmitMessage: v.SubmitMessage,
Groups: v.Groups,
})
}
if err := database.Upsert(ctx, tx, "job_metadata", specs); err != nil {
return err
}
}
metadata := make([]any, 0, len(o))
records := make([]any, 0, len(o))
for _, v := range o {
job := *v
if !s.migrationPhase.WritesJobs() {
// Cutover phase: submit_message and groups live only in job_metadata.
job.SubmitMessage = nil
job.Groups = nil
}
records = append(records, job)
metadata = append(metadata, schedulerdb.JobMetadatum{
JobID: v.Job.JobID,
SubmitMessage: v.Metadata.SubmitMessage,
Groups: v.Metadata.Groups,
})
records = append(records, *v.Job)
}
excluded := []string{"terminated"}
if !s.migrationPhase.WritesJobs() {
excluded = append(excluded, "submit_message", "groups")
if err := database.Upsert(ctx, tx, "job_metadata", metadata); err != nil {
return err
}
err := database.Upsert(ctx, tx, "jobs", records, database.WithExcludeColumns(excluded...))
if err != nil {
if err := database.Upsert(ctx, tx, "jobs", records, database.WithExcludeColumns("terminated")); err != nil {
return err
}

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.

P2 job_metadata is upserted before the jobs row exists in this transaction

The new WriteDbOp path inserts into job_metadata first, then into jobs. If job_metadata(job_id) has an immediate (non-deferred) foreign-key constraint referencing jobs(job_id), this will fail at insert time. The old dualWrite code also did this, so it was presumably not an issue then, but it's worth confirming that any FK is either absent or declared DEFERRABLE INITIALLY DEFERRED before making this the only code path.

@datadog-armadaproject

This comment was marked as resolved.

tgucks added 2 commits June 26, 2026 08:38
Signed-off-by: Trey Guckian <24757349+tgucks@users.noreply.github.com>
Signed-off-by: Trey Guckian <24757349+tgucks@users.noreply.github.com>
@tgucks tgucks force-pushed the job-metadata-locked-migration branch from b6a2413 to 05f81a7 Compare June 26, 2026 13:39
Comment thread internal/scheduleringester/schedulerdb_test.go
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.

1 participant