この記事の要点
- Apple Design Award を2度受賞したタスク管理アプリ Things を開発する Cultured Code が、デバイス間で ToDo を同期するバックエンド Things Cloud を全面的に書き換え、完全に Swift へ移行 した事例です。本番運用1年あまりの経験がまとめられています。
- 旧サービスは Python 2 と Google App Engine で構築されており、応答の遅さ、メモリ使用量の多さ、静的型付けがないことによる変更リスク、相次ぐ deprecation などの問題を抱えていました。プッシュ通知を高速化するために独自の C 製サービスまで用意していたほどです。
- クライアントアプリですでに中心的に使っていた Swift を選択し、3年がかりで書き換えました。決め手は、優れたパフォーマンス、ARC による予測可能なメモリ管理、信頼性と保守性を支える表現力の高い型システム、C・C++ との相互運用性、そして クライアントとサーバーで同じ言語を使える ことでした。
- 結果として、レガシーシステムと比べて 計算コストが3分の1以下に削減、応答時間も大幅に短縮されました。Swift の性能により、独自の C 製プッシュ通知サービスも Swift 製に置き換えられ、コードベースと運用がシンプルになっています。
採用前の課題
Things Cloud は、デバイス間で ToDo を静かに同期し続ける、アプリ体験の屋台骨となるサービスです。operational transformation や Git の内部構造に着想を得た厳密な理論的基盤に支えられ、12年間の本番運用を通じて信頼性に対するユーザーの信頼を獲得してきました。アーキテクチャ自体は強固でしたが、それを支える技術スタックが時代に取り残されていました。
レガシーの Things Cloud は Python 2 と Google App Engine の上に構築されていました。安定はしていたものの、次のような制約が積み重なっていました。
- 応答の遅さ。 遅いレスポンスがユーザー体験を損なっていました。
- メモリ使用量の多さ。 高いメモリ使用量がインフラコストを押し上げていました。
- 静的型付けがないこと。 Python に静的な型がないため、あらゆる変更がリスクを伴いました。
- 独自の C 製サービスへの依存。 プッシュ通知システムを十分に高速化するために、独自の C ベースのサービスを開発せざるを得ませんでした。
こうした問題の蓄積に加え、いくつもの deprecation が迫っていたことから、チームは変化が必要だと判断しました。
Swiftでどう改善したのか
全面的な書き直しは通常は最終手段ですが、Things Cloud にとっては唯一現実的な道でした。Java、Python 3、Go、C++ などさまざまな言語を検討した結果、すでにクライアントアプリの中核を担っていた Swift が、その潜在力と独自の利点で際立っていました。Swift は、優れたパフォーマンス、ARC による予測可能なメモリ管理、信頼性と保守性のための表現力の高い型システム、C・C++ とのシームレスな相互運用性を約束していました。
当初はサーバー向けの Swift サポートが他のエコシステムほど成熟していないという懸念もありましたが、Apple とオープンソースコミュニティの双方が Swift の進化に強くコミットしていました。Swift は長年 Linux 上で確実にコンパイルでき、Swift Server workgroup は2016年からサーバー領域の取り組みを調整しており、SwiftNIO が基盤的な能力への確信を与え、Vapor がすぐに動かし始めるための一通りのツールを提供してくれました。
記事では、新しい Swift ベースのアーキテクチャを構成する主要コンポーネントが、利用している Swift パッケージとともに紹介されています。同様の移行を検討する人にとっての参考になるよう意図されたものです。
- コードとビルド。 サーバーのコードベースは約30,000行で、60MB のバイナリを生成し、ビルドは10分で完了します。HTTP の Web フレームワークとして Vapor を、その基盤となるネットワークフレームワークとして SwiftNIO を使用しています。単一の「モノリス」バイナリをコンパイルし、実行時に異なるパラメータを渡すことで複数のサービスとして動かしています。開発・デバッグ・テストには Xcode を使い、サーバーとクライアントで一貫した開発体験を得ています。
- デプロイ。 プラットフォーム全体を AWS 上にホストし、Infrastructure as Code ツールの Terraform で管理しています。CI パイプラインでテストを自動化して Swift コードを Docker イメージにビルドし、Kubernetes クラスタにデプロイします。クライアントのトラフィックは HAProxy ロードバランサで適切な Swift サービスへ振り分けます。
- ストレージ。 永続データは Amazon Aurora MySQL に保存し、MySQLKit で接続します。データベースを小さく保つため、利用頻度の低いデータは Soto パッケージ経由で S3 に退避します。プッシュ通知やキャッシュのような一時的なデータは、RediStack 経由で Redis に保存します。
- その他のサービス。 Apple Push Notification service との通信には APNSwift を使います。Mail to Things 機能は、メール処理向けの成熟したライブラリを理由に Python 3 で書かれた AWS Lambda 上の処理が担い、その結果を Amazon Simple Queue Service 経由で Swift に渡しています。
- モニタリング。 Swift では独自のロガーで JSON ログを生成し、メトリクスには Swift Prometheus を利用します。ログとメトリクスは Amazon CloudWatch で保存・分析し、インシデント発生時は PagerDuty で担当エンジニアに通知します。さらに、独自の chaos agent が毎日ランダムに Swift サービスの停止やデータベースの再起動といった破壊的な操作を行う chaos testing で、一時的なエラーからの回復力を検証しています。
実運用から得られる示唆
チームは本番投入前に、新しいシステムを既存のレガシーシステムと並走させて検証しました。すべてのリクエストはレガシーシステムが実際に処理しつつ、新システムも独自のロジックとデータベースで同じリクエストを並行処理します。これにより、ユーザー体験を一切リスクにさらすことなく、本番相当の条件で開発・テストでき、最初から十分に鍛えられたシステムを投入できました。
1年あまりの本番運用を経て、Swift はサーバーサイド開発でその約束を果たしたと報告されています。
- コストとパフォーマンス。 Kubernetes クラスタは、それぞれ2仮想 CPU・8GB メモリのインスタンス4台で構成され、ピーク時には秒間500リクエストを処理しています。レガシーシステムと比べて計算コストは3分の1以下に削減され、応答時間も劇的に短縮されました。
- コードベースの簡素化。 Swift の優れた性能により、独自の C 製プッシュ通知サービスを Swift 製に置き換えられ、コードベースと運用が大きくシンプルになりました。
- 安定性。 モダンで表現力の高い言語に移行しつつ、コードはよく動作し、必要な統合はすべて Swift エコシステムが提供してくれました。1年の本番運用で運用上の問題は一度も発生していません。
チームは他のチームにもサーバー指向のプロジェクトで Swift を検討するよう勧めています。今回は全面的な書き直しを選びましたが、Java 相互運用性の強化に向けた取り組みなどを踏まえると、段階的な Swift の採用も魅力的な選択肢だとしています。重い同期処理を伴う長寿命のクラウドサービスにおいても、Swift がパフォーマンス・信頼性・保守性を両立できることを示す事例です。