Summary
retry_interval_seconds is stored and then never read, so retries fire back to back with no
delay. Every mention of it in the installed 2.0.0 package:
configuration.py:185 retry_interval_seconds (float): The interval in seconds between retries.
configuration.py:217 self.retry_interval_seconds = config_dict.get("retry_interval_seconds", 1.0)
sync/api_call.py::_execute_request recurses straight into the next attempt:
except _SERVER_ERRORS as server_error:
self.node_manager.set_node_health(node, is_healthy=False)
return self._execute_request(
method, endpoint, entity_type, as_json,
last_exception=server_error, num_retries=num_retries + 1, **kwargs,
)
async_/api_call.py is the same. 0.21.0 did sleep, at api_call.py:125-127:
logger.debug('Sleeping for {} and retrying...'.format(self.config.retry_interval_seconds))
time.sleep(self.config.retry_interval_seconds)
so the 1.0 rewrite dropped it. 1.0.3, 1.3.0 and 2.0.0 all lack it.
Reproduction
typesense 2.0.0, Python 3.14.0, fresh venv. A loopback HTTP server that answers every request
with 503 and records the arrival time. One node in nodes, num_retries=3,
connection_timeout_seconds=5, client.collections.retrieve().
| how the interval was set |
attempts the server saw |
gaps between them |
total |
retry_interval_seconds=3 |
4 |
0.001, 0.000, 0.000 |
0.003s |
interval_seconds=3 |
4 |
0.002, 0.000, 0.000 |
0.003s |
| nothing passed (default 1.0) |
4 |
0.000, 0.000, 0.000 |
0.002s |
No delay at all. Same measurement, same script, against 0.21.0:
|
attempts |
gaps |
total |
retry_interval_seconds=3 |
4 |
3.019, 3.010, 3.004 |
12.056s |
| nothing passed (default 1.0) |
4 |
1.004, 1.003, 1.008 |
4.021s |
I ran the old version first because a table of zeros could just as easily mean my harness was
not measuring anything. The old client sleeps.
Practical effect is that a node returning 503 or timing out gets four requests inside three
milliseconds instead of over three seconds, and set_node_health marks it unhealthy on the way
through, so all four land before the node has any chance to recover.
A second one, related, if you want it
interval_seconds is in ConfigDict and in the class docstring, and nothing reads it.
retry_interval_seconds is what __init__ reads and it is not in ConfigDict. So the key that
type checks is ignored and the key that works fails type checking. That is the same shape as #73,
which you fixed for connection_timeout_seconds in March 2025 by adding the key to the TypedDict.
timeout_seconds has a third version of it. It is in ConfigDict marked deprecated, and passing
it logs
Deprecation warning: timeout_seconds is now renamed to connection_timeout_seconds
and then the value is dropped. Against a server that sleeps 8 seconds before responding,
timeout_seconds=30 gives up after 3.07s with a ReadTimeout (the 3.0 default), while
connection_timeout_seconds=30 completes at 8.02s. A message saying an option was renamed reads
as though the old spelling still works.
Suggested fix
For the retry gap, in both sync/api_call.py and async_/api_call.py, before the recursive call:
time.sleep(self.config.retry_interval_seconds)
(await asyncio.sleep(...) in the async one.)
For the config keys, either read both spellings or drop the unread ones from ConfigDict:
self.retry_interval_seconds = config_dict.get(
"retry_interval_seconds", config_dict.get("interval_seconds", 1.0)
)
self.connection_timeout_seconds = config_dict.get(
"connection_timeout_seconds", config_dict.get("timeout_seconds", 3.0)
)
Happy to open a PR.
Summary
retry_interval_secondsis stored and then never read, so retries fire back to back with nodelay. Every mention of it in the installed 2.0.0 package:
sync/api_call.py::_execute_requestrecurses straight into the next attempt:async_/api_call.pyis the same. 0.21.0 did sleep, atapi_call.py:125-127:so the 1.0 rewrite dropped it. 1.0.3, 1.3.0 and 2.0.0 all lack it.
Reproduction
typesense 2.0.0, Python 3.14.0, fresh venv. A loopback HTTP server that answers every request
with 503 and records the arrival time. One node in
nodes,num_retries=3,connection_timeout_seconds=5,client.collections.retrieve().retry_interval_seconds=3interval_seconds=3No delay at all. Same measurement, same script, against 0.21.0:
retry_interval_seconds=3I ran the old version first because a table of zeros could just as easily mean my harness was
not measuring anything. The old client sleeps.
Practical effect is that a node returning 503 or timing out gets four requests inside three
milliseconds instead of over three seconds, and
set_node_healthmarks it unhealthy on the waythrough, so all four land before the node has any chance to recover.
A second one, related, if you want it
interval_secondsis inConfigDictand in the class docstring, and nothing reads it.retry_interval_secondsis what__init__reads and it is not inConfigDict. So the key thattype checks is ignored and the key that works fails type checking. That is the same shape as #73,
which you fixed for
connection_timeout_secondsin March 2025 by adding the key to the TypedDict.timeout_secondshas a third version of it. It is inConfigDictmarked deprecated, and passingit logs
and then the value is dropped. Against a server that sleeps 8 seconds before responding,
timeout_seconds=30gives up after 3.07s with aReadTimeout(the 3.0 default), whileconnection_timeout_seconds=30completes at 8.02s. A message saying an option was renamed readsas though the old spelling still works.
Suggested fix
For the retry gap, in both
sync/api_call.pyandasync_/api_call.py, before the recursive call:(
await asyncio.sleep(...)in the async one.)For the config keys, either read both spellings or drop the unread ones from
ConfigDict:Happy to open a PR.