ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Langfuse 后台迁移(Background Migrations)完全指南:从 Prisma 注册到 Env 门控执行的源码级解析

Langfuse 后台迁移(Background Migrations)完全指南:从 Prisma 注册到 Env 门控执行的源码级解析 Langfuse 后台迁移Background Migrations完全指南从 Prisma 注册到 Env 门控执行的源码级解析【免费下载链接】langfuse Open source AI engineering platform: LLM evals, observability, metrics, prompt management, playground, datasets. Integrates with OpenTelemetry, LangChain, OpenAI SDK, LiteLLM, and more. YC W23项目地址: https://gitcode.com/GitHub_Trending/la/langfuse本文围绕 Langfuse 仓库中 worker/src/backgroundMigrations/README.md 的技术骨架展开系统讲解后台迁移机制的设计动机、注册方式、IBackgroundMigration接口契约、管理器BackgroundMigrationManager的加锁/心跳/断点续跑执行流程以及envGate休眠行机制的完整用法。读完本文你将能够判断何种操作应当作为后台迁移而非普通 Prisma 迁移在 Langfuse 中正确新增一个可恢复、可排序、可门控的后台迁移并理解 worker 如何保证迁移过程中事件处理不中断、迁移失败后如何被其他 worker 接管续跑。什么是后台迁移何时必须使用它后台迁移Background Migration是运行时间较长、必须在新版本应用能正确服务之前完成的作业型任务。Langfuse 将其与常规数据库迁移Prisma migration区分开当某个数据变更操作超过约 5 分钟或不是原子操作时就不应再放进标准迁移里而应交给后台迁移机制。典型的适用场景包括填充新增的可选列backfill 历史数据在表之间或系统之间搬运数据如 PostgreSQL 与 ClickHouse 之间的数据同步对存量数据进行加密、重写、清理等耗时操作。从仓库现状看这些场景都有真实对应实现worker/src/backgroundMigrations 目录下共 11 个迁移文件覆盖了addGenerationsCostBackfill.ts——为历史 generation 观测回填成本字段README 中的 CLI 示例即此脚本backfillEventsFullFromObservations.ts、backfillEventsFullFromDatasetRunItems.ts——ClickHouse 事件表回填backfillSysIdForDatasetItems.ts、backfillValidToForDatasetItems.ts——dataset_items 系统 ID 与valid_to时间戳回填createRootSpansFromTraces.ts、rewriteObservationsToPidTidSorting.ts、dropPidTidSortingTables.ts——V4 历史回填链M1M5中的根 span 创建、表重写与清理encryptBlobStorageSecrets.ts——对 blob 存储集成中未加密的secretAccessKey进行加密补写backfillBillingCycleAnchors.ts、patchLLMToolAndLLLMSchemaAuditLogs.ts——计费锚点与审计日志修补。从源码结构可以推断该机制是 Langfuse 处理大规模数据迁移的统一通道几乎每一次涉及存量数据改写的能力发布都会经由它执行。新增后台迁移的完整步骤新增一个后台迁移需要同时做两件事在数据库background_migrations表中插入一行通常通过 Prisma migration SQL在当前目录worker/src/backgroundMigrations新增一个迁移脚本文件。注册行数据库中的迁移状态记录后台迁移的状态持久化在background_migrations表中其 Prisma 模型定义位于 packages/shared/prisma/schema.prisma#L334-L347model BackgroundMigration { id String id default(cuid()) name String unique script String map(script) args Json map(args) state Json default({}) map(state) finishedAt DateTime? map(finished_at) failedAt DateTime? map(failed_at) failedReason String? map(failed_reason) workerId String? map(worker_id) lockedAt DateTime? map(locked_at) map(background_migrations) }各字段语义如下字段类型说明idString (PK)迁移行唯一 ID迁移脚本中硬编码引用见下文nameString (unique)迁移名称必须可排序建议以日期为前缀如20260701_v4_step_2_...scriptString指向迁移脚本文件的名称管理器据此require(./script)加载类argsJson迁移运行参数可携带envGate门控声明、projectId等stateJson迁移运行中间状态如游标、分块进度用于断点续跑finishedAt/failedAt/failedReasonDateTime?/String?完成/失败标记及原因workerId/lockedAtString?/DateTime?分布式锁信息谁在跑、何时锁定的实现类IBackgroundMigration 接口迁移脚本的默认导出必须实现 IBackgroundMigration 接口完整定义如下export interface IBackgroundMigration { validate: ( args: Recordstring, unknown, ) Promise{ valid: boolean; invalidReason: string | undefined }; run: (args: Recordstring, unknown) Promisevoid; abort: () Promisevoid; }三个方法的职责validate(args)在执行前校验前置条件返回{ valid, invalidReason }。校验失败的迁移会被标记为failedAt不会执行run。典型用法包括检查注册行是否存在、检查目标列是否存在见 backfillValidToForDatasetItems.ts#L32-L67 中对information_schema.columns的查询、检查前置迁移是否已成功完成见下文迁移链依赖守卫。run(args)执行实际迁移逻辑必须可恢复、可中断。abort()worker 关闭或锁被接管时被调用迁移实现应在此置位已中止标志并优雅退出。以 backfillValidToForDatasetItems.ts 为范本一个标准的实现会在文件顶部用注释硬编码backgroundMigrationId该 ID 与 Prisma migration SQL 中插入的行 ID 一一对应run()内采用游标式分批处理每次处理一批默认batchSize 1000处理完将lastProcessedProjectId/lastProcessedId游标写回background_migrations.state批次之间默认休眠delayBetweenBatchesMs 200ms以降低对数据库的压力每轮循环检查this.isAborted被中止时立即跳出留下可续跑的状态。其注释明确描述了性能策略一次只处理一个 project以利用(project_id, id, valid_from)复合索引每个 project 内最多批量取 100 个id对用LEAD()窗口函数计算相邻版本的valid_to——窗口函数只作用于当前批次而不是整张表从而避免全表扫描式的内存开销。注册行与脚本的对应关系两者的关联通过硬编码 UUID与script 文件名完成。以 V4 回填链为例Prisma 迁移 SQL 中插入INSERT INTO background_migrations (id, name, script, args) VALUES ( 9c2d5a4f-7b8e-4f6a-a91c-3e5d7f8a2b1c, 20260701_v4_step_2_rewrite_observations_to_pid_tid_sorting, rewriteObservationsToPidTidSorting, {envGate: LANGFUSE_BACKGROUND_MIGRATION_V4_ENABLE_HISTORIC_BACKFILL}::jsonb );而脚本 rewriteObservationsToPidTidSorting.ts 中同样硬编码了该 UUID。管理器正是通过script字段执行new (require(\./${migration.script}).default)() 来加载迁移类见 backgroundMigrationManager.ts#L114。仓库中已注册的后台迁移可见于 packages/shared/prisma/migrations 下多个以add_..._background_migration命名的迁移目录例如20241024121500_add_generations_cost_backfill_background_migration、20260701115500_add_create_root_spans_background_migration等每个目录的migration.sql都以INSERT INTO background_migrations (id, name, script, args)开头。迁移的硬性要求RequirementsREADME 明确了后台迁移必须遵守的七条要求这是设计评审的核心检查清单任何时刻都必须可恢复recoverable迁移可以被中断并且必须能在任意阶段恢复。实现方式有两种——跨系统迁移做成幂等idempotent单数据库内迁移保证每次变更原子。同一时刻只能有一个后台迁移运行这并非技术限制而是为了让推理迁移行为更容易。必须在 changelog 及其他页面高亮提示如果代码依赖某个后台迁移已完成必须明确告知运维人员README 引用了 GitLab 的 upgrade stops 作为沟通范式。迁移名称必须可排序因为迁移按name升序依次执行最好以日期为前缀。必须假设 worker 在迁移运行期间会继续处理事件即迁移应避免调用应用代码避免与应用业务逻辑耦合。必须假设迁移运行期间有新事件持续写入即迁移不能依赖数据库状态是静态的要能容忍并发写入。第 5、6 条尤其关键——它决定了后台迁移与普通迁移的本质区别后台迁移运行在生产流量持续写入的环境中因此逐条扫描、游标续跑、幂等重放是标配设计。执行引擎BackgroundMigrationManager 的调度与锁后台迁移的执行引擎是 backgroundMigrationManager.ts 中的BackgroundMigrationManager类。worker 在启动时会检查环境变量LANGFUSE_ENABLE_BACKGROUND_MIGRATIONS默认true定义于 worker/src/env.ts#L215-L217为真时异步启动BackgroundMigrationManager.run()且不会阻塞队列 worker见 worker/src/app.ts#L125-L130。主循环流程run()的执行逻辑backgroundMigrationManager.ts#L41-L192可概括为扫描可运行迁移在数据库事务中执行findFirst筛选finishedAt null且failedAt null的行同时应用 envGate 过滤见下一节按name升序取出最早一条。锁检查若取到迁移但lockedAt在最近60 秒内被更新过则视为被其他 worker 持有直接结束本轮避免并发执行。注意lockedAt不在数据库查询条件中因为findFirst可能返回其他未完成迁移若在查询中过滤会导致漏锁。加锁通过update将workerId设为本进程的randomUUID()lockedAt设为当前时间。整个查询加锁过程包在isolationLevel: Serializable的事务中maxWait: 5000保证锁获取的原子性。心跳heartbeat调用heartBeat()后每15 秒将当前活跃迁移的lockedAt刷新为当前时间backgroundMigrationManager.ts#L20-L39。这既是我活着的信号也是锁续约的机制。校验并运行先调用migration.validate(args)。校验失败则写入failedAt/failedReason并继续下一条成功则调用migration.run(args)。标记完成run()正常返回且迁移仍处于活跃状态时写入finishedAt并清空lockedAt。循环继续找下一条未完成迁移直到没有可运行的迁移为止。崩溃接管与优雅关闭worker 被 kill由于心跳停止lockedAt不再刷新60 秒后锁自然过期另一个 worker会取到该迁移并从上次断点继续。这正是 README 描述的另一个 worker 会接手续跑机制的实现迁移进度依赖state字段中的游标/分块状态而非进程内内存。worker 优雅关闭close()backgroundMigrationManager.ts#L194-L211会调用活跃迁移的abort()方法随后清空lockedAt但不写finishedAt因为迁移并未完成。该close()在 worker/src/utils/shutdown.ts#L105 的关闭流程中被调用。迁移链依赖守卫管理器本身没有依赖模型——它只按名称顺序执行所有合格行并独立标记每个迁移的完成/失败。这意味着上游迁移失败并不会自动阻止下游迁移在部分数据上运行。为此仓库在 worker/src/backgroundMigrations/utils/backfillBase.ts#L122-L151 提供了checkPredecessorMigrationFinalized(predecessorId, predecessorName)工具前置迁移未注册、failedAt非空、或finishedAt为空均返回校验失败下游迁移在validate()中调用它失败信息会经由管理器的校验失败路径写入failedAt由于链条上的每一步都守卫自己的前置步骤单个失败会传递性地中断整条链。典型用例见 dropPidTidSortingTables.ts#L38-L45M5 清理迁移校验时检查 M420260701_v4_step_4_backfill_events_full_from_dataset_run_items是否已完成。本地与测试环境执行命令行运行方式后台迁移最好能通过命令行直接执行以便本地开发或在 staging 环境测试。README 给出的标准命令是cd worker dotenv -e ../.env -- npx tsx src/backgroundMigrations/script-name.ts # 示例 dotenv -e ../.env -- npx tsx src/backgroundMigrations/addGenerationsCostBackfill.ts该命令依赖dotenv-cli加载仓库根目录的.env用tsx直接运行迁移脚本。脚本通过require.main module判断自身是被直接执行还是被导入被直接执行时进入 CLI 入口例如 backfillValidToForDatasetItems.ts#L163-L176先validate再run失败以非零退出码结束进程。分块回填类迁移还提供了一套标准 CLI 参数定义于 utils/backfillBase.ts#L834-L908 的runBackfillMigrationCli参数短选项默认值说明--concurrency-c1同时运行的 ClickHouse 查询数--pollIntervalMs-p30000轮询活跃查询状态的间隔毫秒--maxRetries-r3单个分块的最大重试次数--retryFailed-ffalse将失败分块重置为 pending 重新尝试--partitions可多值无仅处理指定的 yyyymm 分区深入分块回填基类与 ClickHouse 断点恢复V4 历史回填链的迁移M2M4并非直接实现IBackgroundMigration而是继承抽象基类ChunkedClickhouseBackfillMigrationutils/backfillBase.ts#L429-L823这是 README可恢复要求最完整的工程化体现分块枚举从 ClickHousesystem.parts发现活跃的 yyyymm 分区跳过patch-%与all元分区每个分区生成一个BaseChunkTodopending → in_progress → completed/failed见loadPartitionsFromClickhousebackfillBase.ts#L166-L208。fire-and-poll 模式fireQuerybackfillBase.ts#L247-L346发起一个长时运行的 ClickHouse 查询确认其在system.processes中被跟踪后主动断开 HTTP 连接让查询在服务端继续执行再通过pollQueryStatus轮询完成状态并设置max_execution_time: 0避免服务端超时掐断长查询。崩溃恢复recoverInProgressTodosbackfillBase.ts#L360-L412在每次run()开始时重新关联上一次 worker 遗留的 in-flight 查询——已完成的标记完成失败的按重试计数重置为 pending仍运行的继续跟踪。所有分块状态todos、activeQueries、phase、config都持久化在background_migrations.state中loadState/updateState。并发与重试调度循环按concurrency填充空闲槽位每个分块失败后递增retryCount达到maxRetries才标记为永久失败并抛出错误使管理器写入failedAt--retry-failed可将其重置后重跑。迁移链守卫与表存在性校验validate()先检查前置迁移是否finishedAt再对requiredTables逐表SHOW TABLES确认存在最多重试 5 次、间隔 10 秒随后触发afterTablesValidated钩子执行惰性 DDL如创建 scratch 表。这段实现解释了 README 要求的底层落地所有进度都在数据库中中断只是原地暂停恢复只是重新 attach。环境变量门控迁移envGate / dormant 行动机与机制某些迁移需要在 release N 就随包发布但只能在 release N1 才执行或仅在运维人员显式 opt-in 时执行。为此引入envGate机制带门控的迁移行处于dormant休眠状态——它存在于background_migrations表中且finished_at NULL但管理器在查询时跳过它直到对应的环境变量被设为true。关键设计点门控检查被推入findFirst的谓词中backgroundMigrationManager.ts#L64-L76因此休眠行不会 head-of-line 阻塞其后按名称排序的其他迁移——排在它后面的、未门控或门控已开启的迁移照常运行。如何编写一个门控迁移README 给出了三步流程结合仓库证据展开如下第 1 步选择门控名称必须以LANGFUSE_BACKGROUND_MIGRATION_为前缀。管理器通过扫描已验证的 env不是全部process.env中键以该前缀开头且值为true的变量来发现活跃门控backgroundMigrationManager.ts#L51-L55。第 2 步在 Prisma 迁移 SQL 中通过行的args声明门控INSERT INTO background_migrations (id, name, script, args) VALUES ( ..., 20260521120000_my_dormant_migration, myDormantMigration, {projectId: ..., envGate: LANGFUSE_BACKGROUND_MIGRATION_V4_ENABLE_MY_FEATURE}::jsonb );args.envGate的取值必须与第 1 步的门控名一致。仓库中的真实样例可见 20260701115504_add_drop_pid_tid_sorting_tables_background_migration/migration.sql门控LANGFUSE_BACKGROUND_MIGRATION_V4_DROP_PID_TID_SORTING_TABLES与 20260701115501_add_rewrite_observations_to_pid_tid_sorting_background_migration/migration.sql门控LANGFUSE_BACKGROUND_MIGRATION_V4_ENABLE_HISTORIC_BACKFILL。第 3 步在worker/src/env.ts的EnvSchema中注册该环境变量使用z.enum([true, false]).default(false)使其类型化、启动时校验、且默认休眠LANGFUSE_BACKGROUND_MIGRATION_V4_ENABLE_HISTORIC_BACKFILL: z .enum([true, false]) .default(true), LANGFUSE_BACKGROUND_MIGRATION_V4_DROP_PID_TID_SORTING_TABLES: z .enum([true, false]) .default(false),以上两行取自 worker/src/env.ts#L601-L606注释还解释了各自默认值的取舍历史回填门控默认开启对无历史数据的新部署是 no-op而排序表清理门控默认关闭以便自托管用户保留中间产物做排查直到确认新路径健康后再开启。门控的运行时行为环境变量为true该行对管理器可见按正常的名称顺序执行环境变量为false或缺失该行不可见——不加锁、不打印 skip 日志、不造成 head-of-line 阻塞。管理器执行时会先收集所有值为true的LANGFUSE_BACKGROUND_MIGRATION_*变量构成activeGates数组然后构造OR条件要么args.envGate为空Prisma.AnyNull即未门控要么等于某个活跃门控名。这样一条查询就同时实现了跳过休眠行与保留普通迁移两种语义见 backgroundMigrationManager.ts#L57-L76。自检清单与运维注意事项综合以上机制在 Langfuse 中落地一个新的后台迁移建议按如下清单核查是否确实需要后台迁移单次运行超过约 5 分钟或非原子操作 → 是否则请走标准 Prisma migration。注册完备性Prisma 迁移 SQL 已插入background_migrations行脚本文件已存在于 worker/src/backgroundMigrations默认导出实现了 IBackgroundMigration脚本内硬编码的 UUID 与 SQL 中的id一致。名称可排序name以日期/序号前缀如20260701_v4_step_5_...确保执行顺序可预期。可恢复性单库内逐步原子提交跨库/跨系统PG ↔ ClickHouse幂等进度写入state游标或分块状态。并发友好不依赖数据库静态状态run()内每轮检查isAborted不调用应用层业务代码。门控如需休眠args.envGate以LANGFUSE_BACKGROUND_MIGRATION_前缀命名已在worker/src/env.ts的EnvSchema中注册z.enum([true,false]).default(false)。依赖链如需下游迁移在validate()中调用checkPredecessorMigrationFinalized守卫上游上游失败会传递性阻断整条链。变更沟通若应用代码依赖迁移完成需在 changelog 与部署文档中显式标注参考 GitLab 的 upgrade stops 沟通方式。运行时验证本地用dotenv -e ../.env -- npx tsx src/backgroundMigrations/script-name.ts单跑验证staging 可用--concurrency/--maxRetries/--retry-failed/--partitions控制执行面生产环境确认LANGFUSE_ENABLE_BACKGROUND_MIGRATIONStrue且 worker 心跳15s 刷新lockedAt正常。故障排查迁移卡住时检查background_migrations行的lockedAt60 秒内视为锁有效、failedReason、state中的游标失败后清除failedAt可让管理器重试该行分块迁移可清failedAt后以--retry-failed重跑失败分块。通过这套机制Langfuse 得以在不中断事件流处理的前提下安全地完成从填充可选列到整表跨系统重写的各种重型数据迁移并将失败恢复、多 worker 接管、分阶段发布等运维复杂度收敛在 backgroundMigrationManager.ts 与 utils/backfillBase.ts 两处基础设施之中。【免费下载链接】langfuse Open source AI engineering platform: LLM evals, observability, metrics, prompt management, playground, datasets. Integrates with OpenTelemetry, LangChain, OpenAI SDK, LiteLLM, and more. YC W23项目地址: https://gitcode.com/GitHub_Trending/la/langfuse创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表