Redmineの添付ファイル移行でデータを失わない手順 ― CSVでは運べない実体ファイルの棚卸しと突き合わせ
チケットのCSV移行は成功したのに、添付ファイルだけリンク切れになる。Redmineが添付を「DBのメタ情報」と「filesディレクトリの実体」に分けて持つ仕組みから、移行で起きる5つの事故、移行前の棚卸しSQL、移行後の突き合わせ手順までを実例つきで解説します。
「チケットのCSV移行はうまくいった。担当者もコメントも期日も、ちゃんと入っている。 ……なのに、添付ファイルのリンクをクリックすると全部404になる」
Redmine からの移行で、最後に残って、いちばん静かに失敗するのが添付ファイルです。
チケットの本文やステータスは、CSV を開けば目で見て確認できます。ところが添付ファイルは「移行したつもり」の状態でも画面上は添付欄にファイル名が並んで見えることがあり、実際にクリックされるまで壊れていることに気づけません。気づくのは移行から数週間後、「去年の仕様書が開けない」と言われたときです。
この記事では、Redmine が添付ファイルをどう持っているのかという仕組みから、移行で実際に起きる事故、そして移行の前後で何を数えておけばよいかを整理します。
1. なぜ添付ファイルだけ別問題になるのか
理由は単純で、CSV はメタデータしか運べないからです。
Redmine のチケットを CSV でエクスポートすると、添付ファイルの欄に入るのは「ファイル名」か、せいぜい URL です。ファイルの中身(バイト列)は CSV の中に存在しません。
つまりチケットの移行と添付ファイルの移行は、別々の作業です。ここを1つの作業だと思っていると、CSV の取り込みが成功した時点で「移行が終わった」と判断してしまいます。
2. Redmine は添付を「2か所」に分けて持っている
DB側:attachments テーブル
| 列 | 中身 |
|---|---|
| container_id / container_type | どのチケット(またはWiki・文書)に付いているか |
| filename | 利用者が見る元のファイル名(例: 要件定義_v3.xlsx) |
| disk_filename | ディスク上の実際の保存名(例: 260901120000_a1b2c3.xlsx) |
| disk_directory | 保存先のサブディレクトリ(例: 2026/09) |
| filesize / content_type | サイズとMIMEタイプ |
| digest | 中身のハッシュ |
ディスク側:files/ ディレクトリ
実体は Redmine の files/ 配下(設定によっては attachments_storage_path で指定した場所)に、年月のサブディレクトリを切って置かれています。
ここで最初の落とし穴があります。ディスク上のファイルは元の名前では保存されていません。 disk_filename という別名に置き換わっており、attachments テーブルの行と突き合わせて初めて「これは要件定義_v3.xlsx だ」と分かります。
だから、files/ ディレクトリだけをコピーしても復元できませんし、DBだけを移行してもファイルは1つも付いてきません。
3. 移行で実際に起きる5つの事故
① 実体ファイルが1つも来ていない
いちばん多いパターンです。CSV にファイル名が入っているので、取り込み先の画面には添付の行が表示されます。しかし中身が無いので、クリックすると 404 になります。
見つけ方: 移行先で添付を数件、実際にダウンロードして開いてみる。一覧表示だけで確認を終えないこと。
② 日本語ファイル名が壊れる
filename は日本語を含むことが多く、CSV の文字コードや、ZIP でまとめたときの圧縮ツールの仕様によって簡単に壊れます。ZIP の仕様上ファイル名の文字コードは環境依存になりがちで、Windows で作った ZIP を Linux 側で展開すると文字化けする、という組み合わせは今でも普通に起きます。
対策: ファイル名は ZIP のエントリ名に頼らず、attachments テーブルから取った filename を正とする。実体の受け渡しは disk_filename で行い、表示名はDBから復元します。
③ 同じファイルが大量に複製される
取り込みスクリプトを「途中で失敗したからもう一度」と再実行したときに起きます。1回目にアップロードされた実体はストレージに残ったまま、2回目がまた新しいキーでアップロードするためです。
これは私たち自身が踏みました。取り込みを繰り返した結果、同一の画像が383件、同一のPDFが156件という状態でストレージに積み上がっていました。
対策: 取り込みは冪等(べきとう)にする。digest(ハッシュ)や「元の attachment ID」をキーにして、既に入っているものはスキップする設計にします。
④ どのチケットにも紐づかない「孤児ファイル」が残る
③の続きに起きる、もっと厄介な事故です。
アップロードは成功したがチケットへの紐づけに失敗した分、あるいは紐づけ先のチケットが後から消された分が、ストレージにだけ残り続けます。画面のどこにも出てこないので、誰も気づきません。
私たちのケースでは、ストレージ上の 11,832 件のうち、どこからも参照されていないファイルが 11,810 件・約1.6GBありました。実際に使われていたのは22件・9MBだけです。99.8%がゴミだったことになります。
さらに悪いことに、これはストレージの無料枠(1GB)を突き破り、プロジェクト全体が停止しかねない状態を作っていました。添付ファイルの移行ミスは、リンク切れだけでなく請求と可用性の問題にもなります。
対策: 移行後に「ストレージにある実体」と「DBから参照されている実体」を突き合わせ、差分をゼロにする。やり方は後述します。
⑤ 公開範囲が移行前よりゆるくなる
Redmine ではプロジェクトの権限に従って添付の閲覧可否が決まりますが、移行先がオブジェクトストレージの場合、バケットを公開設定にした瞬間に「URLを知っていれば誰でも取得できる」状態になります。
社内の見積書や個人情報を含む資料が、認証なしで取得できる URL に置かれていないか。移行の最後に必ず、ログアウト状態で添付URLを開いて確認してください。
4. 移行前にやる棚卸し
移行を始める前に、数えておくことが決定的に重要です。移行後に「合っているか」を判断する基準が、これしかないからです。
Redmine のDBに対して、次を出しておきます。
-- 全体の件数と容量
SELECT COUNT(*) AS files, SUM(filesize) AS bytes FROM attachments;
-- 添付の付き先の種類ごと(チケット / Wiki / 文書 …)
SELECT container_type, COUNT(*), SUM(filesize)
FROM attachments GROUP BY container_type ORDER BY 2 DESC;
-- 最大サイズ(移行先のアップロード上限を超えるもの)
SELECT filename, filesize FROM attachments ORDER BY filesize DESC LIMIT 20;
-- 中身が同じファイルの重複(移行前から重複している分)
SELECT digest, COUNT(*) FROM attachments
GROUP BY digest HAVING COUNT(*) > 1 ORDER BY 2 DESC LIMIT 20;この4つの数字を紙に書き出しておきます。とくに1つ目の「件数」と「合計バイト数」は、移行後に必ず突き合わせる対象です。
あわせて、ディスク側も数えます。
# 実体ファイルの件数と容量
find /path/to/redmine/files -type f | wc -l
du -sb /path/to/redmine/filesDBの件数とディスクの件数が移行前の時点で合っていないなら、それは移行の問題ではなく、すでに運用中に壊れていたということです。先に原因を切り分けてください。
5. 移行後にやる3つの突き合わせ
突き合わせ1:件数と容量
移行前に控えた「件数」「合計バイト数」と、移行先の件数・容量を比べます。ここがズレていたら、それ以上進まないこと。
突き合わせ2:参照されていない実体はゼロか
移行先のストレージにあるファイルのキー一覧と、DB側が参照しているキー一覧を突き合わせ、「ストレージにあるがDBから参照されていないもの」を数えます。
ここが0でなければ、事故③④が起きています。私たちはこの確認を移行の数週間後にやったために、1.6GBのゴミを抱えたまま運用していました。
突き合わせ3:実際に開けるか
件数が合っていても、中身が壊れていることがあります。 拡張子ごとに数件ずつ、実際にダウンロードして開いてください。とくに次は必ず確認します。
- 日本語ファイル名のもの(文字化けしていないか)
- Office 系(xlsx / docx)(サイズは合っているのに開けない、が起きやすい)
- 10MBを超える大きいもの(途中で切れていないか)
- ログアウト状態でURLを叩いた場合(取得できてしまわないか)
6. チェックリスト
移行当日に手元に置いておく用です。
- 移行前の件数・合計バイト数を控えた
- 拡張子の分布を確認し、移行先のMIME制限に引っかかるものを把握した
- 最大ファイルサイズが移行先の上限を超えていないか確認した
- 移行前の時点でDB件数とディスク件数が一致していることを確認した
- 取り込みスクリプトが冪等(再実行しても重複しない)になっている
- 移行後、件数と合計バイト数が一致した
- 移行後、参照されていない実体がゼロであることを確認した
- 日本語名・Office系・大容量・ログアウト状態の4パターンで実際に開いた
- 添付を含むバケット/ディレクトリの公開範囲を確認した
7. まとめ
添付ファイルの移行は、チケットの移行とは別の作業です。そして失敗が静かなので、数えて突き合わせる以外に検証する方法がありません。
- Redmine の添付は「DBのメタ情報」と「files ディレクトリの実体」に分かれている
- CSV が運ぶのは前者だけ
- 事故は「対応づけが切れるところ」で起きる
- 移行前に件数・容量・最大サイズ・重複を数えておく
- 移行後に「件数」「参照されていない実体」「実際に開けるか」の3つを突き合わせる
とくに④の孤児ファイルは、リンク切れとして表に出ないままストレージの請求と容量上限を静かに圧迫します。移行直後に必ず一度、突き合わせてください。
Taski(タスキ)では、Redmine からの移行を前提に、チケットとあわせて添付ファイルを取り込む導線を用意しています。移行の計画段階でご相談いただければ、棚卸しの観点からお手伝いできます。
手元の Redmine CSV が移行できるか「1秒診断」
機密データを含むCSVファイルでも安心。ブラウザ上だけで解析するため、サーバーにデータは一切送信されません。文字コード・必須項目・適合度スコアを即座に判定します。
あわせて読みたい関連ノウハウ
RedmineのCSVインポートで失敗する5大原因と解決策 ― 文字コード(Shift-JIS/UTF-8)・日付形式・必須項目エラーを完全攻略
Redmineでのチケット一括登録や、他ツールへの移行エクスポート時に頻発する「文字化け」「日付パース失敗」「改行による列ズレ」「必須項目エラー」の具体的な原因と回避策を完全網羅。実務で役立つチェックリスト付き。
オンプレミスRedmineからクラウド移行する完全ガイド ― サーバー保守費・バージョン追従の限界を脱出する5つのステップ
自社サーバーや社内情シスで抱え込みがちなオンプレRedmine。ハードウェア老朽化・Ruby/RailsのEOL対応・セキュリティリスクを解消し、クラウドSaaSへスムーズに移行するための費用対効果の試算から社内稟議、段階的移行手順を徹底解説。
Redmineの主要プラグイン12選の移行・代替マップ ― ガントチャート・アジャイル・WikiはSaaSでどう再現するか
Redmine運用で必須とされるアジャイルカンバン、拡張ガントチャート、クリップボード画像貼り付け、View Customizeなどのプラグイン機能が、現代的なタスク管理SaaSでどう標準化・代替されるかを徹底整理。プラグイン依存でバージョンアップが止まった環境からの脱出ガイド。
Redmine の移行・併走を、リスクゼロで試してみませんか?
メアド登録不要の「1秒デモ」で直感的な操作感をすぐにお試しいただけます。