Resource Protection
TimeTiles combines quotas for resource allocation with rate limits for request frequency. They protect different boundaries; not every operation needs both.
Two-Layer Protection
| Aspect | Quotas | Rate limits |
|---|---|---|
| Purpose | Limit resource consumption and daily usage | Limit bursts and sustained request frequency |
| Scope | Authenticated user | Identifier chosen by the caller, such as user ID or IP |
| Storage | PostgreSQL | In-memory Map or PostgreSQL |
| Windows | UTC calendar days or current resource totals | Fixed-duration windows starting with the first request |
| Examples | Daily uploads, total events, active schedules | Upload burst, hourly API requests |
Why Both Exist
A user can stay within a daily upload quota while sending requests too quickly. Conversely, requests spread across the day can avoid a burst limit while exhausting storage or processing capacity. Rate limits constrain request frequency; quotas constrain the associated resource use.
These application checks do not replace network-level denial-of-service protection.
Execution Flow
Where both protections apply, reject excessive request frequency before performing quota-controlled work:
Request → Rate-limit check → Quota check/reservation → Process requestCustom routes opt into rate limiting through apiRoute(). Quota enforcement belongs at the operation that consumes the resource, including collection hooks and background import processing. Use QuotaService.checkAndIncrementUsage() when usage must be reserved atomically; a separate check followed by an increment can race with concurrent requests.
Rate-limit and quota errors handled by the shared API error handler return HTTP 429. Background jobs have their own failure and review handling; they do not return HTTP responses.
When Each Triggers
Limits are cumulative: passing a burst window does not bypass an hourly or daily window. For example, the default Regular upload limits allow one request per five seconds but only five per hour. Uploading every six seconds therefore reaches the hourly rate limit before the ten-upload daily quota.
See Usage Limits for the complete defaults rather than treating this example as a separate configuration source.
Trust Level Scaling
Trust levels provide default quotas and upload/API rate limits. Per-user quota overrides take precedence over quota defaults.
The Unlimited (5) level removes many quotas, but still has a file-size quota and rate limits. Fixed technical ceilings, including the import wizard’s 50 MB preview limit, remain independent of trust level.
Daily Resets
Daily quota windows use midnight UTC. The quota-reset maintenance task resets daily counters, and quota accounting also resets stale daily counters on access so enforcement does not depend on the task running at exactly midnight. Non-daily resource totals, such as events and active schedules, are not cleared by this reset.
Rate-limit windows are fixed, not sliding: the first request starts a window, subsequent requests retain its expiry, and the first request after expiry starts a new window. Expiry is checked during requests; cleanup removes expired entries but is not needed to unlock an expired window.
Storage Differences
Set RATE_LIMIT_BACKEND to select rate-limit storage:
memory(default): process-local counters, lost on restart. Suitable for local development and single-process deployments.pg: shared PostgreSQL counters updated by an atomic UPSERT. Use this for multiple workers or replicas so each process does not grant its own independent allowance.
In production, the service rejects memory when WEB_CONCURRENCY or CLUSTER_WORKERS indicates more than one worker. This check cannot discover independently deployed replicas; their configuration must still select pg.
Quotas always use PostgreSQL, regardless of the rate-limit backend.
Implementation
lib/services/quota-service.ts: effective quotas, usage accounting and atomic reservations.lib/services/rate-limit-service.ts: identifiers, multiple rate-limit windows and trust-level limits.lib/services/rate-limit/{memory-store,pg-store}.ts: counter storage and expiry.lib/jobs/handlers/quota-reset-job/: daily quota maintenance.lib/jobs/handlers/rate-limit-cleanup-job.ts: expired PostgreSQL rate-limit counter cleanup.
The memory backend cleans expired entries within the running process. The rate-limit-cleanup maintenance task handles PostgreSQL entries. The separate cache-cleanup task manages application caches, not quota or rate-limit resets.
Related Documentation
- Usage Limits — Quotas, rate limits and preview ceilings
- Configuration — Environment variables and YAML settings