Skip to content

Commit 994d5fd

Browse files
committed
Merge origin/vi_egress: adopt CACHE_RESET, move SERVER_INFO to 0x18
# Conflicts: # core/src/main/java/io/questdb/client/cutlass/qwp/client/QwpEgressMsgKind.java # core/src/main/java/io/questdb/client/cutlass/qwp/client/QwpQueryClient.java
2 parents a81d259 + 359f2d7 commit 994d5fd

5 files changed

Lines changed: 88 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
emitTerminalTransportError("unknown msg_kind 0x" + Integer.toHexString(msgKind & 0xFF));
187196
currentQueryDone = true;
@@ -451,6 +460,22 @@ private void emitTerminalTransportError(String message) {
451460
events.offer(new QueryEvent().asTransportError(WebSocketResponse.STATUS_INTERNAL_ERROR, message));
452461
}
453462

463+
/**
464+
* Decodes a {@code CACHE_RESET} frame body and clears the indicated
465+
* connection-scoped caches on the client side. Body is a single byte mask:
466+
* bit 0 = SYMBOL dict, bit 1 = schema-fingerprint cache.
467+
*/
468+
private void handleCacheReset(long payloadPtr, int payloadLen) {
469+
int bodyStart = QwpConstants.HEADER_SIZE + 1; // msg_kind byte consumed by caller
470+
if (payloadLen < bodyStart + 1) {
471+
emitTerminalTransportError("CACHE_RESET frame truncated before reset_mask");
472+
currentQueryDone = true;
473+
return;
474+
}
475+
byte resetMask = Unsafe.getUnsafe().getByte(payloadPtr + bodyStart);
476+
decoder.applyCacheReset(resetMask);
477+
}
478+
454479
private void handleResultBatch(long payloadPtr, int payloadLen) {
455480
QwpBatchBuffer buf;
456481
try {

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

Lines changed: 18 additions & 12 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,6 +46,14 @@ 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;
49+
/**
50+
* Reset mask bit: clear the connection-scoped SYMBOL dict.
51+
*/
52+
public static final byte RESET_MASK_DICT = 0x01;
53+
/**
54+
* Reset mask bit: clear the connection-scoped schema-fingerprint cache.
55+
*/
56+
public static final byte RESET_MASK_SCHEMAS = 0x02;
4257
public static final byte RESULT_BATCH = 0x11;
4358
public static final byte RESULT_END = 0x12;
4459
/**
@@ -66,19 +81,10 @@ public final class QwpEgressMsgKind {
6681
* Server -> client. Unsolicited frame delivered as the first QWP message
6782
* on every v2 WebSocket connection. Body (little-endian): {@code
6883
* msg_kind:u8, role:u8, epoch:u64, capabilities:u32, server_wall_ns:i64,
69-
* cluster_id:u16_len+utf8, node_id:u16_len+utf8}.
70-
*/
71-
public static final byte SERVER_INFO = 0x17;
72-
/**
73-
* Status byte on a {@code QUERY_ERROR} frame: the query was cancelled,
74-
* either by a client {@code CANCEL} frame or by explicit server-side cancel.
75-
*/
76-
public static final byte STATUS_CANCELLED = 0x0A;
77-
/**
78-
* Status byte on a {@code QUERY_ERROR} frame: a server-side limit was hit
79-
* (query timeout, memory cap, circuit breaker).
84+
* cluster_id:u16_len+utf8, node_id:u16_len+utf8}. The byte-value 0x17 is
85+
* claimed by {@link #CACHE_RESET}; SERVER_INFO lives at 0x18.
8086
*/
81-
public static final byte STATUS_LIMIT_EXCEEDED = 0x0B;
87+
public static final byte SERVER_INFO = 0x18;
8288

8389
private QwpEgressMsgKind() {
8490
}

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,15 @@
8484
* {@link QwpColumnBatchHandler#onError} with the stored status/message. Per-
8585
* query {@code QUERY_ERROR} responses are NOT terminal -- the connection
8686
* remains usable for the next query.
87+
* <p>
88+
* Status byte convention on {@link QwpColumnBatchHandler#onError}: server-
89+
* emitted {@code QUERY_ERROR} frames surface with the server's status code
90+
* ({@link WebSocketResponse#STATUS_PARSE_ERROR}, {@code STATUS_INTERNAL_ERROR},
91+
* {@link QwpConstants#STATUS_CANCELLED}, {@link QwpConstants#STATUS_LIMIT_EXCEEDED},
92+
* etc.). Failures detected client-side (closed client, bind encoding error,
93+
* truncated / unknown frame, decoder out of sync, I/O thread interrupt) all
94+
* surface with {@link WebSocketResponse#STATUS_INTERNAL_ERROR} and the
95+
* specific cause in the message.
8796
*/
8897
public class QwpQueryClient implements QuietCloseable {
8998

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)