再起動できない宇宙ソフトウェアの設計術
宇宙ソフトウェアは致命的な失敗を許されません。再起動に何時間もかかる環境で、エンジニアがどう信頼性を確保しているかを解説します。

午前3時にウェブサーバーがクラッシュします。Kubernetesが再起動させ、ユーザーは一瞬エラーページを見るだけです。誰もクビにはなりません。では、そのサーバーが火星の周回軌道にあり、再起動に45分かかると想像してください。その間は姿勢制御も熱管理も通信もできません。「ユーザー」は25億ドルの宇宙船で、Kubernetesはなく、あるのは自分のコードと、それを動かす耐放射線CPUだけです。
宇宙ソフトウェアは、通常のソフトウェア開発が気楽に見えるほどの制約の下で動いています。ホットフィックスをデプロイすることはできません。SSHでログを確認することもできません。負荷が増えてもサーバーを追加できません。打ち上げ前にすべてのコード行が正しくなければなりません。打ち上げ後は、何年も何十年も、放射線で少しずつ劣化するハードウェア上で、数分単位の遅延と数kbpsの帯域しかない通信リンクを使いながら、自力で動き続けることになるからです。
すべてを左右する制約
放射線。宇宙では高エネルギー粒子が絶えず電子機器に降り注ぎます。1個の粒子でもメモリのビットが反転したり(シングルイベント・アップセット、SEU)、レジスタが壊れたり、プロセッサが停止したりします。これは稀な事象ではありません。低軌道衛星は1日に数千回のビット反転を経験します。そのため宇宙用ハードウェアは耐放射線部品を使いますが、それは遅く、高価で、民生品より何世代も前の技術です。火星探査車パーサヴィアランスのプロセッサはRAD750で、ざっくり言えば1998年頃の200MHzのPowerPCに相当します。
通信遅延。電波で届く火星までの距離は、軌道の位置によって片道4〜24分です。木星なら33〜54分です。これは単なるレイテンシの問題ではありません。地上管制が問題に気づくまでにも、ましてコマンドを送るまでにも往復時間がかかるため、その間は宇宙船が自律的に対処しなければならないということです。ボイジャーが地球から22光時以上離れた場所で異常に遭遇したとき、ソフトウェアはほぼ2日間、自分で判断し続けました。
物理的にアクセスできない。故障した部品の交換も、RAMの増設も、ハードディスクの交換もできません。メインコンピュータが故障し、バックアップも機能しなければ、ミッションは終了です。あらゆる故障モードを打ち上げ前にソフトウェアで想定しておく必要があります。
宇宙向けコードはどう違うのか
宇宙ソフトウェアでは、通常の開発では馬鹿げているほど過剰に思える手法を使います。
三重冗長化(TMR)。重要な計算を独立した3つのプロセッサで3回実行します。多数決器(ボーター)が3つの結果を比較し、多数派の答えを採用します。放射線によって1つのプロセッサが誤った答えを出しても、残り2つが多数決で押し切ります。さらなる安全策として5重冗長化を採用するシステムもあります。
メモリスクラビング。バックグラウンドのプロセスがメモリを絶えず読み出し、誤り訂正符号(ECC)と照合して、訂正不能な複数ビットエラーに蓄積する前に1ビットエラーを修正します。これは常時動いており、メモリの全バイトが毎秒数回チェック・修正されます。
ウォッチドッグタイマー。ソフトウェアが定期的にリセットしなければならないハードウェアタイマーです。ソフトウェアがハング(放射線による停止など)すると、タイマーが満了してハードウェアリセットが発動します。そのためソフトウェアは、実行のどの地点でも予期しない再起動を生き延びられるよう設計しなければなりません。どの計算も中断されて再開される可能性があるからです。
// Simplified space software patterns
// Watchdog — must be kicked regularly or hardware resets
void main_loop(void) {
while (1) {
kick_watchdog(); // Reset the timer — we're alive
// All operations must complete within watchdog timeout
read_sensors();
kick_watchdog();
run_attitude_control();
kick_watchdog();
check_thermal_limits();
kick_watchdog();
process_ground_commands();
kick_watchdog();
}
}
// Critical value stored with redundancy
typedef struct {
int32_t value_a; // Primary copy
int32_t value_b; // Redundant copy
int32_t value_c; // Third copy for voting
} RedundantInt;
int32_t read_redundant(RedundantInt *r) {
// Majority vote — tolerates one corrupted copy
if (r->value_a == r->value_b) return r->value_a;
if (r->value_a == r->value_c) return r->value_a;
if (r->value_b == r->value_c) return r->value_b;
// All three differ — flag anomaly
raise_anomaly(MEMORY_CORRUPTION);
return r->value_a; // Best guess
}
テストこそが製品である
NASAのジェット推進研究所の見積もりでは、宇宙ソフトウェアのテストは開発工数全体の60〜80%を占めます。時間の60%ではなく、総コストの60%です。テストの方が開発より高くつくのは、通常のソフトウェアテストが到達しない水準で正しさを証明しなければならないからです。
すべてのコードパスをテストしなければなりません。Web開発者が言う「高いカバレッジ」ではなく、文字通り、すべての関数のすべての経路を、エラー経路、タイムアウト経路、ハードウェア故障を処理する経路を含めてテストします。分岐カバレッジ100%は出発点であって、ゴールではありません。
単体テストに加えて、宇宙ソフトウェアはハードウェア・イン・ザ・ループ試験(実際の搭載ハードウェア上で実際のソフトウェアを動かし、宇宙環境を模擬する)、長期間ストレス試験(数か月間動かし続けて、タイミングに依存するバグを探す)、故障注入試験(メモリを意図的に壊し、プロセッサを止め、通信リンクを断ち、ソフトウェアが回復するかを確認する)を受けます。
最も重要なコンポーネントには、形式検証の利用も増えています。特定の入力で動くことを確かめるのではなく、形式検証はあらゆる入力に対して正しく動くことを数学的に証明します。高価で時間もかかりますが、宇宙船の姿勢(向き)制御や推進を担うコードでは、そのコストは見合うのです。
それでも起きる失敗
これほど厳格に作られていても、宇宙ソフトウェアは失敗します。その失敗は、どれほど慎重なエンジニアリングにも限界があることを示すため、非常に教訓的です。
- マーズ・クライメイト・オービター(1999年)は、あるチームがヤードポンド法を、別のチームがメートル法を使っていたため墜落しました。ソフトウェアは正しかったのです。間違っていたのは要求仕様でした。間違った仕様は、どれだけテストしても見つかりません。
- アリアン5 フライト501(1996年)は、64ビット浮動小数点数が16ビット整数に変換され、オーバーフローしたため爆発しました。このコードはアリアン4から流用されたもので、アリアン4では値が16ビットの範囲を超えることはありませんでした。アリアン4では正しかったコードが、アリアン5では壊滅的に間違っていたのです。
- マーズ・ポーラー・ランダー(1999年)は、脚の展開時のセンサー振動を接地と誤認し、高度40メートルでエンジンを停止させたことが墜落の原因と考えられています。タイミングに依存するセンサー解釈の問題で、テストが振動特性を完全には再現していなかったため見逃されました。
- ハッブル宇宙望遠鏡の初期の鏡の欠陥(1990年)はソフトウェアのバグではありませんでした。鏡は、校正の狂った検査機器のせいで誤った形状に研磨されたのです。問題は、検査ツールそのものにあったのです。
共通点は、コードは仕様どおりには正しかったが、仕様が現実と一致していなかったことです。モデルと現実のギャップに存在するため、この種のバグは防ぐのが最も難しいものです。
地上のソフトウェアが学べること
私たちの多くは宇宙ソフトウェアを書いていません。しかし、その中のいくつかの手法は、地上で信頼性の高いシステムを作る際にそのまま使えます。
再起動を前提に設計する。宇宙ソフトウェアは、いつでも再起動されうることを前提にし、既知の正常な状態に復帰しなければなりません。Webサービスにも同じ性質が必要です。サーバーがクラッシュして再起動したとき、手動介入なしで復旧できますか。中断したところから処理を再開できますか、それとも作業が失われますか。宇宙エンジニアが必須とみなす防御的エンジニアリングのパターンは、Web開発ではたいていオプション扱いですが、本来はそうであるべきではありません。
正常系だけでなく故障モードをテストする。宇宙ソフトウェアのテストでは、意図的に障害を注入します。プロセスを停止させ、メモリを壊し、ネットワーク接続を切断し、すべてのシステムコールでエラーコードを返させます。多くのWebアプリケーションのテストは機能が動くかどうかに注目しがちで、システムが障害をうまく処理できるかは見ていません。
重要データには冗長性を持たせる。再作成にコストがかかるデータ、たとえば決済記録、ユーザーコンテンツ、設定状態などは、冗長に保存し、定期的に検証しましょう。データベースのレプリケーションは分かりやすい例ですが、アプリケーション内の冗長化(チェックサム、バリデーション、定期的な整合性チェック)は、レプリケーションが伝播させてしまう破損を検出できます。
重要なプロセスにはウォッチドッグを。ハングしてはならないプロセスは監視しましょう。「プロセスが動いているか」だけでなく「プロセスが前に進んでいるか」を確認します。リクエストを処理しているか、状態を更新しているか、期限を守っているかといった、システムが実際に機能していることを検証するヘルスチェックは、プロセス単位の監視では見逃す障害を捉えられます。
新しい宇宙ソフトウェア
商業宇宙産業(SpaceX、Rocket Lab、Planet)は、こうした伝統のいくつかに挑戦しています。SpaceXは、ファルコン9の飛行コンピュータにLinuxとC++を使っています。従来の航空宇宙の基準から見れば異端です。Planet Labsは数百機の小型衛星を運用し、個別仕様のハードウェアというより分散システムとして扱っています。衛星が1機故障しても、コンステレーション全体で補うのです。
このシフトは、より大きな問いを反映しています。本当にどの程度の信頼性が必要なのか。25億ドルの火星探査車は5年間のテストを正当化しますが、数百機からなるコンステレーションの1機にすぎない50万ドルの通信衛星は、それほど多くのテストを必要としません。エンジニアリングの実践はリスクの度合いに合わせるべきであり、別の時代に作られた伝統を盲目的に踏襲すべきではありません。
しかし、予算に関係なく核心の教訓は変わりません。展開後に物理的にアクセスできないソフトウェアは、アクセスできるソフトウェアより高い信頼性が必要です。宇宙船を打ち上げる場合でも、IoT機器群にデプロイする場合でも、エッジコンピューティングのネットワークを運用する場合でも、原則は同じです。SSHで入って直せないなら、ソフトウェア自身がそれを処理しなければなりません。その考え方を適切に適用すれば、あらゆるソフトウェアがより良くなります。


