clickhouse-native 0.10.0-aarch64-linux-gnu → 0.11.1-aarch64-linux-gnu

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 48b6a35f5fab1f2014008a5f31b4b4e1364d094ecbcfb755e7ada161e42846e2
4
- data.tar.gz: f67f936383f7fade31eab0a93cc1c882d20c77735afdda6c222f6b36d31f7156
3
+ metadata.gz: '0928c0e76b392ebf13b29276176b1e0019b0987d1d911803bc39fb540728df0c'
4
+ data.tar.gz: 30ff3e3d0a692bb87993aaaf4ea5d3fccff7a1b1bfaf81bc88c8049cbcbed334
5
5
  SHA512:
6
- metadata.gz: a3ef2222901d66eba3a02c1e1bce1e6a1b69836bb5494279434cb177cdf1903e28c267322b2cb1c71f9aeac6a3ee1c2e681c1af2e2a98e3b98e49a09fb3da140
7
- data.tar.gz: 405ca090b8762f57075408c6dda431300c03bd0a6a8b0e3a947ccc00d7bc8740c6b2a8019816b81065dd72411068fc5ccbda06a8ba5f82785546fb81b2d6f5a6
6
+ metadata.gz: b8dba60e9036999618a1bf1156dd526784ce90235686b7760697998e7e1fe361e4a0c8ed434af196a8ab2df3672ba9c5b8ca0e9393b195816af31287907a6a52
7
+ data.tar.gz: '048be87562219797f37401b8b33d544687945e38c64ea322f727f751f5c829d787f3b437c7c2306f1752b082f00df442f35d0c2554ee292b1021082ee5cfa1ea'
@@ -2,7 +2,7 @@
2
2
 
3
3
  module ClickhouseNative
4
4
  class Client
5
- attr_reader :host, :port, :database
5
+ attr_reader :host, :port, :database, :ping_before_query, :tcp_keepalive, :retry_timeout
6
6
 
7
7
  def describe_table(table, db_name: nil)
8
8
  fq = db_name ? "#{db_name}.#{table}" : table
@@ -8,44 +8,54 @@ module ClickhouseNative
8
8
 
9
9
  def initialize(host:, port:, database: "default", user: "default", password: "",
10
10
  compression: :none, logger: nil, settings: {},
11
- pool_size: 5, pool_timeout: 5)
11
+ pool_size: 5, pool_timeout: 5,
12
+ ping_before_query: true, tcp_keepalive: true, retry_timeout: 1)
12
13
  @host = host
13
14
  @port = port
14
15
  @database = database
15
- client_kwargs = { host:, port:, database:, user:, password:, compression:, logger: }
16
- @set_sql = settings_sql(settings)
16
+ client_kwargs = {
17
+ host:, port:, database:, user:, password:, compression:, logger:, settings:,
18
+ ping_before_query:, tcp_keepalive:, retry_timeout:
19
+ }
17
20
  @pool = ConnectionPool.new(size: pool_size, timeout: pool_timeout) do
18
- client = Client.new(**client_kwargs)
19
- client.execute(@set_sql) if @set_sql
20
- client
21
+ Client.new(**client_kwargs)
21
22
  end
22
23
  end
23
24
 
24
- # On exception, discard the client rather than reuse it: an error
25
- # path leaves the socket in an unknown state. The C++ binding issues
25
+ # On an aborted operation, discard the client rather than reuse it: the
26
+ # socket is left in an unknown state. The C++ binding issues
26
27
  # ResetConnection, but a subsequent send can still surface buffered
27
28
  # protocol errors from the prior aborted operation — those get
28
- # attributed to whatever SQL we tried next (e.g. the SET reapplying
29
- # session settings), producing misleading log lines and re-raises in
30
- # unrelated code. A fresh socket + handshake is cheap relative to
31
- # debugging that.
29
+ # attributed to whatever SQL we tried next, producing misleading log
30
+ # lines and re-raises in unrelated code. A fresh socket + handshake is
31
+ # cheap relative to debugging that.
32
32
  #
33
33
  # ConnectionError gets one automatic retry: pooled connections that
34
34
  # have been idle long enough for the server / an LB to FIN them
35
- # surface as "closed" on the very next recv (errno 0, message
36
- # "closed: Success"). Discarding and re-checking out lands a fresh
37
- # socket and the operation succeeds. The retry only triggers when
38
- # the dead-connection error fired before any data was sent, so
39
- # write operations don't risk double-execution from this path.
35
+ # surface as "closed" on the very next recv (errno is whatever stale
36
+ # value was left in the thread — "closed: Success", "closed: Operation
37
+ # now in progress", etc. all mean the same recv()==0). Discarding and
38
+ # re-checking out lands a fresh socket and the operation succeeds. The
39
+ # retry only triggers when the dead-connection error fired before any
40
+ # data was sent, so write operations don't risk double-execution.
41
+ #
42
+ # This is the backstop: with ping_before_query on (the default) the
43
+ # driver already pings and transparently reconnects a dead socket
44
+ # before running the query, so most stale connections never surface as
45
+ # a ConnectionError here at all. This retry still covers the residual
46
+ # race (socket dies between the ping and the query).
47
+ #
48
+ # A bare `rescue` is not enough to spot an abandoned query. Timeout and
49
+ # Sidekiq shutdown raise off Exception rather than StandardError, and
50
+ # Thread#kill raises nothing at all — Parallel.in_threads kills every
51
+ # sibling worker the moment one of them fails. Miss those and the
52
+ # connection goes back in with the server still streaming a response at
53
+ # it; the next checkout reads that leftover as its own, which surfaces
54
+ # as a bogus packet type far from here.
40
55
  def with
41
56
  attempts = 0
42
57
  begin
43
- @pool.with do |client|
44
- yield client
45
- rescue
46
- @pool.discard_current_connection(&:close)
47
- raise
48
- end
58
+ @pool.with { |client| discard_unless_clean { yield client } }
49
59
  rescue ConnectionError
50
60
  attempts += 1
51
61
  retry if attempts == 1
@@ -87,18 +97,27 @@ module ClickhouseNative
87
97
 
88
98
  private
89
99
 
90
- # Render a `SET key1 = val1, key2 = val2` statement once at pool setup
91
- # so every checked-out connection starts with the same session
92
- # settings. Matches how the HTTP driver injected global_params per
93
- # request. Values: Integer / Float render bare; anything else is
94
- # quoted as a SQL string literal.
95
- def settings_sql(settings)
96
- return nil if settings.nil? || settings.empty?
97
- parts = settings.map do |k, v|
98
- literal = v.is_a?(Numeric) ? v.to_s : "'#{v.to_s.gsub("'", "''")}'"
99
- "#{k} = #{literal}"
100
+ # Keep the client only when the block either finished or left of its own
101
+ # accord. A `break` out of a streaming read is deliberate, and the binding
102
+ # has already reconnected the client on its way out (query_each resets the
103
+ # connection before rb_jump_tag), so it is good to reuse — discarding would
104
+ # buy a second connect on top of the one already paid. An exception is an
105
+ # abort, and so is Thread#kill: it raises nothing, so no rescue ever sees
106
+ # it, and its only trace is an "aborting" thread while ensure runs.
107
+ def discard_unless_clean
108
+ finished = false
109
+ begin
110
+ result = yield
111
+ finished = true
112
+ result
113
+ rescue Exception # rubocop:disable Lint/RescueException
114
+ @pool.discard_current_connection(&:close)
115
+ raise
116
+ ensure
117
+ if !finished && Thread.current.status == "aborting"
118
+ @pool.discard_current_connection(&:close)
119
+ end
100
120
  end
101
- "SET #{parts.join(', ')}"
102
121
  end
103
122
  end
104
123
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ClickhouseNative
4
- VERSION = "0.10.0"
4
+ VERSION = "0.11.1"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: clickhouse-native
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.10.0
4
+ version: 0.11.1
5
5
  platform: aarch64-linux-gnu
6
6
  authors:
7
7
  - Yuri Smirnov