Skip to content

imp(): move rate limiting to grouper#576

Open
e11sy wants to merge 6 commits into
masterfrom
imp/mv-rate-limits-to-grouper
Open

imp(): move rate limiting to grouper#576
e11sy wants to merge 6 commits into
masterfrom
imp/mv-rate-limits-to-grouper

Conversation

@e11sy

@e11sy e11sy commented Jul 7, 2026

Copy link
Copy Markdown
Member

This PR is part of work related to rate limiting issue, se collector part

Problem

  • Rate limiting logic lived in collector and was affected by sentry logs, metrics etc, that were filtered in sentry worker

it've been leading to rate limiting inconsistency
counter of events filled too fast, grouper managed to save 5-10 repetitions, however rate limits were configured to 1000+ per hour

Solution

Move collector rate-limiting logic to the grouper, where we can count actual amount of events that are being saved to db

Steps

  1. Reproduce exactly same logic of projects cache, as we used in collector
    this is used for db calls amount reducing (we fetch projects once in several minutes instead of fetching on each request)

  2. Write redis helper lua script that would update counter on each event (increment or clear on time window expiration)

  3. Cover with tests

@codex-assistant

Copy link
Copy Markdown

Please add a PR description 🙂

Respect the reviewers — a description helps others understand the changes and review them faster. Keep it short, clear, and to the point. It also serves as documentation for future reference.

The PR was moved to Draft until a description is added.

@codex-assistant

Copy link
Copy Markdown

Please add a PR description 🙂

Respect the reviewers — a description helps others understand the changes and review them faster. Keep it short, clear, and to the point. It also serves as documentation for future reference.

The PR was moved to Draft until a description is added.

@codex-assistant

Copy link
Copy Markdown

Please add a PR description 🙂

Respect the reviewers — a description helps others understand the changes and review them faster. Keep it short, clear, and to the point. It also serves as documentation for future reference.

The PR was moved to Draft until a description is added.

@codex-assistant

Copy link
Copy Markdown

Please add a PR description 🙂

Respect the reviewers — a description helps others understand the changes and review them faster. Keep it short, clear, and to the point. It also serves as documentation for future reference.

The PR was moved to Draft until a description is added.

@e11sy e11sy marked this pull request as ready for review July 13, 2026 17:58
@codex-assistant

Copy link
Copy Markdown

Thanks for adding a description — the PR is now marked as Ready for Review.

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 moves per-project rate limiting enforcement into the grouper worker so limits are applied to events that actually get persisted, rather than being skewed by filtering/logging upstream.

Changes:

  • Added per-project rate limit enforcement in GrouperWorker using Redis-backed atomic counters.
  • Introduced an in-memory Mongo-backed cache (ProjectLimitsCache) to resolve effective limits (project → workspace → plan) with periodic refresh.
  • Added metrics + tests for rate limiting behavior, and documented new env vars.

Reviewed changes

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

Show a summary per file
File Description
workers/limiter/src/index.ts Adjusts quota-notification threshold lookup logic in limiter.
workers/grouper/src/index.ts Enforces rate limiting before DB writes; refreshes cached limits periodically; avoids re-entering handle() on duplicate retry.
workers/grouper/src/redisHelper.ts Adds Lua-script-based atomic rate limit counter update in Redis.
workers/grouper/src/projectLimitsCache.ts New Mongo-backed cache resolving effective per-project rate limits.
workers/grouper/src/metrics/grouperMetrics.ts Adds Prometheus counter for rate-limited (dropped) events.
workers/grouper/tests/index.test.ts Adds integration-style test ensuring rate-limited events are dropped (not saved).
workers/grouper/tests/redisHelper.rateLimit.test.ts New unit tests for RedisHelper rate-limit Lua logic.
workers/grouper/README.md Documents rate limiting behavior and new environment variables + test command.
workers/grouper/.env.sample Adds sample env vars for cache refresh period and Redis hash key.
.env.test Updates Redis URL test configuration note (set by Jest redis container setup).

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

Comment on lines +116 to +121
if (eventsLimit === 0) {
return true;
}

const now = Math.floor(Date.now() / MS_IN_SEC);

Comment on lines +255 to +259
} catch (error) {
this.logger.error(`Failed to update rate limit for project ${projectId}`, error);

return false;
}
Comment on lines +166 to +172
this.projectLimitsRefreshInterval = setInterval(() => {
/* eslint-disable-next-line no-void */
void this.projectLimitsCache.refresh()
.catch((error) => {
this.logger.error('Failed to refresh project limits cache', error);
});
}, limitsUpdatePeriodSeconds * MS_IN_SEC);
Comment on lines +75 to +77
const projects = await db.collection<ProjectDocument>('projects')
.find({}, { maxTimeMS: CONTEXT_TIMEOUT_MS })
.toArray();
Comment on lines +129 to +141
const workspaces = await db.collection<WorkspaceDocument>('workspaces')
.aggregate<WorkspaceDocument>([
{
$lookup: {
from: 'plans',
localField: 'tariffPlanId',
foreignField: '_id',
as: 'plan',
},
},
{ $unwind: '$plan' },
], { maxTimeMS: CONTEXT_TIMEOUT_MS })
.toArray();
DEFAULT_PROJECTS_LIMITS_UPDATE_PERIOD_SECONDS,
);

this.projectLimitsRefreshInterval = setInterval(() => {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we already have 2 cache mechanics:

  • lib/cache/controller.ts
  • lim/memoize/index.ts

Let's use one of them and do not implement another one.

*
* @param projectId - project id
*/
private async checkRateLimit(projectId: string): Promise<boolean> {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’m not fond of the constant addition of new methods to the grouper index file. Let’s adhere to the principle of separation of concerns and create a separate ReteLimitter file that will encapsulate all the necessary logic.

eventsPeriod: number;
}

const CONTEXT_TIMEOUT_MS = 5000;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

doc is missing

Comment on lines +73 to +75
const workspaceMap = await this.loadWorkspacesWithPlans(db);

const projects = await db.collection<ProjectDocument>('projects')

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure we need to load all of thousands workspaces and projects and keep them in memory.
You can load plan and rate-limit settings from a particular project on demand and use "memoize" or "cacheController" for caching

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

moreover, we already query project data by getProjectPatterns. We can reorganize it someway to make 1 request that will return both "eventGroupingPatterns" and "rateLimitSettings" fields.

}

/**
* Reload all project limits from MongoDB.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please provide more descriptive documentation. Explain the logic behind prioritizing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants