Repository navigation
Closed
Labels
Milestone
Description
Background
v0.3.2 was a minor release scheduled to be released months ago, but now it's a complete rewrite mainly for two reasons:
-
decoupling(see Proposal of restructuring code (RFC) #570 for details)
- Java client is async and lightweight
- JDBC driver is built on top of Java client
-
switching data format to RowBinary to fix issues and improve performance
Benchmark results...0.3.2-test1...
clickhouse-grpc-jdbcandclickhouse-http-jdbcare new JDBC driver(0.3.2) usingRowBinarydata formatclickhouse-jdbcis the old JDBC driver(0.3.1-patch) based onTabSeparatedclickhouse-native-jdbcis ClickHouse-Native-JDBC 2.6.0
Benchmark settings: thread=1, sampleSize=100000, fetchSize=10000, mode=throughput(ops/s).

0.3.2-test3...
Unlike previous round of testing, ClickHouse container is re-created a few minutes before benchmarking each driver.
- Single thread
- Comparison

Note: HttpClient is async(uses more than one thread in runtime); gRPC uses gzip(why?) which is slower than lz4. - VM utilization

Note: on client side, the new driver consumes less memory and CPU than others, BUT higher CPU on server side(due to overhead of http protocol?).
- Comparison
- 4 threads
0.3.2-test1, 0.3.2-test2, and 0.3.2-test3 are pre-release for public testing.
Downloads
Maven dependency:
<dependency>
<!-- will stop using group id "ru.yandex.clickhouse" starting from 0.4.0 -->
<groupId>com.clickhouse</groupId>
<!-- or clickhouse-grpc-client to use gRPC client -->
<artifactId>clickhouse-http-client</artifactId>
<version>0.3.2-test3</version>
</dependency>To download JDBC drivers:
| Package | Size | Legacy | New | HTTP | gRPC | Remark |
|---|---|---|---|---|---|---|
| clickhouse-jdbc-0.3.2-all.jar | 18.6MB | Y | Y | Y | Y | Both old and new JDBC drivers(besides netty, okhttp is included as well) |
| clickhouse-jdbc-0.3.2-http.jar | 756KB | N | Y | Y | N | New JDBC driver with only http support |
| clickhouse-jdbc-0.3.2-grpc.jar | 17.3MB | N | Y | N | Y | New JDBC driver with only grpc support(only netty, okhttp is excluded) |
| clickhouse-jdbc-0.3.2-shaded.jar | 2.8MB | Y | Y | Y | N | Both old and new JDBC drivers |
Note: the first two are recommended. grpc is experimental so you'd better use http.
Known Issues
- new driver(com.clickhouse.jdbc.ClickHouseDriver) does not work with version before 21.3
java.io.IOException: HTTP/1.1 header parser received no byteswhen using JDK 11+ andhttp_connection_provideris set toHTTP_CLIENTRESOURCE_EXHAUSTED: Compressed gRPC message exceeds maximum size- increasemax_inbound_message_sizeto resolveselect 1 format JSONworks in http but not grpc, because grpc client is not aware of response formatinsert into table values(?, ?)is slow in batch mode - tryinsert into table select c2,c3 from input('c1 String, c2 UInt8, c3 Nullable(UInt32)')insteaduse_time_zoneanduse_server_time_zone_for_datesproperties do not work- no table/index show up under
jdbc(*)database - roaringbitmap is not included in the shaded jar
Key Changes
- Java client and JDBC driver are now in different modules, along with JPMS support
- Replaced data format from TabSeparated to RowBinary
- Support more data types including Date32, Geo types, and mixed use of nested types
- JDBC connection URL now supports abbrebation, protocol and optional port
jdbc:ch://localhostis same asjdbc:clickhouse:http://localhost:8123jdbc:ch:grpc://localhost/dbis same asjdbc:clickhouse:grpc://localhost:9100/db
- New JDBC driver class is
com.clickhouse.jdbc.ClickHouseDriver(will removeru.yandex.clickhouse.ClickHouseDriverstarting from 0.4.0) - JDBC connection properties are simplified
- use
custom_http_headersandcustom_http_paramsfor customization - won't work for grpc client jdbcCompliant(defaults to true) to support fake transaction and standard synchronous UPDATE and DELETE statementstypeMappingsto customize type mapping(e.g.DateTime=java.lang.String,DateTime32=java.lang.String)
- use
Some more details can be found at #736, #747, #769, and #777.
Reacted by DavinQi and JackyWoo






Activity
millin commentedon Dec 1, 2021
Hello, @zhicwu
I tried a new driver in Intellij DataGrip and noticed that the DateTime format is different from that in version 0.3.1.
SELECT now()0.3.1-patch -
2021-12-01 19:27:120.3.2-test1 -
2021-12-01T19:27:12SELECT now('Europe/Paris')0.3.1-patch -
2021-12-01 17:27:150.3.2-test1 -
2021-12-01T17:27:15+01:00zhicwu commentedon Dec 2, 2021
Thank you for testing the new driver and reporting the issue here. I'll try to get it fixed in next build. Actually I like the way DBeaver handle

LocalDateTimeand especiallyOffsetDateTime:Update:

we cannot change behavior of DataGrip, but you can update connection properties by changing
typeMappingsin 0.3.2-test3 as a workaround:enqueue commentedon Dec 2, 2021
This is one of the crucial parts of the usage:
getString?getObject(int)getObject(int, Class<?>-- which class?getTime(int)getDate(int)getTimestamp(int)zhicwu commentedon Dec 2, 2021
Looks like a combination of
getObject()and then convertLocalDateTime(timestamp without time zone)/OffsetDateTime(timestamp with time zone) to string. DBeaver on the other hand has a display issue - submitted dbeaver/dbeaver#14772 to track status.dynaxis commentedon Dec 5, 2021
It seems that the Java client is currently relying on thread pools underneath to implement its async API, I mean, instead of being fully async at its core. Is it tentative or intended?
Also if I make a request to a node and try to make another request w/ the same client to a different node, then depending on the timing, the former call may fail since the second request may close the underlying connection too early. In that regard, I'm not sure what's the intended usage pattern of the Java client API.
For instance, if I want to make a few requests to ClickHouse in the context of an incoming HTTP call, then what am I supposed to reuse across the requests to a ClickHouse node? ClickHouseClient? ClickHouseRequest?
UPDATE: I had a look at
ClickHouseConnectionImpland it seems appropriate to hold aClickHouseRequest(A reference to aClickHouseClientis held by it).zhicwu commentedon Dec 6, 2021
Thanks @dynaxis, these are all good points.
Unfortunately this is intended, because JDBC driver is built on top of the client, meaning we prefer least dependency and we still need to support JDK 8. I hope we can find somewhere in the middle - a compact lib to serve very basic functions for both JDBC and R2DBC drivers.
Yes, you can reuse ClickHouseRequest. Each time you call its execute()/send() method, it will create a sealed copy for the execution, which is similar as copy-on-write data structure for thread safety. On the other, ClickHouseClient is responsible for handling protocol-specific details like how to execute a request and get response. Taking http as an example, depending on whether the concrete http connection(e.g. HttpURLConnection) is reusable, it may suggest to create new connection for each request or simply reuse the same one.
enqueue commentedon Dec 6, 2021
mvn installleads to ca. 6 failures. Will investigate further.setAutocommit(false)breaks the Metabase driver. However, I understand that we we would like to inform the client early on that we do not support transactions. I will see what I can do about it.zhicwu commentedon Dec 9, 2021
Did you mean the version on
masterbranch? We should test the one ondevelopbranch and fake transaction should work.enqueue commentedon Dec 12, 2021
DatabaseMetaDatabehavior is a breaking. Perhaps the new behavior is "more correct", but it is a change users need to be aware of. Compare results of this program:dynaxis commentedon Dec 12, 2021
I'm not sure this is the right place to comment on the current pre-release. Anyway, how about making
ClickHouseRequest.getClient()public? I'm injecting aClickHouseRequestto every invocation of HTTP request handlers and it's cumbersome to passClickHouseClientaround together w/ it.zhicwu commentedon Dec 12, 2021
Yes, I added some missing parts and made changes as well to make the driver more JDBC-compliant.
I'll try to emphasize the change in release notes - all in all, 0.3.2 is not a drop-in replacement of previous version, despite its version(just trying to stick with the roadmap).
Apart from that, as you may notice, more table types were added. I was kind of hoping DBeaver can support that like in SQuirrel SQL(see dbeaver/dbeaver#14773 ).
zhicwu commentedon Dec 13, 2021
Yes it's the right place and thanks for the feedback.
I thought about this a few times as well. In the very beginning, there's a
Contextobject as glue to connect almost everything(client, config, request and response), and then later I removed it as it became too heavy and complex. Later I made request.getClient public for convenience, and then I reverted the change as combination ofcopyandexecute()/send()methods are sufficient enough to my problems.What's your case of making the method public? If it's about issuing more queries, you can try something like
baseRequest.copy().query("sql").execute()?dynaxis commentedon Dec 13, 2021
@zhicwu I hope to create and close a client around every call to a method where actual queries are performed. That is, the reference is required to close the client as a clean up.
My intention is to pick a new ClickHouse node for every incoming HTTP request for the purpose of load balancing, where possibly multiple queries are performed. For that, I need to create a new client for each incoming HTTP request.
If you are uncomfortable w/ making getClient public, it's enough to have a public method for closing the associated client from a ClickHouseRequest.
I guess you may consider a request's holding a client is an implementation detail, but at least to me, it seems a detail that is unlikely to change.
enqueue commentedon Dec 13, 2021
Building, Testing
Thanks @zhicwu for helping me to get the compile / tests running again. For reference:
clickhouse-jdbcdirectory.~/.m2/toolchains.xmllike thismvn -Drelease clean verifyDatabaseMetaData
I created issue #778 to review the use. I think that there aren't many clients relying on this information, but we should not make stuff up.
zhicwu commentedon Dec 14, 2021
Thanks for the explanation. Are those multiple queries issued in same thread or separated ones? This is an example for the former case. The latter one can be tackled with chained Futures in a similar way. IMO, it's safer and easier to close the client once from outside, instead of passing the responsibility to request(s). It sounds like soon we'll need to maintain a reference counter to ensure the client can be only closed after all relevant requests completed. Maybe it's the similar reason of why in JDBC
Statementdoes not have a method to returnConnection, even we know they have connection :)3 more items I can think of for the discussion:
load balancing - ClickHouseCluster is probably a bad example, but I hope it helps you understand that instead of connecting to a specific ClickHouseNode, you can connect to a function to get one. I'll re-write the class in 0.3.3 but let please feel free to share your thoughts.
closing client - as you probably noticed, calling
client.close()may not close underlying connection. This really depends on 2 things: 1) whether we want to reuse connection(see here); 2) how we manage underlying connections(e.g.HttpURLConnectionandHttpClientuse different ways).use
clickhouse-http-client- if you think the API inclickhouse-clientcreates unnecessary overheads, you may consider to skip that and useclickhouse-http-client. I'm not joking, but if you're seeking more control for better performance, you may want to create http connection directly and pipe ClickHouse response to your caller.dynaxis commentedon Dec 14, 2021
@zhicwu I got your point on not wanting to expose the client reference held in a request. Actually the method performing multiple queries is like a Spring MVC handler (I'm currently using Micronaut), and I wanted to inject
ClickHouseRequestinto the method and to avoid situations where the handler's not properly cleaning up the relevantClickHouseClient.So I'm turning to another implementation strategy where the
ClickHouseClientis held separately fromClickHouseRequestand properly closed after completion of handlers, using AOP mechanism.I have a few points to make on the items you listed above:
load balancing
I'm in the course of leveraging
ClickHouseClusterin my code. Initially it was a bit cumbersome to create one from a configuration strings, and the fact that it is stateful (meaning used for tracking if each node is healty or not) bothered me a bit. But in conclusion, I now think the design is reasonable as a low level API.closing client
As I made a point in my previous comment in this issue,
AbstractClienttries very hard to make organized uses of underlying connection with an RW lock, but it seems flawed (?). While a request is about to be sent, the underlying connection might still be closed abruptly by another thread invokingconnectto another node. So a client seems designed thread-safe, but in an important case of sharing it around and connecting to different nodes, it actually is not.I'm not sure if we really need to reuse the same client instance when connecting to a different node. To me, it's more natural to have request objects hold their connection. In that case, it might be a bit weird to call it a request. Maybe better named "connection". But I might be missing some important points here.
I like the idea of programming against a common client interface. For instance, I might write another fully async client and it can be easily switched into.
zhicwu commentedon Dec 29, 2021
Sorry for the inconvinence at your end. I hope we can refine the API in 0.3.3 and mitigate situation like this.
In 0.3.2 I removed public modifier of the class as it's buggy and incomplete. I'll rewrite it in 0.3.3 and need to test against multiple nodes(same cluster or not).
Very true. To be honest, I'm still struggling with whether a client should be able to connect to different nodes. In a cluster environment, each time you call
client.execute, it may pick a different node for execution, some times for load balancing, some times for fail-over, regardless if you're using the exact same request object.The simpler the better. Let me see what I can do in 0.3.3 - maybe support both scenarios?