Skip to content

ratelimit: switch to fixed window#6

Merged
terwey merged 2 commits intomainfrom
claude/investigate-tier-rate-limiter-011CV1uWDoijEV76uvWbK7ng
Nov 11, 2025
Merged

ratelimit: switch to fixed window#6
terwey merged 2 commits intomainfrom
claude/investigate-tier-rate-limiter-011CV1uWDoijEV76uvWbK7ng

Conversation

@terwey
Copy link
Copy Markdown
Collaborator

@terwey terwey commented Nov 11, 2025

Summary

Fixed rate limiter implementation to prevent 429 errors by replacing token bucket algorithm with fixed-window rate limiting that matches 3commas API behavior.

Problem

The SDK was getting frequent 429 (Too Many Requests) errors despite having client-side rate limiting. Two issues were identified:

  1. Token bucket vs Fixed-window mismatch: The previous implementation used golang.org/x/time/rate (token bucket algorithm) which continuously refills tokens. This doesn't match 3commas API's fixed-window rate limiting that resets at clock boundaries (e.g., 12:30:00, 12:31:00).

    With token bucket and PlanStarter (5/min), you could make 5 requests immediately, then earn additional tokens through gradual refills, resulting in ~10 requests within various 60-second sliding windows - exceeding the API's strict 5/minute limit.

  2. 429 handler only blocked routes, not tier limiter: When a 429 was received and matched a specific route, only that route was blocked. The tier limiter (account-wide subscription limit) wasn't blocked, causing cascading 429 errors across other endpoints.

Solution

  • Implemented custom fixedWindowLimiter that aligns to clock boundaries using time.Truncate()
  • Windows reset at fixed times matching 3commas API behavior (minute boundaries for tier limits, 10-second boundaries for smart_trades)
  • Allows bursts within the same window (e.g., all 5 requests in 2 seconds is acceptable for Starter tier)
  • Fixed 429 handler to always block the tier limiter (not just individual routes)
  • Renamed "global" to "tier" throughout for clarity
  • Removed golang.org/x/time/rate dependency
  • Updated README documentation to reflect fixed-window behavior

Testing

All existing tests pass. Rate limiting tests confirm proper enforcement of tier limits with window-aligned behavior.

The previous implementation used golang.org/x/time/rate which implements
a token bucket algorithm that continuously refills tokens. This caused
429 errors because:

1. Token bucket allows exceeding fixed-window limits through gradual refills
   Example: With 5/min limit, burst of 5 + gradual refills = ~10 req/min
   in sliding windows, exceeding 3commas' strict 5/min fixed-window limit

2. 429 handler only blocked the matched route, not the tier limiter
   When tier limit was exceeded, only specific routes were blocked,
   allowing 429s to cascade across other endpoints

Changes:
- Implemented custom fixedWindowLimiter that aligns to clock boundaries
- Windows reset at fixed times (e.g., 12:30:00, 12:31:00) matching 3commas API
- Allows bursts within same window (e.g., 5 requests in 2 seconds is fine)
- Renamed "global" to "tier" for clarity (represents subscription plan limit)
- Fixed 429 handler to always block tier limiter, not just routes
- Removed golang.org/x/time/rate dependency

This matches 3commas' actual rate limiting behavior where limits reset
at the start of each time window, rather than continuous token refills.
Updated rate limiting documentation to reflect the new implementation:
- Changed from "token bucket algorithm" to "fixed-window rate limiting"
- Added explanation of clock-aligned windows
- Clarified that bursts within same window are allowed
- Better explains actual behavior matching 3Commas API
@terwey terwey marked this pull request as ready for review November 11, 2025 11:49
@terwey terwey merged commit f420d6d into main Nov 11, 2025
1 check passed
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.

2 participants