メッシュ登場:どの2台が消えても、失うものはゼロ
Recilic 0.3.0をリリースしました。暗号化されたシャードが、あなたが所有するMac群に5言語対応で分散されるようになりました。ここに至るまでに、メッシュが2日間6分の5の能力で動いていたこと——そしてその間のすべてのバックアップが成功と報告していたことに気づきました。

8台のマシン、そして6台に
テストメッシュは8ノード構成です。このMacBook、Mac mini、そして家で使われずにいた6台の小型マシン。バックアップは各チャンクを4+2のシャードに分割し、6台の異なるノードに渡します。どの2台が消えてもデータは無傷です。
8月12日、同じ秒に、それらの小型ノードすべてが自身のアドバタイズを停止しました:
thread 'mDNS_daemon' panicked: index out of bounds:
the len is 278 but the index is 278
ネットワーク上の何か——プリンター、テレビ、わかりません——からの不正なパケットが1つ、ディスカバリーライブラリのパース処理のバグに到達し、すべてのノードで同時にそのスレッドを落としました。プロセスではなく、「ここにいます」と応答するスレッドだけが死にました。
そのため各ノードは接続を受け付け続けました。各ノードは自身のスーパーバイザーに正常と報告し続けました。そしてシャードの配置先を決定するアプリから見えなくなりました。その決定はディスカバリーで得られる情報に基づいているからです。6つの配置先のうち5つが2日間にわたってすべてのバックアップから静かに脱落し、それらのバックアップはすべて完了し、成功と報告しました。
恥ずべき部分
なぜ誰も気づかなかったのか、正確に述べる価値があります。シグナルが微弱だったからではありません。シグナルが存在しなかったからです。アプリは必要な情報をすべて持っていました——昨日は6ノードと言われ、今日は5ノードと言われた。それについて何の意見も持っていなかっただけです。
バックアップ製品の仕事は、後から信用されることです。「完了」は世界で最も報告しやすく、最も信用に値しないものです。
兄弟バグ、コアを焼く
小型ノードが沈黙している間、Mac miniのストレージノードはCPU 97%に張り付いていました——5日間、うち35時間はカーネル内で、何の仕事もしていませんでした。そのマシンには44のネットワークインターフェースがあります。実際に使われているものがいくつかと、蓄積された20のVPNトンネル、そしてAppleのピアツーピアインターフェース。ディスカバリーライブラリはそのすべてにソケットをバインドしました——マルチキャストポートに37個、Linuxマシンでは3個のところです——そしてそのソケットの1つがポーリングループを永遠に回し続けるのに十分でした。
どちらの不具合も、私たちが取り込んでいなかったバージョンでアップストリーム修正済みでした。最新リリースへの移行にコード変更は不要で、そのマシンのソケットは37から4に、CPUは97%からほぼゼロに下がりました。
肩をすくめる代わりに出荷したもの
依存関係のアップグレードは特定の事故を修正します。設計上の欠陥には何もしません。その欠陥とは、何も監視していなかったことです。3つの対策を入れました:
ノードが別れを告げるようになりました。ストレージノードは停止または再起動時、終了前にアドバタイズメントを取り下げます。Macから計測すると、ノードは最大1時間キャッシュに残る代わりに1.3秒でディスカバリーから消えます。強制終了されたノード——電源断、クラッシュ——は当然何も言えません。そのため次の対策もあります:
Macは配置計画の前に確認します。バックアップが配置を確定する前に、すべての配置先を並行して直接チェックします。キャッシュされたディスカバリーレコードは、マシンがまだ存在する証拠とは見なされなくなりました。これは予想以上に重要でした。以前は、消えたノードが単にスキップされるのではなく——もう存在しないソケットに向けてシャードが送られ続けていたため——バックアップ全体が失敗していました。マシンの電源を切ると次のバックアップが壊れていたのです。
そして結果を報告するようになりました。イベントではなく。バックアップが前回より少ないノードに到達した場合、Recilicはそう伝え、何が犠牲になったかを示します:
「ノードがオフラインです」では誰の役にも立ちません。データがまだ安全かどうかは別の問いであり、正確な答えがあります。5台のマシンに6シャードということは、1台が2つを持つので、その1台を失うとマージンなしで復元閾値ぎりぎりになります。それが表示すべき文です。ノードが単に別のノードに置き換わり耐障害性が変わらない場合は、劣化を騒がずにそう伝えます。
修正のなかのバグ
この警告の最初のバージョンは一度も発火しませんでした。ノードを停止し、バックアップは正しくそれを除外してシャードを別の場所に移動しましたが、サマリーは何も言いませんでした。
世代リポジトリはリストを新しい順に返し、コードはそのリストの末尾を「前回のバックアップ」として取得していました——このメッシュが存在する前の、最も古い世代を。その単一ノードは今日のノード群の部分集合です。そのためチェックは律儀に現在を時間の始まりと比較し、何も問題を見つけませんでした。すべてのユニットテストはパスしました。比較ロジックをテストしていたのであって、何と比較するかの選択をテストしていなかったからです。
正しい答えと見分けのつかない間違った答え。前の世代の選択はリポジトリに属するようになりました。自身のコミット順序を知っているのはリポジトリだけだからです。そしてそれをカバーするテストは、バグを再導入して失敗を確認することで検証しました——テストを信じる唯一の理由はそれです。
まだ変わらないこと
両方の修正は離陸前に起こります。バックアップ開始時に確かに存在し、進捗60%で消えたマシン——蓋を閉じた、電源タップを切った——は、5シャードで完了してそう報告する代わりに、その世代を失敗させます。データは無事です。コードは数学より厳格です。これはissue 16として記録してあり、次に取り組みます。
0.3.0
メッシュの改善に加えて:同一内容は1度だけ保存されるようになり、変更のないファイルの2回目のバックアップは何も転送せず、中断されたバックアップは着地済みのところから再開します。インターフェースは英語、繁体字中国語、簡体字中国語、日本語、韓国語に対応しました。また、EU全域での販売を開始しました。これには独自の事業者ステータス審査が必要でした。
スクリーンショットに目を留めた方もいるかもしれません。8つのノードのうち6つは Mac ではなく、Raspberry Pi 5 や Intel N100 クラスのミニ PC です。動いているのはアプリと並行して開発した Rust 製のストレージ専用デーモンで、暗号文を保存し、保有証明に応え、それ以外は何もしません。マージされたその日から自宅のメッシュを支えています。最適なリリース方法は現在検討中です——0.3.0 はこれに依存せず、アプリ単体で完結しています。
リストア訓練は今もエンドツーエンドの全経路を通します——読み取り、デコード、復号、検証——なぜなら一度も復元したことのないバックアップは仮説だからです。