Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,12 @@ if [ ! -d "$DATA_CACHE_DIR" ]; then
mkdir -p "$DATA_CACHE_DIR"
fi

# Create a directory for heap snapshots. If the backend process approaches its heap
# limit (e.g., due to a memory leak), Node will dump a snapshot here (see
# --heapsnapshot-near-heap-limit in supervisord.conf) so the cause can be diagnosed
# post-mortem instead of only seeing a generic "JavaScript heap out of memory" crash.
mkdir -p "$DATA_CACHE_DIR/heap-snapshots"

# As of v5, SOURCEBOT_ENCRYPTION_KEY must be provided explicitly via an environment variable.
# @see: https://docs.sourcebot.dev/docs/upgrade/v4-to-v5-guide
if [ -z "$SOURCEBOT_ENCRYPTION_KEY" ]; then
Expand Down
20 changes: 20 additions & 0 deletions packages/backend/src/promClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,4 +91,24 @@ export class PromClient {
register: this.registry,
});
}

/**
* Removes all time series associated with a given repo across every repo-scoped
* metric. The `repo` label is a dynamic, user-controlled value (repo name), and
* prom-client never forgets a label combination on its own once it's been
* observed. Without this cleanup, every distinct repo name ever seen over the
* process's lifetime stays resident in memory forever, even after the repo is
* deleted. Call this once a repo is permanently removed (e.g., after a CLEANUP
* job deletes it) to keep the registry's memory footprint bounded by the current
* set of repos rather than the historical set.
*/
public removeRepoMetrics(repoName: string) {
for (const type of ['index', 'cleanup']) {
this.activeRepoIndexJobs.remove({ repo: repoName, type });
this.pendingRepoIndexJobs.remove({ repo: repoName, type });
this.repoIndexJobReattemptsTotal.remove({ repo: repoName, type });
this.repoIndexJobFailTotal.remove({ repo: repoName, type });
this.repoIndexJobSuccessTotal.remove({ repo: repoName, type });
}
}
}
4 changes: 4 additions & 0 deletions packages/backend/src/repoIndexManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -614,6 +614,10 @@ export class RepoIndexManager {
where: { id: jobData.repoId },
});

// The repo no longer exists, so drop its time series from the metrics
// registry rather than retaining them for the lifetime of the process.
this.promClient.removeRepoMetrics(repo.name);

Comment thread
brendan-kellam marked this conversation as resolved.
logger.debug(`Completed cleanup job ${job.data.jobId} for repo ${repo.name} (id: ${repo.id})`);
}

Expand Down
6 changes: 5 additions & 1 deletion supervisord.conf
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,8 @@ autorestart=true
startretries=3
stdout_logfile=/dev/fd/1
stdout_logfile_maxbytes=0
redirect_stderr=true
redirect_stderr=true
; Dump up to 3 heap snapshots to a persisted directory if the process approaches
; its heap limit, so a real memory leak (as opposed to a transient spike) can be
; diagnosed from the actual retained object graph instead of guessing from logs.
environment=NODE_OPTIONS="--heapsnapshot-near-heap-limit=3 --diagnostic-dir=%(ENV_DATA_CACHE_DIR)s/heap-snapshots"
Loading