Files
gitlore/src/ingestion/mod.rs
teernisse 5fd1ce6905 perf(ingestion): implement prefetch pattern for issue discussions
Issue discussion sync was ~10x slower than MR discussion sync because it
used a fully sequential pattern: fetch one issue's discussions, write to
DB, repeat. MR sync already used a prefetch pattern with concurrent HTTP
requests followed by sequential DB writes.

This commit brings issue discussion sync to parity with MRs:

Architecture (prefetch pattern):
  1. HTTP phase: Concurrent fetches via `join_all()` with batch size
     controlled by `dependent_concurrency` config (default 8)
  2. Transform phase: Normalize discussions and notes during prefetch
  3. DB phase: Sequential writes with proper transaction boundaries

Changes:
  - gitlab/client.rs: Add `fetch_all_issue_discussions()` to mirror
    the existing MR pattern for API consistency
  - discussions.rs: Replace `ingest_issue_discussions()` with:
    * `prefetch_issue_discussions()` - async HTTP fetch + transform
    * `write_prefetched_issue_discussions()` - sync DB writes
    * New structs: `PrefetchedIssueDiscussions`, `PrefetchedDiscussion`
  - orchestrator.rs: Update `sync_discussions_sequential()` to use
    concurrent prefetch for each batch instead of sequential calls
  - surgical.rs: Update single-issue surgical sync to use new functions
  - mod.rs: Update public exports

Expected improvement: 5-10x speedup on issue discussion sync (from ~50s
to ~5-10s for large projects) due to concurrent HTTP round-trips.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-03-02 14:14:03 -05:00

41 lines
1.4 KiB
Rust

pub mod dirty_tracker;
pub mod discussion_queue;
pub mod discussions;
pub mod issues;
pub mod merge_requests;
pub mod mr_diffs;
pub mod mr_discussions;
pub mod orchestrator;
pub(crate) mod surgical;
pub use discussions::{
IngestDiscussionsResult, prefetch_issue_discussions, write_prefetched_issue_discussions,
};
pub use issues::{IngestIssuesResult, IssueForDiscussionSync, ingest_issues};
pub use merge_requests::{
IngestMergeRequestsResult, MrForDiscussionSync, get_mrs_needing_discussion_sync,
ingest_merge_requests,
};
pub use mr_discussions::{IngestMrDiscussionsResult, ingest_mr_discussions};
/// Format a set of named counters as a compact human-readable summary,
/// filtering out zero values and joining with middle-dot separators.
/// Returns `"nothing to update"` when all values are zero.
pub(crate) fn nonzero_summary(pairs: &[(&str, usize)]) -> String {
let parts: Vec<String> = pairs
.iter()
.filter(|(_, v)| *v > 0)
.map(|(k, v)| format!("{v} {k}"))
.collect();
if parts.is_empty() {
"nothing to update".to_string()
} else {
parts.join(" \u{b7} ")
}
}
pub use orchestrator::{
DrainResult, IngestMrProjectResult, IngestProjectResult, ProgressCallback, ProgressEvent,
ingest_project_issues, ingest_project_issues_with_progress, ingest_project_merge_requests,
ingest_project_merge_requests_with_progress,
};