-
Notifications
You must be signed in to change notification settings - Fork 3.8k
feat: configurable transport compression strategy #19454
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -21,8 +21,10 @@ | |
|
|
||
| import com.fasterxml.jackson.core.JsonProcessingException; | ||
| import com.fasterxml.jackson.databind.ObjectMapper; | ||
| import com.github.luben.zstd.ZstdOutputStream; | ||
| import com.google.common.collect.ImmutableMap; | ||
| import com.google.common.io.CountingOutputStream; | ||
| import net.jpountz.lz4.LZ4BlockOutputStream; | ||
| import org.apache.druid.client.DirectDruidClient; | ||
| import org.apache.druid.error.DruidException; | ||
| import org.apache.druid.error.ErrorResponse; | ||
|
|
@@ -385,6 +387,36 @@ public interface Writer extends Closeable | |
| void writeResponseEnd() throws IOException; | ||
| } | ||
|
|
||
| /** | ||
| * Picks the best compression encoding the client accepts. Prefers zstd over x-lz4. | ||
| * Returns null if no supported encoding is advertised. | ||
| */ | ||
| @Nullable | ||
| private static String negotiateContentEncoding(@Nullable String acceptEncoding) | ||
| { | ||
| if (acceptEncoding == null) { | ||
| return null; | ||
| } | ||
| if (acceptEncoding.contains("zstd")) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [P2] Honor q=0 when negotiating encodings This substring check treats headers like |
||
| return "zstd"; | ||
| } | ||
| if (acceptEncoding.contains("x-lz4")) { | ||
| return "x-lz4"; | ||
| } | ||
| return null; | ||
| } | ||
|
|
||
| private static OutputStream wrapWithCompressor(OutputStream out, @Nullable String contentEncoding) throws IOException | ||
| { | ||
| if ("zstd".equals(contentEncoding)) { | ||
| return new ZstdOutputStream(out); | ||
| } | ||
| if ("x-lz4".equals(contentEncoding)) { | ||
| return new LZ4BlockOutputStream(out); | ||
| } | ||
| return out; | ||
| } | ||
|
|
||
| public class StreamingHttpResponseAccumulator implements Accumulator<Response, Object>, Closeable | ||
| { | ||
| private final ResponseContext responseContext; | ||
|
|
@@ -442,8 +474,14 @@ public void initialize() | |
|
|
||
| response.setTrailerFields(() -> trailerFields); | ||
|
|
||
| final String contentEncoding = negotiateContentEncoding(request.getHeader("Accept-Encoding")); | ||
| if (contentEncoding != null) { | ||
| response.setHeader("Content-Encoding", contentEncoding); | ||
| } | ||
|
|
||
| try { | ||
| out = new CountingOutputStream(response.getOutputStream()); | ||
| final OutputStream responseOs = response.getOutputStream(); | ||
| out = new CountingOutputStream(wrapWithCompressor(responseOs, contentEncoding)); | ||
| writer = resultsWriter.makeWriter(out); | ||
| } | ||
| catch (IOException e) { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[P1] Decompressor choice races the response headers
The decompressor is selected from responseContentEncoding.get() when the Sequence iterator is created, which can happen immediately after run() returns and before the async HTTP handler has seen the response headers. In that common path this passes an empty encoding and returns the original future, so a later zstd/x-lz4 response is handed to JsonParserIterator still compressed and parsing fails. Defer the encoding lookup until the future completes, e.g. always transform with a function that reads the AtomicReference after handleResponse has set it.