Skip to content

TRT-2801: Rewrite bug loader to use COPY + bulk SQL operations#3784

Open
mstaeble wants to merge 1 commit into
openshift:mainfrom
mstaeble:worktree-bug-loader-copy
Open

TRT-2801: Rewrite bug loader to use COPY + bulk SQL operations#3784
mstaeble wants to merge 1 commit into
openshift:mainfrom
mstaeble:worktree-bug-loader-copy

Conversation

@mstaeble

@mstaeble mstaeble commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Rewrites the bug loader to use PostgreSQL COPY protocol (pgx.CopyFrom) and bulk SQL operations, replacing the old GORM-based row-by-row approach
  • Uses 3 separate BigQuery queries loading into 3 temporary PostgreSQL tables, with all merging done in SQL (zero application-level data joining)
  • Adds a generic CopyToTempTable[T] helper in pkg/db/pgx.go that handles temp table creation, COPY loading, and cleanup (with ON COMMIT DROP for transaction-scoped tables)
  • Transform callback on BQ row iteration validates and pre-parses fields (e.g., jira_id), reporting errors through bl.addError for observability
  • Upserts bugs, syncs test/job associations via anti-join DELETE + INSERT, and reconciles triages entirely in SQL
  • Fixes a pre-existing bug in cmd/sippy/load.go where errors.WithMessage wrapped the wrong variable (err instead of bigqueryErr)

Notable design decisions

  • No audit logs for triage updates: The old GORM AfterUpdate hooks created AuditLog entries for triage changes. These are intentionally omitted since the bug loader is an automated process running on a schedule, and audit logs for automated bulk operations add noise without value.
  • FK guard on associations: Association INSERT queries include INNER JOIN bugs to guard against timing skew where BQ returns association rows referencing bugs that don't pass the upsert filter. Self-healing on next run either way.
  • Soft-delete filtering: Raw SQL in triage reconciliation and association desired-set queries explicitly filters deleted_at IS NULL, replicating what GORM did automatically.
  • Association cleanup scope: DELETE statements scope to all upserted bugs (tmp_bugs), not just bugs with current associations, so stale rows are cleaned up when a bug loses all its matches (matching the old GORM Replace() behavior).

Performance

Phase Production (old, GORM) Staging (new, COPY+SQL)
BQ fetch (3 queries) ~14s ~10s
Upsert + association sync + triage ~28s (row-by-row) <0.5s (bulk SQL)
Total ~42s ~11s

Production timing from the latest fetchdata pod logs. Staging timing from local testing. Hardware differs, but the bulk SQL operations are the main win: under 1 second vs ~28 seconds for GORM's row-by-row upsert of 1,438 bugs.

Behavioral parity

Verified against staging database:

  • Bug upserts, test/job association syncing, and triage reconciliation all produce correct results
  • Triage behaviors preserved: description sync from bug summary, bug-to-triage linking, auto-resolution for single-release triages when bug reaches ON_QA or higher
  • Restricted ("Red Hat Only") Jira cards that fail lookup are handled gracefully (old code logged warnings per card; new code skips them naturally since they have no BQ data)

Test plan

  • make lint passes
  • make test passes
  • make e2e passes
  • Tested against staging database (~11s, 14k bugs fetched, 281 upserted, associations and triages reconciled with zero errors)
  • Verified stale association cleanup works when bugs lose all matches (11 stale rows cleaned on second run)
  • Compared production fetchdata pod logs to confirm behavioral parity

🤖 Generated with Claude Code

@openshift-merge-bot

Copy link
Copy Markdown
Contributor

Pipeline controller notification
This repo is configured to use the pipeline controller. Second-stage tests will be triggered either automatically or after lgtm label is added, depending on the repository configuration. The pipeline controller will automatically detect which contexts are required and will utilize /test Prow commands to trigger the second stage.

For optional jobs, comment /test ? to see a list of all defined jobs. To trigger manually all jobs from second stage use /pipeline required command.

This repository is configured in: automatic mode

@openshift-ci

openshift-ci Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Skipping CI for Draft Pull Request.
If you want CI signal for your change, please convert it to an actual PR.
You can still manually trigger a test run with /test all

@openshift-ci openshift-ci Bot added the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Jul 16, 2026
@coderabbitai

coderabbitai Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Walkthrough

The bug loader now fetches bug and association data from BigQuery, stages it in PostgreSQL temporary tables, and performs transactional upserts and reconciliation. BigQuery initialization errors now wrap the correct error, and bug-loader query labels were renamed.

Changes

Bug loader pipeline

Layer / File(s) Summary
Fetch contracts and loader wiring
pkg/bigquery/bqlabel/labels.go, pkg/dataloader/bugloader/bugloader.go, cmd/sippy/load.go
Bug-loader labels, context handling, BigQuery row fetching and transformation, and BigQuery client error wrapping were updated.
Transactional staging
pkg/dataloader/bugloader/bugloader.go, pkg/db/pgx.go
Bug and association rows are copied into temporary tables and processed in one transaction; temporary-table cleanup handles closed transactions and commit-time dropping.
Database reconciliation
pkg/dataloader/bugloader/bugloader.go
Bugs are upserted, stale test and job associations are removed, missing associations are inserted, and triages are updated, linked, or auto-resolved through SQL reconciliation.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant SippyLoader
  participant BugLoader
  participant BigQuery
  participant PostgreSQL
  SippyLoader->>BugLoader: initialize with context and clients
  BugLoader->>BigQuery: fetch bugs and test/job associations
  BigQuery-->>BugLoader: return query rows
  BugLoader->>PostgreSQL: copy rows into temporary tables
  BugLoader->>PostgreSQL: upsert bugs and synchronize associations
  BugLoader->>PostgreSQL: reconcile triages
  PostgreSQL-->>BugLoader: commit transaction
Loading

Suggested labels: jira/valid-reference


Caution

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

  • Ignore

❌ Failed checks (1 error, 1 warning)

Check name Status Explanation Resolution
No-Sensitive-Data-In-Logs ❌ Error labels.go debug-logs sanitized query labels, including host/IP/URI/user values that can expose internal hostnames and PII. Remove or redact those fields from the debug log, or drop the log entirely.
Test Coverage For New Features ⚠️ Warning The PR adds new loader/DB helpers (CopyToTempTable, rewritten bugloader SQL paths) but deletes bugloader_test.go and adds no replacement tests. Add unit/integration tests for CopyToTempTable, the rewritten bugloader paths, and a regression test for the bigqueryErr wrap fix in cmd/sippy/load.go.
✅ Passed checks (19 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Go Error Handling ✅ Passed Touched code wraps errors with context, uses no panics, and no ignored returned errors or unsafe nil dereferences were found in the changed paths.
Sql Injection Prevention ✅ Passed PASS: changed SQL is static or joins temp tables; temp-table helper validates identifiers and call sites use hardcoded names, so no user input is interpolated.
Excessive Css In React Should Use Styles ✅ Passed The PR only changes Go backend files; no React components or inline style objects are touched, so this CSS style check is not applicable.
Single Responsibility And Clear Naming ✅ Passed BugLoader, CopyToTempTable, and the new query/row names are cohesive and specific; the changed methods each map to one clear loader step.
Feature Documentation ✅ Passed Only docs/features/job-analysis-symptoms.md exists, and it documents symptoms/triage—not the bug-loader pipeline changed here.
Stable And Deterministic Test Names ✅ Passed No Ginkgo titles were added or changed in the PR; touched files contain no It/Describe/Context/When specs, and the deleted bugloader tests were standard Go tests.
Test Structure And Quality ✅ Passed No Ginkgo tests were added or modified; the only test file in the diff was a deleted standard Go test file, so the check is not applicable.
Microshift Test Compatibility ✅ Passed No new Ginkgo e2e tests were added; the only test file changed was a deleted unit-test file, and touched code has no MicroShift-incompatible OpenShift APIs.
Single Node Openshift (Sno) Test Compatibility ✅ Passed PASS: The PR changes loader/db code only; no new Ginkgo e2e tests or SNO-sensitive patterns were added, and the deleted tests were unit tests.
Topology-Aware Scheduling Compatibility ✅ Passed Only loader/DB helper Go files changed; no manifests or scheduling/controller code, and no topology/affinity fields appear in touched files.
Ote Binary Stdout Contract ✅ Passed PASS: The patch only changes bugloader/db error handling and temp-table SQL; no new fmt.Print/os.Stdout/log-to-stdout calls or init/main setup were added.
Ipv6 And Disconnected Network Test Compatibility ✅ Passed No new Ginkgo e2e tests were added in the PR; changed files are loader/DB code and a deleted unit test file, with no It/Describe/Context/When additions.
No-Weak-Crypto ✅ Passed Changed files only add BigQuery/SQL loader logic; scans found no MD5/SHA1/DES/RC4/3DES/Blowfish/ECB, custom ciphers, or secret/token comparisons.
Container-Privileges ✅ Passed PASS: the PR only changes Go source files; no container/K8s manifests were touched, and no privilege fields appear in the modified files.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: rewriting the bug loader to use COPY and bulk SQL operations.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@pkg/dataloader/bugloader/bugloader.go`:
- Around line 246-306: Add end-to-end regression tests for BugLoader.loadIntoDB
covering transaction rollback, empty desired sets, soft-deleted bugs, and
association/triage reconciliation. Also add tests for the transaction and
direct-connection cleanup helpers in pkg/db/pgx.go, including correct handling
of pgx.ErrTxClosed; update both affected files as required and ensure the
lifecycle behavior is exercised through realistic database operations.
- Around line 377-383: Update both deletion statements around deleteTag and the
corresponding job-association deletion so their scope includes every fetched
bug, not only bug IDs present in the temporary association tables. Use the
fetched-bug identity set as the deletion driver, then retain the existing NOT
EXISTS checks against the desired associations so stale rows are removed when a
bug’s desired set is empty.
- Around line 326-349: Update the desiredBugTests and desiredBugJobs association
queries to join only bugs where b.deleted_at IS NULL, preventing soft-deleted
bugs from receiving new association rows. Preserve the existing association
synchronization behavior for active bugs.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Central YAML (inherited)

Review profile: CHILL

Plan: Enterprise

Run ID: 23f86f8e-d836-4918-aed6-def73b7c85e3

📥 Commits

Reviewing files that changed from the base of the PR and between c7f906f and 632cbee.

📒 Files selected for processing (5)
  • cmd/sippy/load.go
  • pkg/bigquery/bqlabel/labels.go
  • pkg/dataloader/bugloader/bugloader.go
  • pkg/dataloader/bugloader/bugloader_test.go
  • pkg/db/pgx.go
💤 Files with no reviewable changes (1)
  • pkg/dataloader/bugloader/bugloader_test.go

Comment thread pkg/dataloader/bugloader/bugloader.go
Comment thread pkg/dataloader/bugloader/bugloader.go
Comment thread pkg/dataloader/bugloader/bugloader.go
@mstaeble
mstaeble force-pushed the worktree-bug-loader-copy branch from 632cbee to 8c29579 Compare July 16, 2026 19:51
@mstaeble
mstaeble marked this pull request as ready for review July 16, 2026 20:13
@mstaeble mstaeble changed the title [WIP] Rewrite bug loader to use COPY + bulk SQL operations Rewrite bug loader to use COPY + bulk SQL operations Jul 16, 2026
@openshift-ci openshift-ci Bot removed the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Jul 16, 2026
@openshift-ci
openshift-ci Bot requested review from deads2k and stbenjam July 16, 2026 20:14
@mstaeble

Copy link
Copy Markdown
Contributor Author

@coderabbitai resolve

@coderabbitai

coderabbitai Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Comments resolved and changes approved.

@openshift-ci openshift-ci Bot added the ready-for-human-review Indicates a PR has been reviewed by automated tools and is ready for human review label Jul 16, 2026
@mstaeble mstaeble changed the title Rewrite bug loader to use COPY + bulk SQL operations [WIP] TRT-2800: Rewrite bug loader to use COPY + bulk SQL operations Jul 17, 2026
@openshift-ci-robot openshift-ci-robot added the jira/valid-reference Indicates that this PR references a valid Jira ticket of any type. label Jul 17, 2026
@openshift-ci-robot

openshift-ci-robot commented Jul 17, 2026

Copy link
Copy Markdown

@mstaeble: This pull request references TRT-2800 which is a valid jira issue.

Warning: The referenced jira issue has an invalid target version for the target branch this PR targets: expected the story to target the "5.0.0" version, but no target version was set.

Details

In response to this:

Summary

  • Rewrites the bug loader to use PostgreSQL COPY protocol (pgx.CopyFrom) and bulk SQL operations, replacing the old GORM-based row-by-row approach
  • Uses 3 separate BigQuery queries loading into 3 temporary PostgreSQL tables, with all merging done in SQL (zero application-level data joining)
  • Adds a generic CopyToTempTable[T] helper in pkg/db/pgx.go that handles temp table creation, COPY loading, and cleanup (with ON COMMIT DROP for transaction-scoped tables)
  • Transform callback on BQ row iteration validates and pre-parses fields (e.g., jira_id), reporting errors through bl.addError for observability
  • Upserts bugs, syncs test/job associations via anti-join DELETE + INSERT, and reconciles triages entirely in SQL
  • Fixes a pre-existing bug in cmd/sippy/load.go where errors.WithMessage wrapped the wrong variable (err instead of bigqueryErr)

Notable design decisions

  • No audit logs for triage updates: The old GORM AfterUpdate hooks created AuditLog entries for triage changes. These are intentionally omitted since the bug loader is an automated process running on a schedule, and audit logs for automated bulk operations add noise without value.
  • FK guard on associations: Association INSERT queries include INNER JOIN bugs to guard against timing skew where BQ returns association rows referencing bugs that don't pass the upsert filter. Self-healing on next run either way.
  • deleted_at IS NULL filter: Raw SQL in triage reconciliation explicitly filters soft-deleted bugs, replicating what GORM did automatically.

Test plan

  • make lint passes
  • make test passes
  • make e2e passes
  • Tested against staging database (~11s, 14k bugs fetched, 281 upserted, associations and triages reconciled with zero errors)

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
  • Improved BigQuery error reporting during bug loading/synchronization.
  • Refreshed bug synchronization pipeline for more consistent upserts, stale association cleanup, and triage reconciliation.
  • Temporary tables now automatically clean up after successful transactions.
  • Suppressed expected failures when database transactions are already closed.
  • Tests
  • Removed outdated bugloader unit tests.

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the openshift-eng/jira-lifecycle-plugin repository.

@openshift-ci openshift-ci Bot added the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Jul 17, 2026
@mstaeble mstaeble changed the title [WIP] TRT-2800: Rewrite bug loader to use COPY + bulk SQL operations [WIP] TRT-2801: Rewrite bug loader to use COPY + bulk SQL operations Jul 17, 2026
@openshift-ci-robot

openshift-ci-robot commented Jul 17, 2026

Copy link
Copy Markdown

@mstaeble: This pull request references TRT-2801 which is a valid jira issue.

Warning: The referenced jira issue has an invalid target version for the target branch this PR targets: expected the story to target the "5.0.0" version, but no target version was set.

Details

In response to this:

Summary

  • Rewrites the bug loader to use PostgreSQL COPY protocol (pgx.CopyFrom) and bulk SQL operations, replacing the old GORM-based row-by-row approach
  • Uses 3 separate BigQuery queries loading into 3 temporary PostgreSQL tables, with all merging done in SQL (zero application-level data joining)
  • Adds a generic CopyToTempTable[T] helper in pkg/db/pgx.go that handles temp table creation, COPY loading, and cleanup (with ON COMMIT DROP for transaction-scoped tables)
  • Transform callback on BQ row iteration validates and pre-parses fields (e.g., jira_id), reporting errors through bl.addError for observability
  • Upserts bugs, syncs test/job associations via anti-join DELETE + INSERT, and reconciles triages entirely in SQL
  • Fixes a pre-existing bug in cmd/sippy/load.go where errors.WithMessage wrapped the wrong variable (err instead of bigqueryErr)

Notable design decisions

  • No audit logs for triage updates: The old GORM AfterUpdate hooks created AuditLog entries for triage changes. These are intentionally omitted since the bug loader is an automated process running on a schedule, and audit logs for automated bulk operations add noise without value.
  • FK guard on associations: Association INSERT queries include INNER JOIN bugs to guard against timing skew where BQ returns association rows referencing bugs that don't pass the upsert filter. Self-healing on next run either way.
  • deleted_at IS NULL filter: Raw SQL in triage reconciliation explicitly filters soft-deleted bugs, replicating what GORM did automatically.

Test plan

  • make lint passes
  • make test passes
  • make e2e passes
  • Tested against staging database (~11s, 14k bugs fetched, 281 upserted, associations and triages reconciled with zero errors)

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
  • Improved BigQuery error reporting during bug loading/synchronization.
  • Refreshed bug synchronization pipeline for more consistent upserts, stale association cleanup, and triage reconciliation.
  • Temporary tables now automatically clean up after successful transactions.
  • Suppressed expected failures when database transactions are already closed.
  • Tests
  • Removed outdated bugloader unit tests.

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the openshift-eng/jira-lifecycle-plugin repository.

Replace per-row GORM operations (~4000 individual DB round-trips) with
PostgreSQL COPY protocol and bulk SQL. Use three separate BigQuery
queries (matching the prod query shapes) for independent slot allocation
and simpler execution plans:

1. All bugs in the recency window (metadata only, no joins)
2. Test/component associations (jira_id, link_name pairs)
3. Job associations (jira_id, link_name pairs)

Each query result loads into its own temp table via COPY protocol, with
zero application-level data merging.

Key changes:
- Three BQ queries with dedicated temp tables (tmp_bugs,
  tmp_test_assocs, tmp_job_assocs) instead of one combined query
- Bugs upserted from tmp_bugs filtered by existence in association
  tables or matching triage URL, using INSERT...ON CONFLICT with
  IS DISTINCT FROM to skip no-op updates
- Test and job associations synced via anti-join DELETE + INSERT ON
  CONFLICT DO NOTHING, with UniqueID expansion handled by SQL joins on
  test_ownerships instead of Go-side map lookups
- Triage reconciliation (description sync, bug linking, auto-resolve)
  done as bulk UPDATEs instead of per-row GORM Save calls
- All operations wrapped in a single transaction for atomicity
- Generic fetchFromBQ[T] function with transform callback for
  type-safe BQ iteration and row validation
- Generic CopyToTempTable helper in pkg/db/pgx.go with ON COMMIT DROP
  for transaction-scoped usage and deferred cleanup for bare connections
- Shared ticketRecencyFilter constant eliminates WHERE clause
  duplication between allBugsQuery and ticketCTE
- Association queries guarded with INNER JOIN bugs to prevent FK
  violations from BQ timing skew

Note: triage updates no longer go through GORM hooks, so audit log
entries are not created for automated bug loader operations (description
sync, bug linking, auto-resolve). This is intentional; the updated_at
column is set explicitly in each UPDATE statement.

Also fixes pre-existing bug in load.go where errors.WithMessage wrapped
the wrong variable (err instead of bigqueryErr), silently swallowing
BigQuery client creation errors.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@mstaeble
mstaeble force-pushed the worktree-bug-loader-copy branch from 8c29579 to e4b85b9 Compare July 17, 2026 17:58
@mstaeble mstaeble changed the title [WIP] TRT-2801: Rewrite bug loader to use COPY + bulk SQL operations TRT-2801: Rewrite bug loader to use COPY + bulk SQL operations Jul 17, 2026
@openshift-ci openshift-ci Bot removed the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Jul 17, 2026
@openshift-merge-bot

Copy link
Copy Markdown
Contributor

Scheduling required tests:
/test e2e

@neisw

neisw commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

/lgtm
/hold
it's your weekend, unhold when you are ready (preferably after the weekend and you take some time afk)

@openshift-ci openshift-ci Bot added the do-not-merge/hold Indicates that a PR should not merge because someone has issued a /hold command. label Jul 17, 2026
@openshift-ci openshift-ci Bot added the lgtm Indicates that a PR is ready to be merged. label Jul 17, 2026
@openshift-ci

openshift-ci Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: mstaeble, neisw

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@openshift-ci openshift-ci Bot added the approved Indicates a PR has been approved by an approver from all required OWNERS files. label Jul 17, 2026
@mstaeble

Copy link
Copy Markdown
Contributor Author

/hold cancel

@openshift-ci openshift-ci Bot removed the do-not-merge/hold Indicates that a PR should not merge because someone has issued a /hold command. label Jul 18, 2026
@mstaeble

Copy link
Copy Markdown
Contributor Author

/hold

@openshift-ci openshift-ci Bot added the do-not-merge/hold Indicates that a PR should not merge because someone has issued a /hold command. label Jul 18, 2026
@openshift-ci

openshift-ci Bot commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

@mstaeble: all tests passed!

Full PR test history. Your PR dashboard.

Details

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. I understand the commands that are listed here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

approved Indicates a PR has been approved by an approver from all required OWNERS files. do-not-merge/hold Indicates that a PR should not merge because someone has issued a /hold command. jira/valid-reference Indicates that this PR references a valid Jira ticket of any type. lgtm Indicates that a PR is ready to be merged. ready-for-human-review Indicates a PR has been reviewed by automated tools and is ready for human review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants