Upgrade to Pro — share decks privately, control downloads, hide ads and more …

コンパウンドプロダクト開発のためのローカルプロセスマネージャー再発明 #layerxgo

コンパウンドプロダクト開発のためのローカルプロセスマネージャー再発明 #layerxgo

Avatar for izumin5210

izumin5210

August 24, 2026

More Decks by izumin5210

Other Decks in Programming

Transcript

  1. whoami @izumin5210 LayerX バクラク事業部 (2022-09 -) Platform Engineering 部 Enabling

    チーム / Dev Infrastructure チーム Staff Software Engineer バックエンドや Web フロントエンドが専門 ISUCON14 4位 好きな Go 1.27 は net/http/httptest.NewTestServer © LayerX Inc.
  2. 前提 ローカルで動かすの大変がち バクラクは複数のプロダクトを 1 つのモノレポで開発するコンパウンドプロダクト 1 つの画面の裏で、サービス同士は互いに呼び合っている: 経費精算 webapp →

    Nuxt / Next.js graphql-gateway → Node.js 経費精算 API Go → ユーザー管理 API → OCR → ビジネスカード API 全部起動はマシンリソース的に厳しい(し、シェルも何個必要やねんという話) 必要最低限だけ手で起動するのも認知負荷的に厳しい(新入社員にはほぼ無理) © LayerX Inc. 8
  3. 変遷 第 1 世代: lxdev — TUI で選んで起動 TUI でサービスを選んで起動する

    プロセスマネージャ シェルが1つにまとまり見通しやすい 「何を起動すればいいか」の共有 よく使う組み合わせを preset として YAML に定義 メンテが大変で存在を知らないと使え ない サービスが増えるほど、 選ぶ・覚えるのも難しい © LayerX Inc. 10
  4. 変遷 第 2 世代: go-all — 全部入り 1 バイナリ 全

    Go サービスを modular monolith 的に 1 つのバイナリ にまとめる 1 つの port で listen し、path ベースの routing で各サービスの handler へ振り分ける (サービス間通信は Connect RPC なので、path prefix でサービスを判別できる) lxdev spawn → :8080 go-all — 1 プロセスに全 Go サービス path で svc B svc C 振り分け → svc A … 課題: 巨大化してビルドが遅い。1 行の変更でも全サービスを再ビルド © LayerX Inc. 11
  5. 変遷 第 3 世代: go-all-plugins — オンデマンドビルド リクエストが来た Go サービスだけをその場でビルドして起動する

    触っているサービスの分のみのビルドで済む lxdev spawn → :8080 request が来たら go build & 起動 → go-all-plugins svc A svc B svc C … 課題①: Go 以外 (Node.js / Python) は守備範囲外 課題②: サービスをまたぐリクエストが逐次ビルドになって遅い © LayerX Inc. 12
  6. 変遷 オンデマンド起動の宿命 — チェインすると逐次ビルド 「リクエストが来てからビルド」なので、サービスをまたぐと webapp のビルドが終わる → API を呼ぶ

    → そこから API のビルドが始まる チェインの深さぶんだけ、待ちが直列に積み上がる webapp API その 1 API その 2 □ 破線 = ビルド + 起動待ち / ▪ 塗り = RUNNING 初回リクエストの体感が、チェインが深いほど悪くなる © LayerX Inc. 13
  7. lxpm プロセスマネージャが全ポートを listen する browser / curl ① request →

    localhost:3014 lxpm daemon :3000 :3001 :3013 :3014 :8080 :9000 :9001 … 64 port expense-webapp pnpm dev ② build & spawn → ③ proxy → 他の約 200 サービスは 未起動のまま daemon が定義済みの port を最初に bind し、サービスのプロセスは起動しない リクエストが来たらその port の担当サービスをビルドして起動し、以後は proxy lxdev の「選ぶ」も、go-all の「全部ビルド」も、どちらも不要に © LayerX Inc. 15
  8. lxpm アーキテクチャ lx pm CLI / TUI Connect RPC (UDS)

    ⇄ port listener 64 port を bind lxpm daemon dispatcher runtime 振り分け / proxy → path / cookie で → build & spawn spawn → go build した bin uv run pnpm dev CLI と daemon は同一バイナリで、 lx pm start したプロセスが daemon になる プロセス定義 ( ProcessSpec ) は proto で宣言し、registry として Go コードに持つ 子プロセスの stdout/stderr は 1 プロセス 1 ファイルに分離して tail できる © LayerX Inc. 17
  9. lxpm ProcessSpec — 宣言は「routing × build」 routing と build の直交する

    2 軸で、 Connect RPC plugin・webapp・worker を 同じ 1 message で表現する プロセスの起動やルーティングに関する 仕様は、すべてこの構造体で表現される © LayerX Inc. 18
  10. lxpm RoutingSpec — 外からどう見えるか 公開 port を listen するのは親 (lxpm)

    match : 同じ port に複数サービスを ぶら下げるときの振り分け条件 connection : 子との繋ぎ方 routing ごと書かなければ、 公開 port なしの常駐 worker になる © LayerX Inc. 19
  11. lxpm BuildSpec — 実行ユニットをどう用意するか : source_dir を 起動のたびに go build

    して spawn external_command : 任意のコマンドを そのまま spawn (pnpm dev / uv run …) どちらで作った子プロセスも、 以後の扱い (routing・ログ・停止) は同じ go_build © LayerX Inc. 20
  12. lxpm 宣言例① Go — Connect RPC plugin registry は proto

    生成型の struct literal を並べた Go ファイル Routing : :9000 に相乗りし、 自分の service 宛の path prefix だけ受ける Connection : uds_http — 親子は UDS で繋ぐ Build : go_build — 起動のたびに go build この形は Connect RPC の service 定義 (API 側の proto) から自動生成される ので、手では書かない © LayerX Inc. 21
  13. lxpm 宣言例② Go / Python — 常駐 worker を書かない =

    公開 port なし。リクエスト起点の lazy spawn はないので、 auto_start / depends_on / 明示 start で起動する Routing © LayerX Inc. 22
  14. lxpm 宣言例③ Node.js — Next.js webapp : tcp_l4 — :3014

    を素通しで公開 Build : external_command — pnpm dev を子として spawn DependsOn : 依存プロセスの宣言 ①の expense-api もここで繋がる Routing © LayerX Inc. 23
  15. lxpm Routing (connection) の実装 — 子への port の受け渡し 各サービスの制約は PORT

    を考慮する だけ → 言語を問わず対応可能 L4 passthrough なので WebSocket / HMR / SSE もそのまま通る © LayerX Inc. 25
  16. lxpm Routing (match) の実装 — 1 つの port に複数のサービス Connect

    RPC は path が /pkg.Service/Method 形式なので、 path prefix でサービスを判別できる = 複数のサービスが同じ port に相乗りできる 相乗りしていても、build & spawn されるのはリクエストが match したサービスだけ 26 © LayerX Inc.
  17. lxpm Routing (match) の応用 — フレームワークの段階移行 同じ port に新旧 2

    つの webapp をぶら下げ、移行済みのリクエストだけ新実装へ流す 実際に Nuxt → Next.js の段階移行がこの宣言だけで動いている © LayerX Inc. 27
  18. lxpm dependsOn — 「同時に動いていてほしい」を宣言する 変遷パートで見た課題② (チェインの逐次ビルド) への答え expense-webapp への最初のリクエストで、依存ごと同時に温まる expense-webapp

    expense-api user-api server と非同期処理を担う worker の同時起動などにも使う © LayerX Inc. □ 破線 = ビルド + 起動待ち / ▪ 塗り = RUNNING 28
  19. 操作 再掲 — daemon は RPC サーバで、UI はただのクライアント lx pm

    CLI agent skill TUI (AI) Connect RPC spawn / lxpm daemon (UDS) ⇄ port listener / dispatcher / runtime proxy → 子プロセス群 操作の口は Connect RPC 1 つ。人間用の UI も AI 用の口も、全部ただのクライアント CLI は lx pm ps / stop / restart / … などのコマンドを提供 --format json © LayerX Inc. で機械も読める 30
  20. 操作 CLI + Skills — AI のための状態把握・操作用インタフェース agent skill を同梱:

    AI が ps → restart → ログ確認までを自律で回す --reason で起動理由を宣言させ、人間は REASON 列で「いつ・なぜ」を監査できる © LayerX Inc. 32
  21. 操作 autoload がいらない世界 lxpm は意図して、Go プロセスの live reload を持たない agent

    coding ではファイルが高速・大量に書き換わる language server も重い大規模モノレポ file watch 起因の高頻度ビルドまで重なるとマシンリソースが厳しい 「一連の編集が完了した」を判断できるのは編集した本人 AI が自分で restart すればいい agent skill が「 .go を編集したら動作確認の前に restart」と教えている (hook で決定論的に実行してもいいですね) © LayerX Inc. 33
  22. 操作 行き着く先 — UI を開かない process management lxpm は社内のリモート開発環境 (background

    agent 基盤) でもそのまま動いている on-demand 起動 = 人の明示的な操作や確認がなくても、 リクエストさえ来ればプロセスが立ち上がる agent が動作確認のために URL を叩くだけで、必要なプロセス群が dependsOn ごと温まる 極論、ターミナルも process manager の UI も開かないままローカル動作確認が可能 © LayerX Inc. https://speakerdeck.com/layerx/built-our-own-background-agent-at-layerx-number-aidevex-findy 34
  23. 操作 オンボーディングも省力化される 新しく来た開発者がローカル環境を立ち上げるのに必要な知識は、環境構築のあとは 「lxpm の使い方と、担当プロダクトのメインアプリの port 番号」だけ ブラウザで開けば、必要なサービス群が dependsOn ごと勝手に温まる

    nix によるローカル環境構築と、開発用 secret 読み出しの自動化* とあわせて、 オンボーディングを大幅に省力化できている © LayerX Inc. * ローカル開発のシークレット設定を自動化する ── Go × AWS Secrets Manager https://zenn.dev/layerx/articles/762ecd4494694f 35
  24. 操作 その他の細かい機能 : gateway や認証系など「どうせ全員使う」ものだけ pre-warm する lazy の例外 debug

    build: TUI の d で、その 1 プロセスだけ debug build で再起動して dlv attach できる ProvisionHook : 初回起動時に一度だけ DB 作成・migrate を実行する宣言 PreBuildHook : 毎回の起動で、ビルドの前に挟む hook ( pnpm build など) AWS SSO keep-alive: daemon が credential を定期リフレッシュし、期限切れを防ぐ takeover: worktree ごとに daemon を立てられ、指定したプロセスの実行だけを 自分の worktree に移せる (他のプロセスは再起動されない) auto_start © LayerX Inc. 36
  25. まとめ ローカルプロセスマネージャー再発明 複数サービスが絡み合うプロダクトは、いずれローカルで「全部起動」が困難になる とはいえ選択的起動も認知負荷が高い lxpm の答え: port を全部握って、リクエストが来たものだけ起動する 境界を「プロセスと port」に置けば、

    を読める言語なら何でも管理できる仕組み オンデマンド起動の弱点 (チェインの逐次ビルド) は の宣言で補う これにより「lxpm 起動するだけでだいたいいい感じに動く」世界に PORT dependsOn agent coding 時代、人間向けだけでなく AI 向けの interface (Skills, CLI) があることでより自律的に開発が進む lxdev → go-all → go-all-plugins → lxpm — どれもその時点のスケールでは正解で、 スケールが変わる(課題意識が変わる)たびに、常識を疑い開発環境を作り直してきた © LayerX Inc. 38