Skip to content

Support more granular timeouts for queries #192

Closed

Description

@matthax

Is your feature request related to a problem? Please describe.
Feature request / enhancement

Describe the solution you'd like
I'd like to have more granular control over timeouts in the client. Specifically, the ping method has a hardcoded timeout of 3 but in our use case we'd like to be able to configure that to something like 1 for example.

  • The clickhouse-driver library solves this using a sync_request_timeout argument. Perhaps something similar could be added to the client constructor here.

The _raw_request method uses the timeout on the client instance, which is more flexible, but our use case requires a per-query timeout setting. Currently, I'm working around this with a solution I don't love

    @contextmanager
    def with_timeout(
        self,
        connect_timeout: Optional[int] = None,
        send_receive_timeout: Optional[int] = None,
    ):
        _old_timeout = self._delegate.timeout
        self._delegate.timeout = Timeout(
            connect=coerce_int(connect_timeout or _old_timeout.connect_timeout),
            read=coerce_int(send_receive_timeout or _old_timeout.read_timeout),
        )
        yield self._delegate
        self._delegate.timeout = _old_timeout

This approach is similar to that in clickhouse-driver which offers a timeout_setter on the Connection instance it provides. A builtin way to supply a timeout either through a context manager or with a sort of per-query setting e.g. client.query(..., connect_timeout=1, send_receive_timeout=3) would be great.

Describe alternatives you've considered
Right now I'm composing a class that adds some workarounds (the with_timeout above) and for ping I override the method

    def ping(self):
        """
        Ping the host, sync_request_timeout is supplied to the call
        TODO: Suggest this as a PR to clickhouse-connect
        """
        try:
            response = self._delegate.http.request(
                "GET", f"{self._delegate.url}/ping", timeout=self._sync_request_timeout
            )
            return 200 <= response.status < 300
        except HTTPError:
            return False

I suppose query context or settings could also be a feasible way to supply the timeout. I don't have a strong opinion on how it should be accomplished and would love to hear your thoughts.

Additional context

I'm happy to PR any of these changes, just wanted to start a discussion on the approach before starting any implementation.

Activity

  1. genzgd commented on May 26, 2023

    @genzgd
    Collaborator

    Ping is obviously an easy fix, adding an optional defaulted timeout parameter (which takes either an float or a urllib3 Timeout) is trivial -- it's also the only method that doesn't go through the _raw_request method.

    Given that a single timeout setting for the client seems to work for the vast majority of use cases I'm hesitant to add yet more constructor arguments to either the Query/InsertContext or the Client. Honestly your approach seems fine for your advanced use case -- with the caveat that you are making the client "less thread safe" by modifying the Client timeout property -- but I think most people who get to that level of control would be aware of that risk.

    As for other approaches, I don't particularly like the query context as a place to handle this, since inserts and "commands" don't utilize it (and it also is suffering from having too many options/properties.

    What do you think about making the connect_timeout and send_receive_timeout take either the current ints or a "provider function" that return an int (or I suppose a float, although fractional second timeouts don't seem necessary).

  2. matthax commented on May 26, 2023

    @matthax
    Author

    @genzgd I definitely hear you on the argument overload. For the provider option, what would you envision that function receiving as arguments?

    Adding a default to ping would be great, that's the bigger pain point of the two.

    And yes, the primary concern I had with the approach I demonstrated above was thread safety, but we do have locks around the client connection pulling that should prevent issues there.

  3. genzgd commented on May 28, 2023

    @genzgd
    Collaborator

    @matthax Thinking through this if you really need per query timeouts they should be added into the QueryContext (even though it has so many properties already). My provider function idea is just a little too fancy.

    Since it's an advanced feature I think using a union of float/int/urllib3 Timeout should be okay, since it will just get passed to the urllib3 library anyway. Any of the methods (inserts, commands, raw*) that need such a timeout should accept that additional parameter.

    If you want to take a stab at a PR along those lines that would be most welcome, otherwise it will probably be a week or so before I get to it (in connection with a few other enhancements I'm working on as I have time.)

  4. matthax commented on May 30, 2023

    @matthax
    Author

    @genzgd I can open a PR for this

  5. joe-clickhouse commented on Sep 10, 2026

    @joe-clickhouse
    Contributor

    Hey @matthax thanks for the suggestion. I know it's been a while...do you still have any interest in this or a need/use case for it?

  6. matthax commented on Sep 12, 2026

    @matthax
    Author

    Hey @matthax thanks for the suggestion. I know it's been a while...do you still have any interest in this or a need/use case for it?

    I have been monkey patching this for ~3y, it would still be nice to have, but I was not approved to open the MR.

  7. joe-clickhouse commented on Sep 15, 2026

    @joe-clickhouse
    Contributor

    @matthax haha I hear ya. I can add e.g. ping(timeout=1.0). Would that let you remove the monkey patch? Or do you also still change connect/read timeouts per query on the same client? If so, could you share one current example?

  8. definitelynotchirag commented on Oct 30, 2026

    @definitelynotchirag
    Contributor

    heyy looks like this has been a long discusion :) @joe-clickhouse if nobody picked this up I can do the ping(timeout=...) part you suggested. Sync and async both, float or urllib3.Timeout, default stays as is

    Per query timeouts I'd leave for a separate PR unless you want them in QueryContext right now

  9. joe-clickhouse commented on Oct 30, 2026

    @joe-clickhouse
    Contributor

    Thanks, @definitelynotchirag go for it. Keep the scope minimal though please:

    • Add timeout: float | None = None to ping() on both the sync and async clients.
    • Take seconds as a float. Don't accept urllib3.Timeout, since the async side uses aiohttp.
    • Keep the default at 3 seconds.

    Per-query connect and read timeouts are out of scope for 1.x. We will look at them in 2.0. Then I say we can close this issue when the ping PR merges.

  10. added a commit that references this issue on Nov 2, 2026
    6f24bb8
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions