Skip to content

bulkload-db: retry chunk uploads and fail loud on incomplete loads#24

Merged
kevinschaper merged 1 commit into
mainfrom
fix/bulkload-fail-loud
Jun 9, 2026
Merged

bulkload-db: retry chunk uploads and fail loud on incomplete loads#24
kevinschaper merged 1 commit into
mainfrom
fix/bulkload-fail-loud

Conversation

@kevinschaper

Copy link
Copy Markdown
Contributor

Problem

bulkload_duckdb silently swallowed per-chunk upload failures. A chunk that timed out (or returned a 5xx) was caught, logged, and counted as 0 — but the load still finished and reported success with a short document count. The CLI then printed Successfully committed <N> documents and exited 0.

In a Monarch KG release build this shipped a Solr entity core with only ~728k of ~1.46M documents (and the association core similarly short) while the Jenkins job passed as SUCCESS. The downstream symptom was get_entity() returning None for genes/diseases that were present in the same release's graph — because their chunk had timed out during indexing and was never retried or flagged.

Root cause in the logs:

Processing 1462594 rows ... with 12 parallel workers
Error processing DuckDB chunk at offset 0: ('Connection aborted.', TimeoutError('timed out'))
... (many chunks time out) ...
Progress: 562,594/1,462,594 documents loaded
DuckDB upload complete!   # <- still "success", exit 0

Fix

  • _upload_docs_to_solr: retries transient failures (timeouts, connection resets, 5xx) with exponential backoff. Re-POSTing a chunk is idempotent because every doc carries a unique id, so a retry overwrites rather than duplicates — safe even when a prior attempt timed out client-side but Solr had already indexed the batch. After exhausting retries it raises instead of returning 0.
  • bulkload_duckdb: tracks failed chunks and raises unless total_loaded == total_rows; the outer handler re-raises rather than returning 0, so the CLI exits non-zero on any incomplete load.

Validation

Against a local Solr with the patched loader:

  • Full load of 1,462,594 rows → committed exactly 1,462,594, exit 0, zero retries needed at the default 4 workers.
  • Fail-loud: pointed at an unreachable Solr → retried with backoff, then RuntimeError: Incomplete Solr load ... uploaded 0/172,957 rows ... Refusing to report success, exit 1.
  • Subsequently used to build a full Monarch release index (entity 1,462,594 / association 15,211,571 / sssom 1,276,312) — all cores complete, counts matched.

Builds on the streaming bulkload_duckdb already on main (#22 / 1a9195a).

bulkload_duckdb silently swallowed per-chunk upload failures: a chunk that
timed out (or returned 5xx) yielded 0 and the load still reported success
with a short document count. In a Monarch KG release build this shipped a
Solr entity core with ~half its documents while the job exited 0.

- _upload_docs_to_solr retries transient failures (timeouts, connection
  resets, 5xx) with exponential backoff. Re-POSTing is idempotent because
  each doc has a unique id, so a retry overwrites rather than duplicates --
  safe even when a prior attempt timed out client-side but Solr had already
  indexed the batch.
- After exhausting retries it raises instead of returning 0.
- bulkload_duckdb tracks failed chunks and raises unless total_loaded ==
  total_rows; the outer handler re-raises rather than returning 0, so the
  CLI exits non-zero on any incomplete load.
@kevinschaper
kevinschaper merged commit b4d92aa into main Jun 9, 2026
2 checks passed
kevinschaper added a commit that referenced this pull request Jun 9, 2026
The version field was never advanced past 0.2.0, so even with a working
publish workflow a release would build 0.2.0 and collide with the existing
PyPI file. Set it to 0.2.3 (next free version; 0.2.1/0.2.2 tags exist but
never published) so the release carries the loader fix (#24) and the
publish-workflow fix.
kevinschaper added a commit that referenced this pull request Jun 9, 2026
…release (#25)

* ci(pypi-publish): bump Python to 3.11 and modernize actions

The publish job pinned Python 3.8 and let snok/install-poetry pull the
latest Poetry, which is now 2.4.1 and dropped Python 3.8 support -- so
'Install Poetry' failed before building, and v0.2.1 and v0.2.2 never
reached PyPI. Bump to Python 3.11 and refresh the deprecated actions
(checkout v2->v4, setup-python v2->v5, gh-action-pypi-publish
v1.2.2->release/v1).

* bump version to 0.2.3 for release

The version field was never advanced past 0.2.0, so even with a working
publish workflow a release would build 0.2.0 and collide with the existing
PyPI file. Set it to 0.2.3 (next free version; 0.2.1/0.2.2 tags exist but
never published) so the release carries the loader fix (#24) and the
publish-workflow fix.
kevinschaper added a commit to monarch-initiative/monarch-ingest that referenced this pull request Jun 9, 2026
Pulls in the retry + fail-loud bulkload fix (linkml/linkml-solr#24,
released in 0.2.3). uv.lock entry updated surgically — 0.2.3's dependency
set is identical to 0.2.0 (the release only changed loader internals, the
publish workflow, and the version), so a full re-resolve wasn't needed
(and is currently blocked by an unrelated koza[grape]/ensmallen pin).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant