Skip to content

Resumable (tus) Uploads für sehr große Dateien - #19

Merged
veniplex merged 1 commit into
mainfrom
claude/ai-module-large-data-processing-oueat5
Jul 18, 2026
Merged

veniplex merged 1 commit into
mainfrom
claude/ai-module-large-data-processing-oueat5

Conversation

@veniplex

Copy link
Copy Markdown
Owner

Überblick

Letzter der zurückgestellten Skalierungs-Bausteine (nach PR #14 Kern, #17 Worker+HNSW, #18 S3+Batch-API): resumable Uploads über das tus-Protokoll. Bei einem Verbindungsabbruch oder Reload während eines sehr großen Uploads läuft dieser am letzten Byte-Offset weiter, statt von vorn zu beginnen. Additiv & opt-by-size — Dateien unter der Schwelle nehmen weiter den unveränderten Direkt-Upload.

Kernidee: tus staged lokal → saveStream finalisiert (treiber-agnostisch)

Statt tus an ein S3-spezifisches Store-Format zu koppeln, schreibt tus in ein lokales Staging-Verzeichnis (@tus/file-store); ein Hintergrund-Job streamt die fertige Datei dann durch storage.saveStream in das konfigurierte Backend (local oder S3) und bekommt dabei Größe + sha256 (für Dedup, Pflicht B). Ein Storage-Pfad, funktioniert mit beiden Treibern.

Änderungen

  • src/app/api/materials/tus/[[...slug]]/route.ts (neu): @tus/server-Endpoint (handleWeb) mit @tus/file-store unter TUS_DIR. Auth pro Request über onIncomingRequest; onUploadCreate prüft Modul-/Ordner-Ownership, Größe + Quota und stempelt die authentifizierte userId in die Metadaten (nicht clientseitig manipulierbar); onUploadFinish reiht den Finalize-Job ein.
  • src/lib/materials/tus-finalize.ts (neu): streamt die Staging-Datei via saveStream in den Storage (local oder S3), registriert das Material, löscht die Staging-Datei.
  • src/lib/materials/ingest.ts (neu): registerUploadedFile — die Sequenz Ordner/Quota/Zip/Dedup/Insert/Embed aus der Upload-Route extrahiert, sodass Direkt-Upload und tus-Finalizer denselben Pfad nutzen (identisches Verhalten). upload/route.ts ruft sie jetzt auf.
  • Jobs: neue Queue finalize-upload + enqueueFinalizeUpload (singletonKey = tus-ID; der Finalizer entfernt die Staging-Datei, Retries sind gefahrlos).
  • Client: upload-client.ts schickt Dateien ≥ 50 MB über tus-js-client (Resume via findPreviousUploads/resumeFromPreviousUpload), kleine Dateien behalten den xhr-Pfad. Die Transport-Wahl ist eine reine, unit-getestete Funktion (upload-transport.ts).
  • Docs: TUS_DIR + Staging-Volume in .env.example, docker-compose.yml (App- und Worker-Service), docs/self-hosting.md.

Datenmodell

Keine Schema-Änderung: tus-file-store (Staging) + pg-boss halten den Zustand persistent — ein unterbrochener Upload lässt sich über Neustarts hinweg fortsetzen, solange das Staging-Volume bleibt.

Tests / Verifikation

  • Neuer Unit-Test src/components/materials/upload-transport.test.ts: Transport-Schwelle (tus ab 50 MB, xhr darunter, tus aus wenn Browser es nicht unterstützt).
  • Statisch: npm run typecheck (0 Fehler), npm run lint (sauber), npm run test (105 bestanden, 3 skipped), npm run build (erfolgreich; die tus-Route ist registriert).
  • Default-Pfad unverändert: Dateien unter 50 MB nehmen exakt den bisherigen xhr-Upload; bestehende Upload-Logik läuft über den geteilten registerUploadedFile-Pfad weiter.

Auf dem Prod-Stand verifizieren: das tus-Protokoll + Resume brauchen einen laufenden Server + Storage. Großen Upload starten, Verbindung mitten drin kappen → Upload läuft am Offset weiter; Material erscheint nach dem Finalize-Job mit korrekter Größe/Hash (local und S3). tus-Staging läuft auch im S3-Mode über lokalen Scratch (ein Durchlauf); ein natives tus-s3-store bleibt spätere Optimierung.

Abschluss der Skalierungs-Add-ons

Damit sind alle geplanten Add-ons (A Worker · B HNSW · C S3 · D Batch-API · E tus) umgesetzt.

🤖 Generated with Claude Code

https://claude.ai/code/session_0191JikNRN8Q2HBtf6fpLXmH


Generated by Claude Code

Add an opt-by-size resumable upload path so a dropped connection or page
reload continues a large upload from the last byte offset instead of
restarting. Additive — files below the threshold keep the existing direct
streaming upload unchanged.

- src/app/api/materials/tus/[[...slug]]/route.ts (new): a @tus/server endpoint
  (handleWeb) with @tus/file-store staging under TUS_DIR. Auth on every request
  via onIncomingRequest; onUploadCreate validates module/folder ownership, size
  and quota and stamps the authenticated userId into the upload metadata;
  onUploadFinish enqueues a finalize job.
- src/lib/materials/tus-finalize.ts (new): streams the staged file through
  storage.saveStream (computing size + sha256, works for local AND S3) then
  registers the material; removes the staging file when done.
- src/lib/materials/ingest.ts (new): registerUploadedFile — the folder/quota/
  zip/dedup/insert/embed sequence extracted from the upload route so the direct
  route and the tus finalizer behave identically. upload/route.ts now calls it.
- jobs: new finalize-upload queue + enqueueFinalizeUpload (singletonKey on the
  tus id; the finalizer removes the staging file so retries are safe).
- client: upload-client.ts routes files >= 50 MB through tus-js-client (resume
  via findPreviousUploads), small files keep the xhr path; shouldUseTus is a
  pure, unit-tested helper (src/components/materials/upload-transport.ts).
- docs: TUS_DIR + staging volume in .env.example, docker-compose.yml,
  docs/self-hosting.md.

No schema change: the tus file-store + pg-boss hold the in-flight state, so an
interrupted upload resumes across restarts as long as the staging volume
persists. tus staging is local even in S3 mode (one scratch pass); a native
tus-s3-store is a later optimization. End-to-end resume is verified on the
production stand; CI covers typecheck/lint/build and the transport helper.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0191JikNRN8Q2HBtf6fpLXmH
@veniplex
veniplex merged commit 77acda0 into main Jul 18, 2026
1 check passed
@veniplex
veniplex deleted the claude/ai-module-large-data-processing-oueat5 branch July 18, 2026 08:19
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.

2 participants