我们的视频在一周内静默失败 — 过期的 Env Var 让我们损失了 $60 并导致 12 位不满意的用户

发布: (2026年3月9日 GMT+8 18:01)
10 分钟阅读
原文: Dev.to

Source: Dev.to

请提供您希望翻译的完整文本(除代码块和 URL 之外),我将把它翻译成简体中文并保持原有的 Markdown 格式。

TL;DR

  • 发生了什么? Fal.ai 的积分被消耗,但没有交付视频,因为 Remotion Lambda 函数名称已过时。
  • 为什么? 部署脚本中的手动步骤在升级 Remotion 后未能更新 REMOTION_LAMBDA_FUNCTION_NAME 环境变量。
  • 如何修复? 通过有针对性的重试脚本重新渲染已保存的资产,并将部署脚本自动化以保持环境变量同步。
  • 接下来怎么办? 每小时的 Inngest cron 会监控卡在 rendering 状态的项目,并在计费激增前提醒我们。

1. The symptom

“You know that sinking feeling when you check your billing dashboard and something doesn’t add up?”

RepoClip – my AI video‑generation SaaS – was burning ≈ $20 / day on Fal.ai credits for three consecutive days (Mar 7‑9).
The first guess was “more users = more videos”, but the videos never reached any user.

2. RepoClip 视频流水线

GitHub URL → Gemini Analysis → Kling Video Clips (fal.ai) → Remotion Lambda Render → Done
  • 每个 “Video Short” → 5 AI 剪辑 (Kling 3.0 Pro) → 与旁白拼接 (Remotion on AWS Lambda)。

该流水线由 Inngest 编排,它会对每个步骤进行记忆化,以便重试时不会重新执行已完成的工作。

相关步骤(简化)

步骤描述
1获取 GitHub 代码
2使用 Gemini 进行分析
3生成视频剪辑 (fal.ai) ← 这里花费金钱
4触发 Remotion Lambda 渲染 ← 失败
5轮询渲染完成
6将项目状态更新为 completed

3. 计费线索

Fal.ai 仪表盘显示 $20 / dayMar 7, 8, 9 → 大约 每日至少 2–4 次视频生成,这看起来对自然流量是合理的。

4. 数据库查询

SELECT
  created_at::date AS date,
  status,
  COUNT(*) AS count
FROM projects
WHERE created_at >= '2026-03-01'
GROUP BY date, status
ORDER BY date;

结果

日期状态计数
Mar 1completed3
Mar 1failed5
Mar 1rendering1
Mar 2rendering2
Mar 5rendering1
Mar 6rendering2
Mar 7rendering2
Mar 8rendering4

自 3 月 1 日起,任何项目都未再达到 completed 所有视频都卡在 rendering——即 Fal.ai 完成生成剪辑后的下一步。

5. 根本原因:过期的 Lambda 函数名称

环境变量(我们原以为的)

REMOTION_LAMBDA_FUNCTION_NAME=remotion-render-4-0-414-mem2048mb-disk2048mb-600sec

实际的 AWS Lambda 函数

aws lambda list-functions --query 'Functions[?starts_with(FunctionName, `remotion`)]'

输出:

remotion-render-4-0-429-mem2048mb-disk2048mb-600sec
remotion-render-4-0-429-mem3008mb-disk4096mb-900sec

函数 remotion-render-4-0-414… 已经不存在
我们将 Remotion 从 v4.0.414 → v4.0.429 升级,部署了新 Lambda,删除了旧的,但 忘记在 Vercel 上更新环境变量

后果

  • renderMediaOnLambda() 抛出 ResourceNotFoundException
  • Inngest 静默重试;客户端没有错误,GA4 也没有 “video_generate_complete” 事件,Sentry 也没有警报。
  • 计费仍在继续,因为 Fal.ai 仍在生成剪辑。

6. Recovery – targeted retry script

所有 12 个卡住的项目已经将资产持久化(assets JSONB 列)。我们避免重新生成昂贵的 Fal.ai 剪辑,而是直接重新渲染已保存的资产。

// The key insight: assets are already saved, just re‑render
const { renderId, bucketName } = await renderMediaOnLambda({
  region: REGION,
  functionName: FUNCTION_NAME, // now pointing to the correct function
  serveUrl: SERVE_URL,
  composition: "ProductVideo",
  inputProps, // built from saved assets
  codec: "h264",
  // …
});

结果: 12/12 视频已恢复(首次尝试恢复 9 个,因瞬时网络超时再恢复 3 个)。未产生额外的 Fal.ai 费用;用户收到了完成邮件。

7. 自动化 – 再也别忘记更新环境变量

旧的手动步骤

echo "Set the following environment variables:"
echo "  REMOTION_LAMBDA_FUNCTION_NAME="

新的自动化步骤

# Extract function name from deploy output
FUNC_NAME=$(echo "$FUNC_OUTPUT" | grep -oE 'remotion-render-[a-zA-Z0-9-]+' | head -1)

# Verify function exists
aws lambda get-function --function-name "$FUNC_NAME" --region "$REGION"

# Auto‑update Vercel + local env
echo -n "$FUNC_NAME" | npx vercel env rm REMOTION_LAMBDA_FUNCTION_NAME production -y
echo -n "$FUNC_NAME" | npx vercel env add REMOTION_LAMBDA_FUNCTION_NAME production
sed -i '' "s|^REMOTION_LAMBDA_FUNCTION_NAME=.*|REMOTION_LAMBDA_FUNCTION_NAME=$FUNC_NAME|" .env.local

现在部署脚本 提取新的 Lambda 名称,进行验证,并自动更新 Vercel 和本地 .env

8. 持续监控 – 用于卡住渲染的 cron 任务

export const monitorStuckRendersFunction = inngest.createFunction(
  { id: "monitor-stuck-renders" },
  { cron: "0 * * * *" }, // every hour
  async ({ step }) => {
    const stuckProjects = await step.run("check-stuck-projects", async () => {
      const threshold = new Date(Date.now() - 30 * 60 * 1000).toISOString();
      const { data } = await supabase
        .from("projects")
        .select("*")
        .eq("status", "rendering")
        .lt("updated_at", threshold);
      return data;
    });

    if (stuckProjects?.length) {
      // Notify Slack / email / create issue
      await step.run("alert", async () => {
        // …implementation…
      });
    }
  }
);
  • 它的作用: 每小时获取状态为 rendering 且已超过 30 分钟的项目,并向团队发送警报。
  • 为什么: 防止出现无声的计费激增,并为未来的回归提供安全保障。

9. 要点

✅ 成功之处❌ 失败之处
在渲染前持久化资产 → 低成本恢复手动环境变量更新步骤被遗漏
Inngest 记忆化防止了 Fal.ai 的重复收费静默重试隐藏了 ResourceNotFoundException
计费异常触发了调查缺少 GA4 “complete” 事件 → 可视性缺失
自动化部署脚本现在确保环境变量同步之前没有监控卡住的渲染

结论: 一个微小的手动步骤导致了超过 60 美元的浪费和糟糕的用户体验。通过持久化中间资产、自动化环境更新并加入主动监控,我们将一次代价高昂的宕机转化为学习机会。 🚀

监控卡住的项目

// Example query to find projects stuck in the “rendering” state
const { data: stuckProjects } = await supabase
  .from("projects")
  .select("id, repo_name, content_mode, updated_at")
  .eq("status", "rendering")
  .lt("updated_at", threshold);

return data ?? [];

// If any projects are stuck, send an alert email with their details
if (stuckProjects.length > 0) {
  // Send alert email with project details
}

如果这段代码一周前就已经存在,我们本可以在一小时内发现问题,而不是等上七天。

实时状态事件

我们添加了两个事件,当用户的浏览器通过 Supabase Realtime 接收到状态更改时触发:

// ProjectStatusListener.tsx
const channel = supabase
  .channel(`project-${projectId}`)
  .on(
    "postgres_changes",
    { /* … */ },
    (payload) => {
      if (payload.new?.status === "completed") {
        gaEvent("video_generate_complete", { project_id: projectId });
      } else if (payload.new?.status === "failed") {
        gaEvent("video_generate_fail", { project_id: projectId });
      }
    }
  )
  .subscribe();

漏斗查询(BigQuery)

-- Start‑to‑complete ratio per day
SELECT
  event_date,
  COUNT(DISTINCT CASE WHEN event_name = 'video_generate_start'
    THEN user_pseudo_id END) AS start_users,
  COUNT(DISTINCT CASE WHEN event_name = 'video_generate_complete'
    THEN user_pseudo_id END) AS complete_users
FROM events_*
GROUP BY event_date;

现在,complete/start 比例的突然下降显得像是一个明确的信号。

成本发现

在调查过程中,我们意识到 每位免费用户都在收到与付费客户相同的 “Kling 3.0 Pro” 片段

  • 每段视频成本(Pro):≈ $5.60
  • 转化率:≈ 3 %
  • 结果:客户获取成本不可持续

解决方案

计划片段类型片段数量大约时长每段视频成本
免费Kling 3.0 Standard3~15 秒$2.52
付费Kling 3.0 Pro5~25 秒$5.60
  • 将 “Kling 3.0 Pro 质量” 作为可感知的升级激励。
  • 将免费层成本降低 55 %

Lessons Learned

  1. Env vars are a silent single point of failure – 自动化它们的生命周期。
  2. Background‑job failures are invisible by default – 为“应该完成但未完成的任务”添加显式监控。
  3. Track completion, not just initiationvideo_generate_complete 数据的缺失是一个关键信号。
  4. Persist intermediate results – 允许在不产生额外 Fal.ai 费用的情况下恢复。
  5. Billing anomalies are monitoring signals – 对异常的支出模式设置警报。

Try RepoClip

RepoClip generates AI‑powered promotional videos from GitHub repositories.
Paste any public repo URL and get a video in minutes — free, no credit card required.

0 浏览
Back to Blog

相关文章

阅读更多 »