こんにちは。メルペイData Platformチームの@siyuan.liuです。 この記事は、 Merpay & Mercoin Advent Calendar 2024 の記事です。 Merpay Data Platformチームは、社内共通のデータ処理基盤の開発と運用を担当しており、バッチ処理やリアルタイム処理など、さまざまなPipelineを提供しています。その中でも、リアルタイムで大量のログを収集するStream Pipelineがあります。このStream処理は、Kubernetes上で Flink Operator を使用し、Flinkを自動的に管理することで実現されています。 しかし、このStream Pipelineでは、トラフィックピーク時にCPUやメモリ不足によるコンシューマーラグが発生するという課題がありました。これを防ぐため、通常は余剰のCPUやメモリを割り当てますが、その結果、コスト増加を招く新たな課題も生じます。 これらの課題を解決するため、HPA(Horizontal Pod Autoscaler)を導入し、外部のDatadogメトリクスにより、トラフィックに応じてサーバー数を自動でスケールアウトさせることで、ピーク時のリソース不足を防ぎました。また、Tortoise VPAを利用してリソースの使用率に基づいて動的なリソース割り当てを行い、無駄なリソース配置を抑制しコスト削減を実現しました。 本記事では、HPAとTortoise VPA(Vertical Pod Autoscaler)を活用したこれらの解決策とその効果について、参考コードと具体的なデータとともに詳しく解説します。 背景と課題 本節では、Stream Pipelineの構成、およびStream Pipelineにおけるトラフィックピーク時のリソース不足と過剰なリソース割り当ての課題について解説します。 Stream Pipelineの構成 Data Platformチームは、主に社内のPaaS Data Platformの開発と管理を行います。このプラットフォームは、Batch、Stream、CDCなどのデータパイプラインを提供し、各マイクロサービスに分散されたデータへのアクセスと活用を効率化します。その中でStream Pipelineは、各Microserviceからアクセスログとイベントログを収集し、ニアリアルタイムでデータをGoogle Cloud Storageに保存する役割を担っています。また、ログをBigQueryに直接保存することや、他のシステムに転送することも可能です。 本記事で取り上げる課題は、上図の赤枠で示したFlink処理の部分と関わります。この部分は主に、FlinkOperatorが管理するFlinkを使用し、Pub/SubやKafka Topicのデータを消費します。 課題1:トラフィックピーク時にCPUやメモリ不足によるコンシューマーラグ イベント、キャンペーンなどにより、一時的にアクセスログやイベントログのトラフィック量が急増することがあります。このような状況下で、Flink処理に十分なリソースを確保できない場合、Pub/Subのデータを消費しきれず、コンシューマーラグが発生します。これにより、データ同期の遅延や、データの損失につながる可能性があります。 このトラフィックピークによるコンシューマーラグをどのように解決するかは、Data Platformチームにとって重要な課題となっています。 課題2:過剰なリソース割り当てによるコスト増加 Flink処理の安定性を確保するため、Data Platformチームでは通常、Flinkに余裕なリソースを割り当てています。たとえば、通常時においてはCPU 0.5 Core、メモリ2048MでFlinkが正常に稼働しますが、トラフィックピークに考慮してCPU 1 Core、メモリ4096Mを割り当てることが一般的です。1つのFlinkに対してこのような対応を行う場合、大きなリソースの無駄にはなりませんが、Data Platformチームが管理するFlinkは200個以上あり、すべてのFlinkにこの方針を適用した結果、リソースの無駄が生じています。 そのため、Flinkのリソースを動的に最適化することが、重要な課題となっています。 HPAによるトラフィックピーク時の自動スケールアウト 本節では、HPAを使用したFlinkの自動スケールアウト方法と、FlinkのReactive ModeによるTask Managerリソースを最大限に活用する方法について、参考コードとともに解説します。 HPAによるFlink TaskManagerの自動スケールアウト トラフィックピークによるリソース不足のコンシューマーラグを防ぐため、HPAを導入しました。 KubernetesのHorizontal Pod Autoscaler(HPA) は、リソース使用量やメトリクスに基づいてPodのレプリカ数を自動的に調整する仕組みです。トラフィックのピーク時にはスケールアウトし、需要が低いときにはスケールダウンすることが可能です。また、Flink OperatorはHPAをサポートしており、HPAの設定でscaleTargetRefのkindをFlinkDeploymentに指定することで、TaskManagerのスケールアウト・スケールダウンを自動的に制御できます。 HPAで使用する外部のDatadogメトリクス oldest_unacked_message_ageは、サブスクライバーによって確認応答(ACK)されていない、サブスクリプションのバックログ内の最も古いメッセージの経過時間(秒単位)の指標です。トラフィックピーク時にFlinkがPub/Subのデータを消費しきれない場合、このメトリクスの値が増加します。このメトリクスをHPAの閾値として設定すれば、トラフィックに応じてFlinkのTaskManagerの数を自動的に調整できます。 参考コードは以下になります。 metadata: name: event-log-or-access-log-hpa spec: scaleTargetRef: name: event-log-or-access-log apiVersion: flink.apache.org/v1beta1 kind: FlinkDeployment maxReplicas: 12 minReplicas: 6 behavior: scaleUp: stabilizationWindowSeconds: 0 policies: - periodSeconds: 15 type: Pods value: 4 - periodSeconds: 15 type: Percent value: 100 selectPolicy: Max scaleDown: policies: - periodSeconds: 90 type: Percent value: 2 selectPolicy: Max metrics: - external: metric: name: oldest_unacked_message_age target: type: Value value: "420" type: External kind: HorizontalPodAutoscaler apiVersion: autoscaling/v2 この設定により、トラフィックピーク時にPub/Subの消費が7分(420秒)以上の遅延となった場合、FlinkのTaskManagerの数が自動的に増加され、リソース不足を防止します。また、トラフィックピークが収まった際には、TaskManagerの数が元の状態まで自動的にスケールダウンされ、リソースの無駄遣いを防ぎます。 Flink Reactive ModeによるTaskManagerのリソース利用率の最適化 トラフィックピーク時には、TaskManagerのリソースが自動的に増加しますが、それに伴ってJobの並列度(parallelism)を調整しないと、スケールアウトしたTaskManagerのリソースが十分に利用されません。 HPAによって増加したTask Managerのリソースを効率的に利用するため、Flink の Reactive Mode を導入しました。Reactive Modeでは、Jobの並列度が可能な最大値に設定され、Cluster内のTaskManagerのリソースを可能な限り利用します。これにより、トラフィックピーク時に TaskManager のリソースを最大限に活用し、効率的なリソース管理が可能となります。 ただし、Reactive Mode は Standalone Mode でのみ使用可能なため、利用時にはご注意ください。 FlinkのReactive Modeを有効化する参考コードは以下になります。 metadata: name: flink spec: image: flink imagePullPolicy: IfNotPresent mode: standalone flinkVersion: v1_18 flinkConfiguration: scheduler-mode: reactive Tortoise VPAによる過剰なリソース割り当ての最適化 本節では、過剰なリソース割り当ての課題を解決するため、Tortoiseの概念、Tortoise VPAのみを導入する理由、およびFlink Operatorが管理するFlinkへのTortoise VPA導入時の注意点を解説します。最後に、これらの理由と注意点を踏まえ、Tortoise VPA導入の参考コードを示します。 Tortoiseとは Tortoiseは、過去のリソースの使用量や過去のレプリカの数を記録しており、それを元にHPAやVPAを最適化し続ける仕組みです。この中で、Tortoise Vertical Pod Autoscaler (VPA) はCPUやメモリのResource Request/Limitを最適化する役割を担います。これにより、負荷が低下した際に、不要なリソース割り当てを削減することが可能になります。Tortoise の詳細については、以下の公開記事をご参照ください。 「 人間によるKubernetesリソース最適化の”諦め”とそこに見るリクガメの可能性 」 Tortoise VPAのみを導入する理由 TortoiseはHPAとVPAの両方をサポートしていますが、DataplatformチームではTortoise VPAのみを使用しています。その理由は二つあります。 一つ目は、DataplatformチームではすでにKubernetes HPAを利用しているためです。既存のKubernetes HPAは外部のDatadogメトリクスに基づいてリソースを調整していますが、Tortoise HPAは外部のメトリクスによるリソース調整をサポートしていません。 2つ目は、FlinkOperatorを利用しているためです。FlinkOperatorはカスタムリソースを用いてFlinkを管理していますが、Tortoise HPAはカスタムリソースをサポートしていません。一方、FlinkOperatorはKubernetes HPAをサポートしているため、既存のKubernetes HPAは問題なく利用できます。 以上の理由から、Data PlatformチームはTortoise HPAを導入せず、Tortoise VPAのみを使用しています。 FlinkへのTortoise VPA導入時の注意点 Flink Operatorが管理するFlinkにTortoise VPAを導入する際には、FlinkDeploymentをTortoiseに管理させるのではなく、JobManagerとTaskManagerのDeploymentを分け、それぞれのTortoiseに管理させる必要があります。JobManagerとTaskManagerは異なる役割を持ち、それに応じて必要なリソースも異なります。しかし、Tortoiseはカスタムリソースをサポートしていないため、Flink OperatorのFlinkDeploymentをTortoiseに管理させると、JobManagerとTaskManagerが同一のリソースとして扱われてしまい、適切な推奨値が算出されなくなります。 また、JobManagerのリソースがTortoiseによって過度にスケールダウンされると、リソース不足によるダウンタイムが発生する可能性があります。そのため、minAllocatedResource(リソース割り当ての最低値)を設定することが重要です 参考コード 上記の理由と注意点を踏まえ、Tortoise VPAによってリソースを調整する設定は以下になります。 metadata: name: event-log-or-access-log-jobmanager-tortoise spec: targetRefs: scaleTargetRef: kind: Deployment name: event-log-or-access-log-jobmanager updateMode: Auto resourcePolicy: - containerName: flink-main-container minAllocatedResources: memory: 4294967296 autoscalingPolicy: - containerName: flink-main-container policy: cpu: Vertical memory: Vertical kind: Tortoise apiVersion: autoscaling.mercari.com/v1beta3 --- metadata: name: event-log-or-access-log-taskmgr-tortoise namespace: merpay-dataplatform-jp-prod spec: targetRefs: scaleTargetRef: kind: Deployment name: event-log-or-access-log-taskmanager updateMode: Auto autoscalingPolicy: - containerName: flink-main-container policy: cpu: Vertical memory: Vertical kind: Tortoise apiVersion: autoscaling.mercari.com/v1beta3 結果と効果 本節では、具体的なデータをもとに、改善の結果とその効果について説明します。 1. HPAによるトラフィックピーク時の安定性の向上 HPA を導入した結果、トラフィックピークの際に Flink の Pod 数が自動的にスケールアウトされることで、リソース不足によるダウンタイムが発生しなくなりました。 上記の図は、Mercariの検索プロジェクトにおけるoldest_unacked_message_age(最も古い未確認応答メッセージの経過時間)とdeployment_replica(レプリカ数)のメトリクスを対照したものです。10:35 ごろ、 oldest_unacked_message_age メトリクスが 増加する直前に、Flink の Pod 数も増加していることが確認されました。また、同日の 16:55 ごろにピークが収束するとともに、Flink の Pod 数も自動的にスケールダウンされ、安定的なリソース利用が実現されました。 この動的スケーリングの結果、従来はリソース不足で発生していたコンシューマーラグはほぼ 0% にまで削減され、システム全体の安定性が大幅に向上しました。 2. VPAによるコストの削減 2024年5月にTortoise VPA を導入した結果、通常時のリソース使用量に基づいた最適な resource request と limit が自動的に設定され、不要なリソースの割り当てが大幅に削減されました。 その結果、月平均で 100万円以上、年間で 1200万円以上のコスト削減を実現しました。 まとめ Data Platformチームが管理するStream Pipelineにおいて、トラフィックピーク時のリソース不足によるコンシューマーラグと、過剰なリソース割り当てによるコスト増加という2つの課題とその解決策を解説しました。HPAを活用した自動スケーリングにより、Flinkのスケールアウト・スケールダウンを実現し、Stream Pipeline全体の安定性が向上しました。また、Tortoise VPAを用いることで、リソースの効率的な割り当てを行い、無駄なリソース配置を抑制し、コストの削減を実現しました。 今後もData Platformチームでは、Stream Pipelineのリソース最適化をさらに進め、同様の対策をCDC PipelineのKafka Connectにも取り込みたいと考えています。 次の記事は @togamiさん です。引き続きお楽しみください。
こんにちは。メルカリのQA Engineering managerの @____rina____ です。 この記事は、 連載:メルカリ ハロ 開発の裏側 – Flutterと支える技術 – の1回目と、 Mercari Advent Calendar 2024 の3日目の記事です。 先日、11月15日に開催された Tokyo Test Fest というイベントで、"Acceptance criteria: QA’s quality boost"というタイトルで発表を行いました。このセッションでは、Flutterに限らず、開発プロセス全体においてAcceptance criteriaをQAエンジニアが書くことの重要性と、それを開発チーム全員で同期レビューすることの大切さについてお話ししました。 Acceptance criteriaは、開発チームの協業を円滑に進めるための重要な要素です。これを正確に定義し、チーム全体が共有することで、品質を大いに向上させることができます。今回の発表では、私たちのプロジェクトでの具体的な事例をもとに、このプロセスの効果についても言及しました。 また、以前に投稿したAcceptance Criteriaに関する記事も、ぜひあわせてご覧ください(ブログは日本語のみです)。 以前の記事は こちら 今回はこの発表内容について詳細をお届けします。以下に書き起こしを掲載します。 Acceptance criteria: QA’s quality boost 東京テストフェストのみなさん、こんにちは!私はリナです。本日お越しいただきありがとうございます。これから、「Acceptance criteria:QA’s quality boost」というテーマでプレゼンテーションを始めます。 Our QA Team’s initiative さて、今日は私たちが行っている取り組みについてお話しします。具体的には、Acceptance Criteria(以下、AC)にテストケースを記載し、それを開発チーム全員でレビューするという方法です。 ACは、スクラムやアジャイル開発でよく使用されます。ACを導入する前は、ユーザーストーリーとテストケースが分かれていました。これにより、とくにテストの際に誤解が生じることがありました。 この新しいプロセスは、開発チーム全体に役立ちます!プロダクトマネージャー(以下、PM)、デザイナー、エンジニア、そしてQAエンジニアの皆が一緒により良く作業できるようになります。各チームメンバーのメリットを見てみましょう。 例えば、PMは、仕様の確認が容易になり、抜け漏れなく開発を進められるようになりました。以前は、テスト実施の段階で初めて仕様の矛盾に気づくことがありましたが、この活動を通して、早い段階で修正できるようになり、手戻りが減りました。 エンジニアは、フロントエンドとバックエンドの実装方針を事前にすり合わせることができ、スムーズな開発が可能になりました。 また、具体的な文言や表示方法をその場で確認することで、デザイナーからのフィードバックもリアルタイムに反映できるようになり、より質の高いプロダクト開発に繋がっています。 QAエンジニアにとって、テストデータの作成方法を共有し、テストを実行することは、テストフェーズでの作業改善に役立ちました。以前はテスト準備中にテストデータの作成についてエンジニアに相談する必要がありました。この新しいアプローチにより、テストの実行のしやすさに基づいて開発の順序について話し合うことが容易になりました。 チーム全体としては、複雑な開発手順でも、全員が共通認識を持って開発を進められるようになり、コミュニケーションコストが削減されました。また、複数プロジェクトが同時進行する際も、それぞれの進捗状況や依存関係を把握しやすくなるため、混乱することなく、スムーズにプロジェクトを進めることができています。それでは、この取り組みをどのように実践しているのか、詳しく見ていきましょう。 3つの取り組み 実施したことは3つです。 まず、テストケースをACに記載するようにしました。次に、レビュー方法を非同期レビューから同期レビューに変更しました。最後に、レビューする人を開発チーム全員としました。 ところで、みなさんは、普段テストケースをどこに保存していますか? また、誰がどのように活用していますか? 例えば…テスト管理ツールを使ったり、Google スプレッドシートや Excelで作成し、共有しているのではないでしょうか?このように、テストケースは様々な場所に保存され、活用方法も様々だと思いますが、多くの場合、QAチーム内でのみ参照され、他の開発メンバーはあまり活用できていないのではないでしょうか?テストケースの価値はQAエンジニアのみなさんは価値を理解していると思います。これを少数の人にのみ提供しているのはもったいないと思いました。 以前は、ユーザーストーリーとテストケースの関連性が弱く、重要なテストを見落とすリスクが高まり、開発プロセスの後半で手戻りが発生することがありました。テストケースはまるで隠された宝の地図のようでした。皆が利用可能であったにも関わらず、その価値は活用されていませんでした。チームはユーザーストーリー(島)に問題を抱えており、必要な助けがすぐそこにあることに気づいていませんでした。 以前は、適切なテストを見つけることは、多くの島が描かれた宝の地図を使うようなものでした。各島には宝がありましたが、地図で各島に何があるのかを探すのに時間がかかりました。今では、各島に標識があります!その標識がACです。それは、各ユーザーストーリーにどのテストが必要かを正確に教えてくれます。 例えば、標識は製品が何をすべきか、何をしてはいけないか、そしてどのようにテストするかを示してくれます。これにより、誰もが理解しやすく、質の高い製品を作り上げやすくなります。 Acceptance criteriaの例 このスライドは、私たちのACの例を示しています。私たちは、テスト対象、テストの条件、そして期待される結果を明確に定義します。 例えば、1行目では、テスト対象はタイトルとラベルの表示です。タイトルとラベルの両方に「ログイン」と表示されることを期待します。 2行目と3行目では、機能フラグの条件に基づいて、画面の期待される動作を定義します。 最後に、iOSとAndroidの両方で同じように動作することを確認するために、テストを行います。 明確な道標(AC)を設けることで、私たちのチームは開発をより効果的に進めることができます。その結果、チームのコラボレーションが改善され、エラーや手戻りが減り、高品質なプロダクトをより早く提供できるようになりました。チーム全員が目標と、そこに到達する方法を理解し、共に取り組んでいます。 効果的なレビューのシンプルなステップ 次に、私たちが実践しているレビュープロセスについてお話しします。このレビューは、開発チーム全体で品質に対する共通認識を醸成し、高品質なプロダクト開発を実現する上で非常に重要な役割を担っています。ACを中心に据え、テストケースの内容を全員で確認することで、認識のズレや手戻りを防ぎ、開発効率を向上させることができるのです。 次にレビューの方法についてお話します。とてもシンプルです!3つのステップがあります。 1つ目は、声に出して読み上げます。1人がそれぞれのACを声に出して読みます。各ユーザーストーリーに対するACとテストケースを確認できます。読み手はそれぞれの項目について簡単に説明します。 2つ目は、質問をすることです。読み上げの後、全員が質問できます。エンジニア、QAエンジニア、PM、デザイナー、誰でもです。様々な視点を持つことが重要です。例えば、 「このテストではどんなデータを使うの?」や「この部分は本当に必要?」あるいは、「ユーザーはこれを理解できるかな?」といった質問です。私たちは良い議論をすることができるようになります。 最後は、確認です。全員がACを理解していることを確認します。これでレビューは終了です。これらのレビューは、チームが最初から品質について合意するのに役立ちます。シンプルな3つのステップで、誰でも実行することができます。 なぜこの新しいレビュープロセスが効果的なのかについてお話します。私たちは、スペックレビューで要件を確認しています。しかし、その段階では、エンジニアとQAエンジニアは自身の領域に対して詳細まで理解することが困難なこともあります。それはぼやけた写真を見ているようなものです。今回のプロセスでは、スペックレビューの後、エンジニアは設計ドキュメントを作成し、QAエンジニアはACを作成し、テストを設計します。ここで初めて、各機能とユーザーストーリーに対する深い理解が得られました。 私たちの新しいレビュープロセスは、この詳細な検討の後に行われます。全員が要件について高い解像度の理解を持ってレビュー会議に参加します。これにより、より集中した生産的な議論が可能になります。共通の明確なビジョンを持って、潜在的な問題を特定し、詳細を洗練させることができます。プロセスの後半、全員が詳細な理解を深めた後にレビューを行うことで、整合性を確保し、誤解を最小限に抑え、最終的に品質の向上と手戻りの削減につながります。 このレビュー方式は、特別なスキルや経験がなくても、誰でも効果を実感できるのでしょうか? 私たちは、様々なスキルレベルのメンバーでこのレビュー方式を試しました。私自身の経験では、QAエンジニアとしてスクラムチームに所属していた時に初めてこの取り組みを始めました。当時は私一人で実施していましたが、チーム全体で効果を実感できました。 現在、私以外のQAエンジニアたちもこの取り組みを実施しています。もちろん、最初は戸惑うメンバーもいましたが、今ではスムーズにレビューを進めることができています。 なぜ、異なるスキルレベルのメンバーでも効果を実感できているのでしょうか?成功の鍵は、"共通認識" です。ACにテストケースを記載することで、エンジニア、QAエンジニア、PM、デザイナーなど、チーム全員が同じ情報を見て、同じレベルで議論できるようになります。 もちろん、もっと解像度をあげたいという改善すべき点もあります。しかし、このレビュー方式は、チーム全体の品質意識を高め、開発プロセスを改善するための大きな可能性を秘めていると確信しています。 本日は、ACにテストケースを組み込むことで、チーム全体の品質意識を高め、開発プロセスを改善する方法についてお話しました。テストケースをACに集約することで、誰でも簡単にテスト内容を確認できるようになりました。それによって開発チーム全員での同期的なレビューが可能になり、議論も活発化し、プロダクトに対する深い共通理解を得られるようになります。これらの変更によって、開発チーム全体が、開発初期段階から品質に関する共通認識を持ち、認識齟齬や手戻りを防ぐことができるようになりました。 私たちは、この取り組みをさらに発展させ、より効果的な品質保証活動を目指していきます。 みなさんも、ぜひこの方法を試してみて、チームで品質向上に取り組んでみてください! ご清聴ありがとうございました。何かご質問があれば、お気軽にお尋ねください。 この記事の内容が、みなさまのプロジェクトや技術的探求に貢献できたなら幸いです。引き続き メルカリ ハロ 開発の裏側 – Flutterと支える技術 – シリーズを通じて、私たちの技術的知見や経験を共有していきますので、どうぞご期待ください。また、 Mercari Advent Calendar 2024 の他の記事もぜひチェックしてみてください。それでは、次回の記事でお会いしましょう!
こんにちは。メルカリのSite Reliability Engineer (SRE)の @yakenji です。 この記事は、 Mercari Advent Calendar 2024 の2日目の記事です。 私たちメルカリのSREは、コアプロダクトであるフリマアプリ「メルカリ」の信頼性を維持・向上させるために、プロダクトのAvailability(可用性)とLatency(性能)を測定しています。また、それらに対してService Level Objective(SLO)を設定した上で、SLOを満たしているかや、一時的な障害などによりAvailabilityとLatencyが悪化していないかの監視を行っています。 その方法としてCritical User Journey (CUJ)に基づいたSLOを運用しています。今回、このSLOを見直し、以下を実現するSLOの再定義に取り組みました。 CUJの定義の明確化 各CUJに対して1対1となるSLIの定義 各CUJとSLOのメンテナンスの自動化 障害時の各CUJの挙動のダッシュボードによる可視化 本取り組みによりSLOのメンテナンスにかかる時間を 99%削減 するとともに、障害検知後に 影響範囲の特定にかかる時間をゼロ にすることを実現しました。 本記事では、上記の取り組みである User Journey SLO について、見直しするに至った背景と上記4項目の詳細、特にE2E Testを用いた自動化によるSLOの継続的最新化とその活用の取り組みを共有します。 現状の課題意識 本題に入る前に、メルカリにおける2種類のSLOと現状の課題意識を共有します。この章を通じて、なぜ私たちがUser Journey SLOの取り組みを始めたのか、何を目指したのかを知っていただけたら幸いです。 マイクロサービスごとのSLOとその課題 メルカリでは、バックエンドにマイクロサービスアーキテクチャを採用しています。例えば、ユーザー情報はユーザーサービス、商品情報はアイテムサービスというように、あるドメインごとにマイクロサービスとして独立しています(上記は一例であり実際とは異なります)。各サービスには必ずオーナーとなるチームが存在し、独立して開発・運用を行っています。各チームは自らの管理するサービスにSLOを設定したうえで、その目標を下回らないように開発フローを回すことが義務付けられています。また、このSLOをベースとしたモニターを作成することで、開発チームは管理するサービスの障害をアラートとして受け取り障害対応も行います。 開発チームがマイクロサービスごとに独立した開発・運用を行う上で、サービスごとのSLOを定義することは必要なアプローチと言えます。一方でサービスごとのSLOだけではいくつかの課題があります。その一つが、お客さま視点でのメルカリというプロダクトの信頼性を評価することが難しいということです。 各マイクロサービスはあるドメインの機能のみを扱うサービスです。お客さまが自身のアカウントの”ユーザー情報を編集する”ようなドメインに閉じたシナリオを想定した場合、関係するサービスはユーザーサービスだけで済むでしょう。この場合、”ユーザー情報を編集する” SLOの達成度合いはユーザーサービスのそれを用いることができるかもしれません。一方で、”購入された商品を発送する”のようなシナリオの場合、関係するサービスは複数にわたります。この場合には各サービスの達成度合いを単純に用いることはできません。 さらに、あるシナリオを想定した場合に実際に使用されるAPIは各サービスのごく一部でしかありませんし、サービスの開発チームはどのAPIがどのシナリオ・ページで使用されているか完全に把握することも困難です(APIは基本的に汎用的なものを用意し各ページで同じものを使用します)。反対にフロントエンドの開発者も、どのサービスにアクセスするかを厳密に意識することは通常ありません。 以上の背景から、マイクロサービスごとのSLOだけでは”購入された商品を発送できるかどうか”のようなお客さまが実際に感じる信頼性を評価できません。例えば上記の例でサービスA/B/CそれぞれのAvailabilityはSLOを満たしていたとしても、お客さま目線でのAvailabilityは想定よりも低いということが考えられます。また、障害対応の観点から見てみると、あるサービスAのアラートがトリガーされても、実際のお客さまに対してどのような影響が出ているのかを判断することもできません。これは障害の優先度の判断や、影響したお客さまへの対応時に問題になります。 SREにおけるSLO 上記の課題を解決するため、SREでは以前よりマイクロサービスごとのSLO以外の独自のSLOとして、Critical User Journey (CUJ)に基づいた私たちのプロダクトであるフリマサービス全体のモニタリングを行ってきました(CUJとはお客さまがプロダクトを利用する際に頻繁に行われる一連のシナリオのうち特に重要なものを指します)。 一方で、以下のような課題もありました。 定義が不明瞭: CUJの定義・CUJに関連するAPIの根拠などが記録されておらず、新たにCUJを追加することやメンテナンスが難しい CUJに対して1対多のSLOが存在: 関連APIのSLOを直接モニタリングしているため、関連APIが複数の場合に複数のSLOが存在し、お客さまが感じる信頼性の評価が難しい Updateが困難: 機能開発により関連APIは高頻度に変化し続けるが、人力での調査が必要なためメンテナンスコストが高く、最新の状態を維持できない SLOが悪化した場合の挙動が不明: 課題1・2と関連してSLOが未達となった場合に、お客さまにどのような影響が出るのかが明確化されていないため、対応の優先度の意思決定やSRE以外が使用することが難しい 特に課題3から、2021年ごろに設定してから十分なメンテナンスをすることができず、モニタリング対象のAPIの過不足がある可能性がありました。また、SRE以外も有効に使用することでメルカリ全体の信頼性向上やインシデント対応の改善に繋げることも背景として1から再構築することを決めました。 User Journey SLOの概略 まず、課題1の”定義が不明瞭”な点と課題2の”CUJに対して1対多のSLOが存在”する点に関連して、User Journey SLOにおいてどのようにCUJを定義・管理したか、どのようなSevice Level Indicaor (SLI)を定義したかを紹介します。 Critical User Journey (CUJ)の定義 User Journey SLOではこれまでのCUJを踏襲し、粒度を商品出品・商品購入・商品検索のように定めました。具体的な各CUJについても再検討を行い、大小含めて約40のCUJを定義しました。課題1を改善するため、すべてのCUJを画面操作とそれによる画面遷移という形で定義し、以下のような遷移図としてドキュメント化しました。さらに、各画面でAvailableな状態も定義し、それを満たしている場合をそのCUJがAvailable・満たしていない場合はUnavailableとしました(基本的に各CUJのコア機能が提供できればAvailableとし、サジェストなど動作しなくてもコア機能に影響しないサブ機能の動作は無視することにしています)。 SLIの決定 課題2を改善するため、定義したCUJを基に、各CUJのAvailablityとLatencyのSLIがそれぞれ1対1になるようなSLIを定義します。SLIはあくまでObservebilityツールで取得可能なメトリクスなどを使用して測定する必要があります。メルカリではBFFなどを用いたお客さまの1操作 = 単一のAPIコールのような構成ではなく、基本的に1操作で複数のAPIコールが発生します。 CUJの各画面が成功したかどうか直接的に測定できれば良いですが、現在そのような仕組みを持っていません。新たな仕組みを導入し直接的な測定を行うことも考えられますが、約40のCUJを全てカバーしつつ、iOS・Android・Web全てのクライアントでアプリを改修することは、とてもエンジニアリングコストが高く現実的ではありません。また、APMツールのRealtime User Monitoring (RUM)から取得することも検討しましたが、サンプリングレートやコスト、実現性の観点から現時点ではこれも困難と判断しました。 そこで、CUJの間に実行されるAPIを関連するAPIとして、そのAPIの成功率などのメトリクスを使用します。ただし、各操作で発生するAPIコールの中には(1) 失敗したらCUJがUnavailableになるもの、(2)失敗してもCUJがAvailableになるものの2つに大別できます。User Journey SLOではSLIをより正確かつロバストなものにするため、(1)に該当するAPIのみを”クリティカルな API”と定義しSLIの計算に使用することにしました。 クリティカルなAPIのメトリクスを使用して、以下の式でCUJのAvailability・LatencyのSLIをそれぞれ一意に定まるように定義しました。 Availability: 複数のクリティカルなAPIの成功率の乗算をCUJの成功率とする クリティカルなAPI A,Bの成功率を S A , S B とするとCUJの成功率 S CUJ は以下で計算 S CUJ = S A × S B Latency: 複数のクリティカルなAPIの目標レスポンスタイムの達成率のうち最も低い達成率をCUJの達成率とする クリティカルなAPI A,Bの目標達成率を A A , A B とするとCUJの成功率 A CUJ は以下で計算 A CUJ = min( A A , A B ) クリティカルなAPIの抽出 上記をSLIとして使用するためには、各CUJごとにクリティカルなAPIを抽出する必要があります。コードの静的解析などの方法も考えられますが、現実的に実現できるか等を勘案した結果、実際のアプリケーションを用いる形で以下の手順で抽出を行いました。 開発アプリケーションと開発環境の中間にプロキシを設置した上でCUJに基いてアプリを実際に操作、実行されたAPIを記録 プロキシで各APIのレスポンスが500エラーを返すように障害注入を設定してアプリを再度操作、CUJの基準を用いてAvailableかどうかを検証 ※クライアントとしては、メルカリのクライアントとして最も使用されているiOS版メルカリを使用しました。 通常クライアントアプリ-サーバー間の通信は暗号化されています。今回は暗号化された状態でも通信の確認とレスポンスの書き換えが可能なプロキシを選定しました。Webインターフェースを通じたインタラクティブな操作ができること、アドオンを開発することで必要な機能を追加できる点から、OSSの mitmproxy を採用しました。 これらの取り組みにより障害検知はCUJとともに通知されるため、障害検知後の影響範囲の特定にかかる時間がゼロになり、対応優先度の決定を瞬時に行うことを実現します。 iOS E2E Testを用いた継続的最新化と可視化 次に、課題3である”Updateが困難”なことを改善するE2E Testを用いたクリティカルなAPIを最新に維持する方法と、課題4である”SLOが悪化した場合の挙動が不明”な点を改善する障害時のアプリの挙動を可視化する方法を紹介します。 自動化の必要性 メルカリのiOSアプリは1ヶ月に複数回リリースされています。また、 トランクベース開発 によりフィーチャーフラグを使用してアプリのアップデートなしで新機能のリリースを行うことも可能です。これら全てのアプリの変更をSREが把握することは困難です。また、手動では高頻度のクリティカルなAPIの調査も困難です。結果として知らない間にクリティカルなAPIが変わり、必要なAPIをモニタリングできなかったり、不必要なAPIを過剰にモニタリングしてしまう事態につながります。そのため、アプリの変更に追従してクリティカルなAPIを定期的に更新するためには更新プロセスの自動化が必要です。 iOS E2E Testを用いた自動化 メルカリでは既にXCTestフレームワークを用いて iOSアプリのE2Eテストを自動化 しています。この既存資産を活用してクリティカルなAPIの抽出を自動化することにしました。 具体的にはまず、XCTestでCUJをテストケースとしてシミュレータで実行可能とします。さらにこのテストケースに対して、CUJで定義したAvailableな状態かを検証するアサーションを追加します。これによりXCTestで実行したCUJがAvailableな状態だったかどうかを自動で判別可能な仕組みが整いました。また、テストケースはアプリと同じリポジトリでバージョン管理されます。 XCTestとは別に、前章で用いたプロキシに対して”プロキシで記録したAPIリストの取得”と”任意のAPIに対する障害注入”を操作API経由で実行可能にするアドオンを開発しました。このアドオンにより、XCTestのテストケースやスクリプトからプロキシの操作が可能になりました。 上記のXCTestの実行とアドオン経由でのプロキシの操作をスクリプト化することによって、前章で示したクリティカルなAPIの抽出を自動で実行します。同時に、実行されたAPIがクリティカルなAPIかどうかの結果と障害注入時のアプリのスクリーンショットをそれぞれBigQueryとGoogle Cloud Strage (GCS)に記録します。 BigQueryに記録したテスト結果はIDで管理し前回実行時との差分を比較できるようになっています。また、APMに作成するSLO・Monitor・Dashboardの定義はUser Journey SLO用に開発したTerraform moduleを用いて行います。これによりクリティカルなAPIを定義するだけで、差分の適用や新しいCUJの追加を行うことが可能になりました。 上記の自動化により以下を実現しました。 コードメンテナンス以外の作業をほぼ全て自動化 テストケースとアプリの変更を同一のリポジトリでバージョン管理 テスト結果をID管理し差分を効率的にAPMに反映 最終的なテストケース数は約40のCUJに対して約60となりました。手動実行では困難な数のテストケースを自動化により効率的に運用することに成功しました。また、約60のテストケースを手動で実行してSLOのメンテナンスを行った場合に比べて、自動化によりメンテナンスにかかる時間を99%削減しました。 ダッシュボードによる結果の可視化 User Journey SLOの最終的に目指す姿の一つは、SRE以外も障害対応やお客さま対応で使用できるようにすることです。そのためには、最新のクリティカルAPIや障害発生時のCUJの挙動を誰もがアクセスできる形とすることが必要です。そこで、Looker Studioでこれらの結果を可視化し、各CUJのAPIコール一覧、APIの障害発生時にアプリのどの画面で失敗するかをスクリーンショットで可視化しました。 現状と今後 前章までの活動でUser Jouney SLOに対して以下を実現しました。 CUJの定義の明確化 各CUJに対して1対1となるSLIの定義 各CUJとSLOのメンテナンスの自動化 障害時の各CUJの挙動のダッシュボードによる可視化 その結果として、約40のCUJと約60のテストケースに対してSLOの運用を行っています。現状はSRE内で試験活用中の段階です。現時点でも新しいSLOの導入により、以下の項目の向上を体感しています。 障害発生検知の速度・精度 障害影響範囲把握の精度 障害原因特定の速度 品質可視化の精度 数値的には以下の効果を実現しました。 障害検知後の 影響範囲の特定にかかる時間がゼロ SLOのメンテナンスにかかる時間を 99%削減 この現状を踏まえて今後はSRE以外での活用を進め、以下の取り組みを行っていく予定です。 社内の障害基準としての活用 お客さま対応での活用 おわりに 本記事ではメルカリのCUJに基づいたSLO運用について、SLI・SLOの詳細やiOS E2E Testを使用した継続的な最新化の取り組みについて紹介しました。SLI・SLOの運用に取り組む方々に何かの参考になれば幸いです。 明日の記事は….rina….さんです。引き続きお楽しみください。
この記事は、 Merpay & Mercoin Advent Calendar 2024 の記事です。 はじめに こんにちは。メルペイでBackend Engineerをしている @hibagon です。2024年4月に新卒として入社しました。 この記事では、メルカリ新卒1年目のエンジニアがどのようなことをしているのかについてご紹介できればと思います。特にインターンや新卒としてメルカリで働くことに興味のある方の参考になれば嬉しいです! それでは早速Let’s Go! 4月🌸 4月はDevDojo(※1)をはじめとする新卒研修に大半の時間を費やします。DevDojoでは、Go言語などのメルカリで使用されている技術を中心に、さまざまな分野について横断的に学びます。 研修の合間の時間では、私のチームでの業務に用いるドメイン知識や開発に関する知識のキャッチアップを行いました。ただしこの時点では、オンボーディング資料があまり整備されていないという問題がありました。私のチームは比較的多くのインターン生を受け入れているということもあり、今後のためにも、キャッチアップ内容のアウトプットとしてオンボーディング資料を執筆することにしました。 ※1 技術トレーニングDevDojoで実際に使用されている学習コンテンツを公開しています。 こちら をご参照ください。 Gopherくん(原著者:Renée French) 5月🌿 ゴールデンウィーク明けからは、いよいよ本格的に配属先のチームに合流して業務を開始しました。 最初に取り組んだのは、リリースフローの改善でした。私のチームではgit flowを採用しており、毎週決まった日にリリースブランチとリリース用のPull Requestを作成する運用なのですが、当時はこれを手動で行っていました。これをGitHubActionsを用いて自動化しました。一見大きなインパクトが無いように思えるかもしれませんが、週に30分程度の定期業務を完全に自動化できたことにより、本記事を執筆している現在までの7ヶ月間で約2人日分の工数削減を達成できたことになります。 その他も小さなタスクを拾いつつ、業務に必要な知識のインプットを中心に行いました。そして5月末時点で、4月から継続的に書き進めていたオンボーディング資料のVer.1を書き上げました🍤 6月☔ 6月からは、業務内容が徐々に本格化していきました。 メルペイにはMerpay APIというBFF (Backend for Frontend) があるのですが、リリースから時が経つにつれMerpay APIが巨大化し、責務も曖昧になってきているという問題が発生しています。 これを受け、Merpay APIが持つ全てのAPIに対して、ひとつずつAPIに責任を持つチームを明確にし、Merpay APIを複数のBFFに分割することで管理することを目指したMerpay API Rearchitectureというプロジェクトが進行しています(※2)。 https://engineering.mercari.com/blog/entry/20231023-mmtf2023-day3-9/より引用 私のチームでは、Ownershipを持っているサービス中では比較的小規模な3D Secureに関するAPIから移行することを決め、このプロジェクトの担当として私がアサインされました。これに際し、 昨日のアドベントカレンダー でも解説されているgRPC Federationという技術を用いました。 さらに、7月中旬開始のメルカードのキャンペーンに向け、開発を行うための知識のキャッチアップを行いました。キャッチアップを行う中で、キャンペーンを行うための開発に関して、課題があることを理解しました。具体的には、キャンペーンのたびに一定規模の開発が発生していること、BFFであるMerpay APIにビジネスロジックが流出していることなどの問題がありました。こちらの課題を整理して、社内Tech Talkで発表しました。現在は改善されつつあります。 またこの頃から、オンコールのシフトにも加わりました。オンコールとは「サービスのパフォーマンスが悪化したり、停止が疑われたりする場合に備えて担当者が常時対応できるようにしておく仕組み」です。メルカリグループではPagerDutyを使用してオンコールシフトを管理しており、PagerDutyに紐づけられた特定のDatadog Monitorが閾値を超えた場合、自動で担当者を呼び出す仕組みになっています。 ※2 詳しくは 【書き起こし】gRPC Federation を利用した巨大なBFFに対するリアーキテクチャの試み – goccy【Merpay & Mercoin Tech Fest 2023】 をご参照ください。 7月🌞 7月前半は、7月中旬開始のメルカードのキャンペーンに向けた開発を急ピッチで進めていました。開発自体も(最初に取り組むちゃんとしたプロジェクトとしては)大変ではあったのですが、それ以上に他チームと連携するのが大変でした。キャンペーン開始を遅らせないために、エンジニアというポジションに関わらず積極的に進捗確認したり、課題を整理するなどの立ち回りを意識して行えたのは良かったポイントかなと思っています。QA中にも不具合など様々な問題が発生して、そのサポートも中々大変でしたが、結果としてはキャンペーンは予定通り開始されたのでよかったです。 実際のキャンペーンLPの一部 7月後半からは、インターン生のメンターを初めて担当させていただきました。私自身も入社してから3ヶ月程度しか経っておらず一抹の不安はありましたが、それ以上にワクワクもしていました。 7月中の目標としては、チームないしメルカリという会社に馴染んでもらうということを最優先に考えていました。そのため、積極的にご飯会を企画したり、社内部活に一緒に参加したりしました。また、この時のために温めていたオンボーディング資料を用いてキャッチアップを進めてもらい、必要に応じて加筆修正も依頼しました。オンボーディング資料は、このようにして新メンバーが加わった際に読んでもらいながら、最新の情報にアップデートしていく運用が良いと思っています。 8月🏖️ 8月は、メンター業務を引き続き行いながら、主に2つのタスクを行っていました。 1つ目は、先程述べたメルカードのキャンペーンの次に行うキャンペーンに向けた開発を行いました。7月は初めてということもあり、私のチームのメンバーに伴走してもらいながら進めていましたが、8月のキャンペーンでは、自走できるようになることを意識していました。 2つ目は、Merpay APIにある3D Secureに関するAPIの移行をgRPC Federationを用いて進めました。これは6月に着手していたのですが、7月は別のタスクを優先していたため、後回しになっていました。この頃から、Merpay API Rearchitectureプロジェクトに関しては、チームでのカウンターパートとして、私が情報収集や開発の主導をしていくことになりました。進捗としては、8月中にはおおよそ3D SecureのAPIの移行準備自体は完了しました。ただ他プロジェクトとの兼ね合いもあり、すぐに本番適用はしていません。 またこの頃に、新卒2年目以内の社員とインターン生が主体となって行うアイディアソンである「未現会議」というイベントの運営にも加わり、計画を進めていきました。 9月🍁 9月は、メンターをしていたインターン生のインターン期間が終了するので、それに関連したサポート業務がメインでした。具体的には、インターン生がキリの良いところまで開発を完了できるようにサポートしたり、インターン後に開始することになったQAのサポートを私が行えるようにするために、引き継ぎをしてもらったりしました。 初めてのメンターを終えたので、ここで振り返りをしておきます。 メンターとして心掛けていたのは、フランクに話せる関係を作り、何でも質問しやすい雰囲気を作ることでした。そのためには、同期的なコミュニケーションの機会が重要だと考え、毎日30分から1時間ほど1on1の時間を取りました。そこでは、業務を進めていくためのインプットや質問対応だけでなく、仕事以外の雑談も交えるようにしていました。また、1on1の時間でも、いつでもメンション飛ばしたり、スポットの1on1セッティングしていいからね!という強調も意識的に行いました。結果としてインターン生が高いパフォーマンスを発揮してくださり、新卒内定のオファーをもらうまで至ったのはメンターとしてとても嬉しく思いました。 余談ですが、インターンお疲れ様会でプレート入りデザートをサプライズで頼んでおいたら、インターン生からもチームメンバーからもとても好評で嬉しかったです。 その他には、引き続きキャンペーンのための開発業務も行っていました。この頃には、ほぼ自走できるようになっていました。 ところで、9月中旬のメンター業務が終了したタイミングで少し長めの休暇をもらい、オーストラリア🇦🇺に旅行に行きました。シドニー港は、世界三大美港に数えられるだけあって、とても美しかったです。また、コアラやクジラなどの様々な生き物に触れ合ったり、砂漠やユーカリの森などの広大な自然を体感して、とてもリフレッシュできました。 クジラのジャンプ(ブリーチ) シドニーの夜景 10月🎃 10月は、なんと早くも2人目のインターン生のメンターを行うことになりました。これは他チームの都合もあり急遽決まったのですが、メンター自体は初めてではなかったので、それ程不安はありませんでした。 例によって10月前半は、チームやメルカリという会社に馴染んでもらうということを意識していました。また、オンボーディング資料を用いてキャッチアップを進めてもらいつつ、加筆修正も加えてもらいました。オンボーディング資料の改善サイクルが良い感じに回ってきたので、この流れを止めることのないようにしたいと思います。 メンター業務以外では、10月中に開始するキャンペーンのQAサポートと、3D Secureに続いてメルペイのiDやバーチャルカードに関するAPIの移行を進めて行きました。 さらに、別の小さめのプロジェクトでは、Spec作成から開発までを受け持つことになりました。小規模ではありましたが、それまではちゃんとしたSpecを書いたことがなかったため、とても良い経験になりました。 またこの頃から、「未現会議」に関するタスクも少しずつ増えてきました。 11月🍂 11月は、内容は伏せますが大玉の開発案件があったため、チーム一丸となってAll for Oneで、その開発を進めていました。ありがたいことに、インターン生もとても活躍してくれました。 今まで述べてきた通り、私はチーム内ではgRPC Federationの知見が一番あるため、gRPC Federationを用いたBFF用新規マイクロサービスの構築を担当しました。 新規マイクロサービスを1から作成するという経験はあまり出来ないのでとても面白く、知見も深められました。特にサービスのリソース設定、モニタリング設定、オンコールの設定、本番環境で新規マイクロサービスを動かすために必要なチェックプロセスなどを通して、普段私のチームが持っているマイクロサービスに関する解像度も高まりました。 また、11月は「未現会議」のアイディア募集期間ということもあり、アナウンスやLunch & Learnの開催など、イベント盛り上げのためのタスクにも、運営チームとしてAll for Oneで取り組みました。 https://about.mercari.com/about/about-us/ より引用 12月以降🎄 この記事を書いている12月以降は、次に控えている大玉の開発案件や、Merpay API Rearchitectureの継続進行、「未現会議」のイベント成功などに向けて尽力していきたいと思います! また、年明けから3人目のインターン生のメンターをさせていただくことになったので、引き続きメンター業務の方も頑張っていこうと思います💪 ところで、今年の年末年始は有休を使わなくても9連休あるということで、非常に楽しみですね!特に今年はスノーボードセットを一式揃えたので、たくさん滑りにいけたらと思っています🏂 まとめ 本記事では、メルペイ新卒1年目のエンジニアがどのようなことをしているのかについて、私自身の振り返りの意味も込め、できるだけ詳細に書いてみました。メルカリは、新卒1年目から1人のプロとして扱われ、とてもチャレンジングなことを任せてもらえる環境です。この記事が、そんなメルカリでインターンや新卒として働くことに興味のある方に対して、具体的なイメージを広げる助けになれば嬉しいです。インターンや新卒採用に関しては、 こちら をご参照ください! 次の記事は@timoさんです。引き続きお楽しみください。
Mercari DBRE( D ata B ase R eliability E ngineering Team)のtaka-hです。 本エントリでは、メルカリの開発環境のデータベース移行の事例を紹介します。なお記事は、 MySQL Advent Calendar 2024 、および TiDB Advent Calendar 2024 とのクロスポストになります。 この記事の手法を用いて、開発環境では、MySQLからTiDBへ移行すると同時に、スキーマ統合するということを達成しています。本記事により、読者の方々がProxySQLや、TiDBのエコシステムに興味を持っていただけることを期待しています。 TL;DR TiDBのData Migration(以後、TiDB DMと呼びます)のTable Routingを用いると、TiDBにスキーマ統合したインスタンスを作成できる 2段のProxySQLを構成し、1段目でルーティングを、2段目でinit-connectにスキーマ変更を設定することによって、アプリケーションの滑らかな移行が達成できる 次の構成図を、説明しきることが本エントリのゴールです。 2段ProxySQLによるスムーズなアプリケーション移行の実現 背景 本節では、今回、スキーマ統合に至った経緯を簡単に説明します。 メルカリの本番環境では、サービス提供開始時から必要なデータ保存量やトラフィックの増加に伴い、垂直シャーディングを繰り返してきました。すなわち、データの性質が異なり、結合クエリの発行が必要ない範囲に関しては、ソースおよびレプリカのセット(以後、クラスタと呼びます)を分け、複数のクラスタで運用をしています。 垂直シャーディング 一方で、開発環境ではデータ規模が商用環境ほど大きくないため、運用をシンプルにするためにも単一のクラスタで運用をしています。 ただ、この垂直分割は一定回数繰り返すことで、分割の難易度は次第に高くなります。やがて、大きなクラスタをこれ以上分割するのが難しくなり、データベースのスケーラビリティを解消するために、非常に複雑で骨の折れる、データおよび機能実装の分離が、アプリケーションに求められるようになります。反対に分割を行わないと、より高性能なハードウェアが求められ、マネージドサービスで所望のインスタンスクラスがない、また、ハードウェアの調達時間が長期化しやすい、といった悩みもありました。そこでメルカリでは、MySQLの互換性が非常に高く、スケーラビリティーの優れたTiDBへ移行することを決断しました。 垂直シャーディングは次第に難しくなる 次に、 垂直シャーディングとスキーマの関係について説明します。 クラスタ構成が開発環境と商用環境で異なるため、アプリケーション開発はこれを前提に行う必要があり、開発時にあらかじめテーブルの結合などが必要ない範囲を定義し、開発環境では、そのまとまりごとにスキーマを作成していました。 開発環境と商用環境のクラスタ/スキーマ構成 開発環境と商用環境の差分のイメージ 開発環境でスキーマをまたがなければ、商用環境でクラスタをまたぐ心配はなく、そのような観点から、アプリケーションはスキーマをまたぐようなクエリは発行しないような実装としていました。 他方で、開発環境と商用環境のスキーマの環境差分は、ユーザー定義、他システム連携のシステム構成など様々な環境差分につながり、開発環境の構成が過度に複雑化したり、開発環境での様々なテストの実効性を低下させることにつながっていました。そこで、TiDBの移行の決定とともに、元々抑止しようとしたクラスタまたぎのクエリを事前に抑制することが一時的に達成されなくなる点を加味しても、スキーマ統合を行いこの環境差分を解消することにしました。 切替の作戦 本節では、MySQLで複数のスキーマに分かれていたデータへのクエリを、TiDBにどのように安全かつスムーズに移行していくか、についての考慮事項について記載します。 対象としているデータベースは、利用しているアプリケーションがとても多く、複数箇所のデータベースの接続の設定変更に、時間がかかることが予想されました。各チームは異なる開発サイクル(Sprint)を持ち、DBREで事前に十分にテストを行っているとはいえ、切戻しも考えられました。 そこで、以下の2点を考慮の上、最終的には ProxySQL というプロキシを下図のように多段に構成し移行をすすめました。 切替を各アプリケーションで個別に行うのではなく、DBREで一元的にコントロールする アプリケーションの必要なコンフィグ修正のタイミングに柔軟性をもたせる 2段ProxySQLによるスムーズなアプリケーション移行の実現 なお、本移行においては、ProxySQLの機能としては非常に重要であり、これを目的に利用する人が多いであろう、 Multiplexing と呼ばれる接続を多重化する機能を無効化して利用しています。 移行後のデータの準備 本節では、移行後のデータの準備について説明します。 MySQLからTiDBへの移行には、 TiDB DM を利用します。 TiDB DMはMySQLのbinary logの情報を元に、DDLを含む変更/差分ログをダウンストリームのTiDBに反映することができます。 TiDB DMによる移行先データの準備 TiDB DMでは、初期のデータ同期および、差分の同期がどちらも可能で、大量のデータがある場合は、TiDBに対して、論理データを非常に高速に物理インポートする TiDB lightning と合わせて利用することで、高速にデータが準備できます。 スキーマ統合については、TiDB DMの標準機能である Table Routing を利用することにより、非常に簡易に解決されます。アップストリームのMySQLの、どのスキーマのどのテーブルを、ダウンストリームのどこに移動するか、を routes という設定に RE2 syntaxの正規表現で記載するだけで、統合したデータが準備されます。 下記に、TiDB DMのコンフィグ例 (一部抜粋)を記します。 mysql-instances: - source-id: "dev-database" route-rules: ["route-mercari", "route-mercari-admin"] target-database: host: tidb.internal.mercari.jp. routes: route-mercari-admin: schema-pattern: "mercari_dev_jp_admin" target-schema: "mercari_admin" route-mercari: schema-pattern: "mercari_dev_jp_master|mercari_dev_jp_ads|..." target-schema: "mercari" 2段ProxySQL 本節では、2段ProxySQLにより先程の要件をどのように達成するかを順に説明します。 1段目のProxySQLは、トラフィックシフトを行う役割を持ちます。 ProxySQLでは、 mysql_query_rules という設定で、様々なルーティングなどが可能で、移行割合を weight というパラメータにより、コントロールできます。 これにより、アプリケーションからのレプリカに対する10%のトラフィックをTiDBに、残りの90%をMySQLにルーティングする、といったことなどが実現できます。 また、TiDBへのトラフィックに関しては、1段目のProxySQLが接続するスキーマに応じて適切な2段階目のProxySQLにルーティングします。 これによりアプリケーション開発者が設定を変更することなく、MySQLからTiDBへ切り替わっていく状況が達成されました。 mysql_servers= ( { address = "mercari-admin-proxysql-router-ilb.internal.mercari.jp." # ProxySQL(mercari_admin) hostgroup = 2 weight = 100 }, { address = "mysql.internal.mercari.jp." # 移行元MySQL hostgroup = 2 weight = 0 }, { address = "mercari-proxysql-router-ilb.internal.mercari.jp." # ProxySQL(mercari) hostgroup = 3 weight = 100 }, { address = "mysql.internal.mercari.jp." # 移行元MySQL hostgroup = 3 weight = 0 }, { address = "tidb.internal.mercari.jp." # 移行先TiDB hostgroup = 6 weight = 100 } ) mysql_query_rules= ( { schemaname = "mercari_dev_jp_admin" # 旧スキーマ flagOUT = 1 }, { schemaname = "mercari_admin" # 新スキーマ flagOUT = 2 }, { schemaname = "mercari" # 新スキーマ flagOUT = 2 }, { flagIN = 1 destination_hostgroup = 2 # ProxySQL(mercari_admin) or MySQL }, { destination_hostgroup = 3 # ProxySQL(mercari) or MySQL }, { flagIN = 2 destination_hostgroup = 6 # TiDB } ) 2段目のProxySQLは、アプリケーションが古いスキーマに接続する設定で動作する際に、スキーマ統合後のTiDBに透過的にアプリケーションを実行させる役割を持ちます。 現在、移行元のMySQLと、移行先のTiDBでスキーマ構成が異なりますから、何も考慮せずにMySQLのトラフィックをTiDBへシフトさせると当然失敗する訳です。スキーマが異なったとしても、同じアプリケーションの設定で、重み付きで少しずつトラフィックをシフトできたら、事前のテスト可能性が向上し、大変望ましいと思います。 この問題を、MySQLサーバーのグローバルスコープのパラメータである、 init_connect に関連したProxySQLの init_connect パラメータでスキーマ変更をさせることにより、解決させます。 mysql_variables= { … init_connect = "use mercari_admin" … } mysql_servers= ( { address = "tidb.internal.mercari.jp." hostgroup = 1 weight = 100 ) 実例にて、動作を確認しましょう。 mercari_dev_jp_adminというスキーマがmercari_adminのスキーマに、スキーマ統合(移行)されるケースを考えます。アプリケーションのスキーマの設定は、古いスキーマ mercari_dev_jp_admin だったとしましょう。 このとき、次の処理の順番により、アプリケーションから見て、スキーマ統合された新しいスキーマのテーブルに対して透過的にクエリが発行されます。 接続フェーズでアプリケーションは mercari_dev_jp_admin (旧スキーマ)に接続を試行 ProxySQLのinit_connectにより接続スキーマが mercari_admin (新スキーマ)に変更される クエリはmercari_admin (新スキーマ)上で実行される 透過的なクエリ発行の例 1点付け加える必要があるのは、移行後のダウンストリームのデータとして、スキーマ統合をした実データとともに、移行前の空のスキーマ(テーブルなどのデータは存在しなくて良い)が必要であることです。 移行先のスキーマ構成 また、本構成が利用できる前提条件として、そもそもクエリにスキーマを変更したり、スキーマを指定してクエリを発行するといった実装がない、という前提があります。メルカリのケースでは移行の背景からも、スキーマを指定してスキーマをまたぐクエリが発行される心配は、元からありませんでした。 この設定の追加によりもたらされるメリットは2つあります。 読取りトラフィックを、新・旧スキーマに重み付きで流す、といった 段階的な移行 が可能になることで、より安全にトラフィックが移行できる アプリケーションのスキーマ設定は、新・旧どちらでも動作し、 コンフィグの並行運用期間 が取れるため、各開発チームのタイミングで設定の移行を柔軟に進められる 実際には、トラフィックを全て切り替えた後、 動作が問題ないことが確認されてから 、各アプリケーションにクライアントの 接続先スキーマの設定変更 依頼を行い、これによりメルカリの開発環境のスムーズな移行が達成されました。(※空のスキーマを誤って不要だと錯誤し、削除してしまうというミスは発生しました) なお、本切替のTiDBへのトラフィックシフトの前には、事前に 主要な利用シナリオ用に作成したベンチマークシナリオによるTiDBの性能評価 想定される遅延増加を、擬似的にデータベースレスポンスに付与し、アプリケーションへの影響を評価、アプリケーションの修正が必要な箇所を特定 現行のクエリをキャプチャし、TiDBへリプレイし問題の有無を確認 ということを終えておりました。 終わりに この記事では、メルカリの開発環境で、MySQLからTiDBへ移行すると同時にスキーマ統合する際に用いた手法について説明しました。 多数のアプリケーション接続をかかえるデータベースを、アプリケーション設定変更は実際に移行が成功した後に、順次行える構成を考え、移行を成功させました。ポイントは次の2点です。 TiDBのData MigrationのTable Routingを用いて、TiDBにスキーマ統合したインスタンスを作成 2段のProxySQLを構成し、1段目でルーティングを、2段目でinit_connectにスキーマ変更を設定 メルカリでは、絶え間なく変化するアプリケーションやトラフィックに柔軟に対応するため、クラウドサービスや、ProxySQLなどのOSSソフトウェアを活用したり、必要な機能を独自に実装を行い、問題の解決にチャレンジしています。 様々な課題に意欲的にチャレンジする仲間を募集しています。 https://careers.mercari.com/bloom/
この記事は、 Merpay & Mercoin Advent Calendar 2024 の記事です。 こんにちは、メルペイ BackendエンジニアのSakabeと申します。 私の所属するKYCチームでは、主に本人確認に関するマイクロサービスの開発を行っています。 現在、メルペイの各チームが共通で使用する大規模なBFF(Backend For Frontend)がその巨大さゆえに管理や拡張が難しくなっています。この問題に対応するため、各チームごとにBFFを切り出す取り組みを進めています。KYCチームではこの対応として、 こちらの記事 で紹介されているgRPC Federationの仕組みを活用し、効率的にBFFサービスの立ち上げを行いました(gRPC FederationのOSSリポジトリは こちら )。 今回の記事では、実際のBFFサービスの構築に使われたprotoファイルと実際に使用したgRPC Federationのoptionを紹介することでgRPC Federationを用いた開発を紹介します。また、開発中に感じたことを説明します。 gRPC Federationを採用した背景 KYCチームがBFFを新規作成する際、gRPC Federationを採用した背景について説明します。巨大なBFFをKYCチーム専用のBFFに切り出すにあたり、gRPC Federationを活用する方法と、ゼロからBFFを構築する方法がありました。その中でgRPC Federationを選んだ理由は以下の2点です。 1点目は、少ない手間でBFFが構築できることです。具体的には、Protoファイルにオプションを記述するだけで、BFFのコードを自動生成することが可能です。これにより、シンプルなロジックについては、ゼロからBFFを構築するよりも開発コストが低いと判断しました。 2点目は、社内に基盤が作られていることです。他のチームでも実際に使われているため、デプロイまでのフローが整備されており迅速なリリースが可能と判断しました。 これらの理由から、KYCチームではBFFを新規作成する手段としてgRPC Federationを採用しました。 gRPC Federationを使用した実際のBFF開発 KYCチームではgRPC Federationを利用して新規のBFFを開発しました。今回は、マイクロサービスに新規実装したエンドポイントをそのまま中継するだけのBFFを作成します。 今回はその様子を以下の順番で紹介していきます。 ベースとなる新規サービスのProtocol Buffers定義の作成 gRPC Federationのoption設定 生成されるGoファイル 1. ベースとなる新規サービスのProtocol Buffers定義の作成 今回新たに公開するマイクロサービスのProtocol Buffersの定義(抜粋)です。リクエストは空でレスポンスでは2つの要素を返しています。 service KYCMicroService { rpc GetConfirmation(GetConfirmationRequest) returns (GetConfirmationResponse) { }; } message GetConfirmationRequest {} message GetConfirmationResponse { bool need_confirmation = 1; ConfirmationEvent confirmation_event = 2; } 2. gRPC Federationのoption設定 このマイクロサービスを中継するためにBFFを作成します。gRPC Federationを使用するため、実際のGoのコードを書く必要はなく、protoファイルにgRPC Federationのoptionを記述しBFFのコードの自動生成を行います。 マイクロサービスのprotoファイルをコピーし、以下のような追記を行いました。 service MerpaySharedKYCAPI { option (.grpc.federation.service) = { }; rpc GetConfirmation(GetConfirmationRequest) returns (GetConfirmationResponse) { }; } message GetConfirmationRequest {} message GetConfirmationResponse { option (.grpc.federation.message) = { def{ call {method : "mercari.path.api.v1.KYC/GetConfirmation"} autobind : true } }; bool need_confirmation = 1; ConfirmationEvent confirmation_event = 2; } まず、serviceにgRPC Federationを利用するためのoptionを記述します。 option (.grpc.federation.service) = {}; 次にmessageのResponseを定義するためのoptionを記述します。 option (.grpc.federation.message) = { def[ { call { method : "mercari.path.api.v1.KYC/GetConfirmation" } autobind : true } ] }; 今回使用したのは、 (grpc.federation.message).def.call と (grpc.federation.message).def.autobind です。 (grpc.federation.message).def.call は、gRPCメソッドを呼び出し、レスポンスを変数に代入します。ただし今回は後述する(grpc.federation.message).def.autobindによる自動的な代入を使用しているため、gRPCのレスポンスを代入する変数を省略しています。 (grpc.federation.message).def.autobind は、callのレスポンスのmessageと自身のfieldの名前と型が一致する場合にfieldの値を自動的に代入します。 逆に一致しない場合は、以下の例のようにレスポンスのfieldを明示的に指定する必要があります。call の結果を res に代入し、res の field である foo の結果を need_confirmation 変数に代入しています。 option (.grpc.federation.message) = { def[ { name: "res" call { method : "mercari.path.api.v1.KYC/GetConfirmation" } } ] def { name: "need_confirmation" by: "res.foo"} ... }; つまり、callとautobindのoptionの記述によって、KYCのマイクロサービスのgRPCエンドポイントをcallした結果がfieldに自動的に代入されます。 3. 生成されるGoファイル grpc-federation-generatorコマンドを実行することでprotoファイルからコードを生成することができます。 上記のprotoファイルから、以下のようなGoのコード(疑似コード)が生成されます。 func (s *MerpaySharedKYCAPI) getConfirmationResponse(ctx context.Context, req *GetConfirmationRequest) (*GetConfirmationResponse, error) { // 新しいレスポンスを作成 res := &GetConfirmationResponse{} // APIへリクエストを送信し、結果を取得 apiResponse, err := s.client.GetConfirmation(ctx, &ExternalAPIRequest{}) if err != nil { logError(ctx, err) return nil, err } // 必要な情報を取得しレスポンスへ設定 res.NeedConfirmation = apiResponse.NeedConfirmation // 追加の情報を変換しレスポンスに追加 confirmationEvent, err := s.convertEvent(apiResponse.ConfirmationEvent) if err != nil { logError(ctx, err) return nil, err } res.ConfirmationEvent = confirmationEvent // 処理が成功した場合、最終レスポンスを返す return res, nil } gRPC Federationを使用してBFFの開発した気づき 今回、gRPC Federationを用いてBFFを開発する中で、以下の4つの点でさまざまな発見や気づきを得ました。 短期間でのBFF構築の可能性 高い表現の自由度 技術導入時のコスト評価の重要性 実行順の依存関係の明確化 以下、それぞれのポイントについて詳しく掘り下げていきます。 短期間でのBFF構築の可能性 1点目は、短期間でのBFF構築が可能であることです。 BFFのprotoの実装は1日で終了しました。今回のようなマイクロサービスのAPIをプロキシするだけのBFFであれば、optionを10行程度追加するだけでBFFを生成できるのは大きなメリットだと感じています。gRPC Federationを選ぶ理由として定型作業の簡素化があげられており、今回のようなマイクロサービスのAPIを中継するだけのBFFの実装はまさに定型作業といえます。gRPC Federationではprotocol buffers上でメッセージの対応関係を記述することで、型変換処理を全て自動生成しています。 また、私自身はGo言語に関する専門的な知識を持っていませんが、gRPC Federationによってコードが自動生成されることで、Goにおける最新のベストプラクティスを享受し、ハイパフォーマンスなコードを利用できます。もし同じ品質のものを開発する場合、もっと時間がかかると思います。その点で、gRPC Federationを使えばGoの初心者であっても短期間で高品質の成果を得られます。 さらに、社内ではgRPC Federationを利用したBFFをモノレポで運用しており、そのレポジトリではBFFを迅速にデプロイする環境が整えられています。 高い表現の自由度 2点目は、表現の自由度が高いということです。 現在はCEL(Common Expression Language)のサポートが行われています。proto上で計算を行ったり、計算結果を元にAPIの呼び出しの分岐を書いたりすることも可能です。また、protoからGoのコードを呼び出すことができるため、他のライブラリを使うなどの用途で一部の処理を切り出すこともできます。 このようにgRPC Federationのoptionでできないことを柔軟に実装することが可能です。 技術導入時のコスト評価の重要性 3点目は、新しい技術導入時のコストとメリットを評価する必要があるということです。 新しい技術を導入する際には、そのコストに対してメリットが上回るかを慎重に評価する必要があります。普段はGo言語で開発しており、Goでも十分に開発が可能です。しかし、新しい技術がそれを上回るメリットを提供するかどうかを判断することが求められます。 私自身は、gRPC Federationのオプションに対する生成コードをある程度想像できていますが、チームメンバーは一から学ぶ必要があり、そのためコードレビューが難しくなる場合があります。また、新しいメンバーが加わった際に、BFFに少しの修正を加えるにしても時間がかかることが多いです。そのため、gRPC Federationを導入した際には、チーム内で的確な開発サイクルやリリースフローをしっかりと周知する必要があると感じています。 これはgRPC Federationに限らず、どのような新しい技術を導入する時にも一般的に言える課題であり、gRPC Federation固有の問題ではないことを明記しておきたいです。 実行順の依存関係の明確化 4点目は、実行順の依存関係を明確にする必要があることです。 依存関係を設定しない場合、処理は並列で行われ、最適化された状態で自動的に呼び出されます。これにより、開発者が自らパフォーマンスチューニングを行わなくても済むという利点があります。 しかし、特定の順序での実行が求められる場合は、依存関係を設定する必要があります。例えば、先に実行したいmessageを次のmessageで使用することで、依存関係を明示し、処理を直列に実行させることが可能です。Goのコードであれば、処理を単に上から順に書いていくことで明確な順序を保てます。しかし、これは依存関係のない処理同士が無駄に順番待ちをする可能性もあります。 依存関係を使って実行順序を制御し始めると、並列に実行されるのか、直列に実行されるのかがわかりにくくなる可能性があります。そのため、単純に上から実行されるようなGoのコードであっても、複雑なprotoファイルになることがあります。これを避けるためには、BFF層に複雑なロジックを書かないことが重要です。 optionを駆使すれば複雑なprotoファイルを実装することも可能ですが、その前にロジックを理解し、それをマイクロサービスで処理することが求められます。私個人としては、ロジックを持っているBFFをマイグレーションする際、そのコードを深く理解し、protoに落とし込みやすくする工夫をしています。 まとめ 今回は、gRPC Federationを用いたBFFの開発について紹介しました。KYCチームでは今回のgRPC Federationによる新規BFF開発を足掛かりとして、既存のBFFマイグレーションを行う予定です。もしgRPC Federationに興味のある方は、実際に手を動かして試してみてください! 次の記事はhibagonさんです。引き続きお楽しみください!
この記事は Mercari Advent Calendar 2024 の1日目の記事です。 メルカリでは流出すると多大な影響を及ぼすような有効期限が長い認証情報を減らすために、有効期限が短い認証情報を発行する仕組みを様々な箇所で導入しています。そして、Platform Security Teamでは社内で運用されていたGitHubの認証情報を生成するToken Serverというサービスを拡張することで、Google Cloud上で動作しているGitHubの自動化サービスが保持していたGitHubへのアクセスに使用する認証情報を有効期限が短いものに切り替えました。 このToken Serverの拡張と移行で用いた技術や課題、解決策について紹介します。 概要 メルカリではGitHubを中心とした開発環境を利用しており、その中でGitHubの運用を自動化する様々なサービスを開発・運用しています。 これらのサービスはPAT(Personal Access Token)やGitHub Appの秘密鍵を使いGitHubへアクセスします。しかしこれらの認証情報は有効期限が設定されていない、もしくはとても長く設定されています。これらの認証情報がサプライチェーン攻撃などによって漏洩すると長期に渡り攻撃者から悪用されるリスクがあります。また、これらの認証情報は一度作成されてしまうと多くの場合、どの認証情報がどのサービスで使われているのか不透明になりがちで、かつ付与された権限が見直されることが少ないです。 これらの課題を解決するために、すでにメルカリ社内で運用されていた、有効期限が短いGitHub認証情報を発行するToken Serverというサービスを拡張し、Google Cloud上で動作しているサービスが有効期限が長い認証情報を使うことなくGitHubにアクセスできるようにしました。 これにより、以下のようなメリットを実現することができました。 有効期限が長い認証情報の削減 管理が不透明な部分が多かったPAT及びGitHub Appの削減 認証情報を付与する対象・必要な権限を1つにまとめて管理することで、認証情報の対象の特定と権限の定期的な見直しを簡略化 また、既存のPATや秘密鍵を用いるサービスの実装を大きく変えることなく、Token Serverに移行できるようなGo言語のライブラリも合わせて開発することで移行をすぐに行うことができました。 Token Server メルカリでは様々な形態でGitHubを運用しており、特にGitHub内での自動化においてはあるレポジトリの変更を別のレポジトリに同時に適用するといった複数レポジトリをまたぐ自動化が頻繁に行われています。 特に社内で標準的に使われているCIであるGitHub Actionsにおいて、複数レポジトリをまたぐ自動化を行うための認証情報はデフォルトでは提供されておらず、一般的にはRepository SecretにPATを保存するか、GitHub Appの秘密鍵を保存し create-github-app-token action などで生成する必要があります。しかし、これらはPATおよびGitHub Appの秘密鍵という有効期限の長い認証情報が必要です。 そこで以前からメルカリは、GitHub Actionsのワークフロー内部で取得できる、GitHubが署名し偽造が困難なOIDC Tokenを受信し検証することで、特定の権限を持つ Installation Access Token を提供するToken Serverサービスを運用しています。 Installation Access TokenはGitHub Appの機能で、GitHub Appで事前に設定されている権限(contentsのread権限・Pull Requestのwrite権限など)のサブセットを特定のレポジトリに限定し発行できるトークンです。またInstallation Access Tokenには1時間の有効期限があり、さらにGitHub API経由で有効期限を待たずに失効させることも可能です。これにより最小権限の原則に従った、限定された権限・アクセス範囲・有効期限を持つ認証情報を提供することができます。 Token Server for GitHubのアーキテクチャ Token Serverは事前に設定されたGitHub Appから対象のレポジトリとブランチごとに設定された権限を持つInstallation Access Tokenを作成し各レポジトリ上でのGitHub ActionsのJobに提供します。この際、レポジトリ名及びブランチ名を特定するため、GitHub ActionsのJob上で取得できるOIDC Tokenを利用します。Job上でOIDC Tokenを取得、Token Serverへ送信、Token Server内でOIDC Tokenの検証、レポジトリとブランチごとに設定された権限を検索、Installation Access Tokenを作成・発行を行います。 Token Serverから発行されるInstallation Access Tokenは複数レポジトリ間のGitHub運用の自動化(コミットの追加・Issueの自動作成・Pull Requestの自動作成など)だけでなく、CIでのビルドにおける内部のライブラリのダウンロードなどにも広く使われています。 (注) 2024年4月にChainguard社から Octo STS がリリースされました。基本的な動作原理はToken Serverと同じですが、Token Serverではより統一された権限管理をサポートしている他、後述するGoogle Cloud上へのワークロードへの組み込み・GitHub AppのLoad Balancingなどよりエンタープライズ環境に適したアーキテクチャが採用されています。 Token ServerのGoogle Cloudへの拡張 メルカリでは多くのサービスをGoogle Cloudで運用しています。これはお客さまへのサービス提供を目的としているマイクロサービス郡だけではなく、内部向けの自動化サービスも同様です。これらのサービスはGitHubにアクセスするためにPATやGitHub Appの秘密鍵を使用していました。 Google Cloudの各リソースには他のリソースを操作する権限を付与するためのService Accountが付与されており、更にroles/iam.serviceAccountTokenCreatorが付与されている場合、Service Accountが付与されているGoogle CloudリソースはAPIを通してGoogleが署名したOIDC Tokenを取得することができます。このOIDC TokenをGitHubの場合と同じようにToken Serverで検証し、事前に設定された権限を持つInstallation Access Tokenを発行するよう、Token Serverを拡張することにしました。 Token Server for Google Cloudのアーキテクチャ これにより、ある特定のGoogle Cloudリソース上で動作しているサービスが、Token ServerへOIDC Tokenを送信し、Installation Access Tokenを発行してもらうことで、GitHubへのアクセスができるようになり、今までGoogle Cloud上で使用していたPATやGitHub Appの秘密鍵をなくすことができます。 Google Cloud上のワークロードへのToken Serverの適用 Token Serverの拡張により、Google Cloud上で動作しているサービスがGitHubへのアクセスに使用する認証情報を有効期限が短いものに切り替えることができるようになりました。 これらの新規機能をGoogle Cloud上に新しく作成するサービスに適用することは比較的容易です。しかし今までGitHub PATやGitHub Appの秘密鍵を使っていたサービスは多くの場合、機能が作り込まれており、Token ServerへInstallation Access Tokenをリクエストし、取得したInstallation Access Tokenを使うように変更することが難しい場合があります。 そして、GitHub AppにはAPIの利用数の制限があります。これはGitHub Appごとに設定されており、GitHub Enterprise Cloudを利用している場合、1時間あたり15000回のAPIリクエストが可能です。この制限を超えるとAPIリクエストが失敗するため、APIリクエストの制限を超えないように、Token Serverに対してのリクエストをなるべく減らす必要があります。 このAPIリクエストはInstallation Access Tokenの発行回数のみではなく、発行されたInstallation Access TokenからのAPIリクエストも含まれます。特にToken Serverにおいては複数のGoogle Cloud上のワークロード、GitHubレポジトリの分のAPIリクエストを発行するため、Token Serverに対してのリクエストをなるべく減らすことが重要です。GitHub APIのリクエストごとにInstallation Access Tokenを発行するのではなく、1時間の有効期限を持つInstallation Access Tokenを発行し、それを有効期限内に使い回すことでAPIリクエストを減らすことができます。 GitHubクライアントの初期化におけるPATからToken Serverへの移行 そこで、PATやGitHub Appの秘密鍵を使っていたサービスの実装を大きく変えず、また自動でInstallation Access Tokenを取得し有効期限内で再使用するライブラリを開発しました。メルカリではGo言語を標準的に使用していることから、Go言語でGitHub関連のサービスを作成する代表的なライブラリである google/go-github をベースにGitHub APIを使うためのクライアントを取得できるようにしました。なお、既にgo-githubライブラリを使っているサービスの場合、Service Accountの設定とライブラリの入れ替えのみでToken Serverに移行できるようにしました。 Token Server用ライブラリの構成 go-githubライブラリは初期化の際、任意のhttp Clientを指定することができます。このhttp Clientには、RoundTripperインターフェイスに任意のRoundTripメソッドを実装することでリクエスト送信前にリクエストの内容を変更することができます。そこでこのRoundTripメソッドを使い、リクエストを送信する前にキャッシュされているInstallation Access Tokenもしくは有効期限が切れている場合はToken Serverから新たなInstallation Access Tokenを取得し使用するようにしました。 Token Server用ライブラリの処理 これにより、go-githubライブラリを使用している既存のサービスのコードを1行変更するだけで、Token Serverに移行することができました。 GitHub AppのLoad Balancing 前述の通り、GitHub Appには1時間あたり15000回のAPIリクエスト制限があります。しかし、Token Serverは複数のGoogle Cloud上のワークロード、GitHubレポジトリのAPIリクエストを発行し、また今後の自動化サービスの増加を想定するとこの制限を超えることが予想されます。そこで、GitHub Appを複数作成し、それぞれのGitHub Appに対してInstallation Access Tokenの発行を分散することで、APIリクエストの制限を超えることなく、多くのAPI処理を提供することができます。 しかし、この分散は複数のGitHub AppをそれぞれのToken ServerのPodに一つずつ読み込んでLoad Balancerによってランダムにリクエストの分散を行い、あるInstallation Access Tokenの利用者が受け取るInstallation Access Tokenの発行元が複数のGitHub Appとなってしまうと、コミットステータスの書き込みを行うサービスにおいて、問題が発生します。 GitHubにおいてはCIの動作結果等をあるコミットに対しerror, failure, pending, successのいずれかを書き込むことができます。しかし、この書き込みはそれぞれ書き込んだGitHub Appごとに記録されます。つまり異なるGitHub Appによってコミットステータスが書き込まれると、コミットステータスが混在してしまいます。この場合、以前失敗したステータスが上書きされることなく、別の新しいステータスのみが書き込まれ、コミットステータスがすべて成功しないとマージできないようなBranch Protectionを設定している場合、マージができなくなるという問題があります。 複数のGitHub Appによるステータス書き込み ある自動化処理において最初は失敗ステータスを書き込み、その後成功ステータスを書き込むような処理を行う場合、同じGitHub Appによって行われる必要があります。もしToken Serverが複数のGitHub Appを持つ場合、最初の失敗ステータスをGitHub App 1で書き込んでしまうと、その後の成功ステータスをGitHub App 2で上書きすることができず、GitHub App 1からの失敗ステータスとGitHub App 2からの成功ステータスが混在してしまいます。 そこで、Installation Access Tokenを発行する対象ごとに常に同じGitHub Appを使うようにするために、1つのToken ServerのPodで複数のGitHub Appを読み込み、GitHubにおいてはリポジトリとブランチ名・Google CloudにおいてはサービスアカウントによってGitHub Appを割り当てるようにしました。 GitHub Appの割り当て処理 このように、GitHub Appのインデックスをリポジトリとブランチ名、サービスアカウントによって割り当てることで、同じリポジトリとブランチ名、サービスアカウントに同じGitHub Appが割り当てられるようにしました。 まとめ Token ServerをGoogle Cloudに拡張し、より多くのサービスがGitHubにアクセスする際に有効期限の短い認証情報を使うようにすることで、有効期限が長い認証情報を減らすことができました。そして既存のサービスを最小限の変更でToken Serverに移行できるようなライブラリを開発することで、移行を容易にしました。また、実運用の中で発見した問題も解決し、セキュアかつ快適なGitHubの自動化サービスの運用をサポートしました。 メルカリのSecurity Teamでは、このような認証情報の有効期限の短いものへの切り替えを推進しており、今後もこのような取り組みを進めていく予定です。 Security Teamにおける採用情報については Mercari Career をご覧ください。
はじめに こんにちは。メルペイVPoEの @keigow です。 この記事は、 Merpay & Mercoin Advent Calendar 2024 の記事です。 昨年のAdvent CalendarでCTOの@kimurasからロードマップを作成する取り組みについての記事( メルカリEngineering Roadmapの作成とその必要性 )を出しました。 こちらでも語られているようにエンジニアリング領域への投資を推進するためにロードマップは重要な役割を果たしますが、当時はメルカリのEngineering Roadmapという形で、メルペイについてはまだ作ることができていませんでした。 本日はメルペイでのエンジニアリングへの投資を推進するために、整備してきたEngineering Roadmapや、それを推進するためのEngineering Projectsという取り組みについて書きたいと思います。 ビジネスロードマップとCompany OKR メルカリグループではミッションの達成に向けて中長期で成し遂げたい目標をグループのロードマップとして定義して、その下に各カンパニーでのビジネスロードマップを定義しています( 参考 )。ビジネスロードマップはお客さまへの体験の向上や、新しいサービスの提供などを中心に、どのような価値をいつまでに届けていきたいのかという観点で整理されています。 会社の経営という観点ではロードマップをベースに四半期単位でメルペイ全体のOKR(Company OKRと呼ぶ)を設定し、それに紐づける形で配下の組織のOKRを設定していくという流れになっています。 ビジネス目標とエンジニアリング投資の両立の難しさ Company OKRがビジネス目標を達成するためのものだとすると、どのようにエンジニアリング観点で重要な中長期の投資(アーキテクチャの改善、FinOpsへの投資、Test Automationなど)をスムーズに実現していくか、は非常に重要な課題です。 ここで触れているエンジニアリングへの投資とは、簡単なリファクタリングなどではなく、半年以上の投資を必要とする大きなプロジェクトを想定しています。このような改善への投資は実際にプロダクトを開発するエンジニアの稼働が必要なことも多いため、ビジネス目標を達成するためのエンジニアの稼働とコンフリクトが発生しがちです。 勿論予めCompany OKRの中でエンジニアリングへの投資についても、横に並べて優先順位をつけて実施することができるのが理想だとは思いますが、ビジネス目標の達成とエンジニアリングへの投資に対する優先順位を0/1で判断することは難しいのが現実です。 また、例えばリアーキテクチャなど大きなエンジニアリングへの投資は複数年にまたがるプロジェクトになることも多いため、Company OKRと比較するとそもそもフォーカスするタイムラインが異なることも判断を難しくする要因の一つとなっています。 これまでのメルペイの取り組みとその課題 以前のメルペイではエンジニアリング投資の推進のためのアプローチとして、Company OKRと同様に四半期ごとにEngineering OKRを定義していました。エンジニア組織として重要な課題がフォーカスされて、インシデントの削減など成果を生み出せたものもありましたが、大きく2つの課題がありました。 1つは上述したような優先順位の判断が個々の裁量に委ねられてしまうことが多く、ビジネス上必要な開発を優先することで、Engineering OKRで設定していた目標の進捗を出すことが難しいケースが発生すること。もう1つは前者の課題から、どうしても緊急度の低い(が重要度は高い)エンジニアリング投資の推進は、不確実性が伴うため、OKRという仕組みに求められる厳密なトラッキングに向いていない部分があることです。 投資のバランスとプログラム型組織 メルペイのプロダクト組織は以前記事( メルペイのProgram型組織への移行 )にしたように、2022年の10月からプログラム型組織(大きなドメイン毎にプロダクトマネージャーやエンジニアを含めたクロスファンクショナルなチームが複数集まっている)に移行しました。 このプログラム型組織に移行した理由の1つに前者の課題(ビジネス目標とエンジニアリング投資のバランス)の解決がありました。現実問題として会社単位での優先順位を0/1で決めることが難しいようなエンジニアリングへの投資であっても、ドメインやチーム毎の単位で見ると繁閑の波があり、隙間時間を利用したり、プロジェクトの推進方法を工夫することで、エンジニアリングへの投資を生み出す余地も存在します。そのため、判断自体を会社単位で行うのではなく、ドメインごとに分割したプログラムという単位で行えるようにすることで、よりスムーズなエンジニアリング投資の推進が行えるようにしました。 個別のプログラムにはプロダクトに責任を負うProduct Headと、エンジニアリングに責任を負うEngineering Headを置くことで、各プログラムの単位でビジネスへの目標とエンジニアリング投資のバランスの意思決定が行えるようにしています。 また各プログラムで作成するロードマップに対して四半期毎にストラテジーレビューという会議体でCPO、CTO、VPoEによるレビューを行うことで、プライオリティの認識をすり合わせ、推進するためのリソースの調整を行っています。 エンジニアリング投資の推進とEngineering Projects 後者の課題(エンジニアリング投資の推進に対するOKRという仕組みのアンマッチ)に対しては、Engineering OKRを設定するのではなく、2023年7月からEngineering Projectsという仕組みを作り対処をするようにしました。 Engineering Projectsは中長期でエンジニア組織として推進したいリアーキテクチャやFinOpsなどのプロジェクトを四半期ごとに選定し、隔週の定例ミーティングで進捗をトラッキングします。期によって異なりますが、FY2025 2Q(2024年10-12月期)では6つのプロジェクトを選定しています。トラッキングは仕組み上OKRの運用に似ているところもありますが、違いとしてObjectiveなどの設定はなく、中長期に渡るプロジェクトの推進を前提とした仕組みです。中長期の計画をベースに四半期の目標を設定し、隔週で進捗のブロッカーとなりうるものの排除や、必要に応じた進め方の調整などを行えるようにしています。 Engineering Roamdapの作成 本来、上図の関係で示したようにEngineering Roadmapがあり、そこから各四半期に実施すべきEngineering Projectsがクリアになっていくべきではあるのですが、メルペイのEngineering Roadmapは今年の6月に作成が完了しました。もともとEngineering Projectsとして定義されていたプロジェクトについては、ある程度ロードマップが明確なものも多かったのでそれらのプロジェクトに対しては改めて取りまとめるという形で整理しました。 それらの取り組みに加え、今後のAI活用の取り組みや、新しい決済プラットフォーム基盤などFoundation領域への取り組みも合わせて作っています。 課題と今後の取り組み プログラム組織やEngineering Projectsの実施に比べ、Engineering Roadmapについてはまだ出来たばかりであり、運用方法やアップデートの方法についてもまだ手探りの状態になってしまっている部分があります。それでも、これまで可視化しきれていなかった中長期の取り組みを可視化することができ、エンジニアリング組織として取り組むべき課題を明確にできた部分があると思っています。 今後もロードマップの運用練度を高めていきつつ、ビジネスロードマップの実現に貢献するための、エンジニアリング投資の実現方法を模索していきたいと思います。 おわりに 次の記事は@sakabeさんです。引き続き Merpay & Mercoin Advent Calendar 2024 をお楽しみください。
こんにちは。メルカリのQA エンジニアリングマネージャーの @____rina____ です。 この度、スキマバイトサービス「メルカリ ハロ」が Google Play ベスト オブ 2024「社会貢献部門 大賞」を受賞しました!メルカリ ハロは、お好きな時間に最短1時間から働ける「空き時間おしごとアプリ」として、多くの方にご利用いただいています。スマホ一つで仕事を探し、働き、給与をもらうまでの一連のプロセスを簡単に完結できます。 そこで今回は、私たちがどのようにこのアプリを開発してきたのか、特にFlutterを中心とした技術面に焦点を当てて、連載シリーズをお届けします。開発の舞台裏から見えてくる様々な工夫やチャレンジについても触れていく予定です。 Theme / Title Author 書き起こし:Acceptance criteria: QA’s quality boost @____rina____ A Newcomer’s Experience of Getting Up to Speed in Development @cherry Hallo Design System(仮) @atsumo Push notifications and CRM integration in Mercari Hallo (仮) @sintario FlutterアプリのQAについて @um TBD @howie TBD @naka How to unit-test Mercari Hallo Flutter app @Heejoon 初回の記事は、@____rina____が執筆予定です。技術的な詳細から開発における試行錯誤まで、多面的にご紹介します。 今回の連載シリーズは、メルカリ アドベントカレンダーとのW掲載となります。アドベントカレンダーでは、メルカリの多彩な技術者たちが自社の最新技術や取り組みについて詳しく紹介しています。私たちの「メルカリ ハロ 開発の裏側」シリーズもその一環として、さらなる技術的洞察を提供できればと考えています。 アドベントカレンダーと合わせてお楽しみいただくことで、メルカリが現在どのような技術の潮流と向き合い、どのように成長を遂げているのか、広範囲に理解を深めていただけることと思います。是非、見逃さずにご注目ください! ブログのアップデートに関しては、メルカリ公式DevX(旧Twitter)の @mercaridevjp でも随時お知らせいたします。ハッシュタグ#メルカリハロ開発の裏側 でぜひ検索し、最新情報をチェックしてください。 メルカリの新たな挑戦がどのように形作られているのか、その技術的な旅路をお楽しみいただければ幸いです。どうぞお楽しみに!
こんにちは。メルペイ Engineering Engagement チームの mikichin です。 今年も、メルカリグループは Advent Calendar を実施します! ▶ Mercari Advent Calendar 2024 はこちら Merpay & Mercoin Advent Calendar とは? Advent Calendar の習慣にもとづいて、メルペイ・メルコインのエンジニアがプロダクトや会社で利用している技術、興味のある技術分野やちょっとしたテクニックなど知見をアウトプットしていきます。このAdvent Calendarを通じてクリスマスまでの毎日を楽しく過ごしていただければと思っています。 2023年のMercari / Merpay Advent Calendar はこちら Mercari Advent Calendar 2023 , Merpay Advent Calendar 2023 公開予定表 (こちらは、後日、各記事へのリンク集になります) Theme / Title Author Engineering RoadmapとEngineering Projectsの話 (仮) @keigow grpc-fedeartionを使ったBFF開発 @sakabe 新卒1年目の振り返り @hibagon The Race condition with multiple DB transactions and its solution @timo HPAとVPAによるストリーミング処理のリソース最適化:ピーク時のリソース不足と過剰なリソース割り当ての解決 @siyuan WYSIWYGウェブページビルダーを支える技術とSever Driven UIへの拡張 @togami イーサリアムのバリデーター監視について @pooh TBD @Joraku About Swift Testing @Cyan From Airflow to Argo and DBT Python models @Yani Seamless Shopping Feed Integration: Bridging the Gap Between Systems @hiramekun Argoworkflow @goro spannerにtime.Now()で生成した時刻を渡してしまう問題を検知する @kobaryo メルカリハロと広告事業を支える決済基盤の仕組み @komatsu 効率と品質向上のための MLOps @rio (仮)会計とシステムの連携 @abcdefuji (仮)Spanner external datasetsを用いたデータ品質管理 @iwata メルペイの数百画面をSwiftUI/Jetpack Composeに移行するプロジェクトを推進する @masamichi TBD @kimuras 初日の記事は keigowさんです。引き続きお楽しみください。
こんにちは。メルカリ Engineering Officeのohitoです。 今年もメルカリとメルペイ・メルコインで2本のAdvent Calendarを実施します! ▶ Merpay & Mercoin Advent Calendar 2024 はこちら Mercari Advent Calendar とは? メルカリグループのエンジニアがプロダクトや会社で利用している技術、興味のある技術分野やちょっとしたテクニックなど知見をアウトプットしていきます。このAdvent Calendarを通じてクリスマスまでの毎日を楽しく過ごしていただければと思っています。 2023年のMercari / Merpay Advent Calendar Mercari Advent Calendar 2023 Merpay Advent Calendar 2023 公開予定表 (こちらは、後日、各記事へのリンク集になります) Theme / Title Author Google CloudからGitHub PATと秘密鍵をなくす – Token ServerのGoogle Cloudへの拡張 @Security Engineering TBD @darren Keeping User Journey SLOs Up-to-Date with iOS E2E Testing in a Microservices Architecture @yakenji 書き起こし:"Acceptance criteria: QA’s quality boost" @….rina…. Streamlining Incident Response with Automation and Large Language Models @florencio TBD(somthing related with finops?) @pakuchi Tips on measuring performance of React apps with Chrome Devtools @samlee A Newcomer’s Experience of Getting Up to Speed in Development @cherry FlutterとDesignSystem (仮) @atsumo New Production Readiness Check experience in Mercari @mshibuya Push notifications and CRM integration in Mercari Hallo (仮) @sintario_2nd External service review as code, with the help of LLMs (current draft) @danny, simon メルカリ Tech Radar @motokiee GitHubのBranch Protectionのバイパス方法(仮) @iso メルカリにおけるナレッジマネジメントの取り組み(仮) @raven FlutterアプリのQAについて @um Enhancing macOS configuration management with GitOps @yu TBD @howie TBD @naka Why good internal tooling matters (TBD final title) @klausa スムーズなCDNプロバイダー移行のために行ったアプローチと次のステップ @hatappi How to unit-test Mercari Hallo Flutter app @Heejoon Studying Ph.D. in computer science as a software engineer — culture shock, benefits, opportunities and so on @greg.weng TBD @kimuras 最初の記事は、「Google CloudからGitHub PATと秘密鍵をなくす – Token ServerのGoogle Cloudへの拡張」です。 どうぞお楽しみに!
はじめに こんにちは、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グループページ