Cloudflare Workers上のHono.js:本番運用6ヶ月の振り返り(ポストモーテム)

Programming tutorial - IT technology blog
Programming tutorial - IT technology blog

なぜスタックをエッジに移行したのか

私は10年近く、Node.jsとExpressでAPIを構築してきました。それは業界標準であり、長年安定して動作していました。しかし、ユーザーベースがグローバルに広がるにつれ、中央集権的なサーバーの限界を無視できなくなりました。レイテンシはコンバージョン率の「静かなる殺人者」です。シンガポールのユーザーがUS-East-1のサーバーにアクセスすると、最初の1バイトが届くまでに200msから300msの遅延が発生します。このラグは体感できるほど大きなものです。

6ヶ月前、私は3つの高トラフィックなマイクロサービスをHono.jsを使用してCloudflare Workersに移行しました。これは単なるフレームワークの入れ替えではなく、インフラストラクチャの完全な再考でした。APIは単一のデータセンターで待機するのではなく、Cloudflareেরグローバルネットワーク上に存在することになります。エンドユーザーからわずか数キロ先のエッジで実行されるのです。本番運用から半年が経過し、結果は明らかです。レスポンスタイムは激減し、インフラのオーバーヘッドはほぼ消失しました。

比較:Express vs. Hono.js

Expressを知っているなら、Hono.jsは馴染みやすく感じるでしょう。ただし、2010年代のような肥大化はありません。ExpressはNode.jsランタイム用に構築されており、歴史的な重みを抱えています。一方、Cloudflare Workersは、Chromeブラウザ内部と同じ高速なテクノロジーであるV8 isolateエンジンで動作します。この環境はNode.jsの標準ライブラリを完全にはサポートしていないため、最新のWeb標準(Web Standards)に合わせて構築されたフレームワークが必要です。

  • ランタイムのオーバーヘッド: 一般的なExpressコンテナは、アイドル状態でも200MBのRAMを消費することがあります。対してHonoは超軽量で、14KB未満に収まります。完全にFetch APIに基づいて構築されているため、モダンなWebに対してネイティブな感覚で動作します。
  • 開発者体験(DX): Expressは古いコールバックパターンに依存していますが、HonoはTypeScriptを第一級市民として扱います。特別な設定なしで、ルートや変数に対して完璧なIDE의自動補完が得られます。
  • ルーティング速度: HonoはRegexpRouterを使用しています。古いフレームワークが採用している線形探索とは異なり、Honoは定数時間でルートをマッチングします。何百ものエンドポイントがあっても、ルーティングのオーバーヘッドは無視できるほど小さいままです。

本番運用6ヶ月の現実

得られたメリット

最大のメリットは、「コールドスタートの消滅」でした。AWS Lambdaでは、一定期間アクセスがないと関数の「起動」に2秒ほどかかることがあります。Cloudflare Workersは5ミリ秒未満で起動します。ユーザーにとって、APIは瞬時に反応するように感じられます。

コスト効率も驚異的でした。月額120ドルの仮想マシンクラスターを、月額5ドルのCloudflare Workersプランに置き換えることができました。アイドル状態のCPUサイクルではなく、実際の実行時間に対してのみ支払うため、インフラ費用は約75%削減されました。スタートアップにとって、これは再確保された膨大な滑走路(ランウェイ)となります。

ベンダーの柔軟性が3つ目の大きな利点です。Honoは標準のWeb API(Request/Response)に準拠しているため、コードが特定のプラットフォームに依存しません。明日、このAPIをDenoやBun、あるいは標準のNode.jsサーバーに移行したくなっても、最小限のリファクタリングで済みます。特定のクラウドプロバイダーのSDKにロックインされることはもうありません。

課題

良いことばかりではありませんでした。V8 isolate環境の標準プランには**厳格な128MBのメモリ制限**があります。重い画像処理や巨大なCSVファイルのメモリ上での操作が必要な場合、壁にぶつかるでしょう。また、C++アドオンやローカルファイルシステムに依存するNode.jsライブラリも使用できません。デプロイ前に、すべての依存関係が「Worker互換」であることを確認する必要があります。

推奨する本番環境の構成

すべてを1つのファイルに詰め込まないでください。保守性の高いAPIにするには、スケールしてもクリーンな状態を保てる構造が必要です。チーム開発において最も効果的だったレイアウトを紹介します。

project-root/
├── src/
│   ├── index.ts        # メインのエントリーポイント
│   ├── routes/         # 機能ベースのルーティング(users, postsなど)
│   ├── middleware/     # 認証、カスタムログ、CORS
│   └── db/             # D1またはKVデータベースのバインディング
├── wrangler.toml       # 環境変数と設定ファイル
├── package.json
└── tsconfig.json

CloudflareのCLIであるWranglerの使用を強くお勧めします。エッジ環境をローカルでシミュレートできるため、従来のサーバーレス開発を悩ませていた「自分のマシンでは動く」というバグを事実上排除できます。

クイックスタート:ゼロからエッジへ

セットアップは迅速です。約30秒で本番対応の雛形を作成できます。まずはHonoのイニシャライザを実行します。

npm create hono@latest my-api

プロンプトが表示されたら `cloudflare-workers` を選択します。以下は、必須のミドルウェアとクリーンなルーティングを含む、標準的な `index.ts` の構成例です。

import { Hono } from 'hono'
import { logger } from 'hono/logger'
import { cors } from 'hono/cors'

const app = new Hono()

// グローバルミドルウェア
app.use('*', logger())
app.use('/api/*', cors())

// シンプルなヘルスチェック
app.get('/', (c) => c.json({ status: 'オンライン', location: 'エッジ' }))

// ダイナミックルート
app.get('/api/user/:id', (c) => {
  const id = c.req.param('id')
  return c.json({
    userId: id,
    timestamp: Date.now()
  })
})

export default app

データへの接続

高速なAPIには高速なデータが必要です。CloudflareはSQLiteベースのサーバーレスSQLデータベースであるD1を提供しています。Hono内でのアクセスは非常に簡単で、定型的な接続ロジック(ボイラープレート)は一切不要です。

app.get('/api/posts', async (c) => {
  // 環境変数からDBバインディングにアクセス
  const { results } = await c.env.DB.prepare(
    'SELECT id, title FROM posts LIMIT 5'
  ).all()
  
  return c.json(results)
})

30秒でのデプロイ

デプロイはこのワークフローの最高の瞬間です。認証が済んだら、以下を実行するだけです。

npm run deploy

コードは最適化・バンドルされ、世界中の300以上のデータセンターにプッシュされます。これにかかる時間は30秒未満です。このスピードはチームの働き方を変えました。大規模でリスクの高い週1回のリリースではなく、小さく段階的なアップデートを1日に何度も行えるようになったのです。

最終的な結論

Cloudflare Workers上のHono.jsに切り替えたことで、仕事の中でも特に退屈な部分がなくなりました。週末にLinuxカーネルのパッチを当てたり、オートスケーリンググループの設定に悩んだりする必要はもうありません。インフラは、10件のリクエストを捌くのと同じくらい簡単に、数百万のリクエストを処理してくれます。新しいプロジェクトを始めるなら、従来のExpressスタックの先を見てください。ユーザーが得られるパフォーマンスの向上と、開発者が得られる心の平穏は、見逃すにはあまりにも惜しいものです。

Share: