Skip to content
Published on

巨大ソフトウェアはどのように開発・管理・維持されるのか?— SWアップデートシステムまで

Authors

はじめに

私たちが毎日使うChromeブラウザ、Android OS、Netflixアプリは数十億行のコードで構成された巨大なソフトウェアだ。これらは一人の天才が作ったのではなく、数千人の開発者が同時に協業して作り上げたものだ。

果たしてこのような巨大なソフトウェアはどのように開発し、どのようにデプロイし、どのようにアップデートし、どのように長期的に維持するのか?

この記事では大規模ソフトウェア開発の核心原理を、アーキテクチャからアップデートシステムの実装まで全方位的に扱う。


1. 巨大ソフトウェアの定義

どこからが「巨大」か?

一般的に以下の条件の一つ以上を満たせば大規模(Large-Scale)ソフトウェアに分類される。

基準
コード規模数百万〜数十億行
開発人員数百〜数千名
サービス数数百〜数千のマイクロサービス
ユーザー規模数億人以上のDAU
デプロイ頻度1日数十〜数百回

実例

  • Google:単一モノレポに約20億行以上のコード。毎日数万件のコミットが発生し、約25,000人以上の開発者が同時に作業。
  • Meta(Facebook):単一レポで数万人のエンジニアが協業。自社ビルドシステムBuckを使用。
  • Netflix:約1,000以上のマイクロサービスで運営。1日数千回のデプロイを実行。カオスエンジニアリングの先駆者。

2. モノレポ vs マルチレポ

大規模ソフトウェアを管理する最初の決定はコードリポジトリ戦略だ。

モノレポ(Monorepo)

すべてのプロジェクトのコードを一つのリポジトリで管理する方式。

company-repo/
  frontend/
  backend/
  mobile/
  infra/
  shared-libs/
  tools/

利点: コード共有が容易、アトミックコミットが可能、リファクタリングが容易、一貫したツーリング

欠点: リポジトリサイズが巨大化、ビルドシステムが複雑化、アクセス制御が困難

マルチレポ(Multi-repo)

各プロジェクト、サービス、ライブラリを独立したリポジトリで管理する方式。

利点: チーム別の独立した開発サイクル、小さいリポジトリで高速クローン、明確なサービス境界

欠点: コード共有が煩雑、依存関係地獄、クロスプロジェクトリファクタリングが困難

主要企業の選択

企業戦略ツール
GoogleモノレポPiper(自社VCS)+ Bazel
MetaモノレポMercurial + Buck
NetflixマルチレポGradle + Nebula
Microsoftモノレポ(一部)VFS for Git(GVFS)
AmazonマルチレポBrazil(自社ビルドシステム)

核心は「どの戦略が絶対的に良い」ではなく、組織の規模と文化に合った戦略を選ぶこと。


3. マイクロサービスアーキテクチャ

モノリスからマイクロサービスへ

初期のスタートアップは通常一つのアプリケーション(モノリス)で始める。しかし規模が大きくなるとビルド時間の増大、単一障害による全体ダウン、チーム間のコード衝突、特定機能のスケールアウト不能などの問題が発生する。

**マイクロサービスアーキテクチャ(MSA)**でこれらの問題を解決する。

MSAの核心構成要素

APIゲートウェイ - すべてのクライアントリクエストの単一エントリーポイント。

# APIゲートウェイルーティング例
routes:
  - path: /api/users
    service: user-service
    methods: [GET, POST]
  - path: /api/orders
    service: order-service
    methods: [GET, POST, PUT]
  - path: /api/payments
    service: payment-service
    methods: [POST]

サービスメッシュ - サービス間通信を管理するインフラレイヤー。Istio、Linkerdなどを使用。

サービスディスカバリ - 数百のサービスが互いを見つけるメカニズム。Consul、Eureka、Kubernetes DNS。

サーキットブレーカー - 特定サービスの障害時にカスケード障害を防止するパターン。

# サーキットブレーカー擬似コード
class CircuitBreaker:
    def __init__(self, threshold=5, timeout=30):
        self.failure_count = 0
        self.threshold = threshold
        self.timeout = timeout
        self.state = "CLOSED"  # CLOSED, OPEN, HALF_OPEN

    def call(self, func):
        if self.state == "OPEN":
            if self.timeout_expired():
                self.state = "HALF_OPEN"
            else:
                raise CircuitOpenError("Service unavailable")
        try:
            result = func()
            self.on_success()
            return result
        except Exception as e:
            self.on_failure()
            raise e

    def on_failure(self):
        self.failure_count += 1
        if self.failure_count >= self.threshold:
            self.state = "OPEN"

    def on_success(self):
        self.failure_count = 0
        self.state = "CLOSED"

4. CI/CDパイプライン

継続的インテグレーション(CI)

開発者がコードをプッシュすると自動的にビルド、テスト、静的解析が実行される。

# GitHub Actions CIパイプライン例
name: CI Pipeline
on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

jobs:
  build-and-test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
      - name: Install dependencies
        run: npm ci
      - name: Lint
        run: npm run lint
      - name: Unit Tests
        run: npm run test:unit
      - name: Build
        run: npm run build

継続的デプロイ(CD)- デプロイ戦略

カナリア(Canary)デプロイ - 新バージョンを全体ユーザーの一部(例:1-5%)にのみ先にデプロイ。問題がなければ段階的に比率を上げる。

ブルーグリーン(Blue-Green)デプロイ - 2つの同一環境を維持し、トラフィックを一度に切り替える。

ローリング(Rolling)アップデート - インスタンスを1つずつ順次交換。Kubernetesのデフォルトデプロイ戦略。


5. フィーチャーフラグ(Feature Flags)

デプロイとリリースを分離する

フィーチャーフラグはコードをデプロイするが機能はまだ有効にしない技法。

// フィーチャーフラグ使用例
const featureFlags = {
  newCheckoutFlow: false,
  darkMode: true,
  aiRecommendation: false,
};

function renderCheckout() {
  if (featureFlags.newCheckoutFlow) {
    return renderNewCheckout();
  }
  return renderLegacyCheckout();
}
活用事例説明
段階的リリース1% - 10% - 50% - 100%で公開
A/Bテスト2つのUIを比較分析
キルスイッチ障害発生時に即座に機能を無効化
ベータテスト特定ユーザーグループのみに公開

注意:フィーチャーフラグが溜まると技術的負債になる。リリース完了後のフラグは必ず整理する。


6. SWアップデートシステムの実装

この記事の核心セクション。ソフトウェアを開発するのと同じくらい重要なのがユーザーにアップデートを安全かつスムーズに届けるシステムだ。

6-1. デスクトップアプリのアップデート

Electron auto-updater - Electronベースのアプリ(VS Code、Slack Desktop、Discordなど)の大半がelectron-updaterを使用。

import { autoUpdater } from 'electron-updater';

autoUpdater.setFeedURL({
  provider: 'github',
  owner: 'my-org',
  repo: 'my-app',
});

autoUpdater.on('update-available', (info) => {
  log.info('新バージョン発見:', info.version);
  showUpdateNotification(info);
});

autoUpdater.on('update-downloaded', (info) => {
  showRestartDialog(info.version);
});

setInterval(() => {
  autoUpdater.checkForUpdates();
}, 4 * 60 * 60 * 1000);

デルタ(Delta)アップデート - ファイル全体を再ダウンロードせず、変更部分のみをダウンロードする方式。

全体アップデート:v1.0 (200MB) -> v1.1 (200MB) = 200MBダウンロード
デルタアップデート:v1.0 (200MB) -> v1.1 (200MB) = 15MBダウンロード(差分のみ)

6-2. モバイルアプリのアップデート

段階的リリース(Staged Rollout) - Google Playの段階的リリース機能を使い全体ユーザーの一部にのみ先にデプロイ。

OTA(Over-The-Air)アップデート - アプリストアを経由せずJavaScriptバンドルを直接プッシュする方式。React NativeでEAS Updateを通じて実装可能。

注意:AppleのガイドラインではOTAでアプリの主要な機能や目的を変更することは禁止。

6-3. サーバーアップデート(デプロイ)

# ArgoCDを利用したGitOpsベースのサーバーアップデート
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: my-service
spec:
  project: default
  source:
    repoURL: https://github.com/my-org/k8s-manifests
    path: services/my-service
    targetRevision: main
  destination:
    server: https://kubernetes.default.svc
    namespace: production
  syncPolicy:
    automated:
      prune: true
      selfHeal: true

ホットスワップ vs コールドアップデート

方式説明使用例
ホットスワップアプリ再起動なしに一部モジュルを交換Javaクラスリロード、Erlangホットコードローディング
ウォームリスタートプロセスのみ再起動(OS再起動なし)Node.js graceful restart
コールドアップデートアプリ完全停止後に再インストールデスクトップアプリ、OSアップデート

7. 技術的負債の管理

技術的負債の類型

意図的 + 慎重:「今はシンプルな実装で行き、来期リファクタリングしよう」
意図的 + 無謀:「設計は後で、まず速く作ろう」
非意図的 + 慎重:「実装後に振り返るとより良い設計があった」
非意図的 + 無謀:「レイヤードアーキテクチャって何?」

リファクタリング戦略

ボーイスカウトルール:コードを見つけた時よりきれいにして去れ。 ゴールデンパス優先:最も多く使われるコードパス(Happy Path)からリファクタリング。 ストラングラーフィグパターン:レガシーシステムを一度に置き換えず、新システムに段階的にトラフィックを移動。


8. 障害対応

ポストモーテム

障害後に作成する分析文書。**非難のない文化(Blameless Culture)**が核心。

SRE(Site Reliability Engineering)

Googleが作った概念。ソフトウェアエンジニアリング方法論を運用に適用する。核心概念:SLI、SLO、SLA、エラーバジェット。

オンコール(On-Call)文化

オンコールローテーション例:

週間1:エンジニアA (Primary)、エンジニアB (Secondary)
週間2:エンジニアB (Primary)、エンジニアC (Secondary)

アラートエスカレーション:
  0分:PagerDuty -> Primaryエンジニア
  5分:無応答 -> Secondaryエンジニア
 15分:無応答 -> チームリード
 30分:未解決 -> VP of Engineering

9. ドキュメンテーションとナレッジ管理

ADR(Architecture Decision Record)

アーキテクチャの決定を文書化する軽量フレームワーク。

RFC(Request for Comments)

大きな技術的変更前にチーム全体のフィードバックを得るプロセス。

主要企業の慣行:

  • Google:Design Docs文化が非常に強い
  • Meta:WorkplaceでRFCを共有し議論
  • Amazon:6-pagerドキュメントとナラティブベースの会議文化が有名

10. オープンソースの力

InnerSource(内部オープンソース)

オープンソース開発方法論を企業内部に適用すること。

一般的な企業開発:
  チームA -> ライブラリX依頼 -> チームB -> バックログ待ち(2-3ヶ月)

InnerSource方式:
  チームA -> ライブラリXPR提出 -> チームBレビュー -> マージ(1-2週間)

実践チェックリスト

アーキテクチャ
  [ ] サービス境界が明確に定義されているか?
  [ ] APIバージョン管理戦略があるか?
  [ ] サーキットブレーカー/リトライポリシーが実装されているか?

CI/CD
  [ ] コードコミットからプロダクションデプロイまで自動化されているか?
  [ ] ロールバックが5分以内に可能か?

アップデートシステム
  [ ] 自動アップデートが実装されているか?
  [ ] アップデートファイルの整合性検証をしているか?
  [ ] アップデート失敗時のロールバック機構があるか?

モニタリング
  [ ] SLI/SLOが定義されているか?

技術的負債
  [ ] 技術的負債を定期的に測定しているか?
  [ ] リファクタリングに割り当てられたエンジニアリング時間があるか?

ドキュメンテーション
  [ ] ADRを書いているか?
  [ ] APIドキュメントが自動生成されているか?

まとめ

巨大ソフトウェアの開発と保守は単に「コーディングがうまい」ことではない。アーキテクチャ設計、ビルドシステム、デプロイ戦略、アップデートメカニズム、障害対応、ドキュメンテーション、チーム文化まですべてが一つのシステムとして有機的に機能しなければならない。

核心教訓:

  1. 小さく作り、頻繁にデプロイせよ - 大きなリリースより小さな変更の連続が安全
  2. 自動化できるなら自動化せよ - CI/CD、テスト、アップデート、モニタリングすべて
  3. 失敗を前提に設計せよ - サーキットブレーカー、ロールバック、カナリアデプロイは必須
  4. アップデートは安全に - チェックサム検証、署名確認、ロールバック機構を備えよ
  5. ドキュメンテーションは投資だ - ADR、RFC、ポストモーテムは未来の自分のため
  6. 技術的負債は利息がつく - 定期的に返済しなければ開発速度が急落する

良いソフトウェアは一度で作られるものではない。絶えず改善し、安全にデプロイし、失敗から学ぶプロセスの繰り返しだ。


参考資料

  • Google Engineering Practices
  • Software Engineering at Google (O'Reilly)
  • The Site Reliability Workbook (O'Reilly)
  • Martin Fowler - Microservices
  • Electron auto-updater公式ドキュメント
  • The Twelve-Factor App