「プログラマが時間について信じている誤解」から10年以上 – なぜ今も障害が起きるのか

Programming

「プログラマが時間について信じている誤解」から10年以上 – なぜ今も障害が起きるのか

はじめに

「1日は24時間」「タイムスタンプは一意」「システムクロックは正確」──これらはすべて間違いだ。

2026年2月、Redditのr/programmingで、2012年に公開された古典的記事「Falsehoods Programmers Believe About Time」が再燃し、スコア317を獲得して話題となっている。なぜ10年以上経った今も、この記事が開発者の共感を呼ぶのか。

答えはシンプルだ:時間処理のバグはなくならない。むしろクラウド、コンテナ、分散システムの時代になり、問題はより複雑になっている。

本記事では、開発者が犯しがちな「時間の誤解」を整理し、現代のシステムでどのように対処すべきかを解説する。

プログラマが信じている55の誤解

元記事では、プログラマが時間について信じがちな誤解をリストアップしている。いくつか抜粋しよう:

日付・暦に関する誤解

| 誤解 | 現実 |
|——|——|
| 1日は常に24時間 | うるう秒、夏時間の切り替えで23時間や25時間になる |
| 月は30日か31日 | 2月は28日か29日(うるう年) |
| 年は365日 | うるう年は366日 |
| 週は常に同じ月・年に収まる | 12月末の週は年をまたぐ |
| うるう年は4年に1回 | 100で割り切れる年はうるう年ではない(2100年問題) |

システムクロックに関する誤解

| 誤解 | 現実 |
|——|——|
| システムクロックは正確 | ドリフトが発生する(1日で数秒ずれる) |
| サーバーとクライアントの時計は同期している | 数秒〜数分のズレは日常的 |
| タイムスタンプは一意 | 同じミリ秒で複数のイベントが発生可能 |
| 1分は常に60秒 | うるう秒で61秒になることがある |

タイムゾーンに関する誤解

| 誤解 | 現実 |
|——|——|
| タイムゾーンは固定 | 政治的な決定で変更される(中国は全域で北京時間) |
| UTCとGMTは同じ | 厳密には異なる(うるう秒の扱い) |
| 夏時間の切り替えは予測可能 | 国によって突然変更される |
| タイムゾーンオフセットは整数時間 | ネパールはUTC+5:45、インドはUTC+5:30 |

なぜ今も問題なのか:KVM仮想マシンの実例

元記事では、CentOS上の古いKVMで発生した実際起きたバグが紹介されている。

何が起きたか

  • KVM仮想マシンは自分が物理ハードウェア上で動いていると「信じ込んでいた」
  • ホストOSがVMをサスペンド状態にすると、VMのシステムクロックはサスペンド時刻で停止
  • 例:13:00にサスペンド → 2時間後に復帰 → VMの時計はまだ13:00のまま
  • VMがアイドルになるたびにサスペンドされ、クロックが現実から乖離
  • 結果

    • 本番環境で「数時間〜数日」のクロックドリフトが発生
    • ログの時刻がめちゃくちゃに
    • スケジュールされたジョブが誤動作
    • セッションタイムアウトが機能しない

    この問題は、クラウド時代のコンテナやサーバーレス環境でも類似の問題が発生し得る

    分散システムでさらに複雑になる

    Google Spannerの「TrueTime」

    Googleは分散データベースSpannerで、時間の不確実性に対処するためTrueTime APIを設計した。

    TrueTime = [earliest, latest] // 現在時刻の「区間」
    
    • 現在時刻を「点」ではなく「区間」として扱う
    • GPS時計と原子時計を組み合わせて不確実性を最小化
    • それでも数ミリ秒の不確実性が残る

    クリストファー・ニンギ氏の言葉

    > “Unfortunately, there is no now.”(残念ながら、「今」という概念は存在しない)

    分散システムでは、「今」を定義すること自体が不可能だ。

    実践的な対策

    1. タイムスタンプは一意ではない

    // 悪い例:タイムスタンプをIDとして使う
    const id = Date.now();

    // 良い例:UUIDを使用 import { randomUUID } from 'crypto'; const id = randomUUID();

    2. 時間の比較は「不確実性」を考慮

    # 悪い例:厳密な等価比較
    if event1.timestamp == event2.timestamp:
        # 同時イベントとして処理

    良い例:許容範囲を設定

    TOLERANCE_MS = 100 # 100msの許容範囲 if abs(event1.timestamp - event2.timestamp) < TOLERANCE_MS: # ほぼ同時として処理

    3. タイムゾーンはUTCで保存、表示時に変換

    // データベースには常にUTCで保存
    const stored = new Date().toISOString(); // "2026-02-26T05:53:00.000Z"

    // 表示時にユーザーのタイムゾーンに変換 const local = new Date(stored).toLocaleString('ja-JP', { timeZone: 'Asia/Tokyo' });

    4. うるう秒を考慮するか、無視するか決める

    多くのシステムでは、うるう秒を「スメアリング(分散)」して処理:

    • Google: 24時間かけて1秒を分散
    • Amazon: 同様のアプローチ
    • 影響が少ないシステムでは無視も選択肢

    5. NTP/Chronyでクロック同期

    # chronyの設定(クラウド環境向け)
    

    /etc/chrony.conf

    server time.google.com iburst server time.cloudflare.com iburst makestep 0.1 3 # 0.1秒以上のズレで即座に修正

    6. テストで時間をモック可能に

    // Vitest/Jestでの時間モック
    test('handles day boundary correctly', () => {
      vi.setSystemTime(new Date('2026-02-26T23:59:59Z'));
      
      // テスト実行
      const result = processEvent();
      
      vi.useRealTimers();
    });
    

    ---

    まとめ:時間は「信頼できないリソース」

    「Falsehoods Programmers Believe About Time」が10年以上経っても共感を呼ぶのは、時間処理が「解決済みの問題」ではなく「常に注意が必要な領域」だからだ。

    覚えておくべき原則

  • システムクロックは信頼するな - NTPで同期、ドリフトを想定
  • タイムゾーンは政治的決定 - ルールは変わる、UTCで保存
  • タイムスタンプは一意ではない - UUIDを使う
  • 「今」は存在しない - 分散システムでは区間で考える
  • エッジケースをテスト** - うるう年、夏時間、年末年始
  • 時間処理のバグは、本番環境でしか発覚しないことが多い。設計段階から「時間は信頼できない」という前提で開発することが、障害を防ぐ最善の策だ。

    ---

    参考リンク

    コメント

    タイトルとURLをコピーしました