Synology Driveの双方向同期で190GiBの重複削除を事故ゼロで終えた —— Claude Codeに実行を任せ、4つのゲートとごみ箱経由の再突合で消した手順

シリーズ: NASの重複整理

Windows機の D:\SynologyDrive には、NAS と双方向同期している古いデータの「仮置き」フォルダがありました。ここにある重複ファイル 194件・190.90GiB を、7回に分けて消しました。事故は0件です。この整理作業を、私の作業記録では「P22」と呼んでいます(このWindows機での作業の通し番号です)。

実行は Claude Code が担当し、Mac Studio から SSH で Windows と NAS を操作しました。私は各段階の承認だけをしています。この記事では、消し方の骨組みである「4つのゲート」と、最後の取り消し手段であるごみ箱を空にするまでの手順を書きます。

結論: 7バッチ・194件・190.90GiB を消し、事故は0件でした

項目 結果
削除 7バッチ・194件・204,976,435,322バイト(190.90GiB)
削除後の確認 194/194 PASS
D: の空き 567.5GiB → 758.38GiB(+190.9GiB。削除量と一致)
NAS の空き 削除の直後は −7.05MiB(実質変わらず)。ごみ箱を空にした 10/24 に +190.98GiB
完了条件 全817行の最終突合 PASS(削除した194行と、対象外にした623行の両方)
期間 削除は 2026-09-14 の1日。ごみ箱を空にしたのは約6週間後の 2026-10-24

D:の空き容量の折れ線グラフ。横軸はB1前・B1後〜B7後の8点、縦軸は空き容量(GiB)で、567.5から758.38まで階段状に増える。B5〜B7で大きく跳ねる。右側に別の小さなグラフで、NASの空き容量を「削除直後」「ごみ箱を空にした後」の2点で示し、削除直後はほぼ横ばい、空にした後に+190.98GiB跳ねる

やったことを一言でまとめると、各段階に「現物を見て、ずれていたら止まる」ゲートを置き、最後の取り消し手段は数週間あとに、記録と1件ずつ照合してから消した、です。

なぜ慎重になる必要があったか: D: で消すと NAS からも消える

Synology Drive Client の同期は双方向で、ローカルでの削除を NAS に反映する設定(ignore_local_remove=0)でした。つまり、

  • D: で消すと、NAS の同じファイルも消える(NAS のごみ箱には入る)
  • NAS の空きは、ごみ箱を空にするまで戻らない。削除の直後に測った NAS の空きは、削除前から −7,393,280バイトで、ほとんど変わりませんでした。同じボリューム内のごみ箱に移っただけだからです
  • 手元(D:)の容量を空けたいだけなら、削除ではなくオンデマンド化(ファイルを NAS 側だけに置き、ローカルは実体なしにする)を使うべき

今回の目的は、重複そのものを消して D: と NAS の両方の容量を取り戻すことでした。なので削除を選びましたが、「消す」が同期で NAS にまで届くので、1件の間違いが両方から消えることになります。

D:とNASの双方向同期の流れ図。左にWindows機のD:\SynologyDrive、右にNAS。D:でファイルを削除すると、矢印で同期を経てNAS上の同じファイルが消え、NASのごみ箱フォルダに移る。ごみ箱の下に「ここを空にするまでNASの空きは戻らない」と注記

前提: 重複はハッシュで確定済み、守るものは先に決めた

  • 仮置きフォルダは約827GB、2000〜2022年のデータが入ったコールドアーカイブです
  • 棚卸しの結果、仮置きの外にあるファイルとの完全重複は 8.1GB しかなく、重複のほとんどは仮置きの中どうしでした
  • 重複は、前のフェーズで SHA256 を取って確定させ、817行のリストにしてありました。各行には「消す側」と「残す側」のパスが入っています

同じ時期に、別件として、長年探していた家族の大事な動画の捜索を始めました。過去の整理で消してしまったかもしれないという心当たりがあったので、見つかるまで削除フェーズを止めました。動画は NAS の中で見つかり、そのコピーの1つは実際に削除候補に入っていました。動画を別の場所に保全し、見つかった場所を「恒久除外」(どの整理作業でも削除しない場所)として登録してから、削除を再開しました。この話は 重複削除の除外リストが削除経路で効いていなかった に書きました。

全体の流れ

ゲートの流れ図。左から順に「dry-run(全817行を判定)」→「plan(バッチの直前の再確認)」→「承認(私)」→「execute(Windows側でも1件ずつ再確認)」→「同期完了を待つ(syncstate)」→「verify(全行PASS)」の箱が並び、7バッチ分を繰り返す矢印。その後に「最終突合(全817行)」→「約6週間待つ」→「ごみ箱を空にする(記録と照合)」。各箱の下に赤字で止まる条件を1行ずつ(例: planとずれたら中止、保護対象なら拒否、同期の根拠がそろうまで判定しない)

ゲート 何を確かめるか ずれていたら
1. dry-run 全817行を、消さずに判定 その行を対象から外す
2. plan + 承認 バッチごとに、承認の直前に現物を読み直す 問題が1件でもあれば止まる
3. execute Windows 側で、1件ずつ範囲・保護・サイズ・日時をもう一度 その1件を消さない
4. syncstate → verify 同期が終わってから、消えたこと・残す側が無事なことを確かめる 根拠がそろうまで判定しない

ゲート1: dry-run —— 消さずに全行を判定する

最初に、817行すべてについて「今消してよいか」を読み取りだけで判定しました。基準は次の7つです。

  1. 消す側と残す側が両方、D: に実在する
  2. サイズがバイト単位で一致する(リストの MB 表記は丸めなので使わない)
  3. 両方の更新日時が、ハッシュを取り始めた日(2026-07-28)より前
  4. 消す側と残す側が、NAS にも同じサイズで実在する
  5. 消す側が恒久除外の配下ではない
  6. 0バイトではなく、ドットファイルでもない
  7. 残す側が、別の行で消す側になっていない

結果は、OK(削除対象)194行・190.90GiB、対象外623行・約0.2MB でした。

対象外のうち584行は0バイトのファイルです。中身が空のファイルは、どれも同じハッシュになります。.temp のような空ファイルが、まったく関係のない別の空ファイルと「重複」と判定されていました。容量の効果もないので、0バイトとドットファイル(.DS_Store など)は最初から外しました。

再ハッシュはしない、と決めた

dry-run で SHA256 を取り直すことも考えました。しかし約191GB を HDD から読み直すことになり、時間もディスクへの負荷も大きくなります。内容の一致はハッシュで確定済みで、その後に変更されていないことはサイズと更新日時で確かめられます。

受け入れたリスクは「更新日時を元に戻すような変更は検出できない」ことです。普段は触らないコールドアーカイブなので、低いと判断しました。

ただし例外があります。B5〜B7 の3バッチは、残す側がどれも同じ31本でした。B7 を消すと、その31本が最後の1組になります。そこで B7 の前だけは、残す側の部分ハッシュも取り、朝の値と一致することを確かめてから消しました。

ゲート2: バッチごとの承認 —— 前の承認を次に流用しない

194行をフォルダ単位で7バッチに分け、小さい順に進めました。

バッチ 対象(種類) 件数 容量
B1 写真フォルダ 7 5.68GiB
B2 小さなフォルダ群 27 3.04GiB
B3 スマホのバックアップ 44 2.14GiB
B4 音声配信の素材 22 8.76GiB
B5 動画編集プロジェクトA の素材 + 1件 32 57.69GiB
B6 動画編集プロジェクトB の素材 31 56.80GiB
B7 動画編集プロジェクトC の素材 31 56.80GiB

7バッチの棒グラフ。横軸にB1〜B7、縦軸に容量(GiB)。B1〜B4は10GiB未満の低い棒、B5〜B7は約57GiBの高い棒。各棒の上に件数(7, 27, 44, 22, 32, 31, 31)を表示

体制は次のとおりです。

  • Claude Code が、バッチの plan(直前の再確認)を作り、結果を報告する
  • 私が、報告を読んで「このバッチを消してよい」と承認する
  • 承認を受けて、Claude Code が execute と、その後の確認を行う

承認は1バッチごとに取り、前のバッチの承認を次のバッチに使い回さないことをルールにしました。B1 がうまくいったから B2 もよい、とはしない、ということです。

plan は承認の直前に再実行します。dry-run のあとに何かが変わっていたら(サイズ・日時・NAS 側の状態・ごみ箱に同じパスの先客がいるか)、問題として出して止まります。execute も、plan とのずれが1件でもあれば全体を中止します。

ゲート3: 削除の直前に、実行側でももう一度確かめる

Mac 側で確認が済んでいても、Windows 側の削除スクリプト(win_delete.ps1)が1件ずつもう一度確かめてから消します。

  • 仮置きフォルダの配下であること
  • 保護リストに載っていないこと(保護リストが届かなければ、全件を中止)
  • サイズと更新日時が plan のときと同じであること

止まっても、削除は0件だった

B5 の execute は2回、途中で止まりました。どちらも削除は0件です。

原因は、Mac から Windows の PowerShell へ ssh の標準入力でデータを渡す部分でした。試験用のスクリプトで同じ呼び出しを20回行うと、1回が20秒以上止まりました。B1〜B4 では、たまたま当たらなかっただけです。

2回目に止まったとき、Windows 側には削除スクリプトのプロセスが入力待ちのまま残っていました。放置すると、セッションが閉じたときに入力の終わりが届いて、記録が残らないまま削除が走るおそれがありました。コマンドラインを確かめて自分の削除スクリプトだと特定してから、そのプロセスだけを止めています。

対策として、Windows への入力は ssh の標準入力を一切使わず、gzip+base64 にしてコマンドの中に埋め込む方式に変えました。変更後に40回送って停止は0回で、その後 plan からやり直しています。

ゲート4: 同期が終わるまで判定しない

D: で消した直後に NAS を見ても、まだ同期が終わっていません。そこで「削除後の確認(verify)」は、同期完了の根拠がそろってから実行しました(syncstate)。

根拠 内容
ファイルごとの完了 消した完全パスごとに、ローカルの削除イベントの完了と、サーバー側の確認がそろう
キューが空 その後に、同期のキューが空になったというログがある
NAS 側 NAS の元の場所に、消したファイルが1件も残っていない

ログは削除を始めた5分前から読みます。Windows の時計が1〜2秒遅れることがあり、B1 では同期ログの1件が読み出し範囲の外に落ちたためです。B3 では、件数がずれて見えた1件(特殊な文字を含む名前)が、別の形のサーバー確認として届いていたと分かり、判定を完全パス単位に改めました。

verify で確かめるのは次の4点で、全行 PASS が条件です。

  1. 消す側が D: から消えている
  2. 消す側が NAS の元の場所から消え、NAS のごみ箱に同じサイズで入っている
  3. 残す側が、D: と NAS の両方に、確認時と同じサイズで存在する
  4. 同期の保留イベントがない

完了条件は「記録が全部 PASS」ではなく「現物の最終突合」

7バッチの verify がすべて PASS しても、それは実行記録の上での話です。記録にない変化、たとえば別の端末からの操作や同期の巻き戻しは、記録の集計からは見えません。

そこで完了条件を「全817行の現状を、読み取りで突合して PASS すること」にしました(phase2_final_reconcile.py)。

区分 確かめたこと 結果
削除した194行 消す側が D: と NAS に無く、ごみ箱に同じサイズである。残す側は D: と NAS に同じサイズである 194/194
対象外の623行 dry-run のときから、存在・サイズ・日時が変わっていない 623/623
保護リスト 登録どおりの種類で実在する 104/104
恒久除外 ルートが実在し、NAS 上のファイル数が登録時と一致する 4/4

「消したもの」だけでなく、「消さなかったもの」が変わっていないことも完了条件に入れたのがポイントです。

最後の取り消し手段を消す: ごみ箱は6週間待ち、P22 分だけ消した

削除したファイルは、NAS のごみ箱に残っています。これが最後の取り消し手段です。すぐには空にせず、全バッチの完了から数週間たってから、改めて承認を取って空にすることにしました。問題が出たときに、ごみ箱から戻せる余地を残すためです。

約6週間後の 10/24 に、次の順で進めました。

  1. 承認の申請に、その時点の最終突合(PASS)を添える
  2. ごみ箱の中身を一覧にし、実行記録と1件ずつ照合する
  3. 削除の直前に、残す側と保護リストの健在を再確認する。最後の1組になった31本は部分ハッシュも取る(31/31 一致)
  4. 削除スクリプトを dry-run し、4条件(195件(削除した194件と、7月に試しに1件だけ消したテスト削除の分)・容量が一致 / 全件が仮置きの配下 / 不一致0 / P22 以外0件)を満たしたら本番
  5. 本番: 195件・204,986,674,102バイトを削除。その後、空になったフォルダとサムネイル類(513件)を削除
  6. 削除後: P22 以外の97エントリが前後で変わっていないこと、NAS の空きが +190.98GiB になったことを確認

ごみ箱を「丸ごと」空にしなかった理由

ごみ箱には、P22 とは関係のない別のフォルダから、9/17 に削除された66件も入っていました。ごみ箱ごと空にすると、それが誤削除だった場合に戻せなくなります。なので、記録とパス・サイズが一致するものだけを、パスを指定して消しました。

検討したが選ばなかったこと

選択肢 選ばなかった理由
全行を再ハッシュしてから消す 約191GB の再読込の時間と HDD の負荷が大きい。内容の一致は確定済みで、変更がないことはサイズと日時で確かめられる
削除の直後にごみ箱を空にする 取り消し手段がなくなる。容量の回復は急ぐ理由がなかった
ごみ箱ごと空にする P22 と無関係な削除分(66件)を巻き込む
NAS の管理画面(File Station)で P22 のフォルダごと消す 手軽だが、記録との1件ずつの照合ができない
実行記録の集計が一致したら完了とする 記録にない変化(別の操作や同期の巻き戻し)を見落とす

まとめ: 次にやるならこうする

やること 理由
守るものを先に決めて、ツールで拒否させる 注意書きでは止まらない
dry-run で全行を判定し、0バイトとドットファイルは外す 空ファイルどうしの偽の重複が混ざる
承認はバッチごとに取り、plan を承認の直前に読み直す 前の承認は、今の状態を保証しない
実行側(Windows)でも1件ずつ確かめる Mac 側の確認から削除までに状態が変わりうる
同期の根拠がそろうまで判定しない 削除直後の確認は意味がない
完了条件は現物の突合。消さなかったものも含める 記録の集計では見えない変化がある
ごみ箱は数週間待ち、記録と照合した分だけ消す 最後の取り消し手段だから

今回は削除のあとから、消したフォルダに「ここに何があったか」を書いたテキストファイルを置きました。次からは削除と同時に置く手順にしています。詳しくは 重複ファイルを消したフォルダに「墓標ファイル」を置く に書きました。

同じゲートの考え方を、削除ではなくコピー(移行)に使った例が Evernote移行の記事 です。

ただし、事故が0件だったことは、守りがすべて効いていたことを意味しません。あとで点検すると、恒久除外は実行の経路では見られていませんでした。その話は 重複削除の除外リストが削除経路で効いていなかった に続きます。