| name | langfuse-migration-deep-dive |
| description | Execute complex Langfuse migrations including data migration and platform changes.
Use when migrating from other observability platforms, moving between Langfuse instances,
or performing major infrastructure migrations.
Trigger with phrases like "langfuse migration", "migrate to langfuse",
"langfuse data migration", "langfuse platform migration", "switch to langfuse".
|
| allowed-tools | Read, Write, Edit, Bash(npm:*) |
| version | 1.0.0 |
| license | MIT |
| author | Jeremy Longshore <jeremy@intentsolutions.io> |
Langfuse Migration Deep Dive
Overview
Comprehensive guide for complex migrations to or between Langfuse instances.
Prerequisites
- Understanding of source and target systems
- Database access (for data migrations)
- Downtime window planned (if needed)
- Rollback plan prepared
Migration Scenarios
| Scenario | Complexity | Downtime | Data Loss Risk |
|---|
| Cloud to Cloud (same) | Low | None | None |
| Self-hosted to Cloud | Medium | Minutes | Low |
| Cloud to Self-hosted | Medium | Minutes | Low |
| Other platform to Langfuse | High | Hours | Medium |
| SDK version upgrade | Low | None | None |
Instructions
Scenario 1: Migrate from Cloud to Self-Hosted
Step 1: Export Data from Cloud
import { Langfuse } from "langfuse";
import fs from "fs/promises";
async function exportCloudData() {
const langfuse = new Langfuse({
publicKey: process.env.LANGFUSE_CLOUD_PUBLIC_KEY!,
secretKey: process.env.LANGFUSE_CLOUD_SECRET_KEY!,
baseUrl: "https://cloud.langfuse.com",
});
const exportDir = `export-${Date.now()}`;
await fs.mkdir(exportDir, { recursive: true });
console.log("Exporting traces...");
let page = 1;
let totalTraces = 0;
while (true) {
const traces = await langfuse.fetchTraces({
limit: 100,
page,
});
if (traces.data.length === 0) break;
await fs.(
,
.(traces., , )
);
totalTraces += traces..;
page++;
( (r, ));
}
.();
.();
.();
datasets = langfuse.({});
fs.(
,
.(datasets., , )
);
exportDir;
}
Step 2: Set Up Self-Hosted Instance
version: "3.8"
services:
langfuse:
image: langfuse/langfuse:latest
ports:
- "3000:3000"
environment:
- DATABASE_URL=postgresql://postgres:${DB_PASSWORD}@db:5432/langfuse
- NEXTAUTH_SECRET=${NEXTAUTH_SECRET}
- NEXTAUTH_URL=${LANGFUSE_URL}
- SALT=${LANGFUSE_SALT}
- ENCRYPTION_KEY=${ENCRYPTION_KEY}
depends_on:
db:
condition: service_healthy
db:
image: postgres:15-alpine
environment:
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=${DB_PASSWORD}
- POSTGRES_DB=langfuse
volumes:
- langfuse-db:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 5s
retries: 5
volumes:
langfuse-db:
Step 3: Import Data to Self-Hosted
import { Langfuse } from "langfuse";
import fs from "fs/promises";
import path from "path";
async function importToSelfHosted(exportDir: string) {
const langfuse = new Langfuse({
publicKey: process.env.LANGFUSE_SELF_HOSTED_PUBLIC_KEY!,
secretKey: process.env.LANGFUSE_SELF_HOSTED_SECRET_KEY!,
baseUrl: process.env.LANGFUSE_SELF_HOSTED_URL!,
});
const files = await fs.readdir(exportDir);
const traceFiles = files.filter((f) => f.startsWith("traces-"));
for (const file of traceFiles) {
const content = await fs.readFile(path.join(exportDir, file), "utf-8");
const traces = JSON.parse(content);
( traceData traces) {
trace = langfuse.({
: traceData.,
: traceData.,
: traceData.,
: traceData.,
: traceData.,
: {
...traceData.,
: ,
: traceData.,
},
});
}
langfuse.();
.();
}
}
Scenario 2: Migrate from LangSmith to Langfuse
Step 1: Create Adapter for LangSmith Data
interface LangSmithRun {
id: string;
name: string;
run_type: "chain" | "llm" | "tool";
inputs: any;
outputs: any;
start_time: string;
end_time: string;
extra?: {
metadata?: Record<string, any>;
};
child_runs?: LangSmithRun[];
}
interface LangfuseTraceData {
name: string;
input: any;
output: any;
metadata: Record<string, any>;
spans: LangfuseSpanData[];
generations: LangfuseGenerationData[];
}
interface LangfuseSpanData {
name: string;
input: any;
output: any;
startTime: Date;
endTime: Date;
}
{
: ;
: ;
: ;
: ;
: ;
: ;
?: { : ; : };
}
(): {
: [] = [];
: [] = [];
() {
(r. === ) {
generations.({
: r.,
: r.?.?. || ,
: r.,
: r.,
: (r.),
: (r.),
: r.?.?.,
});
} {
spans.({
: r.,
: r.,
: r.,
: (r.),
: (r.),
});
}
( child r. || []) {
(child, depth + );
}
}
(run);
{
: run.,
: run.,
: run.,
: {
...run.?.,
: ,
: run.,
},
spans,
generations,
};
}
Step 2: Bulk Import Converted Data
import { Langfuse } from "langfuse";
async function migrateFromLangSmith(runs: LangSmithRun[]) {
const langfuse = new Langfuse();
let migrated = 0;
let failed = 0;
for (const run of runs) {
try {
const converted = convertLangSmithRun(run);
const trace = langfuse.trace({
name: converted.name,
input: converted.input,
output: converted.output,
metadata: converted.metadata,
});
for (const span of converted.spans) {
const s = trace.span({
name: span.name,
input: span.input,
startTime: span.startTime,
});
s.end({
output: span.output,
: span.,
});
}
( gen converted.) {
g = trace.({
: gen.,
: gen.,
: gen.,
: gen.,
});
g.({
: gen.,
: gen.,
: gen.,
});
}
migrated++;
} (error) {
.(, error);
failed++;
}
(migrated % === ) {
langfuse.();
.();
}
}
langfuse.();
{ migrated, failed };
}
Scenario 3: Zero-Downtime SDK Migration
class DualWriteLangfuse {
private oldClient: any;
private newClient: Langfuse;
private writeToOld: boolean = true;
private writeToNew: boolean = true;
constructor(config: {
oldConfig: any;
newConfig: ConstructorParameters<typeof Langfuse>[0];
}) {
this.oldClient = createOldClient(config.oldConfig);
this.newClient = new Langfuse(config.newConfig);
}
trace(params: any) {
const traces: any[] = [];
if (this.writeToOld) {
try {
traces.push({ type: "old", trace: this.oldClient.(params) });
} (error) {
.(, error);
}
}
(.) {
{
traces.({ : , : ..(params) });
} (error) {
.(, error);
}
}
(traces[]?. || {}, {
: {
(prop === || prop === ) {
{
( { trace } traces) {
trace[prop]?.(...args);
}
target[prop]?.(...args);
};
}
target[prop];
},
});
}
() {
. = options.;
. = options.;
}
() {
.([
. && ..?.(),
. && ..(),
]);
}
}
Step 4: Validation and Verification
interface MigrationValidation {
source: {
traceCount: number;
generationCount: number;
dateRange: { start: Date; end: Date };
};
target: {
traceCount: number;
generationCount: number;
dateRange: { start: Date; end: Date };
};
discrepancies: string[];
}
async function validateMigration(
sourceClient: any,
targetClient: Langfuse
): Promise<MigrationValidation> {
const discrepancies: string[] = [];
const sourceTraces = await sourceClient.fetchTraces({ limit: 1 });
const sourceCount = sourceTraces.totalCount || sourceTraces.data.length;
const targetTraces = await targetClient.({ : });
targetCount = targetTraces. || targetTraces..;
countDiff = .(sourceCount - targetCount);
(countDiff > sourceCount * ) {
discrepancies.(
);
}
sampleTraces = sourceClient.({ : });
( trace sampleTraces.) {
targetTrace = targetClient.({
: { : { : trace. } },
});
(targetTrace.. === ) {
discrepancies.();
}
}
{
: {
: sourceCount,
: ,
: { : (), : () },
},
: {
: targetCount,
: ,
: { : (), : () },
},
discrepancies,
};
}
Migration Checklist
| Phase | Task | Status |
|---|
| Planning | | |
| Inventory source data | [ ] |
| Plan downtime window | [ ] |
| Create rollback plan | [ ] |
| Notify stakeholders | [ ] |
| Execution | | |
| Export source data | [ ] |
| Set up target system | [ ] |
| Import data | [ ] |
| Update application config | [ ] |
| Validation | | |
| Verify trace counts | [ ] |
| Spot check data quality | [ ] |
| Test new integration | [ ] |
| Monitor for errors | [ ] |
| Cleanup | | |
| Remove old config | [ ] |
| Archive source data | [ ] |
| Update documentation | [ ] |
Error Handling
| Issue | Cause | Solution |
|---|
| Data loss | Incomplete export | Re-run with pagination |
| Duplicate traces | Re-import | Dedupe by originalId |
| Missing metadata | Format mismatch | Update adapter |
| Performance issues | Large import | Use batch processing |
Rollback Plan
async function rollback() {
process.env.LANGFUSE_PUBLIC_KEY = process.env.OLD_LANGFUSE_PUBLIC_KEY;
process.env.LANGFUSE_SECRET_KEY = process.env.OLD_LANGFUSE_SECRET_KEY;
process.env.LANGFUSE_HOST = process.env.OLD_LANGFUSE_HOST;
console.log("Rollback complete. Restart application to apply.");
await sendSlackNotification("Langfuse migration rolled back");
}
Resources
Next Steps
Migration complete. Return to langfuse-install-auth for new project setup.