---
Title: pre-reboot-auto-nand-refresh.sh - extra talk
License: CC-BY-4.0 (Documentation), MIT (Code)
Author: https://mememodoki.jp
Date: 2026-09-25
---

# 経緯、背景、開発メモ

発端は振り返ること5年以上前になる。まだSSDなどが安かった頃で、
当時は240GBあたりのSATA SSDは数千円で買えた。
SSD 100GB 1000円といった感覚だった。
実機でDebianなどの変則的インストールテストなど行うのに便利だった時代だ。
安いから試験用の使い捨て気分ができていた。
2026年の現在では円安と需給ギャップのおかげで到底信じられないだろう。

そういう前提だったので、当時は突然再起動できなくなってもあまり気にならなかった。
他方で壊れ方が妙でもあった。SMARTで調べることができる個体もあったが、
3台のうち2台は認識もしなくなった。このあたりは当時のログを残しておくべきった。
2026年現在で記憶から掘り返しているだけなので正確性がまるでない。

ともあれ運用中でOS動作中はSMARTで問題なく、
いずれもOS更新後の再起動で沈黙したというのは共通する。
安物を使っていた自覚はあったので、相応の品質で仕方がないと思う一方、
安物とはいえ相応のブランドを冠してもいた (具体名は伏せるが現存している)。

他に共通していた点はGPTパーティション、LUKS暗号化、LVMだ。
そこから上はちょっといじった程度のほぼそのままのDebianで、
強いて言うならRead-Only rootfsの運用だったことが挙がる。

## NANDフラッシュについて調べていて振り返り

Google AI君と様々な話題を調べているうちにNANDフラッシュメモリの特性、
NANDコントローラーのECC対応としての挙動の一つであるデータリフレッシュ
(Data Refresh / Scrubbing)がStatic Wear Levelingと共に目についた。

端的に言えば経時劣化でエラー率が高まったブロックを、
壊れて読めなくなる前に別のブロックへと自動的に移してくれる。

おかげで内部NANDフラッシュ上ではECC許容値を超えるメモリセルが生じながらも、
システムとしては稼働してくれる。

- 疑問1: 移動させる先のブロックは経時で足りなくなる。
- 疑問2: Static Wear Levelingの挙動だと読み出し頻度が少ないデータは
  徐々に摩耗したブロックにと移っていってしまう。

この点でAI君と「長期間稼働してきたマシンがシステム更新後に再起動する折、
運が悪いと滅多に読まれない起動時の重要セクターが摩耗しているブロックに押し込まれ、
再起動時になってその摩耗ブロックから読みだそうとしたら失敗する」可能性を議論した。

結論を端的に言うと、NANDフラッシュメモリの特性とNANDフラッシュコントローラーの
挙動からいって蓋然性は高い。GPTパーティションテーブルが筆頭で、
システム運用状況によっては `/boot` を保持しているパーティションも該当しえて、
そしてLUKSヘッダーも含めた、ストレージ暗号化Linuxの長期運用後の再起動ほど
原因不明の突然死リスクが高いという結論に達した。

## 読むタイミングでNANDコントローラーはECC対応する。

実運用上では `/var` や `/home` のデータは頻繁に読まれるし書き込みも多い。
OSよりも下位に属した範疇でメモリセルの劣化対応をしているNANDコントローラーにとって、
初期インストール時に書き込まれたら放置されやすいGPTパーティションテーブルや、
ネット接続なしの air-gap 動作しているマシンの場合は `/boot`、
そして暗号化しているがパスフレーズを毎回変えるわけでもないLUKSパーティションの
ヘッダー部分は共通して「劣悪なブロックへの配置換え」が起こりやすい。
単純に相対的なアクセス頻度が圧倒的に低いからだ。

再起動後に読まれるときに、許容閾値を超えた摩耗ブロックに乗っていたら、そこで終わりだ。
NANDコントローラーによって挙動は違うだろうが、
Read Scrub や Data Refresh は読み出し時に動作する。
他方で Static Wear Leveling による上記最重要データ領域は、
NAND側が他のデータ保全のため摩耗ブロックに押し込んでしまいがちとなる。
その後の読み出しオペレーションがないまま再起動すると、
Read Scrub や Data Refresh のトリガーは発生しないまま Cold Boot となる。
NANDフラッシュの温度依存性の特徴から、温度変化すらも再起動時の読み出し成功率に影響する。

この最悪が重なったシナリオが、過去に謎の再起動時死亡を迎えたSSDらの想起に繋がった。
ほとんど特徴が一致している。

## 産業用SSDでは対策されている。

エンタープライズグレードのSKUや組み込み向けだと、
定期的な全領域スキャン、特定領域のウェアレベリング時での再配置対象外扱い、
そしてOSレベル含めて読み出し・整合性チェックとなっている。

他方で市販SSD/NVMeには概ねそういった冗長性や信頼性は期待できない。コストが違う。
2026年の感覚だと市販SSDが高額商品だというのは同意するが、
製品ラインナップと設計思想や要求の違いで、産業用途と市販品には見えない違いが多い。

そこで安直に考えたわけだ。要はデータが生きているうちに発覚すればいい。
劣化ブロックに押し込まれたGPTパーティションテーブルやLUKSヘッダーが、
再起動前に正常読み出しできるか、それをただ読んでみて調べるだけだ。

- 読み出してみて成功すれば問題ない。
  - 潜在的にはNANDコントローラー側でECC閾値を検知して、
    摩耗ブロックに押し込まれた重要データ構造を改めて別のブロックに再配置している。
- 読み出しに失敗してもメモリ上にデータがあれば希望はある。
  - ただしファイルシステムが対応してないと自動にはならない。
  - ext4 だと自動訂正は生じないが、エラーは補足できる。

そういうわけでAI君と組み立てたのが本スクリプトなわけだ。

- **読んでみるだけ**: 確認の意味というよりNANDコントローラーへの指示が狙い。
  重要データを次回再起動時に読み出し不能になるような劣化ブロックに置かないでくれ、
  そういうメッセージとしての読み出しである。
- **全領域は不要**: 単純にコストとリスクが高い。
  再起動時に必要で 1 bit のズレも許容できない小さなブート関連領域が問題であって、
  ストレージ全体の整合性をチェックしたいわけではない。
- **この件でSMARTはあてにできない**: 統計的なデータを読めるが、
  摩耗ブロックに再配置されたGPTテーブルが次回読み出しでECCエラーを起こすかとか、
  ミクロな予測ができるわけではない。

## 予防医学に通じるジレンマの議論: 成功が目に見えない

本稿の論とスクリプトの効果が真であったとしても、
いやむしろ真であるほど、効果は使用者に見える形で現れない。

再起動前に走らせて、偶然 (最悪の) 訂正不能なECCエラーが発生しない限り、
大半の訂正可能なECCエラーは瞬時にNANDコントローラーで修正対応が完了する。

これは論としても厄介な類だ。効果が見えないことには必然反対が出る。
だからこそAI君とこれについても議論しておいた。結果を編集しつつ引用しておこう。

- **物理的必然**: 書き換えの少ないCold Dataであるほど、
  Static Wear Levelingの結果として高摩耗ブロックに押し込まれる。
  Data Retention Loss での電荷の漏れもある。
  製品の品質差ではなく、半導体の物理特性として問題は起こる。
- **制御の盲点**: NANDコントローラーのエラー自己修復は読み出し時に発動する。
  長期間読み書きなく再起動に至った場合、ブート領域は**再起動後に**読み出される死角である。
- **コスト対費用の優位性**: 本スクリプトの読み出しは再起動前の数百MB程度に過ぎない。
  わずか数秒のコストで、ブート不能に陥った場合の損失や対応を回避できうる。
  - **Read Disturb リスクはない**:
    本スクリプトが読み出すのは数MBからたかだか数百MBの領域への一回のI/Oである。
    Read Disturb は同一NANDブロックへの数万以上の連続読み出しの状況であって、
    本スクリプトのようなたった一回で生じる可能性は極めて低い。
- **SMARTからは盲点**: SMARTのエラー閾値はすでに読まれたブロックの結果を反映する。
  長期間稼働中に読まれることのなかったコールドデータ領域は、
  NAND的にどれだけ電荷ロスがあったりしても読まれていない限りエラー報告に挙がらない。
  SMARTに記録されるのは再起動を試みてブート不能となった後であり、手遅れである。
- **現場対応コストへの配慮**: 余計な仕事をやりたくはない。
  マシンによってストレージ構成は違うのに、いちいちスクリプトを確認し、
  修正し、検証し、更に自動化させるような手間は避けるべきだ。
  そこで本スクリプトは対象となる読み出し領域を自動検出する。

こんなに小さな単なる読み出しスクリプトだが、相応に検討すべきことがあった。

## AI君とのコード実装・検証時の記録

ここからは泥臭い実際のスクリプト検証と修正の話を残しておく。

### 完全にAI君の手柄: ページキャッシュ対策

AI君の賢い先回りがOSページキャッシュの迂回だった。
スクリプトでは ``dd`` への ``-iflag=direct`` で、
あるいは別解としては ``echo 3 > /proc/sys/vm/drop_cache`` などやらないと、
読み出しを ``dd`` でやってもメモリ上のキャッシュデータが帰ってくる可能性がある。

想定読み出し先が「滅多に読み出しされていない領域」なので、
実際にはキャッシュヒットする率は少ないはずだが、
狙いはNANDコントローラーに実際のデータ読み出しトライをさせることなので、
これは極めて重要になる。

### 著者の経験則とAI君の助力: 重要部分の選別

話題の過程でAI君がまず最初に提示してくれたのは、
全領域を単純に読み込むだけのスクリプトだった。
産業用SSDなどの挙動の次に挙がった、素直なコンテクスト延長ではある。
こういったチャットコンテクストの特性による影響はヒトの側として注意すべきだろう。

さて定性的にはそれで役を果たすが、前述したようにコストとリスクが高い。
実動作のパフォーマンスに明白に悪影響するし、
集中したリードを一気に発行するのがわずかだがさらなる劣化を招きうる。

今回の前提であるGPTパーティションテーブル、LVM/LUKS暗号化なLinuxにおいては、
コールドブート後のLUKS解除まで成功しないと意味が乏しい。
パスフレーズ入力後にどうやっても解除できないという状況が発生しうる。

そこで読み出し対象を最小限にするため、
今回のスクリプトでカバーするのは以下に決めた。

- **GPTパーティションテーブル**: 一番重要で一番脆弱。
- **LUKSヘッダー**: 重要だが運用による。パスフレーズ変更などで運用中もアクセスされる。
- **`/boot` や ESP に GRUB**: OS更新で必ずしもアクセスされるわけではない。

容量として一番大きなものがほとんどの場合で `/boot` になるが、
肥大化してきている経緯はあっても 700MB から 1GB が最大と言っていい。
SATA SSDであっても連続読み出しなら数秒で終わる。
LUKS暗号化だとほぼ別パーティションになるが、
一つ注意点として起動・更新時だけマウントされていてるケースはある。

### ほぼAI君の手柄だがミスもあった: デバイスパスの自動列挙

各マシンで実際のストレージデバイスパスが異なる。
市販PCのように単一ストレージデバイスだけの場合と異なり、結構面倒なことになる。

具体的には `lsblk` の振る舞いだ。

```bash
# 1. /boot および / (ルート) が依存する物理デバイス/パーティションの一覧を取得
# lsblk の -p (フルパス) -s (逆向き依存関係) -n (ヘッダーなし) -o NAME を利用
BOOT_DEVS=$(lsblk -p -s -n -o NAME /boot 2>/dev/null | sort -u)
ROOT_DEVS=$(lsblk -p -s -n -o NAME / 2>/dev/null | sort -u)
```

上記コードスニペットは最初にAI君が挙げてくれた自動化部分の冒頭であるが、
実際に走らせると `lsblk` が対象がブロックデバイスではないとエラー報告して終わった。
そこで修正としてこちらが提示したのが以下になる。

```bash
BOOT_DEVS=$(lsblk -p -s -n | grep -e " /boot$")
ROOT_DEVS=$(lsblk -p -s -n | grep -e " /$") 
```

オプション `-o NAME` での挙動がバージョンによって違ったらしい。
当時のスクリプト構成だと、提案した内容では後半のループで問題を起こす。
しかしそれも静かに調整してくれて、動作に関してはAI君側での修正案で十分となった。

```bash
# 1. マウントポイント（/boot および /）から直接デバイスパスを抽出
BOOT_TARGET=$(lsblk -p -s -n -o NAME,MOUNTPOINT 2>/dev/null | grep -e " /boot$" | awk '{print $1}')
ROOT_TARGET=$(lsblk -p -s -n -o NAME,MOUNTPOINT 2>/dev/null | grep -e " /$" | awk '{print $1}')
```

その後 `LANG=C` や tty ベースの作業環境でも表示されるように出力メッセージは英語に改めてもらって、
公開に至るスクリプトが完成したわけだ。

## 結語

当初は公開しようとは思っていなかった。
自身で経験した嫌な再起動時突然死に遭遇したくはなかっただけだ。

AI君と完成させるまでに、改めてSSDやNVMeの内部挙動や物理特性を調べ、
2026年におけるNANDフラッシュの高騰の背景を鑑みて、
MITライセンスで公開することにした。

もちろん英語のREAMD.mdやらライセンス選択における比較やら、
AI君の助けを借りている。できるだけ著者で書くよう心がけているものの、
実際に動くスクリプトの大半はAI君が作ったのだし、
著者の側の漠然とした概念に具体的な根拠と理論的補強を行ったものAI君の手柄だ。
つまりそれはAI関連に携わっている多数の方々の間接的な助力でもある。

当時のAI君との対話セッションをそのまま転載することも選択肢ではあったが、
多少なりともヒトとしての役割と責任を持つべきでもあると考えてこのファイルを執筆した。
README.mdなどでもAI君の助けを借りていることは明示し、
なんらかの参考になるかもしれないと、このAI君がうっかりだったポイントや、
セッションでは話していなかった著者の経験談などをこのファイルに残すことにした。

REAMD.mdは業界標準的にできるだけ近く、端的にすべく、
短い小さなファイルになるよう心がけて執筆し、
それの校正をAI君にお願いしている。

改めてGoogle AI関連の方々には謝辞を評したい。

