Skip to content

[client-v2] Multipart request keeps Content-Encoding: lz4 but sends a plain body — LZ4_DECODER_FAILED on ClickHouse 26.8 #3075

Description

@polyglotAI-bot

Description

When client-v2 sends statement parameters in the request body (HTTP_SEND_PARAMS_IN_BODY=true, the multipart path) and client request compression is enabled through HTTP compression (compressClientRequest(true) + useHttpCompression(true)), the client sends a plain (uncompressed) multipart body while still declaring Content-Encoding: lz4 and enable_http_compression=1.

The request is therefore self-contradictory. Servers up to 26.3 ignored Content-Encoding for multipart/form-data requests, so this went unnoticed. ClickHouse 26.8 honours the header and rejects the request:

Code: 618. DB::Exception: LZ4 decompression failed. LZ4F version: 100. Error: ERROR_frameType_unknown. (LZ4_DECODER_FAILED)

HttpTransportTests#testMultiPartRequest fails on current main against clickhouse/clickhouse-server:head. It is green in CI only because the CI matrix does not run these legs against head.

Steps to reproduce

  1. Start clickhouse/clickhouse-server:head (26.8.1.1761).
  2. Build current main (379718d).
  3. Run mvn -B -pl client-v2 -DskipUTs=true -Dit.test=HttpTransportTests#testMultiPartRequest -Dfailsafe.failIfNoSpecifiedTests=false verify.

The third block of the test (the one that combines compressClientRequest(true), useHttpCompression(true) and params in body) fails.

The same failure can be produced without the client, which shows exactly what is sent on the wire:

# fails on 26.8 head, succeeds on 25.3 / 25.8 / 26.3
curl -H 'Content-Encoding: lz4' -F 'query=SELECT 1' 'http://server:8123/?enable_http_compression=1'

# succeeds everywhere (same body, header removed)
curl -F 'query=SELECT 1' 'http://server:8123/?enable_http_compression=1'

Error Log or Exception StackTrace

com.clickhouse.client.api.ServerException: Code: 618. DB::Exception: LZ4 decompression failed.
LZ4F version: 100. Error: ERROR_frameType_unknown. (LZ4_DECODER_FAILED) (version 26.8.1.1761 (official build))
  at com.clickhouse.client.api.internal.HttpAPIClientHelper.readClickHouseError(HttpAPIClientHelper.java:528)
  at com.clickhouse.client.api.internal.HttpAPIClientHelper.readError(HttpAPIClientHelper.java:420)
  at com.clickhouse.client.api.internal.HttpAPIClientHelper.doExecuteRequest(HttpAPIClientHelper.java:782)
  ...
  at com.clickhouse.client.api.Client.queryAll(Client.java:2086)

HttpTransportTests.testMultiPartRequest:1831 » Client Failed to get query response

Expected Behaviour

A multipart request must not announce a content encoding that the body does not use. Because the multipart path deliberately does not compress the body, the request must be sent with no Content-Encoding header, and the query must succeed.

Server evidence for the same request, taken with curl (body is plain text in all cases):

Server multipart + Content-Encoding: lz4 multipart, no Content-Encoding
25.3.14.14 1 (header ignored) 1
25.8.28.1 1 (header ignored) 1
26.3.17.110 1 (header ignored) 1
26.8.1.1761 (head) LZ4_DECODER_FAILED 1

The server behaviour on 26.8 is correct — the client request was always invalid, and the older servers only tolerated it.

Root cause

client-v2/src/main/java/com/clickhouse/client/api/internal/HttpAPIClientHelper.java

The multipart path already knows it cannot compress, but it only neutralises one of the two ways compression is requested.

  • Line 642-645 — the body is attached without wrapRequestEntity(...), so it stays uncompressed:
if (useMultipart) {
    req.setEntity(httpEntity); // multipart doesn't support compression right now
} else {
    req.setEntity(wrapRequestEntity(httpEntity, requestConfig));
}
  • Line 563 — the query parameter form of client compression is removed for multipart:
uriBuilder.removeParameter(ClickHouseHttpProto.QPARAM_DECOMPRESS); // multipart request doesn't support compression yet
  • Line 933-936 (addHeaders) — but the HTTP compression form is not, and the header survives:
if (clientCompression && !appCompressedData) {
    setHeader(req, HttpHeaders.CONTENT_ENCODING, DEFAULT_HTTP_COMPRESSION_ALGO); // "lz4"
}

With useHttpCompression(true), addRequestParams sends enable_http_compression=1 instead of decompress=1, so the removeParameter(QPARAM_DECOMPRESS) guard never applies and the Content-Encoding: lz4 header reaches the server together with a plain body.

The mirror configuration (compressClientRequest(true) + useHttpCompression(false)) is unaffected, because there client compression is expressed as decompress=1, which the line-563 guard does remove.

Suggested fix

Extend the existing multipart guard to the header form: in createRequest, when useMultipart is true, also remove HttpHeaders.CONTENT_ENCODING from the request (or skip setting it in addHeaders when the request will be multipart), so the two mitigations stay in step.

Cases that must keep their current behaviour:

  • non-multipart + compressClientRequest(true) + useHttpCompression(true) — body is really LZ4-framed, the header must stay;
  • multipart + compressServerResponse(true)Accept-Encoding is about the response and must not be removed;
  • appCompressedData(true) — the header is not set by the client in the first place.

A regression test would be the existing third block of HttpTransportTests#testMultiPartRequest, run against a server that honours the header.

Configuration

Client Configuration

Client client = new Client.Builder()
        .addEndpoint(endpoint)
        .setUsername(user).setPassword(password)
        .compressClientRequest(true)
        .useHttpCompression(true)
        .setOption(ClientConfigProperties.HTTP_SEND_PARAMS_IN_BODY.getKey(), "true")
        .build();

client.queryAll("SELECT database, name FROM system.tables WHERE name IN {table_names:Array(String)}", params);

Environment

  • Cloud
  • Client version: 0.10.0-rc1-SNAPSHOT (main, 379718d)
  • Language version: OpenJDK 17.0.18
  • OS: Ubuntu 24.04 (x86_64)

ClickHouse Server

  • ClickHouse Server version: 26.8.1.1761 (clickhouse/clickhouse-server:head); not reproducible on 26.3.17.110, 25.8.28.1, 25.3.14.14
  • Non-default settings: none
  • Tables: none — reproduces with SELECT 1 and with system.tables

Found by automated analysis of client-v2 while working on #3068 / #3069, and verified against live servers of four versions rather than by inspection.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions