Compare commits

...
5 Commits
Author SHA1 Message Date
Marc Klingen 2dd5c8a8aa chore: release v3.97.2 2025-08-12 17:31:22 +02:00
Marc Klingen d17b21f04d chore: specify development node version 2025-08-12 17:30:31 +02:00
Marc KlingenandGitHub c5a0f44bdd fix: fail silently when /api/latest-releases returns invalid schema (#8476)
* fix: fail silently when /api/latest-releases returns invalid schema

* push
2025-08-12 15:18:27 +00:00
Steffen SchmitzandGitHub 011b4912f2 chore: limit traces to trace AMT migration to only write into traces_all_amt (#8458)
* chore: limit traces to trace AMT migration to only write into traces_all_amt

* chore: revert validation changes

* chore: simplify query

* chore: tune both queries

* chore: avoid full aggregation during migration

* chore: handle IO coalescing to skip aggregations fully

* chore: remove obsolete query parts
2025-08-12 14:44:55 +00:00
Steffen SchmitzandGitHub a4d773066f chore: move health check to new traces AMTs (#8473) 2025-08-12 14:44:09 +00:00
9 changed files with 167 additions and 77 deletions
+1 -1
View File
@@ -1 +1 @@
v20
v20.19.2
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "langfuse",
"version": "3.97.1",
"version": "3.97.2",
"author": "engineering@langfuse.com",
"license": "MIT",
"private": true,
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "web",
"version": "3.97.1",
"version": "3.97.2",
"private": true,
"license": "MIT",
"engines": {
+1 -1
View File
@@ -1 +1 @@
export const VERSION = "v3.97.1";
export const VERSION = "v3.97.2";
+38 -12
View File
@@ -5,6 +5,7 @@ import { prisma } from "@langfuse/shared/src/db";
import {
convertDateToClickhouseDateTime,
logger,
measureAndReturn,
queryClickhouse,
traceException,
} from "@langfuse/shared/src/server";
@@ -37,20 +38,45 @@ export default async function handler(
try {
if (failIfNoRecentEvents) {
const now = new Date();
const traces = await queryClickhouse({
query: `
SELECT id
FROM traces
WHERE timestamp <= {now: DateTime64(3)}
AND timestamp >= {now: DateTime64(3)} - INTERVAL 3 MINUTE
LIMIT 1
`,
params: {
const traces = await measureAndReturn({
operationName: "healthCheckTraces",
projectId: "__CROSS_PROJECT__",
input: {
now: convertDateToClickhouseDateTime(now),
},
tags: {
feature: "health-check",
type: "trace",
existingExecution: async (input: { now: string }) => {
return queryClickhouse<{ id: string }>({
query: `
SELECT id
FROM traces
WHERE timestamp <= {now: DateTime64(3)}
AND timestamp >= {now: DateTime64(3)} - INTERVAL 3 MINUTE
LIMIT 1
`,
params: input,
tags: {
feature: "health-check",
type: "trace",
experiment_amt: "original",
},
});
},
newExecution: async (input: { now: string }) => {
return queryClickhouse<{ id: string }>({
query: `
SELECT id
FROM traces_7d_amt
WHERE start_time <= {now: DateTime64(3)}
AND start_time >= {now: DateTime64(3)} - INTERVAL 3 MINUTE
LIMIT 1
`,
params: input,
tags: {
feature: "health-check",
type: "trace",
experiment_amt: "new",
},
});
},
});
const observations = await queryClickhouse({
+15 -10
View File
@@ -2,7 +2,6 @@ import { VERSION } from "@/src/constants/VERSION";
import { env } from "@/src/env.mjs";
import { createTRPCRouter, publicProcedure } from "@/src/server/api/trpc";
import { logger } from "@langfuse/shared/src/server";
import { TRPCError } from "@trpc/server";
import { z } from "zod/v4";
const versionSchema = z.string().regex(/^v\d+\.\d+\.\d+(?:[-+].+)?$/); // e.g. v1.2.3, v1.2.3-rc.1, v1.2.3+build.123
@@ -79,27 +78,33 @@ export const publicRouter = createTRPCRouter({
);
body = await response.json();
} catch (error) {
logger.info(
logger.error(
"[trpc.public.checkUpdate] failed to fetch latest-release api",
{
error,
},
);
return null;
}
const releases = ReleaseApiRes.safeParse(body);
if (!releases.success) {
throw new TRPCError({
code: "INTERNAL_SERVER_ERROR",
message: "Release API response is invalid",
});
logger.error(
"[trpc.public.checkUpdate] Release API response is invalid, does not match schema",
{
error: releases.error,
},
);
return null;
}
const langfuseRelease = releases.data.find(
(release) => release.repo === "langfuse/langfuse",
);
if (!langfuseRelease) {
throw new TRPCError({
code: "INTERNAL_SERVER_ERROR",
message: "Release API response is invalid",
});
logger.error(
"[trpc.public.checkUpdate] Release API response is invalid, does not contain langfuse/langfuse",
);
return null;
}
const updateType = compareVersions(VERSION, langfuseRelease.latestRelease);
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "worker",
"version": "3.97.1",
"version": "3.97.2",
"description": "",
"license": "MIT",
"private": true,
@@ -16,6 +16,7 @@ type MigrationState = {
maxDate: string | undefined;
minDate: string | undefined;
queryTimeoutMinutes: number | undefined;
targetTracesAllAmtOnly: boolean | undefined;
};
/**
@@ -91,9 +92,7 @@ async function executeLongRunningQuery(
const abortController = new AbortController();
const timeoutMs = timeoutMinutes * 60 * 1000;
logger.info(
`[Background Migration] Executing traces_null backfill query ${queryId}`,
);
logger.info(`[Background Migration] Executing backfill query ${queryId}`);
// Start the query execution
const queryPromise = client.command({
@@ -208,7 +207,7 @@ export default class MigrateTracesToTracesAMTs implements IBackgroundMigration {
};
}
// Check if new ClickHouse tables exists
// Check if required ClickHouse tables exist
const tables = await clickhouseClient().query({
query: "SHOW TABLES",
});
@@ -249,6 +248,10 @@ export default class MigrateTracesToTracesAMTs implements IBackgroundMigration {
const queryTimeoutMinutes =
initialMigrationState.state?.queryTimeoutMinutes ??
(args.queryTimeoutMinutes as number | undefined);
const targetTracesAllAmtOnly =
initialMigrationState.state?.targetTracesAllAmtOnly ??
(args.targetTracesAllAmtOnly as boolean | undefined) ??
false;
const maxDate = initialMigrationState.state?.maxDate
? new Date(initialMigrationState.state.maxDate)
@@ -264,6 +267,7 @@ export default class MigrateTracesToTracesAMTs implements IBackgroundMigration {
maxDate,
minDate,
queryTimeoutMinutes,
targetTracesAllAmtOnly,
},
},
});
@@ -272,14 +276,21 @@ export default class MigrateTracesToTracesAMTs implements IBackgroundMigration {
const queryStart = Date.now();
// @ts-ignore
const migrationState: { state: { maxDate: string; minDate: string } } =
await prisma.backgroundMigration.findUniqueOrThrow({
where: { id: backgroundMigrationId },
select: { state: true },
});
const migrationState: {
state: {
maxDate: string;
minDate: string;
targetTracesAllAmtOnly?: boolean;
};
} = await prisma.backgroundMigration.findUniqueOrThrow({
where: { id: backgroundMigrationId },
select: { state: true },
});
const maxDate = new Date(migrationState.state.maxDate);
const minDate = new Date(migrationState.state.minDate);
const targetTracesAllAmtOnly =
migrationState.state.targetTracesAllAmtOnly ?? false;
// Get current month in YYYYMM format
const currentMonth = maxDate.toISOString().slice(0, 7).replace("-", "");
@@ -287,45 +298,89 @@ export default class MigrateTracesToTracesAMTs implements IBackgroundMigration {
`[Background Migration] Migrating traces for ${currentMonth}`,
);
const query = `
INSERT INTO traces_null
SELECT
-- Identifiers
project_id,
id,
timestamp as start_time,
null as end_time,
name,
-- Metadata properties
metadata,
user_id,
session_id,
environment,
tags,
version,
release,
-- UI Properties
bookmarked,
public,
-- Aggregations (ignored)
[] as observation_ids,
[] as score_ids,
map() as cost_details,
map() as usage_details,
-- Input/Output
input,
output,
created_at,
updated_at,
event_ts
FROM traces
WHERE toYYYYMM(timestamp) = ${currentMonth}
`;
const targetTable = targetTracesAllAmtOnly
? "traces_all_amt"
: "traces_null";
const query = targetTracesAllAmtOnly
? `
INSERT INTO ${targetTable}
SELECT
-- Identifiers
project_id,
id,
t.timestamp as timestamp,
t.timestamp as start_time,
t.timestamp as end_time,
name,
-- Metadata properties
metadata,
user_id,
session_id,
environment,
tags,
version,
release,
-- UI Properties
arrayReduce('argMaxState', [toNullable(bookmarked)], [event_ts]) as bookmarked,
arrayReduce('argMaxState', [toNullable(public)], [event_ts]) as public,
-- Aggregations
[] as observation_ids,
[] as score_ids,
map() as cost_details,
map() as usage_details,
-- Input/Output
arrayReduce('argMaxState', [coalesce(input, '')], [if(coalesce(input, '') <> '', event_ts, toDateTime64(0, 3))]) as input,
arrayReduce('argMaxState', [coalesce(output, '')], [if(coalesce(output, '') <> '', event_ts, toDateTime64(0, 3))]) as output,
created_at,
updated_at
FROM traces t
WHERE toYYYYMM(t.timestamp) = ${currentMonth}
`
: `
INSERT INTO ${targetTable}
SELECT
-- Identifiers
project_id,
id,
timestamp as start_time,
null as end_time,
name,
-- Metadata properties
metadata,
user_id,
session_id,
environment,
tags,
version,
release,
-- UI Properties
bookmarked,
public,
-- Aggregations (ignored)
[] as observation_ids,
[] as score_ids,
map() as cost_details,
map() as usage_details,
-- Input/Output
input,
output,
created_at,
updated_at,
event_ts
FROM traces
WHERE toYYYYMM(timestamp) = ${currentMonth}
`;
await executeLongRunningQuery(query, queryTimeoutMinutes ?? 90);
@@ -341,7 +396,7 @@ export default class MigrateTracesToTracesAMTs implements IBackgroundMigration {
});
logger.info(
`[Background Migration] Inserted traces into traces_null for ${currentMonth} in ${Date.now() - queryStart}ms`,
`[Background Migration] Inserted traces into ${targetTable} for ${currentMonth} in ${Date.now() - queryStart}ms`,
);
if (maxDate < minDate) {
@@ -412,6 +467,10 @@ async function main() {
short: "t",
default: "90",
},
targetTracesAllAmtOnly: {
type: "boolean",
default: false,
},
},
});
+1 -1
View File
@@ -1 +1 @@
export const VERSION = "v3.97.1";
export const VERSION = "v3.97.2";