フルスタックDartからRustバックエンドへ

この記事は10X 新春ブログリレー 2026の1月5日分の記事です。


弊社が提供するネットスーパーのサービスは、モバイルアプリとWebはFlutterアプリ、バックエンドはDartのgRPCサーバーで実装されています。isomorphicではないですが、言語統一がされたフルスタック的な状態と言えると思います。

バックエンドでのDartはマイナーであり、それに伴って様々なデメリットもありました。それらを乗り越えたり飲み込んだりしながら、5年ほどサービスを運用してきましたが、これから先は方針を転換することに決めました。

この記事では、我々が何故フルスタックDartから方針転換することにしたのかと、今後のバックエンドの言語としてRustが有力になっている背景を説明します。

方針転換のきっかけは採用

バックエンドのDartでは様々な問題にぶつかってきましたが、どれも方針転換に踏み切るほどにはならず、なんとか対処してきたのがこれまでです。そんな中、方針転換のきっかけとなったのが採用です。

事業の初期フェーズではマルチスキルのソフトウェアエンジニアで開発チームを構成し、技術領域で役割を分けないやり方をしてきました。事業のフェーズが進むにしたがって、バックエンドの開発ニーズが高まりましたが、世間のバックエンドエンジニアからすればDartは積極的に選びたい言語ではありません。マネジメントの立場としてはバックエンドを強化したいが、技術スタックがマッチしていないという状況になっていました。

技術的な課題であればなんとかできる可能性もありますが、採用と技術スタックのアンマッチは変えない限りはどうにもなりません。バックエンドにおけるDartの立ち位置が変わるのを待つか、自分たちがバックエンドで使う言語を変更するかを考えた時、会社としては後者を選ぶことに決めました。

バックエンドのDartのふりかえり

次の言語を選ぶ前に、バックエンドにおけるDartのメリットとデメリットを整理しました。

メリットとしては以下のものがありました。

  • 静的型付き言語として程々に機能が揃っている。nullabilityやmutabilityの制御ができる、網羅性検証つきのパターンマッチがある、といったラインは抑えられており、ロジックの記述における不便は少ない。
  • 学習コストが高くない。言語仕様はJavaとKotlinの間くらいという感じで、際立った特徴は少ないのでキャッチアップしやすい。複雑な型制約などが書けないこともあり、言語の理解度の差による実装方法に差が大きくなり過ぎない。
  • シングルバイナリで動かせる。

デメリットとしては以下のものがありました。

  • バックエンドのエコシステムが未成熟。ミドルウェアのドライバはあったりなかったりで、あっても不安なものもある。外部サービスのSDKはまずない。モニタリングや分散トレーシングの実装も少ない。
  • 基本的にはシングルスレッドであり、CPUバウンドな処理はつまりやすい。Isolateで逃すことはできるが、Isolateの立ち上げが重い、メモリ共有ができない、スタックトレースが分断される、といったハードルがあり、広くは使いづらい。

キープできるメリットはキープしつつ、解消すべきデメリットは解消することにしました。

運営自律性という事業上の特性

言語の候補を選ぶ上では、我々の事業に合っているかどうかも考慮しました。

10Xの社内には運営自律性と呼ばれる特性があります。システムの利用者が失敗をタイムリーに認識し、自力で訂正できる状態になっているかどうかを指すものです。プロダクトが扱う業務フローが複雑で、多様なシステムとも連携する必要があるため、運営自律性の有無は運用コストに大きく影響します。

運営自律性はタダではなく、それなりのコストを払う必要があります。開発者がエラーをトレースできるだけでは不十分で、エラーを業務上の意味のあるアプリケーションの仕様として扱うため、その分の実装コストが乗るためです。このコストを払うべきかはプロダクトによって異なると思いますが、我々のプロダクトでは必要だと判断しています。

運営自律性の得やすさは、言語がエラーをどう扱うかに左右されます。最下層のレイヤーのエラーから利用者に見せる最上位のエラーまで漏れなく仕様を定義して変換することを前提とすると、一般的な例外や、型なしのエラーでは、コンパイラによる網羅性検証に頼れなくなるため、その分不利になります。そのため、一貫して型付きでエラーで扱える言語が、事業に適していると判断しました。

現在はRustを検証中

これまでに説明した論点を踏まえて色々と検討した結果、現在はRustで一部のシステムを置き換えの検証を進めています。実装面ではかなり良い手応えを得られていて、幅広いエラーを漏れなく仕様に反映する営みが自然に回せています。一方で、人材の多さやエコシステムの充実度という意味では少し不安もあります(それでもDartよりは遥かに良いですが)。

現状不安を感じている点も踏まえて飲めるものか判断し、最終的な決定に進む予定です。

また、今回決めようとしているのはあくまで汎用的にバックエンドで使う言語であり、特定の言語を使った方が良い場面では引き続きその言語を使うことになると思います。Go、Python、TypeScriptに関しては明確にそういう箇所があり、併用していく予定です。

おわりに

以下のインタビューに、Dartで統一した時の状況がよくまとまってました。 https://type.jp/et/feature/15075/

Dartで統一するまでの壁や、統一したことによるメリットなどを話しているのですが、最後は以下の言葉で締めくくられていました。

――中には、あえて言語を統一せず、そのときどきの現場判断で10以上の言語を使っている開発組織もあると聞きます。石川さんは、エンジニアリング組織はどうやって言語選択していくべきだと思いますか?

大切なのはその言語を使う目的と「どんな問題を解決したいのか」を考えることだと思います。10言語を扱っている組織にしても、きっと事業フェーズやプロダクトの状態など、その時抱える課題や目的に応じて最適な方法を選んでそうなっていると思うんですよね。

うちの会社もプロダクトが増えたりして、Dartだけでは困るような状況になれば、また言語を増やすことだってあるかもしれません。流行っているからといって組織やプロダクトが抱えている問題を解決できないなら、その言語を採用すべきではないですし、新しい言語で自分たちが解決したい問題を解決できるなら、そのためのハードルはフットワーク軽く乗り換えるべき。

組織のフェーズや目的によって正解が違うので、ただ「新しいから」「人気だから」ではなく、その時に抱えている課題を解決するために、言語を選び取っていくスタンスが大切なのだと思います。

上記で触れている"その時に抱えている課題を解決するため"に大きな変化を起こす時が来た、というのが今なのでしょう。

サービスは5年続けることができましたし、今の調子を考えると10年以上は続きそうです。プロダクトの中には、このままで10年目を迎えられる箇所もあれば、このままでは事業成長のボトルネックとなる箇所もあります。何をどう変えるかによって10年目の状態が変わるので、技術的な変化の価値が高いフェーズでもあると思います。

難しいですが、上手くやって良い業績を出せるソフトウェア開発に挑戦していきます。

仲間募集

10Xはそんな感じでエンジニアリングしていきたい仲間を募集しています。ピンと来た方は是非1度話しましょう。

10x.co.jp

10X の CX (Cool Experience) チームで働きませんか

10X の CX (Cool Experience) チームの @metalunk です!

この度、CX チームのバックエンドエンジニアの求人をはじめました。

バックエンドエンジニア(検索、推薦) / 株式会社10X

このブログは当ポジションの魅力を紹介するための文章です。

その中で、CX チームがこれまで上げてきた成果、それらの成果を上げられた理由、いま抱えている問題、それを解決した先に目指していること、をお伝えできたらと思います。

続きを読む

イベントを活用したアプリケーション実装 | お届けチーム取組紹介

これまでシリーズの記事で書いたように、お届けチームの扱っているシステムはイベントを扱って非同期処理をしています。

product.10x.co.jp

非同期処理でイベントを扱うということは、イベントをモデルとして扱うのとセットです。 イベントは書き込み系で作成しか発生しないモデルかつ、参照系でイベントそのものをクエリしないものとしています。

この記事では書き込み系・参照系でそれぞれイベントに関わる実装がどうなっているのか紹介します。

続きを読む

「デフォルト値」の罠containerdのFD上限変更が教えてくれたk8sの教訓

10X SREの栗原です。

弊社ではGKEを利用しています。
GKEでは1.33からcontainerdが1.xから2.xにバージョンアップします。
Migrate nodes to containerd 2  |  Google Kubernetes Engine (GKE)  |  Google Cloud Documentation
それによる影響の調査をしていたとき面白い発見があったので皆様に共有します。

続きを読む

セキュリティチームの専門性を高める業務委託との協働

みなさんこんにちは、セキュリティチームの@sota1235です。

久々の会社ブログ投稿な気がしますが、今回は今までの記事とテイストを変えてセキュリティチームの成果にフォーカスしたいと思います。

背景から丁寧に書いていこうと思っているので前提パートが長いのですが、タイトルにある業務委託の方とどう協業するかという部分を真に理解してもらう上では大事な前提だと私は思ってるので、できればお付き合いください。

(でも忙しい人は読みたい部分だけ読むでもいいですよ、今ならLLMに要約させてもいいかもしれません)

  • チームの成果とは
    • たとえば
    • 質と早さを上げるには
  • 10Xのセキュリティチームの成果を最大化する
    • チーム体制
    • 早さと質を上げていくための取り組み
      • 採用活動
      • チーム輪読会の実施
      • 他チームとの連携
      • 事業観点での優先順位づけ
    • いろいろな取り組みをしていたが…
  • 業務委託の方との協業
    • セキュリティ業務を外注する難しさ
    • 実現したい成果・解きたいIssueを丁寧に定義する
    • 業務委託と協業する具体的な方法
      • 業務委託契約の内容
      • 背景・文脈のインプット
      • 相談トピックのフォーマット
      • 成果はどうだったか
    • なぜうまく協業できたのか
    • 未解決(?)問題 - 業務委託の探し方
  • 最後に
続きを読む

10X SRE 現状報告 2025

お久しぶりです。SRE の @babarot です。2022年4月に書いた 10X に SRE Team ができるまでとこれから 以来、3年ぶり2度目の文章です。10X に SRE チームができてから3年以上が経ち、その間の活動や成果などについて沈黙しまくっていたのですが、振り返ると実に多くのことを達成してきました。最近は会社的にも嬉しいニュースがあり、これから更にやっていくぞ 🔥というフェーズに来ております。この3年間、黙々と頑張りすぎてアウトプットがなかなかできていなかったので、このブログ記事ではこれまでの SRE の取り組みを軽く紹介しつつ、今後はそれぞれのテーマに深ぼったネタを定期的に記事にして投稿してきます!まずは第1弾として2025年時点の 10X SRE の現状報告ブログをどうぞ。

続きを読む

イベント駆動設計を支える非同期処理について | お届けチーム取組紹介

前回記事で書いたように、お届けチームの扱うシステム領域ではさまざまな非同期処理が行われています。

product.10x.co.jp

この記事では

  • 非同期処理の採用するモチベーション
  • 非同期処理の実現方法

を書いています。

非同期処理の採用するモチベーション

主には次の2つのような目的がある箇所で非同期処理を行なっています。

  • 領域間をまたぐため
  • 同期的な処理をミニマルにするため

非同期処理を積極的に採用するにあたり、同期処理とのトレードオフや監視すべきメトリクス等、運用する際のポイントはいくつかありますが、これらは後続の記事で詳しく解説する予定です。

「領域間をまたぐ」

まず前提として「ピックパック」「お客様注文」「配達」のような領域を見出したとします。 その領域を「お客様の注文内容に応じてピックパックが指示される」「ピックパックで商品を品切れにしたら、お客様との取り引き中の商品を品切れにより、お届け数を減らす」というようなケースで「領域間をまたぐ」としています。

領域をまたぐ際にはサービスが目指す方向性と相談し、狙って領域同士を疎結合にするメリットがあれば非同期処理を採用しています。

とくにカスケード障害がさけられるメリットの大きい箇所は進んで非同期処理にしています。

領域間を非同期処理で繋ぐ

「同期処理をミニマルにしたい」

「同期処理をミニマルにしたい」ケースだと、

  • ピックした商品をパック予定のものへ追加する
  • 特定時間(便)におけるピッキングの進捗をサマリする

ようなケースがあります。

予定に追加では同期的にしても意味的には問題ないですが、ピックのAPIはとにかく素早くレスポンスを返したいので、クライアントアプリから呼び出されるRPCではピックのみ行い、パック予定への追加は非同期にしています。

RPCではパッキング用のデータは不要で、ピッキング用のデータは読み込み/書き込みで済みます。

  • 「ピックができたら必ずパック予定の追加できる」ような前提条件を意図的に作る。
  • 予定の追加が結果整合で問題ないかをUIや業務手順上確認する。

など、下準備があって非同期処理を実現できます。  おかげでピックのRPCは低いレイテンシを保てています。

左: 同期処理にすべて収める | 右: 非同期処理

95pertileのグラフで業務がある9時から16時に250ms程度で収まっているので優秀な方です

サマリというのは主にFirestoreでデータ集計をするには都度クエリを実行では実現が難しく、事前に集計しておきたいケースです。 イベント駆動による非同期処理導入前ではRPCをハンドリングしたプロセス上でバックグラウンド処理を実行していました。 サマリというだけあり、データ操作の競合も多いのとサーバが停止するとサマリされないケースもありました。 単一プロセス内での非同期処理からシステム単位での非同期処理になったことで確実に処理することを可能にしました。

ピックや品切れのイベントは三系統の集計の元になる

こういったイベント駆動な非同期処理はFirestoreとEventarc、Cloud Runの組み合わせで実現されています。

https://cloud.google.com/eventarc/docs/run/route-trigger-cloud-firestore?

実現するためのoverview

EventarcによってFirestoreの書き込みをトリガーをにして、ドキュメントを非同期メッセージとしてCloud Runに送りつけていますが、EventarcはCloud Pub/Subで成り立っています。 なので、監視やリトライ設定はCloud Pub/Subへの設定で行なっています。

またCloud Pub/SubのSubscriptionはPush Subscriptionで作られるので、Cloud RunへはプレーンなhttpリクエストとしてCloudEventsの形式にそって送られます。 簡単なメトリクス確認はCloud Run用のダッシュボードで賄えます。

簡単な図

一文で流れを書いてみると、「Firestoreに書き込んだドキュメントが、非同期メッセージとしてCloud Pub/SubにPushされ、CloudEventsの形式でCloud Runへhttpリクエストで送信されます」のようになります。

publish side

シンプルにFirestoreへ書き込みます。 非同期メッセージとして扱われるので基本的にCreateのみ起きるようにしています。 場合によってはDeleteをトリガーにしますが、非同期メッセージをイベントとして扱う際はCreateです。 また、非同期メッセージとして扱うドキュメントはイミュータブルなモノとして扱いためUpdateをしないようにしています。 イベントして扱う場合には発行時間を必ずペイロードに含める、イベントが起きた操作を追跡可能にする識別子を含めるなど一定型にはめて、イベントの発生を非同期メッセージで表せるようにしています。

subscriber side

http requestのbodyがCloudEvents の形式にそっていることを前提にしています。 subscriberにFirestoreのドキュメントを受けとることを前提にサーバを実装できる小さなライブラリを用意してます。 そのライブラリではpublishした際に付与する一意性のある識別子を利用してメッセージを重複して処理しないように制御したり、メッセージを順序正しく処理できるようなユーティリティも実装しています。 なので実装側はシンプルに処理したいイベントと処理そのものをペアでコードが書けるようになっています。

例外系など省略しているが、「何が起きたら」「何する」をイベントハンドラには書いている

メッセージによる非同期処理を本番導入するまでに

今回説明した非同期処理は機能実装の最初のデプロイ時から利用していた訳ではありません。 大雑把に以下3ステップで本番環境での実現しました。

  1. gRPCのリクエストハンドラ内で同期的にイベントハンドラを実行する
  2. gRPCのリクエストハンドラ内で非同期的にイベントハンドラを実行する
  3. gRPCのリクエストハンドラ内ではイベントの永続化だけし、別プロセスでイベントハンドラを実行する ← 今回紹介した仕組み

1. gRPCのリクエストハンドラ内でプログラム上、同期的にイベントハンドラを実行する

この段階ではこの記事で説明している「非同期を導入するメリット」を受けられないわけです。 とはいえリクエスト数が多くなかったり、リクエストのパターンによって問題なく動作します。 ここでのミソは将来非同期処理を導入していこうと画策することでイベントを処理するイベントハンドラで実装することです。

イベントハンドラと呼び出し側でイベント以外を共有しないことで、イベントハンドラの実行場所が変わっても同じロジックがそのまま動きます。

実際のコードを説明ように簡単にかつ色々カットしています

2. gRPCのリクエストハンドラ内でプログラム上、非同期でイベントハンドラを実行する

この段階は3. への準備段階です。 事前にイベント(メッセージ)の流量が本番同等で実行し続けられるか検証はしていましたが、3. の導入はどんなリスクがあるのか未知数でした。

そこでfeature flagを利用して2. と3. の切り替え(切り戻し)は即時でできるようにしました。 findy-tools.io

2, 3の並走している場合、二重処理が問題になるならイベントハンドラ側の仕組みで二重処理は抑制されます。

結果的に3の仕組みに問題がなかったので切り戻しをすることはありませんでした。

UseCaseから特定のイベントハンドラに直接依存しない実現方法を実際にはとっていますが記事用にシンプルにしています

3. gRPCのリクエストハンドラ内ではイベントの永続化だけし、別プロセスでイベントハンドラを実行する

この段階に来ると記事内で紹介しているようにFirestoreへの永続化をトリガーにEventarc経由でCloud Runによりイベントハンドラが実行されます。 2ではプログラム上非同期であるものの、システム的には同期的にイベントハンドラが実行されていました。 それが3ではシステム的にも非同期になります。 なのでイベントを発生させるロジックからは完全に後続の処理を気にすることはなくなります。

サンキューEventarc

メッセージによる非同期処理を実現するのために参考としているのはアウトボックスパターンです。

https://microservices.io/patterns/data/transactional-outbox.html

FirestoreがEventarcを利用することでドキュメントの書き込みをトリガーをするのが容易で非常に助かったところでした。 MessageRelay実現のためにクラウド側の仕組みのトリガーによってやり取りできるのはデータベースをポーリングするより運用しているうえで手間や気にすることが少なくありがたいです。 サンキューEventarc!

次回に続く

次回も引き続きお届けチームによるイベント駆動設計への取り組みを紹介していきます。

お届けチームでは絶賛エンジニアを募集中です。カジュアル面談もwelcomeです。 ご応募お待ちしております。

open.talentio.com