2012年うるう秒バグ:1秒がサーバーを落とした
わずか1秒のうるう秒がLinuxサーバー群のCPUを暴走させ、Reddit・LinkedInを巻き込んだ2012年の障害を解剖。時刻は単調増加しない前提で設計する勘所と、うるう秒スミアなどの回避策を学べる。
- 2012年6月末のうるう秒で多数のLinuxサーバーのCPU使用率が急騰し、Reddit・LinkedIn・Mozillaなどが不調になった。わずか1秒の挿入が世界規模の障害を招いた。
- Linuxのhrtimer/futex周辺で、1秒巻き戻る境界処理がロックとタイマの不整合を起こした。待機スレッドの過剰起床とJavaのビジーループがCPUを使い潰した。
- 時刻はうるう秒・NTP補正・夏時間で飛ぶため、経過時間には単調時計を使う。近年は1秒を薄く分散するうるう秒スミアで、挿入境界そのものを避ける方式もある。
何が起きたか
横にスクロール
2012年6月30日の23時59分59秒(UTC)、世界の時計に1秒が足された。うるう秒である。地球の自転はわずかに揺らいでおり、原子時計が刻む正確な時刻(UTC)と、天文観測に基づく時刻(UT1)とのずれが一定を超えると、国際的な機関がうるう秒を挿入して両者の差を詰める。この日の挿入も、数か月前から予告された通常の運用にすぎなかった。ところが日付が7月1日へ変わる境界で、世界中の多くのLinuxサーバーが突然CPU使用率を跳ね上げ、応答が目に見えて滞り始めた。
被害は無名のサーバーにとどまらなかった。掲示板サービスのReddit、ビジネス向けSNSのLinkedIn、ブラウザで知られるMozillaなど、複数の著名サービスが相次いで不調やダウンに見舞われた。予約システムや航空会社の運航システムにも影響が出たと報じられている。障害に遭った現場に共通していたのは、LinuxとJavaを組み合わせた構成が多かったことだ。
現場のエンジニアを戸惑わせたのは、負荷が急増した理由がアクセス集中ではなかった点である。トラフィックは平常どおりなのに、サーバーのCPUだけが100%近くに張り付く。コンピュータが内部で扱う時刻(Unix時間)は、本来なめらかに増え続けるはずのものだ。その前提が崩れた瞬間に、システムは静かに暴走した。原因がうるう秒だと突き止めた組織は、時刻を設定し直してカーネルの状態をリセットすることで急場をしのいだ。
# NTPを一度止め、現在時刻を明示的に設定し直して
# うるう秒でこじれたカーネルの時刻状態をリセットする
/etc/init.d/ntp stop
date -s "$(date)"
/etc/init.d/ntp start
時刻を設定し直すと、なぜ復旧したのか。うるう秒でこじれていたのは、あくまでカーネルが内部に抱えた時刻とタイマの状態だった。時刻を明示的に入れ直すと、その状態が正常な値へ整え直され、無限に続いていた無駄な起床の連鎖が断ち切られる。サーバーを再起動しても同じく直ったが、稼働中のサービスを止めずに済むぶん、時刻の再設定のほうが現実的な応急処置として広まった。
文字どおり、たった1秒が世界規模の障害の引き金になった一夜だった。
技術的な根本原因
核心はLinuxカーネル内部の時刻処理にあった。うるう秒の挿入は、時刻を単純に1秒進めるのではなく、同じ秒をもう一度繰り返す(実質的に1秒巻き戻す)形で表現される。この「時刻が単調に進まない」一瞬が、カーネルの時間管理に想定外の状態を生んだ。
問題の中心にあったのが、高精度タイマ(hrtimer)と、スレッドの待機・起床を司るfutex周りのロック処理だ。多くのスレッドは「指定した時刻まで眠る」という形で待機している。うるう秒でシステム時刻が補正されると、カーネルは各タイマの期限を計算し直すが、この境界処理に不具合が潜んでいた。まだ起きる時刻ではないスレッドが「もう期限だ」と誤って一斉に起こされ、しかし実際には処理を進める条件が整っていないため、すぐにまた眠りに戻ろうとする。起きては眠り、眠っては叩き起こされる——この無駄な往復が高速で際限なく繰り返された。
起こされるスレッドが1つや2つなら実害はない。問題は、時刻補正の処理がロックを握ったまま、条件変数やタイマで眠る大量のスレッドをまとめて起こしにいった点にある。起こされたスレッドは同じロックを取り合い、まだ条件が整っていないと知ってはふたたび待ち行列へ戻る。列の片端から起こされては元に戻る動きが連鎖し、コア数の多寡に関係なく、すべてのCPUが本来の仕事ではなく起床処理そのもので埋め尽くされた。
これはデッドロックではない。スレッドは停止せず動き続けるが、意味のある前進を一切しない。互いに空回りして処理が進まないこの状態は「ライブロック」と呼ばれる。CPUは全力で回っているのに、仕事は何ひとつ終わらない。
デッドロックは、互いのロック解放を待ってスレッドが「止まる」障害だ。一方ライブロックは、スレッドが「動き続ける」のに前進しない。CPU使用率だけが跳ね上がって一見忙しく見えるため、停止と違って気づきにくく、原因の切り分けが遅れやすい。
拍車をかけたのがアプリケーションのレイヤーだ。当時広く使われていたJavaの実行環境には、タイマやスレッドの起床が多発するとビジーループ(何もせず回り続けるループ)に近い挙動を示すものがあった。カーネルの異常な起床と、アプリ側の空回りが噛み合い、CPU使用率をさらに押し上げた。つまりこの障害は、カーネルの境界処理バグと、その上で動くアプリの実装とが重なって生まれた複合的なものだった。層ごとに何が起きたかを整理すると次のようになる。
| レイヤー | 起きたこと |
|---|---|
| カーネル(hrtimer/futex周り) | 境界処理の不具合で、待機中のスレッドを過剰に起床させた |
| OS全体 | 無駄な起床の往復でCPUが張り付く(ライブロック的な状態) |
| アプリ(Javaなど) | 多発する起床でビジーループ化し、負荷をさらに上乗せした |
教訓
この事故が突きつけた最大の教訓は、時刻は単調増加しないという事実である。多くのプログラマは、時計は一定のペースで前へ進み続けるものだと素朴に信じている。だが現実の時刻は、うるう秒で巻き戻り、NTP補正で飛び、サマータイムの切り替えで1時間ずれる。「あとで測った時刻は、必ずさっきより大きい」という仮定は、そもそも成り立たない。
対策の第一は、経過時間の計測に単調時計(モノトニッククロック)を使うことだ。壁時計(カレンダー上の時刻)は補正で前後するが、単調時計は起動からの経過を単調増加で返し、決して巻き戻らない。「処理に何秒かかったか」「タイムアウトまで何秒あるか」を測りたいなら、壁時計どうしの差分ではなく、単調時計を使うべきだ。多くの言語やOSは、この2種類の時計を別々のAPIとして用意している。
第二は、境界値を軽視しないことだ。1秒の挿入という、年に数えるほどしか起きない特異点が、世界規模の障害を生んだ。稀にしか現れない入力ほどテストから漏れやすく、本番で牙をむく。うるう秒、年またぎ、タイムゾーンの境界のような「めったに来ないが必ず来る」条件は、設計の段階で明示的に洗い出しておく価値がある。うるう秒を模擬的に発生させて挙動を確かめるテストは、この事故のあとになって各所で整備が進んだ。同種の時刻起因の障害は過去のインシデント事例にも繰り返し登場する。
第三に、近年の運用ではうるう秒スミア(smear)という回避策が広く採られるようになった。1秒を一気に挿入する代わりに、その前後の数時間から丸一日をかけて時刻をごくわずかずつ遅らせ、1秒ぶんを薄く塗り広げる。こうすれば時刻の単調増加は保たれ、同じ秒の重複も起きない。Googleなどの大手が先行して採用し、いまでは主要なクラウドの時刻同期サービスでも選べるようになっている。時刻に起因する障害を、時刻の配り方そのもので根本から避ける発想だ。
タイムアウトやリトライの間隔、処理時間の計測で不可解な挙動に出会ったら、まず壁時計を使っていないかを疑うとよい。時刻補正やうるう秒で壁時計が巻き戻ると、負の経過時間や桁外れの待機時間が生まれる。計測は単調時計に寄せるのが定石だ。
まとめ
2012年のうるう秒バグは、たった1秒という最小の境界値が、世界中のサーバーを揺るがしうることを見せつけた。直接の原因はカーネルの時刻処理に潜んだライブロック的な不具合であり、その上で動くアプリの空回りが被害を広げた。だが本質的な教訓はもっと普遍的だ——時刻は単調増加しないという前提に立ち、経過時間の計測には単調時計を使い、めったに来ない境界条件を最初から設計に織り込む。うるう秒スミアのような回避策も含め、「時間」という当たり前の土台をあえて疑う姿勢こそが、次の一夜を静かに乗り切る備えになる。ほかの事例は工学の大惨事から辿れる。
工学の大惨事の記事ガイド
2012年うるう秒バグ:1秒がサーバーを落としたを実務で読む
TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。
解決すること
大惨事
比較で見る軸
難易度: advanced / カテゴリ: 工学の大惨事 / タグ数: 5
導入後に効く点
Linuxのhrtimer/futex周辺で、1秒巻き戻る境界処理がロックとタイマの不整合を起こした。待機スレッドの過剰起床とJavaのビジーループがCPUを使い潰した。
先に潰すリスク
用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。
- 難易度
- advanced
- カテゴリ
- 工学の大惨事
- タグ数
- 5
判断チェックリスト
- 自社の用途が「大惨事 / 時刻」に近いか確認する。
- 強みである「2012年6月末のうるう秒で多数のLinuxサーバーのCPU使用率が急騰し、Reddit・LinkedIn・Mozillaなどが不調になった。わずか1秒の挿入が世界規模の障害を招いた。」が本当に評価軸になるか確認する。
- 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
- 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
- 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。