Skip to content

Fix/db storage - #111

Draft
Amazing-Stardom wants to merge 7 commits into
masterfrom
fix/db-storage
Draft

Fix/db storage#111
Amazing-Stardom wants to merge 7 commits into
masterfrom
fix/db-storage

Conversation

@Amazing-Stardom

Copy link
Copy Markdown
Contributor

Summary

Describe what changed and why.

Linked Issue

Link the agreed issue this PR fulfills.

Validation

Describe the most specific test or validation you ran.

Checklist

  • This PR fulfills an agreed issue.
  • I kept the change narrow and scoped.
  • I ran the most specific relevant validation and described it above.
  • If I changed behavior, I called that out clearly in this PR.
  • If I touched UI, I attached a GIF or video walkthrough. This is required.
  • If this change touches security, disclosure flow, credentials, storage, network behavior, or licensing/entitlement enforcement, I reviewed SECURITY.md and updated related documentation if needed.
  • I have read and accept the Contributor License Agreement.

Notes For Reviewers

Anything specific you want reviewers to focus on.

@RijulTP

RijulTP commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Offload Git Diff Storage to External Blob Store

Overview

This change moves large Git code diffs from PostgreSQL TOAST storage to external Blob Storage. It reduces database size, limits River background job retention to 30 days, and implements a dual-read fallback strategy to maintain backward compatibility.

Technical Highlights

  • docs/architecture/diff_storage_offloading.md: Outlines a 7-step data migration plan and a dual-read fallback strategy for historical reviews.
  • internal/blobstore/blobstore.go: Introduces SaveArtifact and ReadArtifact helpers with a strict 100 MB size limit and dynamic database-driven configuration.
  • internal/api/diff_review.go: Integrates fetchPreloadedChanges to read diffs from Blob Storage before falling back to PostgreSQL.
  • internal/jobqueue/jobqueue.go: Reduces River background job retention from 365 days to 30 days to prevent database bloat.
  • internal/jobqueue/review_worker.go: Persists serialized review diffs to Blob Storage and falls back to PostgreSQL on write failure.
  • internal/review_processor/reviews.go: Adds OrgID to the Review model and updates database queries to retrieve it.
  • scripts/migrate_diffs_to_blobstore.go: Provides a concurrent migration script with dry-run validation to move existing JSONB diffs to Blob Storage.

Impact

  • Functionality: Users can view historical and new reviews seamlessly while diffs are stored in external Blob Storage.
  • Risk: The migration script risks out-of-memory errors and silent failures due to unpaginated query streaming.


func (s *Server) fetchPreloadedChanges(ctx context.Context, orgID, reviewID int64, meta map[string]interface{}) ([]models.CodeDiff, error) {
// 1. Try reading from Blob Storage first (org/<org_id>/review/<review_id>/artifacts/preloaded_changes.json)
rawBlob, err := blobstore.ReadArtifact(ctx, s.db, orgID, reviewID, blobstore.ArtifactPreloadedChanges)

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.

Severity: warning

This block silently ignores all blob read errors. Please log any errors other than NotFound to prevent masking potential storage failures.

Suggestions:

  1. Check if the error is a 'not found' error using blobstore.IsNotExist(err).
  2. Log non-NotFound errors (e.g., network timeouts, expired credentials) to prevent silent failures that mask underlying storage issues.

if err != nil {
log.Printf("[WARN] Failed to marshal diffs for review %d: %v", args.ReviewID, err)
} else {
if err := blobstore.SaveArtifact(ctx, w.db, args.OrgID, args.ReviewID, blobstore.ArtifactPreloadedChanges, modelDiffsPayload); err != nil {

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.

Severity: warning

The UI polling API must read preloaded_changes from the blobstore; otherwise, the UI may break if the data is missing from the database metadata.

Suggestions:

  1. Ensure the GET/polling endpoint for reviews checks blob storage if preloaded_changes is not in DB metadata.

}); err != nil {
log.Printf("[WARN] failed to store preloaded_changes for review %d: %v", args.ReviewID, err)
}
if !savedToBlob {

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.

Severity: warning

Please set preloaded_changes to nil in metaUpdates on success to ensure that stale database values are cleared.

Suggestions:

  1. Set metaUpdates[blobstore.MetaPreloadedChanges] = nil when savedToBlob is true to clear any stale preloaded_changes from the database.

queryCtx, cancel := context.WithTimeout(ctx, 30*time.Minute)
defer cancel()

rows, err := db.QueryContext(queryCtx, query)

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.

Severity: warning

This query does not implement pagination. Loading all rows at once could lead to out-of-memory (OOM) errors when dealing with large datasets.

Suggestions:

  1. Implement keyset pagination (e.g., WHERE id > $1 LIMIT 1000) to process rows in batches.
  2. Avoid loading the entire dataset into memory at once.

ORDER BY id ASC
`

queryCtx, cancel := context.WithTimeout(ctx, 30*time.Minute)

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.

Severity: warning

There is a hardcoded 30-minute timeout on queryCtx, which will abort the migration if the streaming process takes longer than 30 minutes.

Suggestions:

  1. Remove the hardcoded timeout or make it configurable via a command-line flag.
  2. Use batching/pagination to avoid a single long-running query.

item.preloadedBytes = rawData
jobChan <- item
}
if err := rows.Err(); err != nil {

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.

Severity: critical

Although rows.Err() is logged, the error is otherwise ignored, which causes the script to exit with a status of 0 even in the event of a partial failure.

Suggestions:

  1. Track query/iteration errors in a shared variable or channel.
  2. Ensure the script exits with a non-zero status code if rows.Err() is non-nil.

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.

2 participants