T-CREATOR

FFmpeg バッチ運用プレイブック:監視・再試行・キュー管理を systemd/tmux で回す

FFmpeg バッチ運用プレイブック:監視・再試行・キュー管理を systemd/tmux で回す

大量の動画ファイルをバッチ処理する場面では、FFmpeg の実行そのものよりも「運用」が課題になりますよね。エンコード中にエラーが起きたらどう再試行するのか、複数のジョブをどう管理するのか、サーバー再起動後も自動で処理を継続できるのか――こうした疑問にお答えする、実践的なバッチ運用の設計と実装をご紹介します。

systemd と tmux を活用すれば、FFmpeg 処理を 監視・再試行・キュー管理 する堅牢なバッチシステムを構築できます。本記事では、実運用で使える設定ファイルとスクリプトをセットで解説していきますね。

背景

FFmpeg を使った動画変換は、1 本なら ffmpeg -i input.mp4 output.mp4 で済みますが、実際の業務では数百〜数千本の動画を処理するケースがほとんどです。

こうした大量処理では以下のような要件が生まれます。

  • 長時間実行: エンコードは時間がかかるため、SSH 接続が切れても処理を継続したい
  • エラー対応: ネットワーク障害やメモリ不足で失敗した処理を自動で再試行したい
  • 並列処理: CPU コアを有効活用するため、複数ジョブを同時実行したい
  • 自動起動: サーバー再起動後も処理キューを自動再開したい
  • 監視: 処理状況をログやダッシュボードで確認したい

以下は、FFmpeg バッチ処理の全体フローです。

mermaidflowchart TD
  queue["処理キュー<br/>(待機ファイル一覧)"]
  worker1["Worker 1<br/>(FFmpeg プロセス)"]
  worker2["Worker 2<br/>(FFmpeg プロセス)"]
  monitor["監視プロセス<br/>(systemd/tmux)"]
  retry["再試行ロジック<br/>(エラーハンドリング)"]
  log["実行ログ<br/>(成功/失敗)"]

  queue --> worker1
  queue --> worker2
  worker1 -->|成功| log
  worker1 -->|失敗| retry
  worker2 -->|成功| log
  worker2 -->|失敗| retry
  retry --> queue
  monitor -.->|プロセス監視| worker1
  monitor -.->|プロセス監視| worker2
  monitor -.->|自動再起動| queue

図の要点

  • 処理キューから複数の Worker が動画ファイルを取得
  • 失敗時は再試行ロジックがキューに戻す
  • 監視プロセスが Worker の状態を常に確認

課題

FFmpeg バッチ処理で直面する主な課題は以下の 3 点です。

プロセス管理の複雑さ

複数の FFmpeg プロセスを並列実行すると、どのプロセスがどのファイルを処理中なのか把握しづらくなります。また SSH 接続が切れると処理が停止してしまいますね。

エラーハンドリングの難しさ

エンコード失敗の原因は多岐にわたります。

  • ディスク容量不足
  • メモリ不足
  • 破損した入力ファイル
  • FFmpeg のバージョン不整合

単純にスクリプトを実行するだけでは、どのファイルで失敗したのか、再試行すべきかどうかの判断ができません。

運用の継続性

深夜バッチでエンコードを開始しても、サーバーのメンテナンスや予期しない再起動で処理が中断されるケースがあります。手動で再開するのは非効率ですし、処理の重複や漏れが発生するリスクもありますね。

以下の図は、これらの課題がどう関連しているかを示しています。

mermaidflowchart LR
  issue1["SSH 切断で<br/>プロセス停止"]
  issue2["エラー原因の<br/>特定困難"]
  issue3["再起動後の<br/>手動再開"]

  root["バッチ運用の<br/>脆弱性"]

  issue1 --> root
  issue2 --> root
  issue3 --> root

  root --> impact["処理遅延<br/>工数増加<br/>品質低下"]

図の要点

  • 3 つの課題がバッチ運用の脆弱性を生む
  • 結果として処理遅延や工数増加につながる

解決策

これらの課題を解決するために、systemd によるサービス化tmux によるセッション管理を組み合わせた運用設計を採用します。

systemd でのサービス化

systemd は Linux のプロセス管理システムで、以下の機能を提供します。

  • 自動起動: サーバー起動時に自動でバッチを開始
  • 再起動: 異常終了時の自動再起動
  • ログ管理: journalctl による統合ログ閲覧
  • リソース制限: CPU・メモリの使用上限設定

tmux によるセッション管理

tmux はターミナルマルチプレクサで、SSH 接続が切れてもセッションを維持します。

  • 永続化: SSH 切断後もプロセスが継続
  • 複数ウィンドウ: 1 セッション内で複数ジョブを管理
  • アタッチ/デタッチ: 処理状況を随時確認可能

キュー管理とリトライロジック

ファイルベースのシンプルなキュー管理と、終了コードに基づくリトライロジックを実装します。

以下は、解決策の全体アーキテクチャです。

mermaidflowchart TB
  subgraph systemd_layer["systemd レイヤー"]
    service["ffmpeg-batch.service"]
  end

  subgraph tmux_layer["tmux レイヤー"]
    session["永続セッション"]
    window1["Window 1: Worker"]
    window2["Window 2: Monitor"]
  end

  subgraph app_layer["アプリケーションレイヤー"]
    queue_script["queue-manager.sh"]
    worker_script["ffmpeg-worker.sh"]
    retry_script["retry-handler.sh"]
  end

  service --> session
  session --> window1
  session --> window2
  window1 --> worker_script
  window2 --> queue_script
  worker_script --> retry_script

  style systemd_layer fill:#e1f5ff
  style tmux_layer fill:#fff3e0
  style app_layer fill:#f3e5f5

図で理解できる要点

  • systemd が tmux セッションを起動・監視
  • tmux 内で複数の Worker とモニターが動作
  • 各レイヤーが独立しているため、保守性が高い

具体例

ここからは実際の設定ファイルとスクリプトを段階的に解説します。

ディレクトリ構成

まずは作業ディレクトリの構成を確認しましょう。

bash/opt/ffmpeg-batch/
├── bin/               # 実行スクリプト
│   ├── queue-manager.sh
│   ├── ffmpeg-worker.sh
│   └── retry-handler.sh
├── queue/             # キューディレクトリ
│   ├── pending/       # 処理待ち
│   ├── processing/    # 処理中
│   ├── completed/     # 完了
│   └── failed/        # 失敗
├── logs/              # ログファイル
└── config/            # 設定ファイル
    └── worker.conf

このディレクトリ構成により、処理状態がファイルシステム上で明確に管理されます。

systemd サービスファイルの作成

systemd でバッチ処理をサービス化します。以下のファイルを作成してください。

ファイル: ​/​etc​/​systemd​/​system​/​ffmpeg-batch.service

systemd[Unit]
Description=FFmpeg Batch Processing Service
After=network.target

[Service]
Type=forking
User=ffmpeg
Group=ffmpeg
WorkingDirectory=/opt/ffmpeg-batch
ExecStart=/usr/bin/tmux new-session -d -s ffmpeg-batch '/opt/ffmpeg-batch/bin/queue-manager.sh'
ExecStop=/usr/bin/tmux kill-session -t ffmpeg-batch
Restart=on-failure
RestartSec=10
StandardOutput=journal
StandardError=journal

[Install]
WantedBy=multi-user.target

設定の要点

  • Type=forking: tmux セッションをバックグラウンドで起動
  • Restart=on-failure: 異常終了時に 10 秒後再起動
  • StandardOutput=journal: ログを systemd journal に出力

tmux セッション起動スクリプト

tmux 内で複数の Worker を起動する管理スクリプトです。

ファイル: ​/​opt​/​ffmpeg-batch​/​bin​/​queue-manager.sh

bash#!/bin/bash
# queue-manager.sh - FFmpeg バッチのキュー管理スクリプト

set -euo pipefail

# 設定読み込み
source /opt/ffmpeg-batch/config/worker.conf

# ログ設定
LOG_DIR="/opt/ffmpeg-batch/logs"
QUEUE_LOG="${LOG_DIR}/queue-manager.log"

# ログ関数
log() {
  echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "${QUEUE_LOG}"
}

log "Queue Manager started"

スクリプトの役割

  • 設定ファイルから Worker 数やリトライ回数を読み込み
  • ログ出力用の関数を定義
  • tmux の複数ウィンドウで Worker を起動

Worker の起動処理

続いて、複数の Worker ウィンドウを tmux 内に作成します。

bash# Worker 数の取得
WORKER_COUNT=${WORKER_COUNT:-2}

# tmux ウィンドウの作成
for i in $(seq 1 "${WORKER_COUNT}"); do
  tmux new-window -t ffmpeg-batch -n "worker-${i}" \
    "/opt/ffmpeg-batch/bin/ffmpeg-worker.sh ${i}"
  log "Started worker-${i}"
done

# モニターウィンドウの作成
tmux new-window -t ffmpeg-batch -n "monitor" \
  "watch -n 5 /opt/ffmpeg-batch/bin/status-monitor.sh"

log "All workers started. Use 'tmux attach -t ffmpeg-batch' to view."

処理の流れ

  1. 設定ファイルから Worker 数を取得(デフォルト 2)
  2. 各 Worker を tmux の別ウィンドウで起動
  3. 監視用ウィンドウも同時に起動

Worker スクリプトの実装

各 Worker が処理キューからファイルを取得し、FFmpeg を実行します。

ファイル: ​/​opt​/​ffmpeg-batch​/​bin​/​ffmpeg-worker.sh

bash#!/bin/bash
# ffmpeg-worker.sh - FFmpeg Worker スクリプト

set -euo pipefail

WORKER_ID=$1
QUEUE_DIR="/opt/ffmpeg-batch/queue"
LOG_DIR="/opt/ffmpeg-batch/logs"
WORKER_LOG="${LOG_DIR}/worker-${WORKER_ID}.log"

# ログ関数
log() {
  echo "[Worker-${WORKER_ID}] [$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "${WORKER_LOG}"
}

log "Worker started"

初期化処理

  • Worker ID を引数から取得
  • ログファイルを Worker ごとに分離
  • タイムスタンプ付きログ関数を定義

キューからのファイル取得

処理待ちファイルをアトミックに取得する処理です。

bash# ファイル取得関数(排他制御付き)
get_next_file() {
  local lockfile="/tmp/ffmpeg-queue.lock"

  # ロックを取得(最大 10 秒待機)
  exec 200>"${lockfile}"
  flock -w 10 200 || {
    log "Failed to acquire lock"
    return 1
  }

  # pending から最初のファイルを取得
  local file
  file=$(find "${QUEUE_DIR}/pending" -type f -name "*.json" | head -n 1)

  if [[ -z "${file}" ]]; then
    # キューが空
    flock -u 200
    return 1
  fi

  # processing へ移動
  local basename
  basename=$(basename "${file}")
  mv "${file}" "${QUEUE_DIR}/processing/${basename}"

  # ロック解放
  flock -u 200

  echo "${QUEUE_DIR}/processing/${basename}"
}

排他制御のポイント

  • flock でファイルロックを取得し、複数 Worker の競合を防止
  • ファイルを pending から processing へアトミックに移動
  • ロック取得に失敗した場合は処理をスキップ

FFmpeg 実行とエラーハンドリング

実際に FFmpeg を実行し、結果に応じて処理を振り分けます。

bash# メインループ
while true; do
  # 次のファイルを取得
  job_file=$(get_next_file)

  if [[ -z "${job_file}" ]]; then
    log "Queue is empty. Waiting..."
    sleep 10
    continue
  fi

  log "Processing: ${job_file}"

  # ジョブ情報の読み込み
  input_file=$(jq -r '.input' "${job_file}")
  output_file=$(jq -r '.output' "${job_file}")
  options=$(jq -r '.options' "${job_file}")

  log "Input: ${input_file}, Output: ${output_file}"

  # FFmpeg 実行
  if ffmpeg -i "${input_file}" ${options} "${output_file}" \
      >> "${WORKER_LOG}" 2>&1; then
    # 成功
    log "Success: ${output_file}"
    mv "${job_file}" "${QUEUE_DIR}/completed/"
  else
    # 失敗
    exit_code=$?
    log "Failed with exit code ${exit_code}: ${input_file}"
    /opt/ffmpeg-batch/bin/retry-handler.sh "${job_file}" "${exit_code}"
  fi
done

処理の流れ

  1. キューから次のジョブを取得
  2. JSON ファイルから入力・出力パスとオプションを読み込み
  3. FFmpeg を実行し、終了コードで成功・失敗を判定
  4. 成功時は completed へ移動、失敗時は再試行処理へ

リトライハンドラーの実装

エラーの種類に応じて再試行を制御します。

ファイル: ​/​opt​/​ffmpeg-batch​/​bin​/​retry-handler.sh

bash#!/bin/bash
# retry-handler.sh - FFmpeg エラー時の再試行制御

set -euo pipefail

JOB_FILE=$1
EXIT_CODE=$2
QUEUE_DIR="/opt/ffmpeg-batch/queue"
MAX_RETRIES=3

# 現在のリトライ回数を取得
retry_count=$(jq -r '.retry_count // 0' "${JOB_FILE}")

# エラーコード別の判定
case ${EXIT_CODE} in
  1)
    # 一般的なエラー(再試行可能)
    should_retry=true
    ;;
  137|143)
    # メモリ不足 or 強制終了(再試行可能)
    should_retry=true
    ;;
  255)
    # ファイルエラー(再試行不可)
    should_retry=false
    ;;
  *)
    # 不明なエラー(念のため再試行)
    should_retry=true
    ;;
esac

エラーコードの分類

エラーコード原因再試行
1一般的なエラー
137メモリ不足(SIGKILL)
143強制終了(SIGTERM)
255ファイル破損×

リトライ判定処理

再試行回数の上限をチェックし、キューへの戻し方を決定します。

bashif [[ "${should_retry}" == "true" ]] && [[ ${retry_count} -lt ${MAX_RETRIES} ]]; then
  # リトライ回数を更新
  new_retry_count=$((retry_count + 1))
  jq --arg count "${new_retry_count}" '.retry_count = $count' \
    "${JOB_FILE}" > "${JOB_FILE}.tmp"
  mv "${JOB_FILE}.tmp" "${JOB_FILE}"

  # pending へ戻す(リトライ)
  mv "${JOB_FILE}" "${QUEUE_DIR}/pending/"

  echo "Retry scheduled (${new_retry_count}/${MAX_RETRIES})"
else
  # 再試行不可 or 上限到達
  mv "${JOB_FILE}" "${QUEUE_DIR}/failed/"

  echo "Moved to failed queue"
fi

リトライロジック

  • JSON 内の retry_count を更新
  • 上限未満なら pending へ戻して再試行
  • 上限到達または再試行不可なら failed へ移動

設定ファイルの定義

Worker の動作パラメータを外部ファイルで管理します。

ファイル: ​/​opt​/​ffmpeg-batch​/​config​/​worker.conf

bash# Worker 設定ファイル

# 並列 Worker 数
WORKER_COUNT=4

# 最大リトライ回数
MAX_RETRIES=3

# FFmpeg デフォルトオプション
FFMPEG_DEFAULT_OPTIONS="-c:v libx264 -preset medium -crf 23 -c:a aac"

# ログレベル
LOG_LEVEL="info"

# ディスク容量閾値(MB)
MIN_DISK_SPACE=10240

設定項目の説明

項目説明デフォルト値
WORKER_COUNT並列実行する Worker 数4
MAX_RETRIESエラー時の最大再試行回数3
FFMPEG_DEFAULT_OPTIONSFFmpeg のデフォルトエンコード設定H.264/AAC
MIN_DISK_SPACE処理続行に必要な最小ディスク容量10GB

ジョブ投入スクリプト

新しい動画ファイルをキューに追加するスクリプトです。

ファイル: ​/​opt​/​ffmpeg-batch​/​bin​/​enqueue-job.sh

bash#!/bin/bash
# enqueue-job.sh - ジョブをキューに追加

set -euo pipefail

INPUT_FILE=$1
OUTPUT_FILE=$2
OPTIONS=${3:-"-c:v libx264 -preset medium -crf 23 -c:a aac"}

QUEUE_DIR="/opt/ffmpeg-batch/queue"

# 入力ファイルの存在確認
if [[ ! -f "${INPUT_FILE}" ]]; then
  echo "Error: Input file not found: ${INPUT_FILE}"
  exit 1
fi

# ジョブ ID 生成
JOB_ID=$(uuidgen)
JOB_FILE="${QUEUE_DIR}/pending/${JOB_ID}.json"

# ジョブファイル作成
cat > "${JOB_FILE}" <<EOF
{
  "job_id": "${JOB_ID}",
  "input": "${INPUT_FILE}",
  "output": "${OUTPUT_FILE}",
  "options": "${OPTIONS}",
  "retry_count": 0,
  "created_at": "$(date -Iseconds)"
}
EOF

echo "Job enqueued: ${JOB_ID}"

使用例

bash# 1 本の動画を投入
./enqueue-job.sh /data/input/video1.mp4 /data/output/video1.mp4

# カスタムオプション指定
./enqueue-job.sh input.mp4 output.mp4 "-c:v libx265 -crf 28"

# ディレクトリ内の全ファイルを一括投入
find /data/input -name "*.mp4" | while read file; do
  output="/data/output/$(basename "${file}")"
  ./enqueue-job.sh "${file}" "${output}"
done

ステータス監視スクリプト

処理状況を一覧表示するダッシュボードスクリプトです。

ファイル: ​/​opt​/​ffmpeg-batch​/​bin​/​status-monitor.sh

bash#!/bin/bash
# status-monitor.sh - バッチ処理の状況表示

set -euo pipefail

QUEUE_DIR="/opt/ffmpeg-batch/queue"

clear

echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "  FFmpeg Batch Status Monitor"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""

# キュー状況
pending_count=$(find "${QUEUE_DIR}/pending" -name "*.json" | wc -l)
processing_count=$(find "${QUEUE_DIR}/processing" -name "*.json" | wc -l)
completed_count=$(find "${QUEUE_DIR}/completed" -name "*.json" | wc -l)
failed_count=$(find "${QUEUE_DIR}/failed" -name "*.json" | wc -l)

echo "Queue Status:"
echo "  Pending:    ${pending_count}"
echo "  Processing: ${processing_count}"
echo "  Completed:  ${completed_count}"
echo "  Failed:     ${failed_count}"
echo ""

キュー状況の可視化

実行中のジョブ詳細も表示します。

bash# 実行中のジョブ詳細
echo "Active Jobs:"
if [[ ${processing_count} -gt 0 ]]; then
  find "${QUEUE_DIR}/processing" -name "*.json" | while read job_file; do
    job_id=$(jq -r '.job_id' "${job_file}")
    input=$(jq -r '.input' "${job_file}")
    echo "  [${job_id:0:8}] ${input}"
  done
else
  echo "  (none)"
fi
echo ""

# Worker プロセス状況
echo "Worker Processes:"
pgrep -fa "ffmpeg-worker.sh" | awk '{print "  Worker-" NR ": PID " $1}'
echo ""

# システムリソース
echo "System Resources:"
echo "  CPU Usage:  $(top -bn1 | grep "Cpu(s)" | awk '{print $2}')%"
echo "  Memory:     $(free -h | awk 'NR==2{print $3 "/" $2}')"
echo "  Disk:       $(df -h /opt/ffmpeg-batch | awk 'NR==2{print $3 "/" $2}')"

このスクリプトを watch -n 5 と組み合わせれば、リアルタイムなダッシュボードになります。

systemd サービスの有効化

作成した設定ファイルとスクリプトを systemd に登録しましょう。

bash# 実行権限を付与
sudo chmod +x /opt/ffmpeg-batch/bin/*.sh

# systemd サービスをリロード
sudo systemctl daemon-reload

# サービスを有効化(起動時自動開始)
sudo systemctl enable ffmpeg-batch.service

# サービスを起動
sudo systemctl start ffmpeg-batch.service

# 状態確認
sudo systemctl status ffmpeg-batch.service

起動確認の出力例

text● ffmpeg-batch.service - FFmpeg Batch Processing Service
   Loaded: loaded (/etc/systemd/system/ffmpeg-batch.service; enabled)
   Active: active (running) since 2025-01-20 10:30:15 JST; 5min ago
 Main PID: 12345 (tmux: server)
   CGroup: /system.slice/ffmpeg-batch.service
           ├─12345 tmux: server
           ├─12346 /bin/bash /opt/ffmpeg-batch/bin/queue-manager.sh
           ├─12347 /bin/bash /opt/ffmpeg-batch/bin/ffmpeg-worker.sh 1
           └─12348 /bin/bash /opt/ffmpeg-batch/bin/ffmpeg-worker.sh 2

tmux セッションへのアタッチ

処理状況を直接確認するには、tmux セッションにアタッチします。

bash# セッション一覧を確認
tmux list-sessions

# ffmpeg-batch セッションにアタッチ
tmux attach -t ffmpeg-batch

# ウィンドウ切り替え
# Ctrl+b → n (次のウィンドウ)
# Ctrl+b → p (前のウィンドウ)
# Ctrl+b → 0-9 (番号指定)

# デタッチして戻る
# Ctrl+b → d

tmux ウィンドウ構成

ウィンドウ名内容操作
queue-managerキュー管理スクリプト自動実行
worker-1Worker 1 のログリアルタイム表示
worker-2Worker 2 のログリアルタイム表示
monitorステータスダッシュボード5 秒ごと更新

ログの確認とトラブルシューティング

systemd と個別ログの両方を活用します。

bash# systemd journal でサービス全体のログを確認
sudo journalctl -u ffmpeg-batch.service -f

# 特定の Worker のログを確認
tail -f /opt/ffmpeg-batch/logs/worker-1.log

# エラーログのみ抽出
grep "Failed" /opt/ffmpeg-batch/logs/*.log

# 失敗したジョブの確認
ls -lh /opt/ffmpeg-batch/queue/failed/

よくあるエラーと対処法

エラーコード原因対処法
Error 137 (SIGKILL)メモリ不足Worker 数を減らす、swap を増やす
Error 1 (Generic)FFmpeg オプション誤りジョブファイルのオプションを確認
Lock timeout排他制御の競合Worker 数を調整する
Disk spaceディスク容量不足古いファイルを削除、ディスク追加

運用フローの図解

最後に、ジョブ投入から完了までの運用フローを図示します。

mermaidsequenceDiagram
  participant User as 運用者
  participant Enqueue as enqueue-job.sh
  participant Queue as キュー (pending)
  participant Worker as Worker
  participant FFmpeg as FFmpeg
  participant Retry as retry-handler.sh
  participant Complete as 完了 (completed/failed)

  User->>Enqueue: ジョブ投入
  Enqueue->>Queue: JSON ファイル作成
  Queue->>Worker: ファイル取得 (flock)
  Worker->>FFmpeg: エンコード実行

  alt 成功
    FFmpeg-->>Worker: exit 0
    Worker->>Complete: completed へ移動
  else 失敗
    FFmpeg-->>Worker: exit 1
    Worker->>Retry: エラーハンドリング
    alt リトライ可能
      Retry->>Queue: pending へ戻す
    else リトライ不可
      Retry->>Complete: failed へ移動
    end
  end

  Worker->>Queue: 次のファイル取得

シーケンス図の要点

  • ユーザーはジョブ投入スクリプトを使用
  • Worker がキューから排他的にファイルを取得
  • 失敗時はリトライハンドラーが再試行判定
  • 完了または失敗キューへ最終移動

まとめ

本記事では、FFmpeg バッチ処理を systemdtmux で堅牢に運用する方法を解説しました。

実装のポイント

  • systemd でサービス化し、自動起動・再起動を実現
  • tmux でセッションを永続化し、SSH 切断に対応
  • ファイルベースキューでシンプルかつ確実な処理管理
  • flock による排他制御で複数 Worker の競合を防止
  • エラーコード別リトライで適切なエラーハンドリング

運用上のメリット

  1. 自動復旧: サーバー再起動後も処理が自動再開されます
  2. 可視性: ログとダッシュボードで処理状況を常に把握できます
  3. スケーラビリティ: Worker 数を調整して処理能力を簡単に変更できます
  4. 保守性: 設定ファイルとスクリプトを分離し、変更が容易です

大量の動画処理を安定的に運用したい場面で、この設計パターンがお役に立てば幸いです。

関連リンク