You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Fix multi-instance state coordination with file locking and expiration persistence [minor] (#62)
When multiple Traefik routers use this plugin, each creates a separate instance that competes for the same state file. This caused verified IPs to lose their TTLs and get re-challenged across instances.
Changes:
- Add expiration timestamps to State struct for proper TTL serialization
- Implement file locking to prevent concurrent write conflicts
- Add state reconciliation to merge in-memory and file-based state
- Keep periodic full state saves (10 min) as backup for rate/bot caches
- Verified IPs now maintain their TTLs across plugin instances, preventing unnecessary re-challenges when requests hit different routers.
-`verifiedCache`: Stores IPs that have passed challenges (24h default TTL)
21
-
-`botCache`: Caches reverse DNS lookups for bot verification
21
+
-`botCache`: Caches reverse DNS lookups for bot verification (1h TTL)
22
+
-**Why go-cache instead of sync.Map?** The plugin requires automatic TTL-based expiration for all caches. `sync.Map` has no built-in expiration mechanism, requiring manual cleanup goroutines. `go-cache` provides thread-safe maps with automatic expiration and cleanup.
22
23
23
24
### Request Flow Decision Tree
24
25
@@ -120,9 +121,18 @@ Regex is significantly slower (~41ns vs ~3.4ns per operation) - see README bench
120
121
### State Persistence
121
122
122
123
When `persistentStateFile` is configured:
123
-
- State saves every 1 minute to JSON file (`saveState()` at `main.go:695-727`)
124
-
- On startup, loads previous state from file (`loadState()` at `main.go:729-756`)
124
+
- State saves every 10 seconds (with 0-2s random jitter) to JSON file (`saveState()` at `main.go:716-746`)
-**Important**: Each middleware instance runs its own save goroutine. If multiple instances share the same `persistentStateFile`, they will write more frequently (e.g., 2 instances = writes every ~5 seconds)
129
+
-**State Reconciliation**: When `enableStateReconciliation: "true"`, each save performs a read-modify-write cycle to merge state from other instances. This adds I/O overhead but prevents data loss in multi-instance deployments (see `internal/state/state.go:86-100`)
130
+
131
+
**Why not Redis?** Traefik plugins are loaded via Yaegi (a Go interpreter), which has significant limitations:
132
+
- Yaegi cannot interpret Go packages that use `unsafe`, cgo, or complex reflection patterns
133
+
- Popular Redis clients like `go-redis/redis` are incompatible with Yaegi
134
+
135
+
**Current solution**: File-based persistence with reconciliation avoids these issues. Local caches remain fast (no network overhead), state saves are batched (every 10s), and reconciliation handles conflicts without complex coordination. The tradeoff is accepting slightly stale data across instances (max 10s delay) rather than the complexity and performance cost of real-time Redis synchronization.
Copy file name to clipboardExpand all lines: README.md
+1Lines changed: 1 addition & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -119,6 +119,7 @@ services:
119
119
| `enableStatsPage` | `string` | `"false"` | Allows `exemptIps` to access `/captcha-protect/stats` to monitor the rate limiter. |
120
120
| `logLevel` | `string` | `"INFO"` | Log level for the middleware. Options: `ERROR`, `WARNING`, `INFO`, or `DEBUG`. |
121
121
| `persistentStateFile` | `string` | `""` | File path to persist rate limiter state across Traefik restarts. In Docker, mount this file from the host. |
122
+
| `enableStateReconciliation` | `string` | `"false"` | When `"true"`, reads and merges disk state before each save to prevent multiple instances from overwriting data. Adds extra I/O overhead. Only enable for multi-instance deployments sharing state. |
0 commit comments