Skip to content

Commit 707f8b2

Browse files
committed
wip 26
1 parent 00751f1 commit 707f8b2

2 files changed

Lines changed: 70 additions & 0 deletions

File tree

core/src/main/java/io/questdb/client/cutlass/http/client/WebSocketClient.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,10 @@ public abstract class WebSocketClient implements QuietCloseable {
112112
// FLAG_ZSTD in every frame, which is the authoritative signal.
113113
private String qwpAcceptEncoding;
114114
private String qwpClientId;
115+
// Client-requested per-batch row cap advertised via X-QWP-Max-Batch-Rows.
116+
// 0 means "omit the header" (server uses its default cap). Server may clamp
117+
// down to its own hard limit.
118+
private int qwpMaxBatchRows;
115119
private int qwpMaxVersion = 1;
116120
// Receive buffer (native memory)
117121
private long recvBufPtr;
@@ -399,6 +403,18 @@ public void setQwpClientId(String clientId) {
399403
this.qwpClientId = clientId;
400404
}
401405

406+
/**
407+
* Sets the client's preferred per-batch row cap, sent in the
408+
* {@code X-QWP-Max-Batch-Rows} upgrade header. {@code 0} (the default)
409+
* omits the header entirely and the server uses its own cap. Positive
410+
* values ask the server to flush batches sooner (lower time-to-first-row
411+
* for streaming consumers, at the cost of more per-batch overhead); the
412+
* server clamps down to its own maximum.
413+
*/
414+
public void setQwpMaxBatchRows(int maxBatchRows) {
415+
this.qwpMaxBatchRows = maxBatchRows;
416+
}
417+
402418
/**
403419
* Sets the maximum QWP version this client supports, sent in the X-QWP-Max-Version upgrade header.
404420
*/
@@ -497,6 +513,11 @@ public void upgrade(CharSequence path, int timeout, CharSequence authorizationHe
497513
sendBuffer.putAscii(qwpAcceptEncoding);
498514
sendBuffer.putAscii("\r\n");
499515
}
516+
if (qwpMaxBatchRows > 0) {
517+
sendBuffer.putAscii("X-QWP-Max-Batch-Rows: ");
518+
sendBuffer.putAscii(Integer.toString(qwpMaxBatchRows));
519+
sendBuffer.putAscii("\r\n");
520+
}
500521
if (authorizationHeader != null) {
501522
sendBuffer.putAscii("Authorization: ");
502523
sendBuffer.putAscii(authorizationHeader);

core/src/main/java/io/questdb/client/cutlass/qwp/client/QwpQueryClient.java

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,14 @@ public class QwpQueryClient implements QuietCloseable {
5252

5353
public static final String DEFAULT_ENDPOINT_PATH = "/read/v1";
5454
public static final int DEFAULT_WS_PORT = 9000;
55+
/**
56+
* Hard ceiling on {@link #withMaxBatchRows}. Matches the client decoder's
57+
* own {@code MAX_ROWS_PER_BATCH} safety cap so a user cannot ask for a
58+
* per-batch row count the decoder would itself refuse. The server enforces
59+
* its own (typically smaller) cap independently; this is just the client's
60+
* sanity bound.
61+
*/
62+
public static final int MAX_BATCH_ROWS_UPPER_BOUND = 1_048_576;
5563
public static final int QWP_MAX_VERSION = 1;
5664
private static final int DEFAULT_IO_BUFFER_POOL_SIZE = 4;
5765
private static final Logger LOG = LoggerFactory.getLogger(QwpQueryClient.class);
@@ -81,6 +89,13 @@ public class QwpQueryClient implements QuietCloseable {
8189
// payload before it parks, and the client auto-replenishes by the size of
8290
// each batch as the user releases it.
8391
private long initialCreditBytes;
92+
// Client preference for server-side per-batch row cap. 0 means "unset",
93+
// server uses its default. Set via {@code max_batch_rows=N} in the
94+
// connection string or {@link #withMaxBatchRows}. Smaller values give
95+
// streaming consumers earlier access to the first rows at the cost of
96+
// more per-batch overhead; larger values amortise fixed costs over more
97+
// rows. Server may clamp down to its own hard cap.
98+
private int maxBatchRows;
8499
// Volatile so a cancel() call from a thread other than the one that ran
85100
// connect() sees the published reference (and a concurrent null-out from
86101
// close() is observed without a stale-reference race). The thread-safety
@@ -160,6 +175,7 @@ public static QwpQueryClient fromConfig(CharSequence configurationString) {
160175
// zstd opt-in.
161176
String compression = "raw";
162177
int compressionLevel = 3;
178+
int maxBatchRows = 0; // 0 = omit header, server uses its default
163179

164180
while (ConfStringParser.hasNext(configurationString, pos)) {
165181
pos = ConfStringParser.nextKey(configurationString, pos, sink);
@@ -223,6 +239,17 @@ public static QwpQueryClient fromConfig(CharSequence configurationString) {
223239
throw new IllegalArgumentException("compression_level must be in [1, 22]");
224240
}
225241
break;
242+
case "max_batch_rows":
243+
try {
244+
maxBatchRows = Integer.parseInt(value);
245+
} catch (NumberFormatException e) {
246+
throw new IllegalArgumentException("invalid max_batch_rows: " + value);
247+
}
248+
if (maxBatchRows < 1 || maxBatchRows > MAX_BATCH_ROWS_UPPER_BOUND) {
249+
throw new IllegalArgumentException(
250+
"max_batch_rows must be in [1, " + MAX_BATCH_ROWS_UPPER_BOUND + "]");
251+
}
252+
break;
226253
default:
227254
throw new IllegalArgumentException("unknown configuration key: " + key);
228255
}
@@ -236,6 +263,7 @@ public static QwpQueryClient fromConfig(CharSequence configurationString) {
236263
.withCompression(compression, compressionLevel);
237264
if (auth != null) client.withAuthorization(auth);
238265
if (cid != null) client.withClientId(cid);
266+
if (maxBatchRows > 0) client.withMaxBatchRows(maxBatchRows);
239267
return client;
240268
}
241269

@@ -331,6 +359,7 @@ public void connect() {
331359
webSocketClient.setQwpMaxVersion(QWP_MAX_VERSION);
332360
webSocketClient.setQwpClientId(clientId != null ? clientId : defaultClientId());
333361
webSocketClient.setQwpAcceptEncoding(buildAcceptEncodingHeader());
362+
webSocketClient.setQwpMaxBatchRows(maxBatchRows);
334363
webSocketClient.connect(host, port);
335364
webSocketClient.upgrade(endpointPath, authorizationHeader);
336365
negotiatedQwpVersion = webSocketClient.getServerQwpVersion();
@@ -505,6 +534,26 @@ public QwpQueryClient withInitialCredit(long bytes) {
505534
return this;
506535
}
507536

537+
/**
538+
* Asks the server to cap each {@code RESULT_BATCH} at {@code rows} rows.
539+
* Useful for latency-sensitive streaming consumers that want to start
540+
* processing the first row as soon as possible -- a smaller cap flushes
541+
* the first batch sooner, at the cost of more per-batch overhead (WS
542+
* header, send syscall, schema-reference decode). The server clamps down
543+
* to its own hard limit; a value of {@code 0} (default) omits the header
544+
* and the server uses its own cap.
545+
* <p>
546+
* Must be called before {@link #connect}.
547+
*/
548+
public QwpQueryClient withMaxBatchRows(int rows) {
549+
if (rows < 1 || rows > MAX_BATCH_ROWS_UPPER_BOUND) {
550+
throw new IllegalArgumentException(
551+
"max_batch_rows must be in [1, " + MAX_BATCH_ROWS_UPPER_BOUND + "]");
552+
}
553+
this.maxBatchRows = rows;
554+
return this;
555+
}
556+
508557
private static String defaultClientId() {
509558
return "questdb-java-egress/1.0.0";
510559
}

0 commit comments

Comments
 (0)