Commit graph

108 commits

Author SHA1 Message Date
Yiorgis Gozadinos
baf8decb27
Show progress for run-batch drains 2026-06-22 12:51:38 +03:00
Yiorgis Gozadinos
b0706204bd
Allow resuming run-batch manifest replay 2026-06-22 12:22:40 +03:00
Yiorgis Gozadinos
68bbf94577
tighten run-batch manifest replay validation 2026-06-22 11:58:32 +03:00
Yiorgis Gozadinos
7251d104c4
Add run-batch manifest replay 2026-06-22 11:39:12 +03:00
Yiorgis Gozadinos
a3b542dba8
Add run-batch dry-run manifest output 2026-06-22 11:33:26 +03:00
Yiorgis Gozadinos
4b573bfebd
Add side-effect-free batch dry-run discovery 2026-06-22 11:33:26 +03:00
Yiorgis Gozadinos
c6514c9df4
compare off-loop work against actual event-loop thread, fix ty 2026-06-22 10:17:02 +03:00
bryan davis
fe954a090e
move cpu bound actions off of main loop 2026-06-18 16:35:48 -05:00
Yiorgis Gozadinos
1e65d36766
Pass fetched FetchResult to ingester metadata providers 2026-06-16 16:37:26 +03:00
Yiorgis Gozadinos
6add2780e8
Make ingester job retry idempotent against a live sibling 2026-06-16 16:05:05 +03:00
Yiorgis Gozadinos
79a4f49387
Stop re-enqueuing permanently-failed ingester documents 2026-06-16 16:03:51 +03:00
Yiorgis Gozadinos
515f42dd5a
Add custom ingester sources via entry points 2026-06-16 10:54:24 +03:00
Yiorgis Gozadinos
7e20b47e98
Widen SQLite ingester queue pool to serve concurrent connections 2026-06-16 09:25:01 +03:00
Yiorgis Gozadinos
fac62cb347
Follow HTTP redirects in the WebDAV source 2026-06-15 09:59:13 +03:00
Yiorgis Gozadinos
cc73a8629a
Wire metadata providers into ingester ingestion 2026-06-15 08:41:26 +03:00
Yiorgis Gozadinos
5722260857
Add metadata-provider discovery for the ingester 2026-06-15 08:05:45 +03:00
Tres Seaver
b78f0ae9ed
feat(ingester): serve control plane under a configurable base path
Add `ingester.api.root_path` so the HTTP control plane (dashboard + API)
can be reverse-proxied behind a sub-path (e.g. /ingester/) on a shared
origin, instead of needing nginx sub_filter URL-rewriting.

- APIConfig.root_path: normalized ('', or single leading slash, no trailing
  slash) via a field_validator; validate_assignment so CLI overrides
  normalize the same way as config-file values.
- Forwarded to FastAPI(root_path=) and uvicorn.Config(root_path=) so
  OpenAPI/docs links are prefix-aware.
- Dashboard route injects a <base href> matching root_path; all dashboard
  fetches are now base-relative, so they resolve under the prefix while
  staying identical at the root.
- `serve --root-path` CLI flag.
- Docs: "Behind a reverse proxy" section with an nginx example.

Closes #431

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 23:02:43 -04:00
Yiorgis Gozadinos
54524a4ea3
Gate ingester control-plane access logs to DEBUG 2026-06-09 12:46:17 +03:00
Yiorgis Gozadinos
4c186b01c7
Add Database and Configuration panels to the ingester dashboard 2026-06-09 12:28:13 +03:00
Yiorgis Gozadinos
c62166b26e
Add /database and /config endpoints to the ingester API 2026-06-09 12:11:33 +03:00
Yiorgis Gozadinos
bd548837e5
Make the ingester worker circuit breaker per-source 2026-06-09 09:41:54 +03:00
Yiorgis Gozadinos
e2e0a8dc1b
Cover the Postgres queue construction paths in CI 2026-06-04 12:21:47 +03:00
Yiorgis Gozadinos
73e8ac2dd6
Build the SQLite queue URL without reparsing the path 2026-06-04 12:14:27 +03:00
Yiorgis Gozadinos
e37d764ab2
Add a docker-compose for integration test services 2026-06-04 11:13:16 +03:00
Yiorgis Gozadinos
1717bd4996
Make the SQLite job claim atomic across processes 2026-06-03 16:57:57 +03:00
Yiorgis Gozadinos
5cc32f111e
Mask the dburi password in queue init/migrate output 2026-06-03 14:42:32 +03:00
Yiorgis Gozadinos
a3cc13230f
test the Postgres queue and document dburi 2026-06-03 14:34:09 +03:00
Yiorgis Gozadinos
44089e5b1f
Support a dburi for the ingester queue (SQLite + Postgres)
Migrate the ingester queue storage from raw aiosqlite to SQLAlchemy Core
async. The backend is chosen by ingester.queue.dburi: a SQLAlchemy async
URL points the queue at a database server, and SQLite remains the default
when unset. The Postgres path claims jobs with FOR UPDATE SKIP LOCKED so
multiple ingester processes can share one queue; SQLite caps the pool to a
single connection to keep the select-then-update claim atomic.
2026-06-03 14:34:09 +03:00
Yiorgis Gozadinos
8afdd46176
skip spurious ingester DELETE when the resource is back on its source 2026-06-03 14:05:53 +03:00
Yiorgis Gozadinos
46747d369a
Add retention window to ingester queue, prune terminal job rows past retention window 2026-06-03 10:38:58 +03:00
Yiorgis Gozadinos
0550e363d4
Merge pull request #394 from mcdonc/perf/max-file-size-validation
perf: add configurable max_file_size to prevent OOM on large files
2026-06-01 18:49:18 +03:00
Chris McDonough
7e4aa8c71f
Add configurable max_file_size to reject oversized files before ingestion
Large files buffered entirely in RAM can OOM workers. Add
max_file_size to source config (default None = no limit).

FS checks stat().st_size before read_bytes(). HTTP and WebDAV issue
a HEAD request before GET when a limit is configured. S3 checks the
size from the existing head_async() call before get_async().

FileTooLargeError is classified as PermanentError so oversized files
go straight to the DLQ instead of retrying.
2026-06-01 18:40:46 +03:00
Yiorgis Gozadinos
774ac7c350
Merge pull request #393 from mcdonc/perf/batch-sync-state-writes
perf: batch sync_state writes during poller sweeps
2026-06-01 18:33:40 +03:00
Yiorgis Gozadinos
2cd97880fd
Replace sync_state batch 5-tuple with a SyncRow NamedTuple 2026-06-01 18:25:15 +03:00
Chris McDonough
a0a247d18a
Batch sync_state writes during poller sweeps
Each discovered file previously triggered a separate sync.upsert()
call with its own lock acquire + SQLite commit (fsync). On a sweep
finding 1,000 files this meant 1,000 individual commits.

Collect sync_state rows into a list during the sweep and flush them
in a single SyncStateRepo.batch_upsert() call at the end — one lock
acquisition, one commit, one fsync.
2026-06-01 18:21:09 +03:00
Chris McDonough
8deac2fee8
Fix constant re-ingestion when server provides no revision header
HTTP, S3, and WebDAV sources all check `revision is not None and
snapshot.get(uri) == revision` to decide UPSERT vs UNCHANGED.  When
a server returns no ETag or Last-Modified, revision is None and the
condition always fails — every sweep emits UPSERT even though the
content hasn't changed.

Now emit UNCHANGED when revision is None and the URI is already
known (has been ingested before).  A first-time discovery with no
revision still correctly emits UPSERT.
2026-06-01 18:14:27 +03:00
Yiorgis Gozadinos
20634376a3
Merge pull request #411 from mcdonc/chore/coverage-gaps
chore: close coverage gaps in cli, filter, registry, and migrations
2026-06-01 18:12:22 +03:00
Yiorgis Gozadinos
c0faf5ecf3
Merge pull request #408 from mcdonc/fix/directory-errors-permanent
fix: classify IsADirectoryError and NotADirectoryError as PermanentError
2026-06-01 18:07:24 +03:00
Yiorgis Gozadinos
c1932e22e5
Strengthen config-load assertions 2026-06-01 18:03:09 +03:00
Chris McDonough
1bf2505093
Improve test coverage for cli, filter, registry, and migrations
These files were not touched by the recent performance and
correctness PRs but had coverage gaps. Adds tests for:

- CLI: serve, queue init/migrate, config loading, cli() entry point
  including MigrationRequiredError exit path
- filter: _default_supported_extensions, __call__ watchfiles callback,
  FileFilter with supported_extensions=None
- registry: resolve_adhoc_fetcher with bucket-less S3 URI
- migrations: pragma no-cover on unreachable schema upgrade path
  (no diff migrations exist until SCHEMA_VERSION > 1)
2026-06-01 18:00:47 +03:00
Chris McDonough
adf03284ff
Classify IsADirectoryError and NotADirectoryError as PermanentError
Both are OSError subclasses caught by the broad timeout/io handler
and classified as transient. Pointing at a directory instead of a
file or a broken path component will never succeed on retry.
2026-06-01 17:59:23 +03:00
Chris McDonough
61ea22527a
Narrow HTTP discover() exception catch to TransportError only
The bare `except Exception` in HTTPSource.discover() silently
swallowed all errors from HEAD requests — including configuration
errors (bad auth, invalid headers) and programming errors (TypeError,
AttributeError) — treating them identically to network failures by
emitting UPSERT with no revision.

Narrow the catch to httpx.TransportError (the umbrella for
ConnectError, TimeoutException, etc.) and add a debug log. Other
exceptions now propagate to the poller's circuit breaker where they
surface as failures instead of being silently retried forever.
2026-06-01 17:57:51 +03:00
Yiorgis Gozadinos
64f2b7b7d2
Merge pull request #405 from mcdonc/fix/run-batch-hang-on-dead-workers
fix: run_batch hangs forever when all workers die
2026-06-01 17:51:49 +03:00
Yiorgis Gozadinos
49cb6e7591
Merge pull request #409 from mcdonc/fix/sync-state-write-crash
fix: sync_state write failure after mark_succeeded should not crash worker
2026-06-01 17:46:46 +03:00
Yiorgis Gozadinos
e9875fc842
Merge pull request #395 from mcdonc/perf/reuse-httpx-clients
perf: reuse httpx.AsyncClient in HTTP and WebDAV sources
2026-06-01 17:38:05 +03:00
Yiorgis Gozadinos
c5814b31bf
Test source clients close after the worker pool stops
Workers share the pollers' Source instances for fetch(), so the httpx
clients must be closed only after the pool has stopped. Guards both
run_batch() and serve() against reintroducing the shutdown-order bug.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 17:29:53 +03:00
Yiorgis Gozadinos
e3ea207358
Merge pull request #396 from mcdonc/perf/stagger-periodic-polls
perf: stagger periodic poll sweeps to avoid thundering herd
2026-06-01 17:23:22 +03:00
Yiorgis Gozadinos
b79bbcaed4
Merge pull request #399 from mcdonc/fix/discover-stat-race
fix: handle file deleted during discover() stat() call
2026-06-01 17:18:47 +03:00
Yiorgis Gozadinos
fda798a746
Merge pull request #391 from mcdonc/perf/event-driven-worker-wakeup
perf: event-driven worker wakeup via asyncio.Condition
2026-06-01 17:06:40 +03:00
Chris McDonough
22ab79c492 Fix shutdown-order bug: close source clients after workers stop
Workers share the same Source instances as pollers and use them for
fetch(). PollerManager.stop() was closing httpx clients before the
worker pool drained, so in-flight fetches during the shutdown grace
hit a closed client.

- Move source closing out of stop() into a separate close_sources()
- Call close_sources() after _stop_pool() in both serve() and
  run_batch()
- Promote aclose() to the Source protocol with no-op defaults for
  FS and S3, removing the hasattr duck-typing
2026-06-01 09:55:39 -04:00