Harvy data archive: Basin
Harvy can stream every local JSON artifact from a scrape job into Basin Pipelines, with a Basin Catalog Iceberg sink so the records can be queried using Basin SQL. Original job files remain in data/; Basin is an additional archive/analytics copy.
1. Create the Basin table
Requires a Cloudflare account with Workers Paid and R2 enabled. From this project, run npx wrangler basin pipelines setup --name harvy_scraped_data and select:
- HTTP endpoint enabled; require authentication if you will expose the stream URL outside the server.
- Load
health/schema.jsonas the structured stream schema. - Basin Catalog / Iceberg sink: active warehouse bucket
harvy-ql, namespacedefault, tablegraph. - Live pipeline:
cbee9eb32e4a4617a81fda117c750c10(harvyql_pipeline), executing:INSERT INTO harvyql_sink SELECT * FROM harvyql_stream;. - Active stream endpoint:
https://e1e54007204f43768769ee613369c85f.ingest.cloudflare.com(harvyql_stream).
Storage Separation: R2 Direct vs. Basin Catalog
R2 and Basin serve distinct storage layers and use separate buckets:
- Raw Scrapes (
harvy-raw): Ephemeral object storage for crawler payloads, raw HTTP responses, and HTML. Lifecycle ruleexpire-raw-crawls-60dautomatically expires objects after 60 days. - Artifacts & Releases (
harvy-artifacts): Permanent object storage for normalized JSON entities, dossier snapshots, and release manifests. Key-value access (get/put). No expiration. - Basin Warehouse (
harvy-ql): Managed exclusively by Basin Pipelines and Basin Catalog (9061d93166998cf39e011a52b369b812_harvy-ql). Stores Apache Iceberg metadata snapshots and columnar Parquet files queryable via Basin SQL. - Rule: Never apply arbitrary S3 lifecycle/TTL rules to the Basin warehouse bucket (
harvy-ql). Iceberg tables manage their own snapshot compaction (1h interval, 128MB compaction enabled); manual object deletion corrupts the Iceberg table metadata.
The R2 Account API token is entered only into the Basin setup flow; do not put it in wrangler.jsonc or the browser. Basin Pipelines has a 5 MB maximum ingestion request. v2/health/basin_storage.py batches below 5 MB and splits oversized JSON artifacts into ordered text chunks. Cloudflare documents 50 GB/month of included SQL transforms and Iceberg sink delivery on Workers Paid; R2 storage/catalog charges also apply. Initial records may take 3–7 minutes to become queryable.
2. Configure Harvy
Set these server-side for Gunicorn (never in frontend JavaScript):
BASIN_INGEST_URL="https://e1e54007204f43768769ee613369c85f.ingest.cloudflare.com"
BASIN_INGEST_TOKEN="<stream-token-if-authentication-is-enabled>"
WEBSHARE_PROXY_URL="<existing Webshare proxy URL>"BASIN_INGEST_TOKEN may be omitted only when the stream endpoint is intentionally unauthenticated. Harvy writes each completed or partial job’s JSON artifacts; if Basin is not configured or a write fails, the local job files are retained and the job status reports the archive state.
3. Backfill and query
After configuring the stream, backfill existing JSON artifacts once:
.venv/bin/python -m health.basin_backfill --root dataThe backfill excludes the bundled WhatsMyName reference dataset. Event IDs are deterministic per file and chunk; if a backfill is repeated, deduplicate by event_id in queries. For very large files, concatenate payload.raw_json_chunk ordered by chunk_index to reconstruct the source JSON text.
Enable the catalog and set WRANGLER_BASIN_SQL_AUTH_TOKEN to the R2 account token in the shell that runs Wrangler, then query:
npx wrangler basin catalog get <bucket-name>
npx wrangler basin sql query "<warehouse-name>" \
"SELECT source, kind, COUNT(*) AS artifacts FROM default.harvy_scraped_data GROUP BY source, kind ORDER BY artifacts DESC"
npx wrangler basin sql query "<warehouse-name>" \
"SELECT artifact_path, collected_at, payload FROM default.harvy_scraped_data WHERE source = 'www.facebook.com' LIMIT 10"source, kind, artifact_path, timestamps, chunk information, and payloads are stored in the table. Treat the warehouse as sensitive: scraped artifacts can contain personal information. Restrict Cloudflare/R2 access and apply the retention/deletion rules required for your use case.