rebuildGraph Advisory Lock

RebuildGraph() in api/internal/wiki/wiki.go uses a PostgreSQL advisory lock to serialize concurrent rebuilds across instances.

Why

A sync.Map TTL (used for lint cooldown) only prevents re-runs within a single instance. At horizontal scale, two instances could run RebuildGraph simultaneously — both listing all pages, both writing links — producing a confused link graph if either sees a partial view.

Advisory locks are DB-scoped and survive instance restarts. pg_try_advisory_lock is non-blocking: if the lock is held, the call returns false immediately rather than waiting.

Lock key

The project UUID is converted to an int64 lock key via rebuildGraphLockKey():

func rebuildGraphLockKey(projectID string) int64 {
    raw := strings.ReplaceAll(projectID, "-", "")
    b, _ := hex.DecodeString(raw[:16]) // first 8 bytes of the UUID
    return int64(binary.BigEndian.Uint64(b))
}

Behavior

  • Lock acquired → rebuild proceeds normally
  • Lock not acquired (another instance rebuilding) → return (0, nil) — silent skip, not an error
  • Lock is always released via defer after rebuild completes

Files

  • api/internal/wiki/wiki.goRebuildGraph(), rebuildGraphLockKey()
  • api/internal/storage/postgres.goTryAdvisoryLock(), ReleaseAdvisoryLock()