Skip to content

fix: test concurrent cache-miss#4340

Draft
ulemons wants to merge 6 commits into
mainfrom
fix/debuging-query-members
Draft

fix: test concurrent cache-miss#4340
ulemons wants to merge 6 commits into
mainfrom
fix/debuging-query-members

Conversation

@ulemons

@ulemons ulemons commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Summary

Changes

Type of change

  • Bug fix
  • New feature
  • Refactor / cleanup
  • Performance improvement
  • Chore / dependency update
  • Documentation

JIRA ticket

Copilot AI review requested due to automatic review settings July 14, 2026 12:37
@ulemons ulemons self-assigned this Jul 14, 2026
@ulemons ulemons added the Bug Created by Linear-GitHub Sync label Jul 14, 2026
Signed-off-by: Umberto Sgueglia <usgueglia@contractor.linuxfoundation.org>

Copilot AI left a comment

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.

Pull request overview

This PR introduces a Redis-based “single-flight” helper and integrates it into the members advanced query cache-miss/refresh paths to prevent concurrent requests from stampeding the database on cold keys.

Changes:

  • Added withSingleFlight helper in @crowd/redis to coordinate concurrent callers behind a lock while polling cache.
  • Updated queryMembersAdvanced to single-flight cache misses and align background refresh locking with the same primitive.
  • Added a shared cap constant to prevent the activityCount top‑N optimization from silently truncating oversized pages.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
services/libs/redis/src/singleFlight.ts New single-flight helper built on existing Redis mutex primitives.
services/libs/redis/src/index.ts Exports the new single-flight helper from the redis lib barrel.
services/libs/data-access-layer/src/members/queryCache.ts Removes the previous refresh-lock helpers from MemberQueryCache.
services/libs/data-access-layer/src/members/queryBuilder.ts Adds a shared top‑N cap constant and guards optimized path against truncation.
services/libs/data-access-layer/src/members/base.ts Uses single-flight on members advanced cache misses and switches background refresh locking to acquireLock/releaseLock.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 688 to 692
} catch (error) {
log.warn({ cacheKey, err: error }, `Members advanced ${label} background refresh failed`)
} finally {
await cache.releaseRefreshLock(cacheKey)
await releaseLock(redis, cacheKey, token)
}
Comment on lines +72 to +76
} finally {
if (holdsLock) {
await releaseLock(client, lockKey, token)
}
}
Comment on lines +33 to +36
const token = generateUUIDv4()
const deadline = Date.now() + waitTimeoutSeconds * 1000
let holdsLock = false

Comment on lines 27 to 31
this.lockCache = new RedisCache('members-refresh-lock', redis, log)
}

// Returns true if lock was acquired (no other refresh in progress for this key).
// Uses a cryptographically random token to distinguish "we set it" from "already existed".
// TTL ensures the lock auto-expires if the refresh crashes without releasing it.
async tryAcquireRefreshLock(cacheKey: string, ttlSeconds = 90): Promise<boolean> {
try {
const token = randomBytes(16).toString('hex')
const stored = await this.lockCache.setIfNotExistsOrGet(cacheKey, token, ttlSeconds)
return stored === token
} catch {
return true // fail open: if Redis is down, let the refresh proceed
}
}

async releaseRefreshLock(cacheKey: string): Promise<void> {
try {
await this.lockCache.delete(cacheKey)
} catch {
// best effort
}
}

buildCacheKey(params: {
fields?: string[]
Copilot AI review requested due to automatic review settings July 14, 2026 12:42

Copilot AI left a comment

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.

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.

Comment on lines +54 to +55
if (Date.now() >= deadline) {
break
return await compute()
} finally {
if (holdsLock) {
await releaseLock(client, lockKey, token)
log.warn({ cacheKey, err: error }, `Members advanced ${label} background refresh failed`)
} finally {
await cache.releaseRefreshLock(cacheKey)
await releaseLock(redis, cacheKey, token)
Signed-off-by: Yeganathan S <63534555+skwowet@users.noreply.github.com>
Copilot AI review requested due to automatic review settings July 14, 2026 15:53

Copilot AI left a comment

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.

Pull request overview

Copilot reviewed 16 out of 16 changed files in this pull request and generated 7 comments.

Comment on lines +466 to +470
segmentsSearchQuery += `AND EXISTS (
SELECT 1 FROM segments sp
WHERE sp."grandparentSlug" = f.slug
AND sp.id IN (:adminSegments)
)`
return await compute()
} finally {
if (holdsLock) {
await releaseLock(client, lockKey, token)
log.warn({ cacheKey, err: error }, `Members advanced ${label} background refresh failed`)
} finally {
await cache.releaseRefreshLock(cacheKey)
await releaseLock(redis, cacheKey, token)

if (!fromList?.projects?.length) {
try {
selectedProjectGroup.value = await segmentService.getSegmentById(newVal) as ProjectGroup;
Comment on lines +305 to +309
try {
const projectGroup = await resolveProjectGroupForSelection(
projectGroupOrId,
this.projectGroups.list,
);
Comment on lines 493 to +495
s.*,
COUNT(*) OVER () AS "totalCount",
JSONB_AGG(JSONB_BUILD_OBJECT(
'id', f.project_id,
'name', f.project_name,
'status', f.project_status,
'slug', f.project_slug,
'subprojects', f.subprojects
)) AS projects
f.project_count AS "projectCount"
'status', sp.status,
'slug', sp.slug
)) AS subprojects
COUNT(DISTINCT p.id)::int AS project_count
Signed-off-by: Yeganathan S <63534555+skwowet@users.noreply.github.com>
Copilot AI review requested due to automatic review settings July 14, 2026 16:30
…n load

- Fix router guard forEach async bug: replace forEach with for...of so
  auth middleware actually awaits before segment lookup runs, eliminating
  the pre-token /segment/{id} request
- Replace resolveProjectGroupForSelection (findSegment call) with flat
  /segment/subproject/query filtered by grandparentSlug; store result in
  selectedProjectGroupSubprojects state
- activity-timeline and lf-banners now read from selectedProjectGroupSubprojects
  instead of walking projectGroup.projects[].subprojects[]
- utils/segments: getSegmentsFromProjectGroup reads subproject IDs from store
- Remove duplicate fetchActivityTypes/fetchActivityChannels from App.vue
  tenant watcher; menu.vue watcher already handles these correctly once a
  project group is selected

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Joana Maia <jmaia@contractor.linuxfoundation.org>

Copilot AI left a comment

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.

Pull request overview

Copilot reviewed 21 out of 21 changed files in this pull request and generated 5 comments.

Comment on lines +33 to +35
const token = generateUUIDv4()
const deadline = Date.now() + waitTimeoutSeconds * 1000
let holdsLock = false
Comment on lines +72 to +76
} finally {
if (holdsLock) {
await releaseLock(client, lockKey, token)
}
}
Comment on lines 689 to 692
log.warn({ cacheKey, err: error }, `Members advanced ${label} background refresh failed`)
} finally {
await cache.releaseRefreshLock(cacheKey)
await releaseLock(redis, cacheKey, token)
}
Comment on lines +466 to +470
segmentsSearchQuery += `AND EXISTS (
SELECT 1 FROM segments sp
WHERE sp."grandparentSlug" = f.slug
AND sp.id IN (:adminSegments)
)`
@@ -27,27 +27,6 @@ export class MemberQueryCache {
this.lockCache = new RedisCache('members-refresh-lock', redis, log)
Copilot AI review requested due to automatic review settings July 14, 2026 16:36

Copilot AI left a comment

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.

Pull request overview

Copilot reviewed 21 out of 21 changed files in this pull request and generated 8 comments.

Comment on lines +468 to +469
WHERE sp."grandparentSlug" = f.slug
AND sp.id IN (:adminSegments)
Comment on lines +73 to +75
if (holdsLock) {
await releaseLock(client, lockKey, token)
}
log.warn({ cacheKey, err: error }, `Members advanced ${label} background refresh failed`)
} finally {
await cache.releaseRefreshLock(cacheKey)
await releaseLock(redis, cacheKey, token)
Comment on lines +25 to +27
if (!selectedProjectGroupSubprojects.value.length) {
return [projectGroup.id];
}
Comment on lines 319 to +320
this.selectedProjectGroup = projectGroup;
this.fetchSubprojectsForProjectGroup(projectGroup.slug);
Comment on lines +285 to +287
this.selectedProjectGroupSubprojects = response.rows || [];
} catch (e) {
this.selectedProjectGroupSubprojects = [];

if (!fromList?.projects?.length) {
try {
selectedProjectGroup.value = await segmentService.getSegmentById(newVal) as ProjectGroup;
Comment on lines 492 to +495
SELECT
s.*,
COUNT(*) OVER () AS "totalCount",
JSONB_AGG(JSONB_BUILD_OBJECT(
'id', f.project_id,
'name', f.project_name,
'status', f.project_status,
'slug', f.project_slug,
'subprojects', f.subprojects
)) AS projects
f.project_count AS "projectCount"
Full /segment/subproject/query returns integrations, mapped-repo flags,
and all segment fields per subproject — 222 MB for large project groups.
query-lite returns only id, name, url, slug, description which is all
the frontend needs for segment filtering and display.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Joana Maia <jmaia@contractor.linuxfoundation.org>
Copilot AI review requested due to automatic review settings July 14, 2026 16:48

Copilot AI left a comment

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.

Pull request overview

Copilot reviewed 21 out of 21 changed files in this pull request and generated 4 comments.

icon: () => 'layer-group',
});

const displayCount = computed(() => props.projectCount ?? props.projects?.length ?? 0);
Comment on lines +72 to +76
} finally {
if (holdsLock) {
await releaseLock(client, lockKey, token)
}
}
Comment on lines 688 to 692
} catch (error) {
log.warn({ cacheKey, err: error }, `Members advanced ${label} background refresh failed`)
} finally {
await cache.releaseRefreshLock(cacheKey)
await releaseLock(redis, cacheKey, token)
}
export * from './instances'
export * from './rateLimiter'
export * from './mutex'
export * from './singleFlight'
Signed-off-by: Yeganathan S <63534555+skwowet@users.noreply.github.com>
Copilot AI review requested due to automatic review settings July 14, 2026 16:58

Copilot AI left a comment

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.

Pull request overview

Copilot reviewed 21 out of 21 changed files in this pull request and generated 5 comments.

return await compute()
} finally {
if (holdsLock) {
await releaseLock(client, lockKey, token)
log.warn({ cacheKey, err: error }, `Members advanced ${label} background refresh failed`)
} finally {
await cache.releaseRefreshLock(cacheKey)
await releaseLock(redis, cacheKey, token)
}

this.selectedProjectGroup = projectGroup;
this.fetchSubprojectsForProjectGroup(projectGroup.slug);
Comment on lines +53 to +54
projects?: Project[];
projectCount?: number;
Comment on lines +277 to 280
const subprojects = computed(() => selectedProjectGroupSubprojects.value.reduce((acc, sp) => {
acc[sp.id] = { id: sp.id, name: sp.name };
return acc;
}, {}));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Bug Created by Linear-GitHub Sync

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants