標準愚痴出力

個人的なIT作業ログです。もしかしたら一般的に参考になることが書いているかもしれません(弱気

PowerShell 5.1 のパイプラインは各外部コマンドをパラレルに実行しない

+(※追記:解決編あり → PowerShell 7.4 でのパイプラインならパラレルで動いた。 - 標準愚痴出力。 また、これにともない、タイトルを「PowerShellのパイプラインは各外部コマンドをパラレルに実行しない」から「PowerShell 5.1 のパイプラインは各外部コマンドをパラレルに実行しない」に修正)

以前、マイクラのデータを rclone を使ってストレージサーバーにバックアップするバッチファイルを書いた。

前回、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 を作成して、そこから読んでる)