-
Notifications
You must be signed in to change notification settings - Fork 364
fix(worker): reindex repos with missing zoekt shards #1621
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
b66177c
c2af534
5acdcbd
2c5e1fb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,13 +3,14 @@ import { | |
| createLogger, | ||
| getRepoIdFromPath, | ||
| getRepoPath, | ||
| JOB_PRIORITIES, | ||
| REPO_CLEANUP_QUEUE, | ||
| } from "@sourcebot/shared"; | ||
| import { existsSync } from "fs"; | ||
| import { readdir, rm } from "fs/promises"; | ||
| import { INDEX_CACHE_DIR, REPOS_CACHE_DIR } from "./constants.js"; | ||
| import { REPOSITORY_EXECUTION_LOCK } from "./repoLock.js"; | ||
| import type { Settings, Workload } from "./types.js"; | ||
| import type { JobManager, Settings, Workload } from "./types.js"; | ||
| import { getRepoIdFromShardFileName } from "./utils.js"; | ||
|
|
||
| const logger = createLogger("repo-cleanup-workload"); | ||
|
|
@@ -231,3 +232,79 @@ export const cleanupOrphanedRepoResources = async (db: PrismaClient) => { | |
| } | ||
| } | ||
| }; | ||
|
|
||
| // Handles the inverse of cleanupOrphanedRepoResources: repos the DB believes are | ||
| // indexed but whose shard files are missing from disk (e.g., INDEX_CACHE_DIR was | ||
| // wiped independently of the DB, as happens when it's placed on ephemeral storage). | ||
| // Without this, such repos would silently return empty search results until their | ||
| // next scheduled reindex, which can be a long time away. | ||
| export const reindexReposWithMissingShards = async ( | ||
| db: PrismaClient, | ||
| jobManager: JobManager, | ||
| ) => { | ||
| // A missing directory means zero shards exist, not that there's nothing to | ||
| // recover: it's the same "everything is gone" scenario this function exists | ||
| // to handle, so it must still fall through to the DB lookup below. | ||
| let entries: string[]; | ||
| if (existsSync(INDEX_CACHE_DIR)) { | ||
| entries = await readdir(INDEX_CACHE_DIR); | ||
| } else { | ||
| entries = []; | ||
| } | ||
|
|
||
| const repoIdsWithShards = new Set<number>(); | ||
| for (const entry of entries) { | ||
| // Only a real, searchable shard file counts. This excludes in-progress | ||
| // or failed .tmp artifacts, the .meta sidecar zoekt writes alongside | ||
| // each shard, and any other numeric-prefixed file that isn't actually | ||
| // an index (e.g. a stray backup file). | ||
| if (!entry.endsWith(".zoekt")) { | ||
| continue; | ||
| } | ||
| const repoId = getRepoIdFromShardFileName(entry); | ||
|
cubic-dev-ai[bot] marked this conversation as resolved.
|
||
| if (repoId !== undefined) { | ||
| repoIdsWithShards.add(repoId); | ||
| } | ||
| } | ||
|
|
||
| // Considers the same set of repos reconcileJobSchedulers keeps on a recurring | ||
| // reindex schedule: attached to a connection, or explicitly pinned via | ||
| // isAutoCleanupDisabled. Anything outside that set is owned by the cleanup | ||
| // workload above, not re-indexed. | ||
| const indexedRepos = await db.repo.findMany({ | ||
| where: { | ||
| indexedAt: { not: null }, | ||
| OR: [ | ||
| { connections: { some: {} } }, | ||
| { isAutoCleanupDisabled: true }, | ||
| ], | ||
| }, | ||
| select: { id: true, name: true }, | ||
| }); | ||
|
|
||
| const reposMissingShards = indexedRepos.filter( | ||
| (repo) => !repoIdsWithShards.has(repo.id), | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P2: A multi-shard repo is considered healthy when any single shard file remains, so a partial shard loss (one fanout file deleted while others survive) is never detected and the repo is skipped, leaving incomplete search results until the next scheduled reindex. This is one of the scenarios the PR lists as covered, but the presence-only check can't catch it. Prompt for AI agents |
||
| ); | ||
|
|
||
| // Triggered sequentially so that one repo failing to enqueue (e.g. a | ||
| // transient Redis error) doesn't stop the rest from being recovered, and | ||
| // can't take down startup: this runs before the worker installs its | ||
| // uncaught-exception handlers. | ||
| for (const repo of reposMissingShards) { | ||
| logger.warn( | ||
| `Repo ${repo.name} (id: ${repo.id}) is marked as indexed but has no shard files on disk. Re-queuing for indexing.`, | ||
| ); | ||
| try { | ||
| await jobManager.trigger( | ||
| "repo-index", | ||
| { repoId: repo.id }, | ||
| { priority: JOB_PRIORITIES.SCHEDULED }, | ||
| ); | ||
| } catch (error) { | ||
| logger.error( | ||
| `Failed to re-queue repo ${repo.name} (id: ${repo.id}) for indexing:`, | ||
| error, | ||
| ); | ||
| } | ||
| } | ||
| }; | ||
Uh oh!
There was an error while loading. Please reload this page.