Blob Storage Export#
Overview#
The Blob Storage Export feature lets Langfuse projects continuously export observability data — traces, observations (legacy and enriched), and scores — to external object storage: S3, S3-compatible (MinIO, Cloudflare R2, etc.), Google Cloud Storage, and Azure Blob Storage. Exports run on a recurring schedule (20 min, hourly, daily, or weekly) and can optionally backfill all historical data from the earliest record in the project.
Available on: Cloud Pro (Teams Add-on) and Enterprise; all self-hosted deployments.
Architecture#
Cron (scheduler)
└─► handleBlobStorageIntegrationSchedule
└─► BlobStorageIntegrationProcessingQueue (BullMQ)
└─► handleBlobStorageIntegrationProjectJob (worker)
├─ ClickHouse streaming queries (traces / observations / scores / events)
├─ optional gzip pipeline
└─ StorageService.uploadFileBuffered → S3 / GCS / Azure
Key Source Files#
| File | Role |
|---|---|
handleBlobStorageIntegrationSchedule.ts | Cron handler — queries blob_storage_integrations for due projects, enqueues BullMQ jobs |
handleBlobStorageIntegrationProjectJob.ts | Per-project job handler — time-window calculation, ClickHouse queries, upload, state update |
packages/shared/prisma/schema.prisma | BlobStorageIntegration model + related enums |
packages/shared/src/server/repositories/observations.ts | getObservationsForBlobStorageExport , getEventsForBlobStorageExport |
Data Model#
The BlobStorageIntegration Prisma model (table blob_storage_integrations) is keyed by projectId and stores:
- Storage credentials:
type,bucketName,prefix,accessKeyId,secretAccessKey(encrypted),region,endpoint,forcePathStyle - Scheduling state:
nextSyncAt,lastSyncAt,exportFrequency,enabled - Export configuration:
fileType(JSON/CSV/JSONL — Parquet handling lives upstream in API),exportMode,exportStartDate,exportSource,compressed - Error state:
lastError,lastErrorAt,lastFailureNotificationSentAt
Enums#
| Enum | Values |
|---|---|
BlobStorageIntegrationType | S3, S3_COMPATIBLE, AZURE_BLOB_STORAGE |
BlobStorageIntegrationFileType | JSON, CSV, JSONL |
BlobStorageExportMode | FULL_HISTORY, FROM_TODAY, FROM_CUSTOM_DATE |
AnalyticsIntegrationExportSource | TRACES_OBSERVATIONS, TRACES_OBSERVATIONS_EVENTS, EVENTS |
Scheduler (Cron → Queue)#
handleBlobStorageIntegrationSchedule runs on a recurring cron tick. It queries for all enabled integrations where lastSyncAt IS NULL (never run) or nextSyncAt <= now, then bulk-enqueues jobs into BlobStorageIntegrationProcessingQueue. Jobs are deduplicated by jobId = projectId + lastSyncAt to prevent double-processing the same window .
Per-Project Job: Time-Window Calculation#
Each job in handleBlobStorageIntegrationProjectJob resolves a [minTimestamp, maxTimestamp] window:
minTimestamp — determined by getMinTimestampForExport:
- If
lastSyncAtis set: use it (subsequent runs) - If not set (first run), check
exportMode:FULL_HISTORY: queries ClickHousemin(timestamp)across traces, observations, and scores tablesFROM_TODAY/FROM_CUSTOM_DATE: usesexportStartDateor current time
maxTimestamp — capped at the lesser of :
minTimestamp + frequencyIntervalMs(one frequency period ahead)now - 30 minutes(lag buffer to avoid exporting half-written records)
This per-frequency-period cap is the chunking strategy that makes large backfills manageable — each job processes exactly one time chunk then exits.
Catch-Up Mode#
After a successful export, the job checks whether it has caught up to present-day data :
- Caught up (
maxTimestamp >= now - 30 min): setsnextSyncAt = maxTimestamp + frequencyIntervalMs— normal cadence resumes. - Still behind: sets
nextSyncAt = nowand immediately re-enqueues the next chunk job, so historic backfills advance as fast as the worker can process them without waiting for the cron tick.
Export Sources & Tables#
Depending on exportSource :
exportSource | Tables exported |
|---|---|
TRACES_OBSERVATIONS (legacy) | traces, observations, scores |
TRACES_OBSERVATIONS_EVENTS | traces, observations, observations_v2 (enriched events), scores |
EVENTS | observations_v2 (enriched events), scores |
scores are always included regardless of source. The enriched observations path (observations_v2) uses getEventsForBlobStorageExport with EventsQueryBuilder, which flattens trace attributes directly onto observations for better performance . Cloud projects created on or after 2026-05-20 default to enriched observations automatically.
A per-project env-var (LANGFUSE_BLOB_STORAGE_EXPORT_TRACE_ONLY_PROJECT_IDS) forces legacy trace-only export for specific projects .
File Layout & Formats#
Files are written to: {prefix}{projectId}/{table}/{maxTimestamp}.{ext}[.gz]
Supported formats :
- Parquet (default for new integrations) — binary columnar, compressed by the storage engine; gzip does not apply
- CSV, JSON, JSONL — text formats, optionally gzip-compressed
Upload uses storageService.uploadFileBuffered with 100 MB multipart parts to support files up to ~1 TB (AWS S3's 10,000-part limit). The data stream is piped through a format transform and optionally createGzip() before upload.
Error Handling & Notifications#
On failure :
- Persists
lastError/lastErrorAtto the DB - Re-throws to trigger BullMQ retries (5 attempts, exponential backoff)
- Calls
notifyBlobStorageExportFailedInBackground, which emails project admins — rate-limited byLANGFUSE_BLOB_STORAGE_FAILURE_NOTIFICATION_COOLDOWN_HOURS
ClickHouse Timeouts#
Export queries use LANGFUSE_CLICKHOUSE_DATA_EXPORT_REQUEST_TIMEOUT_MS (default: 3,600,000 ms / 60 min) as the client-side timeout. The ClickHouse client derives max_execution_time = ceil(timeout_ms / 1000) + 5 s as a server-side guard. This default was raised from 10 minutes after TIMEOUT_EXCEEDED errors during large project backfills.
Both export query functions use the FINAL modifier to guarantee deduplication correctness at query time .