fix(server): race condition for sync (#14770)

#### PR Dependency Tree


* **PR #14770** 👈

This tree was auto-generated by
[Charcoal](https://github.com/danerwilliams/charcoal)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

## Release Notes

* **New Features**
* Implemented batch processing for calendar synchronization to improve
performance and resource utilization.
* Added distributed locking to prevent concurrent operations in
multi-instance environments.

* **Bug Fixes**
* Improved reliability by preventing duplicate synchronization attempts.

* **Tests**
  * Enhanced test coverage for batch processing and locking mechanisms.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
DarkSky
2026-04-03 03:37:09 +08:00
committed by GitHub
parent 233004f867
commit 0da32d61ae
5 changed files with 121 additions and 28 deletions
@@ -4,6 +4,7 @@ import { Injectable, Logger } from '@nestjs/common';
import { Transactional } from '@nestjs-cls/transactional';
import type { CalendarAccount, Prisma } from '@prisma/client';
import { addDays, subDays } from 'date-fns';
import { chunk } from 'lodash-es';
import {
CalendarProviderRequestError,
@@ -32,6 +33,7 @@ const SYNC_FAILURE_BACKOFF_KEY_PREFIX = 'calendar:sync:backoff:';
const SYNC_FAILURE_BACKOFF_BASE_MS = 5 * 60 * 1000;
const SYNC_FAILURE_BACKOFF_MAX_MS = 6 * 60 * 60 * 1000;
const SYNC_FAILURE_BACKOFF_TTL_SECONDS = 24 * 60 * 60;
const ACCOUNT_SYNC_BATCH_SIZE = 10;
@Injectable()
export class CalendarService {
@@ -433,11 +435,13 @@ export class CalendarService {
const subscriptions =
await this.models.calendarSubscription.listByAccountForSync(accountId);
await Promise.allSettled(
subscriptions.map(subscription =>
this.syncSubscription(subscription.id, { reason: 'polling' })
)
);
for (const batch of chunk(subscriptions, ACCOUNT_SYNC_BATCH_SIZE)) {
await Promise.allSettled(
batch.map(subscription =>
this.syncSubscription(subscription.id, { reason: 'polling' })
)
);
}
}
async listWorkspaceEvents(params: {