+(※追記:解決編あり → PowerShell 7.4 でのパイプラインならパラレルで動いた。 - 標準愚痴出力。 また、これにともない、タイトルを「PowerShellのパイプラインは各外部コマンドをパラレルに実行しない」から「PowerShell 5.1 のパイプラインは各外部コマンドをパラレルに実行しない」に修正)
以前、マイクラのデータを rclone を使ってストレージサーバーにバックアップするバッチファイルを書いた。
- Java版マインクラフトのデータを rclone で「さくらぽけっと」へバックアップする - 標準愚痴出力
- マインクラフトデータのバックアップバッチファイル簡易版 - 標準愚痴出力
- 個人的インシデント:バッチファイルで %DATE% は安易に使うべきではなかった - 標準愚痴出力
前回、CMD.exe で日付文字列を取得する動的変数 %DATE%
は OS の設定で簡単にフォーマットが変わってしまうので、wmic os get LocalDateTime
を使う方法に切り替えた。だが、正直、使いにくいし、分かりにくい。今回はこれを全面的に PowerShell で書き直そうとした。
PowerShell は昔 nyagos のビルドスクリプトとして使ったりして、少々たしなんだが、おまじない Set-ExecutionPolicy -ExecutionPolicy RemoteSigned
をさせないといけないので、毛嫌いしていた。まぁ、プライベートユーズの場合はそういう懸念は無用だし、下手にバグるよりはいいだろう。
(まぁ、nyagos から実行する分には、.ps1 拡張子のコマンドは自動で powershell -ExecutionPolicy RemoteSigned -file
が補完されるので、気にしなくていいんだが)
function backup($world,$remote_dir){ $archive = "/minecraft-" + $world + "-" + (Get-Date -Format "yyyyMMdd") + ".tar.zst" $archive = $remote_dir + $archive $cwd = (Join-Path $env:APPDATA ".minecraft\saves") tar -C $cwd -cvf - $world | zstd | rclone rcat $archive } backup "Demo_World" "sakura:sakura_pocket/Backup/Game"
いきなりの本番実行はこわいので、まずは tar の行を
Write-Host "tar -C $cwd -cvf - $world | zstd | rclone rcat $archive"
で dry-run してみたところ、一応呼び出しのコマンドラインは期待どおりだった。よし、では本番実行してみよう(まぁ、失敗したところで、FTP先にゴミファイルが出来るくらいなんだが)
途中まではうまく動いていたんだが…途中でマシンの動作がだんだんカクカクに…
確認してみると、PowerShell のプロセスが11GB もメモリを消費していた。仕方なく killall powershell
した。
ちなみにバッチファイルで生成していた時の tar.zst アーカイブの大きさは 3.7GB くらい。転送先のサーバーにはファイルは作られていなかった。もしかしたら 0バイトのファイルとかあるんじゃないかと思っていたのだが、それすらなかった。
どうも、PowerShell はコマンドをパイプラインする時、/bin/sh や CMD.exe のように各プロセスをパラレルに実行せず、コマンド出力を全部オンメモリにおき、出力が完全に終わってから次のコマンドの入力に渡すようだ。ひでぇ
PowerShell のパイプラインはバイナリデータではなく、オブジェクトを介するものだとは分かっていたので、もしかしてテキストだと解釈されて文字コード問題とかが発生してしまう可能性は想定していたが、こういう問題があるとはね。知見が増えた。
なお、nyagos のパイプラインはちゃんとパラレルになるように作ってる(各コマンド向けに個別に goroutine を作成して、そこから読んでる)