Skip to content

Hand out the checkpoint when the source is what died - #141

Merged
tonytonycoder11 merged 2 commits into
mainfrom
fix/a-dying-source-still-hands-out-its-token
Aug 11, 2026
Merged

Hand out the checkpoint when the source is what died#141
tonytonycoder11 merged 2 commits into
mainfrom
fix/a-dying-source-still-hands-out-its-token

Conversation

@tonytonycoder11

Copy link
Copy Markdown
Contributor

The CI run on main after #140 went red in three jobs. Two of them are the same defect, the third is
the runner.

The ingest loses the token in the one case the token is for

Build (JDK 21) and Client contract (Linux native) both failed the shared contract case an ingest
killed partway resumes from its token
, on assertNotNull(token, "no checkpoint was ever handed out").

The source is a flow that dies partway on purpose. When it threw, the exception propagated straight out
of ingest's coroutineScope, which cancelled the batches that were still in flight. If the first
batch happened to be one of them, nothing had advanced the acknowledged prefix, and the run reported no
checkpoint at all — so the caller of a two-hundred-million-point load that was killed at four hundred
thousand starts again from zero. Which batches were in flight is a matter of which request came back
first, which is why the same commit passed on the pull request the day before and why the gRPC run of
the same case passed in the failing build.

ingest already refuses to throw a batch failure where it happens, and the comment on sendBatch
gives the reason: cancelling a sibling mid-request turns a point the server accepted into a point no
token counts. A source failure had never been given the same treatment. It is now recorded, the queue
is closed, the workers drain, and it is thrown once the last onCheckpoint has run.

The new unit test holds the first batch in flight past the source's death, so the old behaviour fails
it deterministically rather than once in a while under load — 11 tests in IngestTest, green with the
fix and red without it.

The macOS runner's TLS hiccup

Client contract (macOS native) failed in nine seconds on curl: (60) SSL certificate problem: self signed certificate, downloading the Qdrant release. The certificate is fine; the runner's handshake is
not, intermittently. There was no retry, so a hiccup lasting a second costs the job. Three attempts
with a growing pause, and the failure names what could not be downloaded. -k is not the fix: it would
trade a flake for a job that runs whatever answers the name.

Checked locally

:kdrant-core:jvmTest, ktlintCheck, detekt and a macosArm64 compile of the changed common code.
The rest is on CI here.

An ingest whose source threw left the batches that were still in flight to be
cancelled where they stood, because the exception propagated straight out of the
coroutineScope. Whether the run reported any checkpoint at all then came down to
which request happened to come back first, and a source that died early enough
could leave the caller with no token whatsoever: the case a resume token exists
for was the one case it was missing.

The source's failure is now recorded and thrown after the workers have drained,
which is what a batch failure already did and for the same reason. A batch
cancelled after the server accepted it is a point the collection holds and no
token counts, and re-sending an acknowledged point is free while skipping an
unacknowledged one is silent data loss.

This is what failed "an ingest killed partway resumes from its token" on the JVM
REST run in CI: the assertion that a checkpoint was handed out at all. The
linuxX64 run of the same suite failed in the same build with its case names
truncated out of the console log, and the gRPC run of the same case passed,
which is the shape a timing race has. The new unit test holds a batch in flight
past the source's death, so the old behaviour fails it every time rather than
now and then on a loaded runner.
The hosted runner intermittently fails the TLS handshake against the release
host with "self signed certificate", on a certificate that verifies from
everywhere else and verifies from the runner on the next attempt. There was no
retry, so a hiccup lasting one second red-lined the whole job in nine.

Three attempts with a growing pause, and the failure message names what could
not be downloaded. Verification stays on: -k would trade a flake for a job that
runs whatever answers the name.
@tonytonycoder11
tonytonycoder11 requested a review from a team as a code owner August 11, 2026 22:15
@trueup-by-nacode-studios

Copy link
Copy Markdown

This pull request adds no capability the code did not already have.

What I could not follow

3 calls resolve to a name whose type is written nowhere, so what they reach is not established here, and 2 more are in tests and benchmarks, which this report does not judge. Across everything it read that is 5 of 204, 2%.

  • queue.send(IngestBatch(index++, offset, buffer))kdrant-core/src/commonMain/kotlin/dev/kdrant/Ingest.kt:173
  • queue.send(IngestBatch(index, offset, buffer))kdrant-core/src/commonMain/kotlin/dev/kdrant/Ingest.kt:181
  • pending.remove(nextExpected)kdrant-core/src/commonMain/kotlin/dev/kdrant/Ingest.kt:279

If one of those reaches the network or starts a process, this report does not show it.

4 files read · 3131 ms · against 63c1168 · engine 1.4.1

@tonytonycoder11 tonytonycoder11 moved this to In progress in Kdrant Aug 11, 2026
@tonytonycoder11
tonytonycoder11 merged commit d1f9bb7 into main Aug 11, 2026
12 checks passed
@tonytonycoder11
tonytonycoder11 deleted the fix/a-dying-source-still-hands-out-its-token branch August 11, 2026 22:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: In progress

Development

Successfully merging this pull request may close these issues.

1 participant