Repository navigation
Conversation
Adds api.server.decisions_stream.max_concurrent_requests. When set, /v1/decisions/stream requests beyond the limit wait for a free slot instead of all loading a page of decisions at once. A request that waited reads the cursor after getting its slot, so it still gets an up-to-date delta. Unset, 0 or negative keeps the current behaviour (no limit). Refs crowdsecurity#4711
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #4747 +/- ##
==========================================
+ Coverage 71.28% 71.30% +0.02%
==========================================
Files 542 542
Lines 36751 36770 +19
==========================================
+ Hits 26199 26220 +21
+ Misses 10551 10549 -2
Partials 1 1
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds a new config key:
api.server.decisions_stream.max_concurrent_requests, next to thepage_sizefrom #4733.What and why
Every in-flight
/v1/decisions/streamrequest holds a page of decisions in memory. With ~1.2k bouncers pulling a large delta at once, that's what put our LAPI into an OOM loop (#4711). #4733 caps the memory per request; this caps how many requests do it at once.When set, stream requests beyond the limit wait for a free slot, held until the response is fully written. A request that waited reads the cursor after getting its slot, so it still gets an up-to-date delta. A bouncer that disconnects while queued leaves without taking a slot (503, logged at debug), and since nothing was sent its cursor doesn't move. Unset or 0 keeps the current behaviour (no limit); negative warns and also means no limit. Only GET
/v1/decisions/streamis limited.Things to know when sizing it:
STREAM_REQUEST_TIMEOUT(15s by default), so keep queue waits well under the shortest stream timeout in your fleet.max_concurrent_requests× the memory of onepage_sizepage. This only bounds/v1/decisions/stream; live-modeGET /v1/decisionsis unpaged and unlimited, and out of scope here.Known limitation - your call on how to handle it. A slot is held until the response is fully written, with no time limit. A bouncer that keeps its connection open but stops reading (a hung process, a suspended VM, a link that silently drops traffic) keeps its slot, and
max_concurrent_requestsof them would stall delivery to every other bouncer. This only happens when the limit is on.I tried a per-write deadline (
http.ResponseController.SetWriteDeadline, renewed as data goes out). It works over HTTP/1.1, but doing it properly needs two more things. Over HTTP/2, a stalled connection only fails with the server-wideHTTP2Config.WriteByteTimeout. And the deadline has to be paused around the page queries and the cursor update, or a slow query cuts off a healthy bouncer and can advance its cursor on a truncated response. That's a bigger change to the stream code, so I've left it out. Happy to add it here or as a follow-up, whichever you prefer.Part of #4711
How it was tested
TestLimitStreamConcurrencycovers the limiter against a handler that holds its slot until released: requests beyond the limit wait, freeing one slot lets exactly one queued request in, a request canceled while queued gets a 503 without taking a slot, and 0 and -1 don't limit.TestStreamDecisionMaxConcurrentRequestsruns against the real LAPI: the config reaches the route, the slot is given back after a full response, and while one pull is stuck mid-response a second gets a 503 when the limit is 1 and a 200 when unset or negative.To check the tests aren't tautological, I broke the slot release, the cancel handling, the config wiring and the route wiring, one at a time. A test failed each time.
golangci-lint 2.13.2, run like
make lintfor linux, windows and freebsd: no issues in the changed files.Not yet done: a local run against a real instance. Docs: crowdsecurity/crowdsec-docs#1180.
Checklist
cscli -o json|rawoutput.AI assistance used: mostly
/kind enhancement
/area local-api
/area configuration