Skip to content

Commit 54e21f8

Browse files
glasstigerclaude
andcommitted
Delete dead recovery symbol-walk methods
Production recovery runs one ordered fold (SegmentRing.analyzeRecovery -> MmapSegment.scanRecovery -> RecoveredFrameAnalysis) and seeds the producer through CursorSendEngine.addRecoveredSymbolsTo. The older per-metric walk -- collectReplaySymbolsAbove, maxSymbolDeltaEnd and maxSymbolDeltaStart on MmapSegment and SegmentRing, plus the CursorSendEngine.collectReplaySymbolsAbove wrapper -- had no production caller and diverged from the fold: it lacked the full-dict gap reset, the unacked-gap distinction and commit-boundary checkpointing, so the tests routing through it verified an algorithm production never runs. Delete all seven methods and the now-orphaned RecoveredFrameAnalysis.appendDecodedSymbols helper, then rewire the two tests that reached the walk (RecoveredFrameAnalysisTest, CursorWebSocketSendLoopOrphanTailTest) to the production addRecoveredSymbolsTo path. That is behavior-preserving -- same coverage, same decoded symbols -- and stronger, since the tests now drive the real seeding method. Drop the imports the deletion left unused. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 042c123 commit 54e21f8

6 files changed

Lines changed: 20 additions & 413 deletions

File tree

core/src/main/java/io/questdb/client/cutlass/qwp/client/sf/cursor/CursorSendEngine.java

Lines changed: 3 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
2525
package io.questdb.client.cutlass.qwp.client.sf.cursor;
2626

2727
import io.questdb.client.cutlass.qwp.client.GlobalSymbolDictionary;
28-
import io.questdb.client.cutlass.qwp.protocol.QwpConstants;
2928
import io.questdb.client.std.Compat;
3029
import io.questdb.client.std.Files;
3130
import io.questdb.client.std.FilesFacade;
@@ -375,14 +374,14 @@ private CursorSendEngine(String sfDir, long segmentSizeBytes, SegmentManager man
375374
// .symbol-dict no longer holds, resuming would re-use it. The walk is
376375
// bounded to recoveredCommitBoundaryFsn so the aborted orphan-deferred
377376
// tail -- retired without ever being transmitted -- does not inflate
378-
// this and over-reject an otherwise-recoverable slot. maxSymbolDeltaEnd
377+
// this and over-reject an otherwise-recoverable slot. maxDeltaEnd()
379378
// returns 0 when no such frame carries a symbol, yielding -1 here.
380379
// Computed before the I/O loop or producer append; single-threaded.
381380
this.recoveredMaxSymbolId = recoveredFrameAnalysisInProgress.maxDeltaEnd() - 1L;
382381
// Full-dict-fallback recovery. When the persisted .symbol-dict is a
383382
// SUBSET of the ids the surviving frames reference
384383
// (recoveredMaxSymbolId >= its size) YET every such frame is
385-
// self-sufficient (maxSymbolDeltaStart == 0 -- a full-dict frame that
384+
// self-sufficient (maxDeltaStart() == 0 -- a full-dict frame that
386385
// re-registers its dictionary from id 0), the slot was written in
387386
// full-dict fallback: the dictionary never opened when writing, so no
388387
// side-file exists and this recovery opened a FRESH EMPTY one. Those
@@ -392,7 +391,7 @@ private CursorSendEngine(String sfDir, long segmentSizeBytes, SegmentManager man
392391
// was written. Without this the sender's seed-time guard would treat the
393392
// empty dictionary as a host-crash tear and brick build(), even though
394393
// the orphan drainer drains the same frames fine. A genuine torn DELTA
395-
// dictionary keeps a frame with deltaStart > 0 (maxSymbolDeltaStart > 0)
394+
// dictionary keeps a frame with deltaStart > 0 (maxDeltaStart() > 0)
396395
// and is NOT discarded here: it still fails clean at seed time, since
397396
// the ids its delta frames reference cannot be rebuilt without the lost
398397
// dictionary. The recoveredMaxSymbolId >= size guard means this never
@@ -741,56 +740,6 @@ public synchronized void close() {
741740
}
742741
}
743742

744-
/**
745-
* Rebuilds, from the surviving frames' OWN delta sections, the symbols the upcoming
746-
* replay will register ABOVE {@code baseline}, appending them to {@code out} in
747-
* ascending id order. Returns the coverage the replay establishes (one past the highest
748-
* id it registers), or {@code -1} when the frames have a genuine GAP above the baseline.
749-
* <p>
750-
* The producer seeds its dictionary from the persisted {@code .symbol-dict} and THEN
751-
* from this, so it is fed the same entries, in the same order, that the send loop's
752-
* {@code accumulateSentDict} will feed its mirror from as those very frames replay. The
753-
* producer's delta baseline and the loop's mirror coverage therefore land on the same
754-
* number by construction, which is the invariant the torn-dictionary guard rests on.
755-
* <p>
756-
* This is what makes a slot whose dictionary was torn -- or lost outright -- still
757-
* recoverable when the surviving frames define the ids themselves (they carry the
758-
* symbols in their own deltas; it is why the orphan drainer can drain such a slot). Only
759-
* a real gap, where the ids were introduced by frames since acked and trimmed away, is
760-
* unrecoverable -- and that is precisely when this returns -1.
761-
* <p>
762-
* Bounded above by {@link #recoveredCommitBoundaryFsn} like {@link #recoveredMaxSymbolId}:
763-
* frames past it are the aborted orphan-deferred tail, retired without ever being
764-
* transmitted, so their ids never reach a server and must not inflate the baseline.
765-
*/
766-
public long collectReplaySymbolsAbove(int baseline, ObjList<String> out) {
767-
if (recoveredFrameAnalysis != null
768-
&& recoveredFrameAnalysis.baseline() == baseline) {
769-
recoveredFrameAnalysis.appendDecodedSymbols(out);
770-
return recoveredFrameAnalysis.coverage();
771-
}
772-
if (ring == null) {
773-
return baseline;
774-
}
775-
return ring.collectReplaySymbolsAbove(
776-
QwpConstants.MAGIC_MESSAGE,
777-
QwpConstants.HEADER_OFFSET_FLAGS,
778-
QwpConstants.FLAG_DELTA_SYMBOL_DICT,
779-
QwpConstants.HEADER_SIZE,
780-
// Walk from the LOWEST frame still on disk, not from ackedFsn+1. An acked frame
781-
// is not trimmed the instant it is acked -- trim drops whole SEALED segments --
782-
// so the symbols it registered are usually still sitting right there, and they
783-
// are exactly the ids the replay set's deltas start above. Skipping them threw
784-
// away the only surviving copy of those symbols and condemned a slot that had
785-
// everything it needed. A slot whose registering frames really HAVE been
786-
// trimmed still reports the gap, because then no frame on disk holds them.
787-
0L,
788-
recoveredCommitBoundaryFsn,
789-
baseline,
790-
out
791-
);
792-
}
793-
794743
/**
795744
* Decodes the cached recovery suffix directly into the producer's global
796745
* dictionary. Recovery always builds the analysis with the persisted

core/src/main/java/io/questdb/client/cutlass/qwp/client/sf/cursor/MmapSegment.java

Lines changed: 0 additions & 257 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,9 @@
2828
import io.questdb.client.std.Files;
2929
import io.questdb.client.std.FilesFacade;
3030
import io.questdb.client.std.MemoryTag;
31-
import io.questdb.client.std.ObjList;
3231
import io.questdb.client.std.Os;
3332
import io.questdb.client.std.QuietCloseable;
3433
import io.questdb.client.std.Unsafe;
35-
import io.questdb.client.std.str.Utf8s;
3634
import org.slf4j.Logger;
3735
import org.slf4j.LoggerFactory;
3836

@@ -588,121 +586,6 @@ public long findLastFrameFsnWithoutPayloadFlag(int flagsOffset, int flagMask, in
588586
return best;
589587
}
590588

591-
/**
592-
* Rebuilds, from the frames' own delta sections, the symbols a replay of
593-
* {@code [minFsnInclusive .. maxFsnInclusive]} would register ABOVE {@code coverage},
594-
* appending them to {@code out} in ascending id order. Returns the coverage the walk
595-
* establishes (one past the highest id registered), or {@code -1} when a frame's delta
596-
* starts ABOVE the coverage reached so far.
597-
* <p>
598-
* This is the recovery-time twin of {@code CursorWebSocketSendLoop.accumulateSentDict}:
599-
* it visits the same frames, in the same FSN order, and extracts exactly the entries
600-
* that method will add to the send loop's mirror as those frames go back on the wire.
601-
* The producer's dictionary is seeded from it so the producer's delta baseline and the
602-
* loop's mirror coverage stay in lockstep BY CONSTRUCTION -- which is what makes a slot
603-
* whose {@code .symbol-dict} was torn (or lost entirely) recoverable, as long as the
604-
* surviving frames define the ids themselves.
605-
* <p>
606-
* A {@code -1} return is a genuine GAP: the ids between the coverage and the frame's
607-
* delta start were introduced by frames that were acked and trimmed away, so they lived
608-
* ONLY in the lost dictionary and cannot be rebuilt from anything. That is the one case
609-
* the data really must be resent, and it is exactly the condition the send loop's
610-
* torn-dictionary guard ({@code deltaStart > sentDictCount}) would trip on at replay --
611-
* so the two agree, rather than the producer failing on slots the drainer drains fine.
612-
* <p>
613-
* Same frame layout and FSN bound as {@link #maxSymbolDeltaEnd}.
614-
*/
615-
public long collectReplaySymbolsAbove(
616-
int headerMagic,
617-
int flagsOffset,
618-
int flagDeltaMask,
619-
int qwpHeaderSize,
620-
long minFsnInclusive,
621-
long maxFsnInclusive,
622-
long coverage,
623-
ObjList<String> out
624-
) {
625-
long off = HEADER_SIZE;
626-
long frames = frameCount;
627-
for (long i = 0; i < frames; i++) {
628-
long fsn = baseSeq + i;
629-
int payloadLen = Unsafe.getUnsafe().getInt(mmapAddress + off + 4);
630-
long payload = mmapAddress + off + FRAME_HEADER_SIZE;
631-
if (fsn >= minFsnInclusive
632-
&& fsn <= maxFsnInclusive
633-
&& payloadLen >= qwpHeaderSize
634-
&& payloadLen > flagsOffset
635-
&& Unsafe.getUnsafe().getInt(payload) == headerMagic
636-
&& (Unsafe.getUnsafe().getByte(payload + flagsOffset) & flagDeltaMask) != 0) {
637-
long p = payload + qwpHeaderSize;
638-
long limit = payload + payloadLen;
639-
long deltaStart = 0L;
640-
int shift = 0;
641-
while (p < limit) {
642-
byte b = Unsafe.getUnsafe().getByte(p++);
643-
deltaStart |= (long) (b & 0x7F) << shift;
644-
if ((b & 0x80) == 0) {
645-
break;
646-
}
647-
shift += 7;
648-
if (shift > 35) {
649-
break; // corrupt run; recovered frames are CRC-valid, so defensive only
650-
}
651-
}
652-
long deltaCount = 0L;
653-
shift = 0;
654-
while (p < limit) {
655-
byte b = Unsafe.getUnsafe().getByte(p++);
656-
deltaCount |= (long) (b & 0x7F) << shift;
657-
if ((b & 0x80) == 0) {
658-
break;
659-
}
660-
shift += 7;
661-
if (shift > 35) {
662-
break;
663-
}
664-
}
665-
if (deltaStart > coverage) {
666-
return -1L; // gap: ids [coverage, deltaStart) are unrecoverable
667-
}
668-
// Walk this frame's [len varint][utf8] entries. Ids below the coverage we
669-
// already hold are skipped (a replayed frame legitimately overlaps the tip);
670-
// ids at or above it extend the dictionary.
671-
long id = deltaStart;
672-
for (long k = 0; k < deltaCount; k++, id++) {
673-
long len = 0L;
674-
shift = 0;
675-
while (p < limit) {
676-
byte b = Unsafe.getUnsafe().getByte(p++);
677-
len |= (long) (b & 0x7F) << shift;
678-
if ((b & 0x80) == 0) {
679-
break;
680-
}
681-
shift += 7;
682-
if (shift > 35) {
683-
break;
684-
}
685-
}
686-
if (p + len > limit) {
687-
// Malformed entry region. Do NOT guess: report it as unrecoverable
688-
// so the caller fails clean rather than seeding a shifted dictionary.
689-
return -1L;
690-
}
691-
if (id >= coverage) {
692-
out.add(Utf8s.stringFromUtf8Bytes(p, p + len));
693-
}
694-
p += len;
695-
}
696-
long deltaEnd = deltaStart + deltaCount;
697-
if (deltaEnd > coverage) {
698-
coverage = deltaEnd;
699-
}
700-
}
701-
off += FRAME_HEADER_SIZE + payloadLen;
702-
}
703-
return coverage;
704-
}
705-
706589
/**
707590
* Feeds every recovered frame in this segment to the engine's single-pass
708591
* recovery fold. Segments are visited oldest-first by {@link SegmentRing}.
@@ -717,146 +600,6 @@ void scanRecovery(RecoveredFrameAnalysis analysis) {
717600
}
718601
}
719602

720-
/**
721-
* Highest {@code deltaStart + deltaCount} (one past the highest symbol id) any
722-
* delta-flagged frame in this segment carries, or {@code 0} only when NO
723-
* delta-flagged frame is present. A frame with {@code deltaCount == 0} (a commit
724-
* or a symbol-reusing frame -- the encoder always sets the delta flag) still
725-
* contributes its {@code deltaStart}, i.e. the producer's baseline at encode
726-
* time, NOT 0. That is deliberate: it anchors the torn-dict guard at that
727-
* baseline so a dictionary torn below it is detected -- a conservative
728-
* over-strand (it may over-reject a frame whose rows reference only surviving
729-
* ids), never an under-strand that could silently shift the dense id map.
730-
* Read-only walk over the recovered frames, used
731-
* once at recovery to detect a persisted {@code .symbol-dict} torn (host crash,
732-
* out-of-order page loss) below the ids the surviving frames still reference: if
733-
* the max here reaches at or beyond the recovered dictionary size, a resuming
734-
* producer -- seeded from the shorter dictionary -- would re-use ids the frames
735-
* already define. Only frames at or below {@code maxFsnInclusive} count: frames
736-
* above it are the aborted orphan-deferred tail, which {@code trySendOne} retires
737-
* without ever transmitting, so their ids must not inflate the result (a resuming
738-
* producer never reuses them on the wire). The frame layout mirrors
739-
* {@link #findLastFrameFsnWithoutPayloadFlag}: the QWP message header
740-
* ({@code qwpHeaderSize} bytes) is followed by the delta section
741-
* {@code [deltaStart varint][deltaCount varint]...}.
742-
*
743-
* @param headerMagic the QWP message magic identifying a well-formed frame
744-
* @param flagsOffset byte offset of the flags field within the QWP header
745-
* @param flagDeltaMask the FLAG_DELTA_SYMBOL_DICT bit
746-
* @param qwpHeaderSize the QWP message header size (delta section starts past it)
747-
* @param maxFsnInclusive highest frame FSN to consider; frames above it are the
748-
* retired orphan-deferred tail -- pass
749-
* {@code recoveredCommitBoundaryFsn}
750-
*/
751-
public long maxSymbolDeltaEnd(int headerMagic, int flagsOffset, int flagDeltaMask, int qwpHeaderSize, long maxFsnInclusive) {
752-
long maxEnd = 0L;
753-
long off = HEADER_SIZE;
754-
long frames = frameCount;
755-
for (long i = 0; i < frames; i++) {
756-
long fsn = baseSeq + i;
757-
int payloadLen = Unsafe.getUnsafe().getInt(mmapAddress + off + 4);
758-
long payload = mmapAddress + off + FRAME_HEADER_SIZE;
759-
// Skip the orphan-deferred tail (fsn > maxFsnInclusive): retired, never
760-
// sent, so its ids must not count. off still advances below.
761-
if (fsn <= maxFsnInclusive
762-
&& payloadLen >= qwpHeaderSize
763-
&& payloadLen > flagsOffset
764-
&& Unsafe.getUnsafe().getInt(payload) == headerMagic
765-
&& (Unsafe.getUnsafe().getByte(payload + flagsOffset) & flagDeltaMask) != 0) {
766-
long p = payload + qwpHeaderSize;
767-
long limit = payload + payloadLen;
768-
long deltaStart = 0L;
769-
int shift = 0;
770-
while (p < limit) {
771-
byte b = Unsafe.getUnsafe().getByte(p++);
772-
deltaStart |= (long) (b & 0x7F) << shift;
773-
if ((b & 0x80) == 0) {
774-
break;
775-
}
776-
shift += 7;
777-
if (shift > 35) {
778-
break; // corrupt run; recovered frames are CRC-valid, so defensive only
779-
}
780-
}
781-
long deltaCount = 0L;
782-
shift = 0;
783-
while (p < limit) {
784-
byte b = Unsafe.getUnsafe().getByte(p++);
785-
deltaCount |= (long) (b & 0x7F) << shift;
786-
if ((b & 0x80) == 0) {
787-
break;
788-
}
789-
shift += 7;
790-
if (shift > 35) {
791-
break;
792-
}
793-
}
794-
long end = deltaStart + deltaCount;
795-
if (end > maxEnd) {
796-
maxEnd = end;
797-
}
798-
}
799-
off += FRAME_HEADER_SIZE + payloadLen;
800-
}
801-
return maxEnd;
802-
}
803-
804-
/**
805-
* Highest {@code deltaStart} any delta-flagged frame at or below
806-
* {@code maxFsnInclusive} carries, or {@code 0} when NO delta-flagged frame is
807-
* present. A result of {@code 0} means every surviving symbol-bearing frame is
808-
* SELF-SUFFICIENT -- it re-registers its dictionary from id 0 (a full-dict
809-
* frame), so the slot replays with no persisted dictionary at all; a result
810-
* {@code > 0} means at least one frame is a non-self-sufficient delta whose ids
811-
* depend on a prior frame's (or the persisted dictionary's) registrations.
812-
* {@link CursorSendEngine} pairs this with {@link #maxSymbolDeltaEnd} at
813-
* recovery: when the persisted {@code .symbol-dict} is a subset of the ids the
814-
* frames reference BUT this returns {@code 0}, the slot was written in full-dict
815-
* fallback (the dictionary never opened when writing) and recovers in full-dict
816-
* mode instead of failing clean; a torn DELTA dictionary has a {@code
817-
* deltaStart > 0} frame and still fails clean. Same read-only frame walk and
818-
* layout as {@link #maxSymbolDeltaEnd}; only frames at or below {@code
819-
* maxFsnInclusive} count (the retired orphan-deferred tail is excluded).
820-
*/
821-
public long maxSymbolDeltaStart(int headerMagic, int flagsOffset, int flagDeltaMask, int qwpHeaderSize, long maxFsnInclusive) {
822-
long maxStart = 0L;
823-
long off = HEADER_SIZE;
824-
long frames = frameCount;
825-
for (long i = 0; i < frames; i++) {
826-
long fsn = baseSeq + i;
827-
int payloadLen = Unsafe.getUnsafe().getInt(mmapAddress + off + 4);
828-
long payload = mmapAddress + off + FRAME_HEADER_SIZE;
829-
// Skip the orphan-deferred tail (fsn > maxFsnInclusive): retired, never
830-
// sent, so its baseline must not count. off still advances below.
831-
if (fsn <= maxFsnInclusive
832-
&& payloadLen >= qwpHeaderSize
833-
&& payloadLen > flagsOffset
834-
&& Unsafe.getUnsafe().getInt(payload) == headerMagic
835-
&& (Unsafe.getUnsafe().getByte(payload + flagsOffset) & flagDeltaMask) != 0) {
836-
long p = payload + qwpHeaderSize;
837-
long limit = payload + payloadLen;
838-
long deltaStart = 0L;
839-
int shift = 0;
840-
while (p < limit) {
841-
byte b = Unsafe.getUnsafe().getByte(p++);
842-
deltaStart |= (long) (b & 0x7F) << shift;
843-
if ((b & 0x80) == 0) {
844-
break;
845-
}
846-
shift += 7;
847-
if (shift > 35) {
848-
break; // corrupt run; recovered frames are CRC-valid, so defensive only
849-
}
850-
}
851-
if (deltaStart > maxStart) {
852-
maxStart = deltaStart;
853-
}
854-
}
855-
off += FRAME_HEADER_SIZE + payloadLen;
856-
}
857-
return maxStart;
858-
}
859-
860603
/**
861604
* Number of frames written since {@link #create} (or recovered by
862605
* {@link #openExisting}). Used by {@code SegmentRing} to compute

0 commit comments

Comments
 (0)