TECH PLAY

株式会社メルカリ

株式会社メルカリ の技術ブログ

270

はじめに こんにちは、mercari.go スタッフの kobaryo と earlgray です。 9月19日にメルカリ主催の Go 勉強会 mercari.go #27 を YouTube のオンライン配信にて開催しました。この記事では、当日の各発表を簡単に紹介します。動画もアップロードされていますので、こちらもぜひご覧ください。 Writing profitable tests in Go 1つ目のセッションは @kinbiko さんによる「Writing profitable tests in Go」です。 発表資料: Writing profitable tests in Go 利益の観点での Go によるテストの考え方というテーマで、テストを書くかどうかを決めるルールや Go でのテスト記述のテクニックについて紹介しました。テストはコードの動作を確認する他、将来での変更で問題がないことを保証する点で役に立ちます。しかし、テストには記述の時間や実行のコストが発生するため、組織の過去のインシデントの影響や給与などを基にして、インシデント対応やデバッグに費やす時間やお金を計算してコストが見合っているか考えることが重要とのことでした。また、Go でのテストにおいては、可読性やコードの品質を高めることによる利点、サブテストの可読性のためにテーブル駆動テストを強要するデメリットなどを Tips として紹介して頂きました。この他にも様々な Tips を紹介して頂いたので興味がある方はぜひご覧になってみてください。 テーブル駆動テストは Go では見かけることが多いですし、サブテストの可読性が高いという観点でついついテーブル駆動で記述してしまう方も多いと思います。私自身もそうでしたが、今回利点と欠点を理解することができたため、今後は適切なユースケースで利用したいと思いました。(earlgray) GC24 Recap: Interface Internals 2つ目のセッションは @task4233 さんによる「GC24 Recap: Interface Internals」でした。 発表資料: GC24 Recap: Interface Internals このセッションでは GopherCon 2024 で発表された Interface Internals の振り返りとして、インターフェースを介した関数呼び出しがどのように実行されているかを、デバッガを用いて実際にメモリ中の値を見ながら説明しました。 Go プログラムをアセンブリにすると、関数の処理が書かれてあるメモリアドレスを call 命令で指定することで関数呼び出しを行っていることが分かります。しかしながら、インターフェースを介するメソッド呼び出しだと動的に呼び出す関数が選ばれるため、この仕組みをそのまま用いることができません。このセッションでは、インターフェースがどのようなデータ構造により実装されているかから始め、呼び出されるメソッドのアドレスを決定する方法、またその高速化手法について説明しました。 密度の高いかつ Go 言語のコアな部分を扱った発表だったため、リファレンスを読みつつ何度も見てきちんと理解したいと個人的に感じました。(kobaryo) GC24 Recap: Who Tests the Tests? 3つ目のセッションは @Ruslan さんによる「GC24 Recap: Who Tests the Tests?」でした。 発表資料: GC24 Recap: Who Tests the Tests? このセッションも2つ目の GC24 Recap: Interface Internals と同様 GopherCon 2024 の振り返りで、 Who Tests the Tests? の内容を扱いました。 我々はソフトウェアの品質が保たれているかの指標としてテストのカバレッジを用いますが、それではテストそのものの品質を担保することはできません。このセッションでは、テストの品質を担保する Mutation Test を紹介しました。この手法を用いることで、プログラム中の演算子や bool 値を変更した場合にテストが失敗するかをチェックし、テストが正しいプログラムのみを通すことを保証することができます。また、そのようなプログラムを AST パッケージを利用して自動で生成する方法についても説明しました。 内容がテストそのものの品質を担保するという興味深いものであった上、実践に移しやすい内容となっており、とても有益なセッションでした。このブログを読んでいる皆様もぜひ導入を検討してはいかがでしょうか。(kobaryo) Cloud Pub/Sub – High Speed In-App Notification Delivery 4つ目のセッションは @akram さんによる「Cloud Pub/Sub – High Speed In-App Notification Delivery」です。 発表資料: Cloud Pub/Sub – High Speed In-App Notification Delivery メルカリでの通知の管理を行う Notification platform における Cloud Pub/Sub の活用事例について紹介しました。メルカリではアプリ内通知や To-Do リストの他、メールや Push 通知などをお客様へ送信しています。2,000万人以上のお客様にリアルタイムかつ非同期的な通知を行えるようなパフォーマンスを実現するため、notification platform では Cloud Pub/Sub を使用しています。具体的には、notification platform 内で Push 通知のリクエストを受けて Pub/Sub に publish するサーバと、Pub/Sub を subscribe して実際に通知を行うサーバの2台の構成で通知処理を行っています。この結果、現在メルカリでは1日あたり1,600万以上(ピーク時 400rps) の Push 通知を実現しているとのことでした。 メルカリという大規模プラットフォームにおける Pub/Sub の活用事例としてとても興味深い内容でした。非同期的なタスクの処理にパフォーマンスの課題を感じている方は Pub/Sub の導入を検討してみてはいかがでしょうか。 (earlgray) おわりに 今回はGo言語のコアな部分から実用的なものまで、幅広い4つの発表をお送りしました。GopherCon 2024 に関する発表もあり、運営メンバーとしてもGoの最先端の内容を知ることができ大変勉強になりました。 ライブで視聴いただいた方も録画を観ていただいた方も本当にありがとうございました! 次回の開催もお楽しみに! イベント開催案内を受け取りたい方は、connpass グループのメンバーになってくださいね! メルカリconnpassグループページ
こんにちは、メルカリのAI/LLMチームで機械学習エンジニアをしている arr0w と sho です! 本テックブログでは、Vision-Language Modelの一つである SigLIP [1]を、メルカリの商品データ(Image-Text Pairs)でファインチューニングし、メルカリの商品画像Embeddingの性能を大幅に改善したプロジェクトについて紹介します。 今回作成したSigLIPの性能を評価するために、 商品詳細ページの「見た目が近い商品」のレコメンド機能でA/Bテストを実施しました。 この「見た目が近い商品」のレコメンド機能は、社内では Similar Looks と呼ばれています。作成したモデルをSimilar Looksの類似画像検索に適用し、既存モデルとの比較のためのA/Bテストを行いました。 そして、その結果として、主要なKPIにおいて以下のような顕著な改善が確認できました。 「見た目が近い商品」のタップ率が 1.5倍に増加 商品詳細ページ経由の購入数が +14%増加 A/Bテストを経て、モデルの有効性が確認できたため、今回作成したモデルによるレコメンドは無事採用され、100%リリースに至りました。以降の章では、商品データを用いたSigLIPのファインチューニングやそのオフライン評価、本番デプロイのためのシステム設計など、本プロジェクトの技術的詳細について説明していきます。 商品データを用いたSigLIPのファインチューニング 画像Embedding 画像Embeddingは、画像内の物体やその色、種類といった特徴を数値ベクトルとして表現する技術の総称です。近年、推薦や検索など、さまざまなアプリケーションで使用されています。 メルカリ内でもその重要性は日々増しており、類似商品レコメンド、商品検索、不正出品の検出といった多様な文脈で画像Embeddingが使用されています。 本プロジェクトにてメルカリのAI/LLMチームでは、 Vision-Language ModelであるSigLIP を用いて、商品画像のEmbeddingを改善する取り組みを行いました。 SigLIP 近年、 CLIP [3] や ALIGN [4] などのように、大規模かつノイズの多い画像およびテキストがペアになったデータセット(e.g. WebLI [5])を用いたContrasive Learningで事前学習されたモデルは、ゼロショット分類や検索といった様々なタスクにおいて高い性能を発揮していることが知られています。 SigLIP は、ICCV 2023で発表された論文で紹介されたVision-Language Modelです。SigLIPは、CLIPで使用されている従来のSoftmax Lossを、 Sigmoid Loss に置き換えて事前学習を実施しています。この変更はLossの計算方法を変えるだけのシンプルなものですが、著者たちは、ImageNet [6]を使用した画像分類タスクを含む複数のベンチマークで、 SigLIPは既存手法と比べてパフォーマンスが大幅に向上した と報告しています。 それでは、ここで、後述するメルカリの商品データを用いたSigLIPのファインチューニングのために実装したLoss関数の実装を見てみましょう。 def sigmoid_loss( image_embeds: torch.Tensor, text_embeds: torch.Tensor, temperature: torch.Tensor, bias: torch.Tensor, device: torch.device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu") ): logits = image_embeds @ text_embeds.T * temperature + bias num_logits = logits.shape[1] batch_size = image_embeds.shape[0] labels = -torch.ones( (num_logits, num_logits), device=device, dtype=image_embeds.dtype ) labels = 2 * torch.eye(num_logits, device=device, dtype=image_embeds.dtype) + labels loss = -F.logsigmoid(labels * logits).sum() / batch_size return loss なお、今回のプロジェクトでは、SigLIPのベースモデルとして google/siglip-base-patch16-256-multilingual を使用しました。このモデルは多言語のWebLIデータセットで訓練されており、対応言語にメルカリのサービスで主に使用されている日本語も含まれているためです。 商品データを用いたファインチューニング この章から、メルカリの実データを用いたSigLIPのファインチューニングの実験環境および設定について紹介します。 今回の実験では、過去に出品された商品から、ランダムに抽出した約100万件のメルカリの商品データを使用して、SigLIPのファインチューニングを実施しました。SigLIPへの入力データは、商品タイトル(Text)と商品画像(Image)を用いました。これらはいずれもメルカリ上のお客さまが出品時に作成したものです。 訓練用のコードは PyTorch と Transformers ライブラリを使用して実装しました。さらに、今回は使用したデータセットは、Google Cloud Storage上で管理しており、その規模も大きかったため、データ読み込みプロセスを最適化するために WebDataset を活用し、大量の学習データを効率的に扱えるようにしました。なお、WebDatasetの理解には 公式ドキュメント に加え、 こちらの記事 が大変参考になりました。 モデルの訓練は 1台のL4 GPU を使用して実施しました。訓練パイプラインの構築には、 Vertex AI Custom Training を活用しています。さらに、メルカリは、 Weights & Biases(wandb) のエンタープライズ版を契約しているため、実験のモニタリングには、そちらを活用しました。プロジェクトのタイムライン上、ML実験に割くことができる期間には制約がありましたが、初期にこれらの訓練パイプラインに投資をしたことで、 結果として試行錯誤の回数を増やすことができた ように感じています。 オフライン評価 A/Bテストを実施する前に、既存の「見た目が近い商品」レコメンドのユーザの行動ログを使用してオフライン評価を行いました。Similar Looksは、元々、学習済みのMobileNet [2]( google/mobilenet_v2_1.4_224 )から得られるImage EmbeddingをPCAで128次元に圧縮したEmbeddingを用いていました。オフライン評価では、10000件の行動ログ(セッション)を利用しました。 行動ログの具体例を以下に示します。query_item_idに商品詳細ページに表示されているクエリ画像となる商品のID、similar_item_idに「見た目が近い商品」セクションに表示された商品のID、clickedにその商品を見たかどうかのフラグが格納されています。 session_id | query_item_id | similar_item_id | clicked | ----------------|----------------|-----------------|---------| 0003e191… | m826773… | m634631… | 0 | 0003e191… | m826773… | m659824… | 1 | 0003e191… | m826773… | m742172… | 1 | 0003e191… | m826773… | m839148… | 0 | 0003e191… | m826773… | m758586… | 0 | 0003e191… | m826773… | m808515… | 1 | ... 評価は画像検索タスクとして定式化し、ユーザーのクリックを正例(label:1)として扱いました。パフォーマンスの評価には、nDCG@k(k=5)とprecision@k(k=1,3)を評価指標として使用しました。 これにより、ユーザーの嗜好に合致した形で類似した画像をランク付けするモデルの能力を定量的に評価することができました。 評価にあたっては、比較のために「ランダム推薦」と「現在使われているMobileNetベースの画像検索」2つのベースラインとしました。 以下がオフライン評価の結果になります。 手法 nDCG@5 Precision@1 Precision@3 Random 0.525 0.256 0.501 MobileNet 0.607 0.356 0.601 SigLIP + PCA 0.647 0.406 0.658 SigLIP 0.662 0.412 0.660 評価結果から、 メルカリの商品データでファインチューニングしたSigLIPのImage Encoderから得られた画像Embeddingによる画像検索は、PCAによって768次元から128次元に圧縮された場合でも、MobileNetベースの画像検索を一貫して上回る ということがわかりました。これは、あくまでオフライン評価上は「見た目が近い商品」のレコメンドにおいて、我々が構築したSigLIPモデルが優れたパフォーマンスを示したと言えます。 定量評価だけでなく、目視による定性評価も実施しました。約10万件の商品画像のEmbeddingが格納されたベクターストアを FAISS を用いて作成し、複数の商品で画像検索を行った結果を、以下のようにスプレッドシートにまとめ、目視でチェックしました。 以上のオフライン評価結果から、メルカリの商品データでファインチューニングしたSigLIPのImage Encoderを用いた「見た目が近い商品」レコメンドは、定量的・定性的どちらにも既存のモデルを上回ることが明確に示されました。そのため、作成したモデルを使用してA/Bテストを実施することを決定しました。 次の章では、このモデルを本番環境にデプロイするためのシステム設計について説明します。 システム構成 End-to-End Architecture 個々のコンポーネントの詳細に入る前に、アーキテクチャの全体像を以下に示します: 上図では、メルカリ本体のプラットフォームからSigLIPモデルがホスティングされたマイクロサービスへのデータの流れと、Embeddingがどのように効率的に保存され、取得されるかを示しています。これは初期バージョンですが、このモジュール化された設計により、スケーラビリティと柔軟性を確保しています。 Google Container Registry モデルのデプロイは Google Container Registry(GCR) を通じて管理され、マイクロサービスのDockerイメージがここに保存されています。Dockerイメージは、GitHubリポジトリからGoogle Cloud Buildを使用したCI/CDパイプラインを介して、継続的にビルドされGCRにプッシュされます。 GCRを活用することで、Google Kubernetes Engine(GKE)上のデプロイメントが常に最新のコードバージョンに基づいて行われ、本番環境で稼働しているサービスへのシームレスなアップデートを実現しています。 Google Pub/Sub リアルタイムのデータストリームを処理するために、 Google Pub/Sub を利用しています。メルカリでは、お客さまによって新しく出品が作成されると、新規出品用ののPub/Subのtopicに、Pub/Sub メッセージがpublishされます。推薦や検索をはじめとする関連マイクロサービスがこのtopicをsubscribeすることで、新しい出品に対して、動的に対応できるシステムを実現しています。 今回も同様に、新規出品により発火するEventをSubscribeするWorkerを定義しました( Embedding Worker )。これにより、新しい出品が発生したら、Embeddings Workerが起動します。そこで、新規商品の画像Embeddingを算出し、ベクターデータベースに追加します。この非同期なシステムにより、メルカリの出品量に応じて効果的にスケールすることが可能になっています。 Google Kubernetes Engine システムを構成する主要なマイクロサービスはGoogle Kubernetes Engine(GKE)でデプロイされています。GKEは、本取り組みのアーキテクチャにおける以下の主要サービスをホストしています: Embeddings Worker Embeddings Workerは、Pub/Subの新規出品トピックをSubscribeする重要なサービスです。新規出品の発生ごとに、以下の処理を行います: 出品された商品に対応する画像をストレージから取得 ファインチューニングしたSigLIPモデルを使用して、画像をEmbeddingに変換 類似度検索のレイテンシ改善とストレージコスト削減のため、PCAにより次元を削減(768 dim → 128 dim) EmbeddingをVertex AI Vector Searchに保存 このプロセスにより、効率的な類似画像検索が可能になります。各Embeddingは画像の視覚的内容を表現しているため、メルカリのプラットフォーム全体で視覚的に類似した出品を容易に比較・検索することができます。 Index Cleanup Cron Job メルカリでは多くのお客さまに利用されており、頻繁に新しい商品が出品され、既存の出品が売れたり、削除されたりします。 そのため、現在出品中の商品のみを表示し「見た目が近い商品」レコメンドの体験を向上するために、 削除あるいは売却済みとなった商品を適宜Vector Storeから削除し、最新の状態に保つ 必要がありました。これを実現するために、 Index Cleanup Cron Job を実装しました。 Cronで定期実行されるこのジョブは、Vertex AI Vector Searchから古くなった出品や売却済みの出品に対応するEmbeddingを削除します。 現在はこのBatchで定期実行する方針で体験を大きく損ねず動作していますが、さらなる効率化を図るため、Embedding管理のリアルタイム更新についても、現在検討しています。 Similar Looks Microservice & Caching Similar Looks Microservice は、画像類似性機能の中核となるものです。このサービスは、商品IDを入力として受け取り、対応する画像Embeddingを Vertex AI Vector Search から取得し、最近傍探索を実行してマーケットプレイス内の類似商品を見つけます。 レイテンシを削減するために、このマイクロサービスにも キャッシュ機構 を実装しています。これにより、お客さまが類似商品を閲覧する際に素早いレスポンスを提供し、スムーズなユーザー体験を確保しています。 Vertex AI Vector Search Embeddingの保存と検索には、 Vertex AI Vector Search を使用しています。これはスケーラブルなVector Databaseで、類似したEmbeddingを効率的に検索することができます。メルカリ内の各商品画像は、SigLIPにより、ベクトルに変換され、そのベクトルはVertex AI内で商品IDごとに索引されます。 Vertex AIの最近傍探索アルゴリズムは非常に高速であり、データベース内に数千万件程度の膨大なEmbeddingがある場合でも、視覚的に類似した商品を高速に検索することが可能です。 TensorRTを用いたモデルの最適化 ファインチューニングしたSigLIPモデルのパフォーマンスを最適化し、毎秒生成される大量の出品を高速に処理するため、モデルをPyTorchからNVIDIAの高性能深層学習推論ライブラリであるTensorRTに変換しました。この変換により、推論時間が 約5倍高速化 されました。 TensorRTについて TensorRTはニューラルネットワーク内の操作を、NVIDIA GPU上で効率的に推論できるように、最適化された行列演算のシーケンスに変換します。具体的には、性能を保った上でのモデルの重みのFP16やINT8といった少ない桁数への変換(Precision Calibration)や深層学習モデルを構成する層の融合(Layer Fusion)などの処理を行います。 SigLIPのような大規模モデルをメルカリのような高RPSなサービスにリリースする上で、この改善は非常に重要でした。毎秒大量の商品が出品される中、推論時間を 数百ミリ秒から数十ミリ秒程度に削減できた ことで、 新規出品のほぼすべての商品画像を瞬時にベクトル化 し、Similar Looks Componentsが使用するVertex AI Vector Searchのインデックスに登録できるようになりました。 Next Steps 現在のアーキテクチャは安定しており、スケーラブルですが、私たちは以下のような改善点があると考えており、日々開発に取り組んでいます。 Vector Storeのリアルタイム更新 既に述べたとおり、現在、Index Cleanup Cron Jobが定期的にVertex AI Vector Searchから古くなったEmbeddingを削除しています。しかし、商品が削除されたり売却されたりした時点で即座にEmbeddingを更新するような、よりリアルタイムな方法への移行を計画しています。これにより、Cron Jobによる削除の必要性がなくなり、インデックスが常に最新の状態に保たれることが保証されます。 Triton Inference Server モデル推論をより効率的に処理するため、 Triton Inference Server の使用も検討しています。Tritonは、異なるフレームワーク(例:TensorRT、PyTorch、TensorFlow)の複数のモデルを単一の環境にデプロイすることができます。推論処理をEmbeddings WorkerからTritonに移行することで、モデルの実行をワーカーのロジックから分離し、推論パフォーマンスのスケーリングと最適化においてより大きな柔軟性を得ることができます。 SigLIPモデルを活用した新機能 最後に、ファインチューニングしたSigLIPモデルを活用した新機能の開発に取り組んでいます。「見た目が近い商品」のレコメンドに限らず、開発したSigLIPモデルはたくさんの可能性を秘めていると我々は考えています。このモデルを活用したユーザー体験を向上させる計画は、他にもあるので、今後のメルカリのアップデートにご期待ください 😉 おわりに 今回、メルカリが自社で保有する商品データを用いて、Vision-Language ModelのSigLIPをファインチューニングし、高性能なImage Embedding Modelを構築し、「見た目が近い商品」機能の改善を行いました。 オフライン評価において、ファインチューニングしたSigLIPによる「見た目が近い商品」レコメンドは既存のモデルよりも高い性能を示しました。そのため、 A/Bテストを実施したところ、ビジネスKPIにおいても大幅な改善が確認されました。 本ブログの内容がVision-Language Modelのファインチューニングや評価、深層学習モデルの実サービスへのデプロイ等に興味がある皆さまのお役に立てば幸いです。 メルカリでは、 プロダクト改善を通して大きなインパクトを生み出すことに意欲的な Software Engineer を募集 しています。興味ある方は是非ご応募ください。 参考文献 [1] Sigmoid Loss for Language Image Pre-Training , 2023 [2] MobileNets: Efficient Convolutional Neural Networks for Mobile Vision Applications , 2017 [3] Learning Transferable Visual Models From Natural Language Supervision , 2021 [4] Scaling Up Visual and Vision-Language Representation Learning With Noisy Text Supervision , 2021 [5] PaLI: A Jointly-Scaled Multilingual Language-Image Model , 2022 [6] ImageNet: A Large-Scale Hierarchical Image Database , 2009
Mercari Search Infra Teamのmrkm4ntrです。 Elasticsearchは1ノードに載り切らない量のデータも複数のshardに分割し、複数のノードに載せることで検索が可能になります。shard数を増やすことで並列にスキャンするドキュメントの数が増えるためlatencyが改善します。ではshard数はいくらでも増やせるのでしょうか?もちろんそのようなことはなく、Elastic社の公式ブログ( https://www.elastic.co/jp/blog/how-many-shards-should-i-have-in-my-elasticsearch-cluster )にもあるようにshard数を増やすことによるオーバーヘッドが存在します。ただしそのオーバーヘッドが具体的に何を指すのかは、先ほどの記事では明らかにされていません。本記事ではそのオーバーヘッドの正体を明らかにするとともに、実際にコストの削減を達成したことについて説明します。 背景 我々の運用するindexはmulti-tierと呼称する構成をとっています。multi-tier構成で直近x日分の商品が入っているindexを1st index、全ての商品が入っているindexを2nd indexと呼びます。新しいもの順で商品を取得する場合はまず1st indexにリクエストし、検索結果が不足していた場合は2nd indexにリクエストします。このような構成により2nd indexに来るリクエスト量を減らすことができます。同時に、2nd indexに来るクエリは1st indexでは十分な数の結果を得ることのできなかった珍しいtermを含むことが多いため、スキャンするposting list(termを含むドキュメントのidのリスト)は短くなることが期待できます。1st indexに比べて2nd indexの1クエリ当たりのコストは10倍程度かかるため、これによりコストの削減を実現しています。 2nd indexは24個のshardに分割されています。元々ファイルシステムキャッシュに載るようにshardサイズの2倍(最悪のmergeの場合を考えたらしい)が空きメモリ量よりも小さくなるようにshard数を決めたらしいですが、実際にmemoryのworking setなどを調査し、1ノードに2つのshard載せることができることが発覚しました。その結果全体24 shard、1ノードあたり2つのshardを載せる構成に変更しました。 2nd indexはmulti-tier構成でかなりコストを削減しているものも、今なお最大のリソースが必要なindexでした。そこでこのコストをさらに削減するために調査を行うことにしました。 CPU proflierでflame graphを取得し眺めていたところ、posting listをスキャンする処理(下図の赤枠)には検索処理において半分以下のCPUしか使われていないことがわかりました。 確かにスキャンすべきposting listが短くなるのでこのことはある程度予期していましたが、検索エンジンの処理のメインはposting listのスキャンのはずです。このスキャンを効率化するために様々な工夫がされています。例えばこの記事( https://engineering.mercari.com/blog/entry/20240424-6f298aa43b/ )もスキャン対象を効率的にスキップする話です。では他は何にCPUを使っているのでしょうか。残りの部分について調べると、ほとんどがスキャン対象のposting listをterm dictionaryから取得する処理(以下term lookupと呼ぶ)であることがわかりました。 term lookup Luceneではposting listはfieldとtermの組み合わせごとに存在します。簡単のため今は一つのfieldについて考えます。termをキーとし、posting listの場所をvalueとするhashmapを用意すればposting listの位置の取得は一瞬で終わるのですが、termの数は非常に大きいためJVMのheapに収めるのは現実的ではありません。 そのためLuceneはFSTというデータ構造を使い省メモリ化を実現しています。FSTとはFinite State Transducerの略称で、有限オートマトンの受理状態に値がつくことで入力をその値に変換することができるというものです。FSTはfield毎に存在し、termを入力、postling listの場所を出力(実際はtermとposting listの対応ではなく、termのprefixとprefixを共有するtermのposting listのアドレスをまとめたブロックのアドレスらしい https://github.com/apache/lucene/issues/12513 )とすることでpostling listの取得を実現しています。 この計算量は入力文字列の長さのオーダーですが、データ自体はファイルに保存する形式でmmapされているため、毎回そこから計算するのはそれなりにCPUを使うようです。 term lookup回数の削減 term lookupにCPUを使っているのなら、term lookupの回数を減らせばコスト削減ができるはずです。今2nd indexは24 shardあるので一つのクエリのterm lookup回数は1 termあたり24回必要になります(実際は違うのですが詳細は後ほど)。2nd indexを12 shardに変更すればterm lookup回数は1クエリあたり12回となるため、その分CPU消費が少なくなるはずです。もちろんshardあたりのスキャンするposting listは長くなるためlatencyが悪化する可能性がありますが、先述のとおり2nd indexにはposting listのスキャンが長いクエリがくる可能性は低いため、latencyの悪化の可能性も低いと考えました。また、term数が増加しますがFSTによるlookupの計算量はterm数には比例しないので問題ないはずです。 パフォーマンスを検証するために、まずは開発環境で本番のデータを使って検証しました。Elasticsearchのshrink APIを使って24 shardのindexから12 shardのindexを作成することにします。shrink APIは書き込み禁止にしたindexのshardをコピーしてまとめることで、shard数が元のindexのshard数の約数となるindexを作成する仕組みです。 リアルタイムのデータ更新がなくても先ほどの想定からパフォーマンスは向上すると見込んでいましたが、実際には全く変化がありませんでした。実はそれは当然で、FSTはshard毎に存在するのではなくsegment毎に存在するため、term lookup回数はshard数に直接比例するのではなくsegment数に比例するからでした。shardは内部的には複数のLuceneのsegmentからなるのですが、shrink API実行直後はあくまでもsegmentのグルーピング単位を変更しただけです。そのため全体のsegment数は変わらないためパフォーマンスに変化が見られませんでした。 そこでリアルタイムの更新を一定期間実施したところ、新しいsegmentの追加、mergeが繰り返され1 shardあたりのsegment数は元よりやや大きいくらいの値で収束しました。segment数はLuceneのTiered Merge Policyやドキュメントのサイズ、追加速度などによって決まり、shardあたりのドキュメント数が倍になってもsegment数は単純に倍にはなりません。再度パフォーマンスを測ったところ無事にパフォーマンスの改善が確認できました。 以下が本番環境に適用した結果です。薄い線が一週間前のもので濃い線が適用後のノード数の遷移を表します。 我々のクラスタはこの記事( https://engineering.mercari.com/blog/entry/20230620-f0782fd75f/ )にあるようにCPU使用量でオートスケールするようになっていますが、明らかに必要なノード数が減少したことが見てとれます。latencyに悪影響も見られませんでした。 こちらが適用後のflame graphです。別要因でクエリのパターンが適用前後で変化したため単純比較はできませんが、term lookupの占める割合が小さくなることでposting listのスキャンが占める割合が相対的に大きくなっています。 さらにsegement数を減らせばよりコストが削減できるはずです。segment数はLuceneのTiered Merge Policyのパラメータを変更することにより調整できます。よりsegment数を減らすために index.merge.policy.segments_per_tier を減らしましたが、segment数は思ったほど減少しなかったと同時に、mergeのためのCPU使用量が上がったのでこちらは期待ほど有効ではありませんでした。 まとめ この記事では、shard数の増加によるオーバーヘッドの一つはsegment数が増えることによるterm lookupの回数が増加であることを示しました。同じノードに複数のshardをおいている場合はshardをまとめた方がCPU負荷が低くなります(もちろんlatencyが上がる場合がありますが)。我々のindexは基本的にリアルタイムのデータ更新がありますが、データ更新がないような静的なindexにおいては、force mergeでsegement数を1にしておくとterm lookup回数が最小となりパフォーマンスが改善することが期待できるでしょう。
こんにちは。メルペイ Engineering Engagement チームの mikichin です。 10月5日に開催された「 YAPC::Hakodate 2024 」にメルカリはGold Sponsorをしておりました。今回は参加レポートをお届けします! YAPC::Hakodate 2024 について YAPCはYet Another Perl Conferenceの略で、Perlを軸としたITに関わる全ての人のためのカンファレンスです。 Perlだけにとどまらない技術者たちが、好きな技術の話をし交流するカンファレンスで、技術者であれば誰でも楽しめるお祭りです! 開催概要 開催日時 前夜祭:2024年10月4日(金) 本編:2024年10月5日(土) 場所 公立はこだて未来大学 ブラインドに「YAPC」とあり、いよいよはじまるんだなーとワクワク。素敵な演出をありがとうございます! メルカリメンバーの登壇 今回、前夜祭・本編でメルカリメンバーの登壇がありました! デジタルIDウォレットが切り開くHigh Assurance Identity Proofingの未来 / kokukuma 今回のYAPC::Hakodate 2024本編では、非常に多くのプロポーザルの応募があったということで、前夜祭にてrejectconが開催されkokukumaが登壇しました! デジタルIDウォレットを利用できる環境が整備されつつある現状を振り返りつつ、開発者として今後この状況を楽しみ尽くすための知識として、ユーザー体験の概略や、mDL/mdoc(ISO/IEC 18013-5)について解説し、デジタルIDウォレットによる身元確認が、なぜ身元確認として成立するのかを解説しました。また、これから出てきてほしい5大ニュースを6つ紹介しました。 https://speakerdeck.com/kokukuma/dezitaruiduoretutogaqie-rikai-kuhigh-assurance-identity-proofingnowei-lai フロントエンドの現在地とこれから / koba04 本編14:50〜 からkoba04が登壇しました。 「フロントエンドの現在とこれから」というタイトルで Server Components などの技術がなぜ登場しどういった問題を解決しようとしているのかについて取り上げ、フロントエンドの今とこれからについてを「点ではなく線」で捉えられるようまとめ、発表しました。 フロントエンドエンジニアで普段触れている技術を俯瞰的に捉えたい方や、フロントエンドエンジニアではないけどフロントエンドの最近の技術や変化を把握したい方は、ぜひ資料および後日公開される動画をご確認ください。 https://speakerdeck.com/koba04/hurontoentonoxian-zai-di-tokorekara 参加メンバーの感想 今回、多くのメルカリメンバーが参加していました。参加した感想をご紹介します。 YAPC函館市電LTの様子 普段はAndroidアプリの開発を主にしていますが、そんな自分でも受け入れてくれるワイワイなコミュニティ・イベントで楽しめました! 「CloudNative Meets WebAssembly: Wasm’s Potential to Replace Containers」のセッションは知らない単語が色々出てきて特にまなびになりました〜✨ イベント翌日に「 YAPC函館市電LT 」が開催されていたので、路面電車についてLTもしました🙌 (Kuu) さまざまな分野の話を1日で聞けて、満足度が高かったです。特に「クレジットカードを製造する技術」が個人的には面白いと感じました。クレジットカードという特に秘匿性の高さが求められるものの製造工程は普段あまり聞けない話だったので興味深かったです!(momom) 初めて技術のカンファレンスに参加しましたが、興味深いセッションをたくさん聞けたり、いろいろな人とお話しできて良い機会になりました!最後のmoznionさんのkeynoteの「量が質に転化する」という言葉に感銘を受け、帰ったらたくさんプログラム書いてこうという気持ちになりました🔥 企業のスポンサーブースもどの企業も工夫されてて全部回ってしまいました!コーヒースポンサーをされていたカケハシ社のコーヒーが美味しかったです☕︎ (torichan) 「今日から始める大規模言語モデルのプロダクト活用」のセッションは、業務デザイン、ナレッジなど自分自身がとても興味のある領域なので、ワクワクしました。また、LT ではクリエイティブコーディングの話が刺さったので、早速やっていくぞ〜という気持ちです。(micchie) まとめ わたしは「YAPC::Okinawa 2018 ONNASON」以来の久しぶりの参加となりました!そのときも沖縄科学技術大学院大学 OISTという大学が会場だったこともあり、個人的にすごくなつかしさを感じ、YAPCに戻ってきたなーという思いでいっぱいでした。 前夜祭で行われたアンカンファレンスだけではなく、どのセッションでも登壇者と参加者がコミュニケーションをとりながら意見交換をしており、全員で楽しみ、盛り上がっているカンファレンスであることを感じました。 最後に、YAPC::Hakodate 2024の企画運営、おつかれさま & ありがとうございました! また、次回を楽しみにしています!
こんにちは。株式会社メルカリ iOSエンジニアの kntk です。 8月22日から8月24日にかけて開催された「 iOSDC Japan 2024 」にメルカリはプラチナスポンサーとして参加しました。 本記事では、その参加レポートをお届けします! 登壇 株式会社メルカリからは私を含め2名のエンジニアが登壇しました。 SwiftのSIMDとその利用方法 (レギュラートーク 20分): kntk 座談会 「Strict ConcurrencyとSwift 6が開く新時代: 私たちはどう生きるか?」 (企画 40分): kntk (他4名) App Intentsの未来について研究しよう! (ポスターセッション): jollyjoester SwiftのSIMDとその利用方法 (レギュラートーク 20分): kntk CPUにはSIMD命令と呼ばれる高速演算機能が存在し、それを扱う為の型 SIMDが標準ライブラリに存在します。この型の全体像や使い方についてのセッションです。 実はこのSIMD型はvisionOS開発で用いるRealityKitで必要になることがあり 、Apple Vision Proが発表され少しずつvisionOS開発が始まっている今年が絶好の発表タイミングだと考えプロポーザルを提出しました。 visionOSで必要になるとはいえ、SIMD命令自体の説明という低レイヤ技術の話が含まれているセッションだったため、「あまり人は来てくれないのではないか」と考えていましたが、 想像していたよりも多くの方々が聞きに来てくださり驚きました。 visionOS開発をする際に、少しでも参考になれば良いなと思います。 座談会 「Strict ConcurrencyとSwift 6が開く新時代: 私たちはどう生きるか?」 (企画 40分): kntk (他4名) Swiftの次のメジャーバージョンであるSwift 6から導入される「Swift ConcurrencyのStrict Concurrencyチェックによる完全なデータ競合の防止」についての座談会企画です。座談会形式で私含め5名の登壇者がコメントし合う形でセッションが進行しました。 自分は個人アプリで経験した「Strict Concurrency対応をしたら、長らく原因が謎だったクラッシュが解消された」というエピソードを話しました。 データ競合の最も厄介なポイントは再現やデバッグ(そもそもデータ競合の存在に気づくこと)が難しい点にある と考えているため、静的にデータ競合を防止するStrict Concurrencyチェックは大きな意味があると思っています。 また、もう一つ 「Swiftチームも全てのデータ保護をActorで実現することを目指していない」 という大事なメッセージを伝えました。 Strict Concurrencyの導入ハードルを高く感じる大きな要因の一つとして「Strict Concurrency以前から存在したLock(やQueue)の仕組みの全てを絶対にactorに置き換えなければいけない」という誤解が存在すると考えています。 基本的にはActorが最適な選択肢ではありますが、Strict Concurrencyを段階的に対応する際やActorではカバーできないユースケースにおいては既存のLockの仕組みを使って良いとSwiftチームも考えており 、このメッセージはStrict Concurrency導入のハードルを下げるのに大きく影響すると思い座談会の場で共有しました。 セッション時間が40分だったため、登壇者の事前打ち合わせで議論した内容の半分も話せていないのですが、その反面40分に最低限必要な内容を詰め込んだ密度の高いセッションになったと思います。僕以外の登壇者が全員著名な方だったため、自分が参加して良いものかと正直不安でしたが、上記のエピソードなどが聴講してくださった方々のお役に立てたなら、登壇して良かったなと思います。 僕自身も事前の打ち合わせで登壇者の方々から多くの事を学ばせていただきました。 App Intentsの未来について研究しよう! (ポスターセッション): jollyjoester AppleプラットフォームにはApp Intentsと呼ばれるアプリの特定の機能をシステム(OS)に伝えることができる重要な機能があります。これにより、アプリ外(Siri・ショートカット・ウィジェット)からアプリの機能を利用することが可能になります。 また、iOS18からApple Intelligence (AI)が導入されることによってApp Intentsの重要性が高まることが予想されています。 App Intentsの概要から最小実装例の解説まで網羅したポスターセッションで、キャッチアップにピッタリな内容でした。 また、「こんな体験ができるアプリが良い!作りたい!」と想像を膨らませてワクワクできるセッションでした。 Swift コードバトル Swiftコードバトルはお題で指示された動作をするSwiftコードをより短く書けた方が勝ち、という競技です。 お題は決して難しいものではなく、少し練習すればSwiftプログラマであればどなたでも参加できる難易度を目指しています。 お題例1: 入力された文字列を逆順にして出力するプログラムを書いてください。 お題例2: 与えられた整数リストの要素の合計を計算するプログラムを書いてください。 Swiftコードバトル予選 の説明文から引用 私kntkも参加し、準優勝しました! iOSDC Japan 2024運営によるSwiftコードバトル解説記事 私は昔からSwiftが好きだったため、Swiftの標準ライブラリや”スマートな書き方”には多少自身がありました。競技プログラミングもSwiftで活動していたため、競プロ的な問題に対する経験値もありました。今回のコードバトルは自分のSwift力を試せる良い機会だったと思います。 コードバトル当日は「観客の前でライトに照らされ、顔と画面を配信されながら時間以内にプログラムを書く」という緊張感のある環境だったと同時に、回答を提出して観客の方々から歓声が上がった時はとても気持ちが良く、まるでスポーツでもやっている気分でした。 決勝では敗退してしまいましたが、対戦相手の方は予選の時からの強豪でしたし、決勝の問題が「競プロ的には簡単かつ文字数の工夫方法が多い」という決勝に相応しい・素晴らしい問題だったため、悔しいですが言い訳の余地のない結果だったかなと思っています。今回の学びを活かして今後も精進していきたいと思います。 また、 このコードバトルの良い点は解答の速さよりコードの短さが評価される点です。実際に、当日の試合データを見ると、全7試合中5試合の勝者は、10分以上経過後に提出した回答で勝利しています。 (Swiftに自信はあるが)速度に自信がない方でも十分に勝算があるということです。是非次回があれば、Swiftに自信がある皆さんに参戦していただきたいなと思います。 とても楽しい企画だったのでぜひ来年も開催してもらいたいです!そして次回はもっと多くの方に参戦してほしいです! メルカリスポンサーブース メルカリスポンサーブースでは「Engineering Office Hour」「チェキ撮影」「メルカリ iOSクイズ」の3つを出展しました。 また、ブースに来てくれた方にはノベルティとして メルカリの公式キャラクター が印刷されたコースターをプレゼントしました。 ( 亀の「ゼニー」猫の「ミケ」 うさぎの「ロップ」が印刷されたコースター) Engineering Office Hour Engineering Office Hourは、タイムテーブル形式でメルカリ社員と話せる企画です。簡単な自己紹介やトークテーマ、ブース滞在時間がまとまったポスターを掲載し、参加者が社員やトークテーマ狙いで遊びにいけるような設計を行いました。 実際に特定の社員に会いに来た方や、ある社員が専門とする技術領域の質問をしに来た方がいらっしゃり、とても良い企画だったなと思いました。 チェキ撮影 ブースでは参加者の方とメルカリ社員のチェキ撮影も行っていました!合計で90枚近くのチェキを撮影し、参加者の方にプレゼントしました。(「エモい」と好評でした!) メルカリ iOSクイズ iOS開発に関するクイズを3日分(合計18問)出題しました。3日合計で212件の挑戦をいただき、大変ありがとうございました!詳しい内容はぜひ iOSDC Japan 2024 メルカリブース iOSクイズ解説 をご覧ください! まとめ 今年のiOSDCはSwiftコードバトルや座談会など新しい試みが多く刺激的でした。またスポンサーブースでもEngineering Office Hour・チェキ・クイズに多くの方が参加してくださり、本当にありがとうございました。 最後に、iOSDC Japan 2024 の運営の皆様お疲れ様でした&ありがとうございました!また来年も参加したいなと思います! #iosdc #iwillblog
こんにちは。株式会社メルカリでiOSエンジニアをやっている kntk です。 8月22日から8月24日にかけて開催された「iOSDC Japan 2024」にメルカリはプラチナスポンサーとして参加し、会場ブース出展ではiOS開発に関するクイズを3日分(合計18問)出題しました! 本記事ではこのクイズの問題とその解説をお届けします! 総回答件数: 212件 作問者: kntk sae 前提 個別に記載がない限りXcode 15.4(Swift 5.10)・iOS 17.6.1上での実行結果を正解とします。 最適化オプションは-Oを正解とします。 Day0 前夜祭 (8/22) 回答者数: 26名 平均点: 2.04/6点 全問正解者: 0名 1. 次のプログラムを実行すると何が出力されるでしょう? func f(_ a: Int?) { print("Int?") } func f(_ a: Any) { print("Any") } let a: Int = 1 f(a) 選択肢 A) Int? B) Any 答え B) Any 解説 Swiftにはオーバーロードが存在します。 ある関数呼び出しに当てはまる複数の関数オーバーロードが存在した時、スコア規則というルールで解決する優先順位が決められています。 Intからの変換はInt?よりもAnyが優先です。 参考: Swiftのオーバーロード選択のスコア規則21種類#ランク7: SK_ValueToOptional 2. 以下の数字をカウントするアプリでCounterViewのボタンを5回押した後、RootViewのボタン (“toggle color!”)を1回押すと次のうちどの表示になるでしょうか? struct RootView: View { @State var condition = false var body: some View { VStack { CounterView() .if(condition) { $0.background(Color.red) } Button("toggle color!") { condition.toggle() } } } } struct CounterView: View { @State var count = 0 var body: some View { Button(count.description) { count += 1 } } } extension View { @ViewBuilder func `if`<Content: View>( _ condition: Bool, @ViewBuilder transform: (Self) -> Content ) -> some View { if condition { transform(self) } else { self } } } アプリの表示イメージ 選択肢 A)「5, 赤背景」 B)「5, 背景なし」 C)「0, 赤背景」 D)「0, 背景なし」 答え C) 「0, 赤背景」 解説 Stateの状態はView Identityという識別子によって管理されているため、View Identityが変わるとStateも初期化されます。また、ViewBuilderのif文はそれぞれの分岐のViewで異なるView Identityを持ちます。( Structural Identity ) 今回のプログラムでは、”toggle color”ボタンを押すとif文の分岐が変化し、違うView Identityに変わってしまうため、状態が初期化されcountが0に戻ってしまいます。 このコードの様なif modifierは、使用者側からView Identityが変わることが意識しづらいので注意が必要です。 参考: [SwiftUI] ViewのIdentityと再描画を意識しよう 3. Sendability違反 (Swift 6でのエラー) に該当する型を全て選択してください。 ※classのinitを省略して記載しています struct A: Sendable { let count: Int } struct B: Sendable { var count: Int } final class C: Sendable { let count: Int } final class D: Sendable { var count: Int } 答え Dのみ 解説 Sendableは「Isolation Domain(並列にアクセスが行われる単位)を安全に跨ぐことができる」を表すprotocolです。 structのみで構成されるstructはSendable。structは値型であり、スレッドを跨ぐ際に値のコピーが行われるため、(内部の変数がvarであっても)データ競合の危険性がありません。 classはfinalで内部の変数が全て「let」かつ「Sendable」ならSendable。それ以外はnon-Sendable。classは参照型であり、Isolation Domainを跨いで参照が共有可能ですが、変数がletであれば変数への書き込みが不可能であり、さらにSendableであれば安全にアクセスできることが保証されているため、data raceの危険性がありません。 変数がvarまたはnon-Sendableの場合は書き込みが可能でdata raceの危険性があります。 参考: 公式ドキュメント Sendable 4. nをCollectionの長さとした時、Array.countの計算量は「a」, String.countの計算量は「b」である。 選択肢 A) a: O(n) b: O(n) B) a: O(1) b: O(n) C) a: O(n) b: O(1) D) a: O(1) b: O(1) 答え B) a: O(1) b: O(n) 解説 Collection.countの計算量はO(n)です。ただしRandomAccessCollectionの場合はO(1)となります。 ArrayはRandomAccessCollectionですが、StringはRandomAccessCollectionではありません。 参考: 公式ドキュメント Collection.count 5. 次のプログラムを実行すると何が出力されるでしょう? class Counter { var count = 0 } var counter = Counter() let closure = { [counter] in print(counter.count) } counter.count += 1 closure() counter = Counter() closure() 選択肢 A) 0 0 B) 0 1 C) 1 0 D) 1 1 答え D) 1 1 解説 capture listsを用いて変数をキャプチャすると、クロージャ定義時の値でその変数が初期化されるため、変数の変更が共有されません。 参考: Swift 公式ドキュメント Capture Lists 6. 次のプログラムを実行すると何が出力されるでしょう? func log(info: String = "called: \(#function)") { print(info) } func main() { log() log(info: "called: \(#function)") } main() 選択肢 A) called: main() called: main() B) called: main() called: log(info:) C) called: log(info:) called: main() D) called: log(info:) called: log(info:) 答え C) called: log(info:) called: main() 解説 デフォルト引数における#functionは呼び出し元の関数名を生成する特殊マクロですが、特殊マクロはデフォルト引数のsub-expressionで用いる(例: 文字列展開で値を加工する)と呼び出し元の関数名を参照できなくなります。 参考: SE-0422 Expression macro as caller-side default argument Day1 (8/23) 回答者数: 116名 平均点: 2.98/6点 全問正解者: 4名 1. 次のプログラムを実行すると何が出力されるでしょう? func f() { print("sync") } @_disfavoredOverload func f() async { print("async") } func g() async { await f() } await g() 選択肢 A) async B) sync 答え A) async 解説 @_disfavoredOverload はオーバーロード解決の優先順位を下げるattributeです。 しかし「async関数内の関数呼び出しではasyncオーバーロードを優先する」という別のルールの方が優先度が高いため、この例では影響しません。 この優先度は「スコア規則」と呼ばれるルールで決められています。 参考: Swiftのオーバーロード選択のスコア規則21種類#ランク17: SK_SyncInAsync 2. 次のプログラムを実行すると何が出力されるでしょう? struct User: Hashable { var id: String var name: String func hash(into hasher: inout Hasher) { hasher.combine(name) } } let set: Set<User> = [ User(id: "1", name: "John"), User(id: "2", name: "John") ] print(set.count) 選択肢 A) 0 B) 1 C) 2 答え C) 2 解説 SetやDictionaryにおいて、基本的にはハッシュ値に基づいて保持する要素の管理が行われますが、ハッシュ値が一致した場合はEquatableによる同値比較を行い一意性を担保する仕様になっています。 この例は意図的にハッシュ値を衝突させていますが、ハッシュは仕組み上、元の値が異なってもハッシュ値が一致する可能性があるため、この仕様は重要な普段でも役割を果たしています。 3. 次のプログラムを実行すると何が出力されるでしょう? func doSomething() async -> Int { await withCheckedContinuation { continuation in DispatchQueue.main.asyncAfter(deadline: .now() + 3) { continuation.resume(returning: 1) } } } let task = Task { let result = await doSomething() print(result) } task.cancel() await task.value 選択肢 A) 1 B) 出力なし 答え A) 1 解説 Task.cancel()はTask.isCancelledのフラグを立てるだけなので実際に処理を中止する処理は自分で実装しなければいけません。 今回の例は中止する処理が記述されていないのでTask.cancel()は影響せず3秒後に1が出力されます。 参考: [Swift] async関数とAsyncStreamのキャンセル 4. 次のプログラムを実行すると何が出力されるでしょう? func doSomethingAsyncStream() -> AsyncStream<Int> { AsyncStream { continuation in DispatchQueue.main.asyncAfter(deadline: .now() + 3) { continuation.yield(1) continuation.finish() } } } let task = Task { for await result in doSomethingAsyncStream() { print(result) } print("finish") } task.cancel() await task.value 選択肢 A) 1 finish B) finish C) 1 D) 出力なし 答え B) finish 解説 Task.cancel()はTask.isCancelledのフラグを立てるだけなので実際に処理を中止する処理は自分で実装しなければいけません。 しかし、AsyncStreamに対するfor await inはTaskのキャンセル直後に処理を中止します。 AsyncSequenceに対するfor await inはAsyncSequence.Iterator.next()のシンタックスシュガーであり、AsyncStream.Iterator.next()は処理を中止する実装がされているからです。 ここで、for await inはTaskのキャンセル直後に処理を中止しますが、Task {}自体は処理を中止する処理が実装されていないので、print("finish")は実行されます。 参考: [Swift] async関数とAsyncStreamのキャンセル 5. Swift6.0から外部パッケージの型にprotocolを準拠させる際に表示される警告を消すattributeの名前は何でしょうか? extension Date: @attribute名 Identifiable { public var id: TimeInterval { timeIntervalSince1970 } } 選択肢 A) @conform B) @active C) @retroactive D) @foreign_conform 答え C) @retroactive 解説 (外部のパッケージの型に)後からprotocolを準拠させる機能は便利な一方で、フレームワーク提供側が意図しない利用方法であったり、後から提供側が同じprotocolを別の挙動で準拠させる可能性があると言う危険性があります。その危険性を可視化するための警告と、その警告を消すattributeが追加されました。 参考: SE-0364 Warning for Retroactive Conformances of External Types 6. 次のSwiftUIのコードに対応する表示は次のうちどれでしょう? Text("Hello, mercari!") .padding() .border(Color.red, width: 1) .offset(x: 50, y: 50) .border(Color.blue, width: 1) .overlay(Circle().fill(.green)) 選択肢 答え D) 解説 offsetはレシーバーの「表示位置」だけを変えるmodifierであり、周りのレイアウトやその後のmodifierに影響を与えません。 参考: 公式ドキュメント offset Day2 (8/24) 回答者数: 70名 平均点: 2.21/6点 全問正解者: 2名 1. 次のプログラムの出力が0になるような演算子はどれでしょう? let a: UInt8 = 255 let b: UInt8 = 1 print(a 演算子 b) // 0 選択肢 A) + B) ^ C) | D) &+ 答え D) &+ 解説 Swiftでは+, -, を用いた演算でオーバーフローが発生するとランタイムエラーになります。 一方、&+, &-, & はオーバーフロー演算子と呼ばれ、オーバーフローを許容して桁あがりする(.minに戻る)挙動になります。 参考: Swift 公式ドキュメント オーバーフロー演算子 2. 次のプログラムを実行すると開始から終了までに (約) 何秒かかるでしょう? func wait() async { try? await Task.sleep(for: .seconds(1)) } await (wait(), wait()) 選択肢 A) 1 B) 2 答え B) 2 解説 Swiftではawaitキーワードを一つにまとめたり、位置を変えることができます。 しかし、関数呼び出しの挙動に影響はないため、awaitを一つにまとめても並列に動作はしません。 並列に動作させる場合はそれぞれの呼び出しをasync letで定義する必要があります。 3. 次のプログラムの出力はSwift6未満で「a」Swift6以上で「b」である。 func f( _ a: (() -> Void)? = nil, _ b: (() -> Void)? = nil ) { if a != nil { print("forward") } if b != nil { print("backward") } } f { } 選択肢 A) a: backward b: backward B) a: forward b: backward C) a: backward b: forward D) a: forward b: forward 答え C) a: backward b: forward 解説 Swift6の破壊的変更の一つです。 Swift5.3未満はTrailingClosureはbackward scan (クロージャーを末尾の引数から当てはめる)のみでした。 Swift5.3からSwift5.10まではbackwardとforward両方のチェックを行います。backwardとforward両方候補になった場合は互換性維持の観点でbackwardを選択します。 しかし、Swift6.0(もしくは-enable-upcoming-feature ForwardTrailingClosures)からはforward scanのみとなります。 参考: SE-0286 Forward-scan matching for trailing closures 4. 次の呼び出しが当てはまる関数定義はどれでしょうか。 getUserInfo([Seller(), Buyer()]) protocol User { func getInfo() } struct Seller: User { func getInfo() { /* ... */ } func listItem() { /* ... */ } } struct Buyer: User { func getInfo() { /* ... */ } func purchaseItem() { /* ... */ } } 選択肢 A) func getUserInfo<U: User>(_ users: [U]) { /* ... */ } B) func getUserInfo(_ users: some Sequence<User>) { /* ... */ } C) func getUserInfo(_ users: [some User]) { /* ... */ } D) func getUserInfo(_ users: [any User]) { /* ... */ } 答え D) 解説 A: Conflicting arguments to generic parameter ‘U’ (‘Buyer’ vs. ‘Seller’) B: Cannot convert value of type ‘[Any]’ to expected argument type ‘[any User]’ C: Conflicting arguments to generic parameter ‘some User’ (‘Buyer’ vs. ‘Seller’) ジェネリクスを用いるとBuyerまたはSellerどちらか一方の型の配列として推論されるため、両方の型の値を持つ配列を受け取ることができません。 参考: A: Generics C: Opaque Argument Type (Aのシンタックスシュガー) D: Existential (Argument) Type 5. この表示に対応するプログラムはどれでしょうか? 選択肢 A) Image(systemName: "paperplane.circle.fill") .symbolRenderingMode(.palette) .foregroundStyle(.white, .blue) B) Image(systemName: "paperplane") .symbolVariant(.circle) .symbolVariant(.fill) .symbolRenderingMode(.multicolor) C) Image(systemName: "paperplane.circle.fill") .symbolRenderingMode(.hierarchical) .foregroundStyle(.blue) D) Image(systemName: "paperplane.circle.fill") .symbolRenderingMode(.monochrome) .foregroundStyle(.blue) 答え C) 解説 SF SymbolにsymbolRenderingModeを適応することによって、着色方法を変えることができます。(表示結果 左からA,B,C,Dの順) 参考: 公式ドキュメント symbolRenderingMode 6. 次の関数呼び出しが当てはまる定義をすべて答えてください。 class Super {} class Sub: Super {} let a: Sub? = Sub() f(a) func f(_ a: Any) {} // A func f(_ a: Any?) {} // B func f(_ a: Super?) {} // C 答え A, B, C(全部) 解説 A: Sub?からAnyへの変換。AnyはOptionalも当てはまります。 B: Sub?からAny?への変換。Optionalの要素SubがAnyに変換されています。 C: Sub?からSuper?への変換。Optionalの要素SubがSuperに変換されています。 まとめ 当日はたくさんの方に挑戦いただきありがとうございました! 躓きやすいSwiftやiOSの仕様をピックアップしたクイズになっているので、この記事の解説がSwiftやiOSへの理解を深める助けになれば嬉しいなと思います。#iosdc #iwillblog
こんにちは、Engineering Officeの yasu_shiwaku です。 2024年7月16日、一般社団法人日本CTO協会様主催の「 Developer eXperience AWARD 2024 」にて、「開発者体験ブランド力」調査の中で、 メルカリが昨年に引き続き3年連続で1位に選出されました。 日本CTO協会様のプレスリリースは こちら です。 今年もオフラインの会場で授賞式がおこなわれ、当日は執行役員CTO Marketplaceの 木村俊也 が出席し、受賞コメントを述べました(木村は7/17の パネルディスカッション にも登壇予定です) 昨年に引き続き、多くの方から高い評価を得られたことを嬉しく思います!社内外を問わず、ブログや登壇、イベントへの参加など多岐に渡って情報発信に貢献してくれているエンジニアたちのおかげです。 メルカリグループではエンジニアたちが主体的に発信し、コミュニティにその経験や知見を還元していくことで業界全体を活性化・成長させていくカルチャーを育てています。 またメルカリが利用させていただいているオープンソースコミュニティへの還元として、カンファレンスや プロジェクトスポンサー などの支援活動もおこなっています(メルカリの オープンソース に対する考え方はこちら。公開ソフトウェアは こちら ) メルカリグループは 「あらゆる価値を循環させ、あらゆる人の可能性を広げる」 のミッションのもと、エンジニアリング組織としても、新しいチャレンジや問題解決に向かい合っていく中でエンジニアリングの価値を循環させ、可能性を広げていくために、今後も社内外の開発コミュニティに向けて貢献できるよう、情報発信を続けていければと思います。 エンジニア向け発信媒体一覧 Mercari Engineering Website (本ポータルサイトです) Xアカウント( 英語 ・ 日本語 ) イベント関連 Connpass Meetup YouTubeチャンネル Mercari Gears Mercari devjp メルカリグループでどんな開発者体験ができるのか、またどんなカルチャーがあるのか興味がある方は、ぜひキャリアサイトを一度覗いてみてください! Software Engineer/Engineering Manager
こんにちは。MercoinでBackendエンジニアをしている goro です。 先月6月8日にAbema Towerで開催された「 Go Conference 2024 」にメルカリはSilverスポンサーとして参加し、会場にはブースを出展しました。今回はそのレポートをお届けします! Go Conference 2024 について Go Conferenceは年に1回行われる、プログラミング言語Goに関するカンファレンスです。今年は数年ぶりのオフライン開催で、20以上のGo言語に関するセッションを中心に、オフラインならではの多くのイベントが企画されました。 開催概要 開催日時 2024年6月8日(土) 場所 Abema Tower 当日の様子をご紹介 メルカリブース メルカリブースでは、Goエンジニアの参加者の皆さんに対するオープンクエスチョンと、私たちがアイディアを出し合って作成したクイズを準備しました。 オープンクエスチョンでは「あなたが一番好きなGoのライブラリは?」という質問をしました。 127票もの回答が集まり、1位「net/http」19票、2位「io」「fmt」6票、3位「gin」「echo」5票という結果になりました。投票していただいた方々、ありがとうございました。 またもう一つの企画でGoとメルカリに関するクイズを6問出題しました。 当日出題したクイズの正解に関してもこちらに記載しておきます。挑戦してくださった皆さんありがとうございました。 Q1. 次のコードはコンパイルできるでしょうか? A. No  No: "undefined: T "というエラーになります。 Go Playground: https://go.dev/play/p/jQ-V9THYZLX Q2. 次のコードはコンパイルできるでしょうか? A.No No: error: "use of untyped nil in assignment to _ identifier"というエラーになります。 The Go Playground: https://go.dev/play/p/l2Dyg6JF2xV Q3. 次のGoのコードの出力はGo 1.22では何になるでしょうか? A. 1 Go1.22では1になります。 Go1.22で修正されたループの挙動によるものです。こちらに修正された内容が詳しく書かれています。 https://go.dev/blog/loopvar-preview The Go Playground: https://go.dev/play/p/26VrcKf2dXx Q4. 次のGoのコードの出力は何になるでしょうか。 A. timeout The Go Playground: https://go.dev/play/p/CuaQuBooOQL ※このコードは通常"timeout"と出力されますが、システムの状況によっては異なる結果になる可能性もあります。 Q5. 社内勉強会 Go Friday は現時点(6/7時点)で何回開催されたでしょうか? A. 363 Q6. Cloud Spannerのためにgithub.com/xo/xo をフォークして作成されたライブラリはどれですか? A. github.com/cloudspannerecosystem/yo 今回のイベントに合わせて、メルカリブースに遊びにきていただいた記念として様々なノベルティをお配りしました。ブースにお越しいただいた皆さま、ありがとうございました! セッションについて スポンサーブースにいる時間が長かったのですが、合間にいくつかのセッションを見ることができました。個人的には「 GoのLanguage Server Protocol実装、『gopls』の自動補完の仕組みを学ぶ 」というセッションが非常に興味深かったです。このセッションでは、普段特に意識することなく利用しているgoplsの仕組みを詳しく知ることができ、とても面白かったです。 メルカリからはメルコインVPoEのpoohさんが「 Go1.21から導入されたGo Toolchainの仕組みをまるっと解説 」というテーマでセッションを行いました。立ち見になる程盛り上がっていました。 発表資料はこちらをご確認ください。 https://speakerdeck.com/yamatoya/go1-dot-21karadao-ru-sareta-go-toolchainnoshi-zu-miwomarututojie-shuo/ 抽選会 運営の企画でスポンサーブースを回ってスタンプを集めると抽選会に参加できるというイベントがありました。めちゃくちゃ可愛いGopherのキーホルダーが当たり、家宝にします! まとめ 私にとっては数年ぶりのオフラインでのカンファレンスだったので、どれくらいの方がブースに来てくださるか最初は不安でしたが、実際には100名以上の方々にメルカリのブースへお越しいただけました。クイズやアンケートにお答えいただき、本当にありがとうございました。このメルカリのテックブログを参考にしているというお声もいただき、非常に嬉しく思いました。 最後に、Go Conference 2024の運営の皆さま、本当におつかれさまでした。また次回も参加させていただきます!
はじめに こんにちは、mercari.go スタッフの hiroebe です。 6月18日にメルカリ主催の Go 勉強会 mercari.go #26 を YouTube でのオンライン配信にて開催しました。この記事では、当日の各発表を簡単に紹介します。動画もアップロードされていますので、こちらもぜひご覧ください。 moq – gomockを使わないMock生成 1つめのセッションは @oinume さんによる「moq – gomockを使わないMock生成」です。 発表資料: moq – gomockを使わないMock生成 Go の interface からモックを生成するためのツールである moq について紹介しました。Go のモックライブラリといえば gomock が有名ですが、gomock は生成されるコードが Type Safe でなかったり、多機能ゆえに学習コストが高いといった問題があります。これに対して moq は生成されるコードが Type Safe であり、また機能が絞られているぶん学習コストが低いといった特徴があり、前述の gomock の問題点を解消してくれるツールとなっているそうです。機能が絞られているといっても必要最低限の機能は提供されていて、モックのメソッドが呼び出された回数や引数の値のチェックなどは実現できるとのことです。 個人的にも moq は気になっていたものの使ったことがなかったので、この機会に触ってみたいと思いました。gomock だと too much に感じている方はぜひ試してみてはいかがでしょうか。 Gobraで見る形式検証 2つめのセッションは @kobaryo さんによる「Gobraで見る形式検証」です。 発表資料: Gobraで見る形式検証 形式検証についての概要説明と、Go プログラムの検証器である Gobra の紹介を行いました。形式検証とは「プログラムが仕様を満たしていることを数学的手法を用いて証明すること」で、私たちが普段利用しているプログラムの「型」も一種の軽量な形式検証であると紹介されています。Gobra はアノテーション付きの Go プログラムを入力する検証器で、インターフェースや Goroutine といった Go の主要な機能にも対応しているそうです。発表では、これらの機能が Gobra によってどのように検証されるかについて、具体例を交えて説明されています。 形式検証というテーマは個人的にあまり馴染みのない内容だったので、とても新鮮でおもしろい発表でした。アノテーションの記述量の多さや仕様を定めることの難しさなど形式検証には欠点もあるそうですが、普段の開発でも活用できるような例があればぜひ知りたいと思いました。 gRPC Federation から見る WebAssembly の実践活用法 3つめのセッションは @goccy さんによる「gRPC Federation から見る WebAssembly の実践活用法」です。 発表資料: gRPC Federation から見る WebAssembly の実践活用法 メルペイではリリースからの時間経過に伴って BFF (Backends for Frontends) の肥大化が問題となっており、これを解消するために BFF を複数に分割し、個々の BFF を gRPC Federation を用いて構築することで開発・運用コストを抑えるというアプローチをとっています。gRPC Federation は Protocol Buffers 上で記述した DSL から gRPC Server を自動生成する仕組みで、これによって個々のサービスの開発者はサービスそのもののロジックに集中できるという利点があります。今回の発表では WebAssembly を用いたプラグインの仕組みに焦点を当てて、コンパイラ・ランタイムの選定や、ホストとクライアント間のデータのやり取りについて紹介されています。 WebAssembly の活用事例としてとても興味深い発表でした。gRPC Federation については goccy さんのブログ記事 gRPC Federation: gRPC サービスのための Protocol Buffers を進化させるDSL もぜひ併せてご覧ください。 おわりに 今回は Go のツール紹介や WebAssembly の活用事例など、幅広い内容の発表をお送りしました。普段の開発では触れる機会の少ないような内容もあり、運営としても非常に興味深く聞かせていただきました。 ライブで視聴いただいた方も録画を観ていただけた方も本当にありがとうございました! 次回の開催もお楽しみに! イベント開催案内を受け取りたい方は、connpassグループのメンバーになってくださいね! メルカリconnpassグループページ
こんにちは。メルカリのSoftware Engineerの @tanasho です。 連載:Mercari Hallo, world! -メルカリ ハロ 開発の裏側- の6回目を担当させていただきます。 メルカリ ハロのWebアプリケーションは複数存在し、Webフロントエンドチームが横断的に開発をしています。本記事では、その前提を踏まえ、スピードと品質をどのように両立させて開発しているかを紹介します。 プロジェクトの概要とWebフロントエンドの担当領域 メルカリ ハロは「あたらしい出会いを繋ぎ、信頼と機会をひろげる」がミッションで、いますぐ働き手が欲しいパートナー (事業者) と、いますぐ働きたいクルー(働き手)を繋げるサービスです。クルーは自身のスキルや時間を活用して働くことができます。 メルカリ ハロは複数のアプリケーションが存在し、そのなかでWebフロントエンドが関わる領域として以下の3つがあります: はたらくタブ 事業者管理画面 CS Tool それぞれのアプリケーションの概要は以下の通りです。 はたらくタブ 「はたらくタブ」は、メルカリアプリ内に用意されている、クルーを対象とした機能です。「はたらくタブ」は、スポットワークの経験がない人や、時々働きたいライトなユーザーに向けた機能で、この機能を使うことで、メルカリ ハロの専用アプリと同様に仕事を見つけて働くことができます。またすでにメルカリを使っているお客さまにも利用していただくことで、「メルカリ ハロ」の迅速な認知拡大が期待されています。 関連記事: 『メルカリ ハロ』の成長を加速させる最重要タッチポイント、「はたらくタブ」はこうしてつくられた この「はたらくタブ」はiOSおよびAndroidのメルカリアプリ内のWebView上で動作します。各OSごとに開発する必要がなく、Web開発のみで対応できるため、開発工数を削減できました。 また、リリース時にはアプリストアの審査が不要となるため、柔軟にリリースを行うことが可能です。 さらに、既存のメルカリアプリ内に存在するためアクセス数が多い特徴もあります。 事業者管理画面 事業者管理画面は、メルカリ ハロにおける求人や募集の作成など、パートナー向けのWebアプリケーションです。権限管理が必要な点や、フォームを使った登録・更新の機能が多い点が特徴です。 CS Tool 社内のメルカリ ハロのカスタマーサービス向けのWebアプリケーションです。 お問い合わせに対応するために、クルー検索などの機能が備わっています。 スピードと品質を両立して開発するための取り組み メルカリ ハロを展開するにあたって、連載の最初の記事でも紹介したように、開発のスピードを重要視しています。これはスポットワーク市場の拡大の波に沿って事業を推進するためです。また、最初から正しい仮説を持つことは難しく、仮説の立案、検証、改善のイテレーションを高速に回すことが大事だからです。 またスピード感をもった開発が求められる一方で、高い品質を保つことも重要です。 現在、メルカリ ハロのWebフロントエンドチームは約6名の少人数で「はたらくタブ」「事業者管理画面」「CS Tool」の3つのWebアプリケーションの開発を担当しています。 こうした少人数の構成の中でどのように工夫をして、開発スピードと品質の両立を達成しているかについてご紹介します。 柔軟なアサイン 「はたらくタブ」「事業者管理画面」「CS Tool」を開発するにあたって、特定のWebアプリケーションに優先度の高い機能開発の案件が集中することがあります。これに対応するため、Webフロントエンドチームでは特定のWebアプリケーションに担当を固定せず、横断的にアサインが可能な体制にしています。例えば、「事業者管理画面」に優先度の高い機能開発の案件が多くなった場合、その開発にリソースを集中させます。状況に応じて柔軟にアサインを変えられる体制にすることで、優先度の高い機能をスピード感もって開発することができました。 技術スタック・開発ルールを可能な限り統一 柔軟なアサイン体制の下で生産性と保守性を上げるために「はたらくタブ」「事業者管理画面」「CS Tool」の技術スタックやアプリケーションの構造、開発ルールなどは可能な限り統一しております。 ただし、技術的な問題、アプリケーションの仕様の違いなどにより、一部に差異が生じる箇所も存在しています。 「はたらくタブ」「事業者管理画面」「CS Tool」で扱っている主な技術スタックは以下の通りになります: Next.js (App Router) React TypeScript ESLint Apollo Client ※ Tailwind CSS Storybook React Hook Form Jest Playwright pnpm Datadog 統一の結果、普段担当していない別のWebアプリケーションにアサインされた場合でもスムーズに開発を行うことができました。例えば「事業者管理画面」を開発していたメンバーが「はたらくタブ」の開発を任された場合でも、ほとんど同じ技術スタックや開発の作法を活用できるため、追加の学習コストを抑えて開発に取り組むことができました。また、開発ドキュメントもある程度共通化することができるため、ドキュメントの保守性も向上しました。 さらに、メルカリ ハロではmonorepoを採用( 関連記事 )し、異なるWebアプリケーションを同一リポジトリで管理しています ※ 。これにより、Webアプリケーション間の行き来が容易になったり、lintのルールの設定などがpnpmのworkspaceを使って共有可能になりました。 ※ただし、CS Toolは除く ... ├── dart ├── go └── typescript ├── apps │ ├── partner-portal-web //事業者管理画面 │ ├── work-tab //はたらくタブ ├── libs //共通の関数やlintのルールの設定 ... これらの取り組みによって開発生産性と保守性が向上しただけでなく、誰かが開発で困ったときに他のメンバーが助けやすい体制が整い、チームとして支え合う文化が一層強まったことも大きなメリットだと思います。 以降、その統一された技術スタックでどのように開発しているか紹介します。 UI開発 UI開発において生産性を上げるために社内のメルカリデザインシステム( 関連記事 )を採用しています。 メルカリデザインシステムはFigmaで提供されており、デザイナーはデザインシステム上のトークンやUIコンポーネントを参照して画面を作成しています。フロントエンド開発では、Figma上で使用すべきコンポーネントや色が明示されているため、迷わず開発できるようになっています。また、 カスタムテーマ の設定で、メルカリのデザインシステムのトークンをTailwind CSSに統合しています。これによって、レイアウトの調整やデザインシステムが提供していないUIコンポーネントを作成する際に、簡単に一貫性のあるスタイルを適用することが可能です。 メルカリデザインシステムにもとづいて画面を作成することで、UIコンポーネントの開発工数を削減し、デザイナーと開発者間でUIの細かい挙動についての認識も一致させやすくなりました。 さらに、デザインレビュー時にはプルリクエストの段階でテスト環境にデプロイし、その環境で画面の動作を確認してもらうことができます。実際の画面を操作することで、デザイナーと開発者の認識を更に一致させることができました。 バックエンドとのやり取り メルカリ ハロのフロントエンドとバックエンドとのやり取りはGraphQLで行っており、そのAPI Clientとして社内で利用実績が多く、キャッシュ管理などの豊富な機能を持つApollo Client を採用しています ※ 。ここで取り入れた仕組みや活用している機能についていくつか紹介します。 ※ただし、CS Toolは除く Apollo ClientのHooksを自動生成 ページに必要なAPIのフィールドをGraphQLのスキーマファイルをもとに定義し、Apollo ClientのHooksを自動生成しています。 monorepoを採用しているため、同一リポジトリ内のフロントエンドの開発ディレクトリからバックエンドの開発ディレクトリに配置されているGraphQLのスキーマファイルを直接参照でき、それをもとにApollo ClientのHooksを自動生成するといった仕組みです。 これにより、バックエンドからデータを取得する際には、その自動生成されたApollo ClientのHooksを呼び出すだけで済み、開発効率向上につながりました。また、ページに必要なデータのフィールドのみを取得し、APIの呼び出しも一回にまとめられるため、ネットワークのコストを抑えることができました。 Apollo Clientの機能の活用 Apollo Clientはデータ取得以外にも多くの機能を持っています。一例として、キャッシュの機能です。実行したqueryのレスポンスデータがローカルのインメモリ上にキャッシュされるため、そのキャッシュを活用することができます。例えば、useQueryの fetchPolicy がデフォルトのcache-first の場合ですが、fetch時にメモリにキャッシュが存在していたらそのデータを返します。ただし、存在しない場合はサーバーから取得します。キャッシュを優先しているため速度を重視する場合には有効かと思います。またこのfetchPolicyの設定は用途に合わせて変更可能です。 「事業者管理画面」では、データの表示速度も重要ですが、募集の応募状況など最新の情報を求められることが多く、データの新鮮さも重要です。そのため、キャッシュを活用しつつ、同時にサーバーから最新のデータも取得・更新するcache-and-networkの設定をデフォルトでしています。 さらに、Apollo Clientには Link という機能があります。これはApollo ClientとGraphQLサーバー間のデータフローをカスタマイズするための仕組みです。特にサーバーからのエラーハンドリングにおいてLinkを活用しています。例えば、Unavailableのエラーを返した場合にメンテナンスページにリダイレクトしたり、認証が切れたらtokenをリフレッシュする処理を実装しています。 モック開発 バックエンドのAPIが開発中であっても、フロントエンドの開発を進めたい場合があります。 その際に、まず先にバックエンドからGraphQLのスキーマファイルを共有してもらい、そのスキーマをもとに MSW を利用してAPIをモックし開発しています。この結果、フロントエンドとバックエンドが並列して開発でき、生産性の向上につながりました。 自動テスト リグレッションの自動検知を目的に自動テストを導入しています。 導入にあたって自動テストに費やせる時間と人手では限られているため、効率的・効果的にリグレッションを防ぐためのテスト設計が必要でした。 そこで一つの観点として、他のチームよって品質が担保されているテストと被らないことを重要視しました。以下のテストに関してはすでに他のチームで行われていました。 UIコンポーネントのテスト 前述の通り、メルカリ ハロのUIコンポーネントはメルカリデザインシステムを使っており、そのチームよって自動テストが行われている E2Eテスト QAチームが手動および自動で(フロントエンドからバックエンドまでの疎通を含む)E2Eテストを行っている この観点のもと、Webフロントエンドでは以下の自動テストを設計・運用しました。結果として、他のテストでカバーできていないテストを書く運用になり、効率的に品質を担保することができました。 単体テスト Jestを利用 共通関数の詳細な挙動を担保するため、条件分岐を網羅したテストを実施 ページ単位の統合テスト Playwrightを利用 機能が仕様通り動いているかを主にページ単位でテストを実施 バックエンドへの通信を行わず、必要なAPIのデータはモックし、フロントエンドに閉じた形で実施 運用にあたって、単体テストについては、共通関数として切り出したときにテストを書く運用がチームメンバーに浸透していたため、特に問題はありませんでした。 一方でページ単位の統合テストに関しては、対象となるページ数が多く、様々なテストシナリオが考えられるため、どの統合テストを重視して書くべきかが課題でした。例えば、特定のレスポンスが返ってきた際のページの表示が正しいかどうか、入力したフォームのバリデーションが正しく動作するかどうかなどです。そのため、どのテストを優先して書けば大きな効果を発揮するか検討する必要がありました。 そこで「はたらくタブ」「事業者管理画面」「CS Tool」それぞれのアプリケーションの性質に合わせて統合テストの方針を決めました。 例えば「事業者管理画面」では、フォームをSubmitした後のAPIのリクエストが正しいかのテストを第一優先としました。これは管理画面の性質上、募集作成など重要なデータの作成機能が多いため、意図しないリクエストが送信されて登録されてしまうと大きな問題が発生するためです。 このように、他で品質が担保されていない箇所を単体テスト・統合テストで担保し、統合テストのなかでアプリケーションの性質に合わせて方針を決めることで、効率的・効果的にリグレッションを防ぐことができました。 おわりに スピードと品質を両立させるためのWebフロントエンドの取り組みについて紹介しました。 「はたらくタブ」「事業者管理画面」「CS Tool」のアプリケーション間で可能な限り統一した技術を扱い、フロントエンドチームメンバー内で状況に応じた柔軟なアサインによって、お客さまが求める重要な機能をスピードと品質を両立させて開発し続けられる体制になりました。また、同じチームで性質の異なる3つのWebアプリケーションを経験することは、エンジニアにとって楽しく、やりがいのある挑戦でもありました。 更にこの体制によって、誰かが開発で困った場合でも他のメンバーが助けて支え合う「All For One」な文化が一層強まってると日々感じております。 その「All For One」な文化はメルカリグループ全体からも感じています。メルカリのデザインシステムがあらかじめ用意されていたり、 monorepo戦略がその一例です。自チームにとどまらず全体最適になるよう取り組んでくれたおかげで、この開発スピードと品質の両立が実現できてると感じてます。 この取り組みは終わりではなく、今後もチームやプロダクトの状況を見ながら、前向きに改善し続けることが重要です。引き続き、チームと話し合いを重ねて改善に努めていきたいと思います。 Links 連載:Mercari Hallo, world! -メルカリ ハロ 開発の裏側- メルカリではメンバーを大募集中です。メルカリ ハロの開発やメルカリに興味を持った方がいればぜひご応募お待ちしています。詳しくは以下のページをご覧ください。 Software Engineer, Frontend – Mercari/HR領域新規事業 (Mercari Hallo) Software Engineer, Backend – Mercari/HR領域新規事業 (Mercari Hallo) Software Engineer, iOS/Android (Flutter) – Mercari/HR領域新規事業 (Mercari Hallo) Software Engineer, Site Reliability – Mercari/HR領域新規事業 (Mercari Hallo) QA Engineer – Mercari/HR領域新規事業 (Mercari Hallo) Engineering Manager – Mercari/HR領域新規事業 (Mercari Hallo)
こんにちは。メルカリ ハロでSoftware Engineerをしている @atsumo です。連載『 Mercari Hallo, World! -メルカリ ハロ 開発の裏側- 』の第5回を担当します。 メルカリ ハロではメルカリアプリ内にある「はたらくタブ」とは別にクルー向けのアプリ(ストアで「メルカリ ハロ」と検索してみてください)を用意しています。本記事では、アプリ版のメルカリ ハロで使用している技術とその選定理由、さらにリリースまでの開発の進め方などを踏まえてご紹介したいと思います。 この記事で得られること メルカリ ハロのアプリの技術スタック その技術選定の理由と効果 開発の進め方とヒント 技術スタック メルカリ ハロ アプリで使っている技術スタックをいくつかピックアップして紹介できればと思います。 フレームワーク: Flutter 第2回の記事 の中のメルカリ ハロのモバイルアプリの技術選定でも記載がありましたが、メルカリ ハロは マルチプラットフォームフレームワークのFlutterを採用しています。導入決定までの決断などは第2回で触れられているのでそちらをご覧いただければと思います。そちらの記事の中でも妥当な決断であったとありましたが、リリース後にアプリ開発に関わっているメンバーで振り返りを行った際も、開発効率の向上とサービスとしての品質の担保の両方行うことができ、良い選択だったという意見が多かったです。 開発人数が少ない中、両プラットフォームを同じタイミングでリリースすることができたのはFlutterを採用したことがとても大きかったです。 CLI: Melos Melos は複数パッケージをもつDartプロジェクトの管理に使用されるCLIツールです。 メルカリ ハロではモノレポ(monorepo)を採用しており、下記のような言語ごとのディレクトリ構造になってます。 ├── dart │ ├── analysis_options.yaml │ ├── apps │ │ ├── widgetbook │ │ └── hallo_app (ハロ アプリ) │ ├── melos.yaml │ ├── packages │ │ ├── hallo_design_system (デザインシステム) │ │ ├── hallo_linter │ │ └── ... │ ├── pubspec.lock │ └── pubspec.yaml ├── go ... └── typescript 開発当初、Flutterのプロジェクト自体は1つで複数のパッケージはなかったのですが、後でデザインシステムや社内のシステムを使うものはパッケージに分けていく構想があったため、開発開始時点からMelosを導入していました。初期は主にコマンドをまとめるための用途としてしか使っていなかったのですが、リリース後少し落ち着いたタイミングでデザインシステムをパッケージ分割したり、社内のサービスを使うためのパッケージが増えていったりと複数パッケージを持つプロジェクトとなり、引き続き力を発揮してくれています。 通信: GraphQL モバイルアプリとバックエンドの通信にはGraphQLを採用しています。 メルカリ ハロのアプリでは GraphQLを使用するための packageとして graphql_flutter , graphql_codegen を使用しています。 バックエンド側で定義した graphqlのschemaとアプリ側でアクセスするgraphqlファイルを元に graphql_codegen でGraphQLのサーバーにアクセスするためのファイルを自動生成しています。 サーバリクエストとレスポンスデータのキャッシュはgraphql_flutterが行ってくれるため、次にお話しする状態管理において構成を単純化するのに大きく寄与しました。 状態管理について 公式ドキュメント Differentiate between ephemeral state and app state に ephemeral state(一時的な状態管理) と app state(それ以外の状態管理) と分かれて記載されているのでそれに沿って説明します。 Ephemeral state (一時的な状態管理) Widget内やScreen内で完結するようなステートには flutter_hooks を使用しています。具体的には StateWidgetでのStateにもたせていたようなものには useStateを使用しています。画面を開いた際に画面の閲覧のログなどを送信したい場合には useEffectなどを使用しています。さらに必要に応じてカスタムHooksを作成しています。 App state (サーバリクエストとレスポンスデータ / グローバルステート) サーバサイドリクエストやキャッシュ GraphQLでのやり取りをするために、graphql_flutterを使用しています。graphql_flutterはApollo Clientをモデルとしたgraphqlのpackageです。 Apollo Client はローカルとリモートデータの両方をGraphQLで管理することができる包括的な状態管理のライブラリです。ただ現在、Apollo ClientはFlutter版のpackageがないため、その代わりとして Apollo Clientをモデルとして作られている grahql_flutter を選択しました。 GraphQL自体の仕組みや graphql_flutter が持つキャッシュの仕組みによって、クライアント側で独自の管理をする必要をなくし、状態管理の複雑性を削減しています。 ※ graphql_codegenが生成する hooksのメソッドを使用しています グローバルステート 画面を構成するうえで必要な情報はgraphql_flutterの情報から取得するのですが、複数画面に使用されるものや認証情報などに関しては Riverpod を使用してグローバルステートとして管理しています。 デザインシステム メルカリ ハロのアプリでは、メルカリのデザインシステムではなく、独自のデザインシステムを採用しています。ここでお話するデザインシステムは主にデザイントークンやUIコンポーネント、そのデザインデータや実装されたコンポーネントなどを指しています。 基本的にコンポーネントをベースに画面のUIデザインが行われています。立ち上げ時にはひたすらUIコンポーネントを実装してWidgetbook(後述)で確認し、一通りコンポーネントを実装し終わったあとに画面の実装に入っていきました。 Figma上のComponent propertiesでBooleanやTextだけでなくInstance SwapやVariantを用いてデザインしてもらっていたので、実装する際も他のWidgetがプロパティとして入ることが想像でき、デザインデータとして変更できるプロパティと実装上コンポーネントのプロパティのギャップが少ない状態を作ることができました。 各画面のデザインは主にコンポーネントを使った構成になってます。実装時にはコンポーネントを配置し、プロパティの値にFigmaのものを入れていくというシンプルな作業になり、生産性が非常に高く保たれたと感じています。 画面が増え、機能が増えることで、当時は想定できていなかった見せ方が必要になり、コンポーネント自体のアップデートであったり、新たなコンポーネントを追加することもありますが、初期の段階からある程度コンポーネントができていて画面実装に入れたのはとてもありがたかったです。このようにコンポーネントを先行して実装していく進め方は、他のプロジェクトでも参考にしていただけるのではないでしょうか。 開発したコンポーネントは、次に触れるWidgetbookを使用してカタログとして閲覧できるようにしています。 UIカタログ: Widgetbook Widgetbook はフロントエンドでよく使われるStorybookのFlutter版のようなツールで、デザインシステムで定義したUIコンポーネントや実装したWidgetなどをカタログのように表示することができます。 デザインシステムとして用意しているUIコンポーネントをカタログのように見れることで、途中からチームに参画したメンバーもUIコンポーネントとして用意してあるものを一覧で見ることができたので、画面実装する際もスムーズに行うことができたという声もありました。 画面実装が進むにつれて実際の画面で確認することなどが増えましが、特定条件でのみしか表示されないようなUIに関してはWidgetbookを使用して素早く確認することができとても重宝しております。 現在Widgetbook 3を使用していますが、Widgetbook 4では Goldenテスト を再利用することができる仕組みやモノレポ(monorepo)での使い勝手などを考慮した改善などが検討されているようなので、次のバージョンを期待しています。 まとめ メルカリ ハロのアプリ開発では、いくつかの技術選定が開発を進める上で大きな役割を果たしてくれました。 Flutterの採用により、少人数でのスタートにも関わらず、iOSとAndroidの両プラットフォームを同時にリリースすることができた。 将来的なパッケージの分割も見据えて、モノレポ(monorepo)でのプロジェクト管理にMelosを導入したことで、複数パッケージの管理が容易になった。 バックエンドとの通信にはGraphQLを採用することでサーバーとのやり取りがスムーズになり、状態管理の構成をシンプルに保つことができた。 開発当初からデザインシステムの構築を最優先に進めたことで、デザイナーとの共通言語ができ、Widgetbookを使ってカタログ化することで開発効率を大幅に向上させることができた。 私たち開発チームにとって、これらの技術選定と開発方針が、メルカリ ハロのアプリ開発を支える大きな力になったと感じています。 さいごに メルカリ ハロアプリの開発では、FlutterやGraphQLなどをはじめとする多様な技術を採用しましたが、あくまで現在のチーム状況やサービス内容において適した選択でした。まだまだサービス的にもシステム的にも改良の余地があるので、常により良いものを探求していきたいと思っています。 プロジェクトごとにフィットする技術や方法は異なると思いますが、今回ご紹介したメルカリ ハロでの事例が、みなさんにとって最適解を見つけるためのヒントになれば幸いです。 Links 連載:Mercari Hallo, world! -メルカリ ハロ 開発の裏側- メルカリではメンバーを大募集中です。メルカリ ハロの開発やメルカリに興味を持った方がいればぜひご応募お待ちしています。詳しくは以下のページをご覧ください。 Software Engineer, Frontend – Mercari/HR領域新規事業 (Mercari Hallo) Software Engineer, Backend – Mercari/HR領域新規事業 (Mercari Hallo) Software Engineer, iOS/Android (Flutter) – Mercari/HR領域新規事業 (Mercari Hallo) Software Engineer, Site Reliability – Mercari/HR領域新規事業 (Mercari Hallo) QA Engineer – Mercari/HR領域新規事業 (Mercari Hallo) Engineering Manager – Mercari/HR領域新規事業 (Mercari Hallo)
こんにちは。メルカリのQAエンジニアリングマネージャーの @____rina____ です。今回は、連載『 Mercari Hallo, World! -メルカリ ハロ 開発の裏側- 』の第4回を担当します。 本記事では、メルカリ ハロのサービスローンチまでのQAプロセスを通じて、私たちはどのようにして安心・安全なプロダクトを迅速にリリースするための戦略を実行したか、具体的な方法とともに詳述しています。 この記事を通じて、以下の点についての理解を深めていただけることを目指しています: QAの役割とプロジェクト概要 効率的なQAアサイン戦略 成果物の透明性と管理ツールの効果的な活用方法 また、この記事を書くにあたり、私自身が学んだことや得た教訓についても触れています。これらの経験は、今後のプロジェクトにおいて更なる品質向上と効率化を目指す上で非常に貴重なものとなりました。 プロジェクト概要とQAの役割 「メルカリ ハロ」は、事業者や店舗である”パートナー”と、働き手である”クルー”を結びつけるサービスです。クルーは自身のスキルや時間を活用して働くことができます。「メルカリ ハロ」はメルカリのWorkチームという組織で開発をおこないました。また、このサービスにはWorkチーム以外にも多くのチームが密接に関わっています。例えば、事業者アカウントの作成やKYC、給与振込などはメルペイが担当し、経理はホールディングスのチームが、問い合わせ対応はメルカリが担当しています。  このサービスにおけるQAの役割は、サービスローンチまでに開発を最短かつ安心・安全に進めるための戦略を実行することです。具体的には、品質保証アプローチを駆使し、プロダクトの全体的な品質を高めるとともに、スムーズなリリースを実現することです。 QAエンジニアのアサイン戦略 サービスローンチの当初の計画では、私がすべてのQA活動およびテスト実行を担当する予定でした。しかし、組織や開発の規模が想定以上に大きくなったため、追加のメンバーが必要となりました。その結果、QAエンジニアの採用に加え、業務委託と外部ベンダーの協力を得て、QA活動を以下の3つの主要な役割に分けて進行することにしました。 なお、メルカリアプリにある「はたらくタブ」の開発はメルカリのメンバーが開発しました。 メルカリ(Workチーム)のQAエンジニア WorkチームのQAエンジニアは、テストプロセス全般に加え、以下のタスクを担当しました: オンボーディング資料の作成:これから参画するQAエンジニアやチームのために、オンボーディング資料を作成しました。 テスト設計:各ユーザーストーリーにAcceptance Criteriaを追加しました。Acceptance Criteriaについては、こちらのブログもご参照ください。参照: QAがAcceptance Criteriaにテストしたい項目を追加して、みんなでいつ何をつくるのか考えたよ プロジェクト進捗把握:QAの進捗だけでなく、開発の進捗も管理することで、スムーズなリリースを支援しました。開発の進捗はJIRAで把握したかったので、チケットも率先して作成しました。 各種ドキュメント作成:テスト計画書、テスト完了報告書などを作成しました。 他カンパニーのQAとの連携:今回のサービスでは他のカンパニーとのシステム連携が多かったため、障害を未然に防ぎ、スムーズに開発を進めるためにQA間での連携が必要不可欠でした。定期的なミーティングやドキュメントの認識共有、進捗確認などを通じてコミュニケーションを密に行うことを心掛け、カンパニー間で連携の齟齬が発生しないよう努めました。 各詳細については、後で詳しく説明します。 アルムナイの業務委託のQAエンジニア メルカリのことをよく知るアルムナイ(元社員)のQAエンジニアに業務を委託しました。本業との兼ね合いで時間が限られていたため、時間を最大限に活かしてもらう必要がありました。主に以下の役割を担いました。: オンボーディング資料の更新と作成:新しいチームメンバーがスムーズに参加できるよう、オンボーディング資料を作成および更新しました。 エピック単位のテストと探索的テスト:エピックとは、機能のまとまりを表す単位です。私たちはそれぞれのエピックにユーザーストーリーを紐づけます。通常はユーザーストーリーごとにテストを行いますが、業務委託のメンバーは稼働時間が限られているため、彼らの能力を最大限に活かすために探索的テストを中心に実行しました。 この際、エピックごとにテストを実施し、エピックに対応するユーザーストーリーのテストを行いました。こうして、各機能がうまく連携して動作するかを確認しました。また、Acceptance Criteriaに縛られずに、探索的にテストすることで予期しないバグや問題を早期に発見することを目指しました。 リグレッションテストの作成:サービスローンチ前に多くの機種やOSでも正常に動作することを保証するためにリグレッションテストを実施する必要があります。また、開発中は一貫した開発環境が整っていなかったため、リリース前に一貫した環境で動作することも目的としています。サービスローンチ前の状況では、機能開発に追われてリグレッションテストのケースを作成する時間が取れません。また、記載に一定のルールがないと、テスト実行が難しくなってしまいます。業務委託のメンバーはアルムナイのため、メルカリ時代の知見があり、機能開発に引っ張られずにリグレッションテストの方針を理解し、作成することができます。これにより、多くの人が一緒にテストを実行できる体制を整えました。 テスト設計レビュー:Acceptance Criteriaのレビューおよび修正を行いました。 ベンダーのQAエンジニア 以前メルカリに参画していたメンバーを中心に、外部ベンダーに参画していただき、以下の活動を行いました: テスト設計からテスト実行:テスト設計から実行を担当し、特定の分野における専門知識を活用しました。今回は開発期間もタイトであったため、ユーザーストーリー単位に実行できるものからテスト実行しました。また、他カンパニーとのシステム統合テストもキャッチアップからテスト実行まで実施しました。 リグレッションテストの設計から実行:システム全体の安定性を維持するためにリグレッションテストを実施しました。 以上のように、多様なメンバーと役割分担を用いたQA戦略を採用し、組織の規模と開発の複雑さに対応しました。QA活動の効率化と品質の確保を両立させるために、このような工夫を取り入れました。 成果物の透明性と管理ツールの利用 ここでいう成果物とは、QA活動において作成したドキュメント類を指します。今回は主に以下の成果物を作成しました。 テスト計画書 オンボーディング資料 テスト設計時のAcceptance Criteria リグレッションテストのためのテストケース QAダッシュボード プロジェクト管理ダッシュボード テスト完了報告書 これらの管理はJIRA、Confluence、TestRail、およびGoogle Spreadsheetsを使用して行いました。すべての成果物はクラウド上に保存され、社内で誰でも参照できるようにしました。これにより、現在の状況などにアクセスしやすくし、いつでも参照できる環境を整えました。 このようにして、成果物の透明性を高め、プロジェクト管理ツールを効果的に利用することで、プロジェクト管理の効率化と円滑なコミュニケーションを実現しました。 次にそれぞれの成果物について具体的に紹介します。 テスト計画書 テスト計画は、サービスローンチの成功に不可欠です。前述のとおり、QAに関しても共通して参照できるドキュメントが必要でした。そのひとつがテスト計画書です。 テスト計画書の作成には、国際標準である ISO/IEC/IEEE 29119のPart3 を参考にしました。ISO/IEC/IEEE 29119はソフトウェアテストの国際規格で、ソフトウェアテストに関するプロセス、ドキュメント、技術などを定義しています。Part3はドキュメントについて定義しています。今回は、主に以下の構成で作成しました: 用語集: プロジェクト内で使用される専門用語や略語の明確な定義を提供し、プロジェクト関係者間の誤解を防ぐためです。社内で使用されている他の用語集にないQA特有の用語も含めました。 テストの成果物: テストケース仕様、テストスクリプト、テストレポートなど、生成されるすべてのドキュメントをリストアップしました。 テスト環境: 「メルカリ ハロ」はリリース前ということもあり、開発中のコードはmainにマージして、開発(=テスト)環境でテスト実行をしました。一方で、事業アカウントや給与振り込みを担当するメルペイはすでにリリースされているサービスで、各開発チームで使用する環境を設定する必要がありました。そのため、「メルカリ ハロ」とのシステム統合テストで利用する環境は厳密に設定する必要がありました。そこで、テスト計画書に「いつからどの環境を使うか」を明確に記載しました。これにより、テスト環境の設定を誤ることのないようにしました。 テストの完了条件: テストが正式に完了するための具体的基準を定義しました。特にメルペイとのシステム統合テストにおけるテスト計画書は、多くのチームメンバーが参照する重要なドキュメントとなりました。 オンボーディング資料 オンボーディング資料はいくつかのパートに分けて作成しました。ドキュメントは「オンボーディングクエスト」という名前で、参画したメンバーが各自で進められるようにしています。また、ドキュメントが古くならないように、参画したメンバーがいつでも更新できる仕組みを整えました。オンボーディングクエストについては以下も参考にしてください。参考: Notionを活用したエンジニア向けオンボーディング オンボーディング資料には以下の種類があります: 全社共通のオンボーディング資料:会社全体で必要となる基本的な情報を提供します。 プロダクトに関わるメンバー用のオンボーディング資料:各プロダクトに関する詳細な情報を提供します。 QAエンジニア用のオンボーディング資料:QAエンジニアがテスト実行に使うための設定ページや、不具合発生時のチケット起票ルールなどを含みます。 QAエンジニア用の資料には、主に以下の内容が含まれています: 設定ページ:テスト実行に必要な設定方法を詳細に説明します。 チケット起票ルール:不具合発生時のチケット起票方法やルールを説明します。 テスト設計時のAcceptance Criteria テストケースはAcceptance Criteriaとして、すべてストーリーチケットに直接記載しました。これにより、エンジニアがセルフチェックに利用できるほか、ストーリー完了後のより大きなまとまり(エピック)単位でのテスト実行にも活用できます。テスト実行が完了した後には、QAレビューを行いますが、ストーリー単位でのレビューが可能なため、効率的に進めることができました。 リグレッションテストのためのテストケース 今回QAでは、ストーリー単位でも、エピック単位でも、それぞれでテスト実行をしてきましたが、サービスローンチ前にさまざまなOSやOSバージョンでの動作も担保する必要がありました。 そのため、基本的なシナリオに基づいたテストを実施する必要があり、これを達成するためにリグレッションテストケースを作成しました。このリグレッションテストケースを用いることで、多くの環境での一貫性と安定性を確保しながら、テストを効率的に実行することができます。 QAダッシュボード QAダッシュボードでは、進捗やバグの発生、解決状況を把握することを目的としています。このダッシュボードはJIRAのダッシュボード機能を使用して作成しました。主に以下の内容を表示しています: ユーザーストーリー毎のテストのステータス状況 システム統合テストの進捗状況 ユーザーストーリー毎の不具合対応状況 システム統合テストの不具合対応状況 プロジェクト管理ダッシュボード 全体の進捗状況や各タスクの進捗はJIRAのチケットに記載し、ダッシュボードも作成しました。このダッシュボードでは、全体の予定工数、現在の開発ステータス、週ごとの完了工数などを表示できるようにしています。さらに、これらのデータをGoogle SpreadSheetsでシンプルなグラフに変換し、全社ミーティングで進捗を報告するようにしました。このようにすることで、各ロールの進捗を把握するだけでなく、他のロールの予定や課題も共有しやすくなりました。 テスト完了報告書 テスト完了報告書はConfluenceで作成しました。テスト完了報告書はリリース判定会議でも使用されます。リリース判定会議とは、新しいサービスや大規模なプロジェクトの際に行われる会議で、私はプロダクトの品質を機能面で評価し、報告する役割を担っていました。最終的な承認はエンジニアリングのVPが行います。事前に承認条件を設定し、テスト完了報告書を使用して適切な評価を提供することで、スムーズに承認を得ることができました。 テスト完了報告書の項目の選定には、ISO/IEC/IEEE 29119のPart3を利用しました。この標準を採用することで、評価項目が適切に盛り込まれ、全体のテストプロセスの一貫性と透明性が確保されました。また、この報告書はJIRAと連携し、データが自動更新される仕組みを取り入れることで、報告の正確性とタイムリーな情報更新を実現しました。 おわりに 今回紹介した取り組みにより、「メルカリ ハロ」は大きな問題や遅延もなくサービスローンチをすることができました。今回の活動を通して、短い期間で様々な環境の変化に耐えうるQAの戦略を取ることができたのは大きな収穫でした。QAの役割は単なるテストの実施に留まらず、プロジェクト全体の品質と効率を向上させるための重要な貢献を果たしています。  また、今までのQA活動の経験から得た知識に加えて、JIRAを駆使することでタスクの管理と進捗の可視化を効果的に行うことができました。そして、今まできちんと取り組んだことがなかったISO/IEC 29119標準を用いたドキュメント作成を行い、運用に支障をきたさずに混乱もなくプロジェクトを遂行できたことも大きな学びとなりました。このようなツールと標準の活用が、開発の成功に寄与したと確信しています。 サービスリリース後、QAの役割は少しずつ変化していきました。各QAエンジニアは特定の領域を担当するようになり、Acceptance Criteriaの読み合わせを開始しました。 Acceptance Criteriaの読み合わせを行うことで、PM、デザイナー、エンジニア、QAの間で、Specレビュー(要件レビュー)よりもさらに詳細な議論が可能になります。また、UIのエンドツーエンド(E2E)テストの自動化にも取り組み始めています。 今後も、今回得た経験や学びを今後の開発に活かし、さらなる品質向上と効率化を目指して取り組んでいきたいと思います。 Links 連載:Mercari Hallo, world! -メルカリ ハロ 開発の裏側- メルカリではメンバーを大募集中です。メルカリ ハロの開発やメルカリに興味を持った方がいればぜひご応募お待ちしています。詳しくは以下のページをご覧ください。 Software Engineer, Frontend – Mercari/HR領域新規事業 (Mercari Hallo) Software Engineer, Backend – Mercari/HR領域新規事業 (Mercari Hallo) Software Engineer, iOS/Android (Flutter) – Mercari/HR領域新規事業 (Mercari Hallo) Software Engineer, Site Reliability – Mercari/HR領域新規事業 (Mercari Hallo) QA Engineer – Mercari/HR領域新規事業 (Mercari Hallo) Engineering Manager – Mercari/HR領域新規事業 (Mercari Hallo)
はじめに こんにちは。メルカリ ハロでSRE TLをしている @naka です。 連載:Mercari Hallo, world! -メルカリ ハロ 開発の裏側- の3回目を担当させていただきます。 この記事では、メルカリの新規事業立ち上げにおけるSREの働きや役割に関して、紹介します。 メルカリでは、Platform Engineeringが提供するツールや仕組みを活用して、サービスを立ち上げていきます。新規事業立ち上げのチームだけで、完結するわけではありません。今回は、Platform Engineering時代の新規サービス立ち上げにおけるSREの役割と具体的な動きを、メルカリ ハロを例に取り上げて紹介します。SREが、Platform Engineeringとプロダクト開発チームと一丸となって「All For One」に動いてきた取り組みが少しでも臨場感を持って伝えられればと思います。 Platform Engineering x メルカリShops爆速立ち上げの知見 具体的な活動について触れる前に、まずは全体の背景について説明します。 今回のメルカリ ハロの立ち上げは、ソウゾウ時代の爆速立ち上げの経験とメルカリグループ全体の技術スタックを最大限活用する挑戦でした。 ソウゾウの爆速立ち上げの成功に寄与した技術やプロセスを活かしつつ、そこでの学びを踏まえメルカリグループ全体の知見の蓄積である共通の技術スタックを取り入れることで、新規事業の立ち上げスピードを最大限上げました。 メルカリのPlatform Engineering メルカリでは、Platform Engineeringがプロダクト開発チームを支え、その成果を最大限引き出すための環境を提供しています。メルカリのPlatform Engineeringの詳細は、 こちら を御覧ください。 メルカリグループでは、Kubernetesを基盤に使っており、基本的にすべてのマイクロサービスはKubernetesクラスタ上で動いています。開発チームが、Kubernetesクラスタを簡単に使えるためのツールがPlatformチームによって整えられています。 メルカリ ハロ初期開発メンバー メルカリハロの初期メンバーは、 爆速で「あたらしい出会いを繋ぐ」を創った、メルカリ ハロのエンジニアリング でも紹介があったように、私を含め、ほとんどが株式会社ソウゾウに所属していました。ソウゾウでは、メルカリグループとは異なる技術スタックを採用しており、この知識や経験も活かしてメルカリハロの開発を進めました。ソウゾウで開発していたメルカリShopsの技術スタックに関しては こちら をご覧ください。 ソウゾウではアプリケーション開発寄りの技術スタックにMonorepo、Go、GraphQL、Ent、PostgreSQL、Nextjsなどが採用されており、高いレベルの開発者体験が実現できていました。一方で、基盤に関しては、メルカリグループ全体で用いられているものとはやや異なる技術スタックが採用されていたため、全体として得られた知見や経験を活かしにくいこと、また技術スタックの違いにより人員配置における障壁となっていたことが課題として認識されていました。 役割と活動 「インフラ、ネットワーク、セキュリティ周りはお願いします」と言われて始まったSREとしての役割や活動は、多岐にわたります。今回は、その中でもメルカリらしく、「All For One」や「Be a Pro」を体現している部分をいくつか紹介したいと思います。 アーキテクチャ設計 初期メンバーであり、現在Engineering Headである @napoli と密に連携を取りながら、まずはアーキテクチャ設計やツール選定をしました。 技術スタックの詳細は、 第2回のnapoliの記事 で取り上げられていますが、大まかには、メルカリの技術スタックにアラインしつつ、スピード重視で開発ができるような技術スタックを選択してきました。 その中でも特にインフラ構成やツール選定などに関してはSREも積極的にリードしてきました。 最初のアーキテクチャ決めでは、チーム外との定例ミーティングを開き、メルカリ ハロの要件をまとめてソリューションのPros/Consを議論したり、ミーティング後持ち帰り調査をして、最終的なアーキテクチャが決まるまで、複数部署と連携しながら、メルカリグループの技術スタックのキャッチアップに注力しました。 冒頭で書いたように、もともと主な初期メンバーはソウゾウメンバーだったため、メルカリグループで使われている最新の技術スタックにあまり詳しくない部分もあるので、高速のキャッチアップが必要でした。 ここで、大きな助けとなったのが組織的なサポート体制です。メルカリ ハロのプロジェクトは、グループ内でも優先度が高く設定されたので、各Platformチームから手厚いサポートを受けることができました。 また、Platform DX (Developer Experience) チームからメンバーが1名メルカリ ハロのプロジェクトにアサインされたので、毎週の1on1やSlack上で、素朴なPlatformへの疑問や今のメルカリ ハロでの課題などざっくばらんにディスカッションする機会を設けてもらい、毎週新しい学びを得ながら確実に進捗する事ができました。 また、具体的な要件に応じて、関連するチームのメンバーに声をかけて、ディスカッションしてアーキテクチャ設計をしました。Web Platform、Network、Architect、SRE、Platform DX、IDPなど、本当に多岐にわたりました。 メルカリ ハロのアーキテクチャ設計やインフラ構築を通して30人近くのメンバーと一緒に働くことができ、メルカリの「Be a Pro」なPlatform Engineeringの「All For One」のサポートを最大限活かせたと感じています。 環境構築 アーキテクチャ設計や技術スタックの決定と同時並行で、プロダクトの開発は進んでいました。 この時、スピーディな開発環境構築はメルカリ ハロチーム全体にとってとても重要でした。 「機能実装はないものの全体が動くようになっていきている」というのを、なるべく早い段階でチーム全体に共有することで、さらに前進しようとする強い気持ちを後押しするためです。 Platform Engineeringで提供されているツールの中身を理解しながら、1から環境を設定していきました。現在広く使われている自社製ツールの導入に加えて、Platform Engineeringで新しく開発している将来スタンダードになるであろう新ツールの導入も積極的に行いました。今後、グループ全体で移行するPlatformツールをEarly Adoptorとしていち早く採用することで、Platform側のサポートをより多く受けることができると同時に、Platform側にとっても実際のユースケースからのFeedbackを得ることができるので、メルカリ ハロの立ち上げにとっても、メルカリグループ全体にとっても大きな意味を持つ決定だったと思います。各所のサポートや協力を取り付けることができ、開発環境の構築を無事に完了することができました。 一方、メルカリ ハロでは、一部の技術スタックはメルカリグループでも実績が多くないものもあります。例えば、Cloud SQL for PostgreSQLは、メルカリグループでも使用されているケースはまだ稀です。 こういったケースでは、ソウゾウ時代の知見や、今回新たに時間を割いて検証した結果を用いて、より安全、且つシンプルで使いやすい設定やツールを選定しました。 具体的には、IAM DB 認証のデータベースユーザを採用し、DB userをパスワードなしで管理することで、よりセキュアな設定にしました。 また、DBのSchema 変更時に、意図しない変更の適用によるインシデントを未然に防ぐために、atlasという Database schema migration ツールを導入しました。 atlasの導入により、毎回Schema変更からSQL fileを生成し最終的にApplyされるSQLをReviewerがPR上で確認できるので、より安全にDB schema変更を行うことができるようになります。 ドキュメント整備 アーキテクチャ設計、環境構築、ツール選定などの際には、ドキュメントに経緯を残すことを大事にしてきました。 新規事業の立ち上げは少人数でスタートするのと、スピードを重視するために、最初の設計時に考えたことや設定した作業記録がドキュメントに残らなかったり、ドキュメントはあるが分散してしまっているために、あとから入ってきた人が背景を理解するのが難しいという課題に直面することがあると思います。 例えば、アーキテクチャを一つとっても、最初は全員が認識できるくらいのシンプルなものから始まる事が多く、特にドキュメントにしなくても全員が頭の中で同じものを描く事ができます。しかし、開発人数が一気に増え、開発のスピードもあがると、全体のアーキテクチャがどうなっているのか、詳細に関しても、当初なぜこの選択をしたのかを全員が把握することがとても難しくなります。 そこで、アーキテクチャ設計や開発環境構築の作業と同時並行で、Wikiの初期構成を考えたり、今後のドキュメントの構成の枠組みを作ったりしました。 SREとして何か作業が必要になったものに関しては、基本的にすべて手順を残すようにしたり、アーキテクチャ設計時の細かい議論やSlack上でのやりとりも、なるべくあとから入った人が経緯を知れるように、Referencesにリンクを集約したりと、将来の生産性への投資を初期段階から行ってきました。 あとからJoinしたSREのメンバーも過去にやってきたことにキャッチアップしやすかったと言ってもらえて、ドキュメント整備は最初からやって良かったと思います。 Production Readiness Check 最後に、もう一つ今回のメルカリ ハロのリリースに関して、SREが積極的にリードしてスムーズなリリースに関与した任務を紹介します。 メルカリでは新しいサービスをリリースする前には、事前にProduction Readiness Checkというチェックを通過する必要があります。(参考: Production readiness checklist used for Mercari and Merpay microservices ) このProduction Readiness Check (以下 PRC)の項目は、Applicationの実装上の要件、Kubernetes、Database、Storageなどの基盤の設定項目、セキュリティなど100以上の項目に上ります。 チェックの結果は、新しいサービスをリリースするために通過しなくてはいけないリリース判定の中の一つの項目の提出物として取り扱われています。つまり、PRCの項目がすべて完了していることが、本番環境構築完了の印になります。逆に言うと、PRCを完了していないとリリースができません。 PRCをすべて完了するためには様々なチームメンバーに協力してもらう必要があります。初期段階で専任のSREは一人しかいませんでしたが、途中からはバックエンドエンジニアと Marketplace事業のSREの方にも、兼務としてメルカリ ハロのSREの業務に加わってもらいました。 3名体制になったあとは各メンバーの担当項目を決め、担当者が各項目で関連するメンバーにアプローチしながら、同時並行で進めました。バックエンドエンジニアと兼務のメンバーはバックエンド周りの項目をリードし、Marketplace側のSREメンバーにはモニタリング周りの整備を進めていただき、Web Platform チームにはWeb周りのロードテストを行ってもらいました。 また、全体の進捗を確認するために、三人で定期的に進捗状況を共有し、具体的にやることが明確になっていない項目に関しては、一緒に議論しNext Actionを決めました。 全員で何が何でも完了するぞという強い気持ちと落ちているボールは気づいた人が拾うという精神で、リリース期日までにすべての項目を完了することにつながったと思っています。 三人一丸となって完遂したプロジェクトとして、ここでも「All For One」を強く感じることができました。 学びと課題 SREとして新規事業にゼロから携われたのは、個人としても学びが多く素晴らしい経験となりました。また、チームとしても、Platform Engineeringとプロダクト開発者の距離を近づけ、より早く価値をお客さまに届けることに貢献できたと思っています。 一方で、今回の立ち上げで課題も多く見つかりました。新しいサービス立ち上げのために、ゼロから環境構築を完了するまでに3ヶ月近くかかりました。Platformに対する理解のキャッチアップ、チーム内での要件の確定、他チームとの議論や意思決定など様々な不確実性の高い課題を突破していくために時間がかかってしまった部分や、Documentationの不足や複雑な手順など改善の余地がある程度明確な部分も多々ありました。 今後、メルカリ内で新規事業立ち上げの際にはもっとスピーディに立ち上げられるように、Platform全体の改善、Platform EngineeringへのFeedback、リリース前のプロセスの改善など新規事業立ち上げを経験した私達だからこそ、Platformチームと一緒に改善していきたいです。 リリース後、熱が冷めないうちに、次の新規事業のために既存のプロセスを改善している真っ最中です。 まとめ メルカリ ハロの爆速開発の裏側でどのようにSREが動いていたかを一部ではありますが、知っていただけたでしょうか。「新規サービス立ち上げ期にSREはこう動くべきだ」という明確な責務はないと思っています。だからこそ、柔軟に自分ができることなら何でもやるぞ!くらいのスタンスでプロジェクトに携わってきました。 リリース後の安定稼働を担保するために、リリース前の立ち上げ期にSREが様々な面でプロダクト開発に携わるのは、助走路として開発メンバーと同じ方向を向いて飛び立つことができ、とても有効だったと思っています。 今回は、メルカリグループ全体からのサポートがあってこそのメルカリ ハロのリリースだったと心から感じています。この環境に自分がいれたことにとても感謝しています。そしてその感謝の思いを存分にメルカリ ハロ、そしてメルカリグループ全体に還元していきたいです。 今回新しいサービスをリリースしただけではなく、メルカリグループとして今後もっとスピーディに新しいサービスを立ち上げていけるように組織全体を変えていく任務を担っていると感じています。 これからも、まだまだ改善したいところは山程あります。こんな熱いメルカリ ハロで一緒に働くSREメンバーを募集中です!!!!!! Links 連載:Mercari Hallo, world! -メルカリ ハロ 開発の裏側- メルカリではメンバーを大募集中です。メルカリ ハロの開発やメルカリに興味を持った方がいればぜひご応募お待ちしています。詳しくは以下のページをご覧ください。 Software Engineer, Frontend – Mercari/HR領域新規事業 (Mercari Hallo) Software Engineer, Backend – Mercari/HR領域新規事業 (Mercari Hallo) Software Engineer, iOS/Android (Flutter) – Mercari/HR領域新規事業 (Mercari Hallo) Software Engineer, Site Reliability – Mercari/HR領域新規事業 (Mercari Hallo) QA Engineer – Mercari/HR領域新規事業 (Mercari Hallo)
こんにちは、メルカリ Engineering Office チームの@yuki.tです。 メルカリでは「誰もが高い基準を志しながらお互いに成長できる組織」を目指し、メンバーが相互に学び合う仕組みや機会を大事にしています。 その仕組みの一つとして、社内技術研修「DevDojo」があります。 DevDojoでは、社内の有志によってメルカリで使用されている技術に関する研修を新卒エンジニアの入社タイミングに合わせて毎年提供しています。 そしてDevDojoの一部コンテンツは Learning materials Website で外部公開しています。 今年も4月に様々な研修が提供されました。このブログでは今年の研修の一部をご紹介します。 あらたに提供を開始したコンテンツもありますので、ぜひご覧ください。 技術研修DevDojoとは 新卒エンジニアのオンボーディングは、ビジネスマナーなどの働くうえで必要なことを学ぶ共通研修と、開発に関する技術的なことを学ぶ研修の2つで構成されています。 新卒研修の全体像については こちらのブログ でも紹介されています。 メルカリでは新卒オンボーディングのうちの技術研修をDevDojoと呼んでいます。これは技術開発を学ぶ場として「Development」と「Dojo(道場)」をかけ合わせて名付けられた、完全In-houseの社内研修シリーズです。 DevDojoでは、メルカリ・メルペイのエンジニアが講師として、社内で使用されている技術についてトレーニングやオンボーディングを提供しており、新卒エンジニアは、自分の配属や技術領域に関わらず、プロダクトに関する技術を幅広く学ぶことができます。 メルカリでは、プロダクトに情熱を持って改善するためには、自分の技術領域だけでなくプロダクト全体の理解が必要という考えから、各自の技術領域に限定せずに研修を受講してもらい、研修の実施には組織全体で優先度高く取り組んでいます。 また、研修は社内のメンバーであれば誰でも受講できるようにオープンにしており、こちらも技術領域や職務に関わらず興味のある内容に参加できます。 公開コンテンツはこちら Learning materials Website では、DevDojoで提供されている研修から一部のセッションを公開しています。 今年は新しいテーマのセッションが2つ追加されました。 どちらも、新しくエンジニアとしてのキャリアを歩み始めたメンバーにとって、大事にしてほしい視点や考え方に関する内容となっています。 そのほかの公開セッションもアップデートされています。 メルカリのエンジニアリング組織は、半数以上が海外籍社員のため、いくつかのセッションは英語で提供されています。研修には同時通訳が入り、語学のサポートをしています。 こちらが今年のメルカリ、メルペイの研修コンテンツです! Problem Solving ソフトウェアエンジニアリングを純粋な問題解決として考え、問題の認識から解決までをステップに分け、過去のプロジェクトを参考にしながら解説します。 DevDojoシリーズで初となる、 Principal Engineer によるコンテンツです。 Slide Ship Code Faster 様々なTech Companyで使用されている生産性指標を取り上げ、開発開始から機能リリースまでの時間を短縮するための開発およびエンジニアリングの実践方法について説明します。キャリアをスタートしたばかりのエンジニア向けに、キャリアの進展に関する具体的なステップも提供します。 Slide英語 Mercari Design Doc プロダクト開発に必要なDesign Docの基礎知識を解説します。また、良いDesign Docの書き方やメルカリでDesign Docをどのように使っているかについても説明しています。 Slide英語 Mercari Quality Assurance 安心安全に早い開発サイクルでサービスを持続的に提供していくためには、Quality Assuaranceは非常に大切です。メルカリでどのようなQAのプロセス、ツール、テクニックを使って問題を迅速に特定し、解決しているのかを解説します。 Slide英語 Merpay Quality Assurance メルペイでのQuality Assuaranceの考え方と重要性、そしてQAプロセスとして、開発プロセスのなかでのQAエンジニアの関わり方を解説します。QAエンジニアだけでなく、開発に関わる全員が品質について注意をはらうための取り組みも紹介します。 Slide日本語 / Slide英語 Mercari Incident Management メルカリにおけるインシデントマネジメントとそのベストプラクティスを紹介します。「インシデント前、インシデント中、インシデント後」の3つのフェーズを含む、インシデントジャーニーを説明します。また、インシデントレビューをどのように行い、レトロスペクティブの質を高めているのかについても取り上げています。 Slide英語 [Basic] Machine Learning メルカリではAIを使い、メルカリAIアシストなどユニークな機能を提供しています。このコンテンツでは、一般的な機械学習の考え方や、AI・MLの基礎知識について解説しています。また、メルカリでは実際にMLをどう実装しているのか、実際のプロジェクトについても紹介しています。 Slide英語 Mercari Mobile Development より使いやすいサービスを迅速に提供していくため、メルカリのモバイル開発はリリースサイクルや運用プロセスのルール化を行っています。メルカリのモバイルアプリ開発において実際に運用している開発サイクルとプロセスについて解説します。 Slide英語 Mercari Design System for Mobile 持続的に一貫したサービス体験をお客さまに提供できるよう、メルカリではDesign Systemにとても力を入れています。このコンテンツでは、モバイルにおけるDesign Systemの基礎知識から、メルカリで実際に行っているデザインの作り方、運用方法について解説します。 Slide英語 Auth Platform Onboarding メルカリグループが管理しているサービス間で安全に通信を行うために、認証と認可は切り離せません。本セッションでは、この認証基盤の基礎として、アクセストークンの役割や利用方法等について紹介します。 Slide英語 最後に メルカリでは「 Trust & Openness 」と「 Open Organization 」の企業文化に基づき、オープンなコラボレーションを奨励しています。 この考えのもと、新卒エンジニアには社内の有志エンジニアによってトレーニングやオンボーディングが提供されており、社内だけでなく社外にも組織や技術の情報を共有することで、業界全体へ貢献することを目指して研修コンテンツを公開しています。 今年は2つの新しいテーマのセッションを追加して公開することができましたが、研修の実施と公開には、多くのエンジニアの方々、チームメンバー、関係チームが協力し取り組んでいます。 今後も引き続き、DevDojoシリーズのアップデートを行い、公開していきます。 最後に、メルカリグループでは、積極的にエンジニアを採用しています。ご興味ある方、ぜひご連絡お待ちしております! Open position – Engineering at Mercari
こんにちは。メルカリ ハロのSoftware Engineer (Engineering Head)の @napoli です。 連載:Mercari Hallo, world! -メルカリ ハロ 開発の裏側- の2回目を担当させていただきます。 2024年3月上旬にメルカリハロという新しいサービスが公開されました。メルカリ ハロは好きな時間に最短1時間から働ける「空き時間おしごとアプリ」です。 この記事ではメルカリ ハロを作るにあたり、どういった技術スタックやアーキテクチャを選定したのか、さらにその背景と意思決定をご紹介したいと思います。 この記事で得られること メルカリ ハロで採用されている技術スタックやアーキテクチャの全体像 その意思決定の理由とプロセス これから新規サービスを立ち上げるうえでのヒント 主な技術スタック メルカリ ハロで利用されている主な技術スタックは以下のとおりです。 バックエンド Go Google Cloud Platform (GKE, Cloud SQL for PostgreSQLなど) GraphQL gqlgen ent. フロントエンド React / TypeScript Next.js Apollo Client (React) モバイルアプリ (メルカリ ハロ専用アプリ) Flutter / Dart また、バックエンドのアーキテクチャとしてはモジュラーモノリスを、リポジトリの管理方法としてはmonorepoを採用しています。 モジュラーモノリス (Modular monolith) メルカリグループとして「スポットワーク領域」と呼ばれる領域に参入するため、2023年4月頃に新しいチームを立ち上げました。発足当初はあくまで「PoC (Proof of Concept)」という立ち位置で、この領域でメルカリならではの価値を提供できるかどうか、その検証をしてからサービスを成長させていくという戦略で進められたため、サービスを少ない人数で急速に立ち上げることが求められました。(最初期はエンジニアが1~2名ほどしかいませんでした) こういった背景を踏まえ、バックエンド(サーバ)はモジュラーモノリスのアプローチを採用しました。メルカリグループの主力サービスであるフリマアプリ「メルカリ」は成長過程でモノリスからマイクロサービスに進化してきました。モジュラーモノリスはこれら2つのアプローチの中間に位置する戦略で、端的に言うとモノリスシステムの中にマイクロサービスの戦略を統合する考え方です。結果的に、この選択は正解だったと思います。 容易なサービス間連携 モジュラーモノリスではひとつのサーバにAPIサーバとして求められるすべての機能を含めます。すべての機能は同じプログラムで動いていますが、サーバの中では実際には「モジュール」という単位で機能は独立しています。そして各モジュールが連携することでAPIとしての機能を提供しています。(「モジュール」はメルカリ ハロでは実際には「サービス」と呼んでいます) ここでいうひとつとは「サーバプログラムとしてひとつ」という意味です(「デプロイの単位としてひとつ」とも言えます)。ひとつのサーバにすべてのサービスが実装されているため、当然ながらサービス間のRPC (Remote Procedure Call)は不要です。ひとつのAPIを提供するために複数のRPCを利用する可能性のあるマイクロサービス アーキテクチャとは異なり、プログラム内で関数を呼ぶことで機能を完結させることができます。サービス間のプロトコル定義やネットワークエラー時のハンドリングなどを考慮する必要がなくなり、実装量や設計の難易度を大幅に減らすことができます。 単一のデータベースとトランザクション バックエンドがモジュラーモノリスであることに加え、メルカリ ハロではメインとなるデータベースのインスタンスはひとつです。この構成の場合、データベースのトランザクション機能をフル活用することができます。メルカリ ハロにおいてもお給料に関する情報など、データの整合性が重要なケースがあります。その点でデータベースのトランザクション機構はやはり非常に強力で、マイクロサービスアーキテクチャで大きな悩みごとであったサービス間のデータ不整合の問題の大部分を気にしなくて良くなります。こちらも実装量と設計の難易度を大いに減らしてくれました。 少ないインフラの記述量 メルカリ ハロではIaaSとしてTerraformを採用しています。モジュラーモノリスは基本的に1つのサーバで運用するためマイクロサービスと比較してインフラ関連の記述量を減らすことができます。APIなどアプリケーションに専門性を持つエンジニアにとってはインフラの設定やその動作確認は思ったよりも時間が掛かることが多いと思います。少ない記述量でAPIの実装に集中できることはメルカリハロのクイックな立ち上げにおいて大きなメリットがありました。 気をつけるべきこと モジュラーモノリスはメルカリ ハロにおいて良い選択肢だった一方で、気をつけるべきこともあります。 大きな懸念のひとつは初期設計の難易度が上がりやすいという点です。何も考えずに作ってしまうと、単なるモノリスなシステムになってしまう可能性が大いにあります。正確に言うとモノリスであること自体が問題なのではなく、システムのモジュールやサービスが適切な責任範囲で分離されていないことが大きな問題になり得ます。適切な分離が行われていないシステムでは機能の再利用が難しかったり、一部の改修が思ってもみないところに影響を及ぼしたりします。複雑に相互依存した(絡み合った)システムは理解も難しいですし、テストも難しくなります。理解もテストも難しいということは障害が発生する可能性も高くなるということです。結果として時間が経てば経つほどスピーディな機能開発が困難になってきます。 マイクロサービスアーキテクチャの利点の一つは、この「モジュール/サービスの分離」がインフラレベルで「強制される」点だと思います。プログラムの単位が違うことに加え、データベースも一般的にはマイクロサービスごとに独立していることが多いため、あるサービスの変更は別のサービスには直接影響を与えません。もちろんどの粒度でマイクロサービスを分けるかにも依りますが、開発者は半ば強制的に「モジュール/サービスの適切な単位」を考えなければいけません。サービスとして独立しているため、責任範囲が明確にもなりやすいです。大規模組織にも相性が良く、例えば「このマイクロサービスはこのチームがオーナーを持つ」と言った戦略を取りやすくなります。 一方で、モジュラーモノリスではこの「強制」が良くも悪くもありません。ですが「モジュール/サービスの適切な分離」はマイクロサービスと変わらず重要な関心事です。モジュラーモノリスでは初期の設計者が慎重に設計を行い、各開発者が節度と理解を持って機能を実装していく必要があります。この点に難しさがあると思っています。 とはいえ、メルカリ ハロのように一定規模の新規プロダクトをクイックに開発するうえではモジュラーモノリスはおすすめできるアプローチだと思います。最初から大規模になることが約束されたプロダクトを作るのであればマイクロサービスアーキテクチャのような分散システムを採用するのも効果的だと思いますが、ほとんどの場合、システムを物理的に分離させるのはビジネス的にもプロダクトの規模が大きくなってからでも遅くはないでしょう。 なお、メルカリの別プロジェクトにおいてもモジュラーモノリスの採用例があります。メルカリ ハロの事例とは異なる「既存のモノリスからモジュラーモノリスへ移行する」というアプローチですが、こちらも参考にして頂ければと思います。 メルカリの取引ドメインにおけるモジュラーモノリス化の取り組み monorepo メルカリハロではmonorepoを採用しています。monorepoは、バックエンドやフロントエンドなど、システムを構成する複数のコンポーネントの独立性を保ちつつ、全てのコンポーネントをひとつのリポジトリで管理する手法です。 monorepoを採用してよかったと思う点をいくつか挙げてみます。 システム全体の見通しの良さ monorepoはシステムに必要なコンポーネントが全てひとつのリポジトリに集約されていることが大きな特徴です。これはシステム全体の見通しがとても良くなります。メルカリ ハロの立ち上げ期は開発メンバーの数が少なかったため、ひとりのエンジニアがバックエンドやフロントエンド、モバイルアプリを横断して実装することもありました。その際にコードが集約されていることは開発のしやすさに大きなアドバンテージがあります。「フロントエンドの仕様どうなっているんだろう?」といった確認を行いときに、自身のIDEやEditorのファイル検索機能を使えばすぐ該当の実装に辿り着きます。「別リポジトリに切り替えて、git pullして、ウインドウを切り替えて…」といったことをする必要がありません。言語の違いによる理解の難しさは当然ありますが、素早くシステム全体を調査することができます。 コードレビューのやりやすさ ひとつのGitHubリポジトリ上にプルリクエストが集まるため、異なる職種間でもレビューがしやすくなります。専門的な実装に関しては専門の知識を持つメンバーがレビューしたほうが良いですが、簡単な修正なら他の職種でも可能なことが少なくありません。メルカリ ハロではmain branchへのマージはプルリクエストへのApproveを必須としているので、レビューの速さは重要です。mainにマージできるまでの時間が短いとコンフリクト解消に掛ける時間も減り、QAもしやすく、本質的な作業に集中しやすくなります。もちろん複数レポジトリ(Multi Repository)でもできないことはないですが、monorepoのほうがやりやすいのは間違いないと思います。 GraphQLスキーマファイルの共有 メルカリハロではバックエンドとフロントエンド/モバイルアプリとの通信に(後述する)GraphQLを採用しています。バックエンド側で生成したGraphQLのスキーマを同一のリポジトリで共有できるため、それをもとにフロントエンドのGraphQLのクライアントコードを自動生成したりなど、楽に連携をすることができました。GraphQLスキーマファイルに限らず「必要なファイルをリモートから取得する必要がない」というのは何かと便利ですし、開発環境が安定します。 一緒に開発している感 急にふんわりとした話になってしまいますが、バックエンドやフロントエンドといった職種間でもなんとなく「一緒に開発している感」が出るような気がします。「システム全体の見通しの良さ」にも繋がる話ですが、他の職種の人達がどういった頻度、温度感で日々作業しているかがわかりやすくなります。この感覚は密にコミュニケーションが必要な開発では意外と重要だと思います。目には見えなく数値化しづらいですが、メルカリ ハロの開発体制では良い効果をもたらしていたと思います。 monorepoのリポジトリ構成 メルカリ ハロではGo, dart, TypeScriptが主な言語として使われており、リポジトリルートの直下に各言語ごとにディレクトリを配置しました。これによりエコシステムやCI/CDの管理をしやすくなります。また普段の開発においても、例えばバックエンドを開発する人間は基本的にgoディレクトリ以下のみに絞って作業することができ、同じリポジトリでも独立した環境のように開発できるメリットがあると思います。 独立したビルド環境 メルカリ ハロのmonorepoではバックエンドやフロントエンドなどの各コンポーネントでのビルドの手段は基本的に独立しています。 Bazel のようなビルドを一元的に管理できるツールもありますが、メルカリ ハロでは採用していません。幸いなことにメルカリ ハロの立ち上げ期には各職種で専門性の高いメンバーが居たため、馴染みのある(標準的な)ビルドの手法を採用していました。各メンバーにとっては追加の技術を学習するコストがない分、スムーズにビルド環境を構築できたと思います。運用面でも今のところ大きく困ったことはありません。コンポーネント間でビルドを一元的に管理する手法はメリットもありつつ、かなりの難しさもあるため、明確な理由がなければ各コンポーネントごとに独立してビルド環境を構築するアプローチのほうがおすすめできるかなと思います。 — monorepoについて、いくつかのメリットやメルカリ ハロでの具体的な構成例を挙げました。monorepoの一般的なデメリットはリポジトリサイズが大きくなりやすいところですが、昨今のネットワーク環境やローカル環境を踏まえると相当に大規模なサービスにならない限りほぼ気にすることは無いかなと思います。他にも細かいデメリットはありますが、総じてメリットのほうが大きく上回っていると感じます。新規サービスの立ち上げにおいてはおすすめできるアプローチだと思います。 インフラの全体像 メルカリ ハロではインフラストラクチャにGoogle Cloud Platformを全面的に採用しており、簡単な全体像は以下のようになっています。 バックエンドとなるGraphQLサーバ(Go)はGoogle Kubernetes Engine (GKE)を利用してひとつのサーバとして動いています。同じくNext.jsと、APIの前段となるGateweyもGKE上で動いてます。データベースはCloud SQL for PostgreSQL、メモリストアにはRedisを利用しており、CDNはFastly、 画像最適化(変換)サービスにはCloudflareを採用しています。 Google Kubernetes Engine (GKE) メルカリ ハロではバックエンドのインフラにGoogle Kubernetes Engine (GKE)を採用しています。主に下記の2つの理由で選定を行いました。 メルカリグループでの実績とノウハウ メルカリグループでは多くのサービスをGKEにて運用しており、Platformと呼ばれるチームが運用・保守を行っています。一般的にGKE(kubernetes)はインフラを専門としないエンジニアにとっては難解なことも多いですが、メルカリグループでは初期設定や開発を効率的に行うためのツールやドキュメント、ベストプラクティスが充実しており、Platformチームからの手厚いサポートを受けることもできるため、この点において困ることは比較的少なかったと思います。 エコシステムとの統合 メルカリグループの多くのサービスがGKEを採用しており、同じクラスタ内でgRPCによるサービス間通信を行っています。これにより、既存のサービスをセキュアかつ効率的に利用することが可能です。メルカリ ハロはいくつか既存のメルカリのマイクロサービスを利用する必要があったため、簡単かつセキュアにサービスを利用できることには大きなアドバンテージがありました。 他の選択肢 上記のとおり、メルカリ ハロではグループ内のサポートが充実していたことと、既存のエコシステムとの連携が重要だったためGKEを選択しました。ただ、やはり構築と運用において難易度の高さはあるため、ゼロから独立したサービスを作る場合や、専門のエンジニアが居ない場合はCloud Runのような構築や運用が簡単なServerless環境も選択肢としては大いにありだと思います。 バックエンド / Go バックエンドの実装はGoを採用しています。メルカリグループでの標準的な言語であり、メルカリ内で開発を行う上ではノウハウやリソースアロケーションの面で多言語と比べ圧倒的な優位性があります。また、API開発に適した言語であり、実行速度が速く、Go routineによる並列処理は非常に強力です。 シンプルで読みやすいことも良い点で、そのシンプルさゆえに誰が書いても同じようなコードになります。これは多人数開発ではメリットが大きく、実装の理解やコードレビューの負担を大きく下げてくれます。コードに問題がある場合も比較的気付きやすいと思います。 複雑なコードは、たとえ自分自身が書いたコードでも何を意図してそうしたのか、時間が経つとすぐ分かりづらくなります。そういった意味では「書き手より読み手にやさしい」言語かもしれません。読み手に優しいことは長期的にサービスを運営するうえで大きなアドバンテージになります。個人的にもGoは好きな言語なので、仮にメルカリ以外でAPIを開発するとなっても当分は最有力候補になると思います。 Cloud SQL for PostgreSQL / ent. / atlas DatabaseにはCloud SQL for PostgreSQLを採用しています。メルカリグループではGoogle CloudのSpannerを採用するケースが多いのですが、以下の理由でCloud SQL for PostgreSQLを採用しました。 学習コストの低さ PostgreSQLのようなRDBMSの知識や経験を持つエンジニアは多く、そういったエンジニアとっては新たに学習すべきことは少ないです。それはつまり新しいメンバーが開発に入りやすく、即戦力になりやすいということに繋がります。 充実したエコシステム PostgreSQLは歴史が長く、サードパーティ製のツールやライブラリが豊富です。ツールやライブラリが豊富であることは効率的な開発に繋がりやすく、小さくないアドバンテージとなります。高機能なGUIツールも提供されているため、データを直接調整しながらデバッグを行いたいときなどに非常に役に立ちます。 ポータビリティ性 PostgreSQLはDockerイメージとして提供されており、ローカルのDocker上で簡単に動かすことができます。そのためデータベースを利用するユニットテストもやりやすくなりますし、ローカルでもリモートの開発サーバと近い環境を構築しやすくなります。 メルカリ ハロのサービス特性 メルカリ ハロのサービスの特性上、Readが多く、Writeは比較的少ないです。そのため単一のインスタンスでも相当な期間、問題なくWriteを捌けるだろうと判断しました。ReadにおいてはRead replicaを必要に応じて増やしていくことでかなりのトラフィックを捌けるだろうと考えています。 — 一般的にはこれらに加えて「初期コストの低さ」もメリットになりうると思います。メルカリ ハロでは当初から一定以上の規模のお客さまを想定していたためあまり判断の基準にはなりませんでしたが、多くの新規サービスにとっては重要な観点ではないでしょうか。 ORM ORM(Object-Relational Mapping)としては ent. を採用しています。Go言語向けの強力なORMフレームワークであり、メルカリグループ内でもいくつか採用事例があることから採用しました。コードファーストのアプローチを取っており、高度なクエリ生成機能もあるので、効率的にGoからDatabaseの操作を行えていると感じます。 一般的にはORMを採用すると最適化された柔軟なクエリを書きづらくなりますが、メルカリ ハロでは基本的に非常にシンプルなクエリの組み合わせでAPIを実現しています。その分クエリの発行数が冗長になることもありますが、一方で「理解しやすく、実装しやすい」という大きなメリットがあります。冗長なクエリが多いと心配なのはパフォーマンスですが、基本的にReadの処理はRead replicaを増やすことでスケールしますし、アクセスの頻度が相当多いAPIで無い限り、インデックスさえ適切に貼られていればクエリの数を多少増やしても問題になることはほぼありません。シンプルなクエリは正しくインデックスを貼るのも楽です。実際においても、いまのところメルカリ ハロはデータベースのパフォーマンスは大きな問題にはなっていません。 データベース マイグレーション データベースのマイグレーションには atlas を採用しています。ent.もauto migrationの機能を持っており、差分のDDLを自動で適用してくれたりしますが、いざサービスの運用が始まるとent.のauto migrationだけでは要件を満たさないケースが多く、基本的にはatlasによる管理に統一しています(本番環境においてent.のauto migrationはOffにしています)。atlasはent.と連動してスキーマの差分を自動的に生成してくれるなど、強力な機能を持っており、効率的にmigration作業を行うことができます。DMLにおいてもatlasを使って一部migrationしています。 GraphQL モバイルアプリ含むフロントエンドとバックエンドとの通信にはGraphQLを採用しています。GraphQLはAPI開発においてモダンな選択肢のひとつであり、世の中の多くのサービスでも採用されています。フロントエンド側でフェッチするデータを動的に制御することができ、フロントエンドの仕様が変更になった際もバックエンド側の修正が不要になるケースもあります。クエリをネストすることができるため、フロントエンドはその画面において必要な情報の多くをひとつのAPI Callで取得することができ、不要なAPI Callを減らすことができます。また、静的な型システムを持つスキーマによってI/Fが定義されるため、バックエンドとフロントエンド間で厳密なデータのやりとりすることができます。IDE/Editorによる補完が効きやすいところも嬉しいポイントです。 gqlgen バックエンド側ではGoのGraphQLサーバ実装のひとつである gqlgen を採用しています。シンプルかつ必要十分な機能が揃っており、学習コストも低く使いやすいと感じます。 gqlgenはスキーマファーストのフレームワークであり、基本的にひとつのスキーマファイルでQuery/Mutationを定義するスタイルのため、メルカリ ハロでもひとつのSchemaファイルにすべてのQuery/Mutationが集約されています。そのため現時点でもかなり行数の多いファイルになっており若干の扱い辛さを感じるときもありますが、graphql-eslintを導入してファイルを自動整形したりアルファベット順にType/Query/Mutationを自動ソートするなどして、できるだけメンテナンス性が落ちないように工夫しています。 一方で、スキーマがひとつのファイルに集約されているメリットも多いと思います。見通しが良く、コード自動生成もしやすく、他チームにメルカリ ハロが持つAPIを紹介する際も「このスキーマファイルを見てください」と言えば済むこともあります。 シンプルで扱いやすいPlaygroundが提供されている点も大きいと思います。Playgroundを使うと実装したGraphQLのQuery/Mutationを実際に試すことができます。入力の補完が効いたりQuery/MutationのAPI Reference(Document)も自動で生成してくれます。これが非常に快適で、デバッグや動作確認でとても役立っています。gqlgenでは複雑な設定もなく簡単にPlaygroundを構築することができます。 一方で、gqlgenに限らずですが、GraphQLサーバ実装においてはいわゆるN+1問題に気をつける必要があります。この点においてはRESTなど比べて学習コストと実装コストはやや増えますが、GraphQLを採用する上でそこまで大きなデメリットにはならないかなと思います。対処法としてはdataloaderの採用が一般的で、メルカリ ハロでは graph-gophers/dataloader を採用しています。 なお、メルカリグループで広く採用されているプロトコルにProtocol Buffers(gRPC)があり、こちらも優れた機能を持っています。ただ一般的なWebサービスを作るうえでは、フロントエンドとバックエンド間の通信プロトコルとしてはGraphQLのほうが総合的に扱いやすいのかなと感じます。(もちろんどういったサービスを作るかにも依りますが) あとはRESTも候補になり得ますが、今の時代、明確な理由がない限り敢えてそれを選択するメリットは少ないかなと思います。 React / TypeScript / Next.js メルカリ ハロではWebベースのフロントエンドも実装されています。メルカリアプリ内の「はたらく」タブはWebViewで提供されており、事業者様向けの「事業者管理画面」もPC向けにWebベースで提供されています。 メルカリアプリ内「はたらくタブ」と事業者管理画面 Reactの採用はすぐに決まりました。メルカリグループの他のプロジェクトで採用されているという点で、Vueも候補に挙がりましたが、初期の開発メンバーがReactに慣れていたことと、作りたいサービスに対して十分な機能を備えており、効率的に開発を進められるだろうと判断して採用しました。業界的なトレンド、人材のプールという意味でもReactにアドバンテージがあるだろうと判断しました。 TypeScriptについても迷うことはありませんでした。フロントエンドにおいても昨今の開発では静的型付けは必須と言って良いですし、開発を効率的に進めていく上でさまざまなメリットがあります。Javascriptと比較して若干の難しさはあるかもしれませんが、いまや情報も豊富ですし、一定以上の知識や経験がある開発チームにおいてはほぼ問題にならないでしょう。 Next.jsはメルカリグループでの利用実績や使いやすさ、Reactをベースとしていること、パフォーマンスの良さなどから採用を決めました。 「はたらく」タブに関しては、メルカリアプリとして高いレベルの体験の良さが求められるので、必然的に描画速度も求められます。いまはまだそこまでフル活用されていませんが、Next.jsはパフォーマンス向上のための設定が柔軟にできるため、今後必要に応じて積極的に活用していきたいと思います。 GraphQLのClientとしてはApollo Clientを採用しています。Web フロントエンドにおける人気のフレームワークのひとつで、優れた機能を豊富に持っており、効率的に開発を進めることができます。社内でも採用実績があったため今回も採用しました。Reactとの統合にはReact Hooksを利用しています。 Flutter / Dart メルカリ ハロはメルカリアプリ内のサービスだけでなく、iOS/Android向けの独立したモバイルアプリも提供しています(ストアで「メルカリ ハロ」と検索してみてください)。 メルカリとは独立した「メルカリ ハロ」アプリ その基盤としてFlutter / Dartを採用しています。開発初期の検討時点では他にも以下の選択肢がありました。 iOS / Androidネイティブ (Swift/Kotlin) React Native WebViewベースのアプリ モバイルアプリの技術選定にはWebフロントエンドに比べかなり時間がかかりました。それぞれ同じくらいメリット/デメリットがあり、特に1. 2.についてはメンバーや立場によって意見が様々で、メルカリ ハロのチームだけでなく、メルカリグループ横断的に議論を重ねる必要があったため、決断が難しかったです。主に以下のような点が論点となりました。 開発コスト メンバーの習熟度 パフォーマンス 社内的なリソースアロケーション サードパーティ製のライブラリを含む、エコシステムの充実度 採用のしやすさ メルカリグループとしてのノウハウの集約/分散 色んな論点はありましたが、その中でもやはり「開発コスト」はとても大きな関心事でした。開発初期のチームのメンバーは少なかった一方で、やはり昨今の市場状況を踏まえてクイックなリリースが求められていました。iOS/Android両方をネイティブで開発するとなると単純に考えて2倍近くのコストが掛かりますし、両プラットフォームを同じタイミングでリリースできるとも限りません。チームとしては独立した専用のモバイルアプリの提供と、iOS/Android同時リリースはなんとしても達成したかったため、ネイティブでの開発はスケジュール面でのリスクが大きいと感じていました。 一方で(メルカリ ハロアプリではなく)メルカリアプリはiOS/Androidネイティブで実装されています。リソースアロケーションという意味ではメルカリグループ内では圧倒的な優位性がありました。社外にも当然ながら開発できる人は多いです。しかし前述のとおり初期メンバーは数が少なく、当時は様々な事情により他のチームからメンバーを確保できる保証もありませんでした。(なお、US版のメルカリはReact Nativeで実装されており、こちらもノウハウの面で優位性がありました) パフォーマンスについて懸念する意見もでました。この点についてはiOS/Androidネイティブが一番優れていることに議論の余地はありません。「Flutterのようなクロスプラットフォームで作っても結局ネイティブで作り直す必要があるのではないか」といった指摘もありましたが、現時点では最高のパフォーマンスを追求するよりもまずはリーズナブルにサービスを立ち上げ、お客様に利用してもらうことが何より大事だと判断しました。幸いにもサービスの特性上、iOS/Androidのパフォーマンスをフルに求められるケースは今のところ多くありません。なお、「メルカリアプリ」もサービス開始してから4年ほど経った頃にフルスクラッチでアプリを再開発しています。まずはサービスがそこまで軌道に乗ることが大事ですし、数年後にやってくるかもしれない再開発のタイミングで必要があればiOS/Androidネイティブに切り替えるでも良いだろうという判断になりました。 最終的にはこれらの論点を総合的に踏まえ、Flutterがメルカリ ハロにとっては一番マッチしそうという判断をしました。 メルカリ ハロというサービスだけでなく、メルカリグループとしてみたときにこの判断が本当に正解だったかは今も分かりません。ただ現状から考えると開発コストやパフォーマンスなど、総合的にバランスの取れた開発環境になっていると感じるので、妥当な決断だったのかなと思っています。 おわりに メルカリ ハロで使われている技術スタックやアーキテクチャ、その意思決定に至るプロセスについて、ほんの一部ではありますが簡単にご紹介しました。 新規サービスを立ち上げるうえで技術選定は非常に難しいことだと思います。ビジネスを成功させるために様々な観点から意思決定を行う必要があります。さらに一度決めたものをあとから変更するのは現実的に不可能なことがほとんどであるため、責任は重大です。ただ、同時に多くのエンジニアにとって「楽しく、やりがいのある瞬間」でもあるはずです。 会社の規模やそれぞれの状況によって判断の基準が変わってくるため正解はありませんが、メルカリ ハロではこのように技術選定してきました。これからサービスを立ち上げるみなさんにとって少しでも参考になれば幸いです。 Links 連載:Mercari Hallo, world! -メルカリ ハロ 開発の裏側- メルカリではメンバーを大募集中です。メルカリ ハロの開発やメルカリに興味を持った方がいればぜひご応募お待ちしています。詳しくは以下のページをご覧ください。 Software Engineer, Frontend – Mercari/HR領域新規事業 (Mercari Hallo) Software Engineer, Backend – Mercari/HR領域新規事業 (Mercari Hallo) Software Engineer, iOS/Android (Flutter) – Mercari/HR領域新規事業 (Mercari Hallo) Software Engineer, Site Reliability – Mercari/HR領域新規事業 (Mercari Hallo) QA Engineer – Mercari/HR領域新規事業 (Mercari Hallo)
こんにちは。メルカリのVPoE Workの @godriccao です。『 連載:Mercari Hallo, world! -メルカリ ハロ 開発の裏側- 』の1回目を担当させていただきます。スピードがクリティカルであるメルカリ ハロの展開を支えるエンジニアリングを紹介いたします。 実現したい世界は「新しい出会いを繋ぎ、信頼と機会をひろげる」 メルカリのミッションは「あらゆる価値を循環させ、あらゆる人の可能性を広げる」です。フリマアプリ「メルカリ」でのモノの循環に始まり、これまで「お金」「信用」「暗号資産」を循環させてきました。 今年3月には空き時間おしごとサービス「メルカリ ハロ」を一都三県で提供を開始し、わずか1ヶ月で登録者数250万人に到達、 4月16日より全国展開 しました。いつもお使いのメルカリに「はたらく」タブが追加されると共に、「働く」機能に特化した専用アプリもリリースし、「時間・スキル」を循環の輪に追加しました。 メルカリ ハロのミッションは「新しい出会いを繋ぎ、信頼と機会をひろげる」です。スポットワークの新しい働き方で、「人、場所、おしごと」と新しい出会いを作り、社会課題を解決しながら、新しい価値の循環を広げたいと考えています。 とにかくスピードが重要 スポットワーク市場はネットワーク効果が働いています。おしごとを探す側とおしごとを提供する側、数が多ければ多いほど需給のマッチング効率が上がり、おしごと成立のチャンスが増えます。 またスポットワーク市場は発展途上で、すでに一定のプレイヤーが存在しています。この市場では、ネットワーク効果の強さによって、初めて成長できるプレイヤーが勝つことができます。だからこそ、スピードが最も重要です。 新しいサービスを展開するとき、完璧に作ることよりも、お客さまの声を聞きながら高速にイテレーションするほうが大切です。 爆速開発スピードと品質を両立したメルカリ ハロのエンジニアリング メルカリ ハロのサービス開発に着手したのは2023年の4月からです。2023年10月までは極少人数でサービスの基礎をしっかり作り、10月からチームの人数を一気に増やしました。その後、「メルカリ ハロ」アプリ、事業者管理画面、カスタマサポートツールの機能をさせると同時に、フリマアプリ「メルカリ」の6つ目のタブである「 はたらくタブ 」も開発を鋭意に進めました。2024年3月6日には、上記のすべてのコンポーネントがメルカリ ハロサービスのローンチとともにリリースされ、更に多くの機能を追加し、4月16日に全国展開しました。 2200万超のMAUを持つフリマアプリ「メルカリ」のお客さま基盤をメルカリ ハロで活用するためには、機能面と性能面で一定以上の品質を担保することが必要です。では、なぜ爆速なリリースを実現しながらも品質を担保できたのでしょうか。 メルカリグループの All For One な総力戦 メルカリ ハロの成功は、メルカリグループ全体の総力戦によるものです。各チームの協力がなければ、短期間で高品質なサービスを提供することはできませんでした。具体的には、以下のチームが重要な役割を果たしました。 Architectチーム :開発初期のアーキテクチャー選定とリリース時の品質保証(PRC)に協力。彼らの専門知識と経験により、堅牢なシステム設計と高品質なリリースが実現しました。 Platform、Network、SREチーム :インフラ構築やトラブルシューティングに協力し、スケーラブルで信頼性の高いインフラを提供。これにより、サービスのパフォーマンスと可用性が確保されました。 メルペイとメルカリのFoundationチーム :IDP、KYC、加盟店基盤、決済基盤、- Growth基盤などのFoundation系サービスを提供。これにより、複雑なシステムも短期間にメルカリ ハロにインテグレートでき、品質も保証されました。 これらのチームからの手厚い且つプロフェッショナルなサポートと、しっかり整えられた基盤サービスの提供があったからこそ、短期間で高い品質の意思決定とサービスレベルの担保が可能となりました。 メルカリ ハロ組織の Move Fast と All For One メルカリ ハロの初期メンバーは、ほとんどが元々株式会社ソウゾウのメンバーです。ソウゾウのメンバーは、ベンチャー精神を誰よりも持ち、「 Move Fast 」というバリューを大切にしています。個々のエンジニアがオーナーシップを持って意思決定できるよう、ソウゾウのメンバーはメルカリ ハロのProduct、Design、Backend、Frontend、Mobile、QA、SREチームの骨組みを作り上げました。 メルカリ ハロ組織全体は初期から一丸となり、異なる職種間でも密に連携し、成功のためにあらゆる行動を取りました。エンジニアチーム内やProductチームとの連携はもちろん、Marketing、Customer Support、Sales、Partner Successチームとも密に連携できました。Customer SupportからのVoice of Customer、Partner SuccessからのVoice of Partnerを毎日シェアし、ソリューションを一緒に考えながら、開発の優先度を柔軟に調整してきました。 さらに、Salesの事業者商談にエンジニアも参加し、デモで企業のお客さまの心を捕まえた事例も、この組織の日常的なものになっています。このように一丸となることで、高速なイテレーションが実現しました。 No “Major” Incident メルカリ ハロはリリース後、2200万超のMAUを持つフリマアプリ「メルカリ」のユーザーにリーチ可能なサービスです。これだけの規模でサービスを提供するためには、一定以上の品質が求められます。品質の低いリリースは、ネガティブインパクトも非常に大きくなります。 そこで、私たちはリリース目標として「No “Major” Incident」を掲げました。この目標の背後には、大きなインシデントを発生させないという意図もありますが、同時に、スピードと両立するために、あえて「小さなインシデントは起こしても良い」という方針をチームに宣言しました。これは、スピードを重視しつつ、重大な問題を未然に防ぐための戦略です。 この方針により、チームは細かいトラブルを通して学び、システム全体の可用性を高めることができました。結果として、リリース後には大きなインシデントは発生せず、一定の品質を維持することができました。 メルカリ ハロエンジニアリングのこれから サービスローンチしたばかりなので、やりたいことはたくさんあります。 技術面から言うと、アルゴリズム、ML、LLMなどの技術を含めてうまく活用し、今まで存在しなかった感動的な「人、場所、おしごと」との「出会い」を作りたいと考えています。メルカリの強みであるお客さま基盤とデータをうまく活用し、メルカリ ハロで蓄積した新しいデータを含め、「新しい信頼と機会」をひろげたいと思います。 組織面から言うと、開発のボリュームと複雑度が指数的に増えている中、事業成長スピードと共にスケールする組織体制を作ることが個人的に一番楽しめる課題です。 長期的には、「働き方」や「雇用の方法」そのものが変わっていく転換期に突入していくと感じています。スポットワークを始め、「はたらく」という概念の転換を牽引するサービスになりたいです。そして、個人的にはメルカリ ハロ事業の海外進出の可能性にも期待しています。 最後に メルカリ ハロ開発チームから、より具体的なエンジニアリングエピソードをこれからお送りいたしますので、お楽しみにしてください! Links 連載: Mercari Hallo, world!  -メルカリ ハロ 開発の裏側- メルカリではメンバーを大募集中です。メルカリ ハロの開発やメルカリに興味を持った方がいればぜひご応募お待ちしています。詳しくは以下のページをご覧ください。 Software Engineer, Frontend – Mercari/HR領域新規事業 (Mercari Hallo) Software Engineer, Backend – Mercari/HR領域新規事業 (Mercari Hallo) Software Engineer, Site Reliability – Mercari/HR領域新規事業 (Mercari Hallo) Software Engineer, Machine Learning Leader – Mercari/HR領域新規事業 (Mercari Hallo) QA Engineer – Mercari/HR領域新規事業 (Mercari Hallo)
こんにちは!メルカリのQA Engineering Managerの @____rina____ です。先日、3月6日にメルカリグループの新規事業「 メルカリ ハロ 」がオープンされました。 メルカリ ハロは好きな時間に最短1時間から働ける「空き時間おしごとアプリ」です。仕事を探して、働いて、給与をもらうすべてをスマホで簡単に行うことができます。 またメルカリ ハロは、2023年4月に立ち上がったメルカリWorkチームという、メルカリグループの中の独立した組織で開発が行われています。システムもメルカリのメインのシステムからは独立したかたちで構成されており、メルカリグループとしての技術基盤を活かしつつ、さまざまな技術的なチャレンジを積極的に行っています。 そんなメルカリ ハロのオープンまでの約1年間の開発の裏側を、これから毎週公開していきます! 初日は、 @godric が執筆予定です。 公開に関しては、メルカリ公式DevX(旧Twitter) @mercaridevjp jでも随時お知らせします。ハッシュタグ #メルカリハロ開発の裏側 で検索してみてください。 メルカリの新しい事業での技術的チャレンジを広く届けられたらと思っていますので、どうぞお楽しみに!
こんにちは。メルペイ Engineering Engagement チームの mikichin です。 メルカリは、5月25日から開催される技術書典16にゴールドスポンサーをしています! メルカリ技術書典部では、有志メンバー5名による業務や趣味の技術についてまとめたものとメルペイ立ち上げ当時に戻れたらどんな技術選択をしていたかを振り返るインタビューをまとめた、ここでしか手に入らない2つの新刊を準備しています。 本記事では、新刊とメルカリ技術書典部が販売している本の購入方法についてご紹介します。 技術書典 について ITや機械工作とその周辺領域について書いた本を対象にした同人誌即売会。 技術者たちの「コミケ」とも言われています。 メルカリでは、技術書典3からスポンサーをしており、直近3連続ゴールドスポンサーです。また、有志メンバーで構成されたメルカリ技術書典部では、定期的に新刊を販売しています。 技術書典 16 オンライン開催:5月25日(土)〜6月9日(日) オフライン開催:5月26日(日)池袋・サンシャインシティ 展示ホールD(文化会館ビル2F) 新刊について 技術書典16では、メルカリ技術書典部は新刊を2冊準備しています。 Unleash Mercari Tech! vol.3 有志メンバー5名による業務や趣味の技術についてなどをまとめた一冊。 それぞれが非常に濃い内容となっているので、どれか1つでも興味を持っていただけるとうれしいです。 〈目次〉 第1章 AI時代にひねくれた選択? – 業務委託から正社員への変化球 第2章 CUEでBrainfuckインタープリターを作る 第3章 Goのエラーハンドリングを考える 2024 第4章 真剣に商業登記簿APIを作った話 第5章 OAuth 2.0 ClientをTerraform Custom Providerで宣言的に管理してみた URL: https://techbookfest.org/product/4JE8riJdXX5y1vBEYq7v8L Unleash Mercari Tech! vol.4〜メルペイ立ち上げ当時に戻ったら?〜 メルペイがリリースしてから丸5年。その当時、最善の選択をし開発をしてきていますが、メルペイのサービス拡充はもちろん、ビットコインが売買できるメルコインができるなど、(おそらく)想定していなかった状況に発展してきています。 そこで、「今の知識を持ったまま、メルペイ立ち上げ当時に戻るとしたらどうしてたか?」をテーマにインタビューを行い、まとめました。 各技術領域で、自社サービスの拡大や開発する上で提供されている機能のアップデートなど今の状況を踏まえ、今だったらあのときの開発をどのように進めていたかを振り返っています。 〈目次〉 第1章 Payment Platform編 第2章 iOS / Android編 第3章 Engineering Manager編 第4章 Platform Engineering編 第5章 SRE編 第6章 Architect編 第7章 元メルペイCTO編 URL: https://techbookfest.org/product/uVmfrDWUZd5JD5wJkPndxL オフライン会場で販売します! 5月26日、オフライン会場新刊含め、メルカリ技術書典部の本を販売いたします!入口入ってすぐの「協04」でお待ちしてます。 オンラインでも購入が可能です。 https://techbookfest.org/organization/47710001 執筆したメンバーの多くはブースにいる予定です。オフラインでご参加される方は、ぜひ会場でお会いしましょう!
search infra teamのmrkm4ntrです。我々の運用するElasticsearchにはFunction Score Queryを使ったリクエストが送られてきます。Function Score Queryはサブクエリのスコアに任意の関数を適用できるというもので、とても便利な機能ですが、同時にTop K(スコアが大きいものからK個を取得する場合)クエリ処理の最適化の恩恵を受けられなくなるという欠点もあります。この記事では、Function Score Queryに用いる関数の性質を利用し、Function Score QueryとTop Kのクエリ処理の最適化を両立させる方法について説明します。本記事は読者が検索エンジンの仕組みにある程度詳しいことを想定しています。 Top Kのクエリ処理の最適化 Elasticsearchの検索機能を提供しているライブラリLuceneには、Top Kを取得する際に、Top Kに入る見込みのないもののスコア計算をスキップすることで、パフォーマンスの最適化を図る機能が存在します。 例えば( ”search” OR “engine”)のようなクエリがあり、”search”というtermに対応するposting listの最大スコアが5.0、”engine”というtermに対応するposting listの最大スコアが3.0だとします。 BM25 ( https://ja.wikipedia.org/wiki/Okapi_BM25 ) にて各termに対するドキュメントはスコア付けされるため、indexの構築時に最大スコアが決まります。両方を含む文書のスコアは5.0 + 3.0 = 8.0になります。 ここでスコアの高い順にTop 10を検索することを考えます。この時大きいものから10番目のスコアがmin competitive scoreと呼ばれるものになります。つまり、このmin competitive scoreよりも大きいスコアをとりえない文書はスコア計算する必要がありません。 仮にmin competitive scoreが3.0より大きい値とします。この場合”engine”のみを含む文書のスコアはmin competitive scoreより大きくはならないので”engine”のみを含む文書のスコア計算はスキップできます。本来ならばORはそれぞれのtermのposting listを全て走査する必要があるのですが、”search”のposting listに存在する文書のみのスコア計算をすれば良いことになります。このような手法によりクエリのパフォーマンスを向上させることができます。 Function Score Query Function Score Queryは以下のようなクエリです。クエリを実行した結果はそのまま使うのではなく、サブクエリを実行した結果にfunctionsで指定された関数の戻り値を結合したスコアを最終スコアとして利用します。デフォルトの結合方法は乗算ですが、 score_mode の値によって動作を変更できます。 { "query": { "function_score": { "query": { … // サブクエリ }, "functions": […], "score_mode": … } } } 上記のTop Kクエリ処理の最適化は最大スコアがindex構築時に決まることが前提でした。Function Score Queryを使った場合、サブクエリのスコアに任意の関数の戻り値を結合することができるため、文書の最終スコアがクエリの実行時に決まることになります。このような状況下では先ほど説明したTop Kクエリ処理の最適化を使うことができません。 Function Score Queryを使うとTop Kクエリ処理の最適化がされていないのは、コードを確認すると明白です。min competitive scoreをLuceneのScorerに伝えるには setMinCompetitiveScore というメソッド( https://github.com/apache/lucene/blob/releases/lucene/9.10.0/lucene/core/src/java/org/apache/lucene/search/Scorable.java#L48-L57 ) を使うのですが、ElasticsearchのFunction Score QueryのScorerである FunctionFactorScorer においては setMinCompeitiveScore を呼んでいません。これによりTop Kクエリ処理の最適化がされていないことがわかります。 https://github.com/elastic/elasticsearch/blob/v8.13.2/server/src/main/java/org/elasticsearch/common/lucene/search/function/FunctionScoreQuery.java#L371-L487 TopKクエリ処理の最適化が可能な関数 確かに任意の関数に対してTop Kクエリ処理の最適化を実現するのは不可能です。しかし、利用する関数によってはTop Kクエリ処理の最適化の恩恵を受けれるものも存在します。例えばよく使われる、作成日時からの時間経過でスコアを指数関数的に減衰させる以下のようなFunction Score Queryです。 { "query": { "function_score": { "query": { … // サブクエリ }, "functions": [ { "exp": { "created_time": { "scale": "10d", "decay": 0.8 } } } ], "score_mode": "multiply" } } } このクエリでは、最終スコアは (指数関数的減衰 * サブクエリのスコア) となります。 重要なのはこの減衰は現在日時と作成日時の差において単調減少であるということです。 Luceneでは、posting listをあるフィールドの値で構築時にソートすることができます。posting listを作成日時の降順にソートすることで、上記の減衰関数を用いる際に、posting list内の前のドキュメントよりも必ず減衰値が大きくなることが保証できます。 これにより、サブクエリのみのmin competitive scoreが5.0、今の評価しているドキュメントの減衰が0.7だとすると、実質min competitive scoreを 5.0 / 0.7 = 7.14として扱うことができます。これは単にTop Kクエリ処理の最適化が使えるだけではなく、min competitive scoreがposting listを進むたびに増幅していくことになり、より多くのドキュメントの評価をスキップできる可能性が高まります。 PoCの実装とLuceneへの貢献 それをふまえて、前述の減衰関数を受け取りTop Kクエリ処理の最適化を実現するElasticsearchの新しいクエリをElasticsearchのpluginとして実装しました。基本的にはElasticsearchのFunction Score Queryと同じですが、サブクエリのScorerの setMinCompetitiveScore を適宜呼び出す部分が異なります。 実装自体は簡単でしたが、いざ動作確認すると全くパフォーマンスに変化がありませんでした。リモートデバッグで確認したところ、サブクエリ内のBoolean QueryにTop Kクエリ処理の最適化に必要な WANDScorer ( https://github.com/apache/lucene/blob/releases/lucene/9.10.0/lucene/core/src/java/org/apache/lucene/search/WANDScorer.java ) や BlockMaxConjunctionScorer ( https://github.com/apache/lucene/blob/releases/lucene/9.10.0/lucene/core/src/java/org/apache/lucene/search/BlockMaxConjunctionScorer.java ) が使われておらず、代わりに使われていたのは、それぞれ DisjunctionSumScorer ( https://github.com/apache/lucene/blob/releases/lucene/9.10.0/lucene/core/src/java/org/apache/lucene/search/DisjunctionSumScorer.java ) と ConjunctionScorer ( https://github.com/apache/lucene/blob/releases/lucene/9.10.0/lucene/core/src/java/org/apache/lucene/search/ConjunctionScorer.java ) でした。 調べてみたところ、 WANDScorer や BlockMaxConjunctionScorer はオーバーヘッドが大きいためトップレベルの句の場合でしか利用されないようです。つまり ((A AND B) OR (C AND D)) のようなクエリの場合はORは WANDScorer が使われますが、ANDには ConjunctionScorer が使われることになります。サブクエリをトップレベルの句と認識させるためには、新しく追加したクエリからサブクエリの ScorerSupplier の setTopLevelScoringClause メソッド( https://github.com/apache/lucene/blob/695c0ac84508438302cd346a812cfa2fdc5a10df/lucene/core/src/java/org/apache/lucene/search/ScorerSupplier.java#L46-L54 ) を呼ぶ必要があります。そのように修正したところ、無事に WANDScorer と BlockMaxConjunctionScorer が使われるようになりました。 これでパフォーマンスが改善するかと思われましたが、相変わらず変化がありません。さらに調べると、 WANDScorer の下の ConjunctionScorer (ORの下のAND)と BlockMaxConjunctionScorer の下の DisunctionSumScorer (ANDの下のOR)が最大スコアとしてInfinityを返していました。 Luceneの実装を見ると確かにInfinityを返すようになっています。何故Infinityを返すのか全く意図が掴めずに頭を抱えましたが、トップレベルの句以外は最適化をしないという修正( https://github.com/apache/lucene/pull/12490 ) での漏れだということがわかりました。そこで、それぞれ最大スコアに正しい値を返すように修正したところ、ようやくパフォーマンスが大きく改善することが確認できました。同様にprofile APIにおいても修正漏れがあったため、以下のプルリクエストをLuceneのupstreamに送りました。今は全てmergeされています。 https://github.com/apache/lucene/pull/13031 https://github.com/apache/lucene/pull/13043 https://github.com/apache/lucene/pull/13066 上で実装したpluginを使って我々のワークロードを模したパフォーマンステストを実施したところ、既存のクエリの95pが約35%、99pが60%下がりました。さらにコストも約1/3削減できることがわかりました。 ただし、この最適化の恩恵を受けるためにはリクエストパラメータのtrack_total_hitsをfalseにする、もしくは十分小さい値(1,000以下)に設定する必要があります。というのも最低でもこの件数はヒットするドキュメントを検索する必要があるため、スキップ対象が少なくなるからです。歴史的経緯により、この値をすぐに小さくすることは難しく、また最近この最適化ができない形のクエリがテストされているため、この最適化を実際に我々の本番環境に適用できるかは検討中です。 さいごに この記事ではElasticsearchのFunction Score Queryに使われる関数の単調減少性を利用してTopKスコア処理の最適化の恩恵を受ける方法について述べました。このタスクでLuceneのスコアリング周りの内部実装についての理解が深まりました。また、仮説を実装し、何度もうまくいかない原因を潰していくサイクルは大変でしたがエキサイティングでした。もし仮にこのような最適化を適用できるクエリを利用されている場合は、試してみていただけると幸いです。
*Security & Privacy Divisionの原動力となっているバリュー、それは「By design, by default and at scale(設計で叶える、デフォルトに組み込む、スケールに対応する)」です。 Oktaのユーザーアクセス権の棚卸し作業をPlatform Security Teamに率いてほしいという依頼が寄せられました。このプロジェクトを進める中、私たちは過去の設定や慣習と向き合わなければなりませんでした。なぜなら古いやり方が残っていることで「by design」と「by default 」な管理が難しい状態だったからです。そのような状況にも関わらず、私たちは「at scale」で組織全体を網羅した検査を実施する必要がありました。 この記事では、これらの課題に私達がどのように挑んだかを説明します。 使用したテクノロジー: Neo4j: https://neo4j.com/ Okta: https://www.okta.com/ Slack: https://slack.com 概要 メルカリでは、従業員のSaaSへのアクセスのほぼすべてをOktaを使って認証しています。アクセス権とは、許可するのは簡単ですが取り消すことが難しいものです。 不要なアクセス権を一掃するため、Neo4jを使用して組織とアプリケーションへのアクセスをグラフ化し、ユーザーインターフェースにはSlackを使って調査を実施しました。 全社的に提供しているアプリケーション以外で、現在付与されているアクセス権がすべて必要なものかを全社員に聞き取り調査。 その後、各マネージャーにそられのアクセス権がそれぞれの職責と照らし合わせて妥当かを確認。 情報を集約後、自己申告に基づいて不要なアクセス権をOkta APIを通じて直接削除。 これらをこれらを実装することで社内全体を対象とした大規模な検査を行うことができました。 これまでの道のり メルカリは今年創業11年を迎えました。今でこそ中堅企業に成長したものの、多くの10代が思春期を通過するように、成長痛に似たいくつもの苦労を乗り越えてきました。会社の拡大に伴い新たな従業員の入退社を経験し、アクセス管理に関するニーズも変化していきました。新たに導入されるサービスもあれば、廃止されるサービスもありました。過去にアクセス権の付与を決定した根拠や理由も、現在に至る過程で失われてしまいました。 メルカリはSaaSに大きく依存しているため、IDを管理するソリューションとしてOktaとGoogle Workspaceを使用しています。今回、アクセスレビューのプロジェクトに着手した時点で、Oktaのみですでに約8000のユーザー、500のアクティブアプリケーション、1400のグループが存在していました。アクセス権の削除は退職のケースであれば比較的簡単です。しかし、社内異動の場合は細心の注意を必要とする作業です。また勤務年数の短い従業員であればアクセス権の整理も比較的簡単にできますが、勤務年数が長い場合は長年の間にアクセス権が増えてしまっており見直しが大変な場合もあります。その結果、秩序が失われ、そのせいで複雑さも増してクリーンな状態にするのが難しくなっていました。 プロジェクトの目標 Security teamの最終的な目標は、アクセス権の乱用よって引き起こされる潜在的な被害を可能な限り減らすことです。 アクセス権のクリーンアップによりさまざまな副次的効果が期待できます。 認証システムにおける無秩序さを減らす 各従業員/チームがどのようなシステムを使用しているか、より明確に理解できるようになる システムオーナーにその人のアクセスがまだ必要なのかについてヒアリングし、その調査結果をドキュメント化するというSecurity teamメンバーのストレスを軽減する どのように管理されているのか、それはなぜなのかについて理解するための時間を減らす もう必要ない可能性のあるSaaSを特定する クリーンな状態に基づいて、より優れたアカウントライフサイクル管理のパターンを作成する その他 考えうる戦略 「最小特権の原則」は、事故やインシデントのリスクを軽減する最善の方法のひとつであるものの、その適用と維持には相当な労力が必要であることが予想できました。 「最小特権の原則」を適用して最終目標を達成できるということは、私たちが以下のことを理解している(または把握している)という意味でもあります。 社内にどのようなシステムがあるか それらのシステムオーナーと管理者は誰か 誰がどのアクセス権を使ってこれらのシステムにアクセスできるか 各システムが処理し保存しているデータの種類は何か これらシステムが使用される可能性のあるビジネスプロセスは何か 各社員とシステム、また取り得る行動とそれに伴う結果との間にあるつながり Oktaのデータをもとに簡単に計算してみましょう。アプリケーションは500個あり、ユーザー数は8000です。それらが直接割り当てられている、または1400のグループを通じて割り当てられています。各アプリケーションには複数のユーザーがおり、各グループにも複数のユーザーがいます。アプリケーションによっては複数のグループが存在するものもあり、それを組織体制と全ユーザーにリンクさせると、メルカリ社内には20万を超える関係性が存在するという計算になります。この段階では、各ユーザーのアクセスレベル、各システムで処理・保存されるデータの種類、ユーザーにとってどのようなアクションが可能かすらも分かりません。 仮にOktaから得られる情報のみを起点としましょう。1秒間で判断を下すために必要な情報はすべて揃っているという前提の下、1件の関係性につき1秒かかるとします。それでも前述の20万件の関係性をレビューするには丸々55時間かかってしまいます。したがって、一人の人間が全員分のアクセス状況を見ることは明らかに合理的ではありません。 では、他にも実践できそうな方法はないか見ていきましょう。 戦略1:重要なシステムのみにスコープを絞る 重要なシステムはどれなのか?どのような条件に従って決めるのか?これらの条件を定義しようとすると、考えうる要素が多すぎて誰もが容易に迷子になってしまいます。でも魔法なんて存在しないのですから、どこかしら複雑さが残るのもやむを得ないことです。もし、重要なシステムや機密性の高い情報を含むシステムを特定するという方法を選んでも、誰か(またはどこかのチーム)がすべてのシステムに目を通し、それらが何に使われ、どのようなユーザーがアクセスすべきかを理解して分類しなければなりません。 ただ同時に、私たちは社内にあるシステムを大体把握できています。とりあえず手をつけて始めてみたほうが、一通り情報をかき集めてから目の前にそびえ立つ到底登れそうにない頂に絶望するよりも理にかなうはずです。そうでもしないと、いざ山頂に辿り着いたとしても、全員が疲れ果てているか、すでに会社を辞めた後かのどちらかになっていることでしょう。 もうひとつの問題は、このレビューを行っている間も社内の環境は変化し続けるということです。レビューが完了するまでの間に新たなシステムが導入され、そこにユーザーが追加され、それらシステムは新たなユースケースのために使用されることでしょう。川の流れを止め、その間に魚を数えるようにはいかないのです。 戦略2:フルスコープ、システムオーナーに依頼する システムオーナーに依頼するというのはどうでしょう?アプリの数は500。ユーザー数は1人の場合もあれば全従業員+業務委託が含まれる場合もあります。各システムオーナーが平均10システムを担当するとしても、50人がそれぞれ約4000件のアクセスを確認し、職務内容やサービスの性質、アクセスされるデータに基づき、これらユーザーがアクセスすべきか否かを判断をしなければならないことになります。どこかの時点で、少なくともいくつかの重要なシステムにおいては必要かもしれませんが、秩序のない初期の状態においては有効なアプローチとは言えません。 また、システムオーナーの多くはマネージャーやディレクターです。彼らの時間は貴重です。時間のない人は優先順位を意識するため、この業務はどんなに重要でも後回しにされる可能性が高いでしょう。 戦略3:まずユーザーに質問し、マネージャーにその回答を確認してもらう 他の誰かに聞く前に、まだシステムへのアクセスが必要かどうかをユーザー本人に質問することは可能です。 今回採用した方法はまさにこれで、まずは従業員に以下のように聞いてみます。 これらシステムすべて対してまだアクセス権は必要ですか?はい/いいえ/分からない 回答が集まったら(または期限が過ぎたら)、彼らのマネージャーに質問します。 各メンバーの役割と責任を考慮した上で彼らの回答をレビューし、それらアクセス権が適切かどうかを確認してください。 今回はそこまで実施しなかったものの、3段階目のレビューとしてシステムオーナーへの質問も考えました。 これらのチームはあなたの管理するシステムを使用しています。このシステムの用途を考えると、彼らがアクセスすることに問題はありませんか? この方法の場合、アクセス権を維持するか取り消すかの判断をアクセス権を実際に使用する人に委ねることになります。また、権限の確認を全従業員に割り振ることができるという利点もあります。残念ながらマネージャーにはメンバーがアクセスの必要性を主張しているアプリケーションをすべて確認してもらわなければなりませんが、求められているのは確認だけなので検査は比較的早く終わるはずです。妥当かどうかを簡単に確認するだけなら、通常ひとり5分もかかりません。場合によってはもう少しかかるかもしれませんが、DM(ダイレクトメッセージ)で確認することが可能です。 このプロセスを通して私たちは「Security TeamのAさんが給与システムのアクセス権を持っている」といった、本来であれば例外的なケースを発見したいと思っていました。もし本人が「必要だ」と言ったとしても、少なくともマネージャーにその妥当性を確認してほしいからです。 このプロセスを実施している間、「このアクセス権が付与されているなんて知らなかった」「そもそもこのサービスってなに?」といったコメントが数多く寄せられました。 Oktaの使われ方からして、今回選択した方法が完璧とはいえないことは分かっています。ですが、私たちはOktaでアプリケーションのアクセス権を付与しています。メルカリの場合、アプリケーション内で権限を付与することはほとんどありません。そしてこれはシステムオーナーに委ねられています。​​このようなやり方のため、そもそも最初からアクセスできる対象を制限することでかなりの違いが出てきます。さらに追加のクリーンアップは後からでもできます。その時に、いくつか重要なシステムを優先的に対応することも可能です。 プロセスの実施方法 さて、ここまでに「なぜ検査を行うのか Why 」、「どのシステムを対象とするか What 、「誰が回答し、誰がレビューするのか Who 」が明確になりました。次は、「どうやって全員に質問し、回答を集めるのか How 」です。 スプレッドシートでの検査(現実的ではありません) すべてのユーザー/グループ/アプリを含めると20万行になってしまいGoogleスプレッドシートには収まらないし、全員に開いてレビューするようお願いするのもばかげています。シートの完全性を確保することは可能ではあるものの、さらに多くの作業が必要となります。 Webベースでの検査(現時点では見送る) うまくいくとは思いつつも、少なくともこの段階では検査を実施するためのウェブページは作らないことにしました。 OktaのIdentity Governance Access Certificationキャンペーン機能(我々には有効ではありません) Oktaには Identity governance access certification という機能があります。Oktaが将来的にアクセスレビューとして使用されることを承知の上で一から設定されているのであれば、この機能を利用する方法はうまくいくでしょう。ここではオーナーは特定グループに割り当てられ、そのグループはアプリケーションに割り当てられます。キャンペーンを実施している間、グループオーナーはグループのメンバーがアクセス権を所有すべきかを確認するよう依頼されます。この方法は、グループオーナーがそのユーザーがアクセス権を持つべきかを判断できることを前提としています。グループは多くの場合チームを意味するため、メンバーの管理はマネージャーに委ねられるでしょう。そのチームグループは、アプリケーションオーナーから必要なアプリケーションに割り当ててもらう必要があります。しかし、Oktaには(現時点では)アプリケーションオーナーを定義する属性がありません。 通常のケースはこの方法で問題ないのですが、例外ケースの場合は他のグループを通じて管理する必要があり、その例外を理解できる人に割り当てる必要があります。 私たちの今の状態で考えると、グループ=チームではなく、通常(必ずではないものの)アプリケーションへのアクセスを許可するために使われているので、この方法は有効な策ではありませんでした。この状態は、これらグループにオーナーが割り当てられていないという意味でもあります。システムオーナーに Slack + バックエンド + Neo4j(選んだ方法) 私たちはユーザーインターフェイスとしてSlackを、バックエンドデータベースとしてNeo4jを使うことに決めました。バックエンドにグラフデータベースを使うことで、チーム、メンバー、そのマネージャーに対する問い合わせと、彼らがどのグループを通じてどのようなアクセス権を持っているかを(比較的)簡単に照会できるからです。とりあえず今回は、アプリケーション内で付与されたアクセスのレビューは対象外にすることも決めました。 このブログ記事の残り部分では、私たちが実施したプロセスを説明します。 検査を進めるためには、いくつかのステップを経る必要がありました。 組織構造を復元する Okta上のアプリケーション、グループ、ユーザー、すべてのメンバーシップとそれらの関係を復元する 組織とアクセスを記したグラフを作成する 各チームと従業員向け:Slackのフォームを作成し、どのアクセスがまだ必要かの確認を依頼する ユーザーからの回答を集める 各マネージャー向け:Slackのフォームを作成し、メンバーが必要だと申告しているアプリに同意するかどうか質問する。ユーザーからの応答がない場合はマネージャーに決めてもらう マネージャーからの回答を集める 妥当性の確認:明らかにおかしな回答がないかレビューする Okta APIを通じてアプリケーションへのアクセスやグループメンバーシップを取り消す 変更を記録する ステップ8を除く上記のすべての操作はコードを通じて行います。そうすればこのプロセスを確実に再現することができるからです。 組織構造とアクセス権をデータベースで表す Oktaのユーザーは、チームとマネージャーを示す属性を持つように設定することができますが、いくつか実際の組織構造との相違点が見られたため、最終的には別のソースから完全な構造を抽出し、その構造をOktaのユーザーとリンクさせなければなりませんでした。組織構造をグラフ化することで、Okta上の関係ではなく実際の組織構造を明らかにすることができたので非常に便利でした。 その後、Oktaから特定の組織単位や チームにおけるアプリ、グループ、ユーザー間の関係を抽出することができました。 イメージ1:Oktaと人事データをNeo4jグラフデータベースに統合し、Mermaid.jsで可視化 スキーマ:組織、チーム、マネージャー、メンバー、グループ、アプリケーション間の関係性 オーバーエンジニアリングを防ぐために、少なくとも最初のうちはいくつかショートカットを採用し、各従業員の単位としてOktaUserノードを使用することにしました。現実はもっと複雑な権限が付与された対象を特定する必要があるのですが、この段階ではこれで十分でした。 イメージ2:Mermaid.jsを使って視覚化したデータベース内における関係性の概略図 Neo4jデータベースへの書き込みが終わると、組織、チーム、各チームが使用しているアプリケーションを照会できるようになりました。組織構造のグラフはこのような様子でした。 イメージ3:Neo4jのウェブインターフェイスを使って作成したメルカリの組織構造図 以下のクエリは以下のような意味を表します: 「Platform Security」チーム直下のメンバーで、有効なOktaアプリにアクセス権があるすべてのメンバーに対して: マネージャーを取得する 直近90日間にこれらのアプリを使用したかどうかを取得する ユーザー・アプリ間の関係性のOrgノード、マネージャーノード、関係性のプロパティ、最終使用のプロパティ、およびアプリノードを返す これを元に再度、グループメンバーシップによるアプリへのアクセスを考慮します。 // Team: Platform Security MATCH (o:OrgUnit {name: "Platform Security"})<-[:IS_MEMBER_OF]-(u:OktaUser)-[r:HAS_ACCESS_TO]->(a:OktaApp {status: "ACTIVE"}) WITH o, u, r, a MATCH (u)-[:IS_REPORTING_TO]-(m:OktaUser) WITH o, m, u, r, a OPTIONAL MATCH (u)-[p:HAS_USED]->(a) RETURN o, m, u, PROPERTIES(r) AS r, PROPERTIES(p) AS p, a MATCH (o:OrgUnit {name: "Platform Security"})<-[:IS_MEMBER_OF]-(u:OktaUser)-[r:IS_MEMBER_OF]-(g:OktaGroup)-[:HAS_ACCESS_TO]->(a:OktaApp {status: "ACTIVE"}) WITH o, u, r, g, a MATCH (u)-[:IS_REPORTING_TO]->(m:OktaUser) WITH o, m, u, r, g, a OPTIONAL MATCH (u)-[p:HAS_USED]->(a) RETURN o, m, u, PROPERTIES(r) AS r, PROPERTIES(p) AS p, g, a Query1:Neo4j Cypherを使用して、特定のチームのアプリケーションとグループのアクセスリストを取得 プロセスを開始する コントローラー(アプリ)はユーザーを特定するためにチームのリストを使用しています。チームの再帰的リストは、次のようなクエリでNeo4jデータベースから簡単に抽出できます。 MATCH (t:OrgUnit)-[:IS_PART_OF*]->(o:OrgUnit) WHERE o.name = "Security & Privacy" AND t.status = "active" RETURN t.name AS team, t.orgId AS orgId, o.name AS orgName クエリ2:Neo4j CypherでSecurity & Privacyカテゴリの再帰的チーム階層を復元 ここからスコープ内のチームリストに基づいて、コントローラーから検査開始がマネージャーに通知されます。各チームメンバーにが作成され、SlackのDMで調査フォームが送信されます。 メンバーに調査フォームを送信する Image 4: Sequential flow chart detailing the member campaign process, illustrated with Mermaid.js. The assessment form sent to members is kept simple and is meant to be quick to fill. A user can click on the application name to connect to the app and confirm if they still need access to it, then select “Access needed” or “No need anymore”. イメージ4:メンバーのキャンペーンプロセスのフローチャート 回答収集用のバックエンド 調査フォームが送られたら、あとは回答を待つだけです。バックエンドで回答を受け取り、その回答に従ってNeo4jデータベースを更新する準備は整っています。 イメージ6:調査フォームからの回答を収集する手順のフローチャート 調査を実施している間、手動でマネージャーに進捗状況を送信し、未回答の場合はチームメンバーに確認してもらうよう依頼することができます。 マネージャーによる回答のレビュー 回答の回収が済んだら、未回答・未完了のメンバーがいたとしても、マネージャーにアクセスのレビューを依頼します。メンバーからの回答は一目瞭然であり、チームに関係するアプリケーションもよく知られているはずなのでこのステップは通常すぐに終わります。 マネージャーが対応しない場合は、その上司に進捗がないことを報告することができます。 マネージャーのレビューの流れは以下です: イメージ7:Mermaid.jsを使用して、マネージャーのレビュー作業のシーケンス図 マネージャーに送信されるフォームはユーザーに送られるフォームと似ていますが、必要だと回答されたアプリだけが表示されています。マネージャーはメンバーの回答を確認し、メンバーによりアクセスが必要だと判断されたアクセス権に対して、保持か削除を選択することができます。 イメージ8:Slack内のマネージャーレビューフォームのインターフェイスの例 不要なアクセスのクリーンアップ この段階では、メンバーからの回答が集まり、マネージャーからの確認も回収済みです。個別のアクセスレビューではなく、チーム単位でのアクセス権付与に同意するかの確認をシステムオーナーに依頼することもできましたが、これは今後の検査に回すことにしました。 Okta APIによるアクセス取り消しフローは比較的シンプルです。 イメージ9:アクセス取り消しメカニズムに関するステップのフローチャート まとめ 今回のプロジェクトを通して、従業員やマネージャーが正直に回答してくれると信じることで、従業員がどのアクセス権を持ち、どのアクセス権を必要としているかをレビューすることができました。多くの規格、フレームワーク、規制、ベストプラクティスでは、企業が定期的にこういったレビューを実施することが求められています。しかし、得てして複雑な組織構造や歴史的背景がからみあい、こういったレビューはすぐに手に負えなくなるものです。そこで、従業員とアプリケーション間の複雑な関係性をグラフデータベースに移行し、まず従業員にアクセス権が必要かどうかを質問することで、会社の規模に応じて検査の規模を拡大することができました。また、今回の検査は、システム分類作業に長い時間をかけることなく実施することができました。Oktaに大きく依存しているからこそ、Oktaに焦点を当てることで、大半のシステムをカバーすることができたのです。 このフローにもまだまだ改良の余地はあり、他のシステムへの拡張も可能だと考えています。アクセス許可のルールと確認をより厳格にし、プロビジョニングプロセスに組み込むこともできるかもしれません。 一方、今回私たちはすでに、アクセス中断のリスクを負うことなく、不要となった膨大な量のアクセス権を削除することができました。これは、アクセス権を取り消すか否かを判断する際に、こちらで決めたルールを使用するのではなく、従業員とマネージャーの回答に基づいて行ったからです。