Skip to content

✨ Support time-based dynamic rate limits with scheduling #222

@sodre

Description

@sodre

Problem or Use Case

Currently, rate limits are static - once defined, they remain constant until manually updated via set_limits(). Real-world scenarios often require limits that vary based on:

  1. Time of day - Lower limits during peak hours to protect backend systems
  2. Day of week - Different limits for weekdays vs weekends
  3. Calendar events - Special limits during known high-traffic periods (Black Friday, product launches)
  4. Maintenance windows - Reduced limits during deployments

Users must currently implement their own scheduling logic and call set_limits() at appropriate times, which is error-prone and requires external orchestration.

Proposed Solution

Add schedule support to the Limit model that allows defining time-based variations:

from zae_limiter import Limit, Schedule, ScheduleOverride

# Option 1: Schedule as separate object
schedule = Schedule(
    default=Limit.per_minute("rpm", capacity=1000),
    overrides=[
        ScheduleOverride(
            cron="0 9-17 * * MON-FRI",  # 9 AM - 5 PM weekdays
            limit=Limit.per_minute("rpm", capacity=500),
        ),
        ScheduleOverride(
            cron="0 0-6 * * *",  # Midnight to 6 AM daily
            limit=Limit.per_minute("rpm", capacity=2000),  # Higher off-peak
        ),
    ],
)

# Option 2: Inline on Limit (simpler API)
Limit.per_minute(
    "rpm",
    capacity=1000,
    schedules=[
        {"cron": "0 9-17 * * MON-FRI", "capacity": 500},  # Peak hours
    ],
)

Key Design Decisions

  1. Cron syntax - Use standard 5-field cron for familiarity
  2. Resolution at acquire-time - Evaluate schedule when acquire() is called
  3. Override precedence - First matching schedule wins, or most specific
  4. Immutability preserved - Limit remains frozen; Schedule wraps multiple Limit instances

Schema Impact

Option A: No schema change - Resolve schedules client-side

  • Schedules stored as JSON in limit config
  • RateLimiter evaluates schedule on acquire()
  • ✅ No migration needed
  • ❌ All clients must upgrade to honor schedules

Option B: Server-side resolution - Add Lambda schedule evaluator

  • Aggregator Lambda evaluates schedules
  • Stores "effective limits" that change over time
  • ✅ Works for all client versions
  • ❌ More complex, higher Lambda invocation cost

Success Criteria

  • Define schedules at System/Resource/Entity levels
  • Schedules resolve correctly at acquire-time
  • Audit logs capture which schedule was active
  • CLI can display current effective limits with schedule info
  • Documentation covers common scheduling patterns

Alternatives Considered

  1. External scheduler - Use CloudWatch Events or Step Functions to call set_limits() on schedule. Rejected: Adds operational complexity, no atomicity.

  2. Database TTL-based - Store future limit values with activation timestamps. Rejected: DynamoDB TTL is for deletion, not activation.

  3. Webhook-based - External service pushes limits. This is tracked as a separate feature for event-driven updates.

Open Questions

The following should be discussed in comments before implementation:

Timezone Handling

  • UTC-only (simpler)?
  • Per-schedule timezone support?
  • Entity-level timezone configuration?

Schedule Inheritance

  • Should entity schedules override or merge with resource/system schedules?
  • Example: System has peak-hours schedule, entity has weekend schedule - how do they combine?

Related Work

  • Centralized Configuration (ADR-001) provides the hierarchy for schedule inheritance
  • Usage snapshots could correlate with schedule changes for analysis
  • Part of the Dynamic Rate Limits initiative (see related issues)

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions