fix(embeddings): bound the connection separately from the read - #346
fix(embeddings): bound the connection separately from the read#346bakiburakogun wants to merge 2 commits into
Conversation
The embedding request passed a single timeout value, so nothing bounded establishing the connection. When the TLS handshake to the embedding service stalls — TCP connects, the handshake never completes — the worker waits in recv for the whole request_timeout, 1800 s by default. That stops more than the one request: files_indexing_thread waits for every worker of the batch, so one stalled handshake holds up the batch and no further queue items are fetched until it returns. Give the connection its own bound so the retry already implemented in _get_embedding() can land on a healthy connection. On our deployment this took worker durations from ~300 s, all finishing together at the timeout, to 1.4-85 s, and indexing from 5.2 to 60-65 documents per minute. Fixes nextcloud#345 Signed-off-by: Baki Burak Öğün <63836730+bakiburakogun@users.noreply.github.com>
|
Please hold this one for a moment — see #345 (comment). Running with this change I still caught workers stuck for 28 minutes in the HTTP/2 handshake exchange ( Forcing HTTP/1.1 for the embedding request removed the stall on our four instances (1 of 4 nodes working before, 4 of 4 after). Happy to extend this PR with that, or to make the protocol configurable instead — whichever shape you prefer. |
kyteinsky
left a comment
There was a problem hiding this comment.
thanks!
replied to the addition discussion in the issue.
| # Connection timeout for the embedding requests, kept apart from the read timeout: | ||
| # a stalled TLS handshake would otherwise hold a worker for the whole | ||
| # request_timeout, and the batch waits for every worker in it. |
There was a problem hiding this comment.
this comment is not required, connect timeout is explanatory enough
| timeout=emconf.request_timeout, | ||
| timeout=(HTTP_CONNECT_TIMEOUT, emconf.request_timeout), |
There was a problem hiding this comment.
this works but would be nice to use a structure here: https://urllib3future.readthedocs.io/en/latest/reference/urllib3.util.html#urllib3.util.Timeout
Replaces the tuple with niquests.TimeoutConfiguration and drops the comment on the constant, as requested in review. Signed-off-by: Baki Burak Öğün <63836730+bakiburakogun@users.noreply.github.com>
|
Thanks, comment dropped and the timeout is a structure now. One note on which structure. I started with # niquests/adapters.py
elif isinstance(timeout, TimeoutSauce):
...
else:
timeout = TimeoutSauce(connect=timeout, read=timeout)and that check does not hold for the module under its >>> niquests.TimeoutConfiguration is niquests.adapters.TimeoutSauce
True
>>> isinstance(urllib3_future.util.Timeout(connect=15, read=30), niquests.adapters.TimeoutSauce)
FalsePassing the second one would fall through to the
|
Fixes #345
Problem
_get_embedding()passed a single value as the timeout, so nothing bounded establishing the connection — only reading the response. When the TLS handshake to the embedding service stalls (TCP connects, the handshake never completes) the worker waits inrecvfor the wholerequest_timeout, which defaults to 1800 s.It costs more than the one request:
files_indexing_threaddispatches a batch across workers and then waits for all of them, so a single stalled handshake holds up the batch and no further queue items are fetched until it returns. In the logs that shows up as every worker of a batch finishing at the same moment, at the timeout, instead of at its own pace.py-spy on a stalled worker put it inside
connect(), in the handshake exchange:Change
Add
HTTP_CONNECT_TIMEOUTnext to the existingTCP_CONNECT_TIMEOUTand pass(connect, read)toniquests.post. The read timeout is unchanged, so slow embedding responses are still tolerated exactly as before; only a connection that will not come up now fails quickly, and the retry already implemented in_get_embedding()can land on a healthy connection.15 s is generous relative to what a working handshake costs — the same endpoint from the same container answers a one-text request in 0.28 s — while being far below the read timeout.
Testing
Deployed on Nextcloud 34.0.3, four backend instances, external embedding service. Same corpus, same nodes, nothing else changed:
Sustained over the following hours at 60–65 documents/min. Before the change the run looked stopped rather than slow, which is what sent us looking for a hang in the first place.