Skip to content

markdown export: convert a location set instead of whole shards - #92

Merged
tamnd merged 1 commit into
mainfrom
locations-source
Aug 10, 2026
Merged

markdown export: convert a location set instead of whole shards#92
tamnd merged 1 commit into
mainfrom
locations-source

Conversation

@tamnd

@tamnd tamnd commented Aug 10, 2026

Copy link
Copy Markdown
Owner

Closes the second ship gate box on #67.

The gap

The recovery pass in notes/Spec/2114/03-acquisition-and-crawl.md ends with a set of index locations for the pages we are missing, and nothing turned that set into a Markdown Parquet dataset. fetch --batch streams the records but only as text or JSON, and convert has neither --lang nor --dedup-digest. The only way to finish the pass was markdown export --shards over every shard those pages happen to live in. For a few thousand pages scattered across a few hundred WARC files that is on the order of a thousand times the bytes the pages are worth.

What this adds

markdown export --locations <file|-> reads the location JSONL that columnar locations and index emit, and converts exactly those records with coalesced ranged GETs.

ccrawl columnar locations --crawl CC-MAIN-2026-30 --lang vie --status 200 -o jsonl \
  | ccrawl markdown export --locations - --lang vie --dedup-digest --push=false --out ./md

Everything after the fetch is the pipeline a shard export already uses: the same extractor, the same language filter, the same digest dedup, the same schema, the same ledger and resume. That is deliberate, since a recovery pass that produced a slightly different corpus from an export of the same pages would not be a recovery of anything. The pack now takes a recordSource rather than a reader, and the WARC path and the location path share every stage after that.

New flags on markdown export:

Flag Default Does
--locations (off) Convert the records in this JSONL location stream, - for stdin
--part-size 50000 Locations per parquet part
--gap 1 MiB Coalesce records closer together than this into one ranged read
--max-span 16 MiB Cap on one coalesced ranged read

A part is to a location run what a shard is to a full export: the unit that gets one parquet file, one ledger entry, and one digest dedup set. The stream is cut in order, so the same input cuts the same way every time and a ledger from an interrupted run still means what it said.

A location that will not fetch is skipped rather than failing the part. A recovery pass runs against an index that can disagree with the archive, and a run that dies on the first disagreement never finishes.

--locations bypasses --shards, --source-kind, and the manifest fetch, and it is a usage error with the wet extractor, since WET files have no record offsets to point at.

warc_bytes on a location run is what the ranged reads actually pulled off the wire, holes between coalesced records included, rather than a shard size. That is the number worth publishing here, because comparing it against the shards those records live in is the whole argument for the flag.

Tests

  • TestPackLocationsConvertsOnlyWhatWasAskedFor: six of twelve records in a served WARC, asserting the filler records never appear, that the six coalesce into fewer requests than one per record, and that warc_bytes lands between zero and the whole file.
  • TestPackLocationsAppliesLangAndDedup: the same location set with and without --dedup-digest and --lang, checking the drop count and that the kept rows are all the requested language.
  • TestPackLocationsSurvivesABadLocation: one location pointing at a file that is not there, and the part still comes back with the records that were.
  • TestReadLocationParts and TestReadLocationPartsErrors: the cut into parts including the short tail, order preservation, and the four ways the flag is used wrongly.

gofmt, go vet, go test -race ./..., golangci-lint run, and scripts/docs-drift.sh are all clean. Documented in reference/markdown.md and reference/cli.md.

A recovery pass knows which pages it is missing. It gets them out of a
columnar query as index locations, and until now there was nothing that
turned those locations into a Markdown Parquet dataset. `fetch --batch`
streams records and `convert` has no language filter or dedup, so the
only way to finish the pass was to export the whole shards those pages
live in, which for a few thousand scattered pages is roughly a thousand
times the bytes the pages are worth.

`markdown export --locations` reads exactly the records the stream
points at, with coalesced ranged GETs, and runs the same pipeline over
them: same extractor, same language filter, same digest dedup, same
schema. Only the source changes, which is why the pack now takes a
recordSource rather than a reader, and why the WARC path and the
location path share everything after the fetch.

A part is to a location run what a shard is to a full export: the unit
that gets one parquet file, one ledger entry, and one digest dedup set.
`--part-size` cuts the stream in order, so an interrupted run resumes
where the ledger says it stopped. A location that will not fetch is
skipped rather than failing the part, because a recovery pass runs
against an index that can disagree with the archive.

warc_bytes reports what the ranged reads actually pulled off the wire,
holes included, rather than a shard size. Comparing it against the size
of the shards those records live in is the whole argument for the flag.
@tamnd tamnd added this to the v0.8.0 milestone Aug 10, 2026
@tamnd tamnd added priority: P1 Corpus work, needed by the gao program type: feature New capability area: pipeline Publish and export pipelines labels Aug 10, 2026
@tamnd
tamnd merged commit 1dc0cd0 into main Aug 10, 2026
8 checks passed
@tamnd
tamnd deleted the locations-source branch August 10, 2026 12:00
@github-actions github-actions Bot mentioned this pull request Aug 10, 2026
7 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: pipeline Publish and export pipelines priority: P1 Corpus work, needed by the gao program type: feature New capability

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant