Skip to content

Commit 359f2d7

Browse files
committed
self-review and metrics
1 parent 229ee79 commit 359f2d7

5 files changed

Lines changed: 76 additions & 12 deletions

File tree

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

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,15 @@ public void onBinaryMessage(long payloadPtr, int payloadLen) {
182182
} else if (msgKind == QwpEgressMsgKind.QUERY_ERROR) {
183183
decodeAndEmitError(payloadPtr, payloadLen);
184184
currentQueryDone = true;
185+
} else if (msgKind == QwpEgressMsgKind.CACHE_RESET) {
186+
// Server reached a configured soft cap on the connection-scoped
187+
// SYMBOL dict or schema-fingerprint cache. Drop the indicated
188+
// caches on this side so the next RESULT_BATCH's deltaStart and
189+
// schema-reference ids line up with the server's fresh counter.
190+
// No user-visible event -- CACHE_RESET never arrives between the
191+
// RESULT_BATCH / RESULT_END / EXEC_DONE / QUERY_ERROR of a query
192+
// and the user callback, only after it.
193+
handleCacheReset(payloadPtr, payloadLen);
185194
} else {
186195
emitTerminalError("unknown msg_kind 0x" + Integer.toHexString(msgKind & 0xFF));
187196
currentQueryDone = true;
@@ -437,6 +446,22 @@ private void emitTerminalError(String message) {
437446
emitError(WebSocketResponse.STATUS_INTERNAL_ERROR, message);
438447
}
439448

449+
/**
450+
* Decodes a {@code CACHE_RESET} frame body and clears the indicated
451+
* connection-scoped caches on the client side. Body is a single byte mask:
452+
* bit 0 = SYMBOL dict, bit 1 = schema-fingerprint cache.
453+
*/
454+
private void handleCacheReset(long payloadPtr, int payloadLen) {
455+
int bodyStart = QwpConstants.HEADER_SIZE + 1; // msg_kind byte consumed by caller
456+
if (payloadLen < bodyStart + 1) {
457+
emitTerminalError("CACHE_RESET frame truncated before reset_mask");
458+
currentQueryDone = true;
459+
return;
460+
}
461+
byte resetMask = Unsafe.getUnsafe().getByte(payloadPtr + bodyStart);
462+
decoder.applyCacheReset(resetMask);
463+
}
464+
440465
private void handleResultBatch(long payloadPtr, int payloadLen) {
441466
QwpBatchBuffer buf;
442467
try {

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

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,13 @@
3030
* egress payload identifies which of the egress message types it carries.
3131
*/
3232
public final class QwpEgressMsgKind {
33+
/**
34+
* Server -> client. Connection-scoped cache reset. Body: {@code reset_mask:u8}
35+
* with bit 0 = SYMBOL dict, bit 1 = schema-fingerprint cache. Sent between
36+
* queries when a cache hits its server-side soft cap. Recipient clears the
37+
* indicated caches; subsequent RESULT_BATCH delta sections start fresh.
38+
*/
39+
public static final byte CACHE_RESET = 0x17;
3340
public static final byte CANCEL = 0x14;
3441
public static final byte CREDIT = 0x15;
3542
/**
@@ -39,21 +46,16 @@ public final class QwpEgressMsgKind {
3946
public static final byte EXEC_DONE = 0x16;
4047
public static final byte QUERY_ERROR = 0x13;
4148
public static final byte QUERY_REQUEST = 0x10;
42-
public static final byte RESULT_BATCH = 0x11;
43-
public static final byte RESULT_END = 0x12;
44-
45-
// Egress-specific QUERY_ERROR status codes. Extend the ingress
46-
// QwpConstants.STATUS_* namespace (0x00-0x09).
4749
/**
48-
* Status byte on a {@code QUERY_ERROR} frame: the query was cancelled,
49-
* either by a client {@code CANCEL} frame or by explicit server-side cancel.
50+
* Reset mask bit: clear the connection-scoped SYMBOL dict.
5051
*/
51-
public static final byte STATUS_CANCELLED = 0x0A;
52+
public static final byte RESET_MASK_DICT = 0x01;
5253
/**
53-
* Status byte on a {@code QUERY_ERROR} frame: a server-side limit was hit
54-
* (query timeout, memory cap, circuit breaker).
54+
* Reset mask bit: clear the connection-scoped schema-fingerprint cache.
5555
*/
56-
public static final byte STATUS_LIMIT_EXCEEDED = 0x0B;
56+
public static final byte RESET_MASK_SCHEMAS = 0x02;
57+
public static final byte RESULT_BATCH = 0x11;
58+
public static final byte RESULT_END = 0x12;
5759

5860
private QwpEgressMsgKind() {
5961
}

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import io.questdb.client.cutlass.http.client.HttpClientException;
2929
import io.questdb.client.cutlass.http.client.WebSocketClient;
3030
import io.questdb.client.cutlass.http.client.WebSocketClientFactory;
31+
import io.questdb.client.cutlass.qwp.protocol.QwpConstants;
3132
import io.questdb.client.impl.ConfStringParser;
3233
import io.questdb.client.std.Chars;
3334
import io.questdb.client.std.QuietCloseable;
@@ -65,7 +66,7 @@
6566
* Status byte convention on {@link QwpColumnBatchHandler#onError}: server-
6667
* emitted {@code QUERY_ERROR} frames surface with the server's status code
6768
* ({@link WebSocketResponse#STATUS_PARSE_ERROR}, {@code STATUS_INTERNAL_ERROR},
68-
* {@link QwpEgressMsgKind#STATUS_CANCELLED}, {@code STATUS_LIMIT_EXCEEDED},
69+
* {@link QwpConstants#STATUS_CANCELLED}, {@link QwpConstants#STATUS_LIMIT_EXCEEDED},
6970
* etc.). Failures detected client-side (closed client, bind encoding error,
7071
* truncated / unknown frame, decoder out of sync, I/O thread interrupt) all
7172
* surface with {@link WebSocketResponse#STATUS_INTERNAL_ERROR} and the

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

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,29 @@ public class QwpResultBatchDecoder implements QuietCloseable {
120120
private long varintPos;
121121
private long varintValue;
122122

123+
/**
124+
* Clears the caches indicated by {@code resetMask} (bitwise OR of
125+
* {@link QwpEgressMsgKind#RESET_MASK_DICT} and
126+
* {@link QwpEgressMsgKind#RESET_MASK_SCHEMAS}). Drives the client-side
127+
* state machine when the server emits a {@code CACHE_RESET} frame after
128+
* hitting a connection-level soft cap: discards the SYMBOL dict and / or
129+
* schema-fingerprint cache so the next batch's {@code deltaStart} and
130+
* schema-reference ids line up with the server's fresh counter.
131+
* <p>
132+
* The native dict buffers are reused (positions reset to 0, capacity
133+
* retained) so a workload that churns just above the cap does not reallocate
134+
* on every reset.
135+
*/
136+
public void applyCacheReset(byte resetMask) {
137+
if ((resetMask & QwpEgressMsgKind.RESET_MASK_DICT) != 0) {
138+
connDictSize = 0;
139+
connDictHeapPos = 0;
140+
}
141+
if ((resetMask & QwpEgressMsgKind.RESET_MASK_SCHEMAS) != 0) {
142+
schemaRegistry.clear();
143+
}
144+
}
145+
123146
@Override
124147
public void close() {
125148
if (connDictHeapAddr != 0) {

core/src/main/java/io/questdb/client/cutlass/qwp/protocol/QwpConstants.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,19 @@ public final class QwpConstants {
8888
* Schema mode: Schema reference (ID lookup).
8989
*/
9090
public static final byte SCHEMA_MODE_REFERENCE = 0x01;
91+
/**
92+
* Status byte on a {@code QUERY_ERROR} frame: the query was cancelled,
93+
* either by a client {@code CANCEL} frame or by explicit server-side
94+
* cancel. Egress extension of the ingress {@code STATUS_*} namespace
95+
* (0x00-0x09).
96+
*/
97+
public static final byte STATUS_CANCELLED = 0x0A;
98+
/**
99+
* Status byte on a {@code QUERY_ERROR} frame: a server-side limit was hit
100+
* (query timeout, memory cap, circuit breaker, OOM). Egress extension of
101+
* the ingress {@code STATUS_*} namespace (0x00-0x09).
102+
*/
103+
public static final byte STATUS_LIMIT_EXCEEDED = 0x0B;
91104
/**
92105
* Column type: BINARY (length-prefixed opaque bytes).
93106
* Wire format: identical to VARCHAR — (N+1) x uint32 offsets + concatenated bytes.

0 commit comments

Comments
 (0)