ch_connect 0.2.2 → 0.3.0
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 +4 -4
- data/.standard.yml +5 -0
- data/CHANGELOG.md +10 -1
- data/README.md +156 -87
- data/ext/ch_connect_native/ch_connect_native.c +1001 -0
- data/ext/ch_connect_native/extconf.rb +32 -0
- data/lib/ch_connect/config.rb +60 -19
- data/lib/ch_connect/connection.rb +382 -13
- data/lib/ch_connect/response.rb +0 -8
- data/lib/ch_connect/version.rb +1 -1
- data/lib/ch_connect.rb +14 -11
- data/vendor/clickhouse-c/LICENSE +203 -0
- data/vendor/clickhouse-c/VENDOR.md +24 -0
- data/vendor/clickhouse-c/clickhouse-async.h +301 -0
- data/vendor/clickhouse-c/clickhouse-client.h +995 -0
- data/vendor/clickhouse-c/clickhouse-compression.h +634 -0
- data/vendor/clickhouse-c/clickhouse.h +3394 -0
- metadata +19 -14
- data/lib/ch_connect/body_reader.rb +0 -79
- data/lib/ch_connect/http_transport.rb +0 -59
- data/lib/ch_connect/native_format_parser.rb +0 -405
- data/lib/ch_connect/transport_result.rb +0 -12
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2b03e3eeb6a94bc7bbef9469f177e5a1c50cbd186bb9b83fd0782acfe047ef88
|
|
4
|
+
data.tar.gz: e7e1ab0e6c1749164c471c8d86ae5fd133fff5609589a5ae112ed579028e3d8c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0cad454a3045989417a620d0bb23d9de4b38d22c6991efa01cb6a29ebb25b8530380b914b36f1c8c2b3eb4c565d48ff2969ddbff8da3fe1156d92da6951ef484
|
|
7
|
+
data.tar.gz: cbef85609d1c8a182a4fbf4f41322da86b5f843d6bac8a2539af4e57af01fbed44e11071b675cbf72590fbdd938c3d3a64bfc80e94187cfea4cf7b8ec74ba692
|
data/.standard.yml
CHANGED
data/CHANGELOG.md
CHANGED
|
@@ -1,4 +1,13 @@
|
|
|
1
|
-
##
|
|
1
|
+
## [0.3.0] - 2026-08-11
|
|
2
|
+
|
|
3
|
+
- Switched from HTTP to ClickHouse's native TCP protocol for faster queries and lower allocations
|
|
4
|
+
- Added connection pooling, LZ4/ZSTD compression, TLS, and native ClickHouse URLs
|
|
5
|
+
- Added native query parameters and per-query ClickHouse settings
|
|
6
|
+
- Added support for Ruby array query parameters
|
|
7
|
+
- Added opt-in `idempotent: true` query retries for transport failures on fresh pooled connections
|
|
8
|
+
- Added configurable capped exponential backoff with jitter between connection retries
|
|
9
|
+
- Improved connection timeouts, retries, fork safety, and idle connection handling
|
|
10
|
+
- Removed HTTP configuration; existing connections must use native ports, normally 9000 or 9440
|
|
2
11
|
|
|
3
12
|
## [0.2.2] - 2026-05-06
|
|
4
13
|
|
data/README.md
CHANGED
|
@@ -1,78 +1,96 @@
|
|
|
1
1
|
# ch_connect
|
|
2
2
|
|
|
3
|
-
> **Note:** This gem was previously published as `clickhouse-rb` and
|
|
3
|
+
> **Note:** This gem was previously published as `clickhouse-rb` and was renamed to `ch_connect` due to name conflicts.
|
|
4
4
|
|
|
5
|
-
Fast Ruby client for ClickHouse
|
|
5
|
+
Fast MRI Ruby client for ClickHouse's native TCP protocol.
|
|
6
6
|
|
|
7
7
|
## Features
|
|
8
8
|
|
|
9
|
-
- Native
|
|
10
|
-
- Persistent
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
9
|
+
- Native TCP protocol and binary result decoding
|
|
10
|
+
- Persistent pooled connections safe for concurrent use
|
|
11
|
+
- LZ4 or ZSTD block compression
|
|
12
|
+
- TLS with certificate verification
|
|
13
|
+
- Safe connection-establishment retries and opt-in retries for idempotent queries
|
|
14
|
+
- Interruptible connect, read, and write timeouts
|
|
15
|
+
- Common ClickHouse scalar and composite types
|
|
14
16
|
|
|
15
17
|
## Benchmarks
|
|
16
18
|
|
|
17
|
-
Compared
|
|
19
|
+
Compared with [click_house](https://github.com/shlima/click_house), [clickhouse](https://github.com/archan937/clickhouse), and [click_house-client](https://gitlab.com/gitlab-org/ruby/gems/clickhouse-client) on Ruby 4.0.3. `ch_connect` uses its default LZ4 compression.
|
|
18
20
|
|
|
19
|
-
|
|
21
|
+
Speed (iterations/second, higher is better):
|
|
20
22
|
|
|
21
23
|
| Scenario | ch_connect | click_house | clickhouse | click_house-client |
|
|
22
24
|
|----------|------------|-------------|------------|--------------------|
|
|
23
|
-
| Small queries (10 rows) | **
|
|
24
|
-
| Large queries (100K rows) | **
|
|
25
|
+
| Small queries (10 rows) | **962 i/s** | 363 i/s (2.7x slower) | 317 i/s (3.0x slower) | 372 i/s (2.6x slower) |
|
|
26
|
+
| Large queries (100K rows) | **8.2 i/s** | 1.1 i/s (7.2x slower) | 0.6 i/s (14.1x slower) | 1.5 i/s (5.3x slower) |
|
|
25
27
|
|
|
26
|
-
|
|
28
|
+
Multi-threaded throughput (10K-row query, total queries/second):
|
|
29
|
+
|
|
30
|
+
| Threads | ch_connect | click_house | clickhouse | click_house-client |
|
|
31
|
+
|---------|------------|-------------|------------|--------------------|
|
|
32
|
+
| 1 | **292 q/s** | 69 q/s | 117 q/s | 48 q/s |
|
|
33
|
+
| 4 | **598 q/s** | 162 q/s | 319 q/s | 82 q/s |
|
|
34
|
+
| 8 | **606 q/s** | 158 q/s | 321 q/s | 80 q/s |
|
|
35
|
+
|
|
36
|
+
Memory allocated for a large query (lower is better):
|
|
27
37
|
|
|
28
38
|
| Gem | Allocated |
|
|
29
39
|
|-----|-----------|
|
|
30
|
-
| ch_connect | **
|
|
31
|
-
| click_house |
|
|
32
|
-
|
|
|
33
|
-
|
|
|
40
|
+
| ch_connect | **97 MB** |
|
|
41
|
+
| click_house | 198 MB |
|
|
42
|
+
| click_house-client | 210 MB |
|
|
43
|
+
| clickhouse | 436 MB |
|
|
34
44
|
|
|
35
|
-
See `benchmark/` directory for
|
|
45
|
+
A small query allocates approximately **3.2 KB / 32 Ruby objects**. See the [`benchmark/`](benchmark/) directory for the comparison suite and methodology.
|
|
36
46
|
|
|
37
47
|
## Installation
|
|
38
48
|
|
|
39
|
-
Add to your Gemfile:
|
|
49
|
+
Add the gem to your Gemfile:
|
|
40
50
|
|
|
41
51
|
```ruby
|
|
42
52
|
gem "ch_connect"
|
|
43
53
|
```
|
|
44
54
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
```bash
|
|
48
|
-
bundle install
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
## Usage
|
|
55
|
+
The gem contains a native extension and currently requires MRI Ruby. LZ4 and ZSTD support are detected when the extension is built.
|
|
52
56
|
|
|
53
|
-
|
|
57
|
+
## Configuration
|
|
54
58
|
|
|
55
59
|
```ruby
|
|
56
60
|
require "ch_connect"
|
|
57
61
|
|
|
58
62
|
ChConnect.configure do |config|
|
|
59
63
|
config.host = "localhost"
|
|
60
|
-
config.port =
|
|
64
|
+
config.port = 9000
|
|
61
65
|
config.database = "default"
|
|
62
66
|
config.username = "default"
|
|
63
67
|
config.password = ""
|
|
64
68
|
end
|
|
65
69
|
```
|
|
66
70
|
|
|
67
|
-
Or
|
|
71
|
+
Or use a native ClickHouse URL:
|
|
72
|
+
|
|
73
|
+
```ruby
|
|
74
|
+
ChConnect.configure do |config|
|
|
75
|
+
config.url = "clickhouse://user:pass@localhost:9000/mydb"
|
|
76
|
+
end
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Supported URL schemes:
|
|
80
|
+
|
|
81
|
+
- `clickhouse://` and `tcp://`: plaintext TCP, default port 9000
|
|
82
|
+
- `clickhouses://` and `tcps://`: TLS, default port 9440
|
|
68
83
|
|
|
69
84
|
```ruby
|
|
70
85
|
ChConnect.configure do |config|
|
|
71
|
-
config.url = "
|
|
86
|
+
config.url = "clickhouses://user:pass@clickhouse.example.com:9440/mydb"
|
|
87
|
+
config.ssl_ca = "/path/to/ca.crt" # optional; system roots are the default
|
|
72
88
|
end
|
|
73
89
|
```
|
|
74
90
|
|
|
75
|
-
|
|
91
|
+
HTTP URLs and ClickHouse's HTTP endpoint are not supported.
|
|
92
|
+
|
|
93
|
+
## Querying
|
|
76
94
|
|
|
77
95
|
```ruby
|
|
78
96
|
conn = ChConnect::Connection.new
|
|
@@ -83,9 +101,7 @@ response.each do |row|
|
|
|
83
101
|
end
|
|
84
102
|
```
|
|
85
103
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
Connections use httpx's built-in connection pooling, making them safe for concurrent use:
|
|
104
|
+
A `Connection` owns a connection pool and is safe for concurrent use:
|
|
89
105
|
|
|
90
106
|
```ruby
|
|
91
107
|
conn = ChConnect::Connection.new
|
|
@@ -96,59 +112,96 @@ end
|
|
|
96
112
|
threads.each(&:join)
|
|
97
113
|
```
|
|
98
114
|
|
|
99
|
-
|
|
115
|
+
Configuration is copied when a `Connection` is created. Create a new connection
|
|
116
|
+
to apply configuration changes.
|
|
117
|
+
|
|
118
|
+
### Idempotent retries
|
|
119
|
+
|
|
120
|
+
Queries are not retried after execution may have started unless the caller
|
|
121
|
+
explicitly marks the operation as idempotent:
|
|
100
122
|
|
|
101
123
|
```ruby
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
124
|
+
response = conn.query(
|
|
125
|
+
"SELECT * FROM users WHERE id = {id:UInt64}",
|
|
126
|
+
params: {id: 123},
|
|
127
|
+
idempotent: true
|
|
128
|
+
)
|
|
106
129
|
```
|
|
107
130
|
|
|
108
|
-
|
|
131
|
+
With `idempotent: true`, a transport `ChConnect::ConnectionError` is retried on
|
|
132
|
+
a fresh pooled connection up to `max_retries`. Connection-establishment
|
|
133
|
+
failures continue to use the same retry limit for every query because no query
|
|
134
|
+
has been sent. Retries use exponential backoff with jitter, starting at
|
|
135
|
+
`retry_base_interval` and capped at `retry_max_interval`; set the base interval
|
|
136
|
+
to `0` to disable the delay. `ChConnect::QueryError` is never retried.
|
|
109
137
|
|
|
110
|
-
|
|
138
|
+
Only opt in when repeating the complete operation is safe. In particular,
|
|
139
|
+
writes remain non-retried by default because a lost response does not prove
|
|
140
|
+
that ClickHouse did not apply the write.
|
|
141
|
+
|
|
142
|
+
### Results
|
|
143
|
+
|
|
144
|
+
`Response` implements `Enumerable`:
|
|
111
145
|
|
|
112
146
|
```ruby
|
|
113
147
|
response = conn.query("SELECT id, name, created_at FROM users")
|
|
114
148
|
|
|
115
|
-
# Iterate over rows as hashes with symbol keys
|
|
116
149
|
response.each { |row| puts row[:name] }
|
|
117
|
-
|
|
118
|
-
# Use any Enumerable method
|
|
119
150
|
response.map { |row| row[:id] }
|
|
120
|
-
response.
|
|
121
|
-
response.first # => {id: 1, name: "Alice", created_at: 2024-01-01 00:00:00 UTC}
|
|
151
|
+
response.first
|
|
122
152
|
|
|
123
|
-
#
|
|
124
|
-
response.
|
|
125
|
-
response.
|
|
126
|
-
response.
|
|
153
|
+
response.rows # [[1, "Alice", 2024-01-01 00:00:00 UTC], ...]
|
|
154
|
+
response.columns # [:id, :name, :created_at]
|
|
155
|
+
response.types # [:UInt64, :String, :DateTime]
|
|
156
|
+
response.to_a # [{id: 1, name: "Alice", ...}, ...]
|
|
157
|
+
response.summary # {read_rows: 1, read_bytes: 42, ...}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
`summary[:client_elapsed_ns]` is client wall-clock time for the complete query,
|
|
161
|
+
including pool checkout and connection establishment. Native responses do not
|
|
162
|
+
currently expose the HTTP transport's former server-side `elapsed_ns` metric.
|
|
127
163
|
|
|
128
|
-
|
|
129
|
-
response.to_a # => [{id: 1, name: "Alice", ...}, ...]
|
|
164
|
+
### Query parameters
|
|
130
165
|
|
|
131
|
-
|
|
132
|
-
response
|
|
166
|
+
```ruby
|
|
167
|
+
response = conn.query(
|
|
168
|
+
"SELECT * FROM users WHERE id = {id:UInt64}",
|
|
169
|
+
params: {id: 123}
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
response = conn.query(
|
|
173
|
+
"SELECT * FROM users WHERE tag IN {tags:Array(String)}",
|
|
174
|
+
params: {tags: ["ruby", "clickhouse"]}
|
|
175
|
+
)
|
|
133
176
|
```
|
|
134
177
|
|
|
135
|
-
|
|
178
|
+
`nil` is encoded as the native nullable marker.
|
|
179
|
+
Parameter keys match the names used in query placeholders. Ruby arrays are
|
|
180
|
+
serialized recursively and support strings, symbols, numbers, dates, booleans,
|
|
181
|
+
`nil`, and nested arrays. Other element types raise `ArgumentError`; format
|
|
182
|
+
values such as `Time` and `DateTime` as strings for the target ClickHouse type.
|
|
183
|
+
|
|
184
|
+
### Query settings
|
|
185
|
+
|
|
186
|
+
Any ClickHouse setting can be supplied per query:
|
|
136
187
|
|
|
137
188
|
```ruby
|
|
138
189
|
response = conn.query(
|
|
139
|
-
"SELECT * FROM
|
|
140
|
-
|
|
190
|
+
"SELECT * FROM big_table",
|
|
191
|
+
settings: {
|
|
192
|
+
max_threads: 2,
|
|
193
|
+
max_result_rows: 10_000,
|
|
194
|
+
result_overflow_mode: "break"
|
|
195
|
+
}
|
|
141
196
|
)
|
|
142
197
|
```
|
|
143
198
|
|
|
144
|
-
## Supported
|
|
199
|
+
## Supported data types
|
|
145
200
|
|
|
146
|
-
| ClickHouse
|
|
201
|
+
| ClickHouse type | Ruby type |
|
|
147
202
|
|-----------------|-----------|
|
|
148
|
-
| UInt8/16/32/64 | Integer |
|
|
149
|
-
|
|
|
150
|
-
| Int8/16/32/64 | Integer |
|
|
151
|
-
| Int128/256 | Integer |
|
|
203
|
+
| UInt8/16/32/64/128/256 | Integer |
|
|
204
|
+
| Int8/16/32/64/128/256 | Integer |
|
|
152
205
|
| Float32/64 | Float |
|
|
153
206
|
| Decimal | BigDecimal |
|
|
154
207
|
| Bool | TrueClass/FalseClass |
|
|
@@ -164,66 +217,82 @@ response = conn.query(
|
|
|
164
217
|
| Nullable | nil or inner type |
|
|
165
218
|
| LowCardinality | inner type |
|
|
166
219
|
|
|
167
|
-
|
|
220
|
+
Unsupported types raise `ChConnect::UnsupportedTypeError` and discard the affected pooled connection.
|
|
221
|
+
|
|
222
|
+
## Configuration options
|
|
168
223
|
|
|
169
224
|
| Option | Default | Description |
|
|
170
225
|
|--------|---------|-------------|
|
|
171
|
-
| `scheme` | `"http"` | URL scheme (http/https) |
|
|
172
226
|
| `host` | `"localhost"` | ClickHouse server host |
|
|
173
|
-
| `port` | `
|
|
227
|
+
| `port` | `9000` | Native TCP port (`9440` when TLS is enabled before port resolution) |
|
|
228
|
+
| `compression` | `:lz4` | `:lz4`, `:zstd`, or `nil` |
|
|
229
|
+
| `ssl` | `false` | Enable TLS |
|
|
230
|
+
| `ssl_verify` | `true` | Verify the server certificate |
|
|
231
|
+
| `ssl_ca` | `nil` | CA certificate file; system roots are used by default |
|
|
174
232
|
| `database` | `"default"` | Database name |
|
|
175
|
-
| `username` | `""` | Authentication username |
|
|
233
|
+
| `username` | `"default"` | Authentication username |
|
|
176
234
|
| `password` | `""` | Authentication password |
|
|
177
|
-
| `connection_timeout` | `5` |
|
|
178
|
-
| `read_timeout` | `60` |
|
|
179
|
-
| `write_timeout` | `60` |
|
|
180
|
-
| `keep_alive_timeout` | `
|
|
181
|
-
| `pool_size` | `100` |
|
|
235
|
+
| `connection_timeout` | `5` | TCP and protocol handshake timeout in seconds |
|
|
236
|
+
| `read_timeout` | `60` | Maximum time without response data |
|
|
237
|
+
| `write_timeout` | `60` | Socket write deadline in seconds |
|
|
238
|
+
| `keep_alive_timeout` | `60` | Recycle pooled TCP connections after this many idle seconds; `nil` disables recycling |
|
|
239
|
+
| `pool_size` | `100` | Maximum pooled TCP connections |
|
|
182
240
|
| `pool_timeout` | `5` | Pool checkout timeout in seconds |
|
|
183
|
-
| `max_retries` | `3` |
|
|
184
|
-
| `
|
|
241
|
+
| `max_retries` | `3` | Retry limit for connection establishment and opted-in idempotent query transport failures |
|
|
242
|
+
| `retry_base_interval` | `0.05` | Initial retry delay in seconds; retries use exponential backoff with jitter, and `0` disables the delay |
|
|
243
|
+
| `retry_max_interval` | `1.0` | Maximum retry delay in seconds |
|
|
244
|
+
| `instrumenter` | `NullInstrumenter` | Object responding to `instrument` |
|
|
185
245
|
|
|
186
246
|
## Instrumentation
|
|
187
247
|
|
|
188
|
-
You can instrument queries by providing an instrumenter that responds to `#instrument`:
|
|
189
|
-
|
|
190
248
|
```ruby
|
|
191
249
|
ChConnect.configure do |config|
|
|
192
250
|
config.instrumenter = ActiveSupport::Notifications
|
|
193
251
|
end
|
|
194
252
|
|
|
195
|
-
# Subscribe to events
|
|
196
253
|
ActiveSupport::Notifications.subscribe("query.clickhouse") do |name, start, finish, id, payload|
|
|
197
254
|
puts "Query: #{payload[:sql]} took #{finish - start}s"
|
|
198
255
|
end
|
|
199
256
|
```
|
|
200
257
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
## Error Handling
|
|
258
|
+
## Error handling
|
|
204
259
|
|
|
205
260
|
```ruby
|
|
206
261
|
begin
|
|
207
262
|
conn.query("INVALID SQL")
|
|
208
|
-
rescue ChConnect::QueryError =>
|
|
209
|
-
|
|
263
|
+
rescue ChConnect::QueryError => error
|
|
264
|
+
warn error.message
|
|
210
265
|
end
|
|
211
266
|
|
|
212
|
-
# Unsupported types raise an exception
|
|
213
267
|
begin
|
|
214
268
|
conn.query("SELECT '{}'::JSON")
|
|
215
|
-
rescue ChConnect::UnsupportedTypeError =>
|
|
216
|
-
|
|
269
|
+
rescue ChConnect::UnsupportedTypeError => error
|
|
270
|
+
warn error.message
|
|
217
271
|
end
|
|
218
272
|
```
|
|
219
273
|
|
|
274
|
+
Network, timeout, pool, and protocol failures raise `ChConnect::ConnectionError`.
|
|
275
|
+
They are retried after a query may have started only when `idempotent: true`;
|
|
276
|
+
server `ChConnect::QueryError` responses are never retried.
|
|
277
|
+
When migrating from the HTTP client, change port 8123/8443 to the server's
|
|
278
|
+
native port (normally 9000/9440) and replace `scheme` with `ssl` or a native URL.
|
|
279
|
+
|
|
220
280
|
## Development
|
|
221
281
|
|
|
222
282
|
```bash
|
|
223
|
-
|
|
224
|
-
|
|
283
|
+
cd ext/ch_connect_native
|
|
284
|
+
ruby extconf.rb
|
|
285
|
+
make
|
|
286
|
+
cp "ch_connect_native.$(ruby -rrbconfig -e 'print RbConfig::CONFIG["DLEXT"]')" ../../lib/ch_connect/
|
|
287
|
+
cd ../..
|
|
288
|
+
|
|
289
|
+
CLICKHOUSE_URL=clickhouse://default:password@localhost:9000/default bundle exec rspec
|
|
290
|
+
|
|
291
|
+
# Optional TLS specs
|
|
292
|
+
spec/support/start_tls_clickhouse.sh /tmp/ch-tls password
|
|
293
|
+
CH_TLS_PORT=9440 CH_TLS_CA=/tmp/ch-tls/server.crt \
|
|
294
|
+
CLICKHOUSE_URL=clickhouse://default:password@localhost:9000/default bundle exec rspec
|
|
225
295
|
|
|
226
|
-
# Run linter
|
|
227
296
|
bundle exec standardrb
|
|
228
297
|
```
|
|
229
298
|
|