Share
Embed
コンパウンドプロダクト開発のためのローカルプロセスマネージャー再発明 #layerxgo
izumin5210
August 24, 2026
Transcript
38 slides
-
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. -
前提 ローカルで動かすの大変がち バクラクは複数のプロダクトを 1 つのモノレポで開発するコンパウンドプロダクト 1 つの画面の裏で、サービス同士は互いに呼び合っている: 経費精算 webapp →
Nuxt / Next.js graphql-gateway → Node.js 経費精算 API Go → ユーザー管理 API → OCR → ビジネスカード API 全部起動はマシンリソース的に厳しい(し、シェルも何個必要やねんという話) 必要最低限だけ手で起動するのも認知負荷的に厳しい(新入社員にはほぼ無理) © LayerX Inc. 8 -
変遷 第 1 世代: lxdev — TUI で選んで起動 TUI でサービスを選んで起動する
プロセスマネージャ シェルが1つにまとまり見通しやすい 「何を起動すればいいか」の共有 よく使う組み合わせを preset として YAML に定義 メンテが大変で存在を知らないと使え ない サービスが増えるほど、 選ぶ・覚えるのも難しい © LayerX Inc. 10 -
変遷 第 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 -
変遷 第 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 -
変遷 オンデマンド起動の宿命 — チェインすると逐次ビルド 「リクエストが来てからビルド」なので、サービスをまたぐと webapp のビルドが終わる → API を呼ぶ
→ そこから API のビルドが始まる チェインの深さぶんだけ、待ちが直列に積み上がる webapp API その 1 API その 2 □ 破線 = ビルド + 起動待ち / ▪ 塗り = RUNNING 初回リクエストの体感が、チェインが深いほど悪くなる © LayerX Inc. 13 -
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 -
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 -
lxpm ProcessSpec — 宣言は「routing × build」 routing と build の直交する
2 軸で、 Connect RPC plugin・webapp・worker を 同じ 1 message で表現する プロセスの起動やルーティングに関する 仕様は、すべてこの構造体で表現される © LayerX Inc. 18 -
lxpm RoutingSpec — 外からどう見えるか 公開 port を listen するのは親 (lxpm)
match : 同じ port に複数サービスを ぶら下げるときの振り分け条件 connection : 子との繋ぎ方 routing ごと書かなければ、 公開 port なしの常駐 worker になる © LayerX Inc. 19 -
lxpm BuildSpec — 実行ユニットをどう用意するか : source_dir を 起動のたびに go build
して spawn external_command : 任意のコマンドを そのまま spawn (pnpm dev / uv run …) どちらで作った子プロセスも、 以後の扱い (routing・ログ・停止) は同じ go_build © LayerX Inc. 20 -
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 -
lxpm 宣言例② Go / Python — 常駐 worker を書かない =
公開 port なし。リクエスト起点の lazy spawn はないので、 auto_start / depends_on / 明示 start で起動する Routing © LayerX Inc. 22 -
lxpm 宣言例③ Node.js — Next.js webapp : tcp_l4 — :3014
を素通しで公開 Build : external_command — pnpm dev を子として spawn DependsOn : 依存プロセスの宣言 ①の expense-api もここで繋がる Routing © LayerX Inc. 23 -
lxpm Routing (connection) の実装 — 子への port の受け渡し 各サービスの制約は PORT
を考慮する だけ → 言語を問わず対応可能 L4 passthrough なので WebSocket / HMR / SSE もそのまま通る © LayerX Inc. 25 -
lxpm Routing (match) の実装 — 1 つの port に複数のサービス Connect
RPC は path が /pkg.Service/Method 形式なので、 path prefix でサービスを判別できる = 複数のサービスが同じ port に相乗りできる 相乗りしていても、build & spawn されるのはリクエストが match したサービスだけ 26 © LayerX Inc. -
lxpm Routing (match) の応用 — フレームワークの段階移行 同じ port に新旧 2
つの webapp をぶら下げ、移行済みのリクエストだけ新実装へ流す 実際に Nuxt → Next.js の段階移行がこの宣言だけで動いている © LayerX Inc. 27 -
lxpm dependsOn — 「同時に動いていてほしい」を宣言する 変遷パートで見た課題② (チェインの逐次ビルド) への答え expense-webapp への最初のリクエストで、依存ごと同時に温まる expense-webapp
expense-api user-api server と非同期処理を担う worker の同時起動などにも使う © LayerX Inc. □ 破線 = ビルド + 起動待ち / ▪ 塗り = RUNNING 28 -
操作 再掲 — 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 -
操作 TUI — 人間のための状態把握・操作用インタフェース lazy spawn は「何が動いているか 見えない」と不安になりがち → 全体が見える顔をちゃんと作る
bubbletea 製 github.com/charmbracelet/bubbletea daemon の状態変化を購読してライブ 更新 © LayerX Inc. 31 -
操作 CLI + Skills — AI のための状態把握・操作用インタフェース agent skill を同梱:
AI が ps → restart → ログ確認までを自律で回す --reason で起動理由を宣言させ、人間は REASON 列で「いつ・なぜ」を監査できる © LayerX Inc. 32 -
操作 autoload がいらない世界 lxpm は意図して、Go プロセスの live reload を持たない agent
coding ではファイルが高速・大量に書き換わる language server も重い大規模モノレポ file watch 起因の高頻度ビルドまで重なるとマシンリソースが厳しい 「一連の編集が完了した」を判断できるのは編集した本人 AI が自分で restart すればいい agent skill が「 .go を編集したら動作確認の前に restart」と教えている (hook で決定論的に実行してもいいですね) © LayerX Inc. 33 -
操作 行き着く先 — 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 -
操作 オンボーディングも省力化される 新しく来た開発者がローカル環境を立ち上げるのに必要な知識は、環境構築のあとは 「lxpm の使い方と、担当プロダクトのメインアプリの port 番号」だけ ブラウザで開けば、必要なサービス群が dependsOn ごと勝手に温まる
nix によるローカル環境構築と、開発用 secret 読み出しの自動化* とあわせて、 オンボーディングを大幅に省力化できている © LayerX Inc. * ローカル開発のシークレット設定を自動化する ── Go × AWS Secrets Manager https://zenn.dev/layerx/articles/762ecd4494694f 35 -
操作 その他の細かい機能 : 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 -
まとめ ローカルプロセスマネージャー再発明 複数サービスが絡み合うプロダクトは、いずれローカルで「全部起動」が困難になる とはいえ選択的起動も認知負荷が高い lxpm の答え: port を全部握って、リクエストが来たものだけ起動する 境界を「プロセスと port」に置けば、
を読める言語なら何でも管理できる仕組み オンデマンド起動の弱点 (チェインの逐次ビルド) は の宣言で補う これにより「lxpm 起動するだけでだいたいいい感じに動く」世界に PORT dependsOn agent coding 時代、人間向けだけでなく AI 向けの interface (Skills, CLI) があることでより自律的に開発が進む lxdev → go-all → go-all-plugins → lxpm — どれもその時点のスケールでは正解で、 スケールが変わる(課題意識が変わる)たびに、常識を疑い開発環境を作り直してきた © LayerX Inc. 38