わしらは怪しいコピー隊 ― 138個だけが、どうしても写らないのだ
移行試験でファイル一式を写すことになった。408GB、73万個。Finderはぐんにゃり止まり、rsyncに乗り換えたら今度は138個だけが写らない。犯人はファイル名の切られ方であった。
私はこれまで、ファイルのコピーというものをFinderでしかやったことがなかった。
つまむ。運ぶ。落とす。それで四十年ぐらい生きてきたのである。rsync という名前は聞いたことがあったが、あれはヒゲを生やした本物のエンジニアが黒い画面に向かってカタカタやるやつであって、私のような者が触るものではない、と勝手に決めていた。
その考えが、この日、根こそぎひっくり返ったのだった。
Finderが、ぐんにゃりと止まった
いま、長く使われてきたグループウェアをLinuxへ移す試験をやっている。
データベースだけ運べば終わり、というわけにはいかない。添付ファイル、決裁の途中でできる中間ファイル、PDF、共有された書類 — 実体のファイル群が、ぞろぞろ付いてくるのである。移行試験というのは、要するにこのぞろぞろを本当に運べるのか確かめる作業だ。
で、そのファイル一式を別のディスクへ写すことになった。
408GB。ファイルの数、736,977個。
73万である。ついでにフォルダの数を数えたら47万個あった。フォルダである。中の書類ではない。入れ物だけで47万個あるのだ。
なぜそんなに入れ物があるのかというと、書類のIDごとにフォルダを作る作りだからである。パスはこういう形をしている。
WorkflowAttcDocs/700_15299/files/00_【県地域政策課7月16日〆】….pdf
決裁が一件起きるとIDのフォルダができ、その下に files ができ、そこへ添付が入る。一件につき入れ物がふたつ増える。 だから47万という数は、書類の数がそのまま形になったものなのだった。1フォルダあたりのファイル数は平均1.6個。がらんとした入れ物が47万個ある、と言ったほうが実態に近い。
そしてこの作りが、あとで効いてくる。コピーというのは容量よりも個数で遅くなる。408GBの映画一本ならあっという間だが、73万個に割れているとそうはいかない。Finderがぐんにゃりしたのも、たぶんこれが理由である。
中身の内訳はこうなっていた。
| 階層 | 容量 | 中身 |
|---|---|---|
docu | 306GB | 文書管理の本体 |
WorkflowAttcDocs | 55GB | 決裁の添付 |
notice_attc_docs | 25GB | 通知の添付 |
WorkflowPdfFiles | 17GB | 決裁PDF |
shared_files | 2.4GB | 共有ファイル |
一個あたり平均580KB。スキャンした紙の書類が、そのままの重さで73万枚積んである、と思えばだいたい合っている。役所というのはこれだけの紙を電子にして抱えているのだと、このとき初めて数字で見た。
いつもの調子でFinderに落とした。ドラッグして、ぽとん。残り時間の表示がぴょこんと出て、それから、減らない。
減らないのである。
数分ながめて、私は中断した。こういうときはビールを飲んで寝るに限るのだが、まだ昼である。飲めない。
念のため書いておくと、Finderのコピーが最後まで通るのかどうかは、結局のところ確かめていない。 確かめる気にならなかったのだ。仮に三時間かかるとして、二時間半のところで何かが起きたらどうなるか。そこまでの二時間半はきれいさっぱり消える。どこまで写ったのかも分からない。また最初からやり直すのである。
この「途中でこけたら、続きから進めない」というのが、Finderの一番いけないところだと思う。数十MBならなんということはないが、400GBになると、これは急に恐ろしい性質に化ける。
rsyncというヤツは速かった。しかし手では触りたくない
そこで生まれて初めて rsync を使った。
こいつは差分だけを送る。だから途中でこけても、もう一度実行すれば続きから進む。 属性もそのまま保つ。400GBの恐ろしさは、これで原理的に消える。むむむ、と私はうなった。なんと頼もしいヤツであるか。
しかし手で打つのは、どうしてもイヤだった。理由はふたつある。
ひとつ、覚えられない。 -a だの -h だの --exclude だのを、次に使うときまで覚えている自信が私にはまるでない。半年後の私は、たぶんまたゼロから調べている。というか、半年前に調べたことすら忘れている。
ふたつ、間違いも同じ勢いで実行される。 --delete をひとつ足すと、コピー先にあったものが消える。Finderなら「同じ名前がありますけど」と一応おうかがいを立ててくるが、こいつは訊かない。打ち間違えたら、打ち間違えたとおりに、堂々と実行する。 頼もしいというのは、そういうことでもあるのだった。
というわけで、管理ツールのボタンにした。コピー元とコピー先を選んで、押す。裏で rsync がずんずん走り、進捗と残り時間と速度が出る。
これが、思っていたよりずっと効いた。
169MB/秒。 Finderの前でぐんにゃりしていた400GBが、七分ほどの仕事になったのである。
効いていたのは速さではなく、「続きから進める」というあの性質のほうだった。
そして138個が、どうしても写らなかった
ところが、現地で移行試験をぐるぐる回している最中に、それは起きた。
コピーがエラーで終わったのである。
進捗は最後まで行っているのに、結果は失敗。ログを開いたら、同じ形の行がずらずらずらと並んでいた。
rsync: mkstemp ".../.00_\#343\#200\#220\#347\#234\#214…\#343\#201.L3IjAT"
failed: Illegal byte sequence (92)
……(同じ形の行が138行)
rsync error: some files could not be transferred (code 23) [sender=2.6.9]
184KB、138個。 ファイル名は \#343\#200\#220 の羅列で、どのファイルのことなのかすら読めない。呪文である。
73万個のうちの138個。率にすれば0.019%。運べたほうが圧倒的に多いのだが、それで安心できるかというと、まるでできない。役所の書類は「99.98%は運べました」で済む種類のものではないからである。
正直に告白すると、私はこの時点で「ははあ、rsyncは日本語のファイル名に弱いのだな」と片づけそうになった。そこで納得していたら、この移行試験そのものが止まっていた。 役所のファイル名は日本語なのである。日本語が運べない道具では、そもそも引っ越しができない。
というわけで、犯人を突き止めることにした。
254バイトで、そろいもそろって
ログの \#NNN は、8進数のバイト表記だった。これを元のバイトに戻して、失敗した名前を一個ずつ測ってみた。
すると、そろいもそろって同じ形が出てきたのである。
- 失敗した名前は、138個すべてがきっかり254バイト
- 138個すべてが、末尾でUTF-8が壊れている(
\xe3\x81のように、三バイト文字の二バイト目でぶつりと切れている) - 落ちたのは
docuが120個、WorkflowAttcDocsが18個 — いちばん量の多い階層から出ている。どれも役所の長い件名が、そのままファイル名になったやつ
つまり、こういうことだった。
rsyncは、いったん .<元の名前>.XXXXXX という一時ファイルに書いて、あとから名前を付け替える。 このとき一時名を255バイトに収めようとして、元の名前を切り詰める。ところが切るのがバイト単位なので、日本語名は文字の途中でぶつりといかれる。
254バイト = 1(".") + 246(切り詰められた名前) + 7(".XXXXXX")
そうしてできた壊れたUTF-8の名前を、コピー先のAPFSが「そんな名前は受け付けん」と突き返す。 Illegal byte sequence (92) は、その突き返しの声だったのだ。
日本語のファイル名が悪いのではなかった。元の名前が246バイト(日本語でだいたい82文字)を超えたやつだけが、一時名を作る段階でぽろぽろ落ちていた。ファイルの中身にも、コピー元にも、傷ひとつ付いていない。
決め手は、ログのいちばん最後にあった [sender=2.6.9] である。macOS 14.4より前のMacは、rsyncの中身が古い。同じことを新しいMacでやっても、これは再現しない。 現地のあの機体でだけ起きる、たいへん地味な事件だったのである。
一時ファイルを作らなければいいのだ
一時ファイルの名前でこけているのだから、一時ファイルを作らなければいい。
--inplace を付けると、rsyncは最終的な名前でいきなり書きにいく。あの経路を通らないので、mkstemp そのものが呼ばれない。
代償はある。途中で止めると、書きかけのファイルがコピー先に残る。ただしrsyncは最後まで書けたときにだけ更新日時を合わせるので、もう一度実行すれば写し直される。そもそもrsyncを選んだ理由である「続きから進める」が、ここでもちゃんと効いたのだった。
ボタンの中に、決めごとを埋める
犯人が分かったあと、直したのは --inplace の一行だけではない。
手で打つ道具のままなら、「次からは --inplace を付けること」と手順書に書いて終わりである。そして半年後の私は、その手順書を読まない。 読まないというか、そんなものがあったことを忘れている。ボタンの中に入っているからこそ、決めごととして固められた。
--deleteは持たせない。 コピー先を指定し間違えたときに、そこにあったものまで消える。差分更新はこいつ無しで立派に成り立つ- 除外はmacOSの残骸だけ。 軽くしようとして添付を除く既定など作ると、いちばん試験したいものが最初から欠けた写しができあがる
- コピー先がコピー元の中なら、押す前にはじく。 これをやるとrsyncは自分が書いたファイルを読み続け、ディスクを埋め尽くすまで止まらない。健気だが、たいへん困る
- コピー中に外付けが外れたら止める。 macOSは取り外すと
/Volumes/<名前>ごと消えるので、書き込み先が黙って内蔵ディスクのただのフォルダに化ける。パスは同じ顔をしているので、パスを見ても気づけない(三秒ごとにデバイスが変わっていないか見張ることにした) - フォルダ名の既定は「元の名前機体名日付」。 写しというのは複数の時点のものが一台のディスクに並ぶ。名前が同じだと、どれがいつのものやら分からなくなり、そのうち上書きする
それから、失敗の伝え方も変えた。184KBの呪文をそのまま画面にぶちまけるのではなく、原因の見当 → 終了コードの意味 → 件数 → 先頭10個 → 「写せた分は残っている」 の順に畳む。ファイル名は \#NNN を元に戻してから並べる。読めない名前は、無いのと同じである。
とりわけ効いたのが、終了コードの言い換えだった。code 23 は「全部だめ」ではなく「ほとんど運べて、一部だけ落ちた」という意味なのである。ここを取り違えると、せっかく写せている分まで捨てて、また最初からやることになる。あぶないところであった。
使わないボタンを、ひとつ作った
最後に、もうひとつボタンを足した。コピー元とコピー先を突き合わせて、まだ写せていないファイルを並べるだけのやつである。一バイトも書かない。
code 23 で終わったあと、「落ちたのは本当にあの138個だけなのか」を確かめる手段が要ると思った。408GBを「まあ、たぶん大丈夫だろう」で先へ進めるのは、いくらなんでも心臓に悪い。
作りながら、こんな会話をした。
「まぁ、使うことはないと思うけどね」
そのとおりである。うまくいっていれば、このボタンは一生押されない。それでも置いた。うまくいかなかったときにしか要らない道具は、うまくいかなかったときには、必ず要る。
便利すぎる、というのはこういうことか
一連が終わってから考えるに、私が「いやあ便利になった」と感じているのは、rsyncの機能そのものではなかった。
- 覚えなくてよくなった — オプションは道具の側に住んでいる
- 危ない選択肢が、そもそも存在しない —
--deleteは打とうにも打てない - 失敗が読める — 呪文184KBではなく、原因と件数と次の手が出る
- 確かめられる — 「たぶん写せている」を「写せている」にできる
Finderに戻る理由は、もうひとつも見あたらない。つまむ。運ぶ。落とす。あれで四十年やってきたのに、である。
道具が強いかどうかではなく、間違えたときにどうなるかで、使えるかどうかが決まるのだった。
AIとの協働作業での学び
この日いちばん危なかったのは、「rsyncは日本語ファイル名に弱い」で片づけそうになった、あの瞬間である。
138個が同じエラーで落ちていて、名前はどれも日本語で、しかも読めない。そう結論づける材料は、いかにも揃って見えた。そこで止まっていたら、この移行試験は「日本語ファイル名は運べない」という、ありもしない壁にぶつかったまま終わっていた。
止まらずに済んだのは、ログを推測ではなく実測したからだ。AIに投げたのは「なぜ日本語が写せないのか」ではなく、「失敗した名前を全部復元して、バイト長を測れ」だった。返ってきたのは、138個すべてが254バイトで、末尾が文字の途中で切れている、という事実である。推測を検証する形にすると、AIはめっぽう強い。 最初にやろうとしていた「原因を訊く」だと、たぶんもっともらしい一般論が返ってきて、私は納得して、そこで終わっていた。
これは前に書いたつまづいたのはDatePickerだったと、根っこが同じである。あのときも原因を探さずに小細工で覆い隠そうとして、みごとに外した。症状に名前を付けて納得するのが、いちばんあぶない。
もうひとつ、道具の形について。
この一件で本当に学んだのは、rsyncのオプションではなかった。判断を人の記憶ではなく、道具の側に置くということである。--delete を使わない、除外はOSの残骸だけ、コピー先がコピー元の中ならはじく。これらを手順書に書けば、いつか必ず読まれずに実行される。ボタンの中に入れておけば、間違えようがなくなる。
だから決めた。足りない機能が出てきたら、ターミナルで打たずにボタンにする。 手で打った瞬間に、ここまでの歯止めは全部すぽんと外れるのである。
作業の最後に、こんな話になった。
「完璧なツールがあると、それしか使わないよね」
それが狙いなのだ。ツールしか使われない状態になって初めて、--delete を持たせなかったことが効いてくる。 手打ちの余地が残っているうちは、歯止めは「気をつけましょう」と同じ強さしかない。
そして裏返しの責任は、作る側にある。道具が足りないと、人はその瞬間にターミナルへ戻る。 「これができないから手で打った」が出たら、それは実装漏れなのだと思っている。
以下、まじめな記録である。コピー元408GB・736,977ファイル・472,436フォルダ(写し側で find -type f を実測した736,839件に、失敗した138件を戻した数)。1ファイル平均約580KB。内訳は docu 306GB / WorkflowAttcDocs 55GB / notice_attc_docs 25GB / WorkflowPdfFiles 17GB / shared_files 2.4GB。コピー先は外付けSSD(APFS、USB-SATAブリッジ経由)で実測169MB/秒。失敗したのは138件(docu 120件・WorkflowAttcDocs 18件、68フォルダに分散)で、いずれも mkstemp が Illegal byte sequence (92) を返したもの。復元した一時名は138件すべてが254バイト("." + 246 + ".XXXXXX")で、末尾がマルチバイト文字の途中で切れていた。送信側はApple版rsync 2.6.9(macOS 14.4より前)で、macOS 14.4以降の既定はopenrsyncに変わっており同じ操作では再現しない。対処は --inplace(一時ファイルを作らず最終名で直接書く)。進捗表示はrsyncの出力ではなくコピー先の実消費を3秒ごとに測って算出している — --info=progress2 はopenrsyncが受け付けないため、実装差に依存しない方法を採った。同じ理由で、写し漏れの確認は -n -v(何も書かずに、送ることになるファイルを並べる)で行い、出力名の \#NNN(8進)を元のバイトへ戻してから表示している。