メルカリエンジニアリングブログメルカリのエンジニアブログです。技術情報やエンジニアリング組織などの情報を日々発信していきます。https://engineering.mercari.com/ja© 2023 Mercari, Inc.blog- メルカリのEngineering Roadmapの具体的な運用についてhttps://engineering.mercari.com/blog/entry/20241225-engineering-roadmap/https://engineering.mercari.com/blog/entry/20241225-engineering-roadmap/<p>はじめに こんにちは、メルカリでJapan RegionのCTOを担当している木村です。僭越ながら今年も最後のアドベントカレンダーの投稿を担当させていただきます。 昨年投稿した開発組織にとってのEngineering R […]</p>
Wed, 25 Dec 2024 10:00:41 GMT<h2>はじめに</h2>
<p>こんにちは、メルカリでJapan RegionのCTOを担当している木村です。僭越ながら今年も最後のアドベントカレンダーの投稿を担当させていただきます。</p>
<p>昨年投稿した開発組織にとっての<a href="https://engineering.mercari.com/blog/entry/20231225-creating-mercari-engineering-roadmap/" title="Engineering Roadmapの必要性">Engineering Roadmapの必要性</a>についての記事では、「開発スケジュールの期待値調整」が容易になったり、「将来を見越したアーキテクチャ」を作ることができたり、「Visionを組織に浸透させやすくなるメリット」があることなどをご紹介しました。しかし、昨年は実際にEngineering Roadmap(以下Roadmapと呼ぶ)にどのようなアイテムがあるのか、あるいはどのように運用されているのかといった具体的なご説明までには至ることができませんでした。本稿では、運用上難しかった話なども含めて、より実践的な内容をお話ししたいと思います。</p>
<h2>昨年のRoadmapを振り返る</h2>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/12/f6ab2d54--2024-12-24-20.09.05.png" alt="" /></p>
<p>まずは、前回のRoadmap作成時の狙いと実際に1年後どのような結果になったのかをご説明したいと思います(まだビジネス上オープンになっていないものもあるため、公表されているものに絞ってお話しします。Roadmapに関連するビジネス的なイベントとしては<strong>2024年3月6日に新規事業であるメルカリ ハロをリリース。2024年8月29日に、台湾のお客さまがWeb版「メルカリ」を通じて日本で出品された商品を購入できる「<a href="https://about.mercari.com/press/news/articles/20240829_crossborder/" title="越境取引">越境取引</a>」</strong>の展開をスタートしたほか、<strong>2024年9月10日には生成AIを活用した「AI出品サポート」の提供</strong>を開始しました。</p>
<p>これらを技術的に実現させるため、23年12月の段階で、Roadmapの大きな方向性として以下のように定めていました。</p>
<p><strong><em>① 3つの領域の"開発コストの低下"と"Enabling"を実現する</em></strong><br />
中長期的なビジネスの拡張を実現するために、以下の3つの事項のバランスを保ちつつ、Biz Enablingと開発コストの低減を実現する。</p>
<ul>
<li>既存サービス開発簡易化</li>
<li>新規事業展開簡易化</li>
<li>国際展開</li>
</ul>
<p>メルカリグループではメルペイやメルコイン、今回のハロのように継続的に新規事業を提供しています。このような新規事業を立ち上げるたびにすべてを0から開発するのではなく、既存のプラットフォームを拡張・活用することによって、新規事業の展開をより高速かつ低コストで実現することをゴールに掲げました。この方針を応用することで、国内の新規事業の立ち上げのみではなく、多国展開もより効率的に実現することも目指しています。当然ながら新規のものだけでなく、既存サービスへの改善もあるので、既存サービス開発の効率化も同時に目標として掲げていました。</p>
<p>これらを実現するためにRoadmapの中にアクションアイテムとして定義していたものを一部抜粋してご紹介します。</p>
<h3>Golden Path</h3>
<p>これまで、メルカリグループの組織としてどの技術を標準と位置付けるのかは特に明文化されていませんでした。もちろん言語やデータベースの選定などにおける暗黙的なコンセンサスは組織のなかにありましたが、基本的には各事業が必要な技術の選定をそれぞれで行ってきました。これらは柔軟性や自由度の高さという観点ではうまくワークすることもありましたが、一度使い始めると長期的なメンテナンスコストが発生したり、事業の立ち上げ時にゼロから投資を行う必要があったり、 選定のために同じような議論を繰り返すことになるなど、スピード面で課題がありました。</p>
<p>Golden Pathはグループ内での技術の標準化やフレームワークを作ることによって、開発と運用コストを落とすことと、同時に新規サービスを作る際の効率化を狙いました。アクションアイテムとしては以下のように設定していました。</p>
<p><strong><em>技術標準をベースとしたアプリケーション構築領域(Web, Mobile, Backend)での Bootstrapping tool の開発を進め、Global Expansion への活用が可能な状態になっている。</em></strong></p>
<p>これは言い換えると、アプリケーションを作る際に、メルカリの環境に適した効率的で標準化されたフレームワークを提供することを目的としています。1年後どのようになったかというと、残念ながらすべての領域でこれを実現することはできなかったのですが、WebについてはBootstrapping toolが完成されて、新規事業を作る際のWeb開発を大きく効率化することができました。また、改めてADRや<a href="https://engineering.mercari.com/blog/entry/20241216-mercari-tech-radar-initiative/" title="Tech Radar">Tech Radar</a>の仕組みが整備され、BackendやMobile開発においても開発に使われるTech Stackを改めて標準化することによって、関係者にコンセンサスを取る手間が省けるようになり、技術選定のコストを低減させることができました。</p>
<p>また、新しい microservice や Webアプリケーション を本番環境で運用する前の基準を定めたチェックリストである <a href="https://engineering.mercari.com/blog/entry/20241213-new-production-readiness-check-experience-in-mercari/" title="Production Readiness Check (PRC) の効率化・短縮化">Production Readiness Check (PRC) の効率化・短縮化</a>も実施しました。これまで2ヶ月以上かかってしまっていた PRC を自動化することによって効率化する試みです。</p>
<p>技術の標準化というものは、ごく当たり前のことに聞こえてしまうかもしれませんが、弊社でも継続的な新規事業の創出や技術的なトレンドの変化によって、全社でのコンセンサスを取ることが難しくなってきていました。ここで、この課題をそのままにせず、一度立ち止まり、全社で標準的に用いるTech Stackを再整理することで、改めて開発の高速化を狙う決断をしました。</p>
<h3>IDP</h3>
<p>いわゆるアカウントIDに関するプラットフォームの改善です。IDはビジネス戦略に合わせて先行して技術基盤を用意しなければならず、Roadmapの中でも最重要な項目です。PassKeyに関する開発や普及に関してもこの項目に含まれます。こちらも、国際展開に向けて以下のようなアクションアイテムを設定していました。</p>
<p><strong><em>新しくなったアカウント登録・ログインプロセスが、実際に日本以外のregionから利用される状態になっている。</em></strong></p>
<p>計画的に開発を行い、こちらも1年後の現在、台湾で提供されているサービスでのアカウント作成に活用されています。上記にも述べましたように、IDはビジネス戦略の根幹になる技術と言っても過言ではありません。<a href="https://engineering.mercari.com/blog/entry/20231225-creating-mercari-engineering-roadmap/">昨年の記事</a>にも記載しましたように、お客さまへ新しい価値を提供するには、開発組織としてビジネスの方向性にアラインしつつ、先行してプラットフォームを用意する必要があります。まさに台湾での越境取引の件は昨年の時点で、他国へのビジネス展開が概ね決まっていたので、先行してそれを実現するためのアクションプランを計画的に実装し、提供することができました。また、これまでにもメルコインやメルカリ ハロを提供する中で、メルカリのIDとeKYCさえ完了していればとてもシームレスに新規サービスをご利用いただける仕組みができあがったのも、継続的にIDPが先を見越した開発ができていたことに起因しています。</p>
<h3>AI出品サポート</h3>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/12/bfa2a977--2024-12-24-20.08.49.png" alt="" /></p>
<p>この時期に生成AIの活用の推進も強化しており、9月にリリースされた<a href="https://about.mercari.com/press/news/articles/20240910_aisupport/" title="AI出品サポート">AI出品サポート</a>のアクションアイテムも設定してありました。</p>
<h4>AI出品サポート(出品補助)</h4>
<p>生成AIのポテンシャルの大きさは明らかではありましたが、これをいち早くビジネスに導入して、特にメルカリにおける出品の利便性を向上させてお客さまに早く価値を提供したいと考えていました。この段階ではまだ生成AI社内でも検証段階でありましたが、早い段階でサービス活用の指針を定められていたことで、業界でも比較的早い段階で生成AIを実際にサービスに活用することができました。</p>
<p>Roadmapのアクションアイテムは実際にはより多くのものがあるのですが、雰囲気を掴んでいただくために公開できる範囲で一部のみ抜粋させていただきました。</p>
<p>この先にも述べますが、Roadmap作成の最大のメリットは、Visionを示すだけに留まらず、やること・やらないことを明確に意思表示できることだと感じています。特に大きなリアーキテクチャが伴うものについては「やらないといけないと思っていた」や「やろうと思っていた」ことは、なるべく早くに意思決定して、早く取り掛からなければ、後々解決するのが困難になってしまうことが多々あります。私たちは継続的に解決しなければならない課題について議論し、ビジネスとの方向性とアラインしながら技術的な投資の決定を継続的に行っています。</p>
<h3>Next level of Scalability and Resiliency</h3>
<p>今後のサービスの成長をより堅牢にするために、Infrastructureレベルでの改善もRoadmapに設定していました。これまでも私たちはInfrastructureのResiliencyやSalabilityの改善を行ってきましたが、今後の国際展開によるお客さまの増加や金融事業を提供しているメルペイのResiliencyを改善するためには抜本的な仕組みの改善が必要でした。</p>
<p>Scalabilityの改善に関して、特に大きな進歩は、大規模なコアなデータをMySQLの物理サーバに保存していたものを、<a href="https://qiita.com/official-columns/event/202402-pingcap-02/" title="慎重な検証を重ねた上">慎重な検証を重ねた上</a>で、TiDB Cloudへのmigrationを始めたことです。これによりSclabilityの改善と運用コストの大幅な低減の実現を狙っています。</p>
<p>そして、国際展開するための基盤の準備として、複数拠点でサービスを運用するためのMulti RegionでのInfrastructureの構築も進めています。こちらについては、現在も進行中であり、まとまった形で発表できる状態になったら再度詳しくご説明したいと思います。しかし、並行してInfrastructureのコストを最適化しつつも、Multi Regionでのサービス運用を実現するためにはコストの増加は避けられません。したがって、<a href="https://engineering.mercari.com/en/blog/entry/20240329-finops-at-mercari/" title="FinOpsの文化醸成を継続的に行い">FinOpsの文化醸成を継続的に行い</a>、具体的なコストの低減を全社の目標として共有しています。今年は、特にCUD採用率、Spot VM採用率を全社で上げていき、コンピュートリソースの最適化を実現することができました。</p>
<h2>Roadmapの活用と運用</h2>
<p>この作成したRoadmapをどのように活用、運用しているのかについてご説明します。</p>
<h3>Visionの浸透に活用する</h3>
<p>Engineering組織で重要なことについてVisionの浸透があります。「私たちは今後何を実現したいのか」、そして「どのような過程を経てこれを実現させるのか」を一人一人のエンジニアに理解してもらうことが大事です。浸透について、私たちも特別なことをやっているわけではないのですが、Roadmapが完成したら、Engineerが全員参加するAll Handsで作成したRoadmapを使って説明し、Visionの浸透を図っています。まさに、年末の今も来年のRoadmapを作っているところであり、年明けに来年からの3ヵ年に実現したいVisionとRoadmapを全社で説明することになっています。なかなか一度の説明では浸透しないので、プレゼンテーション資料だけではなく、言語化されたRoadmapの文章をいつでも誰でも見られる状態にすることや、誰でもこれに対してFeedbackできる仕組みを築くことが重要です。それによって、一人でも多くのエンジニアがRoadmapを自分事として捉えて、Roadmapについて真剣に考えてくれることを目指しています。</p>
<h3>OKRの設定に活用する</h3>
<p>私たちはクォーターごと、つまり3ヶ月ごとにOKRを設定しています。OKRを3か月ごとに考えるのは計画性がないととても大変な作業ですし、OKRの設定に時間がかかってしまうと、設定と同時にすぐにまた次のOKRを考えなければならないといった悪循環となってしまいます。Roadmapで年間の計画が決まっていればOKRに設定しなければいけないオブジェクトの多くをRoadmapから転用することができるので、とてもスムーズに作成することができます。</p>
<h3>運用について</h3>
<p>当然ながら、Roadmapは掲げたままにしないこと、形骸化させないことが非常に重要です。そのために継続的に進捗を確認することやRoadmap自体をメンテナンスすることが重要です。これが完璧な運用方法というわけではないですし、将来変わることもあると思いますが、参考までにわたしたちの現在の運用方法をお話ししたいと思います。</p>
<p>基本的には以下のイテレーションでロードマップの作成とアップデートを行なっています。</p>
<ul>
<li>12月にRoadmapのメジャーアップデートを行い(前年のRoadmapをリバイズして1年-3年の計画を作成する)</li>
<li>その後はクォータ末に毎回マイナーアップデートを行う(3月、6月、9月)</li>
</ul>
<p>プログレスのチェックは月に1回各アクションアイテムのプログレスを確認しています。当然ながらプログレスの確認は必ずやったほうが良く、やりながら方向性をアジャストすることもできますし、この継続的な運用によってさらにVisionの浸透が強化されます。</p>
<h2>Engineering Roadmapを運用する上で難しい点と工夫</h2>
<h3>ビジネスプライオリティの影響</h3>
<p>1-3年間のRoadmapを立てて、着々と開発を進めても、ビジネス上のプライオリティが下がってしまったり、方向性が変わることはどうしても発生します。むしろ、そういう変化は受容できる仕組みにしなければ現実的な運用は厳しいと考えています。そのため、私たちは月1回のプログレスチェックでの方向性のアジャストや3か月ごとのマイナーアップデートを行って、なるべくフレキシブルにビジネスの要求に応えられる運用を目指しています。</p>
<h3>アイテムが多くなりすぎる問題</h3>
<p>どうしてもやりたいことを整理するとRoadmapに追加したい項目が多くなりがちになってしまいます。しかし、項目が多くなればなるほど、エンジニアをはじめとする現場のメンバーの理解を得ることが難しくなりますし、現実的には全てに手をつけられなくなってしまうリスクがあります。実際に私も「やらないことを決める」努力をして項目を減らす努力はしているのですが、まだまだ多い状態です。毎年Roadmapを洗練させていくなかで、少しずつ数を絞ってはいますが、実際に運用してみると「少し少ないかな」と思うくらいの方がいいと個人的には思います。</p>
<h2>最後に</h2>
<p>今回は少し具体的にRoadmapの内容や運用についてご説明させていただきました。本当はすべてのRoadmapを公開して、それぞれの狙いや振り返り、改善点などもお話することができるとよりイメージをお伝えしやすいのですが、まだまだ世に公開できていないものもありますので、それはまた来年末にとっておき、ご容赦いただけたら幸いです。Roadmapの設定と運用において、当たり前の内容ではあるのですが、せっかく作成したRoadmapを形骸化させないためには、議論を重ねて極力正確なものを作り、継続的に見直していくことがポイントとなります。そして、作成したものをそのままにせずに、いつでも誰でもアクセスできて、Feedbackを提供できる仕組みと雰囲気づくりをすることによって、血の通ったRoadmapを作成することができます。Roadmapは方向性を言語化することで、ビジネスとエンジニアリングの間の理解の差を埋めて、方向性の不確実性を減らすことができ、自信を持って開発し続けるために欠かせないツールだと考えています。ご一読くださったみなさんにとって、少しでも手助けになったら幸いです。</p>
- Spanner Data Boostを活用したリアルタイムなリコンサイルエラーの検出https://engineering.mercari.com/blog/entry/20241224-spannar-data-boost/https://engineering.mercari.com/blog/entry/20241224-spannar-data-boost/<p>こんにちは。Mercari Corporate Products Teamのエンジニアの@yuki.watanabeです。 この記事は、Mercari Advent Calendar 2024 の21日目の記事です。 は […]</p>
Tue, 24 Dec 2024 11:00:17 GMT<p>こんにちは。Mercari Corporate Products Teamのエンジニアの<a href="https://github.com/yuki0920">@yuki.watanabe</a>です。<br />
この記事は、<a href="https://engineering.mercari.com/blog/entry/20241125-mercari-advent-calendar-2024/">Mercari Advent Calendar 2024</a> の21日目の記事です。</p>
<h2>はじめに</h2>
<p>現在、内製の会計仕訳システムの開発に携わっています。このシステムには様々なバッチ処理が実装されているのですが、BigQueryへクエリしデータを抽出するためのバッチで誤検出の問題がありました。本記事ではこの問題に対して検討した複数のソリューションと結果的にどの方法を採用したのかについて紹介します。<br />
バッチ処理の課題を解決する際の参考にしていただけると幸いです。</p>
<h2>リコンサイルエラー検出のバッチについて</h2>
<h3>会計仕訳システムにおけるデータの流れ</h3>
<p>まず、会計仕訳システム(図のAccounting System)におけるデータの流れを紹介します。お客さまがメルカリやメルペイを使用した場合、取引内容に応じて様々なMicroservicesが処理を行い、金銭に関わるデータがある場合は、会計仕訳システムのPub/Subに送信します。会計仕訳システムではCloud Functionsでバリデーションを行い、Spannerのaccounting_dataテーブルへ登録します。<br />
次に、各Microservicesは会計仕訳システムのPub/Subに送信済みのデータについて、会計仕訳システムのリコンサイル用APIへ送信します。このAPIは後述するリコンサイルと呼ばれる突合処理を行い、結果をSpannerのreconciliationテーブルへ登録するもので、Kubernetes上のgRPC ServerのAPIとして実装されています。<br />
Spannerへ登録されたデータ(accounting_data、 reconciliation)は、Cloud ComposerとDataflowを用いて、BigQueryへ1日に1度差分を同期しています。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/12/9c15d849-golduck_architecture-2024-12テックブログ用.png" alt="" /></p>
<h3>リコンサイルは会計データの確からしさを検証する仕組み</h3>
<p>リコンサイルとは、会計仕訳システムと会計データの送り元となるMicroservice間のデータの突合処理のことを指します。Microserviceはデータベースに登録した会計データをリクエストデータに含め、リコンサイル用APIへリクエストします。APIでは、リクエストデータと会計仕訳システムに登録された会計データ(accounting_data)を突合し、突合結果をreconciliationテーブルのstatusカラムに保持して登録します。このリコンサイルを通じて、Microservice側のデータと会計仕訳システム側のデータが一致していることを保証しています。<br />
以下はreconciliationに登録されるstatusカラムの値のイメージです。</p>
<ul>
<li>突合成功: status=’success’</li>
<li>突合失敗: status=’failed’</li>
</ul>
<h3>リコンサイル検証用バッチでリコンサイルのエラーがないかを確認する</h3>
<p>突合が失敗したデータについてはリコンサイルのエラーと考えられます。そこで、リコンサイルエラー検出用のバッチをCronJobを用いて実装しています。このCronJobでは1日に1度BigQueryへクエリし、リコンサイルエラーのデータを抽出します。エラーのデータが存在する場合は、Microservice Teamへ共有し、再度のリコンサイルAPIへのリクエストを依頼しています。</p>
<h3>SpannerとBigQueryの同期タイムラグによる誤検出</h3>
<p>しかし、上記のバッチには課題が存在しました。Spannerにはリアルタイムにリコンサイル結果が登録されていますが、バッチが参照しているBigQueryには1日に1度しか同期されません。このSpannerとBigQueryの同期タイムラグにより、バッチの実行結果には誤検出である偽陽性のデータが含まれていました。「Spannerには突合済みのデータが存在するが、BigQueryには未同期」のデータは本来は突合が成功していますが、バッチでは突合が失敗したデータとして検出されていました。<br />
このため、バッチによってリコンサイルエラーとして抽出されたデータについて、エンジニアが「Spannerにクエリをして本当にリコンサイルのエラーがあるのかどうかを調べる」という手動の運用作業が発生していました。<br />
会計仕訳システムでは、会計データを扱っているという特性上、毎月の月初に前月分のデータを確定する、いわゆる「締め」が必要になります。月末付近に発生したリコンサイルエラーは速やかに送り元であるMicroservice Teamにリコンサイル依頼をし、リコンサイルエラーを解消しなければなりません。しかし、上記の運用作業が発生する場合、リコンサイルエラーの検出から解消までに日をまたいでしまうこともあり、会計業務への影響が出てしまうこともありました。<br />
これらの運用課題の解消のためには、「リコンサイルエラー検出のバッチの誤検出をゼロにする」ということが必要でした。</p>
<h2>Spanner Data Boostの採用</h2>
<h3>検討したソリューション</h3>
<p>運用課題の解消のため、複数のソリューションを検討しました。</p>
<h4>1. StreamingでSpannerからBigQueryへ同期する方法</h4>
<p>まず、Spannerに登録されたデータをStreamingでリアルタイムにBigQueryへ同期する方法を検討しました。Dataflowの<a href="https://cloud.google.com/dataflow/docs/guides/templates/provided/cloud-spanner-change-streams-to-bigquery">Spanner change streams to BigQuery template</a>などを利用し同期用のJobを作成することで、技術的には実現可能な方法ではあります。Streamingでリアルタイムに同期できると、上記以外の課題の解消にも役立てられるため、大きな恩恵を得られたでしょう。一方で、Stremingの同期用のJobを採用する場合は、同期の不具合がある場合にも備えなければなりません。例えば、同期用Jobが停止する、BigQueryへ二重でデータが登録される、BigQueryへの一部のデータの登録が失敗するなどが考えられます。こうした不具合が発生した場合には、手動運用でリカバリするか、もしくはリカバリ用のシステムの実装が必要になりますが、初期の実装とその後の運用まで含めた工数を考慮すると、既存の課題に対するソリューションとしては過大だと考え、採用を見送りました。</p>
<h4>2. SpannerとBigQueryの同期頻度を増加する方法</h4>
<p>次に、SpannerからBigQueryの同期頻度を増加する方法を検討しました。現状1日に1度行っている同期を2〜3回に増加させ、その後にリコンサイルエラー検出のバッチを実行する方法です。これまでの方法と比較し、偽陽性のデータを減らすことは可能だったかと思います。しかし、Spannerに登録されているBigQuery未連携のデータは多少なりとも存在するため、リコンサイルエラー検出のバッチの誤検出をゼロにすることには向かないと考え、採用を見送りました。</p>
<h4>3. Spanner federated queriesとSpanner Data Boostを利用する方法</h4>
<p>最終的に、Spannerの<a href="https://cloud.google.com/spanner/docs/databoost/databoost-overview">Data Boost</a>を活用することにしました。上述の通り、リコンサイルエラー検出のバッチではBigQueryにクエリをしていますが、このクエリを修正し、BigQueryの<a href="https://cloud.google.com/bigquery/docs/spanner-federated-queries">Spanner federated queries</a>の機能を使い、Spannerへのクエリもしています。Spannerへクエリする際に「Spannerには登録済みだがBigQueryには未同期のデータ」も併せて取得することで、BigQueryとSpannerのデータをどちらも考慮して、リコンサイルエラーのあるデータのみを抽出することができるようになっています。<br />
重要な点としては、Spanner federated queriesを利用する際に、Data Boostを有効化するということです。Data BoostはSpannerのPrimary Instanceへ負荷を与えることなくSpannerへクエリすることができる非常に便利な機能です。リコンサイルエラー検出のバッチでは、Spannerへ登録された1日分のデータを取得しますが、1日分でもかなりのレコード数となるため、もしPrimary Instanceへクエリした場合はパフォーマンスへの影響は避けられません。そこで、Data Boostを有効化しこの問題を回避しています。<br />
この方法を採用した理由は、「リコンサイルエラー検出のバッチの誤検出をゼロにする」という目的を達成可能であり、かつ実装工数の観点でも既存のクエリの改修の範囲で早急に実現できることが見込まれたためです。</p>
<h3>BigQueryへのクエリ改修前後のサンプルコード</h3>
<p>リコンサイルエラー検出のバッチで使用しているBigQueryへのクエリの改修前後のサンプルコードを記載します。</p>
<h3>改修前のクエリサンプル</h3>
<pre><code class="language-sql">SELECT *
FROM example_dataset.reconciliation
WHERE status != 'success'</code></pre>
<p>まず、改修前のクエリではBigQueryのDatasetであるexample_datasetのreconciliationテーブルをFROM句に指定し、statusがsuccess以外のレコードを抽出していました。</p>
<h3>改修後のクエリサンプル</h3>
<pre><code class="language-sql">WITH spanner_reconciliation AS (
SELECT *
FROM EXTERNAL_QUERY('spanner_connection_example', """
SELECT *
FROM reconciliation
WHERE created >= TIMESTAMP(CURRENT_DATE("Asia/Tokyo"), "Asia/Tokyo")
""")
)
SELECT * FROM example_dataset.reconciliation
LEFT JOIN spanner_reconciliation ON reconciliation.id = spanner_reconciliation.id
WHERE status != 'success'
AND (spanner_reconciliation.id IS NULL OR spanner_reconciliation.status != 'success')</code></pre>
<p>改修後のポイントは2点あります。1点目は、WITH句でEXTERNAL_QUERYの関数を利用している点です。第1引数にSpannerを指定したBigQueryのConnection IDを指定し、第2引数には、クエリ実行日に登録されたreconciliationテーブルのレコードを抽出するクエリを指定しています。<br />
2点目は、メインのクエリのWHERE句の絞り込みです。FROM句にreconciliationテーブルを指定することは改修前と同様ですが、加えてWITH句で定義したspanner_reconciliationテーブルをLEFT JOINし、WHERE句で利用しています。これにより、「BigQueryのreconciliationテーブルのレコードでエラーが発生している」かつ「実行日に登録されたSpannerのreconciliationテーブルのレコードでエラーが発生している、またはレコードが存在しない」条件に該当したレコードのみを抽出できるようになっています。</p>
<h2>まとめ</h2>
<p>会計仕訳システムのリコンサイル検証用バッチには、SpannerとBigQueryの同期タイムラグによる誤検出の課題が存在しました。そこで、バッチで実行しているBigQueryへのクエリを改修し、Spanner federated queriesとData Boostを利用しSpannerへもクエリすることで、BigQueryへ未同期のデータも抽出するようにし、同期タイムラグによる課題を解決しました。<br />
本記事執筆時点で、改修版のリリースから2ヶ月程度が経過しています。改修前と比較し、月に10件程度発生していたSpannerの手動クエリによる運用作業がほぼゼロになるなどの効果が出ています。<br />
また、この経験が早速別の機会にも役立ちました。あるバッチでSpannerへクエリする処理が、とある変更をきっかけにインデックスが効かなくなり大幅にパフォーマンスが悪化してしまう問題があったのですが、Spanner federated queriesとData Boostを利用することで、これを解決することができました。<br />
今後もSpannerとBigQueryをしばらく使い続けることが予想されるため、Spanner federated queriesとData Boostを利用したアプローチを様々な場面で活用できるかと思います。<br />
明日の記事は kimras さんです。引き続きお楽しみください。</p>
<h2>参考資料</h2>
<ul>
<li><a href="https://cloud.google.com/spanner/docs/databoost/databoost-overview">Data Boost Overview | Spanner | Google Cloud</a></li>
<li><a href="https://cloud.google.com/bigquery/docs/connect-to-spanner">Connect to Spanner | BigQuery | Google Cloud</a></li>
<li><a href="https://cloud.google.com/bigquery/docs/spanner-federated-queries">Spanner federated queries | BigQuery | Google Cloud</a></li>
<li><a href="https://www.oreilly.co.jp/books/9784873118703/">データ指向アプリケーションデザイン ―信頼性、拡張性、保守性の高い分散システム設計の原理</a></li>
<li><a href="https://www.oreilly.co.jp/books/9784814400652/">データエンジニアリングの基礎 ―データプロジェクトで失敗しないために</a></li>
</ul>
- メルペイの数百画面をSwiftUI/Jetpack Composeに移行するプロジェクトを推進するhttps://engineering.mercari.com/blog/entry/20241221-leading-a-project-to-migrate-hundreds-of-screens-to-swiftui-jetpack-compose-from-uikit-androidview-in-merpay/https://engineering.mercari.com/blog/entry/20241221-leading-a-project-to-migrate-hundreds-of-screens-to-swiftui-jetpack-compose-from-uikit-androidview-in-merpay/<p>こんにちは。メルペイ Engineering Managerの@masamichiです。 この記事は、Merpay & Mercoin Advent Calendar 2024 の記事です。 メルペイのモバイルチ […]</p>
Tue, 24 Dec 2024 10:00:51 GMT<p>こんにちは。メルペイ Engineering Managerの<a href="https://x.com/masamichiueta">@masamichi</a>です。<br />
この記事は、<a href="https://engineering.mercari.com/blog/entry/20241125-merpay-mercoin-advent-calendar-2024/">Merpay & Mercoin Advent Calendar 2024</a> の記事です。</p>
<p>メルペイのモバイルチームでは現在、メルカリアプリ内に存在するメルペイの数百画面をSwiftUI/Jetpack Composeに移行するプロジェクトを推進しています。<br />
この記事では、プロジェクトの経緯とその進め方について紹介します。</p>
<h1>メルペイのリリース</h1>
<p>メルペイが搭載されたメルカリアプリがリリースされたのは2019年2月です。初期の開発は主に2018年に進めていましたが、当時はSwiftUIやJetpackComposeは発表されておらず、メルペイを含むメルカリアプリはUIKit/Android Viewで開発していました。<br />
その後、2019年内にiOS/Android共に宣言的UIフレームワークであるSwiftUI/Jetpack Composeが発表されました。</p>
<h1>GroundUP Appプロジェクト</h1>
<p>一方2020年ごろから、母体となるメルカリアプリは長年の開発で積み重なってきた課題を解決するために コードベースを刷新するGroundUP Appプロジェクトが立ち上がりました。<br />
GroundUP AppプロジェクトではSwiftUI/Jetpack Composeが全面採用され、2022年にリリースすることができました。</p>
<p>プロジェクトの詳細についてはコアメンバーの記事を参照ください。</p>
<ul>
<li><a href="https://careers.mercari.com/mercan/articles/35887/">メルカリの事業とエコシステムをいかにサステナブルなものにするか?かつてない大型プロジェクト「GroundUp App」の道程</a></li>
<li><a href="https://careers.mercari.com/mercan/articles/36183/">これからメルカリのエンジニアリングはもっと面白くなる──iOS&Androidのテックリードが振り返る、すべてがGo Boldだった「GroundUp App」</a></li>
</ul>
<p>メルペイの各種機能はモジュール化してある程度疎結合な状態でメルカリアプリに組み込んでいたため、新しいアプリにも組み込まれた状態を実現し、GroundUP Appプロジェクトと並行しながら新機能の開発を続けていきました。</p>
<p>メルペイの移行についてはこちらの記事を参照ください。</p>
<ul>
<li><a href="https://engineering.mercari.com/blog/entry/20221213-ground-up-app/">メルカリアプリのコードベースを置き換える GroundUP App プロジェクトの話</a></li>
<li><a href="https://engineering.mercari.com/blog/entry/20231023-mmtf2023-day1-4/">【書き起こし】Merpay iOSのGroundUP Appへの移行 – kenmaz【Merpay & Mercoin Tech Fest 2023】</a></li>
</ul>
<h1>DesignSystem</h1>
<p>メルカリではDesignSystemを定義し画面デザインおよび開発を行っています。メルカリでは2019年ごろから段階的にアプリへの導入を進めてきました。</p>
<p>特にGroundUPプロジェクト後の新しいアプリではSwiftUI/Jetpack ComposeのUIコンポーネントに刷新され、DesignSystemの全面的な採用によって画面のUI/UXの統一、ダークモード対応やアクセシビリティの向上が実現しました。</p>
<p>一方で、メルペイは前述の通り初期から開発してきたモジュールをそのまま新しいアプリに統合しました。それらの画面はUIKit/Android Viewで作られており、DesignSystemについてもUIKit/Android Viewの旧バージョンの実装となっていました。それによって、UI/UXの差分、ダークモード非対応、UIフレームワークが違うことによるアーキテクチャの差分といった課題がありました。<br />
GroundUPプロジェクトで得た効果を最大限活用するため、2023年よりメルペイの既存画面のマイグレーションを進めるプロジェクトを開始しました。</p>
<h1>Engineering ProjectsとGolden Path</h1>
<p>メルペイの数百画面をマイグレーションしていくには、長期的な取り組みが必要となります。メルペイではこういった長期的なエンジニアリングへの投資を推進するためにEngineering Projectsという枠組みを構築してきました。<br />
Engineering Projectsの詳細については、VP of Engineeringの@keigowさんの記事をご覧ください。</p>
<ul>
<li><a href="https://engineering.mercari.com/blog/entry/20241204-merpay-engineering-investment/">メルペイのエンジニアリングへの投資を推進する仕組み</a></li>
</ul>
<p>また、現在メルカリグループ全体で標準的な技術スタックをGolden Pathとして定義し、開発効率の向上や技術資産の再利用を目指しています。DesignSystemで採用されているSwiftUI/Jetpack ComposeはGolden Pathとして定義されており、メルペイのマイグレーションプロジェクトは社内ではわかりやすくDesignSystemマイグレーションプロジェクトと呼んでいます。</p>
<ul>
<li><a href="https://careers.mercari.com/mercan/articles/40891/">グローバル展開を推進する開発組織をつくる——Meet Mercari’s Leaders:木村俊也(CTO)</a></li>
</ul>
<p>実際にマイグレーションを実施していくには工数が必要であり、優先度の議論も必要となります。本プロジェクトを立ち上げるにあたってプロジェクト計画書を作成し、背景やアクション、体制やマイルストーンを明確にしました。Golden Pathのような会社の長期的な方針やEngineering Projectsの取り組みもあり、本プロジェクトをEngineering Projectsの1つとして推進しています。</p>
<h1>プロジェクト体制と進め方</h1>
<h2>体制</h2>
<p>メルペイでは、プログラムという大きなドメイン毎にプロダクトマネージャーやエンジニアを含めたクロスファンクショナルなチーム体制で事業を推進しています。<br />
Design System マイグレーションを進めるには全プログラムのモバイルチーム、デザイナーとの連携が必要不可欠です。モバイルチームのリーダーとデザイナーで隔週の定例ミーティングをセットし、進捗やブロッカーの共有、マイルストーンの設定を定期的に行っています。<br />
プロジェクトの立ち上げ期は週次で集まって進め方の型を作っていくのが良いと思いますが、ある程度固まってくると隔週がちょうどいいと感じています。</p>
<p>プロジェクトの情報を全て集めたページを社内のConfluenceに作っています。ここでプロジェクト計画書や体制図、機能ごとのSlackのコミュニケーションチャンネル、デザインや開発のノウハウ、QAのテストケース、機能のリリース状況、定例のミーティング議事録などプロジェクトに必要な情報を俯瞰して見ることができるようにしています。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/12/034b65f3-tableofcontents.png" alt="tableofcontents" /><br />
<em>Table of Contentsの一部抜粋</em></p>
<p>マイグレーションを進めるには工数とタイミングが重要です。プロダクトの新規施策を導入するタイミングで同時にマイグレーションできれば、効率的に進めることができます。一方、それだけでは変化の少ない機能のマイグレーションが進められません。また、緊急度の高い開発に関してはスピードを優先して一旦既存の画面への開発を行うケースもあります。既存の機能をそのまま移行するケース、プロダクトの新規施策を導入するタイミングで同時に移行するケース、どちらもバランスよく進められるように、各プログラムのデザイナーおよびモバイルチームリーダーと密に連携をとりながら進めています。</p>
<h2>Screen Listと進捗の追跡</h2>
<p>画面のマイグレーションをしていくにも、まずどれくらいの機能および画面があるのかをできるだけ正確に把握する必要があります。 メルペイではプロジェクトを立ち上げる際に全ての画面一覧をスプレッドシートにまとめたスクリーンリストを作成しました。これによって画面数や画面パターンを正確に把握したり、機能のオーナーシップを持つチームや開発・デザイン担当者を一元化して把握することができるようになりました。全ての画面にIDを振ってチーム内で対象とする画面の認識齟齬がないようにもしています。</p>
<p>各画面には以下のような進捗ステータスも付けてグラフにすることで、全体の進捗を視覚的に追跡できるようにしています。</p>
<ul>
<li>TODO</li>
<li>Design In Progress</li>
<li>Design In Review</li>
<li>Design Done</li>
<li>Dev in Progress</li>
<li>In QA</li>
<li>Done</li>
</ul>
<p>隔週の定例ミーティングでマイグレーションに取り組んでいる機能の進捗状況を更新しています。</p>
<p>各画面の状況を正確に把握することで、Engineering Projectsの定例ミーティングでもCTO, VPoEに対して透明性高く正確な情報をレポートすることができています。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/12/da9b8b04-screenlist.png" alt="screen list" /><br />
<em>Screen Listのシート一部抜粋</em></p>
<h2>Strategy Sharing</h2>
<p>メルペイでは四半期の後半に一度Strategy Sharingという、次の四半期の施策の優先順位の決定や戦略・ロードマップのレビューを行い全社的に共有するタイミングを設けています。その中で、Engineering Projectsとしても次の四半期にターゲットとする機能と進捗率を定義し、全社的にプロジェクトのマイルストーンを共有しています。これによってエンジニアリング部門以外の方々でも進捗を把握することができ、全社的な認知を得ることができています。</p>
<h2>現在の進捗状況</h2>
<p>これまで2023年から2024年にかけて約2年間プロジェクトを推進してきましたが、2024年12月現在、Androidは約65%、iOSが約60%のマイグレーションを完了してリリースできています。開発中のものも含めると70% ~ 80%のマイグレーションが進んでいます。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/12/9543c8b4-android.png" alt="Android" /><br />
<em>Android Progress</em></p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/12/9f6b7bb3-ios.png" alt="iOS" /><br />
<em>iOS Progress</em></p>
<p>今後もメルペイのモバイルエンジニアリングをアップデートすべく、チーム一丸となって100%を目指してプロジェクトを推進していきます。</p>
<h1>終わりに</h1>
<p>この記事では、メルカリアプリ内に存在するメルペイの数百画面をSwiftUI/Jetpack Composeに移行するプロジェクトプロジェクトの経緯とその進め方について紹介しました。<br />
プロジェクト規模も大きく長期的な取り組みで困難なことも多いですが、テックカンパニーとしてこのような取り組みに挑戦できていることはメルカリグループのエンジニアリング組織としての強みだと思います。<br />
SwiftUI/Jetpack Composeへ移行を検討しているチーム、移行を進めているチームの皆さまの参考になれば幸いです。</p>
<p>次の記事は @kimuras さんです。引き続きお楽しみください。</p>
- スムーズな CDN プロバイダーの移行とその先の取り組みhttps://engineering.mercari.com/blog/entry/20241223-a-smooth-cdn-provider-migration-and-future-initiatives/https://engineering.mercari.com/blog/entry/20241223-a-smooth-cdn-provider-migration-and-future-initiatives/<p>はじめに こんにちは! Microservices Platform Network チーム の hatappi です。 メルカリでは、2023年からCDNプロバイダーを Fastly から Cloudflare へと段 […]</p>
Mon, 23 Dec 2024 11:00:55 GMT<h2>はじめに</h2>
<p>こんにちは! Microservices Platform Network チーム の <a href="https://x.com/hatappi">hatappi</a> です。</p>
<p>メルカリでは、2023年からCDNプロバイダーを Fastly から Cloudflare へと段階的に移行してきました。現在、ほぼすべての既存サービスのトラフィック移行が完了しており、新規サービスについては全て Cloudflare を使用しています。</p>
<p>この記事では、CDNプロバイダーの比較ではなく、移行プロセスに焦点を当て、スムーズに移行するために実施したアプローチを解説します。また、移行が私たちの最終的なゴールというわけではありません。その先の取り組みの一環として、社内向けの「CDN as a Service」についても紹介します。</p>
<h2>背景</h2>
<p>メルカリでは、これまでに開発環境および本番環境を合わせて数百のFastlyサービスが存在しており、これらは私たちNetworkチームによって管理されてきました(メルペイのサービスに関してはFintech SREチームが管理しています)。私たちのチームは、GCP VPCのようなクラウド・ネットワーキングやデータセンター・ネットワーキングも管理しています。そのため、限られた時間の中でスムーズに移行を進める方法を考える必要がありました。</p>
<h2>移行ステップ</h2>
<h3>準備</h3>
<p><a href="https://www.fastly.com/">Fastly</a>と<a href="https://www.cloudflare.com/">Cloudflare</a>はどちらもCDNプロバイダーですが、全く同じ挙動をするわけではありません。たとえば、キャッシュの挙動について見ると、FastlyではオリジンのVaryヘッダーを考慮してキャッシュを分けますが、Cloudflareは現時点では画像に対してのみ対応しています。このように、移行対象のサービスがFastlyでどのような機能を使用しているか、そしてその機能をCloudflareではどのように実現するかを調査する必要がありました。</p>
<p>移行機能を検討する際に重視したのは、現状の挙動を大きく変更しないことです。移行を始めることで、改善点を加えたり新しい機能を試したくなることもあります。そのようなアプローチは、数サービスの移行であれば許容されるかもしれませんが、数百のサービスに対して行うと移行完了に途方もない時間が必要になります。そのため、移行範囲を広げすぎないというこの方針は、移行をスムーズに進めるために重要でした。また、この方針は後のステップでも効果を発揮します。</p>
<h3>実装</h3>
<p>Cloudflareの管理にはTerraformを採用し、公式から提供されている<a href="https://registry.terraform.io/providers/cloudflare/cloudflare">Terraformプロバイダー</a>を使用しました。Terraformのリソースは、各サービスごとに個別に使用するのではなく、Terraformモジュールを作成し、そのモジュールに必要な機能を実装することで、今後のサービス移行時にも再利用できるようにしました。</p>
<p>Fastlyでは、自分たちが実装したロジックやFastlyが提供するロジックが最終的に一つのVCL(Varnish Configuration Language)としてまとめられます。移行の初期段階では、各VCLを個別に確認し、CloudflareのTerraformリソースへ手作業で実装していました。このため、少なくとも実装には30分以上かかっていました。</p>
<p>しかし、各サービスの移行が進むにつれて、VCLのロジックの中でも移行が必要なものと無視できるものがパターン化してきました。そこで移行の後半では、Go を用いて移行スクリプトを作成し、VCLを元にTerraformモジュールの設定を自動化できるようにしました。そして、自動で設定できなかったロジックは、移行検討が必要なものとして出力するようにしました。これにより、シンプルなサービスであれば、数分で実装が完了するようになりました。</p>
<h3>テスト</h3>
<p>ほとんどのサービスには開発環境と本番環境があるため、まず開発環境でテストを行い、その後本番環境の移行を行います。しかし、トラフィックが多いサービスやミッションクリティカルな機能を提供するサービスの移行時には、事前に挙動をテストするためのコードを書きました。準備段階で述べたように、Fastlyと大きく挙動を変えていないため、Fastlyサービスの挙動を基準として比較するテストを書くことができました。これにより、自信を持ってトラフィックの移行を開始することができました。</p>
<h3>トラフィックの移行</h3>
<p>テストをどれだけ重ねても、本番のトラフィックを流す際には慎重に行う必要があります。特に、問題が発生した際には迅速にロールバックすることが求められます。</p>
<p>そこで私たちは、DNSレイヤーでこれらの要件を満たすアプローチを採用しました。メルカリでは<a href="https://aws.amazon.com/route53/">Amazon Route 53</a>や<a href="https://cloud.google.com/dns?hl=en">Google Cloud DNS</a>を使用しており、どちらもWeighted Routingをサポートしています。これにより、少しずつトラフィックをFastlyからCloudflareへ切り替えることができます。何か問題が発生した際には、CloudflareへのWeightを0%にするだけでロールバックが可能となり、手順もシンプルです。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2024/12/74fc6879-a-smooth-cdn-provider-migration-and-future-initiatives-gradual-migration-gradual-migration.jpg"><img src="https://storage.googleapis.com/prd-engineering-asset/2024/12/74fc6879-a-smooth-cdn-provider-migration-and-future-initiatives-gradual-migration-gradual-migration.jpg" alt="" /></a></p>
<p>移行中のモニタリングには<a href="https://www.datadoghq.com/">Datadog</a>を使用し、いくつかのメトリクスを確認しました。</p>
<p>まず、意図したトラフィック率になっているかを監視します。以下の画像は、FastlyとCloudflareのリクエスト比率から見たCloudflareのトラフィック率を示しています。<br />
<a href="https://storage.googleapis.com/prd-engineering-asset/2024/12/a414e43c--2024-12-19-14.35.39.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2024/12/a414e43c--2024-12-19-14.35.39.png" alt="Cloudflare Traffic Rate" /></a></p>
<p>次に、以下の画像はCloudflareへの全リクエストから見た、2xxステータスコード以外のリクエスト比率を示しています。トラフィックの増加に伴い、これらの値が増えないかを確認することも重要な指標となります。<br />
<a href="https://storage.googleapis.com/prd-engineering-asset/2024/12/5eaa8cd0--2024-12-19-14.36.31.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2024/12/5eaa8cd0--2024-12-19-14.36.31.png" alt="Cloudflare Non 2xx Rate" /></a></p>
<p>また、クライアント側から見たFastlyサービスとCloudflareの挙動には大きな変更がないはずなので、それぞれのキャッシュ率やリクエスト数や使用帯域の比較も行いました。</p>
<p>すべてのサービスの移行が完全に無障害で終わったわけではありませんが、これらのアプローチにより大規模な障害を回避し、問題が発生した際には影響範囲を最小限に抑えることができました。</p>
<h2>CDN as a Service</h2>
<p>移行の次のステップとして、Networkチームが集中管理していたCDNサービスの運用をセルフサービス化し、開発者自身が開発・運用できるようにする「CDN as a Service」を目指しています。</p>
<p>今回は、「CDN as a Service」に向けた2つの取り組みを紹介します。</p>
<h3>CDN Kit</h3>
<p>移行の際に触れたTerraformモジュールに私たちは「CDN Kit」という名前をつけています。開発者はCDN Kitを利用することで、1つ1つTerraformリソースを定義する必要がなく、自分が実現したいことを手軽に達成できます。また、私たちPlatformチームとしては、全体に提供したいベストプラクティスを各サービスごとに変更するのではなく、モジュール内に含めることで一箇所で提供できます。</p>
<p>例えば、オリジンへのアクセスをCloudflareを通じて行うというシンプルな要件であれば、開発者は以下のようにCDN Kitを使用するだけで済みます。</p>
<pre><code class="language-hcl">module "cdn_kit" {
source = "..."
company = "mercari"
environment = "development"
domain = "example.mercari.com"
endpoints = {
"@" = {
backend = "example.com"
}
}
}</code></pre>
<p>開発者から見るとシンプルな定義ですが、CDN Kitを利用することで、さまざまなリソースが自動的に作成されます。以下はその一例です。</p>
<ul>
<li>BigQuery へのログ送信
<ul>
<li>Cloudflareが提供するログをBigQueryに格納する際は、通常Cloud Functionsを使用します(<a href="https://developers.cloudflare.com/logs/get-started/enable-destinations/bigquery/">ドキュメント</a>)。しかし、これらを各サービスごとに作成するのは手間がかかるため、CDN Kit内で必要なリソースを自動的に作成しています。</li>
</ul>
</li>
<li>Datadog モニターの作成</li>
<li>ドメインに応じた自動更新される SSL/TLS 証明書の発行</li>
</ul>
<h3>権限付与システム</h3>
<p>Cloudflareのダッシュボードは、インタラクティブにアクセス分析を行える強力なツールです。しかし、開発者にダッシュボードを公開するためには、以下の課題を解消する必要がありました。</p>
<ul>
<li>退職者管理</li>
<li>権限付与の自動化</li>
</ul>
<p>1つ目の退職者管理は、CloudflareのダッシュボードでSSOを有効にし、アイデンティティプロバイダーとしてOktaを利用することで解決しました(<a href="https://developers.cloudflare.com/cloudflare-one/identity/idp-integration/okta/">ドキュメント</a>)。メルカリではOktaを使用しており、退職者の管理はITチームが担当しています。そのため、退職者処理の一環でOktaからアカウントが削除されると、Cloudflareのダッシュボードへのアクセスも自動的にできなくなります。このため、私たちNetworkチームは退職者管理を考慮する必要がありません。</p>
<p>2つ目の権限付与の自動化については、社内の既存のシステムと連携して動作する仕組みを実装しました。以下はその概要図です。<br />
※ Team Kitとは、開発者グループの管理を行うためのTerraformモジュールです。<br />
<a href="https://storage.googleapis.com/prd-engineering-asset/2024/12/cf64dca1-a-smooth-cdn-provider-migration-and-future-initiatives-sso.jpg"><img src="https://storage.googleapis.com/prd-engineering-asset/2024/12/cf64dca1-a-smooth-cdn-provider-migration-and-future-initiatives-sso.jpg" alt="Cloudflare SSO" /></a></p>
<p>開発者チームを管理するTerraformモジュールであるTeam Kit、およびCloudflareを管理するCDN Kitは、GitHubのリポジトリで管理されています。これらのモジュールの更新を自動的に検知するGitHub Actions Workflow を作成しました。このWorkflowは、更新を検知すると、以下に示すような権限管理用のマニフェストファイルを生成し、リポジトリにコミットします。</p>
<pre><code class="language-yaml">account_id: [Cloudflare Account ID]
zone_id: [Cloudflare Zone ID]
zone_name: [Cloudflare Zone Name]
teams:
- team_id: [ID of Team Kit]
roles:
- Domain Administrator Read Only
users:
- email: [email address]
roles:
- Domain Administrator Read Only</code></pre>
<p>次にマニフェストファイルの変更を検知して、別のGitHub Actions Workflowが動作し、マニフェストをもとにCloudflareの各権限を設定します。</p>
<p>Team KitとCDN Kitの変更を検知して動作するGitHub Actions Workflowで、Cloudflareの権限を直接変更しない理由は、マニフェストファイルを保持することで宣言的にCloudflare の権限を管理できるようにするためです。これにより、例えば手動で権限が変更された場合であっても、いつでもマニフェストに基づいて正しい状態に戻すことが可能となります。</p>
<p>この権限付与システムによって、開発者はNetworkチームに権限を依頼する必要なくダッシュボードを見ることができるようになりました。すでに、開発者自らがダッシュボード上で問題を発見し、解決する事例も観測されており、「CDN as a Service」への取り組みがすでに効果を発揮していることを嬉しく思います。</p>
<h2>おわりに</h2>
<p>この記事では、CDNプロバイダーの移行におけるアプローチを紹介し、その後のステップとして社内向けに提供する「CDN as a Service」の取り組みとしてCDN KitというTerraformモジュール、権限付与システムについて説明しました。</p>
- バッチ処理におけるSLO定義とその運用方法https://engineering.mercari.com/blog/entry/20241223-batch-slo/https://engineering.mercari.com/blog/entry/20241223-batch-slo/<p>こんにちは。メルコインでバックエンドエンジニアをしているiwataです。 この記事は、Merpay & Mercoin Advent Calendar 2024 の記事です。 tl;dr バッチ処理のSLO定義っ […]</p>
Mon, 23 Dec 2024 10:00:54 GMT<p>こんにちは。メルコインでバックエンドエンジニアをしているiwataです。<br />
この記事は、<a href="https://engineering.mercari.com/blog/entry/20241125-merpay-mercoin-advent-calendar-2024/">Merpay & Mercoin Advent Calendar 2024</a> の記事です。</p>
<h2>tl;dr</h2>
<ul>
<li>バッチ処理のSLO定義って難しい… そんな悩みを解決するSLO定義方法</li>
<li>BigQueryとSpanner External Datasetを活用した具体的な監視方法の紹介</li>
<li>メルコインの安定稼働を支える技術</li>
</ul>
<p>最近ではビットコインやイーサリアムを<a href="https://about.mercoin.com/news/20240801bitcointsumitate/">積み立てる機能</a>を開発していました。</p>
<p>積立の開発では積立日にバッチ(以下、積立バッチ)を起動することでビットコインなどの仮想通貨の購入処理を実行するようにしました。</p>
<p>積立バッチはお客さまの資産をあつかうとても重要なバッチです。設定された積立日に確実に処理を実行し終える必要があります。このようにシステムの信頼性を考える上で広く認識されている考え方がSLOです。それではバッチ処理におけるSLO定義とはなんでしょうか?</p>
<h2>バッチ処理においてSLOを定義することの難しさ</h2>
<p>一般的にSLOで用いられるSLIとしてはAvailability(可用性)とLatencyがあります。<br />
前者はエラーレートの逆数として算出可能であり、APIがエラーをどのくらいの割り合いで返しているかで監視することが多いです。<br />
後者はAPIの応答時間を99パーセンタイルなどの統計値を基に監視します。<br />
いずれの指標もAPIであればその定義も分かりやすく、監視方法も確立されています。</p>
<p>ではバッチ処理についてあてはめるとどうでしょうか?<br />
バッチのAvailabilityといった場合に、実行時の終了コードだけをみればよいのでしょうか? それともバッチで一括処理するデータひとつひとつのエラーレートをみればよいのでしょうか? またLatencyについてはバッチ処理の実行時間だけをみればよいのでしょうか? それともこちらもデータひとつひとつの処理時間をみればよいのでしょうか?</p>
<p>一方でSLOの設計においてはCUJ(Critical User Journey)に代表されるように、ユーザー視点で考えることが大切です。バッチ処理によってお客さまは何を期待しているのでしょうか。<br />
これらのことを考える上で以下の資料がとても参考になりました。</p>
<p><a href="https://speakerdeck.com/rynsuke/batutichu-li-noslowodoushe-ji-suruka">バッチ処理のSLOをどう設計するか – Speaker Deck</a> </p>
<p>スライドにあるようにバッチ処理で担保したい信頼性をデータの「納期」(デッドライン)と「品質」という観点で整理しました。<br />
積立においてお客さまが期待することは「積立日に積立処理が完了していること」となるはずです。</p>
<ul>
<li>積立日=デッドライン</li>
<li>積立処理完了=データ品質</li>
</ul>
<p>すなわち「積立日の23時59分59秒までにすべての注文処理が完了(残高不足などによる失敗も含む)」をSLOとして定義しました。積立においてはバッチ実行時にタイムアウトなど一時的なエラーが発生した場合に別プロセスで自動リトライする仕組みもあったりしますが、この定義を用いれば別プロセスであってもカバーできます。お客さまからみれば例え何回リトライしていようが、その日中に処理が完了していれば問題ないとみなせるからです。</p>
<p>以降では具体的な監視方法を紹介します。</p>
<h2>BigQueryを使った監視</h2>
<p>メルコインではデータベースとしてCloud Spanner(以下、Spanner)を使っています。<br />
社内では分析用途で使うために、SpannerのデータをBigQueryに定期的に同期するパイプラインが用意されています。Spannerへの負荷を考慮しなくて済むように、監視クエリはBigQueryに対して実行します。</p>
<p>またBigQueryに対して定期的にクエリを実行し、その結果をDatadogから監視する仕組みも構築されているためこれを用いて実現しました。<br />
詳細は省きますが下図のような仕組みが構築されています。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/12/a18b175f-querymon.drawio.png" alt="querymon system diagram" /><br />
ロゴ出典: Slack, Datadog, GitHub, Goolge Cloud</p>
<p>簡単に説明すると、事前に定義しておいたクエリをBigQuery上で定期的に実行し、その結果をカステムメトリクスとしてDatadogに送信しています。クエリ実行した結果のレコード数がカスタムメトリクスとして送信されるので、Datadog上でメトリクスモニターを定義して監視できます。</p>
<p>例えば積立であれば未処理のレコードを返すクエリを定義し、デッドラインである23時59分59秒以降にカスタムメトリクスが1以上であればSLO違反に気づける、という具合です。実際には違反前に気づきたいので十分に余裕をもった時間で気づけるよう監視しています。</p>
<h2>Spanner External Datasetの利用</h2>
<p>単純な用途でこれまで紹介したツール郡を用いることで監視できていました。ところがSpannerに直接クエリを実行せず、BigQueryを使うことで以下のような問題があります。</p>
<ul>
<li>SpannerからBigQueryへの同期がリアルタイムでない</li>
<li>同期処理がテーブル単位で実行される</li>
</ul>
<h3>SpannerからBigQueryへの同期がリアルタイムでない</h3>
<p>同期用のパイプラインは1時間に一回実行されており、リアルタイムにデータが同期されているわけではありません。これによって検知に数時間かかってしまいます。このタイムラグを許容できないケースも考えられます。</p>
<h3>同期処理がテーブル単位で実行される</h3>
<p>同期用のパイプラインはテーブル単位で設定し実行されます。したがって、任意のタイミングでBigQuery上の複数のテーブル間には整合性が担保されていません。<code>JOIN</code>した結果を用いて監視をおこないたい場合にはこれは致命的です。</p>
<h3>Spanner External Dataset</h3>
<p>これらの課題を解決するために一部のクエリでは<a href="https://cloud.google.com/blog/products/data-analytics/introducing-bigquery-external-datasets-for-spanner">Spanner External Dataset</a>を使いました。External Datasetを使うことで以下のようなメリットがあります。</p>
<ul>
<li>BigQueryへの同期は必要なく、Spannerに直接クエリできるのでタイムラグとテーブル間の不整合がなくなる</li>
<li><a href="https://cloud.google.com/spanner/docs/databoost/databoost-overview">Data Boost</a>がつねに有効なのでSpannerへの負荷を考えなくてもよい</li>
</ul>
<p>また同じような機能として<a href="https://cloud.google.com/bigquery/docs/spanner-federated-queries">Spanner Federated Queries</a>がありますが、<a href="https://cloud.google.com/bigquery/docs/reference/standard-sql/federated_query_functions#external_query">EXTERNAL_QUERY関数</a>が読みづらいなどの理由でExternal Datasetを採用しました。</p>
<h2>External Datasetの利用方法</h2>
<p>最後にTerraformを使った利用方法を載せておきます。</p>
<h3>google_bigquery_dataset</h3>
<pre><code class="language-terraform">resource "google_bigquery_dataset" "spanner_external" {
provider = google-beta
project = {your-gcp_project_id}
dataset_id = "spanner_external"
location = "US"
external_dataset_reference {
external_source = "google-cloudspanner:/projects/{your-gcp_project_id}/instances/{your-spanner.google_spanner_instance_name}/databases/{your-database-name}"
connection = ""
}
}</code></pre>
<p>設定値は適宜置き換えてもらえばよいですが、<code>connection</code>だけ<a href="https://cloud.google.com/bigquery/docs/spanner-external-datasets#terraform">オフィシャルドキュメント</a>にあるように空文字で設定する必要があるので注意が必要です。</p>
<h3>google_bigquery_dataset_access</h3>
<pre><code class="language-terraform">resource "google_bigquery_dataset_access" "access_spanner_external" {
project = {your-gcp_project_id}
dataset_id = google_bigquery_dataset.spanner_external.dataset_id
role = "roles/bigquery.dataViewer"
user_by_email = {your-google_service_account.email}
}</code></pre>
<p>クエリを実行するService Accountに対して上記で作成したExternal Datasetへのアクセス権を付与します。</p>
<h3>google_spanner_database_iam_member</h3>
<pre><code class="language-terraform">resource "google_spanner_database_iam_member" "monitor_can_read_database_with_data_boost" {
project = {your-gcp_project_id}
instance = {your-spanner.google_spanner_instance_name}
database = {your-database-name}
role = "roles/spanner.databaseReaderWithDataBoost"
member = "serviceAccount:{your-google_service_account.email}"
}</code></pre>
<p>External Dataset経由でSpannerにもアクセスするので対象のデータベースに対しての<a href="https://cloud.google.com/spanner/docs/iam#spanner.databaseReaderWithDataBoost">spanner.databaseReaderWithDataBoost</a>ロールを付与します。ちなみにこのIAMロールは最近追加されました。Data Boostを使うにはこれまで別途カスタムロールの作成が必要だったり面倒だったのですが、いまではこのロールを割り当てるだけでよくなりました。</p>
<h2>まとめ</h2>
<p>バッチのSLO定義について書きました。<br />
バッチでは「デッドライン」と「データ品質」を基にSLOを定義することでうまく運用できています。<br />
データ品質を監視する方法としてBigQueryに対して定期的にクエリを実行する手法を採用しています。BigQueryとSpannerとの連携についてはExternal Datasetが提供されるようになったことで課題が解消されています。</p>
<p>この記事が読んでいただいた方の運用の手助けになれば幸いです。</p>
<p>次の記事は masamichiさんです。引き続きお楽しみください。</p>
- メルカリハロのFlutter開発とSREhttps://engineering.mercari.com/blog/entry/20241222-mercari-hallo-flutter-development-and-sre/https://engineering.mercari.com/blog/entry/20241222-mercari-hallo-flutter-development-and-sre/<p>こんにちは。メルカリハロのSRE TLの@nakaです。 この記事は、連載:メルカリ ハロ 開発の裏側 – Flutterと支える技術 –の7回目と、 Mercari Advent Calendar 2024 の18日目 […]</p>
Sun, 22 Dec 2024 11:00:36 GMT<p>こんにちは。メルカリハロのSRE TLの<a href="https://x.com/gymnstcs">@naka</a>です。</p>
<p>この記事は、<a href="https://engineering.mercari.com/blog/entry/20241129-mercari-hallo-2024/" title="連載:メルカリ ハロ 開発の裏側 – Flutterと支える技術">連載:メルカリ ハロ 開発の裏側 – Flutterと支える技術</a> –の7回目と、<br />
<a href="https://engineering.mercari.com/blog/entry/20241125-mercari-advent-calendar-2024/" title="Mercari Advent Calendar 2024">Mercari Advent Calendar 2024</a> の18日目の記事です。</p>
<p>今回は、「メルカリ ハロ」のFlutter開発をSREとの関わりという観点から紹介します。日常の業務上はFlutter開発とSREの業務はそこまで密接な関わりはありませんが、Flutter開発の裏側ではSREがそれを支える場面もいくつかあり、そこからSREエンジニアとしての学びもありました。<br />
開発・テスト環境改善やDeveloper Experience (DX) の向上に取り組んでいる具体的な内容を含めて紹介します。</p>
<h2>概要</h2>
<p>メルカリハロのFlutter開発を行っているメンバーは、高いオーナーシップを持ち、自ら理想的な開発環境を構築している理想的なチームです。普段はSREと直接関わることは少ないものの、今回はSREとしてFlutter開発に関連した具体的なサポート事例を紹介し、チーム間のコミュニケーションやSREとしての学びについても触れていきます。</p>
<h2>QAとSRE: E2Eツール選定や自動化設定</h2>
<p>E2Eテストの重要性</p>
<p>信頼性の高いアプリを迅速にリリースするためには、E2E(エンドツーエンド)テストの導入と自動化が欠かせません。メルカリハロでは、QAチームとの連携を通じて、E2Eテストのツール選定や自動化設定においてSREと連携する機会がありました。</p>
<p>ツール選定のサポート</p>
<p>E2Eテストのツール選定時には、セキュリティ観点、メルカリ内で提供されているプラットフォームとの相性、ツールのコスト、ツールの特性などを総合的にみて決定する必要があります。QAメンバーが率先してツール選定をリードしていましたが、立ち上げ当初から環境構築をSREが担当してきたので、ツール選定の検討にも参加しました。<br />
SREチームは、要件を満たすために、QAチームが適切なツールの評価と選定をできるよう支援しました。具体的には、以下の点を重視しました:<br />
セキュリティ: テストツールの仕様を読み、メルカリのセキュリティ基準を満たしているか<br />
統合性: 既存の開発環境やプラットフォームとスムーズに統合できること。PoCを実施してから本導入する場合の段階的導入の具体的なステップ。</p>
<p>これらの情報をQAチームが統合して最終的なツール選定をスムーズに行うことが出来ました。</p>
<p>自動化設定のサポート</p>
<p>ツール選定後、E2Eテストの自動化設定を行う際には、CI/CDパイプラインとの連携やNetworkの設定調整が必要となります。SREチームとしても、以下のサポートを提供しました。</p>
<p>E2Eテストが実行されるCI/CD環境からAPIサーバへアクセスする必要がありますが、ローカルでは動くけどCI/CD環境では動かないなどのケースでサポート依頼が来ました。<br />
SREチームは、QAチームとともにE2Eテストの実行結果の確認や、E2Eテストのシナリオを確認してデバッグしたり、ネットワークの疎通環境の調整などを行ったりして、最終的に自動化が可能な状態になるよう支援しました。</p>
<p>これらの取り組みにより、E2Eテストの自動化が実現し、開発チームは信頼性の高いコードを迅速にリリースできるようになりました。</p>
<h2>FlutterチームとSRE: CI/CD改善のSlack Bot</h2>
<p>メルカリハロでは、各チームのメンバーが自律してCI/CDのPipelineを整備しています。Flutterチームも、CI/CDの課題に積極的に取り組んでおり、リリース時のSlack botとの連携なども行いDX向上に努めてきました。</p>
<p>Slack BotによるCI/CD改善</p>
<p>最近、FlutterチームからGitHub Actionsの失敗時にSlack上から簡単にリトライ(Retry)できるようにしたいという要望がありました。これに応えるために、SREチームはFlutterメンバーとともに以下の取り組みを行いました。</p>
<p>Slack Appの設計とBootstrapingのサポート</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/12/e4018e73-slackapp-1024x683.png" alt="" /></p>
<p>やりたいこととツールを相談して、実現可能な設計を一緒に行いました。</p>
<p>Slack AppからGitHub APIを使うためには、Security チームが管理・提供しているToken Serverの仕組みを使って、Installation Access Tokenを取得する必要があります。</p>
<p>第一回の<a href="https://engineering.mercari.com/blog/entry/20241203-token-server-google-cloud/" title="Google CloudからGitHub PATと秘密鍵をなくす – Token ServerのGoogle Cloudへの拡張">Google CloudからGitHub PATと秘密鍵をなくす – Token ServerのGoogle Cloudへの拡張</a> の中で詳細が紹介されています。</p>
<p>Installation Access Tokenは、必要なScopeを事前に定義しておく必要があり、必要最低限の権限だけを付与することが可能です。また、今回はDX向上用のSlack Appなので、簡単にDeployができるようにCloud Runで構築することにしました。</p>
<p>このアプリが完成すると、失敗したGitHubActionsの再実行を直接Slack上から実行できるようになり、今まで必要だったGitHubのUIを開くひと手間をなくすことができDXの向上に貢献することができます。</p>
<p>FlutterメンバーはCloud Run自体の経験はなかったので、Cloud Runの初期設定として、SREで空のサービスを立ち上げて、Deployする方法を伝え、スピーディに開発に取り組める環境を構築しました。</p>
<p>このSlack Appは現在絶賛開発中ではありますが、すでにSREのサポートが不要な状態にあるので、あとはリリースされる日が来るのが楽しみです。</p>
<h2>コミュニケーションといつでもサポートできる体制構築</h2>
<p>メルカリ ハロの開発プロジェクトのなかで、SREチームとして、今まで業務上深く関わることが少なかったチームに対しても、間接的にFlutter開発をサポートする機会が生まれました。</p>
<p>このサポート体制がうまく機能した裏側には、FlutterチームとSREチームの日頃からの交流があったことも大きな理由の背景だったと思います。</p>
<p>SREチームは、Flutterチームとの信頼関係を築くために、定期的な業務関連の情報共有に加えて、懇親会への参加やチームビルディングランチの開催など日常的な交流を積極的に行っています。これにより、チームを跨いだメンバーとカジュアルにコミュニケーションが取りやすくなり、信頼関係を構築することができました。この信頼関係があることで、いざ何か問題が発生した際に、Flutterチームが気軽にSREチームに相談できる雰囲気が作れているのではと思います。</p>
<p>SREチームは、普段の業務のなかでの関わりが深いチームから、日常業務ではそこまで関わりの多くないチームまで幅広くサポートできるように、常に気軽に相談・質問しやすい雰囲気づくりを心がけています。</p>
<h2>まとめ</h2>
<p>今回は、SREとしてFlutter開発の裏側でのサポート事例として、QAチームと連携してE2Eテスト環境構築したことや、FlutterメンバーとともにDX向上に取り組んだことを紹介しました。<br />
SREの活躍は表には出にくいながらも、裏側での支援の積み重ねによってプロダクトの品質や開発効率に大きく貢献できる場面はたくさんあると感じています。<br />
だからこそ、普段業務上のやり取りが少ない他チームのメンバーとも積極的に情報交換しつつ、何か自分たちが貢献できる業務がないかを貪欲に探していくような姿勢が大切だと考えています。<br />
これからも、他チームからさらに信頼されるSREエンジニアになるために、技術面・組織面の両面から一層高みを目指していきたいと思っています。</p>
<p>引き続き<a href="https://engineering.mercari.com/blog/entry/20241129-mercari-hallo-2024/" title="メルカリ ハロ 開発の裏側 – Flutterと支える技術 –">メルカリ ハロ 開発の裏側 – Flutterと支える技術 –</a>シリーズを通じて、私たちの技術的知見や経験を共有していきますので、どうぞご期待ください。また、<a href="https://engineering.mercari.com/blog/entry/20241125-mercari-advent-calendar-2024/" title="Mercari Advent Calendar 2024">Mercari Advent Calendar 2024</a>の他の記事もぜひチェックしてみてください。それでは、次回の記事でお会いしましょう!</p>
- PaymentPlatformにおける仕訳IDを活用した会計プロセスの最適化https://engineering.mercari.com/blog/entry/20241221-df9d144586/https://engineering.mercari.com/blog/entry/20241221-df9d144586/<p>こんにちは。メルペイ MoMの@abcdefujiです。 この記事は、Merpay & Mercoin Advent Calendar 2024 の記事です。 はじめに 私たちPaymentPlatformは、メ […]</p>
Sun, 22 Dec 2024 10:00:31 GMT<p>こんにちは。メルペイ MoMの<a href="https://twitter.com/_abcdefuji">@abcdefuji</a>です。<br />
この記事は、<a href="https://engineering.mercari.com/blog/entry/20241125-merpay-mercoin-advent-calendar-2024/">Merpay & Mercoin Advent Calendar 2024</a> の記事です。</p>
<h2>はじめに</h2>
<p>私たちPaymentPlatformは、メルカリグループ内のさまざまな価値循環、すなわち決済、返金、送金、入出金、精算などを実現しているチームです。現在、以下の図のように多様なサービスを支えています。<br />
<img src="https://storage.googleapis.com/prd-engineering-asset/2024/12/f7a4e24f--2024-12-21-18.48.12.png" alt="" /></p>
<p>今回、PaymentPlatformがどのようにして各サービス毎の取引を識別し、会計システムと連携しているかについてお話ししたいと思います。また、本記事を作成するにあたり、AccountingチームのKENTYさんに多大なサポートをいただきました。</p>
<h2>概要 – 会計ついて</h2>
<p>そもそも会計とは何のために行うのかを簡単に説明したいと思います。今回は財務会計に関して説明します。<br />
財務会計は、企業の財務状況や経営成績を外部の利害関係者(株主、投資家、債権者、規制当局など)に報告するための会計手法です。主に、財務諸表を作成し、一定の会計基準に基づいてデータを整理・報告します。財務会計の主な成果物は、以下の3つの財務諸表です。</p>
<p>貸借対照表(バランスシート):特定の時点における企業の資産、負債、資本の構成を示すものです。資産は企業が所有するもの、負債は企業が負っている債務、資本は自己資本を表します。<br />
損益計算書(インカムステートメント):一定期間における企業の収益と費用を記録し、最終的な利益または損失を示します。売上高、営業利益、税引前利益、当期純利益などが含まれます。<br />
キャッシュフロー計算書:企業の現金の流出入を記録するもので、営業活動、投資活動、財務活動の各セクションに分かれています。これにより、現金の流れが明確になります。</p>
<p>つまり、システムからデータを集約し上記財務三表を作成するプロセスが必要になります。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/12/4468791b--2024-12-21-18.33.35.png" alt="" /></p>
<p>しかしながら、データを集約させるだけでは実現することはできません。財務三表を実現するためにはデータを仕訳の形式に変換する必要があります。</p>
<h2>複式簿記と仕訳</h2>
<p>仕訳の形式に変換するために複式簿記の知識が必要になります。複式簿記とは、すべての取引を二重に記録する方法で、資産と負債、収益と費用の関係を明確にします。これにより、取引の正確性が高まり、財務諸表が信頼性を持つようになります。</p>
<h3>仕訳</h3>
<p>仕訳は、企業の日々の取引を会計上の勘定科目に振り分ける作業です。取引が行われる際には、「借方」と「貸方」に分けて記録します。</p>
<ul>
<li>借方(Dr.):資産の増加、負債の減少、費用の発生を記録します。</li>
<li>貸方(Cr.):資産の減少、負債の増加、収益の発生を記録します。</li>
</ul>
<p>例えば、商品を10,000円で販売した場合の仕分けは以下のようになります。</p>
<ul>
<li>借方:現金 10,000円(資産が増加)</li>
<li>貸方:売上 10,000円(収益が増加)</li>
</ul>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/12/6934d888--2024-12-21-18.32.06-1024x268.png" alt="" /></p>
<p>このように、各取引について対応する借方と貸方を設定することで、常に帳簿が総合的にバランスを保たれる仕組みが整います。これが複式簿記の基本的な考え方です。</p>
<h3>勘定科目</h3>
<p>企業や組織が財務活動を記録するために使用するカテゴリや項目のことを指します。これらは通常、貸借対照表や損益計算書といった財務諸表に表示されます。勘定科目は、それぞれの取引やイベントを記録し分類するための基本単位であり、以下のようなものが含まれます。</p>
<ul>
<li>資産:現金、預金、受取手形、売掛金、在庫など</li>
<li>負債:買掛金、借入金、支払手形、未払費用など</li>
<li>資本(純資産):資本金、資本剰余金、利益剰余金など</li>
<li>収益:売上、受取利息など</li>
<li>費用:仕入原価、給料、広告宣伝費、租税公課など</li>
</ul>
<p>上記勘定科目に従いデータを会計システムに蓄積されることが望まれます。</p>
<h2>会計システムとPaymentPlatformの接続方法について</h2>
<p>実際のシステムと会計システムの連携について具体的に説明します。以下のような決済シーケンスを想定します。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/12/3d7a4b56--2024-12-21-18.42.03.png" alt="" /></p>
<ol>
<li>お客さまが購入処理を実行します。</li>
<li>次に、メルペイが決済リクエストを処理し、お客さまの残高を減少させます。</li>
<li>同時に、加盟店の売上が増加します。</li>
<li>最後に、PaymentPlatformから会計システムにデータを連携します。</li>
</ol>
<p>上記の流れで、決済データが会計システムに連携されています。このように、決済データを会計システムに連携しています。</p>
<h3>単一のユースケースから複数のユースケースへ</h3>
<p>では、PaymentPlatformが支えるユースケースが拡張し、さまざまなサービスで同じAPIが利用される状況になった場合、どのように取引を分類し、会計の観点から仕訳を行うことができるでしょうか。たとえば、メルカリグループ内の事業Aのサービスと事業Bのサービスから同じ決済APIが利用された場合であっても、商流やユーザーストーリーが異なる場合には、会計の観点からどのように取引を特定すべきでしょうか。</p>
<p>メルペイでは、この問題を解決する手段として仕訳IDを用意しています。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/12/3db7d269--2024-12-21-18.42.24.png" alt="" /></p>
<ol>
<li>お客さまがメルペイでコード決済を実行します。</li>
<li>メルペイは決済リクエストを仕訳IDとともに処理し、お客さまの残高を減少させます。</li>
<li>加盟店の売上は増加させる。</li>
<li>PaymentPlatformから会計システムに対して仕訳IDを用いてデータを連携します。</li>
</ol>
<p>このように、仕訳IDを上位から下位まで一貫して伝搬させ、会計システムにデータを届けています。これにより、複数のユースケースにおいても、会計の観点から決済データの正確な識別が可能となります。</p>
<h2>開発プロセス</h2>
<p>私たちの開発プロセスは、会計との密接な連携を重視しています。</p>
<ol>
<li>開発の初期段階で事業の商流やユーザーストーリーを確認し、そこからお金の動きがどのように発生するかを分析します。この段階で、エンジニア、PdM(プロダクトマネージャー)、および経理の三者間で共通の認識を確立します。</li>
<li>その後、経理によって適切な仕訳IDの設計が行われ、発行された仕訳IDがエンジニアに提供されます。</li>
<li>最後に、エンジニアがその仕訳IDを会計システムまで正確に伝搬させるという流れになっています。</li>
</ol>
<p>このプロセスでは、経理とエンジニアがお互いに歩み寄ることで、システム観点と会計観点の両方において最適な解決策を目指しています。</p>
<h2>実現できた事</h2>
<h3>複雑なクエリからの解放</h3>
<p>仕訳IDを活用することで、複雑な会計クエリからの脱却を実現しました。データを単一のシステムに集約し、仕訳単位での集計が容易になったため、関連システムからの複雑なクエリに頼ることなく、効率的な集計が可能になりました。また、会計システムにデータを集約してイミュータブルな状態で管理することで、冪等性が担保され、後日同じ手法で集計を行っても一貫した結果を得ることができます。</p>
<h3>密な開発体制</h3>
<p>体制としては、エンジニアが会計ドメインを理解しようとし、経理がエンジニアリングを理解しようとする歩み寄りの姿勢が育まれています。新しいユースケースが登場すると、必ずエンジニアから会計上の整理が正確であるかの確認が行われ、会計ドメインを意識しながら開発が進められています。</p>
<h3>コスト削減</h3>
<p>さらに、会計システム連携の共通化により、プロダクト開発コストの削減も達成しています。PaymentPlatformを利用することで、仕訳IDを介して会計システムまで一貫して連携できるため、新規事業や既存システムの拡張において、会計システムとの連携をプラットフォームとして取り込むことが可能です。</p>
<h2>課題 / 今後に関して</h2>
<p>仕訳IDの管理コストに関して、メルカリグループの事業拡大に伴い、PaymentPlatformがサポートするユースケースが大幅に増加しました。そのため、仕訳IDの増加に伴い現行の設計方針を維持できるか、あるいは将来的に維持が難しくなる可能性もあるため、継続的な検討が必要です。システム面でも、増加した仕訳IDに対してアドホックに対応したケースが負債として残っており、これを解消していくことが求められています。</p>
<p>開発プロセスにおいて、現状では会計要件を無視して開発を進めることは難しい状況です。さらに迅速な開発体験を実現するために、会計要件を気にせずに済む開発手法を模索しています。例えば、新規事業や既存の拡張から会計要件に落とし込み、仕訳を決定するまでのプロセスを簡略化したり、ある程度サービス側で整理した要件を出すフレームワークがあると効果的です。</p>
<p>オンボーディングのコストが高い点については、開発プロセスで会計ドメインに関するコミュニケーションが避けられません。特に新しいメンバーにとっては、会計システムと仕訳IDの設計を理解するまでのハードルが高い状況です。このため、キャッチアップのためのドキュメントを整備し、開発プロセスの簡略化を目指しています。</p>
<p>最後に、すべてのケースがPlatformとして吸収できるわけではありません。PaymentPlatformがカバーできるケースとカバーできないケースが存在し、事業やサービスによっては特殊なユースケースがあり、PaymentPlatformを経由せずに会計システムに連携している場合もあります。今後、PaymentPlatformのガバナンスを整理し、どこまでを吸収し、どこを対象外とするかを明確に管理する必要があります。</p>
<p>以上のような課題がある一方で、私たちはさらなる便利なPaymentPlatformの実現を目指し、日々精進を続けていきます。最後までお読みいただきありがとうございました。</p>
<h3>参考資料</h3>
<ul>
<li><a href="https://engineering.mercari.com/blog/entry/2019-09-19-113909/">https://engineering.mercari.com/blog/entry/2019-09-19-113909/</a></li>
<li><a href="https://careers.mercari.com/mercan/articles/40838/">https://careers.mercari.com/mercan/articles/40838/</a></li>
<li><a href="https://engineering.mercari.com/blog/entry/2019-06-07-155849/">https://engineering.mercari.com/blog/entry/2019-06-07-155849/</a></li>
</ul>
<p>次の記事は iwataさんです。引き続きお楽しみください。</p>
- 事業者請求払い: 多様な決済を支える決済基盤の仕組みhttps://engineering.mercari.com/blog/entry/20241221-invoice-payment/https://engineering.mercari.com/blog/entry/20241221-invoice-payment/<p>この記事は Merpay & Mercoin Advent Calendar 2024 の記事です。 メルペイの Payment Core チームでバックエンドエンジニアをしている komatsu です。 普段は […]</p>
Sat, 21 Dec 2024 10:00:26 GMT<p>この記事は <a href="https://engineering.mercari.com/blog/entry/20241125-merpay-mercoin-advent-calendar-2024/">Merpay & Mercoin Advent Calendar 2024</a> の記事です。</p>
<p>メルペイの Payment Core チームでバックエンドエンジニアをしている komatsu です。<br />
普段はメルカリグループのさまざまなプロダクトに共通した決済機能を提供するための決済基盤の開発・運用をしています。<br />
この記事では、私たちが直近開発した新しい決済手段であり、今年リリースされたスキマバイトサービス「メルカリ ハロ」や現在試験運用中のサービス (以降サービス A とします) に利用されている、“事業者請求払い” について紹介します。</p>
<h2>事業者請求払いとは</h2>
<p>メルカリグループが提供するプロダクトは個人のお客さまに支えられていますが、同時にメルカリShops やメルペイなど、多くのパートナー企業 (加盟店、事業者) によっても支えられています。<br />
そのため、個人のお客さまに提供する残高やあと払いといった決済スキーム以外にも、加盟店との資金の流れをシステムで管理するさまざまなユースケースが存在します。<br />
メルカリとパートナー企業間における資金の流れは大きく分けて 2 種類あります。</p>
<ul>
<li>メルカリ -> パートナー企業
<ul>
<li>メルペイ加盟店の売上から手数料などを差し引いた金額を、その加盟店に振り込むための資金の動きです。内部的には、締め日に応じて売上金の精算をし、メルカリが各加盟店に銀行振込をすることで実現されています。</li>
</ul>
</li>
<li>パートナー企業 -> メルカリ
<ul>
<li>加盟店の売上をメルカリに移動することで、toB のサービス提供する場合の資金の動きです。例えばメルカリ ハロでは、お客さまが求人に応募をし、お仕事をすることでパートナー企業から給与が支払われます。ただし、パートナー企業から直接お客さまに支払われるわけではなく、メルカリ ハロを通してお客さまに給与の支払いが行われます。これはアルバイト完了後にすぐ給料が支払われる体験を提供するためや、パートナー企業から手数料を受け取ることを実現するためのものです。他にも試験運用中のサービス A では、加盟店がメルカリに代金を支払うことで、メルカリが加盟店に対してサービスを提供します。</li>
</ul>
</li>
</ul>
<p>このような、パートナー企業からメルカリに資金が移動する場合に利用されるのが事業者請求払いです。<br />
事業者請求払いでは、次のような手順でサービスの提供が行われます。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/12/030d3444-relations.png" alt="" /></p>
<p>メルカリはメルカリ ハロのようなプロダクトを、Payment Platform はメルカリグループにおけるあらゆる資金の移動に利用されている決済基盤を、加盟店は各プロダクトがサービスを提供しているパートナー企業を指します。</p>
<ol>
<li>
<p>加盟店の与信審査依頼<br />
事業者請求払いはサービスを提供してからその金額に応じた支払いを加盟店に請求するため、クレジットカードのようなあと払いの決済スキームです。<br />
そのため、実際にサービスの提供を行う前に、貸し倒れリスクなどを考慮して各加盟店が支払い可能な金額分のサービスを提供する必要があります [1](現在加盟店の与信審査が必要な場合、私たちのチームから外部の会社に API 経由で依頼して実現しています)。</p>
</li>
<li>
<p>審査結果に基づくサービスの提供<br />
与信審査が完了したら、サービスを提供できる状態になります。<br />
メルカリ ハロであれば与信枠内で求人募集ができるようになります。<br />
決済基盤の観点では、このタイミングで利用分を与信総額から都度減らし、利用分は未回収金の債権として管理します。</p>
</li>
<li>
<p>請求<br />
月末などプロダクトが定める締め日をもって請求金額が計算され、加盟店にメルカリに返済するための請求書が送付されます。<br />
請求書には支払う金額のほか、支払先の銀行口座やインボイス制度に基づく明細などが記載されます。</p>
</li>
<li>
<p>請求金額の支払い<br />
加盟店は請求書に記載された金額を、支払期限までに支払います。<br />
決済基盤は入金の通知を受け、債権の消し込みを行います。</p>
</li>
</ol>
<p>ではなぜこのようなスキームが必要なのでしょうか?<br />
事業者請求払いを利用しない最も簡単な方法は、サービス利用時に必要な金額をメルカリに入金することです。<br />
ですが、これには事業者請求払いで解決できる、いくつかの問題点があります。</p>
<ul>
<li>支払いが同月中に何度も発生するため、加盟店もメルカリも振込に関する管理が複雑化する。
<ul>
<li>振込入金に関するオペレーションは手動で行われることが多く、煩雑になります。</li>
<li>事業者請求払いでは月に 1 回程度の作業になるため、加盟店としてもメルカリとしても作業が簡易化されます。</li>
</ul>
</li>
<li>加盟店のキャッシュフローが悪化する
<ul>
<li>サービス利用時に入金する場合、加盟店の売上が立つ前に支払いを行うことになり、手持ちのキャッシュを利用する必要があります。</li>
<li>事業者請求払いではサービス提供の翌月以降に請求をすることで、売上やサービス利用による利益を返済に利用することができます。</li>
</ul>
</li>
<li>適切な金額の請求ができないことがある
<ul>
<li>例えばメルカリ ハロの場合、残業が発生する場合など、サービス提供時 (= アルバイト募集の掲載時) と実際に加盟店が支払う金額には差分が生じることがあるため、前払いの形式では正しい金額を受け取ることが不可能です。</li>
<li>事業者請求払いではサービス提供後に請求が行われるため、実際のサービス利用料を算出したうえで正しい金額を請求することができます。</li>
</ul>
</li>
</ul>
<h2>決済基盤の API 設計</h2>
<p>事業者請求払いの概要を説明したところで、私たち Payment Core チームが既存の決済基盤にこの機能を追加するときの設計について紹介します。</p>
<h3>パートナー間の決済を表現する PartnerTransfer</h3>
<p>決済基盤マイクロサービスである Payment Service はさまざまな資金の動きや決済手段をサポートする API を持っています。<br />
例えば、メルカリで商品を売買する場合、Escrow と呼ばれる API を利用して、買い手の残高やあと払いの枠、クレジットカードといった決済手段を消費し、その分のメルペイ残高を売り手に付与したり、手数料をメルカリ自身の売上として計上します。<br />
他にもコード決済等で購入者から加盟店に資金を移動するための Charge や、キャンペーンのポイント付与等でメルカリからお客さまにポイントを扶養するための Transfer といった API があります。<br />
Payment Service を利用するマイクロサービス、つまりプロダクト側のマイクロサービスはこれらの API を必要に応じて組み合わせながら、さまざまな決済体験をお客さまに提供します。</p>
<p>そして、さまざまある API の中で、パートナー間での資金の流れを表現する PartnerTransfer API があります。<br />
ここでいうパートナーはコード決済を導入している加盟店や、メルカリShops に出店している加盟店、メルカリ ハロに求人を掲載している事業者などが含まれます。<br />
それと同時に、メルカリグループの売上を管理するために、メルカリやメルペイ自身も含まれます。</p>
<p>既存の PartnerTransfer API のユースケースには以下のようなものがあります。</p>
<ul>
<li>メルペイコード決済における加盟店の売上を加算する
<ul>
<li>決済には必ず原資があるため、PartnerTransfer 内部では、メルペイ自身の売上金を減らし、加盟店の売上金に追加する、といった処理が行われます。ここで、売上金に加算ということは、決済基盤が持っている加盟店残高管理用のマイクロサービスである Balance Service 上で加盟店の売上金 <a href="パートナーが持つメルペイ残高のような概念で、加盟店の売上を管理するためのもの。">2</a> が増加することであり、実際に加盟店に振り込まれているわけではない状態です。プロダクトによって定められている精算のタイミングで売上金を加盟店の銀行口座に振り込むことで、現実世界で金銭が移動します。</li>
</ul>
</li>
<li>加盟店の売上からメルカリShops の手数料を差し引く
<ul>
<li>メルカリShops では売り手である加盟店から手数料を徴収するビジネスモデルです。そのため、手数料分を加盟店の売上からメルカリに移動する処理が必要となり、PartnerTransfer によって実行されます。</li>
</ul>
</li>
</ul>
<p>事業者請求払いは原資が加盟店の売上ではなく与信枠という違いはありますが、資金の流れは加盟店からメルカリ自身という点で、PartnerTransfer が想定するユースケースに当てはまります。<br />
そのため、PartnerTransfer がサポートする 1 つの決済手段として事業者請求払いを組み込むことにしました。</p>
<h3>与信管理や精算を柔軟にする設計</h3>
<p>メルカリグループにはさまざまなプロダクトがあり、それぞれのプロダクトで千差万別の要件があります。<br />
決済基盤チームはプロダクトの要望を受け入れつつも、なるべく一般化し、ロバストな設計を保つ必要があります。</p>
<p>グループ内のプロダクトごとに、それぞれが抱える加盟店の特徴や、決済基盤として求められるものが異なるケースがあります。例えば、メルカリ ハロではより多くの事業者がアルバイトを募集できるようにする一方で、サービス A のように与信情報がすでに分かっており、社会的な信用のある特定の事業者に対してのみ機能を提供する場合もあります。<br />
言い換えれば、前者では大量の事業者に対して適切な与信審査をする必要があり、後者は審査をせずに大きな金額を与信として与えることができます。</p>
<p>そのため、私たち決済基盤では 2 通りの事業者請求払いのフローを構築しました。<br />
1 つ目が外部の与信審査や請求を行う企業をバックエンドとして利用するパターンです。<br />
メルカリではメルペイのあと払いなどで利用されている個人向けの与信管理の仕組みはありますが、パートナー向けのものはありませんでした。<br />
そのため、企業与信の管理を行っている企業の API に別のマイクロサービス [3](Payment Service の責務は決済のトランザクション管理や価値交換のためのインターフェースの提供であり、外部サービスとの接続は、Payment Provider という別のマイクロサービスを開発して責務の分割をしています。Payment Service が Payment Provider を gRPC で呼び、Payment Provider が外部サービスを HTTP などのプロトコルで呼ぶようなフローになります。) を介して繋ぎ込みを行いました。<br />
2 つ目は外部サービスを利用せず、メルペイが持つ既存の精算管理の仕組みを利用したパターンです。<br />
こちらは企業与信を審査することはできないかわりに、精算や請求、入金確認までをすべてメルペイ内のシステムで完結して提供します。</p>
<p>これらの決済基盤の裏側の仕組みはユースケースに応じて選択できるものなので、API のインターフェースはなるべく一般化し、パラメータ 1 つで切り替えられる仕組みが必要でした。<br />
そのため、Payment Service の RPC は以下のようになります。</p>
<pre><code class="language-proto">// payment.proto
service PaymentService {
rpc CreatePartnerTransfer(CreatePartnerTransferRequest) returns (CreatePartnerTransferResponse) {}
rpc CapturePartnerTransfer(CapturePartnerTransferRequest) returns (CapturePartnerTransferResponse) {}
rpc CancelPartnerTransfer(CancelPartnerTransferRequest) returns (CancelPartnerTransferResponse) {}
}
message CreatePartnerTransferRequest {
// 資金の移動元のパートナー
uint64 from_partner_id = 1;
// 資金の移動先のパートナー
uint64 to_partner_id = 2;
// from_partner_id が利用する決済手段
PaymentMethod payment_method = 3;
}
message CapturePartnerTransferRequest {
string partner_trasnfer_id = 1;
}
message CancelPartnerTransferRequest {
string partner_trasnfer_id = 1;
}
message PaymentMethod {
enum Type {
// Balance Service が管理するパートナーの売上金を利用する決済
PARTNER_SALES = 1;
// 事業者請求払いの与信を利用する決済
PARTNER_INVOICE = 2;
}
PaymentMethod.Type type = 1;
oneof details {
PaymentMethodPartnerSales partner_sales = 1;
PaymentMethodPartnerInvoice partner_invoice = 2;
}
}
message PaymentMethodPartnerSales {
uint64 amount = 1;
}
message PaymentMethodPartnerInvoice {
// 請求明細の項目を表現する message
message Detail {
enum TaxType {
UNKNOWN = 0;
EIGHT_PERCENT = 1;
TEN_PERCENT = 2;
ANY = 3;
}
// 項目の名称
string name = 1;
// 単価
int64 price = 2;
// 量
int64 quantity = 3;
// 税区分
TaxType tax_type = 4;
}
enum InvoicePaymentProvider {
INVOICE_PAYMENT_PROVIDER_UNKNOWN = 0;
INVOICE_PAYMENT_PROVIDER_XXX = 1; // 外部サービスを利用した事業者請求払い (XXX は仮の名前)
INVOICE_PAYMENT_PROVIDER_INHOUSE = 2; // 内製の事業者請求払い
}
InvoicePaymentProvider invoice_payment_provider = 1;
repeated Detail details = 2;
}</code></pre>
<p>ここで、CreatePartnerTransfer, CapturePartnerTransfer, CancelPartnerTransfer はそれぞれ PartnerTransfer における決済のオーソリ、キャプチャ、キャンセルを表現します。<br />
CreatePartnerTransfer は <code>PaymentMethod</code> を引数に取り、パートナーの残高を消費する決済手段か、事業者請求払いによる与信枠を消費する決済手段化かを選択できます。<br />
事業者請求払いの場合、<code>PaymentMethodPartnerInvoice</code> によって各明細の単価や量、インボイス制度に対応する税区分などを入力できます。<br />
<code>InvoicePaymentProvider</code> によって、バックエンドで利用する事業者請求払いのプロバイダ (外部サービスなのか、メルペイ内製のものなのか) を切り替えることができます。<br />
これによって、Payment Service を利用するプロダクト側はバックエンドのシステムをあまり知らない状態で、ユースケースに応じてパラメータを切り替えるだけで事業者請求払いの機能を一貫して利用することができます [4](実際には各バックエンドに依存する API なども存在しますが、決済のタイミングではこのフィールド以外を意識する必要がありません)。</p>
<h2>決済の整合性担保</h2>
<p>さまざまなマイクロサービスや外部サービスを跨いだ決済スキームである以上、整合性の担保が重要になります。<br />
特に外部サービスとの突合は重要であり、プロダクトローンチ時から厳密な仕組みづくりが必要でした。<br />
決済ごとの与信審査があるため、決済ステータスのライフサイクルは以下のようになります。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/12/9a890df2-status-transitions.png" alt="" /></p>
<p>外部サービスとは毎日一度、前日のすべての取引の状態が CSV ファイルとして SFTP サーバ経由で送られてきます。<br />
外部サービスに接続しているマイクロサービスである Payment Provider では、その時刻になったら CSV ファイルを取得し、決済基盤が持っている決済ステータスと差分がないかを突合します。<br />
一見簡単にみえるこの処理において難しい点は、決済基盤が持っているデータは最新のものであるのに対し、CSV ファイルに含まれるのはあくまで前日終了時点でのステータスであるという点です。<br />
例えば、</p>
<ol>
<li>12/19 23:55 CreatePartnerTransfer によって外部サービスを利用した決済が発生し、オーソリが完了</li>
<li>12/20 00:05 CapturePartnerTransfer によって決済のキャプチャが完了</li>
<li>12/20 06:00 12/19 分の取引データが連携 (status: authorized)</li>
<li>12/20 07:00 12/19 分の突合処理を実行</li>
</ol>
<p>このような時系列の場合、CSV に含まれるデータでは最後のステータスは authorized であるのに対し、私たちの決済基盤では captured になります。<br />
これらを考慮するために、上記の状態遷移をコードで表現し、ステータスに差分が会ったとしても移り得るものなのかを判断し、柔軟に突合する仕組みを作りました。</p>
<p>一方で、1 日以上経ってもステータスが同じにならない場合、それは不整合として検知する必要があります。<br />
例えば、</p>
<ol>
<li>12/19 23:55 CreatePartnerTransfer によって外部サービスを利用した決済が発生し、オーソリが完了</li>
<li>12/20 00:05 CapturePartnerTransfer によって決済のキャプチャが完了</li>
<li>12/20 06:00 12/19 分の取引データが連携 (status: authorized)</li>
<li>12/20 07:00 12/19 分の突合処理を実行</li>
<li>12/21 06:00 12/20 分の取引データが連携 (status: authorized)</li>
<li>12/21 07:00 12/20 分の突合処理を実行</li>
</ol>
<p>この例の場合、5 では取引データは captured になっていることを期待していますが authorized のままになっています。<br />
状態遷移のみを考慮した場合では authorized から captured への遷移は想定されるため不整合と判別できません。<br />
そのため、前回突合された際の決済ステータスを考慮に入れることで、より正確な整合状態を判別するようにしています。</p>
<p>このような突合の仕組みを利用して、より安定した決済基盤としての機能をプロダクトチームに提供しています。</p>
<h2>おわりに</h2>
<p>この記事では、メルカリ ハロをはじめとするメルカリグループが近年注力しているプロダクトにおけるパートナーとの決済手段である、事業者請求払いについて解説しました。<br />
実際にはもっと泥臭い処理が多く存在しており、さまざまなチームやマイクロサービスが関わって全体のフローが構成されていますが、今回は主に与信を利用した決済の部分にフォーカスをしました。<br />
事業者請求払いによる決済スキームは、”あらゆる価値を循環させ、あらゆる人の可能性を広げる“ というメルカリグループのミッションを実現するうえで重要な役割を担っており、Payment Core チームは今後もこのような決済基盤の開発を通じて多くのプロダクトに貢献していきます。</p>
<p>次の記事は abcdefuji さんです。引き続きお楽しみください。</p>
- mSCPとJamf Pro APIによるmacOSセキュリティ設定の手動IaC化の試行https://engineering.mercari.com/blog/entry/20241220-mscp-jamf-api-macos-security-configs-iac/https://engineering.mercari.com/blog/entry/20241220-mscp-jamf-api-macos-security-configs-iac/<p>この記事は、Mercari Advent Calendar 2024 の16日目の記事です。 メルカリでは多くの従業員の業務端末にMacbookを用いています。Security チームがmacOSのセキュリティ設定に関わ […]</p>
Fri, 20 Dec 2024 11:00:55 GMT<p>この記事は、<a href="https://engineering.mercari.com/blog/entry/20241125-mercari-advent-calendar-2024/">Mercari Advent Calendar 2024</a> の16日目の記事です。</p>
<p>メルカリでは多くの従業員の業務端末にMacbookを用いています。Security チームがmacOSのセキュリティ設定に関わる一連の作業品質・効率改善のため、設定内容の手動IaC化(Infrastructure as Code)を検討・試行した際の技術や課題に関わる所見について紹介します。</p>
<h2>概要</h2>
<p>この記事では、macOSのセキュリティ設定に関わる一部の作業を自動化し、作業効率を改善するための取り組みについてご紹介します。具体的には、macOS Security Compliance Project (以降mSCP)とJamf Proという2つのツールを組み合わせることで、macOSのセキュリティ設定をコード化しGitHub上で管理する手法となります。これは、将来的なGitOps化の前段階の位置づけです。</p>
<p>mSCPは、さまざまなコンプライアンスガイドラインに基づいたセキュリティ設定のベースラインを自動生成するツールです。一方、Jamf Proは、MDM(モバイルデバイス管理)ツールとして、macOS端末を一元的に管理します。これら2つのツールを連携させることで、以下のメリットが得られます。</p>
<ul>
<li>設定情報をコード化しバージョン管理することで、設定変更のトレーサビリティを確保し、監査性を高めます。</li>
<li>設定変更作業の自動化により、人的ミスを減らし、複数環境への展開を容易にします。</li>
<li>GitHubのpull request機能を活用し、変更要求ごとにコードレビューと承認プロセスを設けることで、誤った設定変更のリスクを低減します。</li>
</ul>
<p>また、Jamf APIを用いてmSCPの設定内容を含めたJamf Proの構成をコード化する方法とその考慮点についても触れています。これにより、よりシームレスなプロセス自動化を実現することができます。</p>
<p>本記事では、mSCPとJamf Proを組み合わせたmacOSセキュリティ設定の自動化の実例を交えながら、そのメリットや課題、今後の展望について説明しています。macOSのセキュリティ設定の管理に課題を抱えているセキュリティエンジニアやシステム管理者の参考となれば幸いです。</p>
<h2>セキュリティ設定のライフサイクル</h2>
<p>macOSのセキュリティ設定は利用者端末への配布までさまざまな検討・作業が必要となります。まず、セキュリティ設定配布に関わる作業例について下表に紹介します。</p>
<p>表1:セキュリティ設定のフェージング例</p>
<table>
<thead>
<tr>
<th style="text-align: left;">No</th>
<th style="text-align: left;">フェーズ</th>
<th style="text-align: left;">内容</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: left;">1</td>
<td style="text-align: left;">設計</td>
<td style="text-align: left;">設定内容や配布スコープの定義。設定配布の目的・背景や配布に伴う利用者側への影響の試算</td>
</tr>
<tr>
<td style="text-align: left;">2</td>
<td style="text-align: left;">実装</td>
<td style="text-align: left;">設計に基づきセキュリティ設定のためのMDM管理ツール上の手順書等を作成</td>
</tr>
<tr>
<td style="text-align: left;">3</td>
<td style="text-align: left;">テスト</td>
<td style="text-align: left;">実装した設定値が有効かを検証機で検証</td>
</tr>
<tr>
<td style="text-align: left;">4</td>
<td style="text-align: left;">配布</td>
<td style="text-align: left;">実装した設定を本番の端末環境へ配布</td>
</tr>
<tr>
<td style="text-align: left;">5</td>
<td style="text-align: left;">監視運用</td>
<td style="text-align: left;">配布された設定が適切に適用されるかどうかをMDM管理ツール側及び端末側の双方で確認</td>
</tr>
</tbody>
</table>
<p>そして、一連の作業は一過性のものでは無く、OSのバージョン・アップやOS開発元の仕様変更、業務・セキュリティ要件等に伴い、設定の見直しが一定頻度で発生するサイクリックな作業となります。<br />
フェーズ毎の作業の重みは変更内容により変わりますが、作業品質を保ちながら各フェーズを全て手動で実施・管理していくことは人的な工数が多くかかる見込のため、一部自動化を試行し始めました。</p>
<h2>macOS Security Compliance Project (mSCP) とは</h2>
<p>表題の前の助走的な位置づけとして、mSCPの内容について紹介します。mSCPはmacOSのセキュリティ設定を自動作成するためのCLI(Command Line Interface)ツールであり、各種のコンプライアンスガイドライン(※1)に基づいたベースライン(プリセット)を作成可能です。GitHub上でオープンソースとして開発・配布されており、主には下記の機能を提供しています。</p>
<p>※1: NIST 800-53、800-171、DiSA-STIG、CIS Benchmarks, CMMC, CNSSI等</p>
<ol>
<li>YAML形式のテキストファイル編集によるベースラインや設定値のカスタマイズ</li>
<li>セキュリティ設定用の構成プロファイルやスクリプトの作成。尚、スクリプトには構成プロファイル含む全対象設定のチェック機能(除外設定管理)も含まれる。</li>
<li>設定内容に関わるドキュメント生成(adoc, html, json, pdf, xls形式に対応)</li>
</ol>
<pre><code class="language-yaml">title: "タイトル"
description: "概要"
authors: "作成者・チーム名等"
parent_values: "cis_lvl1"
profile:
- section: "auditing"
rules:
- audit_acls_files_configure
- audit_acls_folders_configure
- audit_auditd_enabled
<中略>
- section: "macos"
rules:
<中略>
- section: "passwordpolicy"
rules:
<中略>
- section: "systemsettings"
rules:
<後略></code></pre>
<p>2024年時点でmSCPでは約200項目のセキュリティ設定を構成プロファイルとスクリプトで適用可能です。mSCPを活用することにより、例えば、上表No.1, 2の設計・実装フェーズにおいては以下の様に変更可能です:</p>
<ol>
<li>mSCPで設定可能な全項目に関わるドキュメント生成(例:Microsoft Excelブック形式)</li>
<li>設計のベースとなるコンプライアンスガイドラインの内容をスプレッドシート化</li>
<li>No.1, 2で作成したシートを1つのシートに統合</li>
<li>統合シートで必要に応じて「現状の設定値」や「変更予定の設定値」「変更理由やその影響」列を追加し変更予定内容を関係者内で評価</li>
<li>確定した設定値でシートをフィルターしRuleID(mSCPにおける設定項目の識別名)をYAMLファイルへコピー&ペースト</li>
<li>mSCPコマンドにより構成プロファイル・スクリプトの作成</li>
</ol>
<p>この統合シートの利点としては、設定内容に付随する背景・外部のガイドラインとの差異を一元的に管理し、仮にmSCPにおける設定内容が更新されてもRuleIDやCCE(NISTのCommon Configuration Enumeration)等の識別子で再マッピングが比較的容易に可能なことです。もちろん、設定内容の説明先やその背景に応じてより要約・抽象化する必要はありますが、そのベースの資料としてはこちらのシート一つで収められます。</p>
<p>また、上記リストNo.6「mSCPコマンドにより構成プロファイル・スクリプトの作成」においてもmSCPがGitHubでバージョン管理されているため、GitHub Actions workflowへ組み込み、設定の自動作成を行う場合、mSCPの特定のコミットバージョンを指定して作成することが可能です。<br />
以下はworkflow YAMLファイルの作成例となります。</p>
<pre><code class="language-yaml"><前略>
jobs:
build:
runs-on: example-env # <適切なrunner環境名を指定>
steps:
- name: Checkout this repository
uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
- name: Checkout mSCP repository
uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
with:
repository: usnistgov/macos_security
ref: '6b4330120592baf7f5a696764e67f2fbd0eaaa3a' # tested version
path: 'macos_security'
- uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0
with:
python-version: '3.11'
- name: Install pip
run: python -m ensurepip --upgrade
- name: Install Ruby and Bundler
run: |
sudo apt-get update
sudo apt-get install -y ruby-full
sudo gem install bundler
- name: Install dependencies for mSCP
run: |
cd macos_security
pip install -r requirements.txt
bundle install --binstubs --path mscp_gems
ls -la
- name: Copy mSCP configuration files to cloned macos_security repository
run: |
cp -r ./macos/mSCP/baselines ./macos_security/build/
cp -r ./macos/mSCP/custom/rules/* ./macos_security/custom/rules/
cp ./macos/mSCP/logo_corporate.png ./macos_security/scripts/logo_corporate.png
- name: Generate mSCP guidelines
run: |
cd macos_security
./scripts/generate_guidance.py -p build/baselines/sample_baseline.yaml -l logo_corporate.png -s -x
<後略(作成したファイルの保管やrunner環境によっては自動テスト実行を必要に応じて追加)></code></pre>
<p>また、上表No.3のテストフェーズにおける検証環境への設定配布に際しては、これまでMDM管理ツールなどのWeb UIベースでの手動設定から、mSCPで生成された設定ファイルのアップロードによって一部の設定項目を代替し、さらにチェックスクリプトを用いることで手動・目視での確認項目を削減することができます。</p>
<h3>mSCPとJamf Proを組み合わせる利点</h3>
<p>メルカリではMDM管理ツールとしてJamf Proを利用していますが、mSCPと組み合わせることで上表No.5 監視運用フェーズにおける利点についても紹介します。Jamf Proには管理対象のコンピュータ情報(名前、モデル名、シリアル番 等)について管理者側で新たな属性を追加できる、「拡張属性」機能があります。この拡張属性にはスクリプトの実行結果を設定できるため、mSCPのチェックスクリプトの実行結果を抽出・整形し下図の様に属性値としてJamf Pro上に登録することが可能です(※3)。<br />
※3:<a href="https://github.com/jordanburnette/mSCP_EAs">https://github.com/jordanburnette/mSCP_EAs</a> </p>
<p>下図は対象端末の属性情報一覧の一例ですが、「Baseline – Failed Count」項目は構成プロファイルや設定スクリプトで適用できなかった設定項目数を記載しています。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/12/2272252c-mscp_ea_sample-1024x372.png" alt="mSCP_EA_Sample" /></p>
<p>図1:mSCPの適用結果を抽出する拡張属性(引用元:<a href="https://raw.githubusercontent.com/jordanburnette/mSCP_EAs/refs/heads/main/mSCP_EA_Sample.png">https://raw.githubusercontent.com/jordanburnette/mSCP_EAs/refs/heads/main/mSCP_EA_Sample.png</a> )</p>
<p>Jamf Pro上ではスマートコンピュータグループの設定にて、拡張属性の値を対象端末を絞り込む検索クライテリアとして設定可能であり、例えば下図の2段目の式で「Baseline – Failed Count」> 0の端末の抽出が可能です。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/12/dd24e68e-mscp_non_compliant_jamf_smart_computer_group-1024x225.png" alt="mscp_non_compliant_jamf_smart_computer_group" /></p>
<p>図2:スマートコンピュータグループのクライテリア設定画面</p>
<p>端末側で設定が適用されない状態は、さまざまな要因が考えられますが、ここでは単純に、端末利用者が一時的にローカル管理者権限を借用の上、システム設定を変更し、変更後の戻し忘れと仮定しましょう。仮に対象の設定がmSCPスクリプトで設定可能であれば、Jamf Pro上のポリシーのスコープ設定にて上記のスマート・コンピュータグループを設定し、適当な「トリガー」や「実行頻度」を設定することで、自動で設定の再適用が可能です。</p>
<h3>mSCPを利用する上での考慮点</h3>
<p>mSCPを利用する利点がある一方で、mSCPが作成する各種の設定ファイルを適切に管理する必要があります。下記は約100項目のmSCP Ruleを設定したカスタムベースラインYAMLをもとに作成した設定ファイル一覧です。</p>
<pre><code>build/sample_baseline
├── sample_baseline_compliance.sh
<中略>
├── mobileconfigs
│ ├── preferences
│ │ ├── com.apple.MCX.plist
│ │ ├── com.apple.Safari.plist
│ │ ├── com.apple.SoftwareUpdate.plist
│ │ ├── com.apple.SubmitDiagInfo.plist
│ │ ├── com.apple.Terminal.plist
│ │ ├── com.apple.applicationaccess.plist
│ │ ├── com.apple.controlcenter.plist
│ │ ├── com.apple.loginwindow.plist
│ │ ├── com.apple.mDNSResponder.plist
│ │ ├── com.apple.mobiledevice.passwordpolicy.plist
│ │ ├── com.apple.preferences.sharing.SharingPrefsExtension.plist
│ │ ├── com.apple.screensaver.plist
│ │ ├── com.apple.security.firewall.plist
│ │ ├── com.apple.systempolicy.control.plist
│ │ └── com.apple.timed.plist
│ └── unsigned
│ ├── com.apple.MCX.mobileconfig
│ ├── com.apple.ManagedClient.preferences.mobileconfig
│ ├── com.apple.Safari.mobileconfig
│ ├── com.apple.SoftwareUpdate.mobileconfig
│ ├── com.apple.SubmitDiagInfo.mobileconfig
│ ├── com.apple.Terminal.mobileconfig
│ ├── com.apple.applicationaccess.mobileconfig
│ ├── com.apple.controlcenter.mobileconfig
│ ├── com.apple.loginwindow.mobileconfig
│ ├── com.apple.mDNSResponder.mobileconfig
│ ├── com.apple.mobiledevice.passwordpolicy.mobileconfig
│ ├── com.apple.preferences.sharing.SharingPrefsExtension.mobileconfig
│ ├── com.apple.screensaver.mobileconfig
│ ├── com.apple.security.firewall.mobileconfig
│ └── com.apple.systempolicy.control.mobileconfig
└── preferences
└── org.sample_baseline.audit.plist</code></pre>
<p>構成プロファイル経由で設定する内容はmobileconfigsサブディレクトリ配下にありますが、<em>.plist、 </em>.mobileconfigファイルは各々14、15ファイルに及びます。基本的にはplistかmobileconfigのどちら一方のファイル群を配布することで所定の設定が適用されますが、mSCPのバージョンや適用対象端末のOSバージョンの組み合わせによっては、mobileconfigかplistファイルの一方でのみ有効な設定があることを確認しています。<br />
mSCPはApple社やJamf社の公式サイトで紹介されているツールではありますが(※4)、位置づけとしてはサードパーティツールであり、本番適用に際しては事前にmSCPを利用者側で検証する必要があります。</p>
<p>※4</p>
<ul>
<li><a href="https://support.apple.com/ja-jp/guide/certifications/apc322685bb2/web">https://support.apple.com/ja-jp/guide/certifications/apc322685bb2/web</a> </li>
<li><a href="https://www.jamf.com/blog/macos-security-compliance-project/">https://www.jamf.com/blog/macos-security-compliance-project/</a> </li>
</ul>
<p>このことは、上表No.3 テストフェーズにおける実機検証において、検証パターンの増加に伴うJamf Proへのファイルアップロード回数の増加を意味します。その回数も適用するベースラインの数、適用対象テナント数、試行回数の乗算となり、加えて各ファイルのスコープ設定の調整を踏まえるとWeb UI上での操作数は相応の規模となり、操作数に比例しオペレーションリスクを増加させる要因にもなります。</p>
<h2>Jamf APIを用いた構成のコード化</h2>
<p>前述のようなリスクや手動での操作を低減し一連の配布作業・管理を効率化するためにJamf APIを用いてmSCP設定内容を含めたJamf Pro構成のコード化(YAML形式のテキストファイル)を試行しました。こちらの手法についてはJNUC 2021(※5)で紹介されたセッションを参考にしています。<br />
※5: <a href="https://www.jamf.com/blog/github-as-the-source-of-truth-for-configuration-in-jamf-pro/">https://www.jamf.com/blog/github-as-the-source-of-truth-for-configuration-in-jamf-pro/</a> </p>
<p>Jamf社はClassic APIとJamf Pro APIの2種類のAPIを提供しておりmSCPに関連するコンポーネントを取得・更新するためのAPI概要は以下のとおりです。</p>
<p>表2:mSCPに関わる各Jamf ProコンポーネントのAPI種別</p>
<table>
<thead>
<tr>
<th style="text-align: left;">No</th>
<th style="text-align: left;">コンポーネント名</th>
<th style="text-align: left;">API種別</th>
<th style="text-align: left;">データ形式</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: left;">1</td>
<td style="text-align: left;">カテゴリー</td>
<td style="text-align: left;">Jamf Pro</td>
<td style="text-align: left;">JSON</td>
</tr>
<tr>
<td style="text-align: left;">2</td>
<td style="text-align: left;">コンピュータグループ</td>
<td style="text-align: left;">Classic</td>
<td style="text-align: left;">XML</td>
</tr>
<tr>
<td style="text-align: left;">3</td>
<td style="text-align: left;">拡張属性</td>
<td style="text-align: left;">Jamf Pro</td>
<td style="text-align: left;">JSON</td>
</tr>
<tr>
<td style="text-align: left;">4</td>
<td style="text-align: left;">macOS構成プロファイル</td>
<td style="text-align: left;">Classic</td>
<td style="text-align: left;">XML</td>
</tr>
<tr>
<td style="text-align: left;">5</td>
<td style="text-align: left;">ポリシー</td>
<td style="text-align: left;">Classic</td>
<td style="text-align: left;">XML</td>
</tr>
<tr>
<td style="text-align: left;">6</td>
<td style="text-align: left;">スクリプト</td>
<td style="text-align: left;">Jamf Pro</td>
<td style="text-align: left;">JSON</td>
</tr>
</tbody>
</table>
<p>そして、2024年時点では公式が提供するTerraform providerやAPIラッパーライブラリーが存在しないことから、実装の投資対効果を踏まえて上記のコンポーネントの管理に特化したAPIラッパーや関連するサービスロジックを実装しました。以下はそのプログラムディレクトリ構造例です。</p>
<pre><code>.
<前略>
├── cmd
│ └── main.go
├── go.mod
├── go.sum
├── internal
│ ├── api
│ │ ├── category.go
│ │ ├── client.go
│ │ ├── computer_group.go
│ │ ├── configuration_profile.go
│ │ ├── extension_attribute.go
│ │ ├── policy.go
│ │ └── script.go
<中略>
│ ├── service
│ │ ├── category.go
│ │ ├── computer_group.go
│ │ ├── configuration_profile.go
│ │ ├── extension_attribute.go
│ │ ├── factory.go
│ │ ├── policy.go
│ │ ├── script.go
│ │ └── service.go
│ └── util
│ ├── compare.go
│ └── file.go
<後略></code></pre>
<p>また、上記のプログラムの構成としては下表のとおりです。</p>
<p>表3:各フォルダ配下のプログラム処理概要</p>
<table>
<thead>
<tr>
<th style="text-align: center;">No</th>
<th style="text-align: left;">フォルダ名</th>
<th style="text-align: left;">処理概要</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: center;">1</td>
<td style="text-align: left;">cmd</td>
<td style="text-align: left;">プログラムのエントリーポイントであり、コマンドライン引数やクレデンシャル情報の処理や後述のserviceの生成・実行等</td>
</tr>
<tr>
<td style="text-align: center;">2</td>
<td style="text-align: left;">service</td>
<td style="text-align: left;">ビジネスロジックの実装箇所。後述のapi、util内の関数等を用いてJamf Proコンポーネントのコード化や、コードとの差分確認、及びJamf Pro側へ適用等</td>
</tr>
<tr>
<td style="text-align: center;">3</td>
<td style="text-align: left;">api</td>
<td style="text-align: left;">Jamf Classic / Pro API双方に対応したAPIラッパー。Jamf Pro上の構成情報を取得、更新等</td>
</tr>
<tr>
<td style="text-align: center;">4</td>
<td style="text-align: left;">util</td>
<td style="text-align: left;">各種コンポーネント横断で利用する処理を共通化した関数群<br />・YAMLファイル化した構成定義とJamf Pro上の構成の差分確認を行う<br />・YAMLファイルの読み込みや書き出し処理</td>
</tr>
</tbody>
</table>
<p>こちらのプログラムを用い、先ほどWeb UIで設定したスマート・コンピュータグループをYAMLファイルへエクスポートすると以下の様になります。(一部の値を掲載用に変更しています)</p>
<pre><code>id: 999
name: mSCP - sample_baseline - NonCompliant
is_smart: true
site:
id: -1
name: NONE
criteria:
size: 2
criterion:
- name: Computer Group
and_or: and
search_type: member of
value: xxxxx_group
- name: mSCP - Failed Results Count
priority: 1
and_or: and
search_type: more than
value: "0"</code></pre>
<p>上記の様な単純なクライテリア設定であればWebUIと作業工数はあまり変わらないですが、クライテリア条件の増加やグルーピングの修正等が発生する場合は、YAMLテキストファイル上で修正しAPI経由で更新する手法が効率的です。</p>
<h3>Jamf ProにおけるWeb UI設定内容をAPI経由で取り扱う際の考慮点</h3>
<p>コード化に伴い、設定作業者はWeb UIに代わり、YAMLファイルの編集に注力することが可能ですが、Jamf ProはWeb UI経由での設定した内容については、UIには現れない部分であるものの、API経由で設定を管理する際の考慮点があり、下表に検証・試行の中で確認した主な例を紹介します。</p>
<p>表4:API経由でJamf Proコンポーネントを管理する際の考慮点</p>
<table>
<thead>
<tr>
<th style="text-align: center;">No</th>
<th style="text-align: left;">考慮点</th>
<th style="text-align: left;">詳細</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: center;">1</td>
<td style="text-align: left;">改行コードの取り扱い</td>
<td style="text-align: left;">Web UIで設定した内容をAPI経由で取得する場合、改行コードが\r\n となる要素があります。(例:拡張属性やスクリプトのscriptContents等)</td>
</tr>
<tr>
<td style="text-align: center;">2</td>
<td style="text-align: left;">構成プロファイルにおけるペイロードの要素</td>
<td style="text-align: left;">mSCPにおいてWeb UI経由で初回アップロードした場合は一部の要素が追加・編集されます(例: PayloadRemovalDisallowed、PayloadIdentifier、PayloadUUID等)</td>
</tr>
</tbody>
</table>
<p>これらの内容はJamf ProーYAMLファイル間の差分確認や、mSCP設定内容をAPI経由で更新する際に特に考慮する必要があります。</p>
<h2>macOSセキュリティ設定のIaC化による利点の考察</h2>
<p>弊社の試行に際しては上記の様なWeb UI側との差分の考慮などを踏まえて、GitHub Actions workflowを用いた全コンポーネントの完全なIaC化やGitOps化は現時点では行っておらず、一部手動で構成管理・適用を行う手法を採っています。但し、仮にIaC化を進めて行くと以下の様な利点があります。</p>
<p>表5:IaC化による主な利点の考察</p>
<table>
<thead>
<tr>
<th style="text-align: center;">No</th>
<th style="text-align: left;">IaC化のポイント</th>
<th style="text-align: left;">考えられる主な利点</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: center;">1</td>
<td style="text-align: left;">構成情報のコード化とAPI経由での構成適用</td>
<td style="text-align: left;">• Web UIベースでの設定手順書の作成が不要となり、設定作業者はYAMLファイルの編集で設定が可能となる。<br />• YAMLファイルのコメント記法を活用することで構成内容の補足説明を追記できる。<br />• Jamf Pro テナントをまたいだ構成の移行が容易になる。(例:検証テナント→本番テナント)</td>
</tr>
<tr>
<td style="text-align: center;">2</td>
<td style="text-align: left;">GitHub上で構成情報のバージョン管理とWorkflowによる自動化</td>
<td style="text-align: left;">• Jamf Pro標準の履歴機能より詳細な変更差分を残すことができ、定期的なバックアップ取得や、YAMLファイル内容とJamf Pro上の設定内容を比較処理を行うことで、意図せぬ変更の検知する運用を自動化できる。<br />• 変更の背景起因をPull Request(以降PRと表記)上に残すことができる。<br />• Branch protection rulesやCODEOWNERSファイルの設定を組み合わせることで、簡易的なワークフロー(起票〜レビュー・承認)を作成できる。<br />• Jamf Proの編集権限がないユーザにおいても、リポジトリへのwrite権限があれば設定変更のPR起票ができる 、Jamf Pro上の編集権限者を必要最小限に絞ることが可能。</td>
</tr>
</tbody>
</table>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/12/9d9ce450-os_security_configs_mgmt_diagram.png" alt="os_security_configs_mgmt_diagram" /></p>
<h2>まとめ</h2>
<p>macOSのセキュリティ設定についてmSCPを活用した設定手法や多数の設定ファイルを管理する上で設定ファイルのコード化や、IaC化による利点などについて紹介しました。セキュリティ設定のライフサイクルを踏まえて、これらの要素を掛け合わせることで、プロセス全体の最適化に関わる考察を得られるよう努めました。<br />
ここで記載された内容については全ての組織や環境に画一的に通じる方法では無く、またこの技術への投資における損益分岐点とその時期は組織ごとに異なります。実装寄りの記述も多数含む記載となりましたが、予め実装内容の解像度を上げることで、投資判断や技術負債化の予防の一助となれば幸いです。<br />
メルカリのSecurity Teamでは、Jamf Proに限らずGoogle WorkspaceやOkta等の他のサービスのIaC化の実装へ投資し、設定自動化及び管理高度化を図っています。</p>
<p>Security Teamにおける採用情報については<a href="https://careers.mercari.com/" title="Mercari Career">Mercari Career</a>をご覧ください。</p>
- メルペイにおける機械学習システム運用時の工夫https://engineering.mercari.com/blog/entry/20241219-e9ad9948c2/https://engineering.mercari.com/blog/entry/20241219-e9ad9948c2/<p>こんにちは。メルペイ 機械学習エンジニアの @rio です。 この記事は、Merpay & Mercoin Advent Calendar 2024 の記事です。 本記事では、メルペイの機械学習エンジニアチームで […]</p>
Fri, 20 Dec 2024 10:00:20 GMT<p>こんにちは。メルペイ 機械学習エンジニアの @rio です。<br />
この記事は、<a href="https://engineering.mercari.com/blog/entry/20241125-merpay-mercoin-advent-calendar-2024/">Merpay & Mercoin Advent Calendar 2024</a> の記事です。</p>
<p>本記事では、メルペイの機械学習エンジニアチームで今年取り組んだ、MLOps の省力化および品質向上についてご紹介します。</p>
<h2>目次</h2>
<ul>
<li><a href="#メルペイの機械学習システムの概要">メルペイの機械学習システムの概要</a></li>
<li><a href="#1-開発ブランチのマージ">1. 開発ブランチのマージ</a>
<ul>
<li><a href="#問題点">問題点</a></li>
<li><a href="#解決策">解決策</a></li>
</ul>
</li>
<li><a href="#2-各種マスタデータの更新">2. 各種マスタデータの更新</a>
<ul>
<li><a href="#問題点-1">問題点</a></li>
<li><a href="#解決策-1">解決策</a></li>
</ul>
</li>
<li><a href="#3-機械学習パイプラインの実行">3. 機械学習パイプラインの実行</a>
<ul>
<li><a href="#問題点-2">問題点</a></li>
<li><a href="#解決策-2">解決策</a></li>
</ul>
</li>
<li><a href="#まとめ">まとめ</a></li>
</ul>
<h2>メルペイの機械学習システムの概要</h2>
<p>メルペイでは、毎月の与信枠更新ロジックの一部に機械学習システムを採用しています。<br />
そのため、機械学習エンジニアチームでは毎月リリース作業が発生します。<br />
本記事では、リリース作業のうち、以下の作業に関して品質を担保しながら省力化を目指した取り組みをご紹介します。</p>
<ol>
<li>開発ブランチのマージ</li>
<li>各種マスタデータの更新</li>
<li>機械学習パイプラインの実行</li>
</ol>
<h2>1. 開発ブランチのマージ</h2>
<p>開発ブランチでのさまざまな変更内容を main ブランチに反映させます。<br />
その中でも、毎月必ず発生するのが config ファイルの更新です。<br />
config ファイルでは、機械学習モデルの学習や推論、後処理などで必要な設定をしています。<br />
以下は config で指定している項目の例です。 </p>
<ul>
<li>学習、評価データの期間</li>
<li>推論対象月</li>
<li>ハイパーパラメータ探索に関する設定</li>
<li>モデルやデータセットなどのバージョン</li>
<li>各種 I/O のパス など</li>
</ul>
<h3>問題点</h3>
<p>config ファイルの更新時に、設定漏れや設定の不備など人為的ミスが起きてしまうことが問題でした。複数の機械学習モデルがあることや、各モデルごとの設定項目が多いことが要因としてあげられます。</p>
<h3>解決策</h3>
<p>config ファイルの設定の不備を機械的に検知してユーザに通知する仕組みを導入しました。<br />
ワークフローは以下のとおりです。</p>
<p><center><img src="https://storage.googleapis.com/prd-engineering-asset/2024/12/086d05b2--2024-12-19-14.20.51.png" alt="""></center><br />
<center>図1. config ファイル自動チェックのワークフロー<br />ロゴ出典: GitHub</center></p>
<p>config の修正を以下のケースで分類しています。</p>
<ol>
<li>モデルの推論のみを行うための修正</li>
<li>モデルの再学習を行うための修正</li>
<li>リリースに関係のない修正</li>
</ol>
<p>3.については validatioin は実施しません。<br />
1.2. に関しては、それぞれ一般的な型 validation に加えて、機械学習モデルの要件に紐づく以下のような validation を行っています。</p>
<ul>
<li>作業月と推論対象月が矛盾していないか</li>
<li>先月リリース時の config と比較して矛盾がないか</li>
<li>作業月のNヶ月前の日付が指定されているか など</li>
</ul>
<p>この仕組みの導入により、config ファイル更新時に不備があった場合、PR のマージ前に低コストで気づけるようになりました。</p>
<h2>2. 各種マスタデータの更新</h2>
<p>メルペイの機械学習システムには、さまざまなマスタデータが存在します。<br />
マスタデータの更新は、基本的に PdM や Biz の方が行うため、運用観点で現状は Google スプレッドシートでの管理に落ち着いています。 </p>
<h3>問題点</h3>
<p>システム上の管理は GitHub と BigQuery で行っているため、スプレッドシートのデータを テキストファイルに変換する作業が必要になります。この作業で人為的ミスが起きることと、作業が非効率であることが問題でした。</p>
<h3>解決策</h3>
<p>ワークフローは以下のとおりです。</p>
<p><center><img src="https://storage.googleapis.com/prd-engineering-asset/2024/12/24ea8da5--2024-12-19-14.30.59.png" alt=""></center><center>図2. マスタ更新自動化のワークフロー<br />ロゴ出典:GitHub, Google スプレッドシート</center></p>
<p>スプレッドシートの validation では、機械学習特有のものはありません。<br />
データ型や入力値の範囲、ヘッダの数や名称など、一般的な項目をチェックしています。</p>
<p>複数のマスタデータや、その他のスプレッドシートで管理されているデータにおいて、品質担保および効率化できるよう、この仕組みを使いまわして運用しています。 </p>
<h2>3. 機械学習パイプラインの実行</h2>
<p>毎月のリリース作業には約50個のタスクがあります。<br />
そのうちのいくつかは、AirFlow の DAG を用いて実装されている機械学習パイプラインを実行するタスクです。 </p>
<h3>問題点</h3>
<p>以下の問題がありました。 </p>
<ul>
<li>実行日とタスクが一覧になっているスプレッドシートでタスクを管理しているが、いつどの DAG を実行するかを確認する認知コストがかかる</li>
<li>リリースタスクの数が多いこともあり、ミスや手戻りが発生し得る</li>
<li>リリース作業のオンボーディングが非効率で、新規メンバーのキャッチアップに時間がかかる</li>
</ul>
<h3>解決策</h3>
<p>リリース作業をアシストしてくれる Slack bot、”Release Ops Assistant”(以降 ROA)を導入しました。ワークフローは以下のとおりです。</p>
<p><center><img src="https://storage.googleapis.com/prd-engineering-asset/2024/12/c5ee6ee9--2024-12-19-14.31.09.png" alt="" width=80%></center><br />
<center>図3. ROA のワークフロー<br />ロゴ出典:Slack, Google スプレッドシート, Google Compute Engine, Apache Airflow</center></p>
<p>ROA を実装する際に、下記の要件を満たしたいと考えていました。 </p>
<ul>
<li>Slack からの DAGトリガー要求にリアルタイムで対応できること</li>
<li>実装及び運用コストが低いこと</li>
</ul>
<p>検討したアーキテクチャには、Cloud Functions や Cloud Run などありましたが、最終的には Socket Mode が使えるという理由で Google Compute Engine(以降 GCE)を採用しました。Socket Mode は、WebSocket を使用してリアルタイムでイベントを受信できる接続方式で、ファイアウォールを気にせず簡単にアプリ開発ができるという特徴があるため、上記の要件を満たすことができます。<br />
また、ジョブスケジューラーも Cloud Scheduler や Cloud Tasks などを検討しましたが、最終的には Slack との親和性が最も高いという理由で Slack ワークフローを採用しました。<br />
図3 のワークフロー内赤枠の部分では、Slack Bolt が Socket Mode で GCE のプログラムをトリガーし、GCE が Airflow REST API を叩いて DAG を実行しています。<br />
ROA の導入により、いつどのタスクをやるべきか Bot が通知してくれるので認知コストが下がり、リアルタイムにタスクの実行やステータスの変更が可能となったためミスや手戻りも発生しづらくなりました。 </p>
<p><center><img src="https://storage.googleapis.com/prd-engineering-asset/2024/12/578e4c2a-4.png" alt=""></center><br />
<center>図4. ROA の Slack 画面</center></p>
<h2>まとめ</h2>
<p>本番稼働中の機械学習モデルの運用について、品質を担保しながら省力化することで、生産性をあげる工夫をご紹介しました。<br />
生産性向上はどうしても後回しにしてしまいがちですが、一度腰を据えてまとめて見直しができて非常に良かったと思います。</p>
<p>次の記事は @komatsu さんです。引き続きお楽しみください。</p>
- メルカリ ハロにおけるFlutterアプリのQA戦略:クロスプラットフォーム開発のメリットと注意点https://engineering.mercari.com/blog/entry/20241219-mercari-hallo-qa-strategy-2024/https://engineering.mercari.com/blog/entry/20241219-mercari-hallo-qa-strategy-2024/<p>こんにちは。メルカリWorkチームQA Engineerの@umです。 この記事は、連載:メルカリ ハロ 開発の裏側 – Flutterと支える技術 –の5回目と、 Mercari Advent Calendar 202 […]</p>
Thu, 19 Dec 2024 11:00:06 GMT<p>こんにちは。メルカリWorkチームQA Engineerの<a href="https://x.com/umetsuyu" title="@um">@um</a>です。<br />
この記事は、<a href="https://engineering.mercari.com/blog/entry/20241129-mercari-hallo-2024/" title="連載:メルカリ ハロ 開発の裏側 – Flutterと支える技術 –">連載:メルカリ ハロ 開発の裏側 – Flutterと支える技術 –</a>の5回目と、<br />
<a href="https://engineering.mercari.com/blog/entry/20241125-mercari-advent-calendar-2024/" title="Mercari Advent Calendar 2024">Mercari Advent Calendar 2024</a> の15日目の記事です。</p>
<p>今回は私達が開発している「メルカリ ハロ」のモバイルアプリのQAに焦点を当てて紹介します。</p>
<h2>概要</h2>
<p>メルカリ ハロのモバイルアプリは、クロスプラットフォームフレームワークであるFlutterを採用しています。Flutterによる開発は、単一のコードベースでiOSとAndroid両方のアプリを構築できるため、開発効率の向上に大きく貢献しています。QA活動においても、テスト効率の向上に貢献できるのですが、その特性を理解した上で適切なテスト計画を考える必要があります。この記事では、メルカリ ハロにおけるFlutterアプリQAのメリット・注意点、そして私たちが実践しているテストの進め方について解説します。</p>
<h2>前提</h2>
<p>モバイルアプリ開発には、「ネイティブアプリ開発」と「クロスプラットフォーム開発」という大きく二つのアプローチがあります。Flutterは後者のクロスプラットフォーム開発を可能にするフレームワークです。<br />
ネイティブアプリ開発とは、iOSならSwift、AndroidならKotlinといった、プラットフォーム専用の言語とSDKを用いて開発する手法です。<br />
対するクロスプラットフォーム開発とは、単一のコードベースで複数のプラットフォーム(メルカリ ハロではiOSとAndroid)に対応するアプリを開発する手法です。<br />
この特徴はネイティブアプリの開発者だけでなく、QAエンジニアにとってもテスト効率の向上に大きく貢献します。具体例を以下に示します。</p>
<h2>Flutterを用いたクロスプラットフォーム開発におけるQAのメリット</h2>
<h3>同時テストによる効率化と品質向上</h3>
<p>iOSとAndroidアプリの実装が同時完了するため、両OSのビルドを並べての比較検証が可能になります。これにより、一つの観点で両OSを確認できるだけでなく、OS間のUIの差異や、それぞれ別でテストした場合に見逃しがちな細かな不具合の発見につながります。<br />
<img src="https://storage.googleapis.com/prd-engineering-asset/2024/12/3643f309-img_7012-1-225x300.jpg" alt="" /></p>
<p>また、私たちはスクラム開発の手法を採用しており、QAエンジニアもスクラムチームの一員として参加しています。スクラム開発を行う上で、1つのユーザーストーリーを1枚のストーリーチケットとして扱っており、開発はユーザーストーリー単位で実装されるため、テスト実行もユーザーストーリー単位で実行します。<br />
そのプロセスにおいても、両OSのテストをチケットを分けることなく1枚のストーリーチケットでまとめて実施できるため、管理工数の削減にも貢献しています。</p>
<h3>開発者とのコミュニケーションコスト削減</h3>
<p>QAを行う上で開発者とのコミュニケーションは必要不可欠です。具体的には、スケジュールの調整から始まり、仕様の認識合わせ、開発視点で考慮が必要な観点、Acceptance Criteria(以下ACと記載します)の過不足のチェック、リリース手順の確認など多種多様なコミュニケーションが発生します。</p>
<p>機能をより早く、より安全にリリースするためには、こういったコミュニケーションの質や量を担保することが必要ですが、開発担当者が増えれば増えるほどコミュニケーションの難易度とコストは上がります。一般的にネイティブアプリ開発の場合はOSごとに開発者が分かれていることが多いため、例えばMTGの時間を合わせることが難しくやむをえず情報伝達に時間差が生じてしまったり、複数の担当者間での認識の齟齬が生まれたりすることなどが起こり得るかと思います。一方でFlutterのアプリケーション開発においては両方のOSを同一の開発者が担当するため、やり取りする担当者の数は最小限ですみます。これは単純ではあるものの大きな違いであり、これによりコミュニケーションの難易度とコストが抑えられるように感じます。<br />
<img src="https://storage.googleapis.com/prd-engineering-asset/2024/12/fe2966b2-.png" alt="" /></p>
<h3>テストケース量の削減</h3>
<p>テストケース数を少なくするためのアイディアとして、Flutterの単一コードベースの特徴を活用することができます。</p>
<p>具体的にはバックエンドAPI呼び出し部分やレスポンス表示部分などは片方のOSで正しく動作することを確認できれば、もう片方のOSでのテストを省略できる場合があります。<br />
事前に開発者と実装内容や影響範囲を確認した上で、このような戦略的なテストケース削減を行うことで、リリースまでのリードタイム短縮につなげることができます。<br />
<img src="https://storage.googleapis.com/prd-engineering-asset/2024/12/b1d49dce-.png" alt="" /></p>
<h2>Flutterを用いたクロスプラットフォーム開発におけるQAの注意点</h2>
<p>メリットが多い一方で、Flutter特有の注意点も存在します。</p>
<h3>両OSの不具合影響</h3>
<p>Flutterが単一コードベースであるがゆえに、実装上の不具合が両OSに同時に影響を及ぼす可能性があります。このリスクを低減させるために適切な品質保証活動を行う必要があります。<br />
具体的な活動については、QA Engineering ManagerのrinaがACについて記述した<a href="https://engineering.mercari.com/blog/entry/20241207-mercari-hallo-2024/" title="本連載1回目の記事">本連載1回目の記事</a>をご参照いただければと思います。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/12/4c1b6137-.png" alt="" /></p>
<h3>OS固有の実装への対応</h3>
<p>Flutterは単一コードベースを原則としますが、OSに依存した機能など特定の処理に関してはOSごとに異なる実装を行う必要があります。<br />
メルカリ ハロでは、一例ですが以下のような機能でOSを意識した実装をしています。<br />
・応募したおしごとの日程をカレンダーアプリに登録する機能<br />
・おしごとの労働条件通知書ファイルを端末にダウンロードする機能</p>
<p>QAエンジニアは、OSごとの実装が入っているかどうかについて開発者とコミュニケーションをとり、適切なテスト設計をしたり、テスト実行を行うことが重要です。</p>
<h2>まとめ</h2>
<p>Flutterはクロスプラットフォーム開発のメリットを享受できる一方、QAにおいてはその特性を理解した上で適切なアプローチを取ることが重要です。メルカリ ハロでは、今回ご紹介したメリット・注意点を踏まえ、効率的かつ効果的なQA活動を実践することで、あんしん・あんぜんなアプリを提供できるよう努めています。<br />
この記事の内容が、みなさまのプロジェクトや技術的探求に貢献できたなら幸いです。引き続き「<a href="https://engineering.mercari.com/blog/entry/20241129-mercari-hallo-2024/" title="メルカリ ハロ 開発の裏側 - Flutterと支える技術 -">メルカリ ハロ 開発の裏側 – Flutterと支える技術 –</a>」シリーズを通じて、私たちの技術的知見や経験を共有していきますので、どうぞご期待ください。また、<a href="https://engineering.mercari.com/blog/entry/20241125-mercari-advent-calendar-2024/" title="Mercari Advent Calendar 2024">Mercari Advent Calendar 2024</a>の他の記事もぜひチェックしてみてください。それでは、次回の記事でお会いしましょう!</p>
<p>次回の記事は @howieさんです。引き続きお楽しみください。</p>
- ナレッジマネジメントへの挑戦https://engineering.mercari.com/blog/entry/20241202-6c83b3dd89/https://engineering.mercari.com/blog/entry/20241202-6c83b3dd89/<p>はじめに こんにちは。メルカリEngineering Officeの@ravenです。 この記事は、Mercari Advent Calendar 2024 の14日目の記事です。 Engineering Officeは […]</p>
Wed, 18 Dec 2024 11:00:30 GMT<h2>はじめに</h2>
<p>こんにちは。メルカリEngineering Officeの<a href="https://www.linkedin.com/in/yosuke-tetsubayashi-b8830251">@raven</a>です。<br />
この記事は、<a href="https://engineering.mercari.com/blog/entry/20241125-mercari-advent-calendar-2024/">Mercari Advent Calendar 2024</a> の14日目の記事です。</p>
<p>Engineering Officeはエンジニアリング領域における組織横断課題の解決に取り組んでいる部署です。エンジニアリング組織に対するナレッジマネジメントの改善も私たちの担当領域となります。</p>
<p>私は2024年4月にメルカリに入社しましたが、入社当初からメルカリでのナレッジの探しにくさを感じ、他の同僚に資料や情報の場所を聞いたりしながら必要な情報を探していました。実際に他部署のナレッジがどこにあり、どうやって調べて良いかもわからない状況でした。</p>
<p>そんな中、年次で行なっている全組織のエンジニアへのアンケートにおいて、最も満足度が低い領域の1位に輝いたのが社内のナレッジに関するものでした。アンケート結果に納得している私のもとに、幸運にもナレッジマネジメントの改善プロジェクトのアサイン依頼がやってきたのでした。</p>
<h3>この記事に書かれている内容</h3>
<p>この記事では、道半ばではありますが、私たちのチームがエンジニアのナレッジマネジメントに対する満足度を向上させるべくプロジェクトとして取り組んでいる内容を以下の2つのポイントで紹介したいと思います。</p>
<p>・エンジニアが抱える課題に対してどのようなアプローチをとったのか?<br />
・どのように組織横断でプロジェクトを推進したのか?</p>
<p>自社で同じようなナレッジに関する課題を抱えている方に、少しでも参考になれば幸いです。</p>
<h2>エンジニアたちのナレッジに対する不満</h2>
<p>ナレッジマネジメントを改善すると一言で言っても、簡単ではありません。これまで培ってきたドキュメンテーション文化の変更を、エンジニアに対してお願いする必要があります。単一の組織ですら改善が大変そうな取り組みですが、私たちの部署の担当範囲は、単一の事業部からインドを含む日本リージョンの全てのエンジニアリング組織へと大幅に拡大されたばかりでした。まさに組織横断課題の解決という、私たちの部署のミッションにぴったりの仕事です。そのため、このナレッジマネジメントプロジェクトは、私たちのミッションである「組織横断課題の解決」の真骨頂と言えるような、壮大な取り組みとなりました。</p>
<p>何はともあれ、まずはエンジニアたちがアンケートで回答したナレッジに関する不満の内容を分析することから始めました。エンジニアたちの大きな不満は主に以下のような内容でした。</p>
<ul>
<li>ナレッジが複数のプラットフォームに分散しているので検索性や発見性が低い</li>
<li>ナレッジプラットフォームが多いが、各組織が独自のルールでナレッジを構築しているのでナレッジが集中化および整理されていない</li>
<li>ドキュメントが標準化されておらず、同じ資料でも記載内容や書きぶりが組織によって異なる</li>
<li>ナレッジのメンテナンスがされておらず、重複や古い情報が多い</li>
<li>ナレッジマネジメントに関するトレーニングやガイドラインなどが提供されていない</li>
<li>英語と日本語の資料が存在するが、言語の壁で情報共有がスムーズにできない</li>
</ul>
<p>これらのエンジニアからの不満は、他の会社でも共通する部分が多いのではないでしょうか?<br />
ナレッジマネジメントが適切に行われていないことで、私たちが失っているものは想像以上に多く、それは会社にとってもエンジニア達にとっても大きな損失となります。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/12/144505af-km01-1024x1024.png" alt="欲しいナレッジが見つからず苦悩するエンジニア達" /></p>
<p>私たちのエンジニアがストレスなく、言語の壁と組織の壁を超えて情報を共有したり取得したりできる。そんな世界観を目指してプロジェクトはスタートしました。</p>
<h2>それぞれの課題にどうアプローチしていくのか?</h2>
<p>エンジニアからの課題をまとめると、以下のような課題を解決する必要がありそうでした。</p>
<ul>
<li>複数のプラットフォームにナレッジが分散している</li>
<li>ナレッジに対するルールがなくナレッジが整理されていない</li>
<li>上記の2つの課題により検索性や発見性が低い</li>
<li>言語の壁があり情報共有が進まない</li>
<li>ドキュメントの標準化がされていない</li>
<li>ナレッジが適切にメンテナンスされていない</li>
<li>ナレッジに関するガイドラインやトレーニングが存在しない</li>
</ul>
<p>それぞれの課題に対する私たちのアプローチをご紹介します。</p>
<h3>課題:複数のナレッジプラットフォームにナレッジが分散している</h3>
<p>私たちがドキュメントを作成するツールは大まかに分類すると、以下となります。</p>
<ul>
<li>Confluence</li>
<li>Google Docs / Slide</li>
<li>GitHub (ナレッジをまとめてWebページとして公開)</li>
</ul>
<p>どこにナレッジを集めるのか?という課題に対し、保有している既存資産からナレッジプラットフォームの選定を行うにあたり賛否両論がありました。ドラスティックなアプローチをとって、Confluenceのみにする、他のプラットフォームは利用させない等。しかし、プラットフォームの選定において製品を比較していくと、それぞれの製品に良さがあります。</p>
<table>
<thead>
<tr>
<th>プロダクト</th>
<th>プロダクトの良い部分</th>
</tr>
</thead>
<tbody>
<tr>
<td>Confluence</td>
<td>直感的なページ作成、ナレッジやナレッジの領域管理が容易</td>
</tr>
<tr>
<td>GitHub</td>
<td>バージョン管理、レビューや承認機能などが充実</td>
</tr>
<tr>
<td>Google Workspace</td>
<td>様々なコラボレーションツールとシームレスに連携</td>
</tr>
</tbody>
</table>
<p>色々と検討を重ねた結果、私たちは以下の方針でナレッジプラットフォームの構築を行うことにしました。</p>
<p>「Confluenceをナレッジプラットフォームの中心として、Confluenceに不足している機能を他のプラットフォームで補完する」</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/12/e1c959fe-km02-1024x324.png" alt="Confluenceを中心としたRAGを含む柔軟なナレッジプラットフォーム" /></p>
<h3>課題:ナレッジに対するルールがなくナレッジが整理されていない</h3>
<p>ナレッジプラットフォームはそれぞれのツールの良さを活かすため、柔軟性を持たせる設計としましたが、複数のツールを認めたからといって、情報をそのまま多くのツールに分散したままにしないよう、私たちはConfluence上で各組織ごとのナレッジ領域と、組織内の全てのチームのナレッジを実際に格納する専用のページを人事情報から自動作成する事にしました。各チームが持つコミュニケーションチャネルや、GitHubリポジトリ、設計書などの情報を標準化されたチームごとのテンプレートに情報を記載してもらうことで、まずは各組織のチームが保有している社内で共有する価値がある情報を、組織横断で同じテンプレートを利用してConfluenceへナレッジを集めることとしました。</p>
<p>なぜチームという組織カットのアプローチにしたのかというと、現在の組織構造と指揮命令系統を考慮し、ガバナンスを効かせたりプロジェクトの推進がしやすいというのが主な理由です。他の案としてはプロダクト単位、技術ドメイン単位という案もありましたが、まずナレッジマネジメントの改善の一歩を踏み出すにあたり、ナレッジにおける責任範囲を明確にした上でプロジェクトを推進するためには、このアプローチが最適と判断しました。</p>
<p>また、組織横断でチームごとに同じ粒度で情報を整理するこの取り組みは、お互いの組織の情報や保有するナレッジを相互理解するためにも重要な目的を持っていました。個人的な印象としては、地図のない世界にようやく手書きの粒度の粗い地図ができ、組織横断での見通しが良くなったと感じています。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/12/2bde648a-km03-1024x383.png" alt="会社として有益な情報はConfluenceにリンクしナレッジを集約する" /></p>
<h3>課題:検索性や発見性が低い</h3>
<p>Confluenceに情報をリンクして、ある程度までは各組織が保有しているナレッジに対して導線ができて辿り着ける状態にはなりましたが、所詮リンクを張っただけでは検索性が劇的に向上するわけではありません。</p>
<p>前述したナレッジプラットフォームの図にもConfluence から伸びる矢印にLLM+RAGと記載されていましたが、私達はプロジェクト開始当初からLLM(Large Language Model)チームと連携し、Confluenceの情報をRAG(Retreval Augmented Generation)のソリューションを利用してエンジニアに関連するナレッジを検索できないかを検討していました。すでにLLMチームではGitHubなどの主要なエンジニアリングに関連する情報をRAGに取り込んでいたので、さらにConfluenceに組織横断で集めた情報からエンジニアに役立つ情報をRAGに取り込み、社内のLLMシステムからConfluenceに取り込んだナレッジを提供することにしました。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/12/f789a5a6-km04-1024x417.png" alt="ナレッジマネジメントにRAGを導入することで言語の障壁を下げる" /></p>
<h3>課題:言語の壁があり情報共有が進まない</h3>
<p>日本語のドキュメントは日本語が苦手なエンジニアは読まない。英語のドキュメントは英語が苦手なエンジニアは読まない。当然と言えば当然ですが、ナレッジマネジメントではエンジニア間でナレッジをスムーズに共有できるように、言語の壁を取り除くことが重要です。<br />
しかし、全てのドキュメントに日英のドキュメントを2種類用意するのはリソース面でも難しいですし、Confluenceの翻訳プラグインは翻訳量に応じた従量課金なので、Confluenceをナレッジマネジメントの中心とすることでコスト面のインパクトも気になります。</p>
<p>幸いなことに、私達はLLM+RAGというソリューションが既にあるため、日本語と英語で共有されるべきナレッジの言語の課題はLLM+RAGのソリューションで解決することとしました。英語で書かれているドキュメントの内容も、LLMのシステム上で日本語で質問すると日本語で回答が返ってくるので、ドキュメントにおける言語のバラツキが多い環境下においてもスムーズなナレッジの共有と、今まで閲覧することのなかった新たなナレッジの発見に貢献しそうです。</p>
<h3>課題:ドキュメントの標準化がされていない</h3>
<p>今まではほとんどのケースにおいて、各組織ごとに独自の標準化されたテンプレートを利用していました。さらに複雑な場合には、各組織の中でも複数のテンプレートが存在するという状況でした。<br />
標準化されたドキュメントのテンプレートを利用することで、情報の粒度が均質化され、誰もが過不足なくドキュメントを作成できるようになり、また、読み手に対してもストレスなく情報を共有することができます。私たちはまずはエンジニアの間で作成頻度が高いドキュメントに対しては、標準化されたテンプレートを利用することを推奨することにしました。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/12/8c62b9bc-km05-1024x522.png" alt="ドキュメントを標準化することのメリット" /></p>
<h3>課題:ナレッジが適切にメンテナンスされていない</h3>
<p>ナレッジを常に最新に保つために、私たちはナレッジマネジメントチームがConfluenceに対して実施しているドキュメントの健康診断チェックツールを強化しました。これによりナレッジの情報鮮度や、標準テンプレートの利用状況などのモニタリングと可視化を行い、定期的に点検をエンジニアに依頼することで、ナレッジの維持管理に努めています。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/12/5a3fd124-km06-1024x397.png" alt="ナレッジのメンテナンスにナレッジの健康診断チェックツールを利用" /></p>
<h3>課題:ナレッジに関するガイドラインとトレーニングが存在しない</h3>
<p>ナレッジマネジメントの取り組みをエンジニアに理解してもらうために、私たちは、ドキュメンテーションツールの選択と、ドキュメントの標準化されたテンプレート利用に関するガイドラインをConfluence上で作成しました。ガイドラインは今後さらに拡張していく予定です。</p>
<p>ガイドラインを発行したからといって、全てのエンジニアがガイドラインを熟読し即座にガイドラインに沿った行動を取ってくれるわけではないので、社内のe-Learningシステムにナレッジマネジメントの基本的な考え方と、ガイドラインの内容を学べるトレーニングコースを作成し、必須トレーニングとしてトレーニングを受講してもらい、ガイドラインへの理解とナレッジマネジメントに対する意識改革を推進しています。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/12/f78db2b4-km07-1024x525.png" alt="ナレッジマネジメントとガイドラインに関するトレーニング資料" /></p>
<p>また、トレーニング以外においても、エンジニア向けの全体集会にてナレッジマネジメント関連の情報共有や、Open Doorセッションなどを定期的に開催し、ナレッジマネジメントの重要さを理解してもらう活動を行っています。</p>
<h2>組織横断プロジェクトの推進</h2>
<p>エンジニアが抱える課題に対して、どのようにアプローチするのかが決まっていても、全てをコミットし、デリバリーできなければ、いくら良い施策でも机上の空論で終わってしまいます。</p>
<p>ここでは、組織横断でプロジェクトを推進する際に特に気を付けていたポイントを書いてみます。</p>
<ul>
<li>プロジェクトの設計</li>
<li>可視化</li>
<li>KM(Knowledge Management) コミッティーの組成</li>
<li>IO(Information Owner)のフォローアップ制度</li>
<li>アナウンスと浸透活動</li>
</ul>
<h3>プロジェクトの設計</h3>
<p>プロジェクトを推進するにあたり、ナレッジマネジメント改善における取り組みの概要、スケジュール、詳細タスク、リスク分析、ナレッジの浸透計画、トレーニングや、ナレッジのモニタリング計画などを慎重に検討しました。<br />
また、それらの計画や情報などはConfluence上でプロジェクト管理用のページを作成し、プロジェクトメンバーおよび、他の社員にもプロジェクトの取り組みを認知してもらえるように、情報を積極的に公開するように努めました。</p>
<h3>可視化</h3>
<p>計画や施策などに関しては、視覚的に理解しやすいイメージ図を作成して可視化を行い、プロジェクトのステークホルダーやメンバーが取り組みを理解しやすいように心がけました。会議においても、可視化した取り組みのイメージを活用することで参加者に誤解なく速やかに内容を理解してもらうことができ、組織横断での意思疎通もスムーズに行えました。</p>
<h3>KM(Knowledge Management) コミッティーの組成</h3>
<p>同じ会社といえども、組織ごとに異なるドキュメントの文化や慣習が存在します。<br />
組織横断でプロジェクトを推進するために、まずは各組織からナレッジマネジメントの代表としてIO(Information Owner)を選出してもらい、KMコミッティーを組成しました。各組織から選出されたIOは20名程度になり、私たちはIOと共に、組織ごとに異なるドキュメンテーションの共有や組織横断としてのドキュメンテーションの方針、ガイドラインの検討やトレーニングコンテンツの検討などを行いました。また、ナレッジを組織のチームごとに集約する際もIOが自分の担当組織のマネージャーに更新を依頼し、トレーニングの受講を促したりと、ナレッジマネジメントの改善を一緒に推進することができました。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/12/aa63d032-km08-1024x533.png" alt="ナレッジマネジメントコミッティーのイメージ" /></p>
<h3>IO(Information Owner)のフォローアップ制度</h3>
<p>IOはナレッジマネジメントのプロジェクトのみに注力できるわけではなく、基本的には業務が忙しい方達が担当しています。コミッティーに参加できないIOも存在しますので、プロジェクトメンバーであるナレッジマネジメントチームが担当IOを決めて、個別に1on1などを設定し、IOのフォローアップを行うことで、IO間での情報格差を最小限に抑えられました。</p>
<h3>アナウンスと浸透活動</h3>
<p>いざ、ガイドラインやトレーニングのデリバリーを行ったとしても、実際にエンジニアに届かなければ意味がありません。もちろん周知できるコミュニケーションチャネルにはアナウンスしていますが、すべてのエンジニアに認知してもらい、行動してもらうには、アナウンスだけでは十分ではないのです。私たちは、IOと協力して組織へナレッジマネジメントの活動を落とし込んでもらったり、エンジニアの全体集会やOpen Doorイベントなどでエンジニア向けのナレッジマネジメントの浸透活動を積極的に行いました。</p>
<h2>最後に</h2>
<p>私たちのエンジニアリング領域におけるナレッジマネジメント改善の挑戦に関する取り組みと、組織横断で推進するプロジェクトのポイントを書かせていただきました。</p>
<p>ナレッジマネジメントの活動は、プロジェクトが完了した後でも、定期的にユーザーからのフィードバックをガイドラインやトレーニングコンテンツに反映、標準化テンプレートの拡張と利用推進、LLMへのナレッジの取込みなどを継続して行う必要があります。私達は今後さらに、メルカリのエンジニアリング領域における持続的なナレッジ文化の向上を目指していきます。</p>
<p>また、エンジニアリング領域におけるナレッジ基盤が確立した後は、プロダクトやビジネスの領域にもナレッジマネジメントの取り組みを拡大し、全社レベルでの活動に取り組みを広げていきたいと考えてます。</p>
<p>この記事を読んでいただいた方に、私たちの経験を通じて少しでも参考になることがあれば幸いです。<br />
最後までお読みいただき、ありがとうございました。</p>
- Spannerのよくあるミスをデータフロー解析で検知するhttps://engineering.mercari.com/blog/entry/20241218-detect-time-now-by-data-flow-analysis/https://engineering.mercari.com/blog/entry/20241218-detect-time-now-by-data-flow-analysis/<p>この記事は、Merpay & Mercoin Advent Calendar 2024 の記事です。 はじめに メルペイBalanceチームでバックエンドエンジニアをしている@kobaryoと申します。 皆さんは […]</p>
Wed, 18 Dec 2024 10:00:06 GMT<p>この記事は、<a href="https://engineering.mercari.com/blog/entry/20241125-merpay-mercoin-advent-calendar-2024/">Merpay & Mercoin Advent Calendar 2024</a> の記事です。</p>
<h2>はじめに</h2>
<p>メルペイBalanceチームでバックエンドエンジニアをしている<a href="https://x.com/artoy5884">@kobaryo</a>と申します。</p>
<p>皆さんは日々の開発の中で静的解析を利用していますか?静的解析を利用することで、コードが何かしらのルールに従っているということを保証することができます。プログラムの中にコンパイル時に検出できない何かしらのルールがあり、それに違反していることにプログラムを動かして初めて気付く、という事態になる前に違反を検出することができます。本記事でこれから示す事例は、静的解析で検出すべき良い事例なのではないかと思います。</p>
<p>メルペイの多くのチームでは、データベースとしてSpannerを採用しています。しかしながらSpannerを扱う上で、「<code>allow_commit_timestamp</code>である列にアプリケーション側で生成した現在時刻を入れてしまう」というミスがしばしば見られます。これをしてしまうとSpannerへの列の挿入や更新が確率的に失敗してしまうため、テストやQAでこのミスを発見できず、インシデントに繋がってしまう恐れがあります。</p>
<p>テストやQAでこのミスに気付く可能性もあるのですが、確率的にエラーが発生する性質上、実際にプログラムを動かしてこのミスを検出するのには限界があります。</p>
<p>そこで、Goの<code>time.Now()</code>がSpannerのMutationに含まれてしまっているかをデータフロー解析で検知する静的解析ツール<a href="https://github.com/artoy/nowdet">nowdet</a>を作成しました。</p>
<p>本記事では、まずこのようなエラーが発生する背景から説明して、次にデータフロー解析の概要、nowdetの実装のコアな部分、最後に今後の展望について述べます。</p>
<h2>背景</h2>
<p>Spannerでは<code>TIMESTAMP</code>型の列に<code>allow_commit_timestamp</code>オプションを付けることができます。このような列にプレースホルダ文字列<code>spanner.commit_timestamp()</code>を挿入すると、その名の通りコミット時のタイムスタンプに置き換えられて保存されます。Spannerは<a href="https://cloud.google.com/spanner/docs/true-time-external-consistency#external_consistency
">external consistency</a>という強い一貫性を持っており、これによりトランザクションの順序とタイムスタンプの順序が一致します。そのため、変更履歴といった順序が重要となる処理を、単にコミットタイムスタンプを参照することで実装できます。</p>
<p>この<code>allow_commit_timestamp</code>である列には<code>spanner.commit_timestamp()</code>だけでなくアプリケーション側で生成したタイムスタンプを挿入することもできるのですが、<a href="https://cloud.google.com/spanner/docs/commit-timestamp#provide-timestamp
">過去のタイムスタンプでなければならない</a>という制限があります。アプリケーションで現在時刻を生成しこの列に挿入しようとした場合、Spanner内のクロックとアプリケーションサーバーのクロックは一致していないために、アプリケーションで生成されたタイムスタンプがSpanner内部より未来のタイムスタンプになる可能性があります。この場合、<code>Cannot write timestamps in the future</code>とエラーが発生してしまいます。</p>
<p>実際、私は当初この仕様を知らず、このオプションが付いている列に<code>spanner.commit_timestamp()</code>ではなく誤ってGoの<code>time.Now()</code>で生成した値を挿入してしまっていました(幸いなことにテストでミスが発覚しました)。また、社内のSlackで<code>Cannot write timestamps in the future</code>で検索すると、多数のメッセージがヒットすることから、自分と同じミスをしている開発者は多いことが分かります。</p>
<p>経験上、このミスをした場合に実際にエラーが発生する可能性はそこまで低くないのでミスに気づきやすい、また一度このミスを経験すると同じミスは犯しにくいように感じます。しかしながら、やはり確率的にエラーが出るという点で、静的解析によってこのミスを検知する価値があると私は考えています。</p>
<h2>データフロー解析</h2>
<p>静的解析の手法の1つであるデータフロー解析は、プログラムの実行経路に沿って発生するデータの流れに関する情報を求める手法の総称のことです。例えば、変数がその実行経路を通っても更新されない、といったことを静的に検知する際に利用します。以下のようなプログラムについて考えます。</p>
<pre><code class="language-go">func example(b bool) (int, int){
var x = 0 // may be changed
var y = 0 // immutable
if b {
x = 1
}
return x, y
}</code></pre>
<p>この例の<code>y</code>の値は更新されていないので、<code>y</code>は実際には定数として定義しても問題ありません。この例では、データフロー解析で各プログラムポイントで各変数がどのポイントで定義された値を保持しているか(到達定義)を求めることで、このことを検知することができます。まず、上記のプログラムを制御フローグラフ (CFG)で表します。</p>
<p></br></p>
<div style="text-align: center;">
<img loading="lazy" width="431" height="401" src="https://storage.googleapis.com/prd-engineering-asset/2024/12/d9bfb96f-example_cfg.jpg" alt="" class="aligncenter size-medium wp-image-32919" srcset="https://storage.googleapis.com/prd-engineering-asset/2024/12/d9bfb96f-example_cfg.jpg 431w, https://storage.googleapis.com/prd-engineering-asset/2024/12/d9bfb96f-example_cfg-300x279.jpg 300w" sizes="(max-width: 431px) 100vw, 431px" />
</div>
<p></br></p>
<p>このとき、各プログラムポイントにおける到達定義は以下のようになります。</p>
<p></br></p>
<div style="text-align: center;">
<img loading="lazy" width="431" height="481" src="https://storage.googleapis.com/prd-engineering-asset/2024/12/3df4d9dd-example_cfg_with_reaching_definition-1.jpg" alt="" class="aligncenter size-medium wp-image-32920" srcset="https://storage.googleapis.com/prd-engineering-asset/2024/12/3df4d9dd-example_cfg_with_reaching_definition-1.jpg 431w, https://storage.googleapis.com/prd-engineering-asset/2024/12/3df4d9dd-example_cfg_with_reaching_definition-1-269x300.jpg 269w" sizes="(max-width: 431px) 100vw, 431px" />
</div>
<p></br></p>
<p><code>P5</code>の<code>return</code>文後の<code>y</code>の到達定義が<code>P1</code>のみであり、<code>y</code>は<code>P1</code>で定義されたものだったので、<code>y</code>を定数として定義しても問題ないということが分かりました。実際にはプログラムにループや再帰が含まれていて、到達定義を一度求めた後に再計算しなければならない可能性があり、到達定義が収束するまでこの処理を繰り返します。</p>
<p>今回のような定数で定義できる変数の検出以外にもデータフロー解析を利用することができます。例えばあるbool型の変数がどの実行経路を通っても常にfalseであることを検知したり、あるポインタをdereferenceする際、そのポインタがnilであるような実行経路が存在することを検知したりできます。この記事を読んでいる皆さんも、IDEや静的解析ツールでこのような機能を見たことがあるかと思います。</p>
<h2>nowdetの実装</h2>
<p>今回作成した<a href="https://github.com/artoy/nowdet">nowdet</a>も上記のnilポインタを検知する例とほぼ同様の処理をして、SpannerのMutationに<code>time.Now()</code>が含まれる実行経路が存在するかどうかをチェックしています。</p>
<p>もう少し具体的な処理を例で示します。例えば、以下のような関数について考えます。</p>
<pre><code class="language-go">func insert(ctx context.Context, client *spanner.Client, isNow bool) error {
var now time.Time
if isNow {
now = time.Now()
} else {
now = time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC)
}
_, err := client.Apply(ctx, []*spanner.Mutation{
spanner.Insert(
"Users",
[]string{"name", "created_at"},
[]interface{}{"Alice", now},
),
})
return err
}</code></pre>
<p>この関数では、引数の<code>isNow</code>に<code>true</code>が与えられるとアプリケーション側で生成した現在時刻がSpannerのテーブルに挿入されてしまいます。このプログラムをCFGで表します。nowdetでは解析対象のGoプログラムを静的単一代入形式 (SSA)に変換し、それをデータフロー解析しているので、実際は以下のようになります(赤字が<code>time.Now()</code>が関連している部分)。</p>
<p></br></p>
<div style="text-align: center;">
<img loading="lazy" width="745" height="861" src="https://storage.googleapis.com/prd-engineering-asset/2024/12/9e3b406a-nowdet_cfg-3.jpg" alt="" class="aligncenter size-medium wp-image-32921" srcset="https://storage.googleapis.com/prd-engineering-asset/2024/12/9e3b406a-nowdet_cfg-3.jpg 745w, https://storage.googleapis.com/prd-engineering-asset/2024/12/9e3b406a-nowdet_cfg-3-260x300.jpg 260w" sizes="(max-width: 745px) 100vw, 745px" />
</div>
<p></br></p>
<p>基本的には、<code>time.Now()</code>が代入された変数をマークし、マークされた変数が他の変数に代入される、または含まれた際にその変数にもマークを伝播させるという方針になっています。</p>
<p>例えば、関数を呼び出した際にその関数が<code>time.Now()</code>だった場合にマークをします。上記の例では、<code>P1</code>に<code>t0 = time.Now()</code>があるので、<code>t0</code>をマークします。その他にも、プログラムの実行経路によって変数の値が変わる場合、そのうちの一つがマークした変数から来たものであればマークします。上記の例の<code>P3</code>の<code>t1 = phi [1: t0, 3: t20]</code>では、<code>t1</code>はプログラムの実行経路によって<code>t0</code>か<code>t20</code>になることを表しているので、<code>t1</code>もマークします。</p>
<p>このような処理を繰り返し、最終的に<code>P20</code>で<code>spanner.Insert</code>の引数にマークされた変数<code>t13</code>が含まれるため、Mutationに<code>time.Now()</code>が含まれたと判断されて、アラートを返すという流れになっています。</p>
<p>かなり細かい話をすると、実はスライスが関わる処理では単純にマークをするのではなく、グラフを作っています。上で示した例だと、<code>t13</code>がマークされていれば正しく検知ができて、その<code>t13</code>は<code>P19</code>で<code>t13 = slice t8[:]</code>で定義されています。また、その<code>t8</code>は<code>P12</code>の<code>t8 = new [2]interface{} (slicelit)</code>で定義されています。<code>t8</code>が定義された時点で<code>t8</code>がマークされていないと検知が成功しないのですが、実際には<code>t8</code>が定義された後の<code>P16</code>から<code>P18</code>で、<code>t11 = &t8[1:int]</code>・<code>t12 = make interface{} <- time.Time (t1)</code>・<code>*t11 = t12</code>と、<code>t8</code>とマークされた変数<code>t1</code>が結びついています。そのため、プログラムを上から順に見ていくだけだと<code>t8</code>をマークすることができず、うまく検知することができません。</p>
<p>この問題を解決するため、スライスが関わる処理では、スライスから辺を辿って要素に到達できるようなグラフを生成することで対策をしています。具体的には、<code>P16</code>の<code>t11 = &t8[1:int]</code>と、<code>P18</code>の<code>*t11 = t12</code>の処理で以下のようなグラフが作られます。</p>
<p></br></p>
<div style="text-align: center;">
<img loading="lazy" width="401" height="81" src="https://storage.googleapis.com/prd-engineering-asset/2024/12/85157c5f-graph.jpg" alt="" class="aligncenter size-medium wp-image-32922" srcset="https://storage.googleapis.com/prd-engineering-asset/2024/12/85157c5f-graph.jpg 401w, https://storage.googleapis.com/prd-engineering-asset/2024/12/85157c5f-graph-300x61.jpg 300w" sizes="(max-width: 401px) 100vw, 401px" />
</div>
<p></br></p>
<p>そして、<code>P19</code>の<code>t13 = slice t8[:]</code>で<code>t8</code>がマークされているかをチェックする代わりに、このグラフを<code>t8</code>から辿り、マークされている変数に到達すれば<code>t13</code>をマークする、という処理に変更します。このグラフは<code>t12</code>に到達し、<code>t12</code>は<code>P17</code>の<code>t12 = make interface{} <- time.Time (t1)</code>でマークされているため、<code>t13</code>をマークすることができます。</p>
<h2>今後の展望</h2>
<h3>スキーマを取得し偽陽性を避ける</h3>
<p>現在の実装では、実際に列が<code>allow_commit_timestamp</code>オプションを持っているかどうかにかかわらず<code>time.Now()</code>が挿入されうるかを検知しています。<code>allow_commit_timestamp</code>オプションを利用せずアプリケーション側で生成した現在時刻を挿入するケースももちろんあるので、スキーマを取得し、<code>allow_commit_timestamp</code>オプションを持っている列に関してのみ検知するようにしたいと考えています。</p>
<h3>関数を跨いで<code>time.Now()</code>を検出する</h3>
<p>現在の実装では、同じ関数内で<code>time.Now()</code>を呼び出し、それをMutationに入れている場合のみ検知します。異なる関数もしくはパッケージで呼び出した<code>time.Now()</code>がMutationに入る場合にも検知できるようにしたいと考えています。懸念としては計算時間の増加があるのですが、究極的には<code>time.Now()</code>からMutationまでのフローのみをチェックすればよく、無駄な基本ブロック(CFGの頂点)を解析対象から外す方針にしたいです。</p>
<h3>ポインタの扱い</h3>
<p>スライスが絡んだ際にどのように検知しているかを上で述べたのですが、ポインタが関わるパターン一般に関してうまく動作するわけではありません。先述のグラフの例だと、<code>t8</code>と<code>t12</code>は一方から他方に辿れるというよりは、イコールになるべきです。全ての場合に対応するのは直観的には難しい気がしているので、実用上耐えうるような制限を設け、全ての場合は検知できないが有用ではある、という状態を目指したいです。</p>
<h2>おわりに</h2>
<p>以上、Goの<code>time.Now()</code>がSpannerのMutationに含まれていないかを、nowdetがどのようにして検知しているかについて説明しました。Balanceチームでは(主に別の理由で)<code>time.Now()</code>がコードに含まれていないかをCIでgrepしてチェックしているのですが、誤検知が多いという問題があります。nowdetがこのgrepを置き換えられるよう、これからも開発を続けていきます。</p>
<p>次の記事は@rioさんです。引き続きお楽しみください。</p>
- GitHubのBranch Protectionの突破方法https://engineering.mercari.com/blog/entry/20241217-github-branch-protection/https://engineering.mercari.com/blog/entry/20241217-github-branch-protection/<p>はじめに こんにちは、Platform Securityのisoです。この記事は、Mercari Advent Calendar 2024の記事です。 本記事ではGitHubのbranch protection(prot […]</p>
Tue, 17 Dec 2024 11:00:38 GMT<h2>はじめに</h2>
<p>こんにちは、Platform Securityのisoです。この記事は、<a href="https://engineering.mercari.com/blog/entry/20241125-mercari-advent-calendar-2024/" title="Mercari Advent Calendar 2024">Mercari Advent Calendar 2024</a>の記事です。</p>
<p>本記事ではGitHubのbranch protection(protected branch)について、特にpull requestのマージに承認が必要とする制約をどうにかして突破できないかについて考察します。ぜひ最後までお読みいただけると嬉しいです。</p>
<h2>メルカリにおけるGitHub</h2>
<p>メルカリではGitHubを使ってコードの管理をしています。アプリやバックエンドのコードだけではなく、TerraformやKubernetesなどインフラに関わるあらゆるファイルをGitHubを使って管理しておりGitHub上のデータは非常に重要な役割を担っています。</p>
<p>組織によって開発者に付与するGitHubの権限は様々だと思いますが、メルカリの開発者は基本的に(自チーム以外のリポジトリを含む)多くのリポジトリに書き込み権限を持っています。(もちろんリポジトリの内容を考慮し、限られた開発者のみがアクセスできるリポジトリもあります。)これにより他チームのリポジトリに新しくブランチを作成してpull request(以降、PR)を作成したり、TerraformやKubernetes関連のファイルが保存されているリポジトリにPRを作成してインフラを構成したりといったことが可能となっています。</p>
<p>色々なリポジトリに書き込み権限を持っていることは便利な一方で、そのリポジトリとは全く関係のない開発者がコードを勝手に書き換えられたり、重要なTerraformのファイルをレビューなしで変更できたりしてしまうのは好ましくありません。そこでbranch protection ruleあるいはbranch rulesetを使うことで、デフォルトブランチ(main/masterブランチ)への変更はPRの作成を必須化し、マージには承認を必要とするというセキュアな運用を実現できます。メルカリでは、プロダクションに関わるリポジトリにはすべてこの設定を導入しています。</p>
<p>(なお、GitHubにおいてブランチを保護する方法としてbranch protection ruleとbranch rulesetがありますが本記事が扱う内容においては2つに違いはないため、特に区別せずにbranch protectionと呼びます。)</p>
<h2>Branch Protectionへの攻撃方法</h2>
<p>さて、このようにbranch protectionはリポジトリを守る上で重要な役割を担うわけですが、どのような設定をしたら良いのでしょうか。また本当にbranch protectionで大切なブランチを守り切れるのでしょうか。</p>
<h3>前提条件</h3>
<p>以下のシンプルな条件で考えてみます。</p>
<ul>
<li><strong>前提:</strong> リポジトリにアクセスできるすべての開発者がリポジトリに書き込み権限を持っている</li>
<li><strong>要件:</strong> mainブランチの変更は最低1人からの承認を必須とする(mainブランチは1人では変更できてはいけない)
<ul>
<li>これを満たすためにbranch protection ruleにおいて"Required number of approvals before merging: 1"が設定されているものとする</li>
</ul>
</li>
</ul>
<h3>登場人物</h3>
<p>攻撃方法を検討する上で2人の人物に登場してもらいます。</p>
<div style="display: flex; justify-content: space-between;">
<table style="width: 48%;">
<tr>
<td colspan="2" style="text-align: center;">
<img loading="lazy" src="https://storage.googleapis.com/prd-engineering-asset/2024/12/218597c9-image-1-300x300.png" width="300"
height="300" style="object-fit: scale-down;" />
</td>
</tr>
<tr>
<td style="border-right: none; padding-right: 0px; vertical-align: middle;"><b>Alice</b></td>
<td style="border-left: none;">ソフトウェアエンジニア。日々、コードを書いたりレビューしたりしている。レビューの際にはどんなに巧妙に隠された悪意あるコードも見つけ出すことができる鋭い嗅覚の持ち主。</td>
</tr>
</table>
<table style="width: 48%;">
<tr>
<td colspan="2" style="text-align: center;"><img loading="lazy"
src="https://storage.googleapis.com/prd-engineering-asset/2024/12/54faf742-image-300x300.png" width="300"
height="300" style="object-fit: scale-down;" />
</td>
</tr>
<tr>
<td style="border-right: none; padding-right: 0px; vertical-align: middle;"><b>Mallory</b></td>
<td style="border-left: none;">攻撃者。大きな野望を実現するため、とある方法でリポジトリへの書き込み権限を入手し、mainブランチのコードにバックドアを仕掛けようとしている。</td>
</tr>
</table>
</div>
<h3>Pull requestの役割の整理</h3>
<p>実際の攻撃方法を考える前に、PRにおける役割を整理します。</p>
<p>PRはユーザーやbotなどによって作成されます。本記事ではPRの作成者を"PR creator"と呼びます。</p>
<p>PRのソースブランチ(マージ元)に最後にコミットをプッシュしたユーザーを"last commit pusher"と呼びます。多くの場合で<code>"PR creator" == "last commit pusher"</code>ですが必ずしもそうである必要はありません。</p>
<p>今回の条件下ではPRは最低1人から承認されている必要があります。PRを承認したユーザーを"PR approver"と呼びます。PRの作成者は自身が作成したPRを承認できないので<code>"PR creator" != "PR approver"</code>が常に成り立ちます。</p>
<p>PRは承認された後にマージされますが、マージはリポジトリに書き込み権限があれば誰でもでき、今回の攻撃方法の考察には関わってきません。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/12/e094c040-screenshot-2024-12-16-at-3.03.50.png" width="580" style="display: block; margin: auto;" /></p>
<h3>攻撃パターン0: MalloryがPRを作成しAliceにレビューしてもらう</h3>
<p>まずは最もシンプルにMalloryが悪意あるコードを含むPRを作成しAliceにレビューしてもらうことを考えてみます。</p>
<p>前述の通り、AliceはPRに含まれる悪意あるコードを持ち前の嗅覚で必ず見つけ出すのでこのPRは承認されず、攻撃は失敗に終わります。つまり、今回攻撃パターンを考える上ではAliceがPR approverとなるパターンは検討する必要がありません。</p>
<table>
<thead>
<tr>
<th style="text-align: center;">PR Creator</th>
<th style="text-align: center;">Last Commit Pusher</th>
<th style="text-align: center;">PR Approver</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: center;">Mallory</td>
<td style="text-align: center;">Mallory</td>
<td style="text-align: center;"><del>Alice</del></td>
</tr>
</tbody>
</table>
<h3>攻撃パターン1: Aliceが作成したPRにMalloryがコミットをプッシュし承認する(PR Hijacking)</h3>
<p>この攻撃方法は次の記事で紹介されているPR hijackingと呼ばれる方法です。<br />
<a href="https://www.legitsecurity.com/blog/bypassing-github-required-reviewers-to-submit-malicious-code">https://www.legitsecurity.com/blog/bypassing-github-required-reviewers-to-submit-malicious-code</a></p>
<p>PRは"PR opener"以外のリポジトリに書き込み権限があるユーザーなら誰でも承認ができるため、誰かが作ったPRに勝手にコミットを追加し、承認してマージすることが可能です。</p>
<p>Aliceは自分が作成したPRに勝手にコミットが追加され、マージされたことに気づく可能性はありますが、このPRがDependabotのようなbotによって作成されていた場合、このことに誰も気付けない可能性があります。</p>
<table>
<thead>
<tr>
<th style="text-align: center;">PR Creator</th>
<th style="text-align: center;">Last Commit Pusher</th>
<th style="text-align: center;">PR Approver</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: center;">Alice</td>
<td style="text-align: center;">Mallory</td>
<td style="text-align: center;">Mallory</td>
</tr>
</tbody>
</table>
<p>この攻撃は"Require approval of the most recent reviewable push"というオプションを有効化することで防ぐことができます。このオプションを有効化すると"last commit pusher" != "PR approver"という制約を追加することができるのでMalloryはPRを承認できなくなります。</p>
<h3>攻撃パターン2: MalloryがPRを作成しGitHub Actionsで承認する</h3>
<p>リポジトリの設定によっては、GitHub Actionsのワークフローで<a href="https://docs.github.com/actions/security-for-github-actions/security-guides/automatic-token-authentication" title="自動生成されるGITHUB_TOKEN">自動生成されるGITHUB_TOKEN</a>を使ってPRを承認することができます。GitHub Actionsのワークフローはリポジトリの書き込み権限があれば誰でも作成・追加できるため、Malloryが自身が作成したPRを承認するようなワークフローを作成することも可能です。</p>
<p>GITHUB_TOKENを使ってPRを承認した場合、承認したユーザーは"github-actions"となりMalloryとは別のユーザーがPRを承認したものとして扱われます。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/12/d8760e99-screenshot-2024-12-12-at-2.52.21.png" alt="" /></p>
<table>
<thead>
<tr>
<th style="text-align: center;">PR Creator</th>
<th style="text-align: center;">Last Commit Pusher</th>
<th style="text-align: center;">PR Approver</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: center;">Mallory</td>
<td style="text-align: center;">Mallory</td>
<td style="text-align: center;">github-actions</td>
</tr>
</tbody>
</table>
<p>この攻撃方法は<a href="https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/enabling-features-for-your-repository/managing-github-actions-settings-for-a-repository#preventing-github-actions-from-creating-or-approving-pull-requests" title="Allow GitHub Actions to create and approve pull requests">Allow GitHub Actions to create and approve pull requests</a>を無効化することで防ぐことができます。このオプションを無効化すると"PR creator" != github-actions && "PR approver" != github-actionsという制約を加えることができます。</p>
<h3>攻撃パターン3: GitHub ActionsでPRを作成しMalloryが承認する</h3>
<p>攻撃パターン2の応用として、MalloryがGitHub ActionsのワークフローでPRの作成とコードの追加を行い、Mallory自身がPRを承認するという方法もあります。</p>
<table>
<thead>
<tr>
<th style="text-align: center;">PR Creator</th>
<th style="text-align: center;">Last Commit Pusher</th>
<th style="text-align: center;">PR Approver</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: center;">github-actions</td>
<td style="text-align: center;">github-actions</td>
<td style="text-align: center;">Mallory</td>
</tr>
</tbody>
</table>
<p>この攻撃方法も攻撃パターン2と同様に<a href="https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/enabling-features-for-your-repository/managing-github-actions-settings-for-a-repository#preventing-github-actions-from-creating-or-approving-pull-requests" title="Allow GitHub Actions to create and approve pull requests">Allow GitHub Actions to create and approve pull requests</a>を無効化することで防ぐことができます。</p>
<h3>ここまでのまとめ</h3>
<p>これまで紹介した攻撃パターンとその他に考えうる攻撃パターンを表にまとめます。</p>
<p>なお、表内の対策1と対策2はそれぞれ次に対応します。</p>
<ul>
<li>対策1: "Require approval of the most recent reviewable push"の有効化</li>
<li>対策2: "Allow GitHub Actions to create and approve pull requests"の無効化</li>
</ul>
<table>
<thead>
<tr>
<th style="font-size: 16px;">Attack Pattern</th>
<th style="font-size: 16px;">PR Creator</th>
<th style="font-size: 16px;">Last Commit Pusher</th>
<th style="font-size: 16px;">PR Approver</th>
<th style="font-size: 16px;">対策1で防げるか</th>
<th style="font-size: 16px;">対策2で防げるか</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>Alice</td>
<td>Mallory</td>
<td>Mallory</td>
<td>✅ Yes</td>
<td>❌ No</td>
</tr>
<tr>
<td>2</td>
<td>Mallory</td>
<td>Mallory</td>
<td>github-actions</td>
<td>❌ No</td>
<td>✅ Yes</td>
</tr>
<tr>
<td>3</td>
<td>github-actions</td>
<td>github-actions</td>
<td>Mallory</td>
<td>❌ No</td>
<td>✅ Yes</td>
</tr>
<tr>
<td>4</td>
<td>github-actions</td>
<td>Mallory</td>
<td>Mallory</td>
<td>✅ Yes</td>
<td>✅ Yes</td>
</tr>
<tr>
<td>5</td>
<td>Mallory</td>
<td>github-actions</td>
<td>github-actions</td>
<td>✅ Yes</td>
<td>✅ Yes</td>
</tr>
<tr>
<td>6</td>
<td>Alice</td>
<td>Mallory</td>
<td>github-actions</td>
<td>❌ No</td>
<td>✅ Yes</td>
</tr>
<tr>
<td>7</td>
<td>Alice</td>
<td>github-actions</td>
<td>Mallory</td>
<td>❌ No</td>
<td>❌ No</td>
</tr>
</tbody>
</table>
<h3>攻撃パターン7: Aliceが作成したPRにMalloryがGitHub Actionsでコミットを追加し、Mallory自身が承認する</h3>
<p>表に記載の攻撃パターン1-6はGitHubのオプションを変更することで防ぐことができます。しかし、攻撃パターン7を防ぐ方法は(前提条件を変更しない限り)なさそうです。</p>
<p>具体的にはAliceが作成したPRにMalloryがGitHub Actionsを使って悪意あるコードを追加します。そしてMallory自身がPRを承認しマージします。(コードを追加するPRは必ずしもAliceが作成したPRである必要はなく、Dependabotのようなbotが作成したPRやオープンのまま忘れ去られているPRなどでも問題ありません。このようなPRが使われた場合、攻撃に気づくのは難しいでしょう)</p>
<table>
<thead>
<tr>
<th style="text-align: center;">PR Creator</th>
<th style="text-align: center;">Last Commit Pusher</th>
<th style="text-align: center;">PR Approver</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: center;">Alice</td>
<td style="text-align: center;">github-actions</td>
<td style="text-align: center;">Mallory</td>
</tr>
</tbody>
</table>
<h3>攻撃パターン7を防ぐ方法</h3>
<p>この攻撃はPR creator、last commit pusher、PR approverがすべて違うユーザーであり、これまで紹介したオプションを使用しても防ぐことができません。</p>
<p>GitHubが提供する方法でこの攻撃を防ぐにはPRのマージに必要な承認数(Required number of approvals before merging)を2以上に変更することです。しかしこの数を増やすことは開発者の生産性の低下につながり、あまり良い解決策とは言えません。</p>
<p>コードオーナーによるレビューを必須とするオプション(Require review from Code Owners)を使うことによりこの攻撃が行える可能性を減らすことは可能ですが、Malloryがコードオーナーであった場合は依然として攻撃が可能です。このオプションは攻撃の成功確率を下げることができるかもしれませんが、完璧な対策とはなり得ません。</p>
<p>現状、GitHubが提供する機能だけではこの攻撃を防ぐことはできないため、この攻撃パターンに対処したい場合はなんらかの仕組みを独自に開発する必要があります。例として以下のような方法が考えられます。</p>
<ul>
<li>攻撃パターン7に合致するようなPRがマージされた場合にアラートを上げるような仕組みを作る</li>
<li>PRのマージに必要な承認数を2にして攻撃パターン7に該当しない場合はbotがPRを承認し、botと人間1人による承認でPRがマージできるようにする</li>
</ul>
<p>なお、この件については今年5月ごろにGitHubに報告済みであり、意図した挙動であるという回答をもらっています。またこの件をブログに取り上げることについても承諾を得ています。</p>
<h2>まとめ</h2>
<p>本記事ではGitHubのbranch protectionについて、その回避方法と対策について考察しました。Branch protectionは重要なブランチを守る強力な機能である一方で、GitHub Actionsを利用すると場合によっては突破が可能であり、完全なものではないということもわかりました。本記事が各組織や個人がGitHubをよりセキュアに利用する一助になれば幸いです。</p>
- Argo Workflowsを導入して複数バッチの管理を行った話https://engineering.mercari.com/blog/entry/20241216-9c7bd72262/https://engineering.mercari.com/blog/entry/20241216-9c7bd72262/<p>こんにちは。メルコインでソフトウェアエンジニアをしている@goroです。 この記事は、Merpay & Mercoin Advent Calendar 2024 の記事です。 本記事は自分の所属するチームが管理す […]</p>
Tue, 17 Dec 2024 10:00:11 GMT<p>こんにちは。メルコインでソフトウェアエンジニアをしている<a href="https://twitter.com/awakot_56">@goro</a>です。<br />
この記事は、<a href="https://engineering.mercari.com/blog/entry/20241125-merpay-mercoin-advent-calendar-2024/">Merpay & Mercoin Advent Calendar 2024</a> の記事です。</p>
<p>本記事は自分の所属するチームが管理するマイクロサービスにおいて、ワークフローエンジンであるArgo Workflowsを導入し複数のバッチの制御を行ったので、その際に得た知見を共有します。</p>
<h2>Argo Workflowsとは</h2>
<p>Argo Workflowsは、Kubernetes上で並列ジョブをオーケストレーションするためのオープンソースのコンテナネイティブなワークフローエンジンです。Argo WorkflowsはKubernetesのCRD(Custom Resource Definition)として実装されています。CRDはKubernetes APIを拡張して独自のリソースを定義するものです。</p>
<p>ArgoWorkflowを利用することで、各ワークフローをkubernetesのマニフェストで定義することができます。ワークフローはDAGとしてモデル化されているので、タスク間の複雑な依存関係を表現することができます。</p>
<p>また、ワークフローの各ステップはKubernetesのPodとして実行されます。そのため、ユーザーがArgo Workflowsに詳しくなくても、Kubernetesについての知識があれば比較的容易に構築できます。また通常のKubernetesのPodで特定のタスクを実行できるなら、それをワークフローステップでも同様に実行できるのです。</p>
<p>私たちの開発するシステムはバッチが多く、その依存関係が複雑で、Kubernetes CronJobでこれらのバッチを管理するのが難しくなってきました。そこでワークフローエンジンとしてArgo Workflowsを導入し各バッチを管理するようになりました。</p>
<h2>Argo Workflowsのアーキテクチャ</h2>
<p>次にArgo Workflowsの内部アーキテクチャについて簡単に説明します。<br />
Argo WorkflowsはWorkflowなどのCRDとWorkflow Controller、Argo Serverという2つのDeploymentで構成されます。Workflow Controllerはリコンサイルを行い、Argo ServerはAPIを提供します。なお、Controllerは単独で使用することもできます。</p>
<p>Workflow Controllerと Argo Server は両方とも Argo Workflows namespaceで実行されます。ワークフローとそこから生成される Pod は別のnamespaceで実行されることが一般的です。</p>
<p>それぞれの役割は以下のようなものになっています</p>
<h3>Workflow Controllerの役割</h3>
<p>Workflow ControllerはKubernetesのCRDを監視します。これにより、新しいワークフローが作成されたり、既存のワークフローが更新されたりしたときに適切に処理します。<br />
ユーザーが定義したワークフローの各ステップをKubernetes上のポッドとしてスケジュールします。これには依存関係の解決や並列実行の管理も含まれます。<br />
各タスクの実行状態をリアルタイムで更新し、ワークフロー全体の進行状況を把握します。成功、失敗、中断などの各状態を管理します。<br />
タスクが失敗した場合の再試行ロジックや、失敗の際の回復手順を実行します。これにより、信頼性の高いワークフロー実行が可能になります。</p>
<h3>Argo Serverの役割</h3>
<p>ユーザーが操作できるAPIサーバーを提供します。<br />
ワークフローの管理、ログのアクセス、ワークフローのステータス確認のためのユーザーインターフェースとAPIエンドポイントを提供します。<br />
ユーザーとKubernetesのバックエンドをつなぐ役割を果たし、ワークフローの管理と可視化を容易にします。</p>
<p>Argo WorkflowsでWorkflowが実行されるまでの流れは以下のようなものになります。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/12/a9a61936-.drawio-3.png" alt="" /></p>
<ol>
<li>Userがワークフローをkubectl applyなどを利用しSubmitします。</li>
<li>Argo Serverがワークフローのカスタムリソースを作成します。</li>
<li>Workflow Controllerがそのカスタムリソースを検出します。</li>
<li>Workflow Controllerは、Kubernetes APIを用いてポッドを作成し、ステータスを監視します。このプロセスは、ワークフローが完了するまでループします。Podの実行終了後は次のステップのPodを作成します。</li>
</ol>
<h2>Argo WorkflowsでWorkflowを定義する</h2>
<p>今回、自分たちのサービスでArgo Workflowsを利用する上でWorkflowのカスタムリソースは利用していません。WorkflowTemplateというカスタムリソースでWorkflowを定義し、それをCronWorkflowというスケジュール実行したいワークフローを定義するリソースから呼び出す形でWorkflowを実行しています。</p>
<p>それぞれのリソースについては以下に簡単にまとめています。<br />
WorkflowTemplateは、再利用可能なテンプレートとして複数のWorkflowやCronWorkflowで使用できるため、今回はWorkflowTemplateを利用しました。記載方法に関してはkindが異なるだけでWorkflowとWorflowTemplateは同じ内容になるため、WorkflowをWorflowTemplateにリネームするだけで簡単に再利用できるリソースを作成することができます。</p>
<h3>Workflow</h3>
<p>Workflowは、複数のタスクやジョブを定義し、それらを一貫したシーケンスや依存関係に基づいて実行するためのフレームワークやプロセスです。具体的には、下記のような要素を含みます。<br />
タスクの定義: 具体的な作業内容(例えば、データ処理やモデルのトレーニングなど)<br />
依存関係: あるタスクが他のタスクに依存している場合、その順序を定義。<br />
実行環境: タスクが実行される場所(クラウド、オンプレミスなど)。<br />
実行管理: タスクのスケジューリング、リトライ、エラーハンドリングなど。</p>
<p>例:</p>
<pre><code class="language-yaml">apiVersion: argoproj.io/v1alpha1
kind: Workflow
metadata:
generateName: hello-world-
spec:
entrypoint: whalesay
templates:
- name: whalesay
container:
image: docker/whalesay:latest
command: [cowsay]
args: ["hello world"]</code></pre>
<h3>WorkflowTemplate</h3>
<p>WorkflowTemplateは、再利用可能なテンプレートとして複数のWorkflowで使用できる、Workflowの定義を含んでいます。具体的には、特定のタスクやシーケンスを標準化して、一貫した方法で繰り返し使用することができます。workflowTemplateRefでWorkflowやCronWorkflowから参照され利用されます。TemplateにはClusterTemplateWorkflowsという全てのnamescpaceで再利用するためのTemplateも存在します。</p>
<p>例:</p>
<pre><code class="language-yaml">apiVersion: argoproj.io/v1alpha1
kind: WorkflowTemplate
metadata:
name: hello-world-template
spec:
templates:
- name: whalesay-template
container:
image: docker/whalesay:latest
command: [cowsay]
args: ["hello from template"]</code></pre>
<h3>CronWorkflow</h3>
<p>CronWorkflowは、Cronジョブのようにスケジュールに基づいて定期的に実行されるワークフローです。具体的には、特定の時間や間隔で自動的に実行されるように設定が可能です。</p>
<p>例:</p>
<pre><code class="language-yaml">apiVersion: argoproj.io/v1alpha1
kind: CronWorkflow
metadata:
name: hello-world-cron
spec:
schedule: "0 0 * * *" # 毎日午前0時に実行
workflowSpec:
workflowTemplateRef:
name: hello-world-template # 先ほど定義したWorkflowTemplateの名前を指定</code></pre>
<p>またワークフローの依存関係を定義する方法は以下のStepsとDAGの2つの方法があります。</p>
<h3>STEPS</h3>
<p>STEPSでは一連のステップでタスクを定義することができます。テンプレートの構造は「リストのリスト」となっています。外側のリストは順次実行され、内側のリストは並行して実行されます。<br />
以下の例ではA実行後にB-1とB-2がパラレルに実行されます。</p>
<pre><code class="language-yaml"> - name: step-sample
steps:
- - name: A
template: test
- - name: B-1
template: test
- name: B-2
template: test</code></pre>
<h3>DAG</h3>
<p>DAGは、タスクを依存関係のグラフとして定義することができます。DAGでは、すべてのタスクをリストし、特定のタスクが開始される前に完了しなければならない他のタスクを設定します。依存関係のないタスクは即座に実行されます。<br />
以下の例の場合はA->B->C->Dの順で実行されます。</p>
<pre><code class="language-yaml"> - name: dag-sample
dag:
tasks:
- name: A
template: test
- name: B
dependencies: [A]
template: test
- name: C
dependencies: [B]
template: test
- name: D
dependencies: [C]
template: test</code></pre>
<p>これらのCRDと依存関係の定義を利用することで、以下のような順番にタスクを実行するWorkflowを作成することができました。<br />
<img src="https://storage.googleapis.com/prd-engineering-asset/2024/12/86955d4a-screenshot-2024-12-12-at-2.19.17-1024x489.png" alt="" /></p>
<p>こちらは実際のArgo Workflows Webコンソール上のスクリーンショットを掲載しています。WebコンソールではこちらのようなWorkflowの実行詳細で各Workflowの実行状況をリアルタイムで確認できます。また、コンソール上から簡単にリトライの実行やログの確認を行うこともできます。さらには定義されたWorkflowTemplateやCronWorkflowの一覧も確認することができ、それらを利用してWorkflowの実行をコンソール上からワンクリックで行うことが可能です。</p>
<h2>今回Argo Workflowsを利用して感じたメリット</h2>
<p>最後に今回Argo Workflowsの導入により、感じたいくつかのメリットを紹介します。</p>
<p>まず第一に、Argo WorkflowsがKubernetesネイティブなワークフローエンジンであり、各タスクをKubernetesのPodとして実行するため、他のKubernetesリソースと同様に、YAML形式のマニフェストを使ってワークフローを記述できる点が非常に便利でした。これにより開発者は既存のKubernetes知識を活用して比較的簡単にワークフローを構築できました。</p>
<p>さらに、ワークフローはコンソール上で直感的に可視化されるため、それぞれのタスクがどのように接続され、実行されているかを一目で把握できます。タスクの失敗時には容易にリトライが可能で、完了したタスクのログもすぐにアクセスできるため、迅速なトラブルシューティングが実現します。</p>
<p>また、このようなArgo Worksflowsのメリットは開発者以外の方にも大きな恩恵をもたらしました。例えば、QAチームはKubernetesの複雑なジョブ管理の詳細を知らなくても、kubctlコマンドなどを利用せずコンソール上からタスクを実行できるようになり、QAの効率化にも良い影響が発生しました。</p>
<h2>まとめ</h2>
<p>今回は、Argo Workflowsを利用して複数バッチの制御を行った事例について書きました。今後Argo Workflowsを導入される方の参考に少しでもなれば幸いです。次の記事は kobaryoさんです。引き続きお楽しみください。</p>
<p>参考文献<br />
<a href="https://argoproj.github.io/argo-workflows/">Argo Workflows Documentation</a></p>
- メルカリ Tech Radar の取り組みhttps://engineering.mercari.com/blog/entry/20241216-mercari-tech-radar-initiative/https://engineering.mercari.com/blog/entry/20241216-mercari-tech-radar-initiative/<p>こんにちは。メルカリのVP of Engineeringの @motokieeです。この記事は、Mercari Advent Calendar 2024 の12日目の記事です。 1. はじめに メルカリでは、Tech R […]</p>
Mon, 16 Dec 2024 11:00:10 GMT<p>こんにちは。メルカリのVP of Engineeringの <a href="https://twitter.com/motokiee">@motokiee</a>です。この記事は、<a href="https://engineering.mercari.com/blog/entry/20241125-mercari-advent-calendar-2024/">Mercari Advent Calendar 2024</a> の12日目の記事です。</p>
<h3>1. はじめに</h3>
<p>メルカリでは、Tech Radar の取り組みを2024年に開始しました。この記事では、メルカリTech Radar 導入の意図、定義の進め方、運用についてご紹介します。</p>
<h3>2. Tech Radar とは</h3>
<p><a href="https://www.thoughtworks.com/radar">Tech Radar</a> は、企業が技術選定を効率的に行うためのフレームワークです。元々<a href="https://www.thoughtworks.com/">ThoughtWorks社</a>が作成し、採用する技術のガイドラインとして用いられています。Tech Radar は、技術がどの段階にあるかを評価し、採用すべきか (Adopt)、試行すべきか (Trial)、評価途中であるか (Assess)、あるいは非推奨か (Hold) を示しています。</p>
<h4>ThoughtWorks について</h4>
<p>ThoughtWorks は、技術コンサルティングとソフトウェア開発を専門とするグローバル企業で、Tech Radar を始めとする革新的なアイデアを業界に提供してきました。そのためTech Radar は、企業が技術的な選択をする際の考え方として世界的に知られています。</p>
<h4>構成要素の説明</h4>
<p>Tech Radar は通常、以下の4つの項目で構成されています。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2024/12/a13577cb-i1.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2024/12/a13577cb-i1.png" alt="" /></a></p>
<ul>
<li><strong>Adopt(採用)</strong>: 十分に成熟し、すぐにでも採用する価値がある技術。 </li>
<li><strong>Trial(試行)</strong>: 有望で特定のプロジェクトで試行すべき技術。 </li>
<li><strong>Assess(評価)</strong>: 更なる調査と検証が必要な技術。 </li>
<li><strong>Hold(非推奨)</strong>: 現時点では推奨しない技術。</li>
</ul>
<h3>3. メルカリの Tech Radar について</h3>
<h4>なぜ始めたか</h4>
<p>メルカリでは、これまでフリマ事業をはじめ多くの新規事業にチャレンジしてきており、同時に新たな技術への挑戦も続けています。数多くの新しいことにチャレンジできた一方でメンテナンス等での課題も発生してきました。</p>
<p>ご経験のある方もいると思いますが、新しい技術を採用したもののメンテナンスが特定の人に委ねられてしまうケースはよくあると思います。特定の人がチームにいる間は開発生産性が高い状態を維持できるのですが、その人が異動や退職をしてチームからいなくなってしまうと、チームからナレッジがなくなりトラブルシューティングやメンテナンスが困難になってしまいます。</p>
<p>こういったトラブルシューティングやメンテナンスは、できる限り Platform Engineering でカバーしていきたいと考えています。しかし、メルカリではエンジニアの大多数は Product Engineering に所属し、Platform Engineering 所属のエンジニアの割合は低いです。そのため、Platform Engineering で無制限にメンテナンスなどを引き受けることはできず、一定の選択が必要となります。</p>
<p>「An Elegant Puzzle: Systems of Engineering Management」や「Staff Engineer: Leadership beyond the management track」などの書籍を執筆した Will Larson さんのブログポストの <a href="https://lethain.com/magnitudes-of-exploration/">Magnitudes of exploration</a> を参照してみましょう。</p>
<p>冒頭にサマリとして “Mostly standardize, exploration should drive order of magnitude improvement, and limit concurrent explorations.” と書かれています。日本語訳をすると「主に標準化し、探索は桁違いの改善を促進すべきであり、同時に行う探索を制限する」となります。</p>
<p>意図して選択を行い、限られた持ち物を磨き込み、生産性を劇的に改善し得るものに絞って新しい取り組みを行うことであると解釈しています。Less is More に通ずる考え方で、Tech Radar は過去の取り組みの蓄積でありその証跡となる役割を持っていて、新しい取り組みを始める際の羅針盤となってほしいと考えています。</p>
<p>Tech Radar を導入し活用することで、スピード感をもって一貫性のある技術選定できるようになることを目指しています。</p>
<h4>どのように定義したか</h4>
<p>メルカリのニーズや定義のしやすさに合わせて定義をカスタマイズしています。例えば、 メルカリ Tech Radar は Backend/Platform、 Mobile、 Web の3つのカテゴリに分かれています。この3つの領域に境界を分けて作業を進めるのが効率が良かったためです。</p>
<p>管理には原始的ですがスプレッドシートを利用しています。以下の図は Mobile 領域の languages-and-frameworks の Tech Radar に記載されている内容です。 </p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2024/12/20d8b616-i2.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2024/12/20d8b616-i2.png" alt="" /></a></p>
<p>現在使っている技術すべてに Architecture Decision Records (ADR) のような証跡があるわけではないので、実体としてどのように運用されているかをベースに Adopt などの定義を行いました。</p>
<p>なかには「会社として推奨を続けるべきか」曖昧なものもあり、ADR を作成し議論を行って曖昧な状態を解消したものもあります。</p>
<p>メルカリ Tech Radar で定義したものの大枠は <a href="https://engineering.mercari.com/technology-stack/">https://engineering.mercari.com/technology-stack/</a> に反映されています。Adopt / Trial / Assess / Hold などの詳細な定義について現在外部公開する予定はないのですが、Hold \= つまり会社ですでに使うことを推奨していないものは含まれていません。</p>
<h4>運用について</h4>
<p>Tech Radar は定期的に見直しを行い、最新の技術トレンドと社内のニーズに応じて更新していく必要があります。メルカリでは今年から運用を始めたため2025年に見直す予定で、年次の棚卸しのような行事にできると良いと考えています。</p>
<p>Tech Radar 自体の管理に多くの時間は使っていませんが、 Hold になっている、つまり非推奨のアイテムがどのような状態なのかはトラッキングを行っています。特に、非推奨である理由としてサービスのクローズなどの時間的制約があるものについては注意して関係するチームにマイグレーションのリマインドを行ったりしています。</p>
<h3>4. おわりに</h3>
<p>メルカリでの Tech Radar の取り組みについて紹介しましたが、まだ始めたばかりのためこれから課題はまだまだ出てくると思います。</p>
<p>技術標準の設定を行うような制約を設ける取り組みでは「新しいことができなくなってしまう」という意見も出てきますが、Tech Radar に書かれているものしか使えない・使ってはいけないという状況もメルカリらしくないと考えています。</p>
<p>先ほど紹介した「主に標準化し、探索は桁違いの改善を促進すべきであり、同時に行う探索を制限する」が良い指針になると思っており、どの程度の割合で探索を行っていくかを考えることもこれからの宿題だと考えています。</p>
<p>メルカリとして蓄積してきた数々のナレッジや技術的な取り組みを最大限活用してほしい、見過ごしてほしくないと考えています。Tech Radar はメルカリのエンジニアリングとして何に投資をし全体最適化を行っていくかの判断のベースとなるものだと考えています。</p>
<p>Tech Radar で技術スタックの定義されているいくつかの領域では、選択的集中を行うことで、人や時間をより適切に投資することができ始めています。主にはツールの自動化で、開発スピードとエンジニアの満足度を向上させ、事業の Enable に成功しています。</p>
<p>Tech Radar に Adopt と定義されているものは、会社として継続的改善を行うという意思表明だと考えて運用していきたいと考えています。</p>
<p>以上、メルカリ Tech Radar の取り組みについてのご紹介でした。</p>
<p>明日の記事は iso さんです。引き続きお楽しみください。</p>
- メルカリにおける商品フィードシステムのリアーキテクチャhttps://engineering.mercari.com/blog/entry/20241212-mercaris-seamless-item-feed-integration-bridging-the-gap-between-systems/https://engineering.mercari.com/blog/entry/20241212-mercaris-seamless-item-feed-integration-bridging-the-gap-between-systems/<p>はじめに こんにちは。メルペイの Growth Platform で Backend Engineer をしている@hiramekunです。 この記事は、Merpay & Mercoin Advent Calen […]</p>
Mon, 16 Dec 2024 10:00:27 GMT<h2>はじめに</h2>
<p>こんにちは。メルペイの Growth Platform で Backend Engineer をしている<a href="https://x.com/hiramekun_eng">@hiramekun</a>です。<br />
この記事は、<a href="https://engineering.mercari.com/blog/entry/20241125-merpay-mercoin-advent-calendar-2024/">Merpay & Mercoin Advent Calendar 2024</a> の記事です。<br />
Growth Platformは組織としてはメルペイに所属していますが、メルペイに限定されないさまざまな取り組みを行っています。その一つとして、商品フィードシステムのリアーキテクチャに取り組みました。これにより得られた多くの学びを今回紹介します!</p>
<h2>背景</h2>
<p>商品フィードとは、オンラインストアや商品カタログの情報を一括して管理し、さまざまな販売チャネルや広告プラットフォームに配信するためのデータ形式や仕組みを指します。メルカリでは多用な商品フィードに商品データを連携し、商品が広告として表示されるようにしており、外部メディアに対する商品訴求において重要な役割を担っています。</p>
<p>例えば、Googleのショッピングタブでは多数のサイトの出品を一覧できますが、メルカリの商品も表示されます。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/12/c415ea1d-screenshot-2024-12-09-at-23.27.58-1024x935.png" alt="" /><br />
(出典:<a href="https://www.google.com/search?sca_esv=c7cdf248ce05219c&rlz=1C5CHFA_enJP1073JP1073&q=%E3%82%B9%E3%83%97%E3%83%A9%E3%83%88%E3%82%A5%E3%83%BC%E3%83%B3+%E3%83%91%E3%83%83%E3%82%B1%E3%83%BC%E3%82%B8&udm=28&fbs=AEQNm0Aa4sjWe7Rqy32pFwRj0UkWd8nbOJfsBGGB5IQQO6L3J03RPjGV0MznOJ6Likin94pT_oR1DTSof42bOBxoTNxG8rlVtlHpDT0XaodfzKKV1TwR_qbS-aakEhWquIefCsFKaHB0KYQCzwp_KpjBzgqcrYGhvsLLOtjbuCfHDayPjTnT3CUWZbtHp26Caw_fmPEPneFrC2G3lsNMTxsEciHW3aqFEA&ved=1t:220175&ictx=111&biw=1720&bih=1294&dpr=1">GoogleのShoppingタブ</a>)</p>
<h2>課題</h2>
<p>歴史的経緯から、連携先ごとに異なる商品フィードシステムが分散して作られ管理されており、このことが多くの課題を引き起こしていました。以下にいくつかの例を挙げます。</p>
<ul>
<li>システムによって実装・メンテナンス担当チームが異なり、コミュニケーションコストが増加。</li>
<li>商品情報の取得や非表示商品のフィルタリングなど共通する処理があるにもかかわらず、連携先ごとに固有の実装が行われ、システムごとに異なる問題が発生。</li>
<li>システムごとにデータの取得元が異なり、商品の状態変更がリアルタイムにフィードに反映されない場合がある。</li>
</ul>
<h2>達成したい状態</h2>
<p>こうした課題を解決するために、一つのシステムで全ての連携先に対する実装を提供することを目標として、商品フィード用のマイクロサービスを立ち上げることになりました。Growth Platormがオーナーシップを持っている他の既存のマイクロサービスに機能を追加するという選択肢もありました。しかし、今回は新たなマイクロサービスを立ち上げることに決め、その理由は以下の通りです:</p>
<ul>
<li>既存のマイクロサービスの役割がすでに大きくなっており、その役割がさらに曖昧化することを避けるため。</li>
<li>各連携先サービスの特性に合わせてシステム設計を変更する際、他のシステムへの影響を最小限に抑えることができるため。</li>
<li>商品の更新イベントはRPSが高いため、システムの特性に応じてスケーリングが必要となる可能性があるため。</li>
</ul>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/12/a89c208f-screenshot-2024-12-09-at-23.31.38-1024x471.png" alt="" /></p>
<p>共通のフィルタリング設定や商品データの取得、商品のメタデータ付与などの処理は一つのシステムに統合することが必要です。これは、連携先に依存しない処理であり、修正が全サービスに適用されることを意図しています。</p>
<p>共通実装をまとめる一方、連携先に応じた固有実装は分離する必要があります。新しい連携先サービスを追加する際、必要最低限の差分で実装を完結させることが重要です。特に、外部APIへのリクエスト部分はエンドポイントやレート制限が異なるため、柔軟に変更できるようにしておく必要があります。</p>
<p>また、外部APIリクエストにはエラー対応が欠かせません。全てのエラーをこちらで制御できないので、常にエラーが発生する可能性を念頭に置き、リトライ可能な設計を採用しています。</p>
<h2>技術的アプローチ</h2>
<h3>アーキテクチャ</h3>
<p>具体的なアーキテクチャを紹介します。大枠としては、共通処理を担当するworkerと、連携サービス固有のworker(Batch Requester)に処理を分け、これらをPub/Subでつなぐ設計としました。この設計により、次のような利点があります:</p>
<ul>
<li>各workerのシステム特性に応じたスケーリングが可能。</li>
<li>他のマイクロサービスへのリクエストと外部APIへのリクエストを分離し、外部APIの予測困難な動作を切り離すことが可能。</li>
<li>新しいbatch requesterをPub/Subのsubscriberとして追加することで、共通実装部分を変更せずに新しい連携サービスを追加可能。</li>
<li>商品の状態更新イベントが急激に増えた際に、Pub/Sub Topicがメッセージキューとしてシステムの安定性を高めることができる。</li>
</ul>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/12/292a8151-image-3-1024x420.png" alt="" /><br />
大枠としては、共通処理部分のworkerと連携サービス固有のworker (Batch Requeseter) にworkerを分けて実装し、その二つをPub/Subで繋げるという設計にしました。こうすることで次のような利点があると考えました。</p>
<ul>
<li>workerそれぞれのシステム特性に応じたスケーリングが可能になる</li>
<li>外部APIにおける不確実な挙動を他のマイクロサービスから切り離すことができる</li>
<li>新しいbatch requesterをPub/Sub Topicへのsubscriberとして追加することで、共通実装部分には手を加えずに連携サービスを追加することがきる
<ul>
<li>商品の状態更新イベント数スパイクした時にPub/Sub Topicがメッセージキューとしてシステムの安定性を高める</li>
</ul>
</li>
</ul>
<p>ではそれぞれのworkerについてもう少し詳しく説明します。</p>
<h3>共通処理部分のworker</h3>
<p>共通処理部分のworkerは、商品の状態更新イベントとして別サービスからPub/Sub Topicに流れてくるデータを受け取ります。このTopicをsubscribeしてイベントをリアルタイムに受信し、他のマイクロサービスにリクエストを送ることで追加の商品情報を付与したり、フィルター設定を参照して不適切な商品を除外します。この結果、処理された商品情報をマイクロサービス内でのみ用いるPub/Sub Topicにpublishします。</p>
<p>このworkerにはHPA(Horizontal Pod Autoscaler)が設定されており、CPU使用率に基づいてPod数を動的に調整します。</p>
<h3>サービス固有のworker (Batch Requester)</h3>
<p>次に、その商品情報を受け取る側の実装です。フィード用にカスタマイズされた商品情報のPub/Sub Topicを、連携サービスごとにデプロイされた固有のbatch requesterがsubscribeします。</p>
<p>batch requesterは、外部APIへのリクエストを秒単位で継続的に実行する必要があります。そのため、Go言語で実装されたPodをCronJobではなくDeploymentとしてデプロイしています。Deploymentを使用することで、より細かい時間間隔でタスクを実行でき、必要に応じたスケーリングも柔軟に対応できます。</p>
<p>エラーハンドリングも重要です。外部APIの一時的なエラーやネットワークエラーでリクエストが失敗することがあるため、retry機能を実装しました。本システムではPub/Subのretry機構を活用し、以下のように機能します:</p>
<ul>
<li>batch requesterがPub/Subからメッセージを受け取り、インメモリにバッチとして保存。</li>
<li>一定間隔でそのバッチを外部APIに送信。</li>
<li>送信が成功した場合、そのバッチに含まれる全ての商品に対応するPub/Subメッセージをack。</li>
<li>送信が失敗した場合、全ての対応メッセージをnackし、Pub/Subがメッセージを再送。</li>
</ul>
<p>商品の状態をなるべくリアルタイムでフィードに反映したいため、ある一定の回数retryに失敗した場合はDead-letter topicに転送し、後続のリクエストを優先させます。</p>
<p>SLOとしては、商品フィードに正しく反映されている商品の割合を確認しています。今のところこのSLOは達成できているので、Dead-letter topicに溜まっている商品を再試行するためのジョブは必要ありませんが、将来的にはそうしたジョブを作ることも検討しています。</p>
<h2>最後に</h2>
<p>この商品フィードシステムを構築したことで、商品をよりリアルタイムに近い形でフィードに配信できるようになりました。また、共通の実装と各連携先の特有実装を分けることで、新しい連携先の追加がより簡単になりました。今後は新たな連携先の追加や、フィードデータのカスタマイズを進めていく予定です。</p>
<p>次の記事は@goroさんです。引き続きお楽しみください。</p>
- メルカリ ハロのプッシュ通知と CRM integration の話(Android編)https://engineering.mercari.com/blog/entry/20241214-mercari-hallo-push-notificaiton-and-crm-integration-android/https://engineering.mercari.com/blog/entry/20241214-mercari-hallo-push-notificaiton-and-crm-integration-android/<p>こんにちは。メルカリのソフトウェアエンジニアの @sintario_2nd です。 この記事は、連載:メルカリ ハロ 開発の裏側 – Flutterと支える技術 –の4回目と、 Mercari A […]</p>
Sat, 14 Dec 2024 11:00:41 GMT<p>こんにちは。メルカリのソフトウェアエンジニアの <a href="https://bsky.app/profile/sintario.bsky.social">@sintario_2nd</a> です。<br />
この記事は、連載:<a href="https://engineering.mercari.com/blog/entry/20241129-mercari-hallo-2024/">メルカリ ハロ 開発の裏側 – Flutterと支える技術 –</a>の4回目と、<br />
<a href="https://engineering.mercari.com/blog/entry/20241125-mercari-advent-calendar-2024/">Mercari Advent Calendar 2024</a> の10日目の記事です。</p>
<h2>この記事について</h2>
<p>メルカリ ハロは 2024年3月6日にサービスを開始しました。サービスローンチ後は開発チームの体制が変わり、わたしはGrowth Hackチームに配属されて Customer Relationship Management (CRM) のツールのインテグレーションに関わってきました。いろいろと一筋縄にはいかないこともあり試行錯誤したなかで、今回はプッシュ通知周りで遭遇した課題について、どのような調査を経てどのように解決したか、をご紹介したいと思います。</p>
<h2>メルカリ ハロのプッシュ通知の当初構成</h2>
<p>メルカリは各サービスへの通知を管理する microservice (Notification Service と呼ぶことにします) をすでに持っており、メルカリ ハロのバックエンド (Hallo Backend) から Notification Service 経由で Firebase Cloud Messaging (FCM) によるプッシュ通知が送られるという構成になっています。Hallo Backend からのメッセージがアプリの載っている端末にプッシュ通知として届くまでの図式としては下図のようになります。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/12/9a512727-sintario-on-dec.14-mercari-advent-calendar-2024-engineering-blog-draft-e1734054482654.jpg" alt="Mercari Hallo's push notification flow (original)" /></p>
<p>サービスローンチまでは非常に短い時間であったということもあり、まずは最低限の機能を実現するべく、 Flutter で実装されたアプリ側は <a href="https://firebase.flutter.dev/">FlutterFire</a> を使用して FlutterFire のガイドどおりに素直に実装されていました。</p>
<h2>Braze を組み込む</h2>
<p>サービスローンチ後は一般的にどんなサービスでも利用拡大を目指していくものかと思います。そのため、一定のターゲット属性に当てはまる利用者群に向けてキャンペーンのメッセージをお届けする、といった CRM 施策がよく行われます。メルカリ ハロでは、CRMの分野ではよく使われている <a href="https://www.braze.co.jp/">Braze</a> を採用しています。</p>
<p>Braze は各種のメッセージ手段でキャンペーンを実施する仕組みを持っていて、 In-App Messaging / Content Cards / e-mail / プッシュ通知 などに対応しています。この分野では老舗ということもあり、 <a href="https://www.braze.com/docs/developer_guide/platform_integration_guides/flutter/flutter_sdk_integration">Flutter向け Braze SDK の組み込みガイド</a> も提供されていたので書かれた通りに作業すればすんなり動くものと高をくくっていました。実際 IAM などは軽作業のみですんなり使えて拍子抜けしましたが、プッシュ通知については我々がもともと実装済みだった構成との兼ね合いで、想定よりも苦戦することになりました。以下では Android にフォーカスしてご紹介したいと思います。</p>
<p>Braze は Android のプッシュ通知には FCM を使う(※)ので、Braze がサービスに組み込まれると下図のように FCM の送信をトリガーする経路が2系統になります。アプリから見るとプッシュ通知は一律 FCM が送ってくることになるのでただ受信するだけなら単純なわけですが、実際にはそれぞれのプッシュ通知の着信率や開封率を知りたかったり、通知タップ後に何かしら特別なアクションがしたかったりしますよね。そのため受信したプッシュ通知が Hallo Backend 由来か Braze 由来かはアプリ側で通知の中身を見分け、仕分ける必要があります。</p>
<p>※ iOS については Braze は APNs を直接扱うので少し異なるデータフローになりますが、本題からそれるので今回は説明省略します。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/12/3bd8c93b-sintario-on-dec.14-mercari-advent-calendar-2024-engineering-blog-draft-1-e1734055523525.jpg" alt="Mercari Hallo's push notification flow with Braze" /></p>
<p>実際、なにも考えずに組み立て終わってレビューと機能検証をしていると、エンジニアの同僚やQAから「<strong>プッシュ通知の2重受信が起きることがあるみたいだ</strong>」と指摘されました。Braze SDK側は Braze 由来のプッシュ通知かどうかを見分けられるものの、もともとあった Hallo Backend 由来のプッシュを扱う実装が Braze からのプッシュ通知を見分けることができておらず、 Braze 由来のプッシュ通知が Hallo Backend 由来のプッシュ通知用のハンドラにも処理される2重ハンドリングが起きていました。</p>
<p>Braze からするとこういった FCM 発行者が複数になるパターンはよくある事案として想定されているようで、<a href="https://www.braze.com/docs/developer_guide/platform_integration_guides/android/push_notifications/android/integration/standard_integration/#step-1-register-braze-firebase-messaging-service">Braze と関係ないメッセージを fallback で扱うための FirebaseMessagingService をAndroid プロジェクト側で指定できる</a> ようになっています。braze.xml というリソースファイルに下記のように fallback の有効化と fallback サービス名を指定することになります。</p>
<pre><code class="language-xml"><bool name="com_braze_fallback_firebase_cloud_messaging_service_enabled">true</bool>
<string name="com_braze_fallback_firebase_cloud_messaging_service_classpath">com.company.OurFirebaseMessagingService</string></code></pre>
<p>またこれを前提にしているからか、Dart 向けの <a href="https://github.com/braze-inc/braze-flutter-sdk/blob/10.1.0/lib/braze_plugin.dart">braze_plugin.dart</a> では <code>RemoteMessage</code> が Braze 由来のものかを判別するメソッドを提供していないようです。</p>
<p>ということは、FlutterFire が Dart 層までメッセージを流し込む役目をしている <code>FirebaseMessagingService</code> の実装クラスをこの fallback サービスに指定すればいいのかな、、、となるわけですが、 アプリの AndroidManifest.xml をみてもそんなクラスはどこにも見当たらず、 🤔どうしたものか、となったわけです。</p>
<h2>FlutterFire を読む</h2>
<p>読者の皆さんはもうお気づきでしょうが、私たちは今 Flutter (Dart の世界)の範疇では解決できない領域に足を踏み入れました。ライブラリの Dart の実装部分だけを読んでいてもどうにもなりません。ネイティブアプリの開発知識を駆使して向き合う必要があります。</p>
<p>プッシュ通知のようなネイティブの機能と密接に関わるものはライブラリにもネイティブ実装部分があります。Android の場合は FCM のハンドリングをするサービスを AndroidManifest.xml に宣言する必要があり、アプリ側の manifest ではなくライブラリ側にも AndroidManifest.xml があってビルド時にマージされるので、つまりは FlutterFire のリポジトリにある AndroidManifest.xml を調べてみるのが良いだろう、と当たりをつけたわけです。</p>
<p>ありました。<br />
<a href="https://github.com/firebase/flutterfire/blob/_flutterfire_internals-v1.3.35/packages/firebase_messaging/firebase_messaging/android/src/main/AndroidManifest.xml">https://github.com/firebase/flutterfire/blob/_flutterfire_internals-v1.3.35/packages/firebase_messaging/firebase_messaging/android/src/main/AndroidManifest.xml</a> </p>
<pre><code class="language-xml"><manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="io.flutter.plugins.firebase.messaging">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<!-- Permissions options for the `notification` group -->
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<application>
<service
android:name=".FlutterFirebaseMessagingBackgroundService"
android:permission="android.permission.BIND_JOB_SERVICE"
android:exported="false"/>
<service android:name=".FlutterFirebaseMessagingService"
android:exported="false">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT"/>
</intent-filter>
</service>
<receiver
android:name=".FlutterFirebaseMessagingReceiver"
android:exported="true"
android:permission="com.google.android.c2dm.permission.SEND">
<intent-filter>
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
</intent-filter>
</receiver>
<service android:name="com.google.firebase.components.ComponentDiscoveryService">
<meta-data android:name="com.google.firebase.components:io.flutter.plugins.firebase.messaging.FlutterFirebaseAppRegistrar"
android:value="com.google.firebase.components.ComponentRegistrar" />
</service>
<provider
android:name=".FlutterFirebaseMessagingInitProvider"
android:authorities="${applicationId}.flutterfirebasemessaginginitprovider"
android:exported="false"
android:initOrder="99" /> <!-- Firebase = 100, using 99 to run after Firebase initialises (highest first) -->
</application>
</manifest></code></pre>
<p><a href="https://github.com/firebase/flutterfire/blob/_flutterfire_internals-v1.3.35/packages/firebase_messaging/firebase_messaging/android/src/main/java/io/flutter/plugins/firebase/messaging/FlutterFirebaseMessagingService.java">FlutterFirebaseMessagingService</a> というのがそれなのかな?と思って開いてみると、これ自身は FCM token のリフレッシュを Dart の世界に引っ張り込む入口程度の薄いクラスなのがわかります。</p>
<pre><code class="language-java">public class FlutterFirebaseMessagingService extends FirebaseMessagingService {
@Override
public void onNewToken(@NonNull String token) {
FlutterFirebaseTokenLiveData.getInstance().postToken(token);
}
@Override
public void onMessageReceived(@NonNull RemoteMessage remoteMessage) {
// Added for commenting purposes;
// We don't handle the message here as we already handle it in the receiver and don't want to duplicate.
}
}</code></pre>
<p><code>We don't handle the message here as we already handle it in the receiver and don't want to duplicate.</code> というコメントの通り、 <code>RemoteMessage</code> としてやってきているはずの通知メッセージの実処理は、実は <a href="https://github.com/firebase/flutterfire/blob/_flutterfire_internals-v1.3.35/packages/firebase_messaging/firebase_messaging/android/src/main/java/io/flutter/plugins/firebase/messaging/FlutterFirebaseMessagingReceiver.java">FlutterFirebaseMessagingReceiver</a> のほうが担っています。</p>
<p>なお、</p>
<pre><code class="language-xml"> <receiver
android:name=".FlutterFirebaseMessagingReceiver"
android:exported="true"
android:permission="com.google.android.c2dm.permission.SEND">
<intent-filter>
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
</intent-filter>
</receiver></code></pre>
<p>という記述を見て Android 開発のキャリアの長い方々はお気づきでしょうが、これは FCM ではなくそれ以前にプッシュ通知に使われていた GCM という仕組みの <code>BroadcastReceiver</code> の実装になります。現在は直接的にアプリや一般のライブラリがこれを使用するのは非サポートとなっているので、ご注意を。</p>
<p>FlutterFire の AndroidManifest に登場していた他のクラスについても少し触れておくと</p>
<ul>
<li><a href="https://github.com/firebase/flutterfire/blob/_flutterfire_internals-v1.3.35/packages/firebase_messaging/firebase_messaging/android/src/main/java/io/flutter/plugins/firebase/messaging/FlutterFirebaseMessagingBackgroundService.java">FlutterFirebaseMessagingBackgroundService</a>
<ul>
<li><code>FlutterFirebaseMessagingReceiver</code> がバックグラウンドでプッシュ通知を受け取った場合に内部的にこのサービスを enqueue して、Dart の世界に通知を取り次ぐ役割をします。 <code>BroadcastReceiver</code> が長時間かかる処理をしないようにこのような構成になっているものと思われます。<code>FlutterFirebaseMessagingReceiver</code> を使う限りは AndroidManifest に宣言しておかないといけないもの、ということになります。</li>
</ul>
</li>
</ul>
<p>こうして以下のことがわかったわけです。</p>
<ul>
<li>FlutterFire は <code>FirebaseMessagingService</code> の実装クラスを持っていたが、困ったことに <code>onMessageReceived(RemoteMessage)</code> の実装が空なので Braze からの fallback にはそのまま使えない。</li>
<li>また <code>FlutterFirebaseMessagingReceiver</code> を単純に組み込むとすべてのメッセージが Dart の世界まで引き込まれてしまう。フィルターアウトしたいメッセージの選別する手段を差し込む口が提供されていない。</li>
</ul>
<p>さてどうしよう。。。となりました。</p>
<h2>Braze SDK も読んでみる</h2>
<p>困ったときは実装を読み込むしかないな、ということで Braze SDK の中も見てみます。</p>
<p>Flutter 向けには <a href="https://pub.dev/packages/braze_plugin">braze_plugin</a> として提供されていますが、こちらも各OS向けのネイティブライブラリに依存していて、 Android については <a href="https://github.com/braze-inc/braze-android-sdk">braze-android-sdk</a> の android-sdk-ui モジュールにプッシュ通知周りの実装が入っているのがわかりました。<a href="https://github.com/braze-inc/braze-flutter-sdk/blob/10.1.0/android/build.gradle#L55">このあたり</a> です</p>
<pre><code class="language-groovy">implementation "com.braze:android-sdk-ui:30.4.0"</code></pre>
<p>Braze 自身は標準的な FCM の仕組みに則っていて、 Kotlin で実装された open class の <a href="https://github.com/braze-inc/braze-android-sdk/blob/v30.4.0/android-sdk-ui/src/main/java/com/braze/push/BrazeFirebaseMessagingService.kt">BrazeFirebaseMessagingService</a> により FCM Token と <code>RemoteMessge</code> がハンドリングされるようになっています。このクラスは必ず必要ということになります。うーん。。。ただ <code>onMessageReceived</code> はすごく薄い実装になっていて</p>
<pre><code class="language-kt"> override fun onMessageReceived(remoteMessage: RemoteMessage) {
super.onMessageReceived(remoteMessage)
handleBrazeRemoteMessage(this, remoteMessage)
}</code></pre>
<p><code>handleBrazeRemoteMessage(this, …</code> という記述から Java や Kotlin に詳しい方にはお察しいただける通り、このクラスは Java の static method (Kotlin の companion object の method)としてメッセージのハンドリング実体を提供する</p>
<pre><code class="language-kt"> /**
* Consumes an incoming [RemoteMessage] if it originated from Braze. If the [RemoteMessage] did
* not originate from Braze, then this method does nothing and returns false.
*
* @param remoteMessage The [RemoteMessage] from Firebase.
* @return true iff the [RemoteMessage] originated from Braze and was consumed. Returns false
* if the [RemoteMessage] did not originate from Braze or otherwise could not be handled by Braze.
*/
@JvmStatic
fun handleBrazeRemoteMessage(context: Context, remoteMessage: RemoteMessage): Boolean {
if (!isBrazePushNotification(remoteMessage)) {</code></pre>
<p>およびその冒頭でちらっと見えていますが Braze 由来の <code>RemoteMessage</code> かを判別する static method の</p>
<pre><code class="language-kt"> /**
* Determines if the Firebase [RemoteMessage] originated from Braze and should be
* forwarded to [BrazeFirebaseMessagingService.handleBrazeRemoteMessage].
*
* @param remoteMessage The [RemoteMessage] from [FirebaseMessagingService.onMessageReceived]
* @return true iff this [RemoteMessage] originated from Braze or otherwise
* should be passed to [BrazeFirebaseMessagingService.handleBrazeRemoteMessage].
*/
@JvmStatic
fun isBrazePushNotification(remoteMessage: RemoteMessage): Boolean {</code></pre>
<p>を発見できました…!! これでなんとかなりそうです。</p>
<h2>解決編</h2>
<p>いろいろやり方はあると思いますが、以下のような方法をとりました。</p>
<h3>Step1: FlutterFire の <code>FlutterFirebaseMessagingReceiver</code> を <code>tools:node=”remove”</code> を使って除去</h3>
<p><code>FlutterFirebaseMessagingReceiver</code> が直接 <code>RemoteMessage</code> を拾ってしまう限りは Braze 由来のメッセージを誤ってハンドリングするのを防げないので、<br />
<a href="https://developer.android.com/build/manage-manifests">Managing manifest files</a> にしたがって以下の記述をアプリ側の AndroidManifest.xml に追加し、 FlutterFire によって宣言されてしまう分を除去させます。</p>
<pre><code class="language-xml"> <receiver
android:name="io.flutter.plugins.firebase.messaging.FlutterFirebaseMessagingReceiver"
xmlns:tools="http://schemas.android.com/tools"
tools:node="remove"
android:exported="true"
android:permission="com.google.android.c2dm.permission.SEND">
<intent-filter>
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
</intent-filter>
</receiver></code></pre>
<h3>Step2: <code>BrazeFirebaseMessagingService</code> の派生クラスとして <code>HalloFirebaseMessagingService</code> を実装</h3>
<p><code>BrazeFirebaseMessagingService</code> 相当の機能はすべて残さないといけないし、一方で <code>FirebaseMessagingService</code> の実装クラスが複数いて読解に苦慮するのもなと思ったので、以下のような薄いクラスを書きました。</p>
<pre><code class="language-kt">/**
* We are using both FlutterFire and Braze to handle push notifications.
* Unfortunately we found that Braze notifications could be handled twice
* when integrating both of them following their official ways simply,
* therefore we resolved the issue by having our own [FirebaseMessagingService].
*
* # About Token refresh
*
* This class inherits from [BrazeFirebaseMessagingService] intentionally,
* since [BrazeFirebaseMessagingService.onNewToken] is needed to register a new FCM token to Braze.
* Note that our notification service will also receive a new FCM token in dart layer
* via [FlutterFirebaseMessagingService.onNewToken].
*/
class HalloFirebaseMessagingService: BrazeFirebaseMessagingService() {
/**
* FCM from Hallo backend through notification service should be handled by
* [FlutterFirebaseMessagingReceiver] which will redirect messages to dart layer.
* But braze_plugin provided by Braze for Flutter users doesn't provide any measure
* to filter Braze messages, therefore we need to filter out Braze messages in native layer before
* [FlutterFirebaseMessagingReceiver] works.
*
* [BrazeFirebaseMessagingService] can have a fallback service to handle messages from other than
* Braze, but we don't use the mechanism to not have multiple [FirebaseMessagingService]
* implementations.
*
* Fortunately [BrazeFirebaseMessagingService.handleBrazeRemoteMessage] is provided publicly
* to construct own FCM handling. If it doesn't consume FCM then we should delegate it to
* [FlutterFirebaseMessagingReceiver.onReceive].
*
* Also we removes [FlutterFirebaseMessagingReceiver] from AndroidManifest
* to prevent it from handing FCM directly.
*/
override fun onMessageReceived(remoteMessage: RemoteMessage) {
if (handleBrazeRemoteMessage(this, remoteMessage)) {
return
}
FlutterFirebaseMessagingReceiver().onReceive(this, remoteMessage.toIntent())
}
}</code></pre>
<p><code>handleBrazeRemoteMessage</code> が Braze 由来のメッセージでないとみなしてハンドルしなかった場合は <code>FlutterFirebaseMessagingReceiver</code> のインスタンスを直接作って FlutterFire の処理に乗せ直す、ということをやっています。 AndroidManifest には <code>FlutterFirebaseMessagingReceiver</code> がもう宣言されていない状態で、この場で必要なときだけ明示的に仕事を渡す、というかたちにしました。</p>
<h3>Step3: アプリの AndroidManifest.xml に、 <code>BrazeFirebaseMessagingService</code> のかわりに <code>HalloFirebaseMessagingService</code> を宣言</h3>
<pre><code class="language-xml"><!--
<service
android:name="com.braze.push.BrazeFirebaseMessagingService"
android:exported="false">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
-->
<service
android:name=".HalloFirebaseMessagingService"
android:exported="false">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service></code></pre>
<p>これでめでたく Braze 由来のメッセージは2重ハンドルされることがなくなり、 Hallo Backend 由来のメッセージも従来通り動作する状態を実現することができました。</p>
<h2>結び</h2>
<p>メルカリ ハロは Flutter アプリです。画面や機能の大部分は1ソースで Android / iOS 両方を実現しており、開発コストの圧縮と十分な性能の両立に Flutter という技術選択が寄与しました。それでも、 Flutter の範疇で解決できない問題に直面するケースはまれにあります。プッシュ通知のようなネイティブの処理に強く影響を受ける個別性の高い機能を扱うなかで、外部提供のSDKやライブラリの組み合わせによって発生した課題を、ライブラリのネイティブ実装部分を解読し工夫することによって乗り切った事例をご紹介させていただきました。</p>
<p>この話は iOS 編も実はありまして、method swizzling 満載の Objective-C を読むもう少し大変な冒険譚があるのですが、今回は Android だけで盛りだくさんになってしまいましたので、またの機会があればお話したいと思います。</p>
<hr />
<p>明日の記事は danny さんと simon さんです。引き続きお楽しみください。</p>
- メルカリのProduction Readiness Checkプロセスにおける新たな開発者体験https://engineering.mercari.com/blog/entry/20241213-new-production-readiness-check-experience-in-mercari/https://engineering.mercari.com/blog/entry/20241213-new-production-readiness-check-experience-in-mercari/<p>はじめに こんにちは。メルカリMarketplace SRE Tech Leadの@mshibuyaです。 この記事は、Mercari Advent Calendar 2024 の9日目の記事です。 自身が所属するMar […]</p>
Fri, 13 Dec 2024 11:00:27 GMT<h2>はじめに</h2>
<p>こんにちは。メルカリMarketplace SRE Tech Leadの<a href="https://twitter.com/m4buya">@mshibuya</a>です。<br />
この記事は、<a href="https://engineering.mercari.com/blog/entry/20241125-mercari-advent-calendar-2024/">Mercari Advent Calendar 2024</a> の9日目の記事です。</p>
<p>自身が所属するMarketplace SREチームは、メルカリグループ全体としてのPlatformを提供するPlatform Divisionに所属しています。この記事では、サービスの信頼性を支えるProduction Readiness Checkと呼んでいるプロセスに対して行った改善と、その結果もたらされた開発者体験について取り上げます。</p>
<p>サービスが必要十分な信頼性を持つことの重要性は広く認識されていると考えます。が、そのための取り組みは地道かつ手間がかかるものであり、ともすればそのためのプロセスの存在によって開発スピードを落としてしまうという結果につながりかねません。このたび行われたProduction Readiness Checkプロセスへの改善について、プロセスのどのような側面に着目して改善を行ったか、その結果どのような開発者体験へつながることを目指したかをご紹介します。同種の取り組みを行う皆様の参考となれば幸いです。</p>
<h2>Production Readiness Checkとは</h2>
<p>メルカリには、Production Readiness Check(略してPRC)と呼ばれるプロセスがあります。これは新しく開発されたプロダクトやマイクロサービスが満たすべき一連の基準を集めたチェックリストで、これに合格しないと本番環境において運用開始することはできません。<br />
過去のブログ記事において<a href="https://engineering.mercari.com/blog/entry/2019-12-23-084839/">概要の紹介</a>があるほか、チェック項目そのものについても最新のものではありませんが<a href="https://github.com/mercari/production-readiness-checklist">GitHub上にて公開</a>されています。</p>
<p>メルカリにおいては広くマイクロサービスアーキテクチャを採用しています。フリマアプリ「メルカリ」や、スマホ決済サービス「メルペイ」といったすでに大きな規模となったサービスにおいては多くの機能追加がマイクロサービスの新規開発という形で進みますし、「<a href="https://about.mercoin.com/">メルコイン</a>」や「<a href="https://hallo.mercari.com/">メルカリ ハロ</a>」といった新たに立ち上がるプロダクトについても「メルカリ」・「メルペイ」同様のマイクロサービス基盤における1マイクロサービスとしての形を取ります。したがって、マイクロサービスの新規立ち上げというのは日常的に発生します。またDevOpsにおけるYou build it, you run itの原則に従い、それらが本番での運用に耐える信頼性を持つよう担保していく責任も個々のマイクロサービス開発チームにあります。</p>
<p>マイクロサービス開発チームは必ずしもこうした新規のサービス立ち上げや、そのために必要な信頼性の担保に精通しているとは限りません。開発チームがマイクロサービスの立ち上げを自律的に行いつつも必要な信頼性を担保することがProduction Readiness Checkプロセスの狙いです。</p>
<h2>解決したい課題</h2>
<p>Production Readiness Checkは、メルカリにおいて開発されるサービスがお客さまからの実トラフィックを受け稼働していくために十分な信頼性を持っている(i.e. Production Readyである)ことを担保するために欠かせない役割を果たしてきました。その一方で、次第にこのチェックプロセス自体の運用が開発者にとって重荷となりつつあったことは否定できません。</p>
<p>メルカリにおけるProduction Readiness Checkプロセスは、チェックリストを含むissueを作成することで開始され、issueのcloseとともに終了となります。このissueのopen-closeまでの全期間で実作業が発生しているわけではないので参考値とはなりますが、約5年間のデータでは平均35.5日を要していました。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/12/105a5db9-average-days-to-close-production-readiness-check-issue.png" alt="Average days to close Production Readiness Check issue" /></p>
<p>また、Platform Divisionで行っている開発者インタビューにおいても、Production Readiness Checkプロセスへの不満が多く寄せられている状況でした。<br />
社内開発者からのコメントの例:</p>
<blockquote>
<p>Did PRC as well, lots of “copy this, paste this, take a screenshot of this…”<br />
Overall straightforward, just PRC was a pain</p>
<p>PRC, takes about 4 weeks</p>
<p>Takes a lot of time<br />
Personal opinion is that 1-2 sprints could be cut by simplifying the PRC process</p>
<p>Too many things to check, some things are hard to understand how to verify</p>
<p>最もやりたくない仕事のひとつ。必要なのはわかる</p>
</blockquote>
<p>メルカリグループでは、新規プロダクト立ち上げや既存プロダクトへの機能追加においてスピードがこれまで以上に重視されるようになっており、このProduction Readiness Checkプロセスを高速化し、デリバリーにかかる時間を短縮し省力化することは喫緊の課題とみなされるようになりました。</p>
<h2>既存のプロセスにおける開発者体験</h2>
<p>ここでは新規プロダクトの立ち上げを例に取り、改善前のProduction Readiness Checkプロセスにおける典型的な体験を示します。エピソードはすべて架空のものなので、最悪のケースでは開発者がこんな体験をしていた可能性がある…という程度のものとしてお読みください。</p>
<p>メルカリグループではとある新規のプロダクトを立ち上げることとなりました。このプロダクトはフリマアプリとしてのメルカリとのシステム連携を含むものとなっており、お客さまがスムーズにプロダクトを使っていただくのに十分な信頼性を有している必要があります。</p>
<p>早速開発チームが立ち上げられ、6ヶ月間でのサービス提供開始を目指して怒涛の開発がスタートします。まずチームはプロダクト上の要件を明らかにし、それをシステム上の実装に落とし込むための設計を行い、Design Docの形でまとめます。出来上がった設計を元に実際のアプリケーションコードの実装が順調に進んでいき、リリースも間近の5ヶ月目に大半の機能の実装を終えることができました。</p>
<p>さて、チームは実際にプロダクトのリリース準備に入ります。本番で利用するインフラ環境構築を進めていくのですが、このあたりでチームはProduction Readiness Checkプロセスの存在に気づくのです。このチームは入社してまもないメンバーや既存サービスの定常的な開発に関わってきたメンバーが多かったため、Checkプロセスの必要性を見落としていたのです。これを全部満たすことがプロダクトのリリースに必須と聞いたチームは全力で対応するのですが、対応が必要な項目数自体が多いこともあり、もともと設計上の考慮に含まれていなかった要件なども判明し難航します。</p>
<p>結果、チームはProduction Readiness Checkを完了するのに2ヶ月を要してしまい、その分プロダクトのリリースを延期せざるを得ませんでした。必要な信頼性を満たせていなかった以上仕方ないのですが、その分プロダクトを早く世に出しお客さまからフィードバックをいただくチャンスを失ってしまいました。</p>
<h2>解決策</h2>
<h3>チェックの自動化</h3>
<p>プロセスに労力がかかる要因としてまず挙げられるのが、チェックが必要な項目数そのものが多いこと、またそれが増加するトレンドにあることです。</p>
<p>この種のチェックリスト全般に言えることではありますが、時間経過とともにチェック項目は増加する傾向にあります。なにかトラブルが発生した際の原因としてとある設定を持っていなかったことが指摘され、その再発防止策がチェックリストに追加されるという流れが必然的に発生するためです。<br />
こうした対応がその場しのぎの安易な追加とならないよう抑制的に運用されていたとは理解していますが、それでも典型的なサービスにとってのチェック項目数は公開版の時点では62項目であったものが最新の内部版では71項目となり、約3年間で15%近く増加しています。</p>
<p>また、チェックリストに含まれる項目には「どのような状態を満たす必要があるのか」の定義はあっても、「どのような手段が使われていれば満たされていると見なせるのか」についての言及が十分にはされていないものもあります。このこと自体はマイクロサービスアーキテクチャにおける個々のサービスでの技術選定の柔軟性に配慮した結果ではあると考えられますが、「この確認ができればOK」という基準が明確に示されない状態は実際にチェックリストの対応を行う担当者としても、それをレビューする側としても、都度の検討が必要で負担の大きなものとなっていました。</p>
<p>こうした状況を緩和し労力を削減するため、Production Readiness Checkプロセスにおけるチェックの部分的な自動化を推進しました。プロセスにおけるチェック対象はアプリケーションソースコードやインフラ設定など多岐にわたりますが、自動化によるチェック実装が容易に行えるものから実装を始め、現在はチェック項目の半数近く、45%ほどの項目においてなんらかの自動化チェックが行われるようになっています。</p>
<p>この結果、少なくとも自動化済みの部分に対しては開発者が容易にチェックを行うことができ、求められる状態とのギャップを埋めるためのアクションを取れる状態となっています。自動化チェック自体が「どのような手段が使われていれば満たされていると見なせるのか」の基準を内包しているため、レビュワーにとっても迷いなく判断を行えるようになりました。</p>
<h3>既存のコンポーネントによるProduction Readiness Check対応の拡充</h3>
<p><a href="https://speakerdeck.com/tcnksm/platform-engineering-at-mercari-platform-engineering-kaigi-2024">過去にも様々な機会で発信されているように</a>、メルカリにおいてはPlatform Engineeringの考え方が、プラクティスとして広く実践されています。セルフサービスを重視したPlatformによって開発者生産性を高めていく思想のもと、Platform Divisionはこれまでも多くのコンポーネントを構築し、開発者に提供しています。</p>
<p>Production Readiness Checkプロセスの負荷が高い原因を探る過程において、我々はそれによって求められている要件と、実際にPlatformとして提供しているコンポーネント群が持つ機能にギャップが存在していることを認識しました。</p>
<p>メルカリのPlatformにおいては、SDLCの全領域において、開発者が効率的に必要な目的を達成できるよう様々なコンポーネントを提供しています。それらのコンポーネントが十分にカバーできていない領域ながら、Production Readiness Checkプロセスにおいて大きな労力を要している部分を特定し、そのギャップを満たすためのコンポーネントの機能拡充を行いました。</p>
<p>またより重要かつ費用対効果の高い改善として、それらのコンポーネントによって満たされるProduction Readiness Check上の要求を明確化するようドキュメントの改善を行っています。</p>
<p>今回の取り組みを通じた気づきとして、開発者がマイクロサービスを構築していく上で避けて通れないProduction Readiness Checkプロセスに対し、こういったコンポーネントをいかに統合し全体としての開発者体験を作れるかが重要という点があります。単にコンポーネントを提供するだけではなく、その先にあるチェックプロセスそのものを改善したことで、相互のフィードバックループが機能する状態を作れたのではないかと考えています。</p>
<h3>"Shift-Left"によるアプローチ</h3>
<p>ここでいう"Shift-Left"はソフトウェアテストやセキュリティの文脈でよく使われる考え方で、例えばテスト実施のような何らかの活動をより早い段階(=タイムライン図における左側)に動かすことを指しています。</p>
<p>先にあげた新規プロダクト立ち上げの失敗例においては、チームはプロダクトをリリースする直前、短期間にProduction Readiness Checkプロセスを行うことを試み、その多大な労力ゆえに困難に直面しています。個人的にはこの手の状況を「夏休みの宿題ギリギリ問題」と呼ぶのですが、これはチームが怠惰だからそうなってしまったのではなく、構造的な問題があると考えます。新たなプロダクトを立ち上げるにあたっては数多のチャレンジ・困難が存在しており、それらを解決していくなかで、重要と知りつつも喫緊の必要がないものは先送りせざるを得ないからです。</p>
<p>この問題に対処するためには、仕組みレベルでの改善が必要と考えました。前項の自動化が達成された今、チームは少なくとも自動化されたチェック項目に対しては最小限の労力で何度もチェックを行い、漸進的に要件を満たしていくことが可能となっています。また、既存のコンポーネントによるProduction Readiness Check対応も拡充され、早い段階よりそうしたコンポーネントの採用を進めておくことによって、労せずに必要な要件をあらかじめ満たしておけるようになりました。仕上げとして、これらの施策の存在をチームが開発初期の段階から認識できる状態をつくることで、リリース直前の短期間に作業が集中することを防げると考えました。</p>
<p>とはいえ、単にそうした新プロセスや解決策の存在を周知したところで、声が届く範囲には限界があります。そこで、既に確立された、開発初期に必ず発生する別のプロセスの中に入れ込むことで、開発初期のチームがその存在を漏れなく認識できるよう工夫しています。<br />
メルカリでは新規システムの設計にあたってはDesign Docを作成し関係するチームのレビューを受ける文化が根付いています。そのDesign Docを作成する元となるテンプレートにおいて、Production Readiness Checkの存在そのものや、その中でも早期からの考慮が必要な項目について記載し注意を促すこととしました。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/12/d27e4e54-design-doc-template-prc-section.png" alt="Design Doc section for Production Readiness Check" /></p>
<p>これら一連の"Shift-Left"によるアプローチの結果として、開発者が実際の開発やインフラ環境の構築に着手するずっと前、設計の段階からそれらの要件を知り、Production Readiness Checkプロセスへ向けより早期に意味のあるアクションが取れるようになりました。</p>
<h2>新たなプロセスによる開発者体験</h2>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/12/e3ffc63b-new-prc-experience.png" alt="New PRC experience" /></p>
<p>プロセス改善、そして自動化を取り入れたProduction Readiness Checkプロセスによって、先の架空の新規プロダクト立ち上げにおける開発者体験がどのように変わるかを見ていきましょう。</p>
<p>まずは、Shift-Leftの結果として、チームは6ヶ月の開発期間における最初の段階、設計を行いDesign Docを作る際にProduction Readiness Checkプロセスの存在を知ります。そこで注目すべき要件を理解し、設計段階から考慮しておくことで、必要条件を満たすための根本的なアクション(例えばプロダクト上の要件変更についてステークホルダーと相談するなど)に踏み込むことも可能になります。</p>
<p>5ヶ月目、いよいよプロダクトのリリースが近づき、チームはProduction Readiness Checkプロセスの準備を始めます。事前の想定に基づき要求を満たすために適切なPlatformのコンポーネントを選定済みなので、要求を満たすために新たに行う必要のある変更・作業は最小限に抑えられています。</p>
<p>Production Readiness Checkにおけるチェック項目が満たせているかどうかの確認は、自動化によって大幅に労力が削減されました。おかげでチームは1ヶ月間でProduction Readiness Checkプロセスを完了することができ、お客さまに早く価値を届けフィードバックを得ることで更にプロダクトを磨き上げていくことができるようになりました。</p>
<h2>今後の展望</h2>
<p>このようにProduction Readiness Checkプロセスは一定の改善を受けており、実際のマイクロサービスリリース前におけるチェックにも利用されはじめています。一方、既存のコンポーネントによるProduction Readiness Check要求項目の対応状況や、自動化における対応可能ケースの拡充にはまだまだ改善の余地があり、より良い開発者体験を目指し当面の間注力していくべき領域になると想定しています。</p>
<p>それらの改善を推し進めていった先には何があるでしょう?<br />
筆者個人としては、「チェックを行う」という考え方自体が不要になることが理想的だと考えています。Platformが提供する機能・コンポーネントによって標準でほぼ全ての必要条件が満たされており、開発者はなにも考えずとも、信頼性の高いサービスを構築・運用していける世界観です。<br />
存在を意識する必要がない、当たり前に必要なことが満たされている理想のPlatformを目指して、道のりは遠いながらもいかにそこに近づいていけるかを考えたいです。</p>
<h2>まとめ</h2>
<p>この記事では、メルカリでのProduction Readiness Checkプロセスの概要について説明し、そのプロセスに対してどのような改善を行ったか、その結果としてどのような開発者体験を作ることができたかをご紹介しました。</p>
<p>明日の記事はsintario_2ndさんです。引き続きお楽しみください。</p>
- メルカリのエンジニアリングカルチャーについてhttps://engineering.mercari.com/blog/entry/20241213-mercari-engineering-culture/https://engineering.mercari.com/blog/entry/20241213-mercari-engineering-culture/<p>こんにちは。メルペイVPoEの@jorakuです。 この記事は、Merpay & Mercoin Advent Calendar 2024 の記事です。 メルペイに入社して半年が経ちました。この期間で改めて個人的 […]</p>
Fri, 13 Dec 2024 10:00:05 GMT<p>こんにちは。メルペイVPoEの@jorakuです。<br />
この記事は、<a href="https://engineering.mercari.com/blog/entry/20241125-merpay-mercoin-advent-calendar-2024/">Merpay & Mercoin Advent Calendar 2024</a> の記事です。</p>
<p>メルペイに入社して半年が経ちました。この期間で改めて個人的に実感した「メルカリらしさ」についてお話ししたいと思います。メルカリといえば、Go Bold / All for One / Be a ProといったValueが知られていますが、メルカリグループの魅力はそれだけにとどまりません。多様性を尊重し、挑戦を恐れず、学び続けるこの環境は、成果を最大化させる基礎を築き、自分自身やチームの成長にも大きく貢献しています。</p>
<h1>1. Go Global / 多様性を包摂する文化</h1>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/12/a191e345--2024-12-12-23.43.46.png" alt="" /></p>
<p>メルカリには55カ国からプロフェッショナル人材が集まっています。特にエンジニアの半数以上は外国籍の優秀なエンジニアであり、異なる文化や考え方に触れることで、自分の視野が広がるのを実感します。この「多様性」は単なるスローガンではなく、日常の中で感じられるリアルなものです。<br />
特にメルカリはグローバルに展開し、かつ国内外問わず多様なモノや価値の循環を行っているため、異なる視点やアイデアを生み出すことに貢献しています。</p>
<p>参考:<br />
<a href="https://about.mercari.com/press/news/articles/20240925_impactreport/" title="メルカリIMPACT REPORT">メルカリIMPACT REPORT</a></p>
<h3>GOTサポートで言語の壁を越える</h3>
<p>多様な背景を持つメンバーが活躍する中で、GOT (Global Operations Team) のサポートは非常に心強い存在です。同時通訳者の組織が整備されており、必要に応じていつでも誰でも活用できます。これにより、言語の壁を感じることなくスムーズに仕事を進められます。私も活用することがありますが、GOTの皆さんの技術用語の理解の深さには毎回驚かされます。</p>
<p>参考:<br />
<a href="https://careers.mercari.com/mercan/articles/37731/" title="GOTの取り組み">GOTの取り組み</a><br />
<a href="https://careers.mercari.com/mercan/articles/24948/" title="言語サポートの仕組み">言語サポートの仕組み</a></p>
<h3>やさしい日本語とやさしい英語</h3>
<p>さらに、お互いの理解を深めるために「やさしい日本語」や「やさしい英語」を使う文化も根付いています。「伝わりにくいことは歩み寄る姿勢」が自然と浸透しているのは、メルカリらしいポイントだと感じます。</p>
<p>参考:<br />
<a href="https://careers.mercari.com/mercan/articles/39203/" title="やさしい日本語・英語の活用">やさしい日本語・英語の活用</a></p>
<h1>2. Scrap and Buildの精神</h1>
<p>メルペイに入って感じたもう一つの素晴らしい文化が、技術的負債の解消やシステムのリアーキテクチャといった「Scrap and Build」の精神です。技術だけではなく組織や人材まで及びます。現状に満足することなく、常に改善を目指して行動する姿勢が、エンジニアとしてのやりがいを大きくしています。過去の話ですがアプリをゼロから作り直した話はとてもGo Boldな取り組みでした。そのような大胆な意思決定が可能であり、それを最後までやり切る力がメルカリの強みです。<br />
現在も、多くのマイグレーションやリアーキテクチャが行われ、常に進化し続ける土台を作っています。12/6の@hibagon-sanのアドベントカレンダーにおいてもMerpay API Rearchitectureについて触れられています。</p>
<p>参考:<br />
<a href="https://careers.mercari.com/mercan/articles/35887/" title="大型プロジェクト「GroundUp App」の道程">大型プロジェクト「GroundUp App」の道程</a><br />
<a href="https://careers.mercari.com/mercan/articles/36183/" title="iOS&Androidのテックリードが振り返る、すべてがGo Boldだった「GroundUp App」">iOS&Androidのテックリードが振り返る、すべてがGo Boldだった「GroundUp App」</a><br />
<a href="https://engineering.mercari.com/blog/entry/20241204-first-seven-months-new-grads/" title="メルカリ新卒1年目のエンジニアが最初の7ヶ月間でやったこと">メルカリ新卒1年目のエンジニアが最初の7ヶ月間でやったこと</a></p>
<h3>循環する組織</h3>
<p>また、半年ごとに役割が変わるほどの柔軟な組織体制も魅力です。これにより、新しいチャレンジが可能となり常に新しい知識や経験を重ねることができます。もちろん専門性を深掘りしたい人はその意思も反映されます。<br />
一度メルカリを離れたとしても、再びメルカリに戻ってくる人材も少なくありません。このような柔軟な文化が、組織としての強さを支えています。<br />
社外に応募している職種はそのまま社内公募(Bold Choice)にも出されるため、自分のキャリアを後押しすることも可能です。</p>
<p>参考:<br />
<a href="https://careers.mercari.com/talent-development/" title="Talent Development(Bold Choice)">Talent Development(Bold Choice)</a></p>
<h1>3. ドキュメント文化</h1>
<p>メルカリで働く上で特に印象的なのが「ドキュメント文化」です。この文化は、メルカリが持つ透明性と効率性を象徴するものといえます。</p>
<h3>会議前の「読む読むタイム」</h3>
<p>会議によっては事前に議題に関するドキュメントが作成され、参加者が「読む読むタイム」を持ちます。この時間を通じて、全員が同じ情報を共有した状態で議論を始めることができるため、深い議論や迅速な意思決定が可能になります。さらに、意思決定の背景や議論の経緯もドキュメントに残されるため、後から過去の決定やプロセスを追跡することができます。</p>
<h3>図よりも文字で表現</h3>
<p>提案資料や説明の場では、Power PointやSlideを使うのではなく、文字で詳細に表現するのが特徴です。これは、図では曖昧になりやすい部分も正確に伝えることができるためです。この文化は、Amazonの「ナラティブ文化」に似ていますが、メルカリ独自の進化を遂げています。</p>
<h3>VisionならびにRoadmapの言語化</h3>
<p>特に興味深いのは、VisionやRoadmapの作成が全社員に求められる点です。多くの企業では、方向性の策定はDirectorやその上の役職者が主に担当しますが、メルカリではプロジェクトをリードする社員にはVisionやRoadmapを作成することが奨励されています。このプロセスにより自分やチームの進むべき方向性や戦略を明らかにし、ステークホルダの共通認識や期待値の調整が容易になります。長期的な視点をもったアーキテクチャに取り組むこともできます。これが、個人の成長とメルカリ全体の方向性をリンクさせる鍵となっています。</p>
<p>参考:<br />
<a href="https://engineering.mercari.com/blog/entry/20231225-creating-mercari-engineering-roadmap/" title="メルカリエンジニアリングのロードマップ作成">メルカリエンジニアリングのロードマップ作成</a></p>
<h1>4. 成長し続ける環境</h1>
<p>最後に、学びのサポートについてです。成長自体は業務における成果によってもたらされますし、成長するかしないかは全ては個人の問題だと捉えています。ただ、メルカリではエンジニアの成長を後押しする制度が整っています。お互いが学び合うOpen Doorは頻繁に開催され、プリンシパルエンジニアによるAsk me anything など。また、書籍購入支援や、O’Reilly Safari Books Onlineの提供、海外カンファレンスへの参加など、個人の成長を強力に後押ししてくれます。このような支援のおかげで、自分のスキルを高めることができ、より高いパフォーマンスを発揮できるようになっています。</p>
<p>参考:<br />
<a href="https://engineering.mercari.com/blog/entry/20231205-fintech-techtalk/" title="Fintech Tech Talk at Office Week を開催したよ!">Fintech Tech Talk at Office Week を開催したよ!</a><br />
<a href="https://careers.mercari.com/talent-development/" title="Talent Development(Learning Support)">Talent Development(Learning Support)</a></p>
<p>参考:<br />
メルペイエンジニアが参加した/予定の海外カンファレンス(一部)</p>
<table>
<thead>
<tr>
<th style="text-align: left;">Conference Name</th>
<th style="text-align: left;">Technology Area</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: left;">Droidcon Berlin/Lisbon/London/San Francisco/NYC</td>
<td style="text-align: left;">Android</td>
</tr>
<tr>
<td style="text-align: left;">GopherCon UK 2024</td>
<td style="text-align: left;">Backend</td>
</tr>
<tr>
<td style="text-align: left;">QCon 2024 Shanghai</td>
<td style="text-align: left;">Backend</td>
</tr>
<tr>
<td style="text-align: left;">SREcon24 Europe/Middle East/Africa , Americas</td>
<td style="text-align: left;">SRE</td>
</tr>
<tr>
<td style="text-align: left;">Pragma Conference 2024</td>
<td style="text-align: left;">iOS</td>
</tr>
<tr>
<td style="text-align: left;">SwiftLeeds 2024</td>
<td style="text-align: left;">iOS</td>
</tr>
<tr>
<td style="text-align: left;">KubeCon + CloudNativeCon North America</td>
<td style="text-align: left;">SRE</td>
</tr>
<tr>
<td style="text-align: left;">Do iOS 2024</td>
<td style="text-align: left;">iOS</td>
</tr>
<tr>
<td style="text-align: left;">Agile Testing Days 2024</td>
<td style="text-align: left;">QA/Testing</td>
</tr>
<tr>
<td style="text-align: left;">AppDevCon 2025</td>
<td style="text-align: left;">Mobile</td>
</tr>
<tr>
<td style="text-align: left;">KotlinConf 2024</td>
<td style="text-align: left;">Android</td>
</tr>
<tr>
<td style="text-align: left;">Google I/O 2024</td>
<td style="text-align: left;">Android</td>
</tr>
<tr>
<td style="text-align: left;">EuroSTAR Conference 2024</td>
<td style="text-align: left;">QA/Testing</td>
</tr>
<tr>
<td style="text-align: left;">WWDC24</td>
<td style="text-align: left;">iOS</td>
</tr>
<tr>
<td style="text-align: left;">React Advanced London</td>
<td style="text-align: left;">Frontend</td>
</tr>
</tbody>
</table>
<h1>終わりに</h1>
<p>メルペイに入社して半年、メルカリのエンジニアリングカルチャーの強みを日々実感しています。これまで多くの事を学び取り入れてきたつもりですが、この短い期間で、全く異なる変化を生み出せている事を感じています。それは、メルカリの文化がGo Globalな挑戦を後押しし、常に進化し続ける環境を提供してくれるからだと思います。引き続きこれまでの強みを活かした組織を作っていければと思います。</p>
<p>また、メルカリグループのミッションである「あらゆる価値を循環させ、あらゆる人の可能性を広げる」、そしてメルペイのミッションである「信用を創造して、なめらかな社会を創る」は、特に今の時代において、その意義と重要性を増していると感じます。いき過ぎた資本主義や、社会や経済が目まぐるしく変化する中で、これらのミッションは多くの人々にとって新しい価値を提供し、持続可能な未来を形作るための力になると確信しています。このミッションへの思いはまた別途述べる事にします。<br />
この記事を通じて、少しでもメルカリのエンジニアリングカルチャーについてお伝えできていれば幸いです。</p>
<p>次の記事は cyanさんです。引き続きお楽しみください。</p>
- メルカリ ハロのデザインシステムとFlutterhttps://engineering.mercari.com/blog/entry/20241210-flutter-hallo-design-system/https://engineering.mercari.com/blog/entry/20241210-flutter-hallo-design-system/<p>こんにちは。メルカリ ハロのモバイルチームのEMの@atsumoです。 この記事は、連載:メルカリ ハロ 開発の裏側 – Flutterと支える技術 –の3回目と、 Mercari Advent […]</p>
Thu, 12 Dec 2024 11:00:59 GMT<p>こんにちは。メルカリ ハロのモバイルチームのEMの<a href="https://twitter.com/atsumo">@atsumo</a>です。<br />
この記事は、連載:<a href="https://engineering.mercari.com/blog/entry/20241129-mercari-hallo-2024/">メルカリ ハロ 開発の裏側 – Flutterと支える技術 –</a>の3回目と、<br />
<a href="https://engineering.mercari.com/blog/entry/20241125-mercari-advent-calendar-2024/">Mercari Advent Calendar 2024</a> の7日目の記事です。</p>
<h1>はじめに</h1>
<p>メルカリ ハロは2024年3月にリリースされた、Flutterを用いて開発されたアプリケーションです。本記事では、デザインシステムの導入によって実現した開発効率の向上と、その具体的な運用方法について共有いたします。</p>
<h1>目次</h1>
<ol>
<li>デザインシステムの概要</li>
<li>メルカリ ハロのデザインシステム Componentのご紹介</li>
<li>FigmaからFlutterへの実装について</li>
<li>Componentを使った画面実装</li>
<li>1年経過して見えてきた課題</li>
<li>今後の展望</li>
<li>まとめ</li>
</ol>
<p>それでは、順を追って説明していきます。</p>
<h1>デザインシステムの概要</h1>
<p>デザインシステムについて触れたいと思います。</p>
<p>私たちはデザインシステムを『サービスにおけるUXやUIの一貫性を保つための要素と仕組み』と捉えています。デザインシステムを活用することで、個別のコンポーネントや色やタイポグラフィの設定だけではなく、サービス全体におけるそれらの一貫性を担保しながら、開発体験および生産性を向上させることができます。</p>
<p>デザインシステムによってデザイナーの創造性を制限してしまうのではないか?など自由度が下がってしまうのではないかと懸念されることがあるかも知れませんが、私がデザインシステム導入を通じて感じたことは、制限があることで創造性が損なわれるのではなく、むしろ明確なルールと基盤があるおかげで、本来解決すべき課題に集中できるということです。今回メルカリ ハロでデザインシステムを導入したことで、デザイナーとエンジニア間でとても効率的に開発を進めることができたと感じています。</p>
<p>参考: Figma Blog – <a href="https://www.figma.com/ja-jp/blog/design-systems-101-what-is-a-design-system/">デザインシステムの基本: デザインシステムとは?</a></p>
<h1>メルカリ ハロのデザインシステム Componentのご紹介</h1>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/12/63c0697b--index.png" alt="" /></p>
<ul>
<li>Design Token</li>
<li>Components</li>
<li>Specific Components<br />
などのように大きく3つの構成要素で分かれています。</li>
</ul>
<h2>Design Token</h2>
<p>Design Tokenとして定義しているのは5つあります。</p>
<ul>
<li>Color</li>
<li>Space</li>
<li>Radius</li>
<li>Shadow</li>
<li>Typography</li>
</ul>
<p>Colorに関してはDark Mode / Light Modeを定義しておりvariablesとして管理されています。</p>
<p>Figmaのデザインデータでは、Spacing、Radius、Typographyなども画面サイズに応じて変数で管理されています。ただし、ハロアプリはタブレットなどの大きな画面に対応していないため、これらは反映されていません。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/12/c6030272-token.png" alt="" /></p>
<h2>Components</h2>
<p>Componentsではドメインに依存しないようなUI Componentを定義しています。</p>
<p>Button / AppBar / Checkbox / Divider / Toggle / Badge / Tab / ProgressBar / Indicator / Snackbar / Tooltip など他のデザインシステムにもあるような一般的なものや</p>
<p>Input / Section Title / State Message / Callout / Notesなど、少しハロ独自で使っているがドメインに依存しすぎないようなComponentを用意しています。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/12/90defd56-components.png" alt="" /></p>
<h2>Specific Components</h2>
<p>Specific Componentsとしてハロのドメインや仕様に依存していて、複数画面で使用されるようなComponentも定義しています。</p>
<p>Specific Components自体も基本的にはComponentsの組み合わせでできており、それぞれのComponentが持っているプロパティはSpecific Componentでも適用することが可能です。</p>
<p>こちらのSpecific Componentsのケースで言うと内部でCalloutという上記のComponentとして定義したものを使用しているため、CalloutのComponentのプロパティで変更できる部分に関してはこちらでも変更することが可能です。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/12/e4b99b7e-specific_component_light.png" alt="" /></p>
<h1>FigmaからFlutterへの実装について</h1>
<p>Listのアイテムを例に説明していきます。<br />
ハロ内ではListItemでも複数のパターンのitemを用意しています。<br />
これを全て1つのComponentとしてプロパティを切り替えで定義してしまうと、プロパティがたくさん増え、いろんな組み合わせでいろんな表現ができる一方で、わかりにくいComponentになってしまいます。<br />
ハロでは下記の様にそれぞれ別のComponentとして取り扱っています。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/12/766e33a5-listitem.png" alt="" /></p>
<p>Figma上でコンポーネントのプロパティを✏️で示してもらうことで、変更可能な値がわかりやすくなり、実装時にはそのプロパティを基にしてコンポーネントを作成することで、デザインデータとコードの認識を一致させることができました。</p>
<p>Show Divider: Top / Show Divider: Bottomなど Figmaのデザイン上は便利な一方でだが実際に実装時にComponentには入れない部分などもありますが、おおよそ同じようなプロパティ構成になっています。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/12/19b606de-listitem_figma_prop.png" alt="" /></p>
<p>実際のComponentのコード</p>
<pre><code class="language-dart">class ListItemWithValue extends ConsumerWidget {
ListItemWithValue({
required this.label,
required this.value,
required this.horizontalPadding,
super.key,
this.note,
this.onTap,
this.align = ListItemValueAlign.end,
...
}) : assert(
!((note?.isNotEmpty ?? false) && align == ListItemValueAlign.start),
'Value align can only be start if note is not null',
);
final String label;
final String value;
final String? note;
final GestureTapCallback? onTap;
final ListItemValueAlign align;
...
}</code></pre>
<p>Figmaのデータを元に実装する際にはFigmaのDev Modeをよく利用しています。Dev Modeを使用することでCSSやiOSのUIKit / SiwftUI、AndroidのXML / Composeなどのコードに変換された状態で確認することができます。残念ながら、現在Flutterは公式サポートされていないですが、Community pluginでFlutterのコードに変換するようなPluginがいくつかあり、そちらを使うことで実装を容易にすることができます。</p>
<p><a href="https://www.figma.com/community/plugin/842128343887142055/figma-to-code-html-tailwind-flutter-swiftui">Figma to Code (HTML, Tailwind, Flutter, SwiftUI)</a>などのpluginを使って、変換されたコードを見ながら、Widgetの構成を検討することがあります。変換されたコードは高さや幅が固定値になってしまっていたり、無駄なWidgetがあったりするためそのまま使うのは難しいですが、実装時の参考になることは多いです。</p>
<h2>Componentを確認する</h2>
<p>前回 <a href="https://engineering.mercari.com/blog/entry/20240606-mercari-hallo-app-tech-stacks/">メルカリ ハロ アプリの技術スタックの紹介</a>でも<a href="https://pub.dev/packages/widgetbook">Widgetbook</a>についても軽く触れていますが、Componentを確認するためにWidgetbookを使用しています。</p>
<table>
<thead>
<tr>
<th style="text-align: center;">※1: Figma上のプロパティ</th>
<th style="text-align: center;">※2: コード上のプロパティ</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: center;"><img src="https://storage.googleapis.com/prd-engineering-asset/2024/12/b3fc88fb-figma_playground.png" alt="" /></td>
<td style="text-align: center;"><img src="https://storage.googleapis.com/prd-engineering-asset/2024/12/9c36cf3f-widgetbook_ui_catlog.png" alt="" /></td>
</tr>
</tbody>
</table>
<p>上記のように Figma上の ✏️ 部分(※1)とコードのプロパティ(※2)の認識を合わせることで、Componentで変更できる値がわかりやすくなり、コミュニケーションがとてもスムーズに行えるようになっています。<br />
画面実装前のComponentを量産する時期には、新規画面実装の際のカタログとしてWidgetbookが大いに活躍していました。Webでよく使われている<a href="https://storybook.js.org/">Storybook</a>が持っているようなさまざまな機能などが今後Widgetbookにも追加されていけば、画面実装時のモックUI実装にも大いに役立つ可能性があり、今後もWidgetbookに期待しています。</p>
<h2>Componentを使った画面実装</h2>
<p>メルカリ ハロのデザインシステムを活用して、具体的な画面実装を行う方法について説明します。ここでは、設定画面を例に、どのようにデザインシステムのComponentを使用して画面を構築するかを紹介します。</p>
<h3>設定画面の実装</h3>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/12/5f46624d-settings_screen_sample.png" alt="" /></p>
<p>設定画面では、以下のようにComponentを使用しています。</p>
<ul>
<li>NavigationBar – 画面のタイトルと戻るボタンを表示</li>
<li>Notes – 設定に関する説明文を表示</li>
<li>ListItem – 現在の設定状態を表示</li>
<li>Button – 設定変更用のボタン</li>
</ul>
<pre><code class="language-dart">class SettingsScreen extends HookConsumerWidget {
const SettingsScreen({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final theme = ref.watch(appThemeProvider);
return Scaffold(
// 1. Navigation Bar - 画面のタイトルと戻るボタンを表示
appBar: const HalloNavigationBar.normal(
title: Text('設定画面タイトル'),
),
body: SafeArea(
child: Padding(
padding: EdgeInsets.only(
left: theme.spacing.size4,
right: theme.spacing.size4,
top: theme.spacing.size8,
bottom: theme.spacing.size10,
),
child: Column(
children: [
// 2. Notes - 設定に関する説明文を表示
const HalloNotes(
content: '設定に関する説明文をここに記載します',
),
Gap(theme.spacing.size8),
// 3. List Item - 現在の設定状態を表示
HalloListItem.value(
label: '設定項目名A',
value: '設定状態',
),
const HalloDivider(),
HalloListItem.leadingWidget(
label: '設定項目名B',
leading: const Icon(Icons.info_outline_rounded),
note: '設定項目に関するメモなどを記載する',
doubleLine: true,
),
const HalloDivider(),
HalloListItem.trailingWidget(
label: '設定項目名C',
trailing: HalloSwitchButton(
value: true,
onChanged: (value) {
// Switchのオンオフによって実行される処理
},
),
note: 'オンオフを設定することが可能',
),
const HalloDivider(),
const Spacer(),
// 4. Button - 設定変更用のボタン
HalloButton.filled(
label: '設定を変更する',
onPressed: () {
// 設定画面を開く処理
},
),
],
),
),
),
);
}
}</code></pre>
<p>このように、デザインシステムのコンポーネントを使用することで、統一感のあるUIを簡単に実装することができます。各コンポーネントは再利用可能であり、デザインの一貫性を保ちながら効率的に開発を進めることができます。</p>
<h1>リリースから半年後に見えてきた課題</h1>
<p>メルカリ ハロは今年の3月にリリースされています。去年の今頃は上記のComponentを用いて各Component自体はWidgetbookで確認することができ、それらのComponentを使用して新規の画面を実装するフェーズでした。Componentの活用により、生産性高く画面実装を行うことができたと感じています。<br />
最近は新しい画面を作ることもありますが、今まであった画面の機能の改善や機能を追加していくことも増えています。その中で既存のコンポーネントでは対応しきれないケースや、当初の設計では考慮されていなかった部分が少しずつ明らかになってきました。<br />
例えばComponentに内包されているPaddingやMarginとは違うものを使った方が、単体の画面構成としては収まりが良いなど、既存のものとは少しだけ異なる亜種のComponentが生まれ始めています。ルールに縛られすぎてUXを阻害するものになってはいけないと思いつつも、デザインシステムは、ユーザー体験だけでなく、開発者やデザイナーの体験も高めることで、その効果を最大限に発揮できると考えています。<br />
現在はデザイナーとコミュニケーションを取り、基本的にはデザインシステムで定義されているものを使用するというルールを改めて確認するようにしています。一方で、デザインシステムで定義されているものだけではユーザー体験を損ねる可能性があるケースにし関しては、デザインシステム自体の改善であったり、Specific Componentsを定義するような流れをとっています。</p>
<h1>今後の展望</h1>
<p>メルカリ ハロのデザインシステムは、さらなる開発の効率化と品質向上を目指して進化を続けています。この目的のために、いくつかの新しい可能性を模索しています。<br />
まだアイデアの段階ではありますが、Figmaなどのデザインツールにおいて、Componentが正しく使用されていない箇所を自動的に検知できるlintの仕組みを導入することを検討しています。また、新たなチームメンバーが加わる際に、デザインシステムを円滑に理解し活用できるよう、勉強会やドキュメントの整備にも力を入れています。全員が共通の知識とスキルを持てる環境を作り、プロジェクトに貢献できるよう取り組んでいきます。</p>
<p>さらに<a href="https://help.figma.com/hc/ja/articles/24039793359767-Figma-AI%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6">Figma AI</a>はじめ<a href="https://v0.dev/">v0</a>や<a href="https://bolt.new/">bolt.new</a>など生成AIを使ったデザインおよび実装のアプローチに興味を持っており、これらを参考にメルカリ ハロのデザインシステムに基づいたデザインから実装の効率化の方法を模索しています。これによって、デザインを試行錯誤するプロセスが、デザイナーやエンジニアだけでなく、他の職種の方々にも手の届きやすいものになればと考えています。</p>
<h1>まとめ</h1>
<p>この記事では、メルカリ ハロのデザインシステム導入による開発効率向上とその具体的な運用方法についてご紹介しました。デザインシステムは、ビジュアルの一貫性を保つだけでなく、デザイナーとエンジニアが円滑に連携し、生産性を高めるための基盤となっています。リリースから半年以上が経過し、新たな課題も浮上していますが、引き続きデザイナーと協力してデザインシステムの改善を続けています。<br />
今後は生成AIの活用を視野に入れつつ、さらなる効率化とデザインの品質向上を進め、より良いユーザー体験を提供できるよう努めていきます。</p>
<p>明日の記事はmshibuyaさんです。引き続きお楽しみください。</p>
- 効率的な情報検索を可能にするSlackの検索テクニックhttps://engineering.mercari.com/blog/entry/20241211-8351140ebb/https://engineering.mercari.com/blog/entry/20241211-8351140ebb/<p>こんにちは。メルコイン エンジニアのpoohです。 この記事は、Merpay & Mercoin Advent Calendar 2024 の記事です。 目的 LLM(大規模言語モデル)による情報取得能力が向上し […]</p>
Thu, 12 Dec 2024 10:00:08 GMT<p>こんにちは。メルコイン エンジニアのpoohです。<br />
この記事は、<a href="https://engineering.mercari.com/blog/entry/20241125-merpay-mercoin-advent-calendar-2024/">Merpay & Mercoin Advent Calendar 2024</a> の記事です。</p>
<h1>目的</h1>
<p>LLM(大規模言語モデル)による情報取得能力が向上している今日、以下で紹介する検索テクニックが不要になる時代が目前に迫っているかもしれません。しかし、まだしばらくは役立つ場面が多いと思いますので、ぜひ参考にしてください。</p>
<p>検索技術の中には「知っている人にとっては当たり前」でも、「知らない人にとっては驚き」につながるものがあります。今回ご紹介するのは、Slackで効率的に情報を検索するための基本的なテクニックです。</p>
<h1>説明の前に</h1>
<p>今回の投稿のためにSlackのドキュメントを読み、知らなかった検索クエリを知ることができ、いい学びとなりました。</p>
<p>新たに<code>on:today</code> は頻繁に使うようになりました。<br />
自分の仕事の振り返りと情報共有を目的として個人的に日報を書いています。その際に、「from:@pooh on:Today」を早速使っています。</p>
<h1>ダブルクォーテーションで括る</h1>
<p>最初に紹介するのは、「ダブルクォーテーションで括る」方法です。<br />
Slackでは通常、検索クエリに基づいて曖昧検索が行われます。たとえば、以下のような結果が得られます。</p>
<ul>
<li>「探してね」で検索 → 「探し」「探す」がヒット</li>
<li>「見つかった」で検索 → 「見つか」「見つから」「見つかり」「見つかる」がヒット</li>
</ul>
<p>この曖昧検索は便利な場合もありますが、正確に特定の単語を検索したい場合には不便です。その際には、検索語句をダブルクオーテーション(例: "見つかった")で括ることで、完全一致する結果だけを表示させることができます。</p>
<h1>特定キーワードの除外(-)</h1>
<p>次に紹介するのは、「-(マイナス)」を使った方法です。特定のキーワードを含むメッセージを検索結果から除外したい場合に便利です。</p>
<p>例:<br />
<code>KPI -FY24Q1</code><br />
→ 「FY24Q1」を含むメッセージを除外した結果が表示されます。</p>
<h1>特定ユーザーの発言を検索(from:@)</h1>
<p>特定のユーザーの発言のみを検索したい場合は、from:@を使用します。</p>
<p>例:<br />
<code>from:@example</code><br />
→ Slack名が@exampleの人の発言のみが検索されます。</p>
<p>さらに、複数のユーザーを対象とする場合は以下のように記述します。<br />
<code>from:@example1 from:@example2</code><br />
→ @example1と@example2の両方の発言が表示されます。</p>
<h1>チャネル内の発言を検索(in:)</h1>
<p>特定のチャネル内での発言を検索する場合には、in:を使用します。</p>
<p>例:<br />
<code>in:#all-random</code><br />
→ #all-randomチャネル内の発言が検索されます。</p>
<p>また、特定のDMを検索する場合には以下のように記述します。<br />
<code>in:@example1</code><br />
→ @example1とのDMを対象に検索。</p>
<p>複数のチャネルやDMを対象にしたい場合は以下のように記述できます。<br />
<code>in:#all-random in:example1,example2</code></p>
<h1>特定ユーザーとのメッセージを検索(with:)</h1>
<p>with:を使うと、特定のユーザーとのやり取りを簡単に検索できます。</p>
<p>例:<br />
<code>with:@example1</code><br />
→ 以下を含む発言が対象になります。</p>
<ul>
<li>@example1とのDM</li>
<li>@example1が参加しているスレッド内のメッセージ</li>
</ul>
<h1>日付を指定して検索(before: / after: / on:)</h1>
<p>検索範囲を特定の日付に絞りたい場合は、before: / after: / on:を使用します。</p>
<p>例:</p>
<p><code>on:2024-12-01</code><br />
→ 2024年12月1日の発言だけが表示されます。</p>
<p><code>before:2024-12-09</code><br />
→ 2024年12月8日以前の発言が表示されます。</p>
<p><code>after:2024-11-30</code><br />
→ 2024年12月1日以降の発言が表示されます。</p>
<p>日付は年月日のみならず、月単位でも指定可能です。<br />
例:</p>
<p><code>on:2024-10</code> → 2024年10月の発言<br />
<code>before:2024-10</code> → 2024年9月以前の発言</p>
<h1>まとめ</h1>
<p>ここで紹介した検索クエリは組み合わせて使用することで、さらに効率的な検索が可能になります。</p>
<p>例:<br />
<code>on:2024-12-08 from:@example1 "連絡して" -FY24Q1</code><br />
→ 「2024年12月8日」、「@example1」、「"連絡して"を含む」、「FY24Q1を除外した」発言が検索結果として表示されます。</p>
<p>適切な検索クエリを駆使することで、必要な情報を迅速かつ正確に見つけることが可能です。ぜひ試してみてください!</p>
- WYSIWYGウェブページビルダーを支える技術とSever Driven UIへの拡張https://engineering.mercari.com/blog/entry/20241210-f7c478382a/https://engineering.mercari.com/blog/entry/20241210-f7c478382a/<p>この記事は、Merpay & Mercoin Advent Calendar 2024 の記事です。 はじめに こんにちは。メルペイ Frontend の @togami です。 私たちのチームでは Engage […]</p>
Wed, 11 Dec 2024 10:00:10 GMT<p>この記事は、<a href="https://engineering.mercari.com/blog/entry/20241125-merpay-mercoin-advent-calendar-2024/">Merpay & Mercoin Advent Calendar 2024</a> の記事です。</p>
<h1>はじめに</h1>
<p>こんにちは。メルペイ Frontend の @togami です。<br />
私たちのチームでは Engagement Platform、通称 EGP という内製マーケティングツールの開発をしています。ポイントやクーポンなどのインセンティブの配布、LP の作成と公開、キャンペーンの作成など CRM 関連のことをマーケターや PM がコーディングの知識なしで行えるようにするための社内ツールです。EGP はメルカリ US を除く全てのプロダクトで使われている会社全体の共通基盤となっています。 本記事ではこの中でも LP 作成機能、通称 <strong>EGP Pages</strong> について紹介します。また、 EGP Pages の拡張版であり Server Driven UI の実装である <strong>EGP Cards </strong>について紹介します。</p>
<h1>EGP Pages とは</h1>
<p>EGP Pages は WYSIWYG コンポーネントエディタです。LP の作成に特化しています。</p>
<ul>
<li>Text</li>
<li>Image</li>
<li>Layout</li>
<li>Lottie</li>
<li>Entry Button</li>
</ul>
<p>など全部で 28 種類のコンポーネントが用意されており、それらを組み合わせて LP を作成します。このツールを使って公開された LP は月間で 5000 万 PV ほどあり、月によっては 100 を超える LP が新たに公開されています。</p>
<h2>EGP Pages の使い方</h2>
<p>どのように機能するのか簡単なサンプルの作成を通してみていきます。 このサンプルでは Web ブラウザで閲覧している時は QR コードとテキストを、Mobile で閲覧している時はテキストカスタムボタンを表示するようにします。</p>
<p>①まずメニューから Layout コンポーネントを選択します(図1.1)。</p>
<figure style="text-align: center; margin-top: 12px">
<img src="https://storage.googleapis.com/prd-engineering-asset/2024/12/13f55f66-1-1.png" alt="コンポーネントメニュー" /><figcaption>図1.1 コンポーネントメニュー</figcaption></figure>
<p>左側の Tree View に Layout が追加されました。 ②この Layout は Web 向けのコンテンツのコンテナとして使用するため、右側のタブの設定から”Web Content”と名付けます(図1.2)。</p>
<figure style="text-align: center; margin-top: 12px">
<img src="https://storage.googleapis.com/prd-engineering-asset/2024/12/8bc24322-1-2.png" alt="Layout へのネーミング" /><figcaption>図1.2 Layout へのネーミング</figcaption></figure>
<p>③続いて QR コードのコンポーネント、Text のコンポーネントを挿入します。Text には"Please install app"というテキストを入れます(図1.3)。 </p>
<figure style="text-align: center; margin-top: 12px">
<img src="https://storage.googleapis.com/prd-engineering-asset/2024/12/f40eec45-1-3.png" alt="QRとTextの追加と設定" /><figcaption>図1.3 QRとTextの追加と設定</figcaption></figure>
<p>④次にアプリで閲覧している時のためのコンテンツを保持するために別の Layout コンポーネントを追加します。”Mobile Content”と名付け、その中にテキストとボタンを追加します(図1.4)。</p>
<figure style="text-align: center; margin-top: 12px">
<img src="https://storage.googleapis.com/prd-engineering-asset/2024/12/d56002c5-1-4.png" alt="Mobile 用のコンテンツ追加" /><figcaption>図1.4 Mobile 用のコンテンツ追加</figcaption></figure>
<p>⑤全てのコンテンツを作成した後、When コンポーネントを使用して Layout をラップします。このコンポーネントは表示条件を設定でき、その条件が True の時のみ表示されるものです。 ⑥When で作成したコンテンツをラップして表示する条件を追加します。JavaScriptで条件を追加が、多用する条件についてはテンプレートが存在するので利用します。ここでは Mobile でのみ表示したいので、”Is using mobile apps”を選択すると条件が設定されます(図1.5)。</p>
<figure style="text-align: center; margin-top: 12px">
<img src="https://storage.googleapis.com/prd-engineering-asset/2024/12/db94f46d-1-5.png" alt="Whenによる条件分岐の設定" /><figcaption>図1.5 Whenによる条件分岐の設定</figcaption></figure>
<p>⑦同様に Web 向けにもうひとつ When コンポーネントを追加し、⑧条件を設定します(図1.6)。 </p>
<figure style="text-align: center; margin-top: 12px">
<img src="https://storage.googleapis.com/prd-engineering-asset/2024/12/d211197e-1-6.png" alt="Web用コンテンツの分岐" /><figcaption>図1.6 Web用コンテンツの分岐</figcaption></figure>
<p>このエディタにはデバイスの設定(Web/iOS/Android)やログイン状態、エントリー状態の有無など、状態をエミュレートする機能が備わっています。⑨iOS を想定して描画結果を確認してみると、このように Mobile Content の When ブロック内の要素のみが描画されているのがわかります(図1.7)。</p>
<figure style="text-align: center; margin-top: 12px">
<img src="https://storage.googleapis.com/prd-engineering-asset/2024/12/59433152-1-7.png" alt="エディタによるiOS環境のエミュレート" /><figcaption>図1.7 エディタによるiOS環境のエミュレート</figcaption></figure>
<p>⓾同様に Web を想定したものです。Mobile のブロックは描画されておらず、Web のコンテンツのみが表示されています(図1.8)。 このように形でコンディショナルレンダリングをし、Web と Mobile で要素を出しわけたページを作成ができます。</p>
<figure style="text-align: center; margin-top: 12px">
<img src="https://storage.googleapis.com/prd-engineering-asset/2024/12/a46ef785-1-8.png" alt="エディタによるWeb環境のエミュレート" /><figcaption>図1.8 エディタによるWeb環境のエミュレート</figcaption></figure>
<p>簡単なサンプルを通して基本的な使い方を説明しました。今回は触れませんが、これ以外にも LOG の設定や API のモック、開発中のモック機能や Dark モード/Light モードへの対応、日本語以外の英語や台湾華語への設定なども行うことができます。</p>
<h2>アークテクチャ</h2>
<p>EGP Pages は以下のようなアーキテクチャで構成されています(図2.1, 図2.2)。</p>
<figure style="text-align: center; margin-top: 12px">
<img src="https://storage.googleapis.com/prd-engineering-asset/2024/12/431cda75-2-1.png" alt="EGP Pages の全体アーキテクチャ" /><figcaption>図2.1 EGP Pages の全体アーキテクチャ</figcaption></figure>
<figure style="text-align: center; margin-top: 12px">
<img src="https://storage.googleapis.com/prd-engineering-asset/2024/12/225cbf51-2-2.png" alt="EGP pages エディタ部分のアーキテクチャ" /><figcaption>図2.2 EGP pages エディタ部分のアーキテクチャ</figcaption></figure>
<p>DB は Cloud Firestore を使用しており、ユーザーが作成したコンポーネントやページの情報を保存しています。ユーザーが作成したコンポーネントやページの情報は全て JSON 形式で保存されており、それを元にページの描画しています。</p>
<p>先ほどの Firestore からロードしたスキーマを元にEGPのエディタの state の初期化 と 編集中の LP を React で描画するための transform を行います。 そして<strong>描画された結果は iframe を通じてエディタに表示されます。</strong> エディタの右側に各種設定項目があり、変更すると Redux の state が更新されて、ます。その結果がリアルタイムで反映されます。</p>
<p><strong>エディタから LP を公開する時には Cloud Run functions 上で LP を SSG することでで HTML と CSS を生成します。生成された静的なページを CDN 経由で配信しています。</strong> こうして配信された HTML が Mobile なら Webview 経由で、Web ならそのまま描画されます。 最終的には静的な HTML を配信しているだけなのでパフォーマンスについても概ね良好です。</p>
<h2>エディタの仕組み</h2>
<p>エディタの仕組みについて、もう少し詳しく説明します。 一見複雑そうに見えますが、実際には各要素は決められたスキーマに沿った単純な JSON データです。先ほどのサンプルを例に挙げて説明します。</p>
<p>まず、Text コンポーネントですがこのエレメントは <code>tagName: "Text"</code> を持ち、<code>props</code> として <code>value</code> や <code>className</code> を持っています。これらの <code>value</code> や <code>className</code> は、エディタの右側にあるパネルから変更できます。</p>
<p>具体的な JSON データは以下のようになります。</p>
<pre><code class="language-json">{
"tlVV3bzegT-Pi59b3sJgQ": {
"id": "tlVV3bzegT-Pi59b3sJgQ",
"name": null,
"tagName": "Text",
"props": {
"value": "Please install the app!",
"className": "text-center text-[0.8125rem] text-[#000000]"
},
"meta": null
}
}</code></pre>
<p>次に、<strong>QR コード</strong> コンポーネントも同様の構造を持っています。ただし、QR コード特有の <code>url</code> や表示に関するプロパティを持っています。 以下がその JSON データです。</p>
<pre><code class="language-json">{
"SsUPPIaLOkuh7zUzgIn-S": {
"id": "SsUPPIaLOkuh7zUzgIn-S",
"name": null,
"tagName": "QrCode",
"props": {
"url": "https://jp.mercari.com",
"margin": "4",
"scale": "4",
"darkColor": "#000000",
"lightColor": "#ffffff",
"className": "self-center"
},
"meta": null
}
}</code></pre>
<p>これらの各エレメントは、それぞれ親子関係を持って構成されています。具体的には、<strong>Layout</strong> コンポーネントがコンテナとなり、その中に <strong>Text</strong> と <strong>QR コード</strong> のコンポーネントを子要素として持っています。</p>
<p>以下がその Layout コンポーネントの JSON データです。</p>
<pre><code class="language-json">{
"id": "W682tXEHtC1eWgvLMvyYx",
"name": "Web Content",
"tagName": "Layout",
"props": {
"className": "flex flex-col",
"children": [
":=element.SsUPPIaLOkuh7zUzgIn-S",
":=element.tlVV3bzegT-Pi59b3sJgQ"
]
},
"meta": null
}</code></pre>
<p><code>children</code> には、このレイアウトの子要素として含まれるコンポーネントの参照が記述されています。<code>":=element.SsUPPIaLOkuh7zUzgIn-S"</code> は先ほどの QR コードコンポーネントを、<code>":=element.tlVV3bzegT-Pi59b3sJgQ"</code> は Text コンポーネントを指しています。 つまり、この Layout コンポーネントは先ほど定義した QR コードと Text のコンポーネントを子要素として持ち、それらを含むコンテナとして機能しています。</p>
<p>これらの JSON データを元に、エディタ内部では React のコンポーネントを再帰的に組み立てていきます。具体的には、<strong>tagName に対応する React のコンポーネントを生成し、props の情報をそれぞれに渡します。</strong>こうして生成されたコンポーネントツリーが、最終的に画面上に描画されます。</p>
<figure style="text-align: center; margin-top: 12px">
<img src="https://storage.googleapis.com/prd-engineering-asset/2024/12/225cbf51-2-2.png" alt="再掲 EGP pages エディタ部分のアーキテクチャ" /><figcaption>図2.2(再掲) EGP pages エディタ部分のアーキテクチャ</figcaption></figure>
<p>ユーザーがエディタでコンポーネントの設定を変更すると、その変更は直ちに対応する JSON データに反映されます。そして、その更新された JSON データを元に React コンポーネントが再レンダリングされ、プレビュー画面にリアルタイムで反映されます。</p>
<h2>LP のスタイリング</h2>
<p>LP のスタイリングの裏側では Tailwind CSS を使っています。 CSS をあまり知らなくてもスタイリングが行えるように UIウィジェットを作成し、そこから Tailwind CSS の<code>className</code>を付与することでスタイリングしています(図2.3)。</p>
<figure style="text-align: center; margin-top: 12px">
<img src="https://storage.googleapis.com/prd-engineering-asset/2024/12/9a6932b8-2-3.png" alt="Tailwind CSSを付与するためのUIウィジェット" /><figcaption>図2.3 UIウィジェット</figcaption></figure>
<p>また最終手段になりますが個々のエレメントに任意の<code>className</code>が付与できるので、細かい調整や少し複雑な表現も Tailwind CSS でできることは全て実現ができるようになっています。</p>
<h2>Native Bridge</h2>
<p>LP の要件によっては Webview と Mobile 間での通信が必要です。例えばボタンをクリックした時にアプリ内の特定の画面を開いたり、特定のアクションを実行するといった動作です。 Webview と Mobile 間で通信するための方法はいくつかあります。その一つとして例えばカスタム URL スキームがあります。しかし、この方法では単方向でしか通信できなかったりセキュリティリスクがあります。</p>
<p>そこでより安全に Webview と Mobile 間で双方向通信を行うために、私たちは <strong>Onix</strong> という内製の Native Bridge を作成しました。これは<strong><a href="https://developer.mozilla.org/en-US/docs/Web/API/Channel_Messaging_API">Channel Messaging API</a>を利用した双方向通信をサポートしています。</strong>加えて端末の OS やバージョンによる Webview webview の API の差分のハンドリングや Channel Messaging API をサポートしていないバージョンの場合 Deep link へ fallback なども行います。 この Native Bridge は現在 iOS と Android のみ対応しているため、今後は Flutter の Webview でも対応する予定です。</p>
<h2>技術的な課題</h2>
<p>これまで一通りエディタについて見てきましたが、もちろん課題もあります。その 1 つとして<strong>このエディタは複数人によるリアルタイム編集ができません。</strong>そのため、予期せぬ上書きが発生してしまうことがありました。 詳しくは<a href="https://engineering.mercari.com/en/blog/entry/20231207-enhancing-collaboration-and-reliability-the-journey-of-version-history-in-our-page-editor-tool/">こちらのブログをご覧ください</a>。</p>
<p>この問題に対しては<a href="https://github.com/yjs/yjs">yjs</a>を使ってリアルタイム編集を実現しようと検証中です。このライブラリは <strong>CRDT(Conflict-free replicated data type)</strong>と呼ばれるデータ構造の JavaScript による実装で多くの同時編集機能があるアプリケーションで使われています。</p>
<h2>運用上の課題</h2>
<p>EGP Pages は<strong>あくまで LP というドメインに特化したエディタ</strong>です。そのため、大量の API コールや条件分岐が存在したり、特別なイベントハンドラが必要な<strong>複雑なものを作ろうとすると限界があります。</strong><br />
LSP や Linter、Formatter、Syntax Highlight、コードジャンプや検索がない VSCode でいわゆる Web Application チックなものを開発しているのを想像してみてください。エンジニアの方には伝わると思いますが、このような制限された環境では複雑な機能を実装したり大規模な物を管理したりすることが非常に困難だということは想像に難くないでしょう。</p>
<p>加えて、<strong>通常のテキストプログラミングと違ってテストを書くことも難しく基本マニュアルで QA しなければなりません。</strong>変更によって予期せぬ問題が発生するリスクが常につきまとっているので、小さな修正でさえ慎重に作業を進める必要があります。</p>
<p>また、これはローコードプラットフォームを作っていると直面することが多い問題ですが、<strong>できることが増えれば増えるほど、より複雑なデザインや機能要件が求められてしまいます。</strong>個々の要件をエンジニアなしで済むような UI や仕組みとして加えていくと、表面上は綺麗でもどんどんコードが複雑化していきます。そのため、どこまでをエディタの機能として提供し、どこからはエンジニアによるカスタマイズが必要かを常に考えていく必要があります。</p>
<p>当初 EGP もエンジニアなしで完結する ノーコード/ローコードプラットフォームのような方向性もありました。しかし今は<strong>エンジニアと非エンジニアが効果的に共同作業できるツールを目指すという方向性になっています。</strong> そのため、引き続き 非エンジニア向けに UI ウィジェットを改善していくのはもちろんエンジニア向けにコンポーネントの Encapsulation や KV pair のコンフィグなどの新しいメカニズムを導入してエンジニアと非エンジニアがよりシームレスに協働できる環境を整え複雑な要件にも対応できるようにしていきます。</p>
<h1>Server Driven UI</h1>
<p>最後に Server Driven UI の実装であり、現在開発中の <strong>EGP Cards</strong> について解説します。<br />
Server Driven UI とはサーバーからデータを一緒に UI の構造も返却してクライアント(Web / Mobile)でレンダリングする手法です。 2021 年に AirBnB が出した<strong>A Deep Dive into Airbnb’s Server-Driven UI System</strong> という記事で有名です。</p>
<p><a href="https://medium.com/airbnb-engineering/a-deep-dive-into-airbnbs-server-driven-ui-system-842244c5f5">A Deep Dive into Airbnb’s Server-Driven UI System</a></p>
<p>この手法には次のようなメリットがあります。</p>
<ul>
<li>サーバー側の変更で UI を更新できるため、アプリのアップデートを待たずにリリースできる</li>
<li>クライアント側の実装を簡素化できる</li>
<li>クロスプラットフォームの一貫性を保ちやすい</li>
<li>Native で描画するため Webview と比べてパフォーマンスが良い</li>
</ul>
<h2>採用背景</h2>
<p>これまで、マーケターは主に別のツールを用いてパーソナライズされたコンテンツを配信していました。しかし、利用可能なコンテンツカードは <strong>3 種類</strong>のみで、そのカスタマイズ性も限定的でした。また、独自コンポーネントを使用しているため実際に配信されるまでコンテンツのプレビューができず<strong>テストが非効率</strong> でした。</p>
<p>これに加えて、<strong>各プラットフォームでの開発工数の増大</strong>が課題となっていました。現在メルカリでは、プロダクトの増加に伴って使用しているプラットフォームが多岐に渡っています。</p>
<ul>
<li>Mercari アプリ:Swift/Kotlin</li>
<li>Mercari Shops、はたらくタブ :アプリ内 WebView</li>
<li>Mercari Hallo:Flutter</li>
<li>Mercari Web:Web</li>
</ul>
<p>当初、この課題に対して EGP Pages の活用を検討しました。しかし、WebView を用いた実装では<strong>モバイルアプリのパフォーマンスに影響を及ぼす可能性があり</strong>、高パフォーマンスを求める要件には適さないという問題がありました。このような状況下で、EGP Pages の利点を活かしつつ課題を克服するために Server Driven UI の採用を決定しました。</p>
<h2>EGP Cards</h2>
<p>私たちは、Server Driven UI の実装として <strong>EGP Cards</strong>と名付けたシステムを開発しました。 まず Mobile と Web 間共用の UI を記述するために<strong>Card UI Protocol</strong>という JSON ベースのプロトコルを定義しました。</p>
<p>この Protocol を用いてクロスプラットフォーム UI を記述します。 一例として、以下のような UI を考えます。</p>
<ul>
<li>上下中央揃え、縦横 200px の Box</li>
<li>Box 中に"This is test"という Text が中央揃えで配置</li>
</ul>
<p>これを Card UI Protocol で記述すると以下のようになります。</p>
<pre><code>{
"id": "root",
"type": "Layout",
// UIの構造を定義
"styles": {
"direction": "row",
"wrap": false,
"mainAxis": "center",
"crossAxis": "center",
"width": {
"preferred": {
"v": 200,
"u": "px"
}
},
"height": {
"preferred": {
"v": 200,
"u": "px"
}
},
"background": {
"light": "#ffffff",
"dark": "#222222"
}
},
"children": [
{
"id": "SCaWRbQdLtwLoiCuEjh8h",
"type": "Text",
// UIの構造を定義
"styles": {
"fontSize": {
"v": 13,
"u": "px"
},
"textAlign": "center"
},
"props": {
"value": "This is Test"
},
"children": []
}
]
}</code></pre>
<p>この JSON をもとにレンダリングエンジンで描画すると以下のような UI になります(図3.1)。</p>
<figure style="text-align: center; margin-top: 12px">
<img src="https://storage.googleapis.com/prd-engineering-asset/2024/12/3bb03a1f-3-1.png" alt="Card UI Protocolで記述したコンポーネントの描画結果" /><figcaption>図3.1 Card UI Protocolで記述したコンポーネントの描画結果</figcaption></figure>
<h2>EGP Cards Editor</h2>
<p>このプロトコルで UI を簡単に作成できるように、EGP Pages のエディタを拡張しました。エディタ上で スタイリングされたコンポーネントを Card UI Protocol に変換して保存できるようにしています(図3.2)。</p>
<figure style="text-align: center; margin-top: 12px">
<img src="https://storage.googleapis.com/prd-engineering-asset/2024/12/54095640-3-2.png" alt="エディタの Cards 向け拡張" /><figcaption>図3.2 エディタの Cards 向け拡張</figcaption></figure>
<p><strong>EGP Pages では HTML ファイルと CSS ファイルを生成していましたが、EGP Cards ではコンポーネントを Card UI Protocol の形式に変換し最終的には JSON ファイルがデータベースに保存されます。</strong></p>
<p>この <code>JSON</code> ファイルをクライアントに送信し、<strong>各プラットフォームでネイティブの UI として描画します。</strong>レンダリングエンジンは各プラットフォームの言語(Swift/Kotlin/Flutter/JavaScript)で実装されています。以下のように、単一の JSON ファイルから各プラットフォームで共通の UI を描画します(図3.3)。</p>
<figure style="text-align: center; margin-top: 12px">
<img src="https://storage.googleapis.com/prd-engineering-asset/2024/12/da00d72f-3-3.png" alt="単一のJSONファイルをもとにしたクロスプラットフォームレンダリング" /><figcaption>図3.3 単一のJSONファイルをもとにしたクロスプラットフォームレンダリング</figcaption></figure>
<p>さらに、この EGP Cards をセグメンテーションサービスと組み合わせることで、ユーザーごとにパーソナライズされたコンテンツの配信や、A/B テスト の実施などユースケースの拡大を図っています(図3.4)。</p>
<figure style="text-align: center; margin-top: 12px">
<img src="https://storage.googleapis.com/prd-engineering-asset/2024/12/4a7ce9d9-3-4.png" alt="セグメンテーションサービスと連携した例" /><figcaption>図3.4 セグメンテーションサービスと連携した例</figcaption></figure>
<h1>まとめ</h1>
<p>本記事では内製マーケティングツール <strong>Engagement Platform(EGP)</strong>と、その中でも特に <strong>EGP Pages</strong> および <strong>Server Driven UI</strong> の実装の <strong>EGP Cards</strong> について紹介しました。 今後も技術的・運用上の課題に取り組みつつ、EGP を進化させていくことでマーケティング活動の効率化と効果向上を目指していきます。</p>
<p>次の記事は @poohさん です。引き続きお楽しみください。</p>
- HPAとVPAによるストリーミング処理のリソース最適化https://engineering.mercari.com/blog/entry/20241209-d304ea2a4c/https://engineering.mercari.com/blog/entry/20241209-d304ea2a4c/<p>こんにちは。メルペイData Platformチームの@siyuan.liuです。 この記事は、Merpay & Mercoin Advent Calendar 2024 の記事です。 Merpay Data P […]</p>
Tue, 10 Dec 2024 10:00:12 GMT<p>こんにちは。メルペイData Platformチームの@siyuan.liuです。<br />
この記事は、<a href="https://engineering.mercari.com/blog/entry/20241125-merpay-mercoin-advent-calendar-2024/">Merpay & Mercoin Advent Calendar 2024</a> の記事です。</p>
<p>Merpay Data Platformチームは、社内共通のデータ処理基盤の開発と運用を担当しており、バッチ処理やリアルタイム処理など、さまざまなPipelineを提供しています。その中でも、リアルタイムで大量のログを収集するStream Pipelineがあります。このStream処理は、Kubernetes上で<a href="https://nightlies.apache.org/flink/flink-kubernetes-operator-docs-main/">Flink Operator</a>を使用し、Flinkを自動的に管理することで実現されています。<br />
しかし、このStream Pipelineでは、トラフィックピーク時にCPUやメモリ不足によるコンシューマーラグが発生するという課題がありました。これを防ぐため、通常は余剰のCPUやメモリを割り当てますが、その結果、コスト増加を招く新たな課題も生じます。<br />
これらの課題を解決するため、HPA(Horizontal Pod Autoscaler)を導入し、外部のDatadogメトリクスにより、トラフィックに応じてサーバー数を自動でスケールアウトさせることで、ピーク時のリソース不足を防ぎました。また、Tortoise VPAを利用してリソースの使用率に基づいて動的なリソース割り当てを行い、無駄なリソース配置を抑制しコスト削減を実現しました。</p>
<p>本記事では、HPAとTortoise VPA(Vertical Pod Autoscaler)を活用したこれらの解決策とその効果について、参考コードと具体的なデータとともに詳しく解説します。</p>
<h2>背景と課題</h2>
<p>本節では、Stream Pipelineの構成、およびStream Pipelineにおけるトラフィックピーク時のリソース不足と過剰なリソース割り当ての課題について解説します。</p>
<h3>Stream Pipelineの構成</h3>
<p>Data Platformチームは、主に社内のPaaS Data Platformの開発と管理を行います。このプラットフォームは、Batch、Stream、CDCなどのデータパイプラインを提供し、各マイクロサービスに分散されたデータへのアクセスと活用を効率化します。その中でStream Pipelineは、各Microserviceからアクセスログとイベントログを収集し、ニアリアルタイムでデータをGoogle Cloud Storageに保存する役割を担っています。また、ログをBigQueryに直接保存することや、他のシステムに転送することも可能です。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/12/be115253-tech_blog.png" alt="Stream PIpelineのアーキテクチャー図" /></p>
<p>本記事で取り上げる課題は、上図の赤枠で示したFlink処理の部分と関わります。この部分は主に、FlinkOperatorが管理するFlinkを使用し、Pub/SubやKafka Topicのデータを消費します。</p>
<h3>課題1:トラフィックピーク時にCPUやメモリ不足によるコンシューマーラグ</h3>
<p>イベント、キャンペーンなどにより、一時的にアクセスログやイベントログのトラフィック量が急増することがあります。このような状況下で、Flink処理に十分なリソースを確保できない場合、Pub/Subのデータを消費しきれず、コンシューマーラグが発生します。これにより、データ同期の遅延や、データの損失につながる可能性があります。</p>
<p>このトラフィックピークによるコンシューマーラグをどのように解決するかは、Data Platformチームにとって重要な課題となっています。</p>
<h3>課題2:過剰なリソース割り当てによるコスト増加</h3>
<p>Flink処理の安定性を確保するため、Data Platformチームでは通常、Flinkに余裕なリソースを割り当てています。たとえば、通常時においてはCPU 0.5 Core、メモリ2048MでFlinkが正常に稼働しますが、トラフィックピークに考慮してCPU 1 Core、メモリ4096Mを割り当てることが一般的です。1つのFlinkに対してこのような対応を行う場合、大きなリソースの無駄にはなりませんが、Data Platformチームが管理するFlinkは200個以上あり、すべてのFlinkにこの方針を適用した結果、リソースの無駄が生じています。</p>
<p>そのため、Flinkのリソースを動的に最適化することが、重要な課題となっています。</p>
<h2>HPAによるトラフィックピーク時の自動スケールアウト</h2>
<p>本節では、HPAを使用したFlinkの自動スケールアウト方法と、FlinkのReactive ModeによるTask Managerリソースを最大限に活用する方法について、参考コードとともに解説します。</p>
<h3>HPAによるFlink TaskManagerの自動スケールアウト</h3>
<p>トラフィックピークによるリソース不足のコンシューマーラグを防ぐため、HPAを導入しました。<a href="https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/">KubernetesのHorizontal Pod Autoscaler(HPA)</a>は、リソース使用量やメトリクスに基づいてPodのレプリカ数を自動的に調整する仕組みです。トラフィックのピーク時にはスケールアウトし、需要が低いときにはスケールダウンすることが可能です。また、Flink OperatorはHPAをサポートしており、HPAの設定でscaleTargetRefのkindをFlinkDeploymentに指定することで、TaskManagerのスケールアウト・スケールダウンを自動的に制御できます。</p>
<p>HPAで使用する外部のDatadogメトリクス oldest_unacked_message_ageは、サブスクライバーによって確認応答(ACK)されていない、サブスクリプションのバックログ内の最も古いメッセージの経過時間(秒単位)の指標です。トラフィックピーク時にFlinkがPub/Subのデータを消費しきれない場合、このメトリクスの値が増加します。このメトリクスをHPAの閾値として設定すれば、トラフィックに応じてFlinkのTaskManagerの数を自動的に調整できます。</p>
<p>参考コードは以下になります。</p>
<pre><code class="language-yaml">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</code></pre>
<p>この設定により、トラフィックピーク時にPub/Subの消費が7分(420秒)以上の遅延となった場合、FlinkのTaskManagerの数が自動的に増加され、リソース不足を防止します。また、トラフィックピークが収まった際には、TaskManagerの数が元の状態まで自動的にスケールダウンされ、リソースの無駄遣いを防ぎます。</p>
<h3>Flink Reactive ModeによるTaskManagerのリソース利用率の最適化</h3>
<p>トラフィックピーク時には、TaskManagerのリソースが自動的に増加しますが、それに伴ってJobの並列度(parallelism)を調整しないと、スケールアウトしたTaskManagerのリソースが十分に利用されません。<br />
HPAによって増加したTask Managerのリソースを効率的に利用するため、Flink の <a href="https://nightlies.apache.org/flink/flink-docs-release-1.19/docs/deployment/elastic_scaling/#reactive-mode">Reactive Mode</a> を導入しました。Reactive Modeでは、Jobの並列度が可能な最大値に設定され、Cluster内のTaskManagerのリソースを可能な限り利用します。これにより、トラフィックピーク時に TaskManager のリソースを最大限に活用し、効率的なリソース管理が可能となります。</p>
<p>ただし、Reactive Mode は Standalone Mode でのみ使用可能なため、利用時にはご注意ください。</p>
<p>FlinkのReactive Modeを有効化する参考コードは以下になります。</p>
<pre><code class="language-yaml">metadata:
name: flink
spec:
image: flink
imagePullPolicy: IfNotPresent
mode: standalone
flinkVersion: v1_18
flinkConfiguration:
scheduler-mode: reactive
</code></pre>
<h2>Tortoise VPAによる過剰なリソース割り当ての最適化</h2>
<p>本節では、過剰なリソース割り当ての課題を解決するため、Tortoiseの概念、Tortoise VPAのみを導入する理由、およびFlink Operatorが管理するFlinkへのTortoise VPA導入時の注意点を解説します。最後に、これらの理由と注意点を踏まえ、Tortoise VPA導入の参考コードを示します。</p>
<h3>Tortoiseとは</h3>
<p>Tortoiseは、過去のリソースの使用量や過去のレプリカの数を記録しており、それを元にHPAやVPAを最適化し続ける仕組みです。この中で、Tortoise Vertical Pod Autoscaler (VPA) はCPUやメモリのResource Request/Limitを最適化する役割を担います。これにより、負荷が低下した際に、不要なリソース割り当てを削減することが可能になります。Tortoise の詳細については、以下の公開記事をご参照ください。<br />
「<a href="https://engineering.mercari.com/blog/entry/20240206-3a12bb1288/">人間によるKubernetesリソース最適化の”諦め”とそこに見るリクガメの可能性</a>」</p>
<h3>Tortoise VPAのみを導入する理由</h3>
<p>TortoiseはHPAとVPAの両方をサポートしていますが、DataplatformチームではTortoise VPAのみを使用しています。その理由は二つあります。<br />
一つ目は、DataplatformチームではすでにKubernetes HPAを利用しているためです。既存のKubernetes HPAは外部のDatadogメトリクスに基づいてリソースを調整していますが、Tortoise HPAは外部のメトリクスによるリソース調整をサポートしていません。<br />
2つ目は、FlinkOperatorを利用しているためです。FlinkOperatorはカスタムリソースを用いてFlinkを管理していますが、Tortoise HPAはカスタムリソースをサポートしていません。一方、FlinkOperatorはKubernetes HPAをサポートしているため、既存のKubernetes HPAは問題なく利用できます。<br />
以上の理由から、Data PlatformチームはTortoise HPAを導入せず、Tortoise VPAのみを使用しています。</p>
<h3>FlinkへのTortoise VPA導入時の注意点</h3>
<p>Flink Operatorが管理するFlinkにTortoise VPAを導入する際には、FlinkDeploymentをTortoiseに管理させるのではなく、JobManagerとTaskManagerのDeploymentを分け、それぞれのTortoiseに管理させる必要があります。JobManagerとTaskManagerは異なる役割を持ち、それに応じて必要なリソースも異なります。しかし、Tortoiseはカスタムリソースをサポートしていないため、Flink OperatorのFlinkDeploymentをTortoiseに管理させると、JobManagerとTaskManagerが同一のリソースとして扱われてしまい、適切な推奨値が算出されなくなります。<br />
また、JobManagerのリソースがTortoiseによって過度にスケールダウンされると、リソース不足によるダウンタイムが発生する可能性があります。そのため、minAllocatedResource(リソース割り当ての最低値)を設定することが重要です</p>
<h3>参考コード</h3>
<p>上記の理由と注意点を踏まえ、Tortoise VPAによってリソースを調整する設定は以下になります。</p>
<pre><code class="language-yaml">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
</code></pre>
<h2>結果と効果</h2>
<p>本節では、具体的なデータをもとに、改善の結果とその効果について説明します。</p>
<h3>1. HPAによるトラフィックピーク時の安定性の向上</h3>
<p>HPA を導入した結果、トラフィックピークの際に Flink の Pod 数が自動的にスケールアウトされることで、リソース不足によるダウンタイムが発生しなくなりました。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/12/3127d4da-tech_blog2.png" alt="未確認メッセージの経過時間とレプリカ数の対照図" /></p>
<p>上記の図は、Mercariの検索プロジェクトにおけるoldest_unacked_message_age(最も古い未確認応答メッセージの経過時間)とdeployment_replica(レプリカ数)のメトリクスを対照したものです。10:35 ごろ、 oldest_unacked_message_age メトリクスが 増加する直前に、Flink の Pod 数も増加していることが確認されました。また、同日の 16:55 ごろにピークが収束するとともに、Flink の Pod 数も自動的にスケールダウンされ、安定的なリソース利用が実現されました。</p>
<p>この動的スケーリングの結果、従来はリソース不足で発生していたコンシューマーラグはほぼ 0% にまで削減され、システム全体の安定性が大幅に向上しました。</p>
<h3>2. VPAによるコストの削減</h3>
<p>2024年5月にTortoise VPA を導入した結果、通常時のリソース使用量に基づいた最適な resource request と limit が自動的に設定され、不要なリソースの割り当てが大幅に削減されました。<br />
その結果、月平均で 100万円以上、年間で 1200万円以上のコスト削減を実現しました。</p>
<h2>まとめ</h2>
<p>Data Platformチームが管理するStream Pipelineにおいて、トラフィックピーク時のリソース不足によるコンシューマーラグと、過剰なリソース割り当てによるコスト増加という2つの課題とその解決策を解説しました。HPAを活用した自動スケーリングにより、Flinkのスケールアウト・スケールダウンを実現し、Stream Pipeline全体の安定性が向上しました。また、Tortoise VPAを用いることで、リソースの効率的な割り当てを行い、無駄なリソース配置を抑制し、コストの削減を実現しました。<br />
今後もData Platformチームでは、Stream Pipelineのリソース最適化をさらに進め、同様の対策をCDC PipelineのKafka Connectにも取り込みたいと考えています。</p>
<p>次の記事は @togamiさん です。引き続きお楽しみください。</p>
- Acceptance criteria: QA’s quality boosthttps://engineering.mercari.com/blog/entry/20241207-mercari-hallo-2024/https://engineering.mercari.com/blog/entry/20241207-mercari-hallo-2024/<p>こんにちは。メルカリのQA Engineering managerの@____rina____です。 この記事は、連載:メルカリ ハロ 開発の裏側 – Flutterと支える技術 –の1回目と、 […]</p>
Sat, 07 Dec 2024 08:00:24 GMT<p>こんにちは。メルカリのQA Engineering managerの<a href="https://engineering.mercari.com/blog/author/underscore42rina/">@____rina____</a>です。</p>
<p>この記事は、<a href="https://engineering.mercari.com/blog/entry/20241129-mercari-hallo-2024/">連載:メルカリ ハロ 開発の裏側 – Flutterと支える技術 –</a>の1回目と、<br />
<a href="https://engineering.mercari.com/blog/entry/20241125-mercari-advent-calendar-2024/">Mercari Advent Calendar 2024</a> の3日目の記事です。</p>
<p>先日、11月15日に開催された<a href="https://tokyotestfest.com/" title="Tokyo Test Fest">Tokyo Test Fest</a>というイベントで、"Acceptance criteria: QA’s quality boost"というタイトルで発表を行いました。このセッションでは、Flutterに限らず、開発プロセス全体においてAcceptance criteriaをQAエンジニアが書くことの重要性と、それを開発チーム全員で同期レビューすることの大切さについてお話ししました。</p>
<p>Acceptance criteriaは、開発チームの協業を円滑に進めるための重要な要素です。これを正確に定義し、チーム全体が共有することで、品質を大いに向上させることができます。今回の発表では、私たちのプロジェクトでの具体的な事例をもとに、このプロセスの効果についても言及しました。</p>
<p>また、以前に投稿したAcceptance Criteriaに関する記事も、ぜひあわせてご覧ください(ブログは日本語のみです)。<br />
以前の記事は<a href="https://engineering.mercari.com/blog/entry/20220912-cf3da857e5/" title="こちら">こちら</a></p>
<p>今回はこの発表内容について詳細をお届けします。以下に書き起こしを掲載します。</p>
<h2>Acceptance criteria: QA’s quality boost</h2>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/12/2bf07150-title1.png" alt="" /></p>
<p>東京テストフェストのみなさん、こんにちは!私はリナです。本日お越しいただきありがとうございます。これから、「Acceptance criteria:QA’s quality boost」というテーマでプレゼンテーションを始めます。</p>
<h3>Our QA Team’s initiative</h3>
<p>さて、今日は私たちが行っている取り組みについてお話しします。具体的には、Acceptance Criteria(以下、AC)にテストケースを記載し、それを開発チーム全員でレビューするという方法です。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/12/0da24a85-page2.png" alt="" /><br />
ACは、スクラムやアジャイル開発でよく使用されます。ACを導入する前は、ユーザーストーリーとテストケースが分かれていました。これにより、とくにテストの際に誤解が生じることがありました。</p>
<p>この新しいプロセスは、開発チーム全体に役立ちます!プロダクトマネージャー(以下、PM)、デザイナー、エンジニア、そしてQAエンジニアの皆が一緒により良く作業できるようになります。各チームメンバーのメリットを見てみましょう。</p>
<p> 例えば、PMは、仕様の確認が容易になり、抜け漏れなく開発を進められるようになりました。以前は、テスト実施の段階で初めて仕様の矛盾に気づくことがありましたが、この活動を通して、早い段階で修正できるようになり、手戻りが減りました。<br />
エンジニアは、フロントエンドとバックエンドの実装方針を事前にすり合わせることができ、スムーズな開発が可能になりました。<br />
また、具体的な文言や表示方法をその場で確認することで、デザイナーからのフィードバックもリアルタイムに反映できるようになり、より質の高いプロダクト開発に繋がっています。<br />
QAエンジニアにとって、テストデータの作成方法を共有し、テストを実行することは、テストフェーズでの作業改善に役立ちました。以前はテスト準備中にテストデータの作成についてエンジニアに相談する必要がありました。この新しいアプローチにより、テストの実行のしやすさに基づいて開発の順序について話し合うことが容易になりました。<br />
チーム全体としては、複雑な開発手順でも、全員が共通認識を持って開発を進められるようになり、コミュニケーションコストが削減されました。また、複数プロジェクトが同時進行する際も、それぞれの進捗状況や依存関係を把握しやすくなるため、混乱することなく、スムーズにプロジェクトを進めることができています。それでは、この取り組みをどのように実践しているのか、詳しく見ていきましょう。</p>
<h3>3つの取り組み</h3>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/12/ad59fe68-page3.png" alt="" /></p>
<p>実施したことは3つです。</p>
<p>まず、テストケースをACに記載するようにしました。次に、レビュー方法を非同期レビューから同期レビューに変更しました。最後に、レビューする人を開発チーム全員としました。</p>
<p>ところで、みなさんは、普段テストケースをどこに保存していますか? また、誰がどのように活用していますか?<br />
例えば…テスト管理ツールを使ったり、Google スプレッドシートや Excelで作成し、共有しているのではないでしょうか?このように、テストケースは様々な場所に保存され、活用方法も様々だと思いますが、多くの場合、QAチーム内でのみ参照され、他の開発メンバーはあまり活用できていないのではないでしょうか?テストケースの価値はQAエンジニアのみなさんは価値を理解していると思います。これを少数の人にのみ提供しているのはもったいないと思いました。</p>
<p> 以前は、ユーザーストーリーとテストケースの関連性が弱く、重要なテストを見落とすリスクが高まり、開発プロセスの後半で手戻りが発生することがありました。テストケースはまるで隠された宝の地図のようでした。皆が利用可能であったにも関わらず、その価値は活用されていませんでした。チームはユーザーストーリー(島)に問題を抱えており、必要な助けがすぐそこにあることに気づいていませんでした。</p>
<p> 以前は、適切なテストを見つけることは、多くの島が描かれた宝の地図を使うようなものでした。各島には宝がありましたが、地図で各島に何があるのかを探すのに時間がかかりました。今では、各島に標識があります!その標識がACです。それは、各ユーザーストーリーにどのテストが必要かを正確に教えてくれます。<br />
例えば、標識は製品が何をすべきか、何をしてはいけないか、そしてどのようにテストするかを示してくれます。これにより、誰もが理解しやすく、質の高い製品を作り上げやすくなります。</p>
<h3>Acceptance criteriaの例</h3>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/12/dbec7df9-page4.png" alt="" /></p>
<p> このスライドは、私たちのACの例を示しています。私たちは、テスト対象、テストの条件、そして期待される結果を明確に定義します。<br />
例えば、1行目では、テスト対象はタイトルとラベルの表示です。タイトルとラベルの両方に「ログイン」と表示されることを期待します。<br />
2行目と3行目では、機能フラグの条件に基づいて、画面の期待される動作を定義します。<br />
最後に、iOSとAndroidの両方で同じように動作することを確認するために、テストを行います。</p>
<p> 明確な道標(AC)を設けることで、私たちのチームは開発をより効果的に進めることができます。その結果、チームのコラボレーションが改善され、エラーや手戻りが減り、高品質なプロダクトをより早く提供できるようになりました。チーム全員が目標と、そこに到達する方法を理解し、共に取り組んでいます。</p>
<h3>効果的なレビューのシンプルなステップ</h3>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/12/1acf828e-page5.png" alt="" /><br />
次に、私たちが実践しているレビュープロセスについてお話しします。このレビューは、開発チーム全体で品質に対する共通認識を醸成し、高品質なプロダクト開発を実現する上で非常に重要な役割を担っています。ACを中心に据え、テストケースの内容を全員で確認することで、認識のズレや手戻りを防ぎ、開発効率を向上させることができるのです。</p>
<p> 次にレビューの方法についてお話します。とてもシンプルです!3つのステップがあります。</p>
<p> 1つ目は、声に出して読み上げます。1人がそれぞれのACを声に出して読みます。各ユーザーストーリーに対するACとテストケースを確認できます。読み手はそれぞれの項目について簡単に説明します。<br />
2つ目は、質問をすることです。読み上げの後、全員が質問できます。エンジニア、QAエンジニア、PM、デザイナー、誰でもです。様々な視点を持つことが重要です。例えば、<br />
「このテストではどんなデータを使うの?」や「この部分は本当に必要?」あるいは、「ユーザーはこれを理解できるかな?」といった質問です。私たちは良い議論をすることができるようになります。<br />
最後は、確認です。全員がACを理解していることを確認します。これでレビューは終了です。これらのレビューは、チームが最初から品質について合意するのに役立ちます。シンプルな3つのステップで、誰でも実行することができます。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/12/6251bd1b-page6.png" alt="" /><br />
なぜこの新しいレビュープロセスが効果的なのかについてお話します。私たちは、スペックレビューで要件を確認しています。しかし、その段階では、エンジニアとQAエンジニアは自身の領域に対して詳細まで理解することが困難なこともあります。それはぼやけた写真を見ているようなものです。今回のプロセスでは、スペックレビューの後、エンジニアは設計ドキュメントを作成し、QAエンジニアはACを作成し、テストを設計します。ここで初めて、各機能とユーザーストーリーに対する深い理解が得られました。<br />
私たちの新しいレビュープロセスは、この詳細な検討の後に行われます。全員が要件について高い解像度の理解を持ってレビュー会議に参加します。これにより、より集中した生産的な議論が可能になります。共通の明確なビジョンを持って、潜在的な問題を特定し、詳細を洗練させることができます。プロセスの後半、全員が詳細な理解を深めた後にレビューを行うことで、整合性を確保し、誤解を最小限に抑え、最終的に品質の向上と手戻りの削減につながります。<br />
このレビュー方式は、特別なスキルや経験がなくても、誰でも効果を実感できるのでしょうか?</p>
<p>私たちは、様々なスキルレベルのメンバーでこのレビュー方式を試しました。私自身の経験では、QAエンジニアとしてスクラムチームに所属していた時に初めてこの取り組みを始めました。当時は私一人で実施していましたが、チーム全体で効果を実感できました。<br />
現在、私以外のQAエンジニアたちもこの取り組みを実施しています。もちろん、最初は戸惑うメンバーもいましたが、今ではスムーズにレビューを進めることができています。<br />
なぜ、異なるスキルレベルのメンバーでも効果を実感できているのでしょうか?成功の鍵は、"共通認識" です。ACにテストケースを記載することで、エンジニア、QAエンジニア、PM、デザイナーなど、チーム全員が同じ情報を見て、同じレベルで議論できるようになります。</p>
<p> もちろん、もっと解像度をあげたいという改善すべき点もあります。しかし、このレビュー方式は、チーム全体の品質意識を高め、開発プロセスを改善するための大きな可能性を秘めていると確信しています。<br />
本日は、ACにテストケースを組み込むことで、チーム全体の品質意識を高め、開発プロセスを改善する方法についてお話しました。テストケースをACに集約することで、誰でも簡単にテスト内容を確認できるようになりました。それによって開発チーム全員での同期的なレビューが可能になり、議論も活発化し、プロダクトに対する深い共通理解を得られるようになります。これらの変更によって、開発チーム全体が、開発初期段階から品質に関する共通認識を持ち、認識齟齬や手戻りを防ぐことができるようになりました。</p>
<p>私たちは、この取り組みをさらに発展させ、より効果的な品質保証活動を目指していきます。<br />
みなさんも、ぜひこの方法を試してみて、チームで品質向上に取り組んでみてください!<br />
ご清聴ありがとうございました。何かご質問があれば、お気軽にお尋ねください。</p>
<p>この記事の内容が、みなさまのプロジェクトや技術的探求に貢献できたなら幸いです。引き続き<a href="https://engineering.mercari.com/blog/entry/20241129-mercari-hallo-2024/" title="メルカリ ハロ 開発の裏側 - Flutterと支える技術 -">メルカリ ハロ 開発の裏側 – Flutterと支える技術 –</a>シリーズを通じて、私たちの技術的知見や経験を共有していきますので、どうぞご期待ください。また、<a href="https://engineering.mercari.com/blog/entry/20241125-mercari-advent-calendar-2024/" title="Mercari Advent Calendar 2024">Mercari Advent Calendar 2024</a>の他の記事もぜひチェックしてみてください。それでは、次回の記事でお会いしましょう!</p>
- E2E Testを用いたマイクロサービスアーキテクチャでのUser Journey SLOの継続的最新化https://engineering.mercari.com/blog/entry/20241204-keeping-user-journey-slos-up-to-date-with-e2e-testing-in-a-microservices-architecture/https://engineering.mercari.com/blog/entry/20241204-keeping-user-journey-slos-up-to-date-with-e2e-testing-in-a-microservices-architecture/<p>こんにちは。メルカリのSite Reliability Engineer (SRE)の@yakenjiです。 この記事は、Mercari Advent Calendar 2024 の2日目の記事です。 私たちメルカリのS […]</p>
Fri, 06 Dec 2024 11:00:10 GMT<p>こんにちは。メルカリのSite Reliability Engineer (SRE)の<a href="https://www.linkedin.com/in/kenji-tsuchiya-5395a518a/">@yakenji</a>です。<br />
この記事は、<a href="https://engineering.mercari.com/blog/entry/20241125-mercari-advent-calendar-2024/">Mercari Advent Calendar 2024</a> の2日目の記事です。</p>
<p>私たちメルカリのSREは、コアプロダクトであるフリマアプリ「メルカリ」の信頼性を維持・向上させるために、プロダクトのAvailability(可用性)とLatency(性能)を測定しています。また、それらに対してService Level Objective(SLO)を設定した上で、SLOを満たしているかや、一時的な障害などによりAvailabilityとLatencyが悪化していないかの監視を行っています。</p>
<p>その方法としてCritical User Journey (CUJ)に基づいたSLOを運用しています。今回、このSLOを見直し、以下を実現するSLOの再定義に取り組みました。</p>
<ol>
<li>CUJの定義の明確化</li>
<li>各CUJに対して1対1となるSLIの定義</li>
<li>各CUJとSLOのメンテナンスの自動化</li>
<li>障害時の各CUJの挙動のダッシュボードによる可視化</li>
</ol>
<p>本取り組みによりSLOのメンテナンスにかかる時間を<strong>99%削減</strong>するとともに、障害検知後に<strong>影響範囲の特定にかかる時間をゼロ</strong>にすることを実現しました。</p>
<p>本記事では、上記の取り組みである User Journey SLO について、見直しするに至った背景と上記4項目の詳細、特にE2E Testを用いた自動化によるSLOの継続的最新化とその活用の取り組みを共有します。</p>
<h2>現状の課題意識</h2>
<p>本題に入る前に、メルカリにおける2種類のSLOと現状の課題意識を共有します。この章を通じて、なぜ私たちがUser Journey SLOの取り組みを始めたのか、何を目指したのかを知っていただけたら幸いです。</p>
<h3>マイクロサービスごとのSLOとその課題</h3>
<p>メルカリでは、バックエンドにマイクロサービスアーキテクチャを採用しています。例えば、ユーザー情報はユーザーサービス、商品情報はアイテムサービスというように、あるドメインごとにマイクロサービスとして独立しています(上記は一例であり実際とは異なります)。各サービスには必ずオーナーとなるチームが存在し、独立して開発・運用を行っています。各チームは自らの管理するサービスにSLOを設定したうえで、その目標を下回らないように開発フローを回すことが義務付けられています。また、このSLOをベースとしたモニターを作成することで、開発チームは管理するサービスの障害をアラートとして受け取り障害対応も行います。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/12/16f77d8f-01_microservices.png" alt="" /></p>
<p>開発チームがマイクロサービスごとに独立した開発・運用を行う上で、サービスごとのSLOを定義することは必要なアプローチと言えます。一方でサービスごとのSLOだけではいくつかの課題があります。その一つが、お客さま視点でのメルカリというプロダクトの信頼性を評価することが難しいということです。</p>
<p>各マイクロサービスはあるドメインの機能のみを扱うサービスです。お客さまが自身のアカウントの”ユーザー情報を編集する”ようなドメインに閉じたシナリオを想定した場合、関係するサービスはユーザーサービスだけで済むでしょう。この場合、”ユーザー情報を編集する” SLOの達成度合いはユーザーサービスのそれを用いることができるかもしれません。一方で、”購入された商品を発送する”のようなシナリオの場合、関係するサービスは複数にわたります。この場合には各サービスの達成度合いを単純に用いることはできません。</p>
<p>さらに、あるシナリオを想定した場合に実際に使用されるAPIは各サービスのごく一部でしかありませんし、サービスの開発チームはどのAPIがどのシナリオ・ページで使用されているか完全に把握することも困難です(APIは基本的に汎用的なものを用意し各ページで同じものを使用します)。反対にフロントエンドの開発者も、どのサービスにアクセスするかを厳密に意識することは通常ありません。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/12/33734adf-02_3services-e1733301620126.png" alt="" /></p>
<p>以上の背景から、マイクロサービスごとのSLOだけでは”購入された商品を発送できるかどうか”のようなお客さまが実際に感じる信頼性を評価できません。例えば上記の例でサービスA/B/CそれぞれのAvailabilityはSLOを満たしていたとしても、お客さま目線でのAvailabilityは想定よりも低いということが考えられます。また、障害対応の観点から見てみると、あるサービスAのアラートがトリガーされても、実際のお客さまに対してどのような影響が出ているのかを判断することもできません。これは障害の優先度の判断や、影響したお客さまへの対応時に問題になります。</p>
<h3>SREにおけるSLO</h3>
<p>上記の課題を解決するため、SREでは以前よりマイクロサービスごとのSLO以外の独自のSLOとして、Critical User Journey (CUJ)に基づいた私たちのプロダクトであるフリマサービス全体のモニタリングを行ってきました(CUJとはお客さまがプロダクトを利用する際に頻繁に行われる一連のシナリオのうち特に重要なものを指します)。 一方で、以下のような課題もありました。</p>
<ol>
<li><strong>定義が不明瞭:</strong><br />
CUJの定義・CUJに関連するAPIの根拠などが記録されておらず、新たにCUJを追加することやメンテナンスが難しい</li>
<li><strong>CUJに対して1対多のSLOが存在:</strong><br />
関連APIのSLOを直接モニタリングしているため、関連APIが複数の場合に複数のSLOが存在し、お客さまが感じる信頼性の評価が難しい</li>
<li><strong>Updateが困難:</strong><br />
機能開発により関連APIは高頻度に変化し続けるが、人力での調査が必要なためメンテナンスコストが高く、最新の状態を維持できない</li>
<li><strong>SLOが悪化した場合の挙動が不明:</strong><br />
課題1・2と関連してSLOが未達となった場合に、お客さまにどのような影響が出るのかが明確化されていないため、対応の優先度の意思決定やSRE以外が使用することが難しい</li>
</ol>
<p>特に課題3から、2021年ごろに設定してから十分なメンテナンスをすることができず、モニタリング対象のAPIの過不足がある可能性がありました。また、SRE以外も有効に使用することでメルカリ全体の信頼性向上やインシデント対応の改善に繋げることも背景として1から再構築することを決めました。</p>
<h2>User Journey SLOの概略</h2>
<p>まず、課題1の”定義が不明瞭”な点と課題2の”CUJに対して1対多のSLOが存在”する点に関連して、User Journey SLOにおいてどのようにCUJを定義・管理したか、どのようなSevice Level Indicaor (SLI)を定義したかを紹介します。</p>
<h3>Critical User Journey (CUJ)の定義</h3>
<p>User Journey SLOではこれまでのCUJを踏襲し、粒度を商品出品・商品購入・商品検索のように定めました。具体的な各CUJについても再検討を行い、大小含めて約40のCUJを定義しました。課題1を改善するため、すべてのCUJを画面操作とそれによる画面遷移という形で定義し、以下のような遷移図としてドキュメント化しました。さらに、各画面でAvailableな状態も定義し、それを満たしている場合をそのCUJがAvailable・満たしていない場合はUnavailableとしました(基本的に各CUJのコア機能が提供できればAvailableとし、サジェストなど動作しなくてもコア機能に影響しないサブ機能の動作は無視することにしています)。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/12/2bf78c3d-03_lite-listing.png" alt="" /></p>
<h3>SLIの決定</h3>
<p>課題2を改善するため、定義したCUJを基に、各CUJのAvailablityとLatencyのSLIがそれぞれ1対1になるようなSLIを定義します。SLIはあくまでObservebilityツールで取得可能なメトリクスなどを使用して測定する必要があります。メルカリではBFFなどを用いたお客さまの1操作 = 単一のAPIコールのような構成ではなく、基本的に1操作で複数のAPIコールが発生します。</p>
<p>CUJの各画面が成功したかどうか直接的に測定できれば良いですが、現在そのような仕組みを持っていません。新たな仕組みを導入し直接的な測定を行うことも考えられますが、約40のCUJを全てカバーしつつ、iOS・Android・Web全てのクライアントでアプリを改修することは、とてもエンジニアリングコストが高く現実的ではありません。また、APMツールのRealtime User Monitoring (RUM)から取得することも検討しましたが、サンプリングレートやコスト、実現性の観点から現時点ではこれも困難と判断しました。</p>
<p>そこで、CUJの間に実行されるAPIを関連するAPIとして、そのAPIの成功率などのメトリクスを使用します。ただし、各操作で発生するAPIコールの中には(1) 失敗したらCUJがUnavailableになるもの、(2)失敗してもCUJがAvailableになるものの2つに大別できます。User Journey SLOではSLIをより正確かつロバストなものにするため、(1)に該当するAPIのみを”クリティカルな API”と定義しSLIの計算に使用することにしました。 </p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/12/2e8c670a-04_critical_api-e1733301725194.png" alt="" /></p>
<p>クリティカルなAPIのメトリクスを使用して、以下の式でCUJのAvailability・LatencyのSLIをそれぞれ一意に定まるように定義しました。</p>
<ul>
<li><strong>Availability:</strong><br />
複数のクリティカルなAPIの成功率の乗算をCUJの成功率とする<br />
クリティカルなAPI A,Bの成功率を <em>S<sub>A</sub></em>, <em>S<sub>B</sub></em> とするとCUJの成功率 <em>S<sub>CUJ</sub></em> は以下で計算<br />
<em>S<sub>CUJ</sub></em> = <em>S<sub>A</sub></em> × <em>S<sub>B</sub></em></li>
<li><strong>Latency:</strong><br />
複数のクリティカルなAPIの目標レスポンスタイムの達成率のうち最も低い達成率をCUJの達成率とする<br />
クリティカルなAPI A,Bの目標達成率を <em>A<sub>A</sub></em>, <em>A<sub>B</sub></em> とするとCUJの成功率 <em>A<sub>CUJ</sub></em> は以下で計算<br />
<em>A<sub>CUJ</sub></em> = min(<em>A<sub>A</sub></em>, <em>A<sub>B</sub></em>)</li>
</ul>
<h3>クリティカルなAPIの抽出</h3>
<p>上記をSLIとして使用するためには、各CUJごとにクリティカルなAPIを抽出する必要があります。コードの静的解析などの方法も考えられますが、現実的に実現できるか等を勘案した結果、実際のアプリケーションを用いる形で以下の手順で抽出を行いました。</p>
<ol>
<li>開発アプリケーションと開発環境の中間にプロキシを設置した上でCUJに基いてアプリを実際に操作、実行されたAPIを記録</li>
<li>プロキシで各APIのレスポンスが500エラーを返すように障害注入を設定してアプリを再度操作、CUJの基準を用いてAvailableかどうかを検証</li>
</ol>
<p>※クライアントとしては、メルカリのクライアントとして最も使用されているiOS版メルカリを使用しました。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/12/9c0d93b7-05_proxy-e1733301842561.png" alt="" /></p>
<p>通常クライアントアプリ-サーバー間の通信は暗号化されています。今回は暗号化された状態でも通信の確認とレスポンスの書き換えが可能なプロキシを選定しました。Webインターフェースを通じたインタラクティブな操作ができること、アドオンを開発することで必要な機能を追加できる点から、OSSの <a href="https://mitmproxy.org/">mitmproxy</a> を採用しました。</p>
<p>これらの取り組みにより障害検知はCUJとともに通知されるため、障害検知後の影響範囲の特定にかかる時間がゼロになり、対応優先度の決定を瞬時に行うことを実現します。</p>
<h2>iOS E2E Testを用いた継続的最新化と可視化</h2>
<p>次に、課題3である”Updateが困難”なことを改善するE2E Testを用いたクリティカルなAPIを最新に維持する方法と、課題4である”SLOが悪化した場合の挙動が不明”な点を改善する障害時のアプリの挙動を可視化する方法を紹介します。</p>
<h3>自動化の必要性</h3>
<p>メルカリのiOSアプリは1ヶ月に複数回リリースされています。また、<a href="https://engineering.mercari.com/blog/entry/20231211-large-team-development-at-mercari-ios/">トランクベース開発</a> によりフィーチャーフラグを使用してアプリのアップデートなしで新機能のリリースを行うことも可能です。これら全てのアプリの変更をSREが把握することは困難です。また、手動では高頻度のクリティカルなAPIの調査も困難です。結果として知らない間にクリティカルなAPIが変わり、必要なAPIをモニタリングできなかったり、不必要なAPIを過剰にモニタリングしてしまう事態につながります。そのため、アプリの変更に追従してクリティカルなAPIを定期的に更新するためには更新プロセスの自動化が必要です。</p>
<h3>iOS E2E Testを用いた自動化</h3>
<p>メルカリでは既にXCTestフレームワークを用いて<a href="https://engineering.mercari.com/blog/entry/2018-08-07-123000/">iOSアプリのE2Eテストを自動化</a>しています。この既存資産を活用してクリティカルなAPIの抽出を自動化することにしました。</p>
<p>具体的にはまず、XCTestでCUJをテストケースとしてシミュレータで実行可能とします。さらにこのテストケースに対して、CUJで定義したAvailableな状態かを検証するアサーションを追加します。これによりXCTestで実行したCUJがAvailableな状態だったかどうかを自動で判別可能な仕組みが整いました。また、テストケースはアプリと同じリポジトリでバージョン管理されます。</p>
<p>XCTestとは別に、前章で用いたプロキシに対して”プロキシで記録したAPIリストの取得”と”任意のAPIに対する障害注入”を操作API経由で実行可能にするアドオンを開発しました。このアドオンにより、XCTestのテストケースやスクリプトからプロキシの操作が可能になりました。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/12/82b2bbc5-06_proxy_addon-e1733301902654.png" alt="" /></p>
<p>上記のXCTestの実行とアドオン経由でのプロキシの操作をスクリプト化することによって、前章で示したクリティカルなAPIの抽出を自動で実行します。同時に、実行されたAPIがクリティカルなAPIかどうかの結果と障害注入時のアプリのスクリーンショットをそれぞれBigQueryとGoogle Cloud Strage (GCS)に記録します。</p>
<p>BigQueryに記録したテスト結果はIDで管理し前回実行時との差分を比較できるようになっています。また、APMに作成するSLO・Monitor・Dashboardの定義はUser Journey SLO用に開発したTerraform moduleを用いて行います。これによりクリティカルなAPIを定義するだけで、差分の適用や新しいCUJの追加を行うことが可能になりました。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/12/45bc74df-07_workflow-e1733301956779.png" alt="" /></p>
<p>上記の自動化により以下を実現しました。</p>
<ul>
<li>コードメンテナンス以外の作業をほぼ全て自動化</li>
<li>テストケースとアプリの変更を同一のリポジトリでバージョン管理</li>
<li>テスト結果をID管理し差分を効率的にAPMに反映</li>
</ul>
<p>最終的なテストケース数は約40のCUJに対して約60となりました。手動実行では困難な数のテストケースを自動化により効率的に運用することに成功しました。また、約60のテストケースを手動で実行してSLOのメンテナンスを行った場合に比べて、自動化によりメンテナンスにかかる時間を99%削減しました。</p>
<h3>ダッシュボードによる結果の可視化</h3>
<p>User Journey SLOの最終的に目指す姿の一つは、SRE以外も障害対応やお客さま対応で使用できるようにすることです。そのためには、最新のクリティカルAPIや障害発生時のCUJの挙動を誰もがアクセスできる形とすることが必要です。そこで、Looker Studioでこれらの結果を可視化し、各CUJのAPIコール一覧、APIの障害発生時にアプリのどの画面で失敗するかをスクリーンショットで可視化しました。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/12/1fa0a74c-08_dashboard.png" alt="" /></p>
<h2>現状と今後</h2>
<p>前章までの活動でUser Jouney SLOに対して以下を実現しました。</p>
<ol>
<li>CUJの定義の明確化</li>
<li>各CUJに対して1対1となるSLIの定義</li>
<li>各CUJとSLOのメンテナンスの自動化</li>
<li>障害時の各CUJの挙動のダッシュボードによる可視化</li>
</ol>
<p>その結果として、約40のCUJと約60のテストケースに対してSLOの運用を行っています。現状はSRE内で試験活用中の段階です。現時点でも新しいSLOの導入により、以下の項目の向上を体感しています。</p>
<ul>
<li>障害発生検知の速度・精度</li>
<li>障害影響範囲把握の精度</li>
<li>障害原因特定の速度</li>
<li>品質可視化の精度</li>
</ul>
<p>数値的には以下の効果を実現しました。</p>
<ul>
<li>障害検知後の<strong>影響範囲の特定にかかる時間がゼロ</strong></li>
<li>SLOのメンテナンスにかかる時間を<strong>99%削減</strong></li>
</ul>
<p>この現状を踏まえて今後はSRE以外での活用を進め、以下の取り組みを行っていく予定です。</p>
<ul>
<li>社内の障害基準としての活用</li>
<li>お客さま対応での活用</li>
</ul>
<h2>おわりに</h2>
<p>本記事ではメルカリのCUJに基づいたSLO運用について、SLI・SLOの詳細やiOS E2E Testを使用した継続的な最新化の取り組みについて紹介しました。SLI・SLOの運用に取り組む方々に何かの参考になれば幸いです。</p>
<p>明日の記事は….rina….さんです。引き続きお楽しみください。</p>
- メルカリ新卒1年目のエンジニアが最初の7ヶ月間でやったことhttps://engineering.mercari.com/blog/entry/20241204-first-seven-months-new-grads/https://engineering.mercari.com/blog/entry/20241204-first-seven-months-new-grads/<p>この記事は、Merpay & Mercoin Advent Calendar 2024 の記事です。 はじめに こんにちは。メルペイでBackend Engineerをしている@hibagonです。2024年4月 […]</p>
Fri, 06 Dec 2024 10:00:39 GMT<p>この記事は、<a href="https://engineering.mercari.com/blog/entry/20241125-merpay-mercoin-advent-calendar-2024/">Merpay & Mercoin Advent Calendar 2024</a> の記事です。<br /></br></p>
<h2>はじめに</h2>
<p>こんにちは。メルペイでBackend Engineerをしている<a href="https://x.com/hbyotto0416">@hibagon</a>です。2024年4月に新卒として入社しました。<br />
この記事では、メルカリ新卒1年目のエンジニアがどのようなことをしているのかについてご紹介できればと思います。特にインターンや新卒としてメルカリで働くことに興味のある方の参考になれば嬉しいです!<br />
</br>それでは早速Let’s Go!</p>
<h2>4月🌸</h2>
<p>4月はDevDojo(※1)をはじめとする新卒研修に大半の時間を費やします。DevDojoでは、Go言語などのメルカリで使用されている技術を中心に、さまざまな分野について横断的に学びます。<br />
研修の合間の時間では、私のチームでの業務に用いるドメイン知識や開発に関する知識のキャッチアップを行いました。ただしこの時点では、オンボーディング資料があまり整備されていないという問題がありました。私のチームは比較的多くのインターン生を受け入れているということもあり、今後のためにも、キャッチアップ内容のアウトプットとしてオンボーディング資料を執筆することにしました。</br></p>
<p>※1 技術トレーニングDevDojoで実際に使用されている学習コンテンツを公開しています。<a href="https://engineering.mercari.com/learning-materials/" title="こちら">こちら</a>をご参照ください。</p>
<p></br></p>
<div style="text-align: center;">
<img src="https://storage.googleapis.com/prd-engineering-asset/2024/12/3205fbab-gopher.jpg" alt="Gopherくん" style="display: block; margin: 0 auto; max-width: 100%;" /></p>
<div>Gopherくん(原著者:Renée French)</div>
</div>
<h2>5月🌿</h2>
<p>ゴールデンウィーク明けからは、いよいよ本格的に配属先のチームに合流して業務を開始しました。</br><br />
最初に取り組んだのは、リリースフローの改善でした。私のチームではgit flowを採用しており、毎週決まった日にリリースブランチとリリース用のPull Requestを作成する運用なのですが、当時はこれを手動で行っていました。これをGitHubActionsを用いて自動化しました。一見大きなインパクトが無いように思えるかもしれませんが、週に30分程度の定期業務を完全に自動化できたことにより、本記事を執筆している現在までの7ヶ月間で約2人日分の工数削減を達成できたことになります。</br><br />
その他も小さなタスクを拾いつつ、業務に必要な知識のインプットを中心に行いました。そして5月末時点で、4月から継続的に書き進めていたオンボーディング資料のVer.1を書き上げました🍤</p>
<h2>6月☔</h2>
<p>6月からは、業務内容が徐々に本格化していきました。</br><br />
メルペイにはMerpay APIというBFF (Backend for Frontend) があるのですが、リリースから時が経つにつれMerpay APIが巨大化し、責務も曖昧になってきているという問題が発生しています。 これを受け、Merpay APIが持つ全てのAPIに対して、ひとつずつAPIに責任を持つチームを明確にし、Merpay APIを複数のBFFに分割することで管理することを目指したMerpay API Rearchitectureというプロジェクトが進行しています(※2)。</br></br></p>
<div style="text-align: center;">
<img src="https://storage.googleapis.com/prd-engineering-asset/2024/12/caf38445--2024-12-04-15.48.21.png" alt="Merpay API Re-Architecture" style="display: block; margin: 0 auto; max-width: 100%;" /></p>
<div>https://engineering.mercari.com/blog/entry/20231023-mmtf2023-day3-9/より引用</div>
</div>
<p></br></p>
<p>私のチームでは、Ownershipを持っているサービス中では比較的小規模な3D Secureに関するAPIから移行することを決め、このプロジェクトの担当として私がアサインされました。これに際し、<a href="https://engineering.mercari.com/blog/entry/20241204-developing-bff-using-grpc-federation/" title="昨日のアドベントカレンダー">昨日のアドベントカレンダー</a>でも解説されているgRPC Federationという技術を用いました。</br><br />
さらに、7月中旬開始のメルカードのキャンペーンに向け、開発を行うための知識のキャッチアップを行いました。キャッチアップを行う中で、キャンペーンを行うための開発に関して、課題があることを理解しました。具体的には、キャンペーンのたびに一定規模の開発が発生していること、BFFであるMerpay APIにビジネスロジックが流出していることなどの問題がありました。こちらの課題を整理して、社内Tech Talkで発表しました。現在は改善されつつあります。</br><br />
またこの頃から、オンコールのシフトにも加わりました。オンコールとは「サービスのパフォーマンスが悪化したり、停止が疑われたりする場合に備えて担当者が常時対応できるようにしておく仕組み」です。メルカリグループではPagerDutyを使用してオンコールシフトを管理しており、PagerDutyに紐づけられた特定のDatadog Monitorが閾値を超えた場合、自動で担当者を呼び出す仕組みになっています。</br><br />
※2 詳しくは <a href="https://engineering.mercari.com/blog/entry/20231023-mmtf2023-day3-9/" title="【書き起こし】gRPC Federation を利用した巨大なBFFに対するリアーキテクチャの試み – goccy【Merpay & Mercoin Tech Fest 2023】">【書き起こし】gRPC Federation を利用した巨大なBFFに対するリアーキテクチャの試み – goccy【Merpay & Mercoin Tech Fest 2023】</a> をご参照ください。</p>
<h2>7月🌞</h2>
<p>7月前半は、7月中旬開始のメルカードのキャンペーンに向けた開発を急ピッチで進めていました。開発自体も(最初に取り組むちゃんとしたプロジェクトとしては)大変ではあったのですが、それ以上に他チームと連携するのが大変でした。キャンペーン開始を遅らせないために、エンジニアというポジションに関わらず積極的に進捗確認したり、課題を整理するなどの立ち回りを意識して行えたのは良かったポイントかなと思っています。QA中にも不具合など様々な問題が発生して、そのサポートも中々大変でしたが、結果としてはキャンペーンは予定通り開始されたのでよかったです。</p>
<div style="text-align: center;">
<img src="https://storage.googleapis.com/prd-engineering-asset/2024/12/0d0e5d99--2024-12-04-14.03.32.png" alt="実際のキャンペーンLPの一部" style="display: block; margin: 0 auto; width: 50%;" /></p>
<div>実際のキャンペーンLPの一部</div>
</div>
<p></p>
<p>7月後半からは、インターン生のメンターを初めて担当させていただきました。私自身も入社してから3ヶ月程度しか経っておらず一抹の不安はありましたが、それ以上にワクワクもしていました。<br />
7月中の目標としては、チームないしメルカリという会社に馴染んでもらうということを最優先に考えていました。そのため、積極的にご飯会を企画したり、社内部活に一緒に参加したりしました。また、この時のために温めていたオンボーディング資料を用いてキャッチアップを進めてもらい、必要に応じて加筆修正も依頼しました。オンボーディング資料は、このようにして新メンバーが加わった際に読んでもらいながら、最新の情報にアップデートしていく運用が良いと思っています。</p>
<h2>8月🏖️</h2>
<p>8月は、メンター業務を引き続き行いながら、主に2つのタスクを行っていました。</br><br />
1つ目は、先程述べたメルカードのキャンペーンの次に行うキャンペーンに向けた開発を行いました。7月は初めてということもあり、私のチームのメンバーに伴走してもらいながら進めていましたが、8月のキャンペーンでは、自走できるようになることを意識していました。</br><br />
2つ目は、Merpay APIにある3D Secureに関するAPIの移行をgRPC Federationを用いて進めました。これは6月に着手していたのですが、7月は別のタスクを優先していたため、後回しになっていました。この頃から、Merpay API Rearchitectureプロジェクトに関しては、チームでのカウンターパートとして、私が情報収集や開発の主導をしていくことになりました。進捗としては、8月中にはおおよそ3D SecureのAPIの移行準備自体は完了しました。ただ他プロジェクトとの兼ね合いもあり、すぐに本番適用はしていません。</br><br />
またこの頃に、新卒2年目以内の社員とインターン生が主体となって行うアイディアソンである「未現会議」というイベントの運営にも加わり、計画を進めていきました。</p>
<h2>9月🍁</h2>
<p>9月は、メンターをしていたインターン生のインターン期間が終了するので、それに関連したサポート業務がメインでした。具体的には、インターン生がキリの良いところまで開発を完了できるようにサポートしたり、インターン後に開始することになったQAのサポートを私が行えるようにするために、引き継ぎをしてもらったりしました。</br><br />
初めてのメンターを終えたので、ここで振り返りをしておきます。</br><br />
メンターとして心掛けていたのは、フランクに話せる関係を作り、何でも質問しやすい雰囲気を作ることでした。そのためには、同期的なコミュニケーションの機会が重要だと考え、毎日30分から1時間ほど1on1の時間を取りました。そこでは、業務を進めていくためのインプットや質問対応だけでなく、仕事以外の雑談も交えるようにしていました。また、1on1の時間でも、いつでもメンション飛ばしたり、スポットの1on1セッティングしていいからね!という強調も意識的に行いました。結果としてインターン生が高いパフォーマンスを発揮してくださり、新卒内定のオファーをもらうまで至ったのはメンターとしてとても嬉しく思いました。</br><br />
余談ですが、インターンお疲れ様会でプレート入りデザートをサプライズで頼んでおいたら、インターン生からもチームメンバーからもとても好評で嬉しかったです。</p>
<p>その他には、引き続きキャンペーンのための開発業務も行っていました。この頃には、ほぼ自走できるようになっていました。</p>
<p>ところで、9月中旬のメンター業務が終了したタイミングで少し長めの休暇をもらい、オーストラリア🇦🇺に旅行に行きました。シドニー港は、世界三大美港に数えられるだけあって、とても美しかったです。また、コアラやクジラなどの様々な生き物に触れ合ったり、砂漠やユーカリの森などの広大な自然を体感して、とてもリフレッシュできました。</br></br></p>
<div style="display: flex; justify-content: space-between; align-items: center;">
<div style="text-align: center; flex: 1; margin-right: 10px;">
<img src="https://storage.googleapis.com/prd-engineering-asset/2024/12/9b0a38be-.jpeg" alt="クジラのジャンプ" style="max-width: 100%; height: auto;" /></p>
<p>クジラのジャンプ(ブリーチ)</p>
</p></div>
<div style="text-align: center; flex: 1; margin-left: 10px;">
<img src="https://storage.googleapis.com/prd-engineering-asset/2024/12/b8269dc7-img_0823-scaled.jpg" alt="シドニーの夜景" style="max-width: 100%; height: auto;" /></p>
<p>シドニーの夜景</p>
</p></div>
</div>
<h2>10月🎃</h2>
<p>10月は、なんと早くも2人目のインターン生のメンターを行うことになりました。これは他チームの都合もあり急遽決まったのですが、メンター自体は初めてではなかったので、それ程不安はありませんでした。</br><br />
例によって10月前半は、チームやメルカリという会社に馴染んでもらうということを意識していました。また、オンボーディング資料を用いてキャッチアップを進めてもらいつつ、加筆修正も加えてもらいました。オンボーディング資料の改善サイクルが良い感じに回ってきたので、この流れを止めることのないようにしたいと思います。</br><br />
メンター業務以外では、10月中に開始するキャンペーンのQAサポートと、3D Secureに続いてメルペイのiDやバーチャルカードに関するAPIの移行を進めて行きました。</br><br />
さらに、別の小さめのプロジェクトでは、Spec作成から開発までを受け持つことになりました。小規模ではありましたが、それまではちゃんとしたSpecを書いたことがなかったため、とても良い経験になりました。</br><br />
またこの頃から、「未現会議」に関するタスクも少しずつ増えてきました。</p>
<h2>11月🍂</h2>
<p>11月は、内容は伏せますが大玉の開発案件があったため、チーム一丸となってAll for Oneで、その開発を進めていました。ありがたいことに、インターン生もとても活躍してくれました。</br><br />
今まで述べてきた通り、私はチーム内ではgRPC Federationの知見が一番あるため、gRPC Federationを用いたBFF用新規マイクロサービスの構築を担当しました。</br><br />
新規マイクロサービスを1から作成するという経験はあまり出来ないのでとても面白く、知見も深められました。特にサービスのリソース設定、モニタリング設定、オンコールの設定、本番環境で新規マイクロサービスを動かすために必要なチェックプロセスなどを通して、普段私のチームが持っているマイクロサービスに関する解像度も高まりました。</br><br />
また、11月は「未現会議」のアイディア募集期間ということもあり、アナウンスやLunch & Learnの開催など、イベント盛り上げのためのタスクにも、運営チームとしてAll for Oneで取り組みました。</br></br></p>
<div style="text-align: center;">
<img src="https://storage.googleapis.com/prd-engineering-asset/2024/12/c551dcfe--2024-12-04-15.27.45.png" alt="All for One" style="display: block; margin: 0 auto; width: 80%;" /></p>
<div>
https://about.mercari.com/about/about-us/ より引用
</div>
</div>
<h2>12月以降🎄</h2>
<p>この記事を書いている12月以降は、次に控えている大玉の開発案件や、Merpay API Rearchitectureの継続進行、「未現会議」のイベント成功などに向けて尽力していきたいと思います!<br />また、年明けから3人目のインターン生のメンターをさせていただくことになったので、引き続きメンター業務の方も頑張っていこうと思います💪</br><br />
ところで、今年の年末年始は有休を使わなくても9連休あるということで、非常に楽しみですね!特に今年はスノーボードセットを一式揃えたので、たくさん滑りにいけたらと思っています🏂</p>
<h2>まとめ</h2>
<p>本記事では、メルペイ新卒1年目のエンジニアがどのようなことをしているのかについて、私自身の振り返りの意味も込め、できるだけ詳細に書いてみました。メルカリは、新卒1年目から1人のプロとして扱われ、とてもチャレンジングなことを任せてもらえる環境です。この記事が、そんなメルカリでインターンや新卒として働くことに興味のある方に対して、具体的なイメージを広げる助けになれば嬉しいです。インターンや新卒採用に関しては、<a href="https://careers.mercari.com/new-graduates/" title="こちら">こちら</a>をご参照ください!</br></p>
<p>次の記事は@timoさんです。引き続きお楽しみください。</p>
- 多段ProxySQLによるスキーマ統合およびTiDB移行https://engineering.mercari.com/blog/entry/20241129-dff9a89c92/https://engineering.mercari.com/blog/entry/20241129-dff9a89c92/<p>Mercari DBRE(DataBase Reliability Engineering Team)のtaka-hです。 本エントリでは、メルカリの開発環境のデータベース移行の事例を紹介します。なお記事は、MySQL […]</p>
Fri, 06 Dec 2024 07:00:06 GMT<p>Mercari DBRE(<strong>D</strong>ata<strong>B</strong>ase <strong>R</strong>eliability <strong>E</strong>ngineering Team)のtaka-hです。</p>
<p>本エントリでは、メルカリの開発環境のデータベース移行の事例を紹介します。なお記事は、<a href="https://qiita.com/advent-calendar/2024/mysql" title="MySQL Advent Calendar 2024">MySQL Advent Calendar 2024</a>、および<a href="https://qiita.com/advent-calendar/2024/tidb" title="TiDB Advent Calendar 2024">TiDB Advent Calendar 2024</a>とのクロスポストになります。</p>
<p>この記事の手法を用いて、開発環境では、MySQLからTiDBへ移行すると同時に、スキーマ統合するということを達成しています。本記事により、読者の方々がProxySQLや、TiDBのエコシステムに興味を持っていただけることを期待しています。</p>
<h2>TL;DR</h2>
<ul>
<li>TiDBのData Migration(以後、TiDB DMと呼びます)のTable Routingを用いると、TiDBにスキーマ統合したインスタンスを作成できる</li>
<li>2段のProxySQLを構成し、1段目でルーティングを、2段目でinit-connectにスキーマ変更を設定することによって、アプリケーションの滑らかな移行が達成できる</li>
</ul>
<p>次の構成図を、説明しきることが本エントリのゴールです。</p>
<figure id="attachment_32181" aria-describedby="caption-attachment-32181" style="width: 580px" class="wp-caption aligncenter"><img loading="lazy" src="https://storage.googleapis.com/prd-engineering-asset/2024/12/f6e4289d-cleanshot-2024-11-29-at-17.01.07-1024x526.png" alt="2段ProxySQLによるスムーズなアプリケーション移行の実現" width="580" height="298" class="size-large wp-image-32181" srcset="https://storage.googleapis.com/prd-engineering-asset/2024/12/f6e4289d-cleanshot-2024-11-29-at-17.01.07-1024x526.png 1024w, https://storage.googleapis.com/prd-engineering-asset/2024/12/f6e4289d-cleanshot-2024-11-29-at-17.01.07-300x154.png 300w, https://storage.googleapis.com/prd-engineering-asset/2024/12/f6e4289d-cleanshot-2024-11-29-at-17.01.07-768x394.png 768w, https://storage.googleapis.com/prd-engineering-asset/2024/12/f6e4289d-cleanshot-2024-11-29-at-17.01.07.png 1040w" sizes="(max-width: 580px) 100vw, 580px" /><figcaption id="caption-attachment-32181" class="wp-caption-text">2段ProxySQLによるスムーズなアプリケーション移行の実現</figcaption></figure>
<h2>背景</h2>
<p>本節では、今回、スキーマ統合に至った経緯を簡単に説明します。</p>
<p>メルカリの本番環境では、サービス提供開始時から必要なデータ保存量やトラフィックの増加に伴い、垂直シャーディングを繰り返してきました。すなわち、データの性質が異なり、結合クエリの発行が必要ない範囲に関しては、ソースおよびレプリカのセット(以後、クラスタと呼びます)を分け、複数のクラスタで運用をしています。</p>
<figure id="attachment_32184" aria-describedby="caption-attachment-32184" style="width: 450px" class="wp-caption aligncenter"><img src="https://storage.googleapis.com/prd-engineering-asset/2024/12/a4ac003e-cleanshot-2024-11-29-at-17.06.25.png" alt="垂直シャーディング" width="450" class="size-full wp-image-32184" srcset="https://storage.googleapis.com/prd-engineering-asset/2024/12/a4ac003e-cleanshot-2024-11-29-at-17.06.25.png 588w, https://storage.googleapis.com/prd-engineering-asset/2024/12/a4ac003e-cleanshot-2024-11-29-at-17.06.25-300x172.png 300w" sizes="(max-width: 588px) 100vw, 588px" /><figcaption id="caption-attachment-32184" class="wp-caption-text">垂直シャーディング</figcaption></figure>
<p>一方で、開発環境ではデータ規模が商用環境ほど大きくないため、運用をシンプルにするためにも単一のクラスタで運用をしています。</p>
<p>ただ、この垂直分割は一定回数繰り返すことで、分割の難易度は次第に高くなります。やがて、大きなクラスタをこれ以上分割するのが難しくなり、データベースのスケーラビリティを解消するために、非常に複雑で骨の折れる、データおよび機能実装の分離が、アプリケーションに求められるようになります。反対に分割を行わないと、より高性能なハードウェアが求められ、マネージドサービスで所望のインスタンスクラスがない、また、ハードウェアの調達時間が長期化しやすい、といった悩みもありました。そこでメルカリでは、MySQLの互換性が非常に高く、スケーラビリティーの優れたTiDBへ移行することを決断しました。</p>
<figure id="attachment_32186" aria-describedby="caption-attachment-32186" style="width: 600px" class="wp-caption aligncenter"><img src="https://storage.googleapis.com/prd-engineering-asset/2024/12/d0b70a98-cleanshot-2024-11-29-at-17.07.37.png" alt="垂直シャーディングは次第に難しくなる" width="600" class="size-full wp-image-32186" srcset="https://storage.googleapis.com/prd-engineering-asset/2024/12/d0b70a98-cleanshot-2024-11-29-at-17.07.37.png 728w, https://storage.googleapis.com/prd-engineering-asset/2024/12/d0b70a98-cleanshot-2024-11-29-at-17.07.37-300x164.png 300w" sizes="(max-width: 728px) 100vw, 728px" /><figcaption id="caption-attachment-32186" class="wp-caption-text">垂直シャーディングは次第に難しくなる</figcaption></figure>
<p>次に、 垂直シャーディングとスキーマの関係について説明します。<br />
クラスタ構成が開発環境と商用環境で異なるため、アプリケーション開発はこれを前提に行う必要があり、開発時にあらかじめテーブルの結合などが必要ない範囲を定義し、開発環境では、そのまとまりごとにスキーマを作成していました。</p>
<p><figure id="attachment_32189" aria-describedby="caption-attachment-32189" style="width: 527px" class="wp-caption aligncenter"><img loading="lazy" src="https://storage.googleapis.com/prd-engineering-asset/2024/12/01d23ff5-cleanshot-2024-11-29-at-17.12.44.png" alt="開発環境と商用環境のクラスタ/スキーマ構成" width="527" height="423" class="size-full wp-image-32189" srcset="https://storage.googleapis.com/prd-engineering-asset/2024/12/01d23ff5-cleanshot-2024-11-29-at-17.12.44.png 527w, https://storage.googleapis.com/prd-engineering-asset/2024/12/01d23ff5-cleanshot-2024-11-29-at-17.12.44-300x241.png 300w" sizes="(max-width: 527px) 100vw, 527px" /><figcaption id="caption-attachment-32189" class="wp-caption-text">開発環境と商用環境のクラスタ/スキーマ構成</figcaption></figure><figure id="attachment_32190" aria-describedby="caption-attachment-32190" style="width: 473px" class="wp-caption aligncenter"><img loading="lazy" src="https://storage.googleapis.com/prd-engineering-asset/2024/12/688f47e1-cleanshot-2024-11-29-at-17.14.34.png" alt="開発環境と商用環境の差分のイメージ" width="473" height="392" class="size-full wp-image-32190" srcset="https://storage.googleapis.com/prd-engineering-asset/2024/12/688f47e1-cleanshot-2024-11-29-at-17.14.34.png 473w, https://storage.googleapis.com/prd-engineering-asset/2024/12/688f47e1-cleanshot-2024-11-29-at-17.14.34-300x249.png 300w" sizes="(max-width: 473px) 100vw, 473px" /><figcaption id="caption-attachment-32190" class="wp-caption-text">開発環境と商用環境の差分のイメージ</figcaption></figure></p>
<p>開発環境でスキーマをまたがなければ、商用環境でクラスタをまたぐ心配はなく、そのような観点から、アプリケーションはスキーマをまたぐようなクエリは発行しないような実装としていました。</p>
<p>他方で、開発環境と商用環境のスキーマの環境差分は、ユーザー定義、他システム連携のシステム構成など様々な環境差分につながり、開発環境の構成が過度に複雑化したり、開発環境での様々なテストの実効性を低下させることにつながっていました。そこで、TiDBの移行の決定とともに、元々抑止しようとしたクラスタまたぎのクエリを事前に抑制することが一時的に達成されなくなる点を加味しても、スキーマ統合を行いこの環境差分を解消することにしました。</p>
<h2>切替の作戦</h2>
<p>本節では、MySQLで複数のスキーマに分かれていたデータへのクエリを、TiDBにどのように安全かつスムーズに移行していくか、についての考慮事項について記載します。</p>
<p>対象としているデータベースは、利用しているアプリケーションがとても多く、複数箇所のデータベースの接続の設定変更に、時間がかかることが予想されました。各チームは異なる開発サイクル(Sprint)を持ち、DBREで事前に十分にテストを行っているとはいえ、切戻しも考えられました。</p>
<p>そこで、以下の2点を考慮の上、最終的には<a href="https://proxysql.com/">ProxySQL</a>というプロキシを下図のように多段に構成し移行をすすめました。</p>
<ul>
<li>切替を各アプリケーションで個別に行うのではなく、DBREで一元的にコントロールする</li>
<li>アプリケーションの必要なコンフィグ修正のタイミングに柔軟性をもたせる</li>
</ul>
<figure id="attachment_32181" aria-describedby="caption-attachment-32181" style="width: 580px" class="wp-caption aligncenter"><img loading="lazy" src="https://storage.googleapis.com/prd-engineering-asset/2024/12/f6e4289d-cleanshot-2024-11-29-at-17.01.07-1024x526.png" alt="2段ProxySQLによるスムーズなアプリケーション移行の実現" width="580" height="298" class="size-large wp-image-32181" srcset="https://storage.googleapis.com/prd-engineering-asset/2024/12/f6e4289d-cleanshot-2024-11-29-at-17.01.07-1024x526.png 1024w, https://storage.googleapis.com/prd-engineering-asset/2024/12/f6e4289d-cleanshot-2024-11-29-at-17.01.07-300x154.png 300w, https://storage.googleapis.com/prd-engineering-asset/2024/12/f6e4289d-cleanshot-2024-11-29-at-17.01.07-768x394.png 768w, https://storage.googleapis.com/prd-engineering-asset/2024/12/f6e4289d-cleanshot-2024-11-29-at-17.01.07.png 1040w" sizes="(max-width: 580px) 100vw, 580px" /><figcaption id="caption-attachment-32181" class="wp-caption-text">2段ProxySQLによるスムーズなアプリケーション移行の実現</figcaption></figure>
<p>なお、本移行においては、ProxySQLの機能としては非常に重要であり、これを目的に利用する人が多いであろう、<a href="https://proxysql.com/documentation/multiplexing/">Multiplexing</a>と呼ばれる接続を多重化する機能を無効化して利用しています。</p>
<h2>移行後のデータの準備</h2>
<p>本節では、移行後のデータの準備について説明します。<br />
MySQLからTiDBへの移行には、<a href="https://docs.pingcap.com/tidb/stable/dm-overview">TiDB DM</a> を利用します。</p>
<p>TiDB DMはMySQLのbinary logの情報を元に、DDLを含む変更/差分ログをダウンストリームのTiDBに反映することができます。</p>
<figure id="attachment_32193" aria-describedby="caption-attachment-32193" style="width: 600px" class="wp-caption aligncenter"><img src="https://storage.googleapis.com/prd-engineering-asset/2024/12/bf6309fb-cleanshot-2024-11-29-at-17.19.04.png" alt="TiDB DMによる移行先データの準備" width="600" class="size-full wp-image-32193" srcset="https://storage.googleapis.com/prd-engineering-asset/2024/12/bf6309fb-cleanshot-2024-11-29-at-17.19.04.png 759w, https://storage.googleapis.com/prd-engineering-asset/2024/12/bf6309fb-cleanshot-2024-11-29-at-17.19.04-300x212.png 300w" sizes="(max-width: 759px) 100vw, 759px" /><figcaption id="caption-attachment-32193" class="wp-caption-text">TiDB DMによる移行先データの準備</figcaption></figure>
<p>TiDB DMでは、初期のデータ同期および、差分の同期がどちらも可能で、大量のデータがある場合は、TiDBに対して、論理データを非常に高速に物理インポートする<a href="https://docs.pingcap.com/tidb/stable/tidb-lightning-overview">TiDB lightning</a>と合わせて利用することで、高速にデータが準備できます。</p>
<p>スキーマ統合については、TiDB DMの標準機能である<a href="https://docs.pingcap.com/tidb/stable/dm-table-routing">Table Routing</a>を利用することにより、非常に簡易に解決されます。アップストリームのMySQLの、どのスキーマのどのテーブルを、ダウンストリームのどこに移動するか、を routes という設定に RE2 syntaxの正規表現で記載するだけで、統合したデータが準備されます。</p>
<p>下記に、TiDB DMのコンフィグ例 (一部抜粋)を記します。</p>
<pre><code>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"</code></pre>
<h2>2段ProxySQL</h2>
<p>本節では、2段ProxySQLにより先程の要件をどのように達成するかを順に説明します。</p>
<p>1段目のProxySQLは、トラフィックシフトを行う役割を持ちます。</p>
<p>ProxySQLでは、<a href="https://proxysql.com/documentation/main-runtime/#mysql_query_rules">mysql_query_rules</a>という設定で、様々なルーティングなどが可能で、移行割合を weight というパラメータにより、コントロールできます。<br />
これにより、アプリケーションからのレプリカに対する10%のトラフィックをTiDBに、残りの90%をMySQLにルーティングする、といったことなどが実現できます。<br />
また、TiDBへのトラフィックに関しては、1段目のProxySQLが接続するスキーマに応じて適切な2段階目のProxySQLにルーティングします。<br />
これによりアプリケーション開発者が設定を変更することなく、MySQLからTiDBへ切り替わっていく状況が達成されました。</p>
<pre><code>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
}
)</code></pre>
<p>2段目のProxySQLは、アプリケーションが古いスキーマに接続する設定で動作する際に、スキーマ統合後のTiDBに透過的にアプリケーションを実行させる役割を持ちます。<br />
現在、移行元のMySQLと、移行先のTiDBでスキーマ構成が異なりますから、何も考慮せずにMySQLのトラフィックをTiDBへシフトさせると当然失敗する訳です。スキーマが異なったとしても、同じアプリケーションの設定で、重み付きで少しずつトラフィックをシフトできたら、事前のテスト可能性が向上し、大変望ましいと思います。<br />
この問題を、MySQLサーバーのグローバルスコープのパラメータである、<a href="https://dev.mysql.com/doc/refman/8.0/en/server-system-variables.html#sysvar_init_connect">init_connect</a>に関連したProxySQLの<a href="https://proxysql.com/documentation/global-variables/mysql-variables/#mysql-init_connect">init_connect</a>パラメータでスキーマ変更をさせることにより、解決させます。</p>
<pre><code>mysql_variables=
{
…
init_connect = "use mercari_admin"
…
}
mysql_servers=
(
{
address = "tidb.internal.mercari.jp."
hostgroup = 1
weight = 100
)</code></pre>
<p>実例にて、動作を確認しましょう。</p>
<p>mercari_dev_jp_adminというスキーマがmercari_adminのスキーマに、スキーマ統合(移行)されるケースを考えます。アプリケーションのスキーマの設定は、古いスキーマ mercari_dev_jp_admin だったとしましょう。</p>
<p>このとき、次の処理の順番により、アプリケーションから見て、スキーマ統合された新しいスキーマのテーブルに対して透過的にクエリが発行されます。</p>
<ol>
<li>接続フェーズでアプリケーションは mercari_dev_jp_admin (旧スキーマ)に接続を試行</li>
<li>ProxySQLのinit_connectにより接続スキーマが mercari_admin (新スキーマ)に変更される</li>
<li>クエリはmercari_admin (新スキーマ)上で実行される</li>
</ol>
<figure id="attachment_32196" aria-describedby="caption-attachment-32196" style="width: 1049px" class="wp-caption aligncenter"><img src="https://storage.googleapis.com/prd-engineering-asset/2024/12/c5237af8-cleanshot-2024-11-29-at-19.05.09.png" alt="新旧スキーマへの透過的なクエリ発行の例" width="1049" class="size-full wp-image-32196" srcset="https://storage.googleapis.com/prd-engineering-asset/2024/12/c5237af8-cleanshot-2024-11-29-at-19.05.09.png 1049w, https://storage.googleapis.com/prd-engineering-asset/2024/12/c5237af8-cleanshot-2024-11-29-at-19.05.09-300x138.png 300w, https://storage.googleapis.com/prd-engineering-asset/2024/12/c5237af8-cleanshot-2024-11-29-at-19.05.09-1024x471.png 1024w, https://storage.googleapis.com/prd-engineering-asset/2024/12/c5237af8-cleanshot-2024-11-29-at-19.05.09-768x354.png 768w" sizes="(max-width: 1049px) 100vw, 1049px" /><figcaption id="caption-attachment-32196" class="wp-caption-text">透過的なクエリ発行の例</figcaption></figure>
<p>1点付け加える必要があるのは、移行後のダウンストリームのデータとして、スキーマ統合をした実データとともに、移行前の空のスキーマ(テーブルなどのデータは存在しなくて良い)が必要であることです。</p>
<figure id="attachment_32197" aria-describedby="caption-attachment-32197" style="width: 692px" class="wp-caption aligncenter"><img loading="lazy" src="https://storage.googleapis.com/prd-engineering-asset/2024/12/13345a6e-cleanshot-2024-11-29-at-19.07.40.png" alt="移行先のTiDBのスキーマ構成" width="692" height="194" class="size-full wp-image-32197" srcset="https://storage.googleapis.com/prd-engineering-asset/2024/12/13345a6e-cleanshot-2024-11-29-at-19.07.40.png 692w, https://storage.googleapis.com/prd-engineering-asset/2024/12/13345a6e-cleanshot-2024-11-29-at-19.07.40-300x84.png 300w" sizes="(max-width: 692px) 100vw, 692px" /><figcaption id="caption-attachment-32197" class="wp-caption-text">移行先のスキーマ構成</figcaption></figure>
<p>また、本構成が利用できる前提条件として、そもそもクエリにスキーマを変更したり、スキーマを指定してクエリを発行するといった実装がない、という前提があります。メルカリのケースでは移行の背景からも、スキーマを指定してスキーマをまたぐクエリが発行される心配は、元からありませんでした。</p>
<p>この設定の追加によりもたらされるメリットは2つあります。</p>
<ol>
<li>読取りトラフィックを、新・旧スキーマに重み付きで流す、といった<strong>段階的な移行</strong>が可能になることで、より安全にトラフィックが移行できる</li>
<li>アプリケーションのスキーマ設定は、新・旧どちらでも動作し、<strong>コンフィグの並行運用期間</strong>が取れるため、各開発チームのタイミングで設定の移行を柔軟に進められる</li>
</ol>
<p>実際には、トラフィックを全て切り替えた後、<strong>動作が問題ないことが確認されてから</strong>、各アプリケーションにクライアントの<strong>接続先スキーマの設定変更</strong>依頼を行い、これによりメルカリの開発環境のスムーズな移行が達成されました。(※空のスキーマを誤って不要だと錯誤し、削除してしまうというミスは発生しました)</p>
<p>なお、本切替のTiDBへのトラフィックシフトの前には、事前に</p>
<ul>
<li>主要な利用シナリオ用に作成したベンチマークシナリオによるTiDBの性能評価</li>
<li>想定される遅延増加を、擬似的にデータベースレスポンスに付与し、アプリケーションへの影響を評価、アプリケーションの修正が必要な箇所を特定</li>
<li>現行のクエリをキャプチャし、TiDBへリプレイし問題の有無を確認<br />
ということを終えておりました。</li>
</ul>
<h2>終わりに</h2>
<p>この記事では、メルカリの開発環境で、MySQLからTiDBへ移行すると同時にスキーマ統合する際に用いた手法について説明しました。</p>
<p>多数のアプリケーション接続をかかえるデータベースを、アプリケーション設定変更は実際に移行が成功した後に、順次行える構成を考え、移行を成功させました。ポイントは次の2点です。</p>
<ul>
<li>TiDBのData MigrationのTable Routingを用いて、TiDBにスキーマ統合したインスタンスを作成</li>
<li>2段のProxySQLを構成し、1段目でルーティングを、2段目でinit_connectにスキーマ変更を設定</li>
</ul>
<p>メルカリでは、絶え間なく変化するアプリケーションやトラフィックに柔軟に対応するため、クラウドサービスや、ProxySQLなどのOSSソフトウェアを活用したり、必要な機能を独自に実装を行い、問題の解決にチャレンジしています。</p>
<p>様々な課題に意欲的にチャレンジする仲間を募集しています。</p>
<p><a href="https://careers.mercari.com/bloom/">https://careers.mercari.com/bloom/</a></p>
- gRPC Federationを使ったBFF開発の実態https://engineering.mercari.com/blog/entry/20241204-developing-bff-using-grpc-federation/https://engineering.mercari.com/blog/entry/20241204-developing-bff-using-grpc-federation/<p>この記事は、Merpay & Mercoin Advent Calendar 2024 の記事です。 こんにちは、メルペイ BackendエンジニアのSakabeと申します。 私の所属するKYCチームでは、主に本 […]</p>
Thu, 05 Dec 2024 10:00:27 GMT<p>この記事は、<a href="https://engineering.mercari.com/blog/entry/20241125-merpay-mercoin-advent-calendar-2024/">Merpay & Mercoin Advent Calendar 2024</a> の記事です。</p>
<p>こんにちは、メルペイ BackendエンジニアのSakabeと申します。<br />
私の所属するKYCチームでは、主に本人確認に関するマイクロサービスの開発を行っています。</p>
<p>現在、メルペイの各チームが共通で使用する大規模なBFF(Backend For Frontend)がその巨大さゆえに管理や拡張が難しくなっています。この問題に対応するため、各チームごとにBFFを切り出す取り組みを進めています。KYCチームではこの対応として、<a href="https://engineering.mercari.com/blog/entry/20240401-4f426bd460/" title="こちらの記事">こちらの記事</a>で紹介されているgRPC Federationの仕組みを活用し、効率的にBFFサービスの立ち上げを行いました(gRPC FederationのOSSリポジトリは<a href="https://github.com/mercari/grpc-federation" title="こちら">こちら</a>)。</p>
<p>今回の記事では、実際のBFFサービスの構築に使われたprotoファイルと実際に使用したgRPC Federationのoptionを紹介することでgRPC Federationを用いた開発を紹介します。また、開発中に感じたことを説明します。</p>
<h2>gRPC Federationを採用した背景</h2>
<p>KYCチームがBFFを新規作成する際、gRPC Federationを採用した背景について説明します。巨大なBFFをKYCチーム専用のBFFに切り出すにあたり、gRPC Federationを活用する方法と、ゼロからBFFを構築する方法がありました。その中でgRPC Federationを選んだ理由は以下の2点です。<br />
1点目は、少ない手間でBFFが構築できることです。具体的には、Protoファイルにオプションを記述するだけで、BFFのコードを自動生成することが可能です。これにより、シンプルなロジックについては、ゼロからBFFを構築するよりも開発コストが低いと判断しました。<br />
2点目は、社内に基盤が作られていることです。他のチームでも実際に使われているため、デプロイまでのフローが整備されており迅速なリリースが可能と判断しました。<br />
これらの理由から、KYCチームではBFFを新規作成する手段としてgRPC Federationを採用しました。</p>
<h2>gRPC Federationを使用した実際のBFF開発</h2>
<p>KYCチームではgRPC Federationを利用して新規のBFFを開発しました。今回は、マイクロサービスに新規実装したエンドポイントをそのまま中継するだけのBFFを作成します。</p>
<p>今回はその様子を以下の順番で紹介していきます。</p>
<ol>
<li>ベースとなる新規サービスのProtocol Buffers定義の作成</li>
<li>gRPC Federationのoption設定</li>
<li>生成されるGoファイル</li>
</ol>
<h3>1. ベースとなる新規サービスのProtocol Buffers定義の作成</h3>
<p>今回新たに公開するマイクロサービスのProtocol Buffersの定義(抜粋)です。リクエストは空でレスポンスでは2つの要素を返しています。</p>
<pre><code>service KYCMicroService {
rpc GetConfirmation(GetConfirmationRequest) returns (GetConfirmationResponse) {
};
}
message GetConfirmationRequest {}
message GetConfirmationResponse {
bool need_confirmation = 1;
ConfirmationEvent confirmation_event = 2;
}</code></pre>
<h3>2. gRPC Federationのoption設定</h3>
<p>このマイクロサービスを中継するためにBFFを作成します。gRPC Federationを使用するため、実際のGoのコードを書く必要はなく、protoファイルにgRPC Federationのoptionを記述しBFFのコードの自動生成を行います。</p>
<p>マイクロサービスのprotoファイルをコピーし、以下のような追記を行いました。</p>
<pre><code>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;
}</code></pre>
<p>まず、serviceにgRPC Federationを利用するためのoptionを記述します。</p>
<pre><code>option (.grpc.federation.service) = {};</code></pre>
<p>次にmessageのResponseを定義するためのoptionを記述します。</p>
<pre><code>option (.grpc.federation.message) = {
def[ {
call { method : "mercari.path.api.v1.KYC/GetConfirmation" }
autobind : true
}
]
};</code></pre>
<p>今回使用したのは、<a href="https://github.com/mercari/grpc-federation/blob/main/docs/references.md#grpcfederationmessagedefcall" title="(grpc.federation.message).def.call">(grpc.federation.message).def.call</a>と<a href="https://github.com/mercari/grpc-federation/blob/main/docs/references.md#grpcfederationmessagedefautobind" title="(grpc.federation.message).def.autobind">(grpc.federation.message).def.autobind</a>です。<br />
<a href="https://github.com/mercari/grpc-federation/blob/main/docs/references.md#grpcfederationmessagedefcall" title="(grpc.federation.message).def.call">(grpc.federation.message).def.call</a>は、gRPCメソッドを呼び出し、レスポンスを変数に代入します。ただし今回は後述する(grpc.federation.message).def.autobindによる自動的な代入を使用しているため、gRPCのレスポンスを代入する変数を省略しています。<br />
<a href="https://github.com/mercari/grpc-federation/blob/main/docs/references.md#grpcfederationmessagedefautobind" title="(grpc.federation.message).def.autobind">(grpc.federation.message).def.autobind</a>は、callのレスポンスのmessageと自身のfieldの名前と型が一致する場合にfieldの値を自動的に代入します。</p>
<p>逆に一致しない場合は、以下の例のようにレスポンスのfieldを明示的に指定する必要があります。call の結果を res に代入し、res の field である foo の結果を need_confirmation 変数に代入しています。</p>
<pre><code>option (.grpc.federation.message) = {
def[ {
name: "res"
call { method : "mercari.path.api.v1.KYC/GetConfirmation" }
}
]
def { name: "need_confirmation" by: "res.foo"}
...
};</code></pre>
<p>つまり、callとautobindのoptionの記述によって、KYCのマイクロサービスのgRPCエンドポイントをcallした結果がfieldに自動的に代入されます。</p>
<h3>3. 生成されるGoファイル</h3>
<p>grpc-federation-generatorコマンドを実行することでprotoファイルからコードを生成することができます。</p>
<p>上記のprotoファイルから、以下のようなGoのコード(疑似コード)が生成されます。</p>
<pre><code>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
}</code></pre>
<h2>gRPC Federationを使用してBFFの開発した気づき</h2>
<p>今回、gRPC Federationを用いてBFFを開発する中で、以下の4つの点でさまざまな発見や気づきを得ました。</p>
<ol>
<li>短期間でのBFF構築の可能性</li>
<li>高い表現の自由度</li>
<li>技術導入時のコスト評価の重要性</li>
<li>実行順の依存関係の明確化</li>
</ol>
<p>以下、それぞれのポイントについて詳しく掘り下げていきます。</p>
<ul>
<li>短期間でのBFF構築の可能性</li>
</ul>
<p>1点目は、短期間でのBFF構築が可能であることです。<br />
BFFのprotoの実装は1日で終了しました。今回のようなマイクロサービスのAPIをプロキシするだけのBFFであれば、optionを10行程度追加するだけでBFFを生成できるのは大きなメリットだと感じています。gRPC Federationを選ぶ理由として定型作業の簡素化があげられており、今回のようなマイクロサービスのAPIを中継するだけのBFFの実装はまさに定型作業といえます。gRPC Federationではprotocol buffers上でメッセージの対応関係を記述することで、型変換処理を全て自動生成しています。<br />
また、私自身はGo言語に関する専門的な知識を持っていませんが、gRPC Federationによってコードが自動生成されることで、Goにおける最新のベストプラクティスを享受し、ハイパフォーマンスなコードを利用できます。もし同じ品質のものを開発する場合、もっと時間がかかると思います。その点で、gRPC Federationを使えばGoの初心者であっても短期間で高品質の成果を得られます。<br />
さらに、社内ではgRPC Federationを利用したBFFをモノレポで運用しており、そのレポジトリではBFFを迅速にデプロイする環境が整えられています。</p>
<ul>
<li>高い表現の自由度</li>
</ul>
<p>2点目は、表現の自由度が高いということです。<br />
現在はCEL(Common Expression Language)のサポートが行われています。proto上で計算を行ったり、計算結果を元にAPIの呼び出しの分岐を書いたりすることも可能です。また、protoからGoのコードを呼び出すことができるため、他のライブラリを使うなどの用途で一部の処理を切り出すこともできます。<br />
このようにgRPC Federationのoptionでできないことを柔軟に実装することが可能です。</p>
<ul>
<li>技術導入時のコスト評価の重要性</li>
</ul>
<p>3点目は、新しい技術導入時のコストとメリットを評価する必要があるということです。<br />
新しい技術を導入する際には、そのコストに対してメリットが上回るかを慎重に評価する必要があります。普段はGo言語で開発しており、Goでも十分に開発が可能です。しかし、新しい技術がそれを上回るメリットを提供するかどうかを判断することが求められます。<br />
私自身は、gRPC Federationのオプションに対する生成コードをある程度想像できていますが、チームメンバーは一から学ぶ必要があり、そのためコードレビューが難しくなる場合があります。また、新しいメンバーが加わった際に、BFFに少しの修正を加えるにしても時間がかかることが多いです。そのため、gRPC Federationを導入した際には、チーム内で的確な開発サイクルやリリースフローをしっかりと周知する必要があると感じています。<br />
これはgRPC Federationに限らず、どのような新しい技術を導入する時にも一般的に言える課題であり、gRPC Federation固有の問題ではないことを明記しておきたいです。</p>
<ul>
<li>実行順の依存関係の明確化</li>
</ul>
<p>4点目は、実行順の依存関係を明確にする必要があることです。<br />
依存関係を設定しない場合、処理は並列で行われ、最適化された状態で自動的に呼び出されます。これにより、開発者が自らパフォーマンスチューニングを行わなくても済むという利点があります。<br />
しかし、特定の順序での実行が求められる場合は、依存関係を設定する必要があります。例えば、先に実行したいmessageを次のmessageで使用することで、依存関係を明示し、処理を直列に実行させることが可能です。Goのコードであれば、処理を単に上から順に書いていくことで明確な順序を保てます。しかし、これは依存関係のない処理同士が無駄に順番待ちをする可能性もあります。<br />
依存関係を使って実行順序を制御し始めると、並列に実行されるのか、直列に実行されるのかがわかりにくくなる可能性があります。そのため、単純に上から実行されるようなGoのコードであっても、複雑なprotoファイルになることがあります。これを避けるためには、BFF層に複雑なロジックを書かないことが重要です。<br />
optionを駆使すれば複雑なprotoファイルを実装することも可能ですが、その前にロジックを理解し、それをマイクロサービスで処理することが求められます。私個人としては、ロジックを持っているBFFをマイグレーションする際、そのコードを深く理解し、protoに落とし込みやすくする工夫をしています。</p>
<h2>まとめ</h2>
<p>今回は、gRPC Federationを用いたBFFの開発について紹介しました。KYCチームでは今回のgRPC Federationによる新規BFF開発を足掛かりとして、既存のBFFマイグレーションを行う予定です。もしgRPC Federationに興味のある方は、実際に手を動かして試してみてください!</p>
<p>次の記事はhibagonさんです。引き続きお楽しみください!</p>
- Google CloudからGitHub PATと秘密鍵をなくす – Token ServerのGoogle Cloudへの拡張https://engineering.mercari.com/blog/entry/20241203-token-server-google-cloud/https://engineering.mercari.com/blog/entry/20241203-token-server-google-cloud/<p>この記事はMercari Advent Calendar 2024の1日目の記事です。 メルカリでは流出すると多大な影響を及ぼすような有効期限が長い認証情報を減らすために、有効期限が短い認証情報を発行する仕組みを様々な箇 […]</p>
Wed, 04 Dec 2024 11:00:37 GMT<p>この記事は<a href="https://engineering.mercari.com/blog/entry/20241125-mercari-advent-calendar-2024/">Mercari Advent Calendar 2024</a>の1日目の記事です。</p>
<p>メルカリでは流出すると多大な影響を及ぼすような有効期限が長い認証情報を減らすために、有効期限が短い認証情報を発行する仕組みを様々な箇所で導入しています。そして、Platform Security Teamでは社内で運用されていたGitHubの認証情報を生成するToken Serverというサービスを拡張することで、Google Cloud上で動作しているGitHubの自動化サービスが保持していたGitHubへのアクセスに使用する認証情報を有効期限が短いものに切り替えました。</p>
<p>このToken Serverの拡張と移行で用いた技術や課題、解決策について紹介します。</p>
<h1>概要</h1>
<p>メルカリではGitHubを中心とした開発環境を利用しており、その中でGitHubの運用を自動化する様々なサービスを開発・運用しています。</p>
<p>これらのサービスはPAT(Personal Access Token)やGitHub Appの秘密鍵を使いGitHubへアクセスします。しかしこれらの認証情報は有効期限が設定されていない、もしくはとても長く設定されています。これらの認証情報がサプライチェーン攻撃などによって漏洩すると長期に渡り攻撃者から悪用されるリスクがあります。また、これらの認証情報は一度作成されてしまうと多くの場合、どの認証情報がどのサービスで使われているのか不透明になりがちで、かつ付与された権限が見直されることが少ないです。</p>
<p>これらの課題を解決するために、すでにメルカリ社内で運用されていた、有効期限が短いGitHub認証情報を発行するToken Serverというサービスを拡張し、Google Cloud上で動作しているサービスが有効期限が長い認証情報を使うことなくGitHubにアクセスできるようにしました。<br />
これにより、以下のようなメリットを実現することができました。</p>
<ul>
<li>有効期限が長い認証情報の削減</li>
<li>管理が不透明な部分が多かったPAT及びGitHub Appの削減</li>
<li>認証情報を付与する対象・必要な権限を1つにまとめて管理することで、認証情報の対象の特定と権限の定期的な見直しを簡略化</li>
</ul>
<p>また、既存のPATや秘密鍵を用いるサービスの実装を大きく変えることなく、Token Serverに移行できるようなGo言語のライブラリも合わせて開発することで移行をすぐに行うことができました。</p>
<h1>Token Server</h1>
<p>メルカリでは様々な形態でGitHubを運用しており、特にGitHub内での自動化においてはあるレポジトリの変更を別のレポジトリに同時に適用するといった複数レポジトリをまたぐ自動化が頻繁に行われています。</p>
<p>特に社内で標準的に使われているCIであるGitHub Actionsにおいて、複数レポジトリをまたぐ自動化を行うための認証情報はデフォルトでは提供されておらず、一般的にはRepository SecretにPATを保存するか、GitHub Appの秘密鍵を保存し<a href="https://github.com/actions/create-github-app-token">create-github-app-token action</a>などで生成する必要があります。しかし、これらはPATおよびGitHub Appの秘密鍵という有効期限の長い認証情報が必要です。</p>
<p>そこで以前からメルカリは、GitHub Actionsのワークフロー内部で取得できる、GitHubが署名し偽造が困難なOIDC Tokenを受信し検証することで、特定の権限を持つ<a href="https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/generating-an-installation-access-token-for-a-github-app">Installation Access Token</a>を提供するToken Serverサービスを運用しています。</p>
<p>Installation Access TokenはGitHub Appの機能で、GitHub Appで事前に設定されている権限(contentsのread権限・Pull Requestのwrite権限など)のサブセットを特定のレポジトリに限定し発行できるトークンです。またInstallation Access Tokenには1時間の有効期限があり、さらにGitHub API経由で有効期限を待たずに失効させることも可能です。これにより最小権限の原則に従った、限定された権限・アクセス範囲・有効期限を持つ認証情報を提供することができます。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/12/10cc9513-1-token-server-github.png" alt="" /></p>
<p><center>Token Server for GitHubのアーキテクチャ</center></p>
<p>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を作成・発行を行います。<br />
Token Serverから発行されるInstallation Access Tokenは複数レポジトリ間のGitHub運用の自動化(コミットの追加・Issueの自動作成・Pull Requestの自動作成など)だけでなく、CIでのビルドにおける内部のライブラリのダウンロードなどにも広く使われています。</p>
<p>(注) 2024年4月にChainguard社から<a href="https://www.chainguard.dev/unchained/the-end-of-github-pats-you-cant-leak-what-you-dont-have">Octo STS</a>がリリースされました。基本的な動作原理はToken Serverと同じですが、Token Serverではより統一された権限管理をサポートしている他、後述するGoogle Cloud上へのワークロードへの組み込み・GitHub AppのLoad Balancingなどよりエンタープライズ環境に適したアーキテクチャが採用されています。</p>
<h1>Token ServerのGoogle Cloudへの拡張</h1>
<p>メルカリでは多くのサービスをGoogle Cloudで運用しています。これはお客さまへのサービス提供を目的としているマイクロサービス郡だけではなく、内部向けの自動化サービスも同様です。これらのサービスはGitHubにアクセスするためにPATやGitHub Appの秘密鍵を使用していました。</p>
<p>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を拡張することにしました。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/12/9cd649e6-2-token-server-gcp.png" alt="" /></p>
<p><center>Token Server for Google Cloudのアーキテクチャ</center></p>
<p>これにより、ある特定のGoogle Cloudリソース上で動作しているサービスが、Token ServerへOIDC Tokenを送信し、Installation Access Tokenを発行してもらうことで、GitHubへのアクセスができるようになり、今までGoogle Cloud上で使用していたPATやGitHub Appの秘密鍵をなくすことができます。</p>
<h1>Google Cloud上のワークロードへのToken Serverの適用</h1>
<p>Token Serverの拡張により、Google Cloud上で動作しているサービスがGitHubへのアクセスに使用する認証情報を有効期限が短いものに切り替えることができるようになりました。</p>
<p>これらの新規機能をGoogle Cloud上に新しく作成するサービスに適用することは比較的容易です。しかし今までGitHub PATやGitHub Appの秘密鍵を使っていたサービスは多くの場合、機能が作り込まれており、Token ServerへInstallation Access Tokenをリクエストし、取得したInstallation Access Tokenを使うように変更することが難しい場合があります。</p>
<p>そして、GitHub AppにはAPIの利用数の制限があります。これはGitHub Appごとに設定されており、GitHub Enterprise Cloudを利用している場合、1時間あたり15000回のAPIリクエストが可能です。この制限を超えるとAPIリクエストが失敗するため、APIリクエストの制限を超えないように、Token Serverに対してのリクエストをなるべく減らす必要があります。<br />
この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リクエストを減らすことができます。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/12/e33d100b-3-library-code.png" alt="" /></p>
<p><center>GitHubクライアントの初期化におけるPATからToken Serverへの移行</center></p>
<p>そこで、PATやGitHub Appの秘密鍵を使っていたサービスの実装を大きく変えず、また自動でInstallation Access Tokenを取得し有効期限内で再使用するライブラリを開発しました。メルカリではGo言語を標準的に使用していることから、Go言語でGitHub関連のサービスを作成する代表的なライブラリである<a href="https://github.com/google/go-github">google/go-github</a>をベースにGitHub APIを使うためのクライアントを取得できるようにしました。なお、既にgo-githubライブラリを使っているサービスの場合、Service Accountの設定とライブラリの入れ替えのみでToken Serverに移行できるようにしました。</p>
<h2>Token Server用ライブラリの構成</h2>
<p>go-githubライブラリは初期化の際、任意のhttp Clientを指定することができます。このhttp Clientには、RoundTripperインターフェイスに任意のRoundTripメソッドを実装することでリクエスト送信前にリクエストの内容を変更することができます。そこでこのRoundTripメソッドを使い、リクエストを送信する前にキャッシュされているInstallation Access Tokenもしくは有効期限が切れている場合はToken Serverから新たなInstallation Access Tokenを取得し使用するようにしました。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/12/b68dde27-4-token-server-library-506x1024.png" alt="" /></p>
<p><center>Token Server用ライブラリの処理</center></p>
<p>これにより、go-githubライブラリを使用している既存のサービスのコードを1行変更するだけで、Token Serverに移行することができました。</p>
<h1>GitHub AppのLoad Balancing</h1>
<p>前述の通り、GitHub Appには1時間あたり15000回のAPIリクエスト制限があります。しかし、Token Serverは複数のGoogle Cloud上のワークロード、GitHubレポジトリのAPIリクエストを発行し、また今後の自動化サービスの増加を想定するとこの制限を超えることが予想されます。そこで、GitHub Appを複数作成し、それぞれのGitHub Appに対してInstallation Access Tokenの発行を分散することで、APIリクエストの制限を超えることなく、多くのAPI処理を提供することができます。</p>
<p>しかし、この分散は複数のGitHub AppをそれぞれのToken ServerのPodに一つずつ読み込んでLoad Balancerによってランダムにリクエストの分散を行い、あるInstallation Access Tokenの利用者が受け取るInstallation Access Tokenの発行元が複数のGitHub Appとなってしまうと、コミットステータスの書き込みを行うサービスにおいて、問題が発生します。<br />
GitHubにおいてはCIの動作結果等をあるコミットに対しerror, failure, pending, successのいずれかを書き込むことができます。しかし、この書き込みはそれぞれ書き込んだGitHub Appごとに記録されます。つまり異なるGitHub Appによってコミットステータスが書き込まれると、コミットステータスが混在してしまいます。この場合、以前失敗したステータスが上書きされることなく、別の新しいステータスのみが書き込まれ、コミットステータスがすべて成功しないとマージできないようなBranch Protectionを設定している場合、マージができなくなるという問題があります。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/12/c1a81d93-5-token-server-status.png" alt="" /></p>
<p><center>複数のGitHub Appによるステータス書き込み</center></p>
<p>ある自動化処理において最初は失敗ステータスを書き込み、その後成功ステータスを書き込むような処理を行う場合、同じGitHub Appによって行われる必要があります。もしToken Serverが複数のGitHub Appを持つ場合、最初の失敗ステータスをGitHub App 1で書き込んでしまうと、その後の成功ステータスをGitHub App 2で上書きすることができず、GitHub App 1からの失敗ステータスとGitHub App 2からの成功ステータスが混在してしまいます。</p>
<p>そこで、Installation Access Tokenを発行する対象ごとに常に同じGitHub Appを使うようにするために、1つのToken ServerのPodで複数のGitHub Appを読み込み、GitHubにおいてはリポジトリとブランチ名・Google CloudにおいてはサービスアカウントによってGitHub Appを割り当てるようにしました。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/12/83ded9e1-6-token-server-index.png" alt="" /></p>
<p><center>GitHub Appの割り当て処理</center></p>
<p>このように、GitHub Appのインデックスをリポジトリとブランチ名、サービスアカウントによって割り当てることで、同じリポジトリとブランチ名、サービスアカウントに同じGitHub Appが割り当てられるようにしました。</p>
<h1>まとめ</h1>
<p>Token ServerをGoogle Cloudに拡張し、より多くのサービスがGitHubにアクセスする際に有効期限の短い認証情報を使うようにすることで、有効期限が長い認証情報を減らすことができました。そして既存のサービスを最小限の変更でToken Serverに移行できるようなライブラリを開発することで、移行を容易にしました。また、実運用の中で発見した問題も解決し、セキュアかつ快適なGitHubの自動化サービスの運用をサポートしました。<br />
メルカリのSecurity Teamでは、このような認証情報の有効期限の短いものへの切り替えを推進しており、今後もこのような取り組みを進めていく予定です。</p>
<p>Security Teamにおける採用情報については<a href="https://careers.mercari.com/">Mercari Career</a>をご覧ください。</p>
- メルペイのエンジニアリングへの投資を推進する仕組みhttps://engineering.mercari.com/blog/entry/20241204-merpay-engineering-investment/https://engineering.mercari.com/blog/entry/20241204-merpay-engineering-investment/<p>はじめに こんにちは。メルペイVPoEの@keigowです。 この記事は、Merpay & Mercoin Advent Calendar 2024 の記事です。 昨年のAdvent CalendarでCTOの@ […]</p>
Wed, 04 Dec 2024 10:00:36 GMT<h2>はじめに</h2>
<p>こんにちは。メルペイVPoEの<a href="https://x.com/keigow">@keigow</a>です。<br />
この記事は、<a href="https://engineering.mercari.com/blog/entry/20241125-merpay-mercoin-advent-calendar-2024/">Merpay & Mercoin Advent Calendar 2024</a> の記事です。</p>
<p>昨年のAdvent CalendarでCTOの@kimurasからロードマップを作成する取り組みについての記事(<a href="https://engineering.mercari.com/blog/entry/20231225-creating-mercari-engineering-roadmap/">メルカリEngineering Roadmapの作成とその必要性</a>)を出しました。</p>
<p>こちらでも語られているようにエンジニアリング領域への投資を推進するためにロードマップは重要な役割を果たしますが、当時はメルカリのEngineering Roadmapという形で、メルペイについてはまだ作ることができていませんでした。</p>
<p>本日はメルペイでのエンジニアリングへの投資を推進するために、整備してきたEngineering Roadmapや、それを推進するためのEngineering Projectsという取り組みについて書きたいと思います。</p>
<h2>ビジネスロードマップとCompany OKR</h2>
<p>メルカリグループではミッションの達成に向けて中長期で成し遂げたい目標をグループのロードマップとして定義して、その下に各カンパニーでのビジネスロードマップを定義しています(<a href="https://careers.mercari.com/mercan/articles/38932/">参考</a>)。ビジネスロードマップはお客さまへの体験の向上や、新しいサービスの提供などを中心に、どのような価値をいつまでに届けていきたいのかという観点で整理されています。</p>
<p>会社の経営という観点ではロードマップをベースに四半期単位でメルペイ全体のOKR(Company OKRと呼ぶ)を設定し、それに紐づける形で配下の組織のOKRを設定していくという流れになっています。</p>
<h2>ビジネス目標とエンジニアリング投資の両立の難しさ</h2>
<p>Company OKRがビジネス目標を達成するためのものだとすると、どのようにエンジニアリング観点で重要な中長期の投資(アーキテクチャの改善、FinOpsへの投資、Test Automationなど)をスムーズに実現していくか、は非常に重要な課題です。</p>
<p>ここで触れているエンジニアリングへの投資とは、簡単なリファクタリングなどではなく、半年以上の投資を必要とする大きなプロジェクトを想定しています。このような改善への投資は実際にプロダクトを開発するエンジニアの稼働が必要なことも多いため、ビジネス目標を達成するためのエンジニアの稼働とコンフリクトが発生しがちです。</p>
<p>勿論予めCompany OKRの中でエンジニアリングへの投資についても、横に並べて優先順位をつけて実施することができるのが理想だとは思いますが、ビジネス目標の達成とエンジニアリングへの投資に対する優先順位を0/1で判断することは難しいのが現実です。</p>
<p>また、例えばリアーキテクチャなど大きなエンジニアリングへの投資は複数年にまたがるプロジェクトになることも多いため、Company OKRと比較するとそもそもフォーカスするタイムラインが異なることも判断を難しくする要因の一つとなっています。</p>
<h2>これまでのメルペイの取り組みとその課題</h2>
<p>以前のメルペイではエンジニアリング投資の推進のためのアプローチとして、Company OKRと同様に四半期ごとにEngineering OKRを定義していました。エンジニア組織として重要な課題がフォーカスされて、インシデントの削減など成果を生み出せたものもありましたが、大きく2つの課題がありました。</p>
<p>1つは上述したような優先順位の判断が個々の裁量に委ねられてしまうことが多く、ビジネス上必要な開発を優先することで、Engineering OKRで設定していた目標の進捗を出すことが難しいケースが発生すること。もう1つは前者の課題から、どうしても緊急度の低い(が重要度は高い)エンジニアリング投資の推進は、不確実性が伴うため、OKRという仕組みに求められる厳密なトラッキングに向いていない部分があることです。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/12/2ec20bda--2024-12-03-23.10.58.png" alt="Engineering OKRの関係性" /></p>
<h2>投資のバランスとプログラム型組織</h2>
<p>メルペイのプロダクト組織は以前記事(<a href="https://engineering.mercari.com/blog/entry/20231202-merpay-program-organization/">メルペイのProgram型組織への移行</a>)にしたように、2022年の10月からプログラム型組織(大きなドメイン毎にプロダクトマネージャーやエンジニアを含めたクロスファンクショナルなチームが複数集まっている)に移行しました。</p>
<p>このプログラム型組織に移行した理由の1つに前者の課題(ビジネス目標とエンジニアリング投資のバランス)の解決がありました。現実問題として会社単位での優先順位を0/1で決めることが難しいようなエンジニアリングへの投資であっても、ドメインやチーム毎の単位で見ると繁閑の波があり、隙間時間を利用したり、プロジェクトの推進方法を工夫することで、エンジニアリングへの投資を生み出す余地も存在します。そのため、判断自体を会社単位で行うのではなく、ドメインごとに分割したプログラムという単位で行えるようにすることで、よりスムーズなエンジニアリング投資の推進が行えるようにしました。</p>
<p>個別のプログラムにはプロダクトに責任を負うProduct Headと、エンジニアリングに責任を負うEngineering Headを置くことで、各プログラムの単位でビジネスへの目標とエンジニアリング投資のバランスの意思決定が行えるようにしています。</p>
<p>また各プログラムで作成するロードマップに対して四半期毎にストラテジーレビューという会議体でCPO、CTO、VPoEによるレビューを行うことで、プライオリティの認識をすり合わせ、推進するためのリソースの調整を行っています。</p>
<h2>エンジニアリング投資の推進とEngineering Projects</h2>
<p>後者の課題(エンジニアリング投資の推進に対するOKRという仕組みのアンマッチ)に対しては、Engineering OKRを設定するのではなく、2023年7月からEngineering Projectsという仕組みを作り対処をするようにしました。</p>
<p>Engineering Projectsは中長期でエンジニア組織として推進したいリアーキテクチャやFinOpsなどのプロジェクトを四半期ごとに選定し、隔週の定例ミーティングで進捗をトラッキングします。期によって異なりますが、FY2025 2Q(2024年10-12月期)では6つのプロジェクトを選定しています。トラッキングは仕組み上OKRの運用に似ているところもありますが、違いとしてObjectiveなどの設定はなく、中長期に渡るプロジェクトの推進を前提とした仕組みです。中長期の計画をベースに四半期の目標を設定し、隔週で進捗のブロッカーとなりうるものの排除や、必要に応じた進め方の調整などを行えるようにしています。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/12/29d4dccd--2024-12-03-23.11.43.png" alt="Engineering ProjectsとProgram OKR" /></p>
<h2>Engineering Roadmapの作成</h2>
<p>本来、上図の関係で示したようにEngineering Roadmapがあり、そこから各四半期に実施すべきEngineering Projectsがクリアになっていくべきではあるのですが、メルペイのEngineering Roadmapは今年の6月に作成が完了しました。もともとEngineering Projectsとして定義されていたプロジェクトについては、ある程度ロードマップが明確なものも多かったのでそれらのプロジェクトに対しては改めて取りまとめるという形で整理しました。</p>
<p>それらの取り組みに加え、今後のAI活用の取り組みや、新しい決済プラットフォーム基盤などFoundation領域への取り組みも合わせて作っています。</p>
<h2>課題と今後の取り組み</h2>
<p>プログラム組織やEngineering Projectsの実施に比べ、Engineering Roadmapについてはまだ出来たばかりであり、運用方法やアップデートの方法についてもまだ手探りの状態になってしまっている部分があります。それでも、これまで可視化しきれていなかった中長期の取り組みを可視化することができ、エンジニアリング組織として取り組むべき課題を明確にできた部分があると思っています。</p>
<p>今後もロードマップの運用練度を高めていきつつ、ビジネスロードマップの実現に貢献するための、エンジニアリング投資の実現方法を模索していきたいと思います。</p>
<h2>おわりに</h2>
<p>次の記事は@sakabeさんです。引き続き<a href="https://engineering.mercari.com/blog/entry/20241125-merpay-mercoin-advent-calendar-2024/">Merpay & Mercoin Advent Calendar 2024</a>をお楽しみください。</p>
- 【12/7スタート】連載:メルカリ ハロ 開発の裏側 – Flutterと支える技術 –https://engineering.mercari.com/blog/entry/20241129-mercari-hallo-2024/https://engineering.mercari.com/blog/entry/20241129-mercari-hallo-2024/<p>こんにちは。メルカリのQA エンジニアリングマネージャーの@____rina____です。 この度、スキマバイトサービス「メルカリ ハロ」が Google Play ベスト オブ 2024「社会貢献部門 大賞」を受賞しま […]</p>
Fri, 29 Nov 2024 15:30:13 GMT<p>こんにちは。メルカリのQA エンジニアリングマネージャーの<a href="https://engineering.mercari.com/blog/author/underscore42rina/">@____rina____</a>です。</p>
<p>この度、スキマバイトサービス「メルカリ ハロ」が Google Play ベスト オブ 2024「社会貢献部門 大賞」を受賞しました!メルカリ ハロは、お好きな時間に最短1時間から働ける「空き時間おしごとアプリ」として、多くの方にご利用いただいています。スマホ一つで仕事を探し、働き、給与をもらうまでの一連のプロセスを簡単に完結できます。</p>
<p><img loading="lazy" src="https://storage.googleapis.com/prd-engineering-asset/2024/11/ecdadc6e-mercarihallo__googleaward-1024x537.jpg" alt="" width="580" height="304" class="size-large wp-image-32153" srcset="https://storage.googleapis.com/prd-engineering-asset/2024/11/ecdadc6e-mercarihallo__googleaward-1024x537.jpg 1024w, https://storage.googleapis.com/prd-engineering-asset/2024/11/ecdadc6e-mercarihallo__googleaward-300x157.jpg 300w, https://storage.googleapis.com/prd-engineering-asset/2024/11/ecdadc6e-mercarihallo__googleaward-768x403.jpg 768w, https://storage.googleapis.com/prd-engineering-asset/2024/11/ecdadc6e-mercarihallo__googleaward-1200x630.jpg 1200w, https://storage.googleapis.com/prd-engineering-asset/2024/11/ecdadc6e-mercarihallo__googleaward.jpg 1429w" sizes="(max-width: 580px) 100vw, 580px" /> </p>
<p>そこで今回は、私たちがどのようにこのアプリを開発してきたのか、特にFlutterを中心とした技術面に焦点を当てて、連載シリーズをお届けします。開発の舞台裏から見えてくる様々な工夫やチャレンジについても触れていく予定です。</p>
<table>
<thead>
<tr>
<th>Theme / Title</th>
<th>Author</th>
</tr>
</thead>
<tbody>
<tr>
<td><a href="https://engineering.mercari.com/blog/entry/20241207-mercari-hallo-2024/" title="Acceptance criteria: QA's quality boost">Acceptance criteria: QA’s quality boost</a></td>
<td><a href="https://x.com/____rina____" title="____rina____">@____rina____</a></td>
</tr>
<tr>
<td><a href="https://engineering.mercari.com/en/blog/entry/20241210-from-embedded-to-standalone-a-newcomers-transition-to-hallo-flutter-app-development/" title="From Embedded to Standalone: A Newcomer’s Transition to Hallo Flutter App Development">From Embedded to Standalone: A Newcomer’s Transition to Hallo Flutter App Development</a></td>
<td><a href="https://engineering.mercari.com/en/blog/author/Cherry%20Lu/" title="@cherry">@cherry</a></td>
</tr>
<tr>
<td><a href="https://engineering.mercari.com/blog/entry/20241210-flutter-hallo-design-system/" title="メルカリ ハロのデザインシステムとFlutter">メルカリ ハロのデザインシステムとFlutter</a></td>
<td><a href="https://x.com/atsumo" title="atsumo">@atsumo</a></td>
</tr>
<tr>
<td><a href="https://engineering.mercari.com/blog/entry/20241214-mercari-hallo-push-notificaiton-and-crm-integration-android/" title="メルカリ ハロのプッシュ通知と CRM integration の話(Android編)">メルカリ ハロのプッシュ通知と CRM integration の話(Android編)</a></td>
<td><a href="https://bsky.app/profile/sintario.bsky.social" title="@sintario_2nd">@sintario_2nd</a></td>
</tr>
<tr>
<td><a href="https://engineering.mercari.com/blog/entry/20241219-mercari-hallo-qa-strategy-2024/" title="メルカリ ハロにおけるFlutterアプリのQA戦略:クロスプラットフォーム開発のメリットと注意点">メルカリ ハロにおけるFlutterアプリのQA戦略:クロスプラットフォーム開発のメリットと注意点</a></td>
<td><a href="https://x.com/umetsuyu" title="um">@um</a></td>
</tr>
<tr>
<td><a href="https://engineering.mercari.com/en/blog/entry/20241221-flutter-forward-crafting-type-safe-native-interfaces-with-pigeon/" title="Flutter Forward: Crafting Type-Safe Native Interfaces with Pigeon">Flutter Forward: Crafting Type-Safe Native Interfaces with Pigeon</a></td>
<td><a href="https://x.com/howie_zuo" title="howie">@howie</a></td>
</tr>
<tr>
<td><a href="https://engineering.mercari.com/blog/entry/20241222-mercari-hallo-flutter-development-and-sre/" title="メルカリハロのFlutter開発とSRE">メルカリハロのFlutter開発とSRE</a></td>
<td><a href="https://x.com/gymnstcs" title="@naka">@naka</a></td>
</tr>
<tr>
<td><a href="https://engineering.mercari.com/en/blog/entry/20241224-how-to-unit-test-mercari-hallo-flutter-app/" title="How to unit-test Mercari Hallo Flutter app">How to unit-test Mercari Hallo Flutter app</a></td>
<td><a href="https://engineering.mercari.com/en/blog/author/powdream/" title="@Heejoon">@Heejoon</a></td>
</tr>
</tbody>
</table>
<p>初回の記事は、@____rina____が執筆予定です。技術的な詳細から開発における試行錯誤まで、多面的にご紹介します。<br />
今回の連載シリーズは、メルカリ アドベントカレンダーとのW掲載となります。アドベントカレンダーでは、メルカリの多彩な技術者たちが自社の最新技術や取り組みについて詳しく紹介しています。私たちの「メルカリ ハロ 開発の裏側」シリーズもその一環として、さらなる技術的洞察を提供できればと考えています。</p>
<p>アドベントカレンダーと合わせてお楽しみいただくことで、メルカリが現在どのような技術の潮流と向き合い、どのように成長を遂げているのか、広範囲に理解を深めていただけることと思います。是非、見逃さずにご注目ください!</p>
<p>ブログのアップデートに関しては、メルカリ公式DevX(旧Twitter)の <a href="https://x.com/mercaridevjp" title="@mercaridevjp">@mercaridevjp</a> でも随時お知らせいたします。ハッシュタグ#メルカリハロ開発の裏側 でぜひ検索し、最新情報をチェックしてください。</p>
<p>メルカリの新たな挑戦がどのように形作られているのか、その技術的な旅路をお楽しみいただければ幸いです。どうぞお楽しみに!</p>
- 「Merpay & Mercoin Advent Calendar 2024」開催のお知らせhttps://engineering.mercari.com/blog/entry/20241125-merpay-mercoin-advent-calendar-2024/https://engineering.mercari.com/blog/entry/20241125-merpay-mercoin-advent-calendar-2024/<p>こんにちは。メルペイ Engineering Engagement チームの mikichin です。 今年も、メルカリグループは Advent Calendar を実施します! ▶Mercari Advent Cale […]</p>
Thu, 28 Nov 2024 10:00:14 GMT<p>こんにちは。メルペイ Engineering Engagement チームの <a href="https://twitter.com/chida_miki" title="mikichin">mikichin</a> です。<br />
今年も、メルカリグループは Advent Calendar を実施します!<br />
<br />
▶<a href="https://engineering.mercari.com/blog/entry/20241125-mercari-advent-calendar-2024">Mercari Advent Calendar 2024 はこちら</a><br />
</p>
<h1>Merpay & Mercoin Advent Calendar とは?</h1>
<p>Advent Calendar の習慣にもとづいて、メルペイ・メルコインのエンジニアがプロダクトや会社で利用している技術、興味のある技術分野やちょっとしたテクニックなど知見をアウトプットしていきます。このAdvent Calendarを通じてクリスマスまでの毎日を楽しく過ごしていただければと思っています。</p>
<h3>2023年のMercari / Merpay Advent Calendar はこちら</h3>
<ul>
<li><a href="https://engineering.mercari.com/blog/entry/20231124-mercari-advent-calendar-2023/">Mercari Advent Calendar 2023</a>, <a href="https://engineering.mercari.com/blog/entry/20231124-merpay-advent-calendar-2023/">Merpay Advent Calendar 2023</a><br />
</li>
</ul>
<h1>公開予定表 (こちらは、後日、各記事へのリンク集になります)</h1>
<table>
<thead>
<tr>
<th style="text-align: left;">Theme / Title</th>
<th style="text-align: left;">Author</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: left;"><a href="https://engineering.mercari.com/blog/entry/20241204-merpay-engineering-investment/" title="メルペイのエンジニアリングへの投資を推進する仕組み">メルペイのエンジニアリングへの投資を推進する仕組み</a></td>
<td style="text-align: left;">@keigow</td>
</tr>
<tr>
<td style="text-align: left;"><a href="https://engineering.mercari.com/blog/entry/20241204-developing-bff-using-grpc-federation/" title="gRPC Federationを使ったBFF開発の実態">gRPC Federationを使ったBFF開発の実態</a></td>
<td style="text-align: left;">@sakabe</td>
</tr>
<tr>
<td style="text-align: left;"><a href="https://engineering.mercari.com/blog/entry/20241204-first-seven-months-new-grads/" title="メルカリ新卒1年目のエンジニアが最初の7ヶ月間でやったこと">メルカリ新卒1年目のエンジニアが最初の7ヶ月間でやったこと</a></td>
<td style="text-align: left;">@hibagon</td>
</tr>
<tr>
<td style="text-align: left;"><a href="https://engineering.mercari.com/en/blog/entry/20241206-the-race-condition-in-multiple-db-transactions-and-the-solutions/" title="The Race Condition in multiple DB transactions and the solutions">The Race Condition in multiple DB transactions and the solutions</a></td>
<td style="text-align: left;">@timo</td>
</tr>
<tr>
<td style="text-align: left;"><a href="https://engineering.mercari.com/blog/entry/20241209-d304ea2a4c/" title="HPAとVPAによるストリーミング処理のリソース最適化">HPAとVPAによるストリーミング処理のリソース最適化</a></td>
<td style="text-align: left;">@siyuan</td>
</tr>
<tr>
<td style="text-align: left;"><a href="https://engineering.mercari.com/blog/entry/20241210-f7c478382a/" title="WYSIWYGウェブページビルダーを支える技術とSever Driven UIへの拡張">WYSIWYGウェブページビルダーを支える技術とSever Driven UIへの拡張</a></td>
<td style="text-align: left;">@togami</td>
</tr>
<tr>
<td style="text-align: left;"><a href="https://engineering.mercari.com/blog/entry/20241211-8351140ebb/" title="効率的な情報検索を可能にするSlackの検索テクニック">効率的な情報検索を可能にするSlackの検索テクニック</a></td>
<td style="text-align: left;">@pooh</td>
</tr>
<tr>
<td style="text-align: left;"><a href="https://engineering.mercari.com/blog/entry/20241213-mercari-engineering-culture/" title="メルカリのエンジニアリングカルチャーについて">メルカリのエンジニアリングカルチャーについて</a></td>
<td style="text-align: left;">@Joraku</td>
</tr>
<tr>
<td style="text-align: left;"><a href="https://engineering.mercari.com/en/blog/entry/20241212-learnings-about-swift-testing/" title="Learnings About Swift Testing">Learnings About Swift Testing</a></td>
<td style="text-align: left;">@Cyan</td>
</tr>
<tr>
<td style="text-align: left;"><a href="https://engineering.mercari.com/en/blog/entry/20241214-from-airflow-to-argo-workflows-and-dbt-python-models/" title="From Airflow to Argo Workflows and dbt Python models">From Airflow to Argo Workflows and dbt Python models</a></td>
<td style="text-align: left;">@Yani</td>
</tr>
<tr>
<td style="text-align: left;"><a href="https://engineering.mercari.com/blog/entry/20241212-mercaris-seamless-item-feed-integration-bridging-the-gap-between-systems/" title="メルカリにおける商品フィードシステムのリアーキテクチャ">メルカリにおける商品フィードシステムのリアーキテクチャ</a><br /><a href="https://engineering.mercari.com/en/blog/entry/20241212-mercaris-seamless-item-feed-integration-bridging-the-gap-between-systems/" title="Mercari’s Seamless Item Feed Integration: Bridging the Gap Between Systems">Mercari’s Seamless Item Feed Integration: Bridging the Gap Between Systems</a></td>
<td style="text-align: left;">@hiramekun</td>
</tr>
<tr>
<td style="text-align: left;"><a href="https://engineering.mercari.com/blog/entry/20241216-9c7bd72262/" title="Argo Workflowsを導入して複数バッチの管理を行った話">Argo Workflowsを導入して複数バッチの管理を行った話</a></td>
<td style="text-align: left;">@goro</td>
</tr>
<tr>
<td style="text-align: left;"><a href="https://engineering.mercari.com/blog/entry/20241218-detect-time-now-by-data-flow-analysis/" title="Spannerのよくあるミスをデータフロー解析で検知する">Spannerのよくあるミスをデータフロー解析で検知する</a></td>
<td style="text-align: left;">@kobaryo</td>
</tr>
<tr>
<td style="text-align: left;"><a href="https://engineering.mercari.com/blog/entry/20241219-e9ad9948c2/" title="メルペイにおける機械学習システム運用時の工夫">メルペイにおける機械学習システム運用時の工夫</a></td>
<td style="text-align: left;">@rio</td>
</tr>
<tr>
<td style="text-align: left;"><a href="https://engineering.mercari.com/blog/entry/20241221-invoice-payment/" title="事業者請求払い: 多様な決済を支える決済基盤の仕組み">事業者請求払い: 多様な決済を支える決済基盤の仕組み</a></td>
<td style="text-align: left;">@komatsu</td>
</tr>
<tr>
<td style="text-align: left;"><a href="https://engineering.mercari.com/blog/entry/20241221-df9d144586/" title="PaymentPlatformにおける仕訳IDを活用した会計プロセスの最適化">PaymentPlatformにおける仕訳IDを活用した会計プロセスの最適化</a></td>
<td style="text-align: left;">@abcdefuji</td>
</tr>
<tr>
<td style="text-align: left;"><a href="https://engineering.mercari.com/blog/entry/20241223-batch-slo/" title="バッチにおけるSLO定義とその運用方法">バッチにおけるSLO定義とその運用方法</a></td>
<td style="text-align: left;">@iwata</td>
</tr>
<tr>
<td style="text-align: left;"><a href="https://engineering.mercari.com/blog/entry/20241221-leading-a-project-to-migrate-hundreds-of-screens-to-swiftui-jetpack-compose-from-uikit-androidview-in-merpay/" title="メルペイの数百画面をSwiftUI/Jetpack Composeに移行するプロジェクトを推進する">メルペイの数百画面をSwiftUI/Jetpack Composeに移行するプロジェクトを推進する</a><br /><a href="https://engineering.mercari.com/en/blog/entry/20241221-leading-a-project-to-migrate-hundreds-of-screens-to-swiftui-jetpack-compose-from-uikit-androidview-in-merpay/" title="Leading a project to migrate hundreds of screens to SwiftUI/Jetpack Compose from UIKit / AndroidView in Merpay">Leading a project to migrate hundreds of screens to SwiftUI/Jetpack Compose from UIKit / AndroidView in Merpay</a></td>
<td style="text-align: left;">@masamichi</td>
</tr>
<tr>
<td style="text-align: left;"><a href="https://engineering.mercari.com/blog/entry/20241225-engineering-roadmap/" title="メルカリのEngineering Roadmapの具体的な運用について">メルカリのEngineering Roadmapの具体的な運用について</a></td>
<td style="text-align: left;">@kimuras</td>
</tr>
</tbody>
</table>
<p>初日の記事は keigowさんです。引き続きお楽しみください。</p>
- 「Mercari Advent Calendar 2024」開催のお知らせhttps://engineering.mercari.com/blog/entry/20241125-mercari-advent-calendar-2024/https://engineering.mercari.com/blog/entry/20241125-mercari-advent-calendar-2024/<p>こんにちは。メルカリ Engineering Officeのohitoです。 今年もメルカリとメルペイ・メルコインで2本のAdvent Calendarを実施します! ▶Merpay & Mercoin Adve […]</p>
Thu, 28 Nov 2024 10:00:02 GMT<p>こんにちは。メルカリ Engineering Officeのohitoです。<br />
今年もメルカリとメルペイ・メルコインで2本のAdvent Calendarを実施します!<br />
<br />
▶<a href="https://engineering.mercari.com/blog/entry/20241125-merpay-mercoin-advent-calendar-2024">Merpay & Mercoin Advent Calendar 2024 はこちら</a><br />
</p>
<h1>Mercari Advent Calendar とは?</h1>
<p>メルカリグループのエンジニアがプロダクトや会社で利用している技術、興味のある技術分野やちょっとしたテクニックなど知見をアウトプットしていきます。このAdvent Calendarを通じてクリスマスまでの毎日を楽しく過ごしていただければと思っています。</p>
<h3>2023年のMercari / Merpay Advent Calendar</h3>
<ul>
<li><a href="https://engineering.mercari.com/blog/entry/20231124-mercari-advent-calendar-2023/">Mercari Advent Calendar 2023</a></li>
<li><a href="https://engineering.mercari.com/blog/entry/20231124-merpay-advent-calendar-2023/">Merpay Advent Calendar 2023</a><br />
</li>
</ul>
<h1>公開予定表 (こちらは、後日、各記事へのリンク集になります)</h1>
<table>
<thead>
<tr>
<th style="text-align: left;">Theme / Title</th>
<th style="text-align: left;">Author</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: left;"><a href="https://engineering.mercari.com/blog/entry/20241203-token-server-google-cloud/">Google CloudからGitHub PATと秘密鍵をなくす – Token ServerのGoogle Cloudへの拡張</a></td>
<td style="text-align: left;">@Security Engineering</td>
</tr>
<tr>
<td style="text-align: left;"><a href="https://engineering.mercari.com/blog/entry/20241204-keeping-user-journey-slos-up-to-date-with-e2e-testing-in-a-microservices-architecture/">E2E Testを用いたマイクロサービスアーキテクチャでのUser Journey SLOの継続的最新化</a></td>
<td style="text-align: left;">@yakenji</td>
</tr>
<tr>
<td style="text-align: left;"><a href="https://engineering.mercari.com/blog/entry/20241207-mercari-hallo-2024/">Acceptance criteria: QA’s quality boost</a></td>
<td style="text-align: left;">@….rina….</td>
</tr>
<tr>
<td style="text-align: left;"><a href="https://engineering.mercari.com/en/blog/entry/20241206-streamlining-security-incident-response-with-automation-and-large-language-models/">Streamlining Incident Response with Automation and Large Language Models</a></td>
<td style="text-align: left;">@florencio</td>
</tr>
<tr>
<td style="text-align: left;"><a href="https://engineering.mercari.com/en/blog/entry/20241209-insights-from-finops-x-europe-2024-a-scholars-journey/">Insights from FinOps X Europe 2024: A Scholar’s Journey?</a></td>
<td style="text-align: left;">@pakuchi</td>
</tr>
<tr>
<td style="text-align: left;"><a href="https://engineering.mercari.com/en/blog/entry/20241209-the-react-profiler-demystified/">React Profiler Demystified</a></td>
<td style="text-align: left;">@samlee</td>
</tr>
<tr>
<td style="text-align: left;"><a href="https://engineering.mercari.com/en/blog/entry/20241210-from-embedded-to-standalone-a-newcomers-transition-to-hallo-flutter-app-development/">From Embedded to Standalone: A Newcomer’s Transition to Hallo Flutter App Development</a></td>
<td style="text-align: left;">@cherry</td>
</tr>
<tr>
<td style="text-align: left;"><a href="https://engineering.mercari.com/blog/entry/20241210-flutter-hallo-design-system/">メルカリ ハロのデザインシステムとFlutter</a></td>
<td style="text-align: left;">@atsumo</td>
</tr>
<tr>
<td style="text-align: left;"><a href="https://engineering.mercari.com/blog/entry/20241213-new-production-readiness-check-experience-in-mercari/">New Production Readiness Check experience in Mercari</a></td>
<td style="text-align: left;">@mshibuya</td>
</tr>
<tr>
<td style="text-align: left;"><a href="https://engineering.mercari.com/en/blog/entry/20241213-from-good-to-great-evolving-your-role-as-a-quality-consultant/">From Good to Great: Evolving Your Role as a Quality Consultant</a></td>
<td style="text-align: left;">@Udit</td>
</tr>
<tr>
<td style="text-align: left;"><a href="https://engineering.mercari.com/blog/entry/20241214-mercari-hallo-push-notificaiton-and-crm-integration-android/">メルカリ ハロのプッシュ通知と CRM integration の話(Android編)</a></td>
<td style="text-align: left;">@sintario_2nd</td>
</tr>
<tr>
<td style="text-align: left;"><a href="https://engineering.mercari.com/en/blog/entry/20241215-llms-at-work/">LLMs at Work: Outsourcing External Service Review Grunt Work to AI</a></td>
<td style="text-align: left;">@danny, simon</td>
</tr>
<tr>
<td style="text-align: left;"><a href="https://engineering.mercari.com/blog/entry/20241216-mercari-tech-radar-initiative/">メルカリ Tech Radarの取り組み</a></td>
<td style="text-align: left;">@motokiee</td>
</tr>
<tr>
<td style="text-align: left;"><a href="https://engineering.mercari.com/blog/entry/20241217-github-branch-protection/">GitHubのBranch Protectionの突破方法</a></td>
<td style="text-align: left;">@iso</td>
</tr>
<tr>
<td style="text-align: left;"><a href="https://engineering.mercari.com/blog/entry/20241202-6c83b3dd89/">ナレッジマネジメントへの挑戦</a></td>
<td style="text-align: left;">@raven</td>
</tr>
<tr>
<td style="text-align: left;"><a href="https://engineering.mercari.com/blog/entry/20241219-mercari-hallo-qa-strategy-2024/">メルカリ ハロにおけるFlutterアプリのQA戦略:クロスプラットフォーム開発のメリットと注意点</a></td>
<td style="text-align: left;">@um</td>
</tr>
<tr>
<td style="text-align: left;"><a href="https://engineering.mercari.com/blog/entry/20241220-mscp-jamf-api-macos-security-configs-iac/">mSCPとJamf Pro APIによるmacOSセキュリティ設定の手動IaC化の試行</a></td>
<td style="text-align: left;">@yu</td>
</tr>
<tr>
<td style="text-align: left;"><a href="https://engineering.mercari.com/en/blog/entry/20241221-flutter-forward-crafting-type-safe-native-interfaces-with-pigeon/">Flutter Forward: Crafting Type-Safe Native Interfaces with Pigeons</a></td>
<td style="text-align: left;">@howie</td>
</tr>
<tr>
<td style="text-align: left;"><a href="https://engineering.mercari.com/blog/entry/20241222-mercari-hallo-flutter-development-and-sre/">メルカリハロのFlutter開発とSRE</a></td>
<td style="text-align: left;">@naka</td>
</tr>
<tr>
<td style="text-align: left;"><a href="https://engineering.mercari.com/en/blog/entry/20241223-good-tools-are-rare-we-should-make-more/">Good tools are rare. We should make more!</a></td>
<td style="text-align: left;">@klausa</td>
</tr>
<tr>
<td style="text-align: left;"><a href="https://engineering.mercari.com/blog/entry/20241223-a-smooth-cdn-provider-migration-and-future-initiatives/">スムーズなCDNプロバイダーの移行とその先の取り組み</a></td>
<td style="text-align: left;">@hatappi</td>
</tr>
<tr>
<td style="text-align: left;"><a href="https://engineering.mercari.com/en/blog/entry/20241224-how-to-unit-test-mercari-hallo-flutter-app/">How to unit-test Mercari Hallo Flutter app</a></td>
<td style="text-align: left;">@Heejoon</td>
</tr>
<tr>
<td style="text-align: left;"><a href="https://engineering.mercari.com/blog/entry/20241224-spannar-data-boost/">Spanner Data Boostを活用したリアルタイムなリコンサイルエラーの検出</a></td>
<td style="text-align: left;">@yuki_watanabe</td>
</tr>
<tr>
<td style="text-align: left;"><a href="https://engineering.mercari.com/blog/entry/20241225-engineering-roadmap/">メルカリのEngineering Roadmapの具体的な運用について</a></td>
<td style="text-align: left;">@kimuras</td>
</tr>
</tbody>
</table>
<p>最初の記事は、「<a href="https://engineering.mercari.com/blog/entry/20241203-token-server-google-cloud/">Google CloudからGitHub PATと秘密鍵をなくす – Token ServerのGoogle Cloudへの拡張</a>」です。</p>
<p>どうぞお楽しみに!</p>
- mercari.go #27 を開催しました #mercarigohttps://engineering.mercari.com/blog/entry/20241111-4986eb8e8c/https://engineering.mercari.com/blog/entry/20241111-4986eb8e8c/<p>はじめに こんにちは、mercari.go スタッフの kobaryo と earlgray です。 9月19日にメルカリ主催の Go 勉強会 mercari.go #27 を YouTube のオンライン配信にて開催し […]</p>
Mon, 11 Nov 2024 13:22:45 GMT<h2>はじめに</h2>
<p>こんにちは、mercari.go スタッフの kobaryo と earlgray です。</p>
<p>9月19日にメルカリ主催の Go 勉強会 <a href="https://mercari.connpass.com/event/329214/">mercari.go #27</a> を YouTube のオンライン配信にて開催しました。この記事では、当日の各発表を簡単に紹介します。動画もアップロードされていますので、こちらもぜひご覧ください。</p>
<p><iframe loading="lazy" width="560" height="315" src="https://www.youtube.com/embed/AEXVYTsM94Y?si=qx3blvmYtC_fK4nd" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe></p>
<h2>Writing profitable tests in Go</h2>
<p>1つ目のセッションは @kinbiko さんによる「Writing profitable tests in Go」です。</p>
<p>発表資料: <a href="https://drive.google.com/file/d/1CgAGa1oOJj9n7WONnljd3ohY4zQf6nNi/view?usp=sharing">Writing profitable tests in Go</a></p>
<p>利益の観点での Go によるテストの考え方というテーマで、テストを書くかどうかを決めるルールや Go でのテスト記述のテクニックについて紹介しました。テストはコードの動作を確認する他、将来での変更で問題がないことを保証する点で役に立ちます。しかし、テストには記述の時間や実行のコストが発生するため、組織の過去のインシデントの影響や給与などを基にして、インシデント対応やデバッグに費やす時間やお金を計算してコストが見合っているか考えることが重要とのことでした。また、Go でのテストにおいては、可読性やコードの品質を高めることによる利点、サブテストの可読性のためにテーブル駆動テストを強要するデメリットなどを Tips として紹介して頂きました。この他にも様々な Tips を紹介して頂いたので興味がある方はぜひご覧になってみてください。</p>
<p>テーブル駆動テストは Go では見かけることが多いですし、サブテストの可読性が高いという観点でついついテーブル駆動で記述してしまう方も多いと思います。私自身もそうでしたが、今回利点と欠点を理解することができたため、今後は適切なユースケースで利用したいと思いました。(earlgray)</p>
<h2>GC24 Recap: Interface Internals</h2>
<p>2つ目のセッションは <a href="https://x.com/task4233">@task4233</a> さんによる「GC24 Recap: Interface Internals」でした。</p>
<p>発表資料: <a href="https://speakerdeck.com/task4233/recap-interface-internals">GC24 Recap: Interface Internals</a></p>
<p>このセッションでは <a href="https://www.gophercon.com/">GopherCon 2024</a> で発表された <a href="https://www.gophercon.com/agenda/session/1343574">Interface Internals</a> の振り返りとして、インターフェースを介した関数呼び出しがどのように実行されているかを、デバッガを用いて実際にメモリ中の値を見ながら説明しました。</p>
<p>Go プログラムをアセンブリにすると、関数の処理が書かれてあるメモリアドレスを call 命令で指定することで関数呼び出しを行っていることが分かります。しかしながら、インターフェースを介するメソッド呼び出しだと動的に呼び出す関数が選ばれるため、この仕組みをそのまま用いることができません。このセッションでは、インターフェースがどのようなデータ構造により実装されているかから始め、呼び出されるメソッドのアドレスを決定する方法、またその高速化手法について説明しました。</p>
<p>密度の高いかつ Go 言語のコアな部分を扱った発表だったため、リファレンスを読みつつ何度も見てきちんと理解したいと個人的に感じました。(kobaryo)</p>
<h2>GC24 Recap: Who Tests the Tests?</h2>
<p>3つ目のセッションは @Ruslan さんによる「GC24 Recap: Who Tests the Tests?」でした。</p>
<p>発表資料: <a href="https://drive.google.com/file/d/1HVw5oSUcq8lAM2YZSr-tFf00Q5e1Jt5y/view?usp=drive_link">GC24 Recap: Who Tests the Tests?</a></p>
<p>このセッションも2つ目の GC24 Recap: Interface Internals と同様 <a href="https://www.gophercon.com/">GopherCon 2024</a> の振り返りで、<a href="https://www.gophercon.com/agenda/session/1340645">Who Tests the Tests?</a> の内容を扱いました。</p>
<p>我々はソフトウェアの品質が保たれているかの指標としてテストのカバレッジを用いますが、それではテストそのものの品質を担保することはできません。このセッションでは、テストの品質を担保する Mutation Test を紹介しました。この手法を用いることで、プログラム中の演算子や bool 値を変更した場合にテストが失敗するかをチェックし、テストが正しいプログラムのみを通すことを保証することができます。また、そのようなプログラムを AST パッケージを利用して自動で生成する方法についても説明しました。</p>
<p>内容がテストそのものの品質を担保するという興味深いものであった上、実践に移しやすい内容となっており、とても有益なセッションでした。このブログを読んでいる皆様もぜひ導入を検討してはいかがでしょうか。(kobaryo)</p>
<h2>Cloud Pub/Sub – High Speed In-App Notification Delivery</h2>
<p>4つ目のセッションは @akram さんによる「Cloud Pub/Sub – High Speed In-App Notification Delivery」です。</p>
<p>発表資料: <a href="https://drive.google.com/file/d/1RaAtCVTjLW8aMGB_JAPF65y8X_RAgbUt/view?usp=drive_link">Cloud Pub/Sub – High Speed In-App Notification Delivery</a></p>
<p>メルカリでの通知の管理を行う 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 通知を実現しているとのことでした。</p>
<p>メルカリという大規模プラットフォームにおける Pub/Sub の活用事例としてとても興味深い内容でした。非同期的なタスクの処理にパフォーマンスの課題を感じている方は Pub/Sub の導入を検討してみてはいかがでしょうか。 (earlgray)</p>
<h2>おわりに</h2>
<p>今回はGo言語のコアな部分から実用的なものまで、幅広い4つの発表をお送りしました。GopherCon 2024 に関する発表もあり、運営メンバーとしてもGoの最先端の内容を知ることができ大変勉強になりました。</p>
<p>ライブで視聴いただいた方も録画を観ていただいた方も本当にありがとうございました!</p>
<p>次回の開催もお楽しみに!<br />
イベント開催案内を受け取りたい方は、connpass グループのメンバーになってくださいね!<br />
<a href="https://mercari.connpass.com/">メルカリconnpassグループページ</a></p>
- Vision-Language Modelを活用した「見た目が近い商品」レコメンド改善の取り組みhttps://engineering.mercari.com/blog/entry/20241104-similar-looks-recommendation-via-vision-language-model/https://engineering.mercari.com/blog/entry/20241104-similar-looks-recommendation-via-vision-language-model/<p>こんにちは、メルカリのAI/LLMチームで機械学習エンジニアをしているarr0wとshoです! 本テックブログでは、Vision-Language Modelの一つであるSigLIP [1]を、メルカリの商品データ(Im […]</p>
Fri, 08 Nov 2024 14:33:38 GMT<p>こんにちは、メルカリのAI/LLMチームで機械学習エンジニアをしている<a href="https://x.com/arr0w_swe">arr0w</a>と<a href="https://x.com/akiyamasho_dev">sho</a>です!</p>
<p>本テックブログでは、Vision-Language Modelの一つである<strong>SigLIP</strong> [1]を、メルカリの商品データ(Image-Text Pairs)でファインチューニングし、メルカリの商品画像Embeddingの性能を大幅に改善したプロジェクトについて紹介します。</p>
<p>今回作成したSigLIPの性能を評価するために、<strong>商品詳細ページの「見た目が近い商品」のレコメンド機能でA/Bテストを実施しました。</strong></p>
<p style="text-align: center"><img src="https://storage.googleapis.com/prd-engineering-asset/2024/10/d93d5f67-1000000796-1024x795.png" width="512px"></p>
<p>この「見た目が近い商品」のレコメンド機能は、社内では<strong>Similar Looks</strong>と呼ばれています。作成したモデルをSimilar Looksの類似画像検索に適用し、既存モデルとの比較のためのA/Bテストを行いました。</p>
<p>そして、その結果として、主要なKPIにおいて以下のような顕著な改善が確認できました。</p>
<ul>
<li>「見た目が近い商品」のタップ率が<strong>1.5倍に増加</strong></li>
<li>商品詳細ページ経由の購入数が<strong>+14%増加</strong></li>
</ul>
<p>A/Bテストを経て、モデルの有効性が確認できたため、今回作成したモデルによるレコメンドは無事採用され、100%リリースに至りました。以降の章では、商品データを用いたSigLIPのファインチューニングやそのオフライン評価、本番デプロイのためのシステム設計など、本プロジェクトの技術的詳細について説明していきます。</p>
<h2>商品データを用いたSigLIPのファインチューニング</h2>
<h3>画像Embedding</h3>
<p>画像Embeddingは、画像内の物体やその色、種類といった特徴を数値ベクトルとして表現する技術の総称です。近年、推薦や検索など、さまざまなアプリケーションで使用されています。</p>
<p>メルカリ内でもその重要性は日々増しており、類似商品レコメンド、商品検索、不正出品の検出といった多様な文脈で画像Embeddingが使用されています。</p>
<p>本プロジェクトにてメルカリのAI/LLMチームでは、<strong>Vision-Language ModelであるSigLIP</strong>を用いて、商品画像のEmbeddingを改善する取り組みを行いました。</p>
<h3>SigLIP</h3>
<p>近年、<strong>CLIP [3]</strong>や<strong>ALIGN [4]</strong>などのように、大規模かつノイズの多い画像およびテキストがペアになったデータセット(e.g. WebLI [5])を用いたContrasive Learningで事前学習されたモデルは、ゼロショット分類や検索といった様々なタスクにおいて高い性能を発揮していることが知られています。</p>
<p><strong>SigLIP</strong>は、ICCV 2023で発表された論文で紹介されたVision-Language Modelです。SigLIPは、CLIPで使用されている従来のSoftmax Lossを、<strong>Sigmoid Loss</strong>に置き換えて事前学習を実施しています。この変更はLossの計算方法を変えるだけのシンプルなものですが、著者たちは、ImageNet [6]を使用した画像分類タスクを含む複数のベンチマークで、<strong>SigLIPは既存手法と比べてパフォーマンスが大幅に向上した</strong>と報告しています。</p>
<p>それでは、ここで、後述するメルカリの商品データを用いたSigLIPのファインチューニングのために実装したLoss関数の実装を見てみましょう。</p>
<pre><code class="language-python">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</code></pre>
<p>なお、今回のプロジェクトでは、SigLIPのベースモデルとして<a href="https://huggingface.co/google/siglip-base-patch16-256-multilingual">google/siglip-base-patch16-256-multilingual</a>を使用しました。このモデルは多言語のWebLIデータセットで訓練されており、対応言語にメルカリのサービスで主に使用されている日本語も含まれているためです。</p>
<h3>商品データを用いたファインチューニング</h3>
<p>この章から、メルカリの実データを用いたSigLIPのファインチューニングの実験環境および設定について紹介します。</p>
<p>今回の実験では、過去に出品された商品から、ランダムに抽出した約100万件のメルカリの商品データを使用して、SigLIPのファインチューニングを実施しました。SigLIPへの入力データは、商品タイトル(Text)と商品画像(Image)を用いました。これらはいずれもメルカリ上のお客さまが出品時に作成したものです。</p>
<p>訓練用のコードは<a href="https://github.com/pytorch/pytorch">PyTorch</a>と<a href="https://github.com/huggingface/transformers/">Transformers</a>ライブラリを使用して実装しました。さらに、今回は使用したデータセットは、Google Cloud Storage上で管理しており、その規模も大きかったため、データ読み込みプロセスを最適化するために<a href="https://github.com/webdataset/webdataset">WebDataset</a>を活用し、大量の学習データを効率的に扱えるようにしました。なお、WebDatasetの理解には<a href="https://github.com/webdataset/webdataset/blob/main/README.md">公式ドキュメント</a>に加え、<a href="https://zenn.dev/turing_motors/articles/petabyte_webdataset">こちらの記事</a>が大変参考になりました。</p>
<p>モデルの訓練は<strong>1台のL4 GPU</strong>を使用して実施しました。訓練パイプラインの構築には、<a href="https://cloud.google.com/vertex-ai/docs/training/overview"><strong>Vertex AI Custom Training</strong></a>を活用しています。さらに、メルカリは、<a href="https://wandb.ai/site/"><strong>Weights & Biases(wandb)</strong></a>のエンタープライズ版を契約しているため、実験のモニタリングには、そちらを活用しました。プロジェクトのタイムライン上、ML実験に割くことができる期間には制約がありましたが、初期にこれらの訓練パイプラインに投資をしたことで、<strong>結果として試行錯誤の回数を増やすことができた</strong>ように感じています。</p>
<h3>オフライン評価</h3>
<p>A/Bテストを実施する前に、既存の「見た目が近い商品」レコメンドのユーザの行動ログを使用してオフライン評価を行いました。Similar Looksは、元々、学習済みのMobileNet [2](<a href="https://huggingface.co/google/mobilenet_v2_1.4_224">google/mobilenet_v2_1.4_224</a>)から得られるImage EmbeddingをPCAで128次元に圧縮したEmbeddingを用いていました。オフライン評価では、10000件の行動ログ(セッション)を利用しました。</p>
<p>行動ログの具体例を以下に示します。query_item_idに商品詳細ページに表示されているクエリ画像となる商品のID、similar_item_idに「見た目が近い商品」セクションに表示された商品のID、clickedにその商品を見たかどうかのフラグが格納されています。</p>
<pre><code>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 |
...</code></pre>
<p>評価は画像検索タスクとして定式化し、ユーザーのクリックを正例(label:1)として扱いました。パフォーマンスの評価には、nDCG@k(k=5)とprecision@k(k=1,3)を評価指標として使用しました。</p>
<p>これにより、ユーザーの嗜好に合致した形で類似した画像をランク付けするモデルの能力を定量的に評価することができました。</p>
<p>評価にあたっては、比較のために「ランダム推薦」と「現在使われているMobileNetベースの画像検索」2つのベースラインとしました。</p>
<p>以下がオフライン評価の結果になります。</p>
<table>
<thead>
<tr>
<th style="text-align: center;">手法</th>
<th style="text-align: center;">nDCG@5</th>
<th style="text-align: center;">Precision@1</th>
<th style="text-align: center;">Precision@3</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: center;">Random</td>
<td style="text-align: center;">0.525</td>
<td style="text-align: center;">0.256</td>
<td style="text-align: center;">0.501</td>
</tr>
<tr>
<td style="text-align: center;">MobileNet</td>
<td style="text-align: center;">0.607</td>
<td style="text-align: center;">0.356</td>
<td style="text-align: center;">0.601</td>
</tr>
<tr>
<td style="text-align: center;"><strong>SigLIP + PCA</strong></td>
<td style="text-align: center;"><strong>0.647</strong></td>
<td style="text-align: center;"><strong>0.406</strong></td>
<td style="text-align: center;"><strong>0.658</strong></td>
</tr>
<tr>
<td style="text-align: center;"><strong>SigLIP</strong></td>
<td style="text-align: center;"><strong>0.662</strong></td>
<td style="text-align: center;"><strong>0.412</strong></td>
<td style="text-align: center;"><strong>0.660</strong></td>
</tr>
</tbody>
</table>
<p>評価結果から、<strong>メルカリの商品データでファインチューニングしたSigLIPのImage Encoderから得られた画像Embeddingによる画像検索は、PCAによって768次元から128次元に圧縮された場合でも、MobileNetベースの画像検索を一貫して上回る</strong>ということがわかりました。これは、あくまでオフライン評価上は「見た目が近い商品」のレコメンドにおいて、我々が構築したSigLIPモデルが優れたパフォーマンスを示したと言えます。</p>
<p>定量評価だけでなく、目視による定性評価も実施しました。約10万件の商品画像のEmbeddingが格納されたベクターストアを<a href="https://github.com/facebookresearch/faiss">FAISS</a>を用いて作成し、複数の商品で画像検索を行った結果を、以下のようにスプレッドシートにまとめ、目視でチェックしました。</p>
<p style="text-align: center"><img src="https://storage.googleapis.com/prd-engineering-asset/2024/11/8cc05bc2-image1.png" width="768px"></p>
<p>以上のオフライン評価結果から、メルカリの商品データでファインチューニングしたSigLIPのImage Encoderを用いた「見た目が近い商品」レコメンドは、定量的・定性的どちらにも既存のモデルを上回ることが明確に示されました。そのため、作成したモデルを使用してA/Bテストを実施することを決定しました。</p>
<p>次の章では、このモデルを本番環境にデプロイするためのシステム設計について説明します。</p>
<h2>システム構成</h2>
<h3>End-to-End Architecture</h3>
<p>個々のコンポーネントの詳細に入る前に、アーキテクチャの全体像を以下に示します:</p>
<p style="text-align: center"><img src="https://storage.googleapis.com/prd-engineering-asset/2024/11/48c8be80-image3.png" width="768px"></p>
<p>上図では、メルカリ本体のプラットフォームからSigLIPモデルがホスティングされたマイクロサービスへのデータの流れと、Embeddingがどのように効率的に保存され、取得されるかを示しています。これは初期バージョンですが、このモジュール化された設計により、スケーラビリティと柔軟性を確保しています。</p>
<h4>Google Container Registry</h4>
<p>モデルのデプロイは<strong>Google Container Registry(GCR)</strong>を通じて管理され、マイクロサービスのDockerイメージがここに保存されています。Dockerイメージは、GitHubリポジトリからGoogle Cloud Buildを使用したCI/CDパイプラインを介して、継続的にビルドされGCRにプッシュされます。</p>
<p>GCRを活用することで、Google Kubernetes Engine(GKE)上のデプロイメントが常に最新のコードバージョンに基づいて行われ、本番環境で稼働しているサービスへのシームレスなアップデートを実現しています。</p>
<h4>Google Pub/Sub</h4>
<p>リアルタイムのデータストリームを処理するために、<strong>Google Pub/Sub</strong>を利用しています。メルカリでは、お客さまによって新しく出品が作成されると、新規出品用ののPub/Subのtopicに、Pub/Sub メッセージがpublishされます。推薦や検索をはじめとする関連マイクロサービスがこのtopicをsubscribeすることで、新しい出品に対して、動的に対応できるシステムを実現しています。</p>
<p>今回も同様に、新規出品により発火するEventをSubscribeするWorkerを定義しました(<strong>Embedding Worker</strong>)。これにより、新しい出品が発生したら、Embeddings Workerが起動します。そこで、新規商品の画像Embeddingを算出し、ベクターデータベースに追加します。この非同期なシステムにより、メルカリの出品量に応じて効果的にスケールすることが可能になっています。</p>
<h4>Google Kubernetes Engine</h4>
<p>システムを構成する主要なマイクロサービスはGoogle Kubernetes Engine(GKE)でデプロイされています。GKEは、本取り組みのアーキテクチャにおける以下の主要サービスをホストしています: </p>
<h4>Embeddings Worker</h4>
<p>Embeddings Workerは、Pub/Subの新規出品トピックをSubscribeする重要なサービスです。新規出品の発生ごとに、以下の処理を行います: </p>
<ol>
<li>出品された商品に対応する画像をストレージから取得</li>
<li>ファインチューニングしたSigLIPモデルを使用して、画像をEmbeddingに変換</li>
<li>類似度検索のレイテンシ改善とストレージコスト削減のため、PCAにより次元を削減(768 dim → 128 dim)</li>
<li>EmbeddingをVertex AI Vector Searchに保存</li>
</ol>
<p>このプロセスにより、効率的な類似画像検索が可能になります。各Embeddingは画像の視覚的内容を表現しているため、メルカリのプラットフォーム全体で視覚的に類似した出品を容易に比較・検索することができます。</p>
<h4>Index Cleanup Cron Job</h4>
<p>メルカリでは多くのお客さまに利用されており、頻繁に新しい商品が出品され、既存の出品が売れたり、削除されたりします。</p>
<p>そのため、現在出品中の商品のみを表示し「見た目が近い商品」レコメンドの体験を向上するために、<strong>削除あるいは売却済みとなった商品を適宜Vector Storeから削除し、最新の状態に保つ</strong>必要がありました。これを実現するために、<strong>Index Cleanup Cron Job</strong>を実装しました。</p>
<p>Cronで定期実行されるこのジョブは、Vertex AI Vector Searchから古くなった出品や売却済みの出品に対応するEmbeddingを削除します。</p>
<p>現在はこのBatchで定期実行する方針で体験を大きく損ねず動作していますが、さらなる効率化を図るため、Embedding管理のリアルタイム更新についても、現在検討しています。</p>
<h4>Similar Looks Microservice & Caching</h4>
<p><strong>Similar Looks Microservice</strong>は、画像類似性機能の中核となるものです。このサービスは、商品IDを入力として受け取り、対応する画像Embeddingを<strong>Vertex AI Vector Search</strong>から取得し、最近傍探索を実行してマーケットプレイス内の類似商品を見つけます。 </p>
<p>レイテンシを削減するために、このマイクロサービスにも<strong>キャッシュ機構</strong>を実装しています。これにより、お客さまが類似商品を閲覧する際に素早いレスポンスを提供し、スムーズなユーザー体験を確保しています。</p>
<h4>Vertex AI Vector Search</h4>
<p>Embeddingの保存と検索には、<strong>Vertex AI Vector Search</strong>を使用しています。これはスケーラブルなVector Databaseで、類似したEmbeddingを効率的に検索することができます。メルカリ内の各商品画像は、SigLIPにより、ベクトルに変換され、そのベクトルはVertex AI内で商品IDごとに索引されます。</p>
<p>Vertex AIの最近傍探索アルゴリズムは非常に高速であり、データベース内に数千万件程度の膨大なEmbeddingがある場合でも、視覚的に類似した商品を高速に検索することが可能です。</p>
<h4>TensorRTを用いたモデルの最適化</h4>
<p>ファインチューニングしたSigLIPモデルのパフォーマンスを最適化し、毎秒生成される大量の出品を高速に処理するため、モデルをPyTorchからNVIDIAの高性能深層学習推論ライブラリであるTensorRTに変換しました。この変換により、推論時間が<strong>約5倍高速化</strong>されました。</p>
<h4>TensorRTについて</h4>
<p>TensorRTはニューラルネットワーク内の操作を、NVIDIA GPU上で効率的に推論できるように、最適化された行列演算のシーケンスに変換します。具体的には、性能を保った上でのモデルの重みのFP16やINT8といった少ない桁数への変換(Precision Calibration)や深層学習モデルを構成する層の融合(Layer Fusion)などの処理を行います。</p>
<p>SigLIPのような大規模モデルをメルカリのような高RPSなサービスにリリースする上で、この改善は非常に重要でした。毎秒大量の商品が出品される中、推論時間を<strong>数百ミリ秒から数十ミリ秒程度に削減できた</strong>ことで、<strong>新規出品のほぼすべての商品画像を瞬時にベクトル化</strong>し、Similar Looks Componentsが使用するVertex AI Vector Searchのインデックスに登録できるようになりました。</p>
<h3>Next Steps</h3>
<p>現在のアーキテクチャは安定しており、スケーラブルですが、私たちは以下のような改善点があると考えており、日々開発に取り組んでいます。</p>
<h4>Vector Storeのリアルタイム更新</h4>
<p>既に述べたとおり、現在、Index Cleanup Cron Jobが定期的にVertex AI Vector Searchから古くなったEmbeddingを削除しています。しかし、商品が削除されたり売却されたりした時点で即座にEmbeddingを更新するような、よりリアルタイムな方法への移行を計画しています。これにより、Cron Jobによる削除の必要性がなくなり、インデックスが常に最新の状態に保たれることが保証されます。</p>
<h4>Triton Inference Server</h4>
<p>モデル推論をより効率的に処理するため、<a href="https://developer.nvidia.com/triton-inference-server">Triton Inference Server</a>の使用も検討しています。Tritonは、異なるフレームワーク(例:TensorRT、PyTorch、TensorFlow)の複数のモデルを単一の環境にデプロイすることができます。推論処理をEmbeddings WorkerからTritonに移行することで、モデルの実行をワーカーのロジックから分離し、推論パフォーマンスのスケーリングと最適化においてより大きな柔軟性を得ることができます。</p>
<h4>SigLIPモデルを活用した新機能</h4>
<p>最後に、ファインチューニングしたSigLIPモデルを活用した新機能の開発に取り組んでいます。「見た目が近い商品」のレコメンドに限らず、開発したSigLIPモデルはたくさんの可能性を秘めていると我々は考えています。このモデルを活用したユーザー体験を向上させる計画は、他にもあるので、今後のメルカリのアップデートにご期待ください 😉</p>
<h2>おわりに</h2>
<p>今回、メルカリが自社で保有する商品データを用いて、Vision-Language ModelのSigLIPをファインチューニングし、高性能なImage Embedding Modelを構築し、「見た目が近い商品」機能の改善を行いました。</p>
<p>オフライン評価において、ファインチューニングしたSigLIPによる「見た目が近い商品」レコメンドは既存のモデルよりも高い性能を示しました。そのため、<strong>A/Bテストを実施したところ、ビジネスKPIにおいても大幅な改善が確認されました。</strong></p>
<p>本ブログの内容がVision-Language Modelのファインチューニングや評価、深層学習モデルの実サービスへのデプロイ等に興味がある皆さまのお役に立てば幸いです。</p>
<p>メルカリでは、<strong>プロダクト改善を通して大きなインパクトを生み出すことに意欲的な<a href="https://apply.workable.com/mercari/">Software Engineer</a>を募集</strong>しています。興味ある方は是非ご応募ください。</p>
<h2>参考文献</h2>
<p>[1] <a href="https://arxiv.org/abs/2303.15343">Sigmoid Loss for Language Image Pre-Training</a>, 2023<br />
[2] <a href="https://arxiv.org/abs/1704.04861">MobileNets: Efficient Convolutional Neural Networks for Mobile Vision Applications</a>, 2017<br />
[3] <a href="https://arxiv.org/abs/2103.00020">Learning Transferable Visual Models From Natural Language Supervision</a>, 2021<br />
[4] <a href="https://arxiv.org/abs/2102.05918">Scaling Up Visual and Vision-Language Representation Learning With Noisy Text Supervision</a>, 2021<br />
[5] <a href="https://arxiv.org/abs/2209.06794">PaLI: A Jointly-Scaled Multilingual Language-Image Model</a>, 2022<br />
[6] <a href="https://www.image-net.org/static_files/papers/imagenet_cvpr09.pdf">ImageNet: A Large-Scale Hierarchical Image Database</a>, 2009</p>
- Elasticsearchのshard数の増加により発生するオーバーヘッドの正体https://engineering.mercari.com/blog/entry/20241010-97cca785fa/https://engineering.mercari.com/blog/entry/20241010-97cca785fa/<p>Mercari Search Infra Teamのmrkm4ntrです。 Elasticsearchは1ノードに載り切らない量のデータも複数のshardに分割し、複数のノードに載せることで検索が可能になります。shar […]</p>
Thu, 10 Oct 2024 11:08:08 GMT<p>Mercari Search Infra Teamのmrkm4ntrです。</p>
<p>Elasticsearchは1ノードに載り切らない量のデータも複数のshardに分割し、複数のノードに載せることで検索が可能になります。shard数を増やすことで並列にスキャンするドキュメントの数が増えるためlatencyが改善します。ではshard数はいくらでも増やせるのでしょうか?もちろんそのようなことはなく、Elastic社の公式ブログ(<a href="https://www.elastic.co/jp/blog/how-many-shards-should-i-have-in-my-elasticsearch-cluster">https://www.elastic.co/jp/blog/how-many-shards-should-i-have-in-my-elasticsearch-cluster</a> )にもあるようにshard数を増やすことによるオーバーヘッドが存在します。ただしそのオーバーヘッドが具体的に何を指すのかは、先ほどの記事では明らかにされていません。本記事ではそのオーバーヘッドの正体を明らかにするとともに、実際にコストの削減を達成したことについて説明します。</p>
<h2>背景</h2>
<p>我々の運用する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倍程度かかるため、これによりコストの削減を実現しています。</p>
<p>2nd indexは24個のshardに分割されています。元々ファイルシステムキャッシュに載るようにshardサイズの2倍(最悪のmergeの場合を考えたらしい)が空きメモリ量よりも小さくなるようにshard数を決めたらしいですが、実際にmemoryのworking setなどを調査し、1ノードに2つのshard載せることができることが発覚しました。その結果全体24 shard、1ノードあたり2つのshardを載せる構成に変更しました。</p>
<p>2nd indexはmulti-tier構成でかなりコストを削減しているものも、今なお最大のリソースが必要なindexでした。そこでこのコストをさらに削減するために調査を行うことにしました。</p>
<p>CPU proflierでflame graphを取得し眺めていたところ、posting listをスキャンする処理(下図の赤枠)には検索処理において半分以下のCPUしか使われていないことがわかりました。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/10/020f9b18--2024-10-02-11.42.41.png" alt="" /></p>
<p>確かにスキャンすべきposting listが短くなるのでこのことはある程度予期していましたが、検索エンジンの処理のメインはposting listのスキャンのはずです。このスキャンを効率化するために様々な工夫がされています。例えばこの記事(<a href="https://engineering.mercari.com/blog/entry/20240424-6f298aa43b/">https://engineering.mercari.com/blog/entry/20240424-6f298aa43b/</a> )もスキャン対象を効率的にスキップする話です。では他は何にCPUを使っているのでしょうか。残りの部分について調べると、ほとんどがスキャン対象のposting listをterm dictionaryから取得する処理(以下term lookupと呼ぶ)であることがわかりました。</p>
<h2>term lookup</h2>
<p>Luceneではposting listはfieldとtermの組み合わせごとに存在します。簡単のため今は一つのfieldについて考えます。termをキーとし、posting listの場所をvalueとするhashmapを用意すればposting listの位置の取得は一瞬で終わるのですが、termの数は非常に大きいためJVMのheapに収めるのは現実的ではありません。</p>
<p>そのためLuceneはFSTというデータ構造を使い省メモリ化を実現しています。FSTとはFinite State Transducerの略称で、有限オートマトンの受理状態に値がつくことで入力をその値に変換することができるというものです。FSTはfield毎に存在し、termを入力、postling listの場所を出力(実際はtermとposting listの対応ではなく、termのprefixとprefixを共有するtermのposting listのアドレスをまとめたブロックのアドレスらしい <a href="https://github.com/apache/lucene/issues/12513">https://github.com/apache/lucene/issues/12513</a> )とすることでpostling listの取得を実現しています。 </p>
<p>この計算量は入力文字列の長さのオーダーですが、データ自体はファイルに保存する形式でmmapされているため、毎回そこから計算するのはそれなりにCPUを使うようです。</p>
<h2>term lookup回数の削減</h2>
<p>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数には比例しないので問題ないはずです。</p>
<p>パフォーマンスを検証するために、まずは開発環境で本番のデータを使って検証しました。Elasticsearchのshrink APIを使って24 shardのindexから12 shardのindexを作成することにします。shrink APIは書き込み禁止にしたindexのshardをコピーしてまとめることで、shard数が元のindexのshard数の約数となるindexを作成する仕組みです。</p>
<p>リアルタイムのデータ更新がなくても先ほどの想定からパフォーマンスは向上すると見込んでいましたが、実際には全く変化がありませんでした。実はそれは当然で、FSTはshard毎に存在するのではなくsegment毎に存在するため、term lookup回数はshard数に直接比例するのではなくsegment数に比例するからでした。shardは内部的には複数のLuceneのsegmentからなるのですが、shrink API実行直後はあくまでもsegmentのグルーピング単位を変更しただけです。そのため全体のsegment数は変わらないためパフォーマンスに変化が見られませんでした。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/10/948913ec--2024-10-10-10.21.29.png" alt="" /></p>
<p>そこでリアルタイムの更新を一定期間実施したところ、新しいsegmentの追加、mergeが繰り返され1 shardあたりのsegment数は元よりやや大きいくらいの値で収束しました。segment数はLuceneのTiered Merge Policyやドキュメントのサイズ、追加速度などによって決まり、shardあたりのドキュメント数が倍になってもsegment数は単純に倍にはなりません。再度パフォーマンスを測ったところ無事にパフォーマンスの改善が確認できました。</p>
<p>以下が本番環境に適用した結果です。薄い線が一週間前のもので濃い線が適用後のノード数の遷移を表します。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/10/797a14e3--2024-10-09-14.30.41.png" alt="" /></p>
<p>我々のクラスタはこの記事(<a href="https://engineering.mercari.com/blog/entry/20230620-f0782fd75f/">https://engineering.mercari.com/blog/entry/20230620-f0782fd75f/</a> )にあるようにCPU使用量でオートスケールするようになっていますが、明らかに必要なノード数が減少したことが見てとれます。latencyに悪影響も見られませんでした。</p>
<p>こちらが適用後のflame graphです。別要因でクエリのパターンが適用前後で変化したため単純比較はできませんが、term lookupの占める割合が小さくなることでposting listのスキャンが占める割合が相対的に大きくなっています。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/10/d9c44fc5--2024-10-08-11.02.28.png" alt="" /></p>
<p>さらにsegement数を減らせばよりコストが削減できるはずです。segment数はLuceneのTiered Merge Policyのパラメータを変更することにより調整できます。よりsegment数を減らすために<code>index.merge.policy.segments_per_tier</code>を減らしましたが、segment数は思ったほど減少しなかったと同時に、mergeのためのCPU使用量が上がったのでこちらは期待ほど有効ではありませんでした。</p>
<h2>まとめ</h2>
<p>この記事では、shard数の増加によるオーバーヘッドの一つはsegment数が増えることによるterm lookupの回数が増加であることを示しました。同じノードに複数のshardをおいている場合はshardをまとめた方がCPU負荷が低くなります(もちろんlatencyが上がる場合がありますが)。我々のindexは基本的にリアルタイムのデータ更新がありますが、データ更新がないような静的なindexにおいては、force mergeでsegement数を1にしておくとterm lookup回数が最小となりパフォーマンスが改善することが期待できるでしょう。</p>
- YAPC::Hakodate 2024に参加してきたよ! #yapcjapanhttps://engineering.mercari.com/blog/entry/20241009-yapc-hakodate-2024/https://engineering.mercari.com/blog/entry/20241009-yapc-hakodate-2024/<p>こんにちは。メルペイ Engineering Engagement チームの mikichin です。 10月5日に開催された「YAPC::Hakodate 2024」にメルカリはGold Sponsorをしておりました […]</p>
Wed, 09 Oct 2024 11:00:16 GMT<p>こんにちは。メルペイ Engineering Engagement チームの <a href="https://x.com/chida_miki" title="mikichin">mikichin</a> です。<br />
10月5日に開催された「<a href="https://yapcjapan.org/2024hakodate/" title="YAPC::Hakodate 2024">YAPC::Hakodate 2024</a>」にメルカリはGold Sponsorをしておりました。今回は参加レポートをお届けします!</p>
<h2>YAPC::Hakodate 2024 について</h2>
<p>YAPCはYet Another Perl Conferenceの略で、Perlを軸としたITに関わる全ての人のためのカンファレンスです。 Perlだけにとどまらない技術者たちが、好きな技術の話をし交流するカンファレンスで、技術者であれば誰でも楽しめるお祭りです!</p>
<h3>開催概要</h3>
<p>開催日時<br />
前夜祭:2024年10月4日(金)<br />
本編:2024年10月5日(土)<br />
場所 公立はこだて未来大学<br />
<img src="https://storage.googleapis.com/prd-engineering-asset/2024/10/b56beb02-img_4908-scaled.jpg" alt="" /></p>
<p>ブラインドに「YAPC」とあり、いよいよはじまるんだなーとワクワク。素敵な演出をありがとうございます!</p>
<h2>メルカリメンバーの登壇</h2>
<p>今回、前夜祭・本編でメルカリメンバーの登壇がありました!</p>
<h3>デジタルIDウォレットが切り開くHigh Assurance Identity Proofingの未来 / kokukuma</h3>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/10/f9908873-img_4853-scaled.jpg" alt="" /></p>
<p>今回のYAPC::Hakodate 2024本編では、非常に多くのプロポーザルの応募があったということで、前夜祭にてrejectconが開催されkokukumaが登壇しました!</p>
<p>デジタルIDウォレットを利用できる環境が整備されつつある現状を振り返りつつ、開発者として今後この状況を楽しみ尽くすための知識として、ユーザー体験の概略や、mDL/mdoc(ISO/IEC 18013-5)について解説し、デジタルIDウォレットによる身元確認が、なぜ身元確認として成立するのかを解説しました。また、これから出てきてほしい5大ニュースを6つ紹介しました。<br />
<a href="https://speakerdeck.com/kokukuma/dezitaruiduoretutogaqie-rikai-kuhigh-assurance-identity-proofingnowei-lai">https://speakerdeck.com/kokukuma/dezitaruiduoretutogaqie-rikai-kuhigh-assurance-identity-proofingnowei-lai</a></p>
<h3>フロントエンドの現在地とこれから / koba04</h3>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/10/f709bcdf-img_4922-scaled.jpg" alt="" /></p>
<p>本編14:50〜 からkoba04が登壇しました。<br />
「フロントエンドの現在とこれから」というタイトルで Server Components などの技術がなぜ登場しどういった問題を解決しようとしているのかについて取り上げ、フロントエンドの今とこれからについてを「点ではなく線」で捉えられるようまとめ、発表しました。<br />
フロントエンドエンジニアで普段触れている技術を俯瞰的に捉えたい方や、フロントエンドエンジニアではないけどフロントエンドの最近の技術や変化を把握したい方は、ぜひ資料および後日公開される動画をご確認ください。<br />
<a href="https://speakerdeck.com/koba04/hurontoentonoxian-zai-di-tokorekara">https://speakerdeck.com/koba04/hurontoentonoxian-zai-di-tokorekara</a></p>
<h2>参加メンバーの感想</h2>
<p>今回、多くのメルカリメンバーが参加していました。参加した感想をご紹介します。<br />
<img src="https://storage.googleapis.com/prd-engineering-asset/2024/10/5f2121d5-img_4889-768x1024.jpg" alt="" /><br />
<br />
<figure id="attachment_31531" aria-describedby="caption-attachment-31531" style="width: 580px" class="wp-caption aligncenter"><img loading="lazy" src="https://storage.googleapis.com/prd-engineering-asset/2024/10/3b48565c-gzlwzkxacaapvhd-768x1024.jpeg" alt="" width="580" height="773" class="size-large wp-image-31531" srcset="https://storage.googleapis.com/prd-engineering-asset/2024/10/3b48565c-gzlwzkxacaapvhd-768x1024.jpeg 768w, https://storage.googleapis.com/prd-engineering-asset/2024/10/3b48565c-gzlwzkxacaapvhd-225x300.jpeg 225w, https://storage.googleapis.com/prd-engineering-asset/2024/10/3b48565c-gzlwzkxacaapvhd-1152x1536.jpeg 1152w, https://storage.googleapis.com/prd-engineering-asset/2024/10/3b48565c-gzlwzkxacaapvhd-1200x1600.jpeg 1200w, https://storage.googleapis.com/prd-engineering-asset/2024/10/3b48565c-gzlwzkxacaapvhd.jpeg 1536w" sizes="(max-width: 580px) 100vw, 580px" /><figcaption id="caption-attachment-31531" class="wp-caption-text">YAPC函館市電LTの様子</figcaption></figure></p>
<blockquote>
<p>普段はAndroidアプリの開発を主にしていますが、そんな自分でも受け入れてくれるワイワイなコミュニティ・イベントで楽しめました!<br />
「CloudNative Meets WebAssembly: Wasm’s Potential to Replace Containers」のセッションは知らない単語が色々出てきて特にまなびになりました〜✨<br />
イベント翌日に「<a href="https://connpass.com/event/326666/" title="YAPC函館市電LT">YAPC函館市電LT</a>」が開催されていたので、路面電車についてLTもしました🙌 (Kuu)<br />
<br />
さまざまな分野の話を1日で聞けて、満足度が高かったです。特に「クレジットカードを製造する技術」が個人的には面白いと感じました。クレジットカードという特に秘匿性の高さが求められるものの製造工程は普段あまり聞けない話だったので興味深かったです!(momom)<br />
<br />
初めて技術のカンファレンスに参加しましたが、興味深いセッションをたくさん聞けたり、いろいろな人とお話しできて良い機会になりました!最後のmoznionさんのkeynoteの「量が質に転化する」という言葉に感銘を受け、帰ったらたくさんプログラム書いてこうという気持ちになりました🔥 企業のスポンサーブースもどの企業も工夫されてて全部回ってしまいました!コーヒースポンサーをされていたカケハシ社のコーヒーが美味しかったです☕︎ (torichan)<br />
<br />
「今日から始める大規模言語モデルのプロダクト活用」のセッションは、業務デザイン、ナレッジなど自分自身がとても興味のある領域なので、ワクワクしました。また、LT ではクリエイティブコーディングの話が刺さったので、早速やっていくぞ〜という気持ちです。(micchie)</p>
</blockquote>
<h2>まとめ</h2>
<p>わたしは「YAPC::Okinawa 2018 ONNASON」以来の久しぶりの参加となりました!そのときも沖縄科学技術大学院大学 OISTという大学が会場だったこともあり、個人的にすごくなつかしさを感じ、YAPCに戻ってきたなーという思いでいっぱいでした。<br />
前夜祭で行われたアンカンファレンスだけではなく、どのセッションでも登壇者と参加者がコミュニケーションをとりながら意見交換をしており、全員で楽しみ、盛り上がっているカンファレンスであることを感じました。</p>
<p>最後に、YAPC::Hakodate 2024の企画運営、おつかれさま & ありがとうございました!<br />
また、次回を楽しみにしています!<br />
<img src="https://storage.googleapis.com/prd-engineering-asset/2024/10/be3fcb6a-img_5056-scaled.jpg" alt="" /></p>
- iOSDC Japan 2024に参加・登壇しました #iosdc #iwillbloghttps://engineering.mercari.com/blog/entry/20240925-iosdc-2024-report/https://engineering.mercari.com/blog/entry/20240925-iosdc-2024-report/<p>こんにちは。株式会社メルカリ iOSエンジニアのkntkです。 8月22日から8月24日にかけて開催された「iOSDC Japan 2024」にメルカリはプラチナスポンサーとして参加しました。 本記事では、その参加レポー […]</p>
Wed, 25 Sep 2024 10:00:06 GMT<p>こんにちは。株式会社メルカリ iOSエンジニアの<a href="https://x.com/kntkymt">kntk</a>です。<br />
8月22日から8月24日にかけて開催された「<a href="https://iosdc.jp/2024/">iOSDC Japan 2024</a>」にメルカリはプラチナスポンサーとして参加しました。<br />
本記事では、その参加レポートをお届けします!</p>
<h2>登壇</h2>
<p>株式会社メルカリからは私を含め2名のエンジニアが登壇しました。</p>
<ul>
<li><a href="https://fortee.jp/iosdc-japan-2024/proposal/e8fa967f-16cd-475b-b134-6364b2a044cd">SwiftのSIMDとその利用方法</a>(レギュラートーク 20分): <a href="https://x.com/kntkymt">kntk</a></li>
<li><a href="https://fortee.jp/iosdc-japan-2024/proposal/5e7b95a8-9a2e-47d5-87a7-545c46c38b25">座談会 「Strict ConcurrencyとSwift 6が開く新時代: 私たちはどう生きるか?」</a>(企画 40分): <a href="https://x.com/kntkymt">kntk</a> (他4名)</li>
<li><a href="https://fortee.jp/iosdc-japan-2024/proposal/3bae1d6b-76d5-412d-8842-3fa3d2354a60">App Intentsの未来について研究しよう!</a>(ポスターセッション): <a href="https://x.com/jollyjoester">jollyjoester</a></li>
</ul>
<h3><a href="https://fortee.jp/iosdc-japan-2024/proposal/e8fa967f-16cd-475b-b134-6364b2a044cd">SwiftのSIMDとその利用方法</a>(レギュラートーク 20分): <a href="https://x.com/kntkymt">kntk</a></h3>
<p>CPUにはSIMD命令と呼ばれる高速演算機能が存在し、それを扱う為の型 SIMDが標準ライブラリに存在します。この型の全体像や使い方についてのセッションです。<br />
<strong>実はこのSIMD型はvisionOS開発で用いるRealityKitで必要になることがあり</strong>、Apple Vision Proが発表され少しずつvisionOS開発が始まっている今年が絶好の発表タイミングだと考えプロポーザルを提出しました。</p>
<p>visionOSで必要になるとはいえ、SIMD命令自体の説明という低レイヤ技術の話が含まれているセッションだったため、「あまり人は来てくれないのではないか」と考えていましたが、<strong>想像していたよりも多くの方々が聞きに来てくださり驚きました。</strong>visionOS開発をする際に、少しでも参考になれば良いなと思います。</p>
<h3><a href="https://fortee.jp/iosdc-japan-2024/proposal/5e7b95a8-9a2e-47d5-87a7-545c46c38b25">座談会 「Strict ConcurrencyとSwift 6が開く新時代: 私たちはどう生きるか?」</a>(企画 40分): <a href="https://x.com/kntkymt">kntk</a> (他4名)</h3>
<p>Swiftの次のメジャーバージョンであるSwift 6から導入される「Swift ConcurrencyのStrict Concurrencyチェックによる完全なデータ競合の防止」についての座談会企画です。座談会形式で私含め5名の登壇者がコメントし合う形でセッションが進行しました。</p>
<p>自分は個人アプリで経験した「Strict Concurrency対応をしたら、長らく原因が謎だったクラッシュが解消された」というエピソードを話しました。<strong>データ競合の最も厄介なポイントは再現やデバッグ(そもそもデータ競合の存在に気づくこと)が難しい点にある</strong>と考えているため、静的にデータ競合を防止するStrict Concurrencyチェックは大きな意味があると思っています。<br />
また、もう一つ <strong>「Swiftチームも全てのデータ保護をActorで実現することを目指していない」</strong> という大事なメッセージを伝えました。<br />
Strict Concurrencyの導入ハードルを高く感じる大きな要因の一つとして「Strict Concurrency以前から存在したLock(やQueue)の仕組みの全てを絶対にactorに置き換えなければいけない」という誤解が存在すると考えています。<strong>基本的にはActorが最適な選択肢ではありますが、Strict Concurrencyを段階的に対応する際やActorではカバーできないユースケースにおいては既存のLockの仕組みを使って良いとSwiftチームも考えており</strong>、このメッセージはStrict Concurrency導入のハードルを下げるのに大きく影響すると思い座談会の場で共有しました。</p>
<p>セッション時間が40分だったため、登壇者の事前打ち合わせで議論した内容の半分も話せていないのですが、その反面40分に最低限必要な内容を詰め込んだ密度の高いセッションになったと思います。僕以外の登壇者が全員著名な方だったため、自分が参加して良いものかと正直不安でしたが、上記のエピソードなどが聴講してくださった方々のお役に立てたなら、登壇して良かったなと思います。<br />
僕自身も事前の打ち合わせで登壇者の方々から多くの事を学ばせていただきました。</p>
<h3><a href="https://fortee.jp/iosdc-japan-2024/proposal/3bae1d6b-76d5-412d-8842-3fa3d2354a60">App Intentsの未来について研究しよう!</a>(ポスターセッション): <a href="https://x.com/jollyjoester">jollyjoester</a></h3>
<p>AppleプラットフォームにはApp Intentsと呼ばれるアプリの特定の機能をシステム(OS)に伝えることができる重要な機能があります。これにより、アプリ外(Siri・ショートカット・ウィジェット)からアプリの機能を利用することが可能になります。<br />
また、iOS18からApple Intelligence (AI)が導入されることによってApp Intentsの重要性が高まることが予想されています。</p>
<p><strong>App Intentsの概要から最小実装例の解説まで網羅したポスターセッションで、キャッチアップにピッタリな内容でした。</strong>また、「こんな体験ができるアプリが良い!作りたい!」と想像を膨らませてワクワクできるセッションでした。</p>
<h2>Swift コードバトル</h2>
<blockquote>
<p>Swiftコードバトルはお題で指示された動作をするSwiftコードをより短く書けた方が勝ち、という競技です。 お題は決して難しいものではなく、少し練習すればSwiftプログラマであればどなたでも参加できる難易度を目指しています。<br />
お題例1: 入力された文字列を逆順にして出力するプログラムを書いてください。<br />
お題例2: 与えられた整数リストの要素の合計を計算するプログラムを書いてください。</p>
</blockquote>
<p><a href="https://iosdc.connpass.com/event/326837/">Swiftコードバトル予選</a>の説明文から引用</p>
<p><strong>私kntkも参加し、準優勝しました!</strong><br />
<a href="https://blog.iosdc.jp/2024/08/30/iosdc-japan-2024-swift-code-battle/">iOSDC Japan 2024運営によるSwiftコードバトル解説記事</a></p>
<p>私は昔からSwiftが好きだったため、Swiftの標準ライブラリや”スマートな書き方”には多少自身がありました。競技プログラミングもSwiftで活動していたため、競プロ的な問題に対する経験値もありました。今回のコードバトルは自分のSwift力を試せる良い機会だったと思います。</p>
<p>コードバトル当日は「観客の前でライトに照らされ、顔と画面を配信されながら時間以内にプログラムを書く」という緊張感のある環境だったと同時に、回答を提出して観客の方々から歓声が上がった時はとても気持ちが良く、まるでスポーツでもやっている気分でした。</p>
<p>決勝では敗退してしまいましたが、対戦相手の方は予選の時からの強豪でしたし、決勝の問題が「競プロ的には簡単かつ文字数の工夫方法が多い」という決勝に相応しい・素晴らしい問題だったため、悔しいですが言い訳の余地のない結果だったかなと思っています。今回の学びを活かして今後も精進していきたいと思います。</p>
<p>また、<strong>このコードバトルの良い点は解答の速さよりコードの短さが評価される点です。実際に、当日の試合データを見ると、全7試合中5試合の勝者は、10分以上経過後に提出した回答で勝利しています。</strong>(Swiftに自信はあるが)速度に自信がない方でも十分に勝算があるということです。是非次回があれば、Swiftに自信がある皆さんに参戦していただきたいなと思います。<br />
とても楽しい企画だったのでぜひ来年も開催してもらいたいです!そして次回はもっと多くの方に参戦してほしいです!</p>
<h2>メルカリスポンサーブース</h2>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/09/55d29ccb-img_469.jpg" alt="" /></p>
<p>メルカリスポンサーブースでは「Engineering Office Hour」「チェキ撮影」「メルカリ iOSクイズ」の3つを出展しました。<br />
また、ブースに来てくれた方にはノベルティとして<a href="https://www.youtube.com/watch?v=hH1P3ImZo_o">メルカリの公式キャラクター</a>が印刷されたコースターをプレゼントしました。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/09/b1b388e2-img_4777_1-scaled.jpg" alt="" /><br />
( 亀の「ゼニー」猫の「ミケ」 うさぎの「ロップ」が印刷されたコースター)</p>
<h3>Engineering Office Hour</h3>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/09/c217c42c-engineering-office-hour-poster-design-1.png" alt="" /></p>
<p>Engineering Office Hourは、タイムテーブル形式でメルカリ社員と話せる企画です。簡単な自己紹介やトークテーマ、ブース滞在時間がまとまったポスターを掲載し、参加者が社員やトークテーマ狙いで遊びにいけるような設計を行いました。<br />
実際に特定の社員に会いに来た方や、ある社員が専門とする技術領域の質問をしに来た方がいらっしゃり、とても良い企画だったなと思いました。</p>
<h3>チェキ撮影</h3>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/09/82046b55-img_4695.jpg" alt="" /></p>
<p>ブースでは参加者の方とメルカリ社員のチェキ撮影も行っていました!合計で90枚近くのチェキを撮影し、参加者の方にプレゼントしました。(「エモい」と好評でした!)</p>
<h3>メルカリ iOSクイズ</h3>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/09/8d70cc4c-img_4774-1.jpg" alt="" /></p>
<p>iOS開発に関するクイズを3日分(合計18問)出題しました。3日合計で212件の挑戦をいただき、大変ありがとうございました!詳しい内容はぜひ<a href="https://engineering.mercari.com/blog/entry/20240905-iosdc-2024-quiz/">iOSDC Japan 2024 メルカリブース iOSクイズ解説</a>をご覧ください!</p>
<h2>まとめ</h2>
<p>今年のiOSDCはSwiftコードバトルや座談会など新しい試みが多く刺激的でした。またスポンサーブースでもEngineering Office Hour・チェキ・クイズに多くの方が参加してくださり、本当にありがとうございました。</p>
<p>最後に、iOSDC Japan 2024 の運営の皆様お疲れ様でした&ありがとうございました!また来年も参加したいなと思います! #iosdc #iwillblog</p>
- iOSDC Japan 2024 メルカリブース iOSクイズ解説 #iosdc #iwillbloghttps://engineering.mercari.com/blog/entry/20240905-iosdc-2024-quiz/https://engineering.mercari.com/blog/entry/20240905-iosdc-2024-quiz/<p>こんにちは。株式会社メルカリでiOSエンジニアをやっているkntkです。 8月22日から8月24日にかけて開催された「iOSDC Japan 2024」にメルカリはプラチナスポンサーとして参加し、会場ブース出展ではiOS […]</p>
Fri, 06 Sep 2024 11:00:23 GMT<p>こんにちは。株式会社メルカリでiOSエンジニアをやっている<a href="http://x.com/kntkymt">kntk</a>です。<br />
8月22日から8月24日にかけて開催された「iOSDC Japan 2024」にメルカリはプラチナスポンサーとして参加し、会場ブース出展ではiOS開発に関するクイズを3日分(合計18問)出題しました!</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/09/67a83d32-gvky5ljbgaa66pd-scaled.jpeg" alt="" /></p>
<p>本記事ではこのクイズの問題とその解説をお届けします!</p>
<ul>
<li>総回答件数: 212件</li>
<li>作問者: <a href="https://x.com/kntkymt">kntk</a> <a href="https://x.com/arventurist">sae</a></li>
</ul>
<p>前提</p>
<ul>
<li>個別に記載がない限りXcode 15.4(Swift 5.10)・iOS 17.6.1上での実行結果を正解とします。</li>
<li>最適化オプションは-Oを正解とします。</li>
</ul>
<h2>Day0 前夜祭 (8/22)</h2>
<ul>
<li>回答者数: 26名</li>
<li>平均点: 2.04/6点</li>
<li>全問正解者: 0名</li>
</ul>
<h3>1. 次のプログラムを実行すると何が出力されるでしょう?</h3>
<pre><code class="language-swift">func f(_ a: Int?) { print("Int?") }
func f(_ a: Any) { print("Any") }
let a: Int = 1
f(a)</code></pre>
<h4>選択肢</h4>
<p>A) Int?<br />
B) Any</p>
<h4>答え</h4>
<p>B) Any</p>
<h4>解説</h4>
<p>Swiftにはオーバーロードが存在します。<br />
ある関数呼び出しに当てはまる複数の関数オーバーロードが存在した時、スコア規則というルールで解決する優先順位が決められています。<br />
Intからの変換はInt?よりもAnyが優先です。</p>
<p>参考: <a href="https://zenn.dev/kntk/articles/5b758d29b5c49d#%E3%83%A9%E3%83%B3%E3%82%AF7%3A-sk_valuetooptional">Swiftのオーバーロード選択のスコア規則21種類#ランク7: SK_ValueToOptional</a></p>
<h3>2. 以下の数字をカウントするアプリでCounterViewのボタンを5回押した後、RootViewのボタン (“toggle color!”)を1回押すと次のうちどの表示になるでしょうか?</h3>
<pre><code class="language-swift">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
}
}
}</code></pre>
<h4>アプリの表示イメージ</h4>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/09/5bb27d28-f99a3d6904c21752499b5b74cb393e512b3035cfe9db8798d1539d410441886a-300x237.png" alt="" /></p>
<h4>選択肢</h4>
<p>A)「5, 赤背景」<br />
B)「5, 背景なし」<br />
C)「0, 赤背景」<br />
D)「0, 背景なし」</p>
<h4>答え</h4>
<p>C) 「0, 赤背景」</p>
<h4>解説</h4>
<p>Stateの状態はView Identityという識別子によって管理されているため、View Identityが変わるとStateも初期化されます。また、ViewBuilderのif文はそれぞれの分岐のViewで異なるView Identityを持ちます。(<a href="https://zenn.dev/kntk/articles/1f1b40da6fe181#structural-identity">Structural Identity</a>)<br />
今回のプログラムでは、”toggle color”ボタンを押すとif文の分岐が変化し、違うView Identityに変わってしまうため、状態が初期化されcountが0に戻ってしまいます。<br />
このコードの様なif modifierは、使用者側からView Identityが変わることが意識しづらいので注意が必要です。<br />
参考: <a href="https://zenn.dev/kntk/articles/1f1b40da6fe181">[SwiftUI] ViewのIdentityと再描画を意識しよう</a></p>
<h3>3. Sendability違反 (Swift 6でのエラー) に該当する型を全て選択してください。</h3>
<p>※classのinitを省略して記載しています</p>
<pre><code class="language-swift">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
}</code></pre>
<h4>答え</h4>
<p>Dのみ</p>
<h4>解説</h4>
<p>Sendableは「Isolation Domain(並列にアクセスが行われる単位)を安全に跨ぐことができる」を表すprotocolです。<br />
structのみで構成されるstructはSendable。structは値型であり、スレッドを跨ぐ際に値のコピーが行われるため、(内部の変数がvarであっても)データ競合の危険性がありません。</p>
<p>classはfinalで内部の変数が全て「let」かつ「Sendable」ならSendable。それ以外はnon-Sendable。classは参照型であり、Isolation Domainを跨いで参照が共有可能ですが、変数がletであれば変数への書き込みが不可能であり、さらにSendableであれば安全にアクセスできることが保証されているため、data raceの危険性がありません。<br />
変数がvarまたはnon-Sendableの場合は書き込みが可能でdata raceの危険性があります。</p>
<p>参考: <a href="https://developer.apple.com/documentation/swift/sendable">公式ドキュメント Sendable</a></p>
<h3>4. nをCollectionの長さとした時、Array.countの計算量は「a」, String.countの計算量は「b」である。</h3>
<h4>選択肢</h4>
<p>A) a: O(n) b: O(n)<br />
B) a: O(1) b: O(n)<br />
C) a: O(n) b: O(1)<br />
D) a: O(1) b: O(1)</p>
<h4>答え</h4>
<p>B) a: O(1) b: O(n)</p>
<h4>解説</h4>
<p>Collection.countの計算量はO(n)です。ただしRandomAccessCollectionの場合はO(1)となります。<br />
ArrayはRandomAccessCollectionですが、StringはRandomAccessCollectionではありません。<br />
参考: <a href="https://developer.apple.com/documentation/swift/collection/count-4l4qk">公式ドキュメント Collection.count</a></p>
<h3>5. 次のプログラムを実行すると何が出力されるでしょう?</h3>
<pre><code class="language-swift">class Counter {
var count = 0
}
var counter = Counter()
let closure = { [counter] in
print(counter.count)
}
counter.count += 1
closure()
counter = Counter()
closure()</code></pre>
<h4>選択肢</h4>
<p>A) 0 0<br />
B) 0 1<br />
C) 1 0<br />
D) 1 1</p>
<h4>答え</h4>
<p>D) 1 1</p>
<h4>解説</h4>
<p>capture listsを用いて変数をキャプチャすると、クロージャ定義時の値でその変数が初期化されるため、変数の変更が共有されません。<br />
参考: <a href="https://docs.swift.org/swift-book/documentation/the-swift-programming-language/expressions/#Capture-Lists">Swift 公式ドキュメント Capture Lists</a></p>
<h3>6. 次のプログラムを実行すると何が出力されるでしょう?</h3>
<pre><code class="language-swift">func log(info: String = "called: \(#function)") {
print(info)
}
func main() {
log()
log(info: "called: \(#function)")
}
main()</code></pre>
<h4>選択肢</h4>
<p>A) called: main() called: main()<br />
B) called: main() called: log(info:)<br />
C) called: log(info:) called: main()<br />
D) called: log(info:) called: log(info:)</p>
<h4>答え</h4>
<p>C) called: log(info:) called: main()</p>
<h4>解説</h4>
<p>デフォルト引数における#functionは呼び出し元の関数名を生成する特殊マクロですが、特殊マクロはデフォルト引数のsub-expressionで用いる(例: 文字列展開で値を加工する)と呼び出し元の関数名を参照できなくなります。</p>
<p>参考: <a href="https://github.com/swiftlang/swift-evolution/blob/main/proposals/0422-caller-side-default-argument-macro-expression.md">SE-0422 Expression macro as caller-side default argument</a></p>
<h2>Day1 (8/23)</h2>
<ul>
<li>回答者数: 116名</li>
<li>平均点: 2.98/6点</li>
<li>全問正解者: 4名</li>
</ul>
<h3>1. 次のプログラムを実行すると何が出力されるでしょう?</h3>
<pre><code class="language-swift">func f() { print("sync") }
@_disfavoredOverload
func f() async { print("async") }
func g() async {
await f()
}
await g()</code></pre>
<h4>選択肢</h4>
<p>A) async<br />
B) sync</p>
<h4>答え</h4>
<p>A) async</p>
<h4>解説</h4>
<p><code>@_disfavoredOverload</code>はオーバーロード解決の優先順位を下げるattributeです。<br />
しかし「async関数内の関数呼び出しではasyncオーバーロードを優先する」という別のルールの方が優先度が高いため、この例では影響しません。</p>
<p>この優先度は「スコア規則」と呼ばれるルールで決められています。</p>
<p>参考: <a href="https://zenn.dev/kntk/articles/5b758d29b5c49d#%E3%83%A9%E3%83%B3%E3%82%AF17%3A-sk_syncinasync">Swiftのオーバーロード選択のスコア規則21種類#ランク17: SK_SyncInAsync</a></p>
<h3>2. 次のプログラムを実行すると何が出力されるでしょう?</h3>
<pre><code class="language-swift">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)</code></pre>
<h4>選択肢</h4>
<p>A) 0<br />
B) 1<br />
C) 2</p>
<h4>答え</h4>
<p>C) 2</p>
<h4>解説</h4>
<p>SetやDictionaryにおいて、基本的にはハッシュ値に基づいて保持する要素の管理が行われますが、ハッシュ値が一致した場合はEquatableによる同値比較を行い一意性を担保する仕様になっています。<br />
この例は意図的にハッシュ値を衝突させていますが、ハッシュは仕組み上、元の値が異なってもハッシュ値が一致する可能性があるため、この仕様は重要な普段でも役割を果たしています。</p>
<h3>3. 次のプログラムを実行すると何が出力されるでしょう?</h3>
<pre><code class="language-swift">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</code></pre>
<h4>選択肢</h4>
<p>A) 1<br />
B) 出力なし</p>
<h4>答え</h4>
<p>A) 1</p>
<h4>解説</h4>
<p>Task.cancel()はTask.isCancelledのフラグを立てるだけなので実際に処理を中止する処理は自分で実装しなければいけません。</p>
<p>今回の例は中止する処理が記述されていないのでTask.cancel()は影響せず3秒後に1が出力されます。<br />
参考: <a href="https://zenn.dev/kntk/articles/0001fdd2a4f76d">[Swift] async関数とAsyncStreamのキャンセル</a></p>
<h3>4. 次のプログラムを実行すると何が出力されるでしょう?</h3>
<pre><code class="language-swift">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</code></pre>
<h4>選択肢</h4>
<p>A) 1 finish<br />
B) finish<br />
C) 1<br />
D) 出力なし</p>
<h4>答え</h4>
<p>B) finish</p>
<h4>解説</h4>
<p>Task.cancel()はTask.isCancelledのフラグを立てるだけなので実際に処理を中止する処理は自分で実装しなければいけません。</p>
<p>しかし、AsyncStreamに対するfor await inはTaskのキャンセル直後に処理を中止します。<br />
AsyncSequenceに対するfor await inはAsyncSequence.Iterator.next()のシンタックスシュガーであり、AsyncStream.Iterator.next()は処理を中止する実装がされているからです。</p>
<p>ここで、for await inはTaskのキャンセル直後に処理を中止しますが、Task {}自体は処理を中止する処理が実装されていないので、print("finish")は実行されます。</p>
<p>参考: <a href="https://zenn.dev/kntk/articles/0001fdd2a4f76d">[Swift] async関数とAsyncStreamのキャンセル</a></p>
<h3>5. Swift6.0から外部パッケージの型にprotocolを準拠させる際に表示される警告を消すattributeの名前は何でしょうか?</h3>
<pre><code class="language-swift">extension Date: @attribute名 Identifiable {
public var id: TimeInterval { timeIntervalSince1970 }
}</code></pre>
<h4>選択肢</h4>
<p>A) @conform<br />
B) @active<br />
C) @retroactive<br />
D) @foreign_conform</p>
<h4>答え</h4>
<p>C) @retroactive</p>
<h4>解説</h4>
<p>(外部のパッケージの型に)後からprotocolを準拠させる機能は便利な一方で、フレームワーク提供側が意図しない利用方法であったり、後から提供側が同じprotocolを別の挙動で準拠させる可能性があると言う危険性があります。その危険性を可視化するための警告と、その警告を消すattributeが追加されました。<br />
参考: <a href="https://github.com/swiftlang/swift-evolution/blob/main/proposals/0364-retroactive-conformance-warning.md">SE-0364 Warning for Retroactive Conformances of External Types</a></p>
<h3>6. 次のSwiftUIのコードに対応する表示は次のうちどれでしょう?</h3>
<pre><code class="language-swift">Text("Hello, mercari!")
.padding()
.border(Color.red, width: 1)
.offset(x: 50, y: 50)
.border(Color.blue, width: 1)
.overlay(Circle().fill(.green))</code></pre>
<h4>選択肢</h4>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/09/571c4a52-4efa79dc4006aa91fb92b9975ea54f7d304ffb93f18231d78a35733ef22de125-1024x887.png" alt="" /></p>
<h4>答え</h4>
<p>D)</p>
<h4>解説</h4>
<p>offsetはレシーバーの「表示位置」だけを変えるmodifierであり、周りのレイアウトやその後のmodifierに影響を与えません。<br />
参考: <a href="https://developer.apple.com/documentation/swiftui/view/offset(x:y:)">公式ドキュメント offset</a></p>
<h2>Day2 (8/24)</h2>
<ul>
<li>回答者数: 70名</li>
<li>平均点: 2.21/6点</li>
<li>全問正解者: 2名</li>
</ul>
<h3>1. 次のプログラムの出力が0になるような演算子はどれでしょう?</h3>
<pre><code class="language-swift">let a: UInt8 = 255
let b: UInt8 = 1
print(a 演算子 b) // 0</code></pre>
<h4>選択肢</h4>
<p>A) +<br />
B) ^<br />
C) |<br />
D) &+</p>
<h4>答え</h4>
<p>D) &+</p>
<h4>解説</h4>
<p>Swiftでは+, -, <em>を用いた演算でオーバーフローが発生するとランタイムエラーになります。<br />
一方、&+, &-, &</em>はオーバーフロー演算子と呼ばれ、オーバーフローを許容して桁あがりする(.minに戻る)挙動になります。</p>
<p>参考: <a href="https://docs.swift.org/swift-book/documentation/the-swift-programming-language/advancedoperators/#Overflow-Operators">Swift 公式ドキュメント オーバーフロー演算子</a></p>
<h3>2. 次のプログラムを実行すると開始から終了までに (約) 何秒かかるでしょう?</h3>
<pre><code class="language-swift">func wait() async {
try? await Task.sleep(for: .seconds(1))
}
await (wait(), wait())</code></pre>
<h4>選択肢</h4>
<p>A) 1<br />
B) 2</p>
<h4>答え</h4>
<p>B) 2</p>
<h4>解説</h4>
<p>Swiftではawaitキーワードを一つにまとめたり、位置を変えることができます。<br />
しかし、関数呼び出しの挙動に影響はないため、awaitを一つにまとめても並列に動作はしません。<br />
並列に動作させる場合はそれぞれの呼び出しをasync letで定義する必要があります。</p>
<h3>3. 次のプログラムの出力はSwift6未満で「a」Swift6以上で「b」である。</h3>
<pre><code class="language-swift">func f(
_ a: (() -> Void)? = nil,
_ b: (() -> Void)? = nil
) {
if a != nil { print("forward") }
if b != nil { print("backward") }
}
f { }</code></pre>
<h4>選択肢</h4>
<p>A) a: backward b: backward<br />
B) a: forward b: backward<br />
C) a: backward b: forward<br />
D) a: forward b: forward</p>
<h4>答え</h4>
<p>C) a: backward b: forward</p>
<h4>解説</h4>
<p>Swift6の破壊的変更の一つです。</p>
<p>Swift5.3未満はTrailingClosureはbackward scan (クロージャーを末尾の引数から当てはめる)のみでした。<br />
Swift5.3からSwift5.10まではbackwardとforward両方のチェックを行います。backwardとforward両方候補になった場合は互換性維持の観点でbackwardを選択します。<br />
しかし、Swift6.0(もしくは-enable-upcoming-feature ForwardTrailingClosures)からはforward scanのみとなります。</p>
<p>参考: <a href="https://github.com/swiftlang/swift-evolution/blob/main/proposals/0286-forward-scan-trailing-closures.md">SE-0286 Forward-scan matching for trailing closures</a></p>
<h3>4. 次の呼び出しが当てはまる関数定義はどれでしょうか。</h3>
<pre><code class="language-swift">getUserInfo([Seller(), Buyer()])
protocol User {
func getInfo()
}
struct Seller: User {
func getInfo() { /* ... */ }
func listItem() { /* ... */ }
}
struct Buyer: User {
func getInfo() { /* ... */ }
func purchaseItem() { /* ... */ }
}</code></pre>
<h4>選択肢</h4>
<p>A)<br />
<code>func getUserInfo<U: User>(_ users: [U]) { /* ... */ }</code></p>
<p>B)<br />
<code>func getUserInfo(_ users: some Sequence<User>) { /* ... */ }</code></p>
<p>C)<br />
<code>func getUserInfo(_ users: [some User]) { /* ... */ }</code></p>
<p>D)<br />
<code>func getUserInfo(_ users: [any User]) { /* ... */ }</code></p>
<h4>答え</h4>
<p>D)</p>
<h4>解説</h4>
<p>A: Conflicting arguments to generic parameter ‘U’ (‘Buyer’ vs. ‘Seller’)<br />
B: Cannot convert value of type ‘[Any]’ to expected argument type ‘[any User]’<br />
C: Conflicting arguments to generic parameter ‘some User’ (‘Buyer’ vs. ‘Seller’)</p>
<p>ジェネリクスを用いるとBuyerまたはSellerどちらか一方の型の配列として推論されるため、両方の型の値を持つ配列を受け取ることができません。</p>
<p>参考:<br />
A: Generics<br />
C: Opaque Argument Type (Aのシンタックスシュガー)<br />
D: Existential (Argument) Type</p>
<h3>5. この表示に対応するプログラムはどれでしょうか?</h3>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/09/e9ca89eb-screenshot-2024-08-06-at-15.19.59-1.png" alt="" /></p>
<h4>選択肢</h4>
<p>A)</p>
<pre><code class="language-swift">Image(systemName: "paperplane.circle.fill")
.symbolRenderingMode(.palette)
.foregroundStyle(.white, .blue)</code></pre>
<p>B)</p>
<pre><code class="language-swift">Image(systemName: "paperplane")
.symbolVariant(.circle)
.symbolVariant(.fill)
.symbolRenderingMode(.multicolor)</code></pre>
<p>C)</p>
<pre><code class="language-swift">Image(systemName: "paperplane.circle.fill")
.symbolRenderingMode(.hierarchical)
.foregroundStyle(.blue)</code></pre>
<p>D)</p>
<pre><code class="language-swift">Image(systemName: "paperplane.circle.fill")
.symbolRenderingMode(.monochrome)
.foregroundStyle(.blue)</code></pre>
<p>答え<br />
C)</p>
<p>解説<br />
SF SymbolにsymbolRenderingModeを適応することによって、着色方法を変えることができます。(表示結果 左からA,B,C,Dの順)</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/09/e2bb8750-screenshot-2024-08-06-at-15.19.38-300x81.png" alt="" /></p>
<p>参考: <a href="https://developer.apple.com/documentation/swiftui/symbolrenderingmode">公式ドキュメント symbolRenderingMode</a></p>
<h3>6. 次の関数呼び出しが当てはまる定義をすべて答えてください。</h3>
<pre><code class="language-swift">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</code></pre>
<h4>答え</h4>
<p>A, B, C(全部)</p>
<h4>解説</h4>
<p>A: Sub?からAnyへの変換。AnyはOptionalも当てはまります。<br />
B: Sub?からAny?への変換。Optionalの要素SubがAnyに変換されています。<br />
C: Sub?からSuper?への変換。Optionalの要素SubがSuperに変換されています。</p>
<h2>まとめ</h2>
<p>当日はたくさんの方に挑戦いただきありがとうございました!<br />
躓きやすいSwiftやiOSの仕様をピックアップしたクイズになっているので、この記事の解説がSwiftやiOSへの理解を深める助けになれば嬉しいなと思います。#iosdc #iwillblog</p>
- 技術者が選ぶ「テクノロジーブランディング」評価の高い企業ランキングで、メルカリが3年連続1位を受賞!https://engineering.mercari.com/blog/entry/20240716-dx-award-2024/https://engineering.mercari.com/blog/entry/20240716-dx-award-2024/<p>こんにちは、Engineering Officeのyasu_shiwakuです。 2024年7月16日、一般社団法人日本CTO協会様主催の「Developer eXperience AWARD 2024」にて、「開発者体 […]</p>
Tue, 16 Jul 2024 18:21:19 GMT<p>こんにちは、Engineering Officeの<a href="https://twitter.com/yaccho0101">yasu_shiwaku</a>です。</p>
<p>2024年7月16日、一般社団法人日本CTO協会様主催の「<a href="https://cto-a.org/developerexperienceaward">Developer eXperience AWARD 2024</a>」にて、「開発者体験ブランド力」調査の中で、<strong>メルカリが昨年に引き続き3年連続で1位に選出されました。</strong> 日本CTO協会様のプレスリリースは<a href="https://prtimes.jp/main/html/rd/p/000000035.000081310.html">こちら</a>です。</p>
<p>今年もオフラインの会場で授賞式がおこなわれ、当日は執行役員CTO Marketplaceの<a href="https://x.com/kimuras">木村俊也</a>が出席し、受賞コメントを述べました(木村は7/17の<a href="https://cto-a.org/dxd2024/session-day2-sp">パネルディスカッション</a>にも登壇予定です)</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/07/0583ab8b-img_4172-1-scaled.jpg" alt="" /></p>
<p>昨年に引き続き、多くの方から高い評価を得られたことを嬉しく思います!社内外を問わず、ブログや登壇、イベントへの参加など多岐に渡って情報発信に貢献してくれているエンジニアたちのおかげです。</p>
<p>メルカリグループではエンジニアたちが主体的に発信し、コミュニティにその経験や知見を還元していくことで業界全体を活性化・成長させていくカルチャーを育てています。</p>
<p>またメルカリが利用させていただいているオープンソースコミュニティへの還元として、カンファレンスや<a href="https://engineering.mercari.com/blog/entry/20220315-mercari-now-sponsoring-python-and-php/">プロジェクトスポンサー</a>などの支援活動もおこなっています(メルカリの<a href="https://engineering.mercari.com/open-source/">オープンソース</a>に対する考え方はこちら。公開ソフトウェアは<a href="https://github.com/mercari/">こちら</a>)</p>
<p>メルカリグループは<strong>「あらゆる価値を循環させ、あらゆる人の可能性を広げる」</strong>のミッションのもと、エンジニアリング組織としても、新しいチャレンジや問題解決に向かい合っていく中でエンジニアリングの価値を循環させ、可能性を広げていくために、今後も社内外の開発コミュニティに向けて貢献できるよう、情報発信を続けていければと思います。</p>
<h2>エンジニア向け発信媒体一覧</h2>
<ul>
<li><a href="https://engineering.mercari.com/">Mercari Engineering Website</a>(本ポータルサイトです)</li>
<li>Xアカウント(<a href="https://twitter.com/MercariDev">英語</a>・<a href="https://twitter.com/mercaridevjp">日本語</a>)</li>
<li>イベント関連
<ul>
<li><a href="https://mercari.connpass.com/">Connpass</a></li>
<li><a href="https://www.meetup.com/MercariDev/">Meetup</a></li>
</ul>
</li>
<li>YouTubeチャンネル
<ul>
<li><a href="https://www.youtube.com/c/MercariGears">Mercari Gears</a></li>
<li><a href="https://www.youtube.com/channel/UCTnpXQ-1q2MNBvqf_qTOExw">Mercari devjp</a></li>
</ul>
</li>
</ul>
<p>メルカリグループでどんな開発者体験ができるのか、またどんなカルチャーがあるのか興味がある方は、ぜひキャリアサイトを一度覗いてみてください!<br />
<a href="https://careers.mercari.com/jobs/engineering/">Software Engineer/Engineering Manager</a></p>
- Go Conference 2024に参加しました!#goconhttps://engineering.mercari.com/blog/entry/20240716-goconference-2024/https://engineering.mercari.com/blog/entry/20240716-goconference-2024/<p>こんにちは。MercoinでBackendエンジニアをしているgoroです。 先月6月8日にAbema Towerで開催された「Go Conference 2024」にメルカリはSilverスポンサーとして参加し、会場に […]</p>
Tue, 16 Jul 2024 10:30:30 GMT<p>こんにちは。MercoinでBackendエンジニアをしている<a href="https://x.com/awakot_56">goro</a>です。<br />
先月6月8日にAbema Towerで開催された「<a href="https://gocon.jp/2024/">Go Conference 2024</a>」にメルカリはSilverスポンサーとして参加し、会場にはブースを出展しました。今回はそのレポートをお届けします!</p>
<h2>Go Conference 2024 について</h2>
<p>Go Conferenceは年に1回行われる、プログラミング言語Goに関するカンファレンスです。今年は数年ぶりのオフライン開催で、20以上のGo言語に関するセッションを中心に、オフラインならではの多くのイベントが企画されました。</p>
<h3>開催概要</h3>
<p>開催日時<br />
2024年6月8日(土)<br />
場所 Abema Tower</p>
<h2>当日の様子をご紹介</h2>
<h3>メルカリブース</h3>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/07/30e1f441-img_3503.jpg" alt="" /></p>
<p>メルカリブースでは、Goエンジニアの参加者の皆さんに対するオープンクエスチョンと、私たちがアイディアを出し合って作成したクイズを準備しました。</p>
<p>オープンクエスチョンでは「あなたが一番好きなGoのライブラリは?」という質問をしました。<br />
<img src="https://storage.googleapis.com/prd-engineering-asset/2024/07/c91c993a-img_3543.jpg" alt="" /></p>
<p>127票もの回答が集まり、1位「net/http」19票、2位「io」「fmt」6票、3位「gin」「echo」5票という結果になりました。投票していただいた方々、ありがとうございました。</p>
<p>またもう一つの企画でGoとメルカリに関するクイズを6問出題しました。</p>
<p>当日出題したクイズの正解に関してもこちらに記載しておきます。挑戦してくださった皆さんありがとうございました。</p>
<p>Q1. 次のコードはコンパイルできるでしょうか?<br />
<img src="https://storage.googleapis.com/prd-engineering-asset/2024/07/ea985b36-screenshot-2024-05-31-at-15.58.25.png" alt="" /><br />
A. No <br />
No: "undefined: T "というエラーになります。<br />
Go Playground: <a href="https://go.dev/play/p/jQ-V9THYZLX">https://go.dev/play/p/jQ-V9THYZLX</a></p>
<p>Q2. 次のコードはコンパイルできるでしょうか?<br />
<img src="https://storage.googleapis.com/prd-engineering-asset/2024/07/dfd55464-screenshot-2024-05-31-at-15.59.10.png" alt="" /></p>
<p>A.No<br />
No: error: "use of untyped nil in assignment to _ identifier"というエラーになります。<br />
The Go Playground: <a href="https://go.dev/play/p/l2Dyg6JF2xV">https://go.dev/play/p/l2Dyg6JF2xV</a></p>
<p>Q3. 次のGoのコードの出力はGo 1.22では何になるでしょうか?<br />
<img src="https://storage.googleapis.com/prd-engineering-asset/2024/07/7e4e436b-screenshot-2024-05-31-at-16.22.04.png" alt="" /><br />
A. 1<br />
Go1.22では1になります。<br />
Go1.22で修正されたループの挙動によるものです。こちらに修正された内容が詳しく書かれています。<a href="https://go.dev/blog/loopvar-preview">https://go.dev/blog/loopvar-preview</a><br />
The Go Playground: <a href="https://go.dev/play/p/26VrcKf2dXx">https://go.dev/play/p/26VrcKf2dXx</a></p>
<p>Q4. 次のGoのコードの出力は何になるでしょうか。<br />
<img src="https://storage.googleapis.com/prd-engineering-asset/2024/07/50030654-screenshot-2024-05-31-at-16.21.20.png" alt="" /><br />
A. timeout<br />
The Go Playground: <a href="https://go.dev/play/p/CuaQuBooOQL">https://go.dev/play/p/CuaQuBooOQL</a><br />
※このコードは通常"timeout"と出力されますが、システムの状況によっては異なる結果になる可能性もあります。</p>
<p>Q5. 社内勉強会<a href="https://mercan.mercari.com/articles/2018-09-28-181445/">Go Friday</a>は現時点(6/7時点)で何回開催されたでしょうか?<br />
A. 363</p>
<p>Q6. Cloud Spannerのためにgithub.com/xo/xo をフォークして作成されたライブラリはどれですか?<br />
A. github.com/cloudspannerecosystem/yo </p>
<p>今回のイベントに合わせて、メルカリブースに遊びにきていただいた記念として様々なノベルティをお配りしました。ブースにお越しいただいた皆さま、ありがとうございました!<br />
<img src="https://storage.googleapis.com/prd-engineering-asset/2024/07/68b5d4e4-img_3519.jpg" alt="" /><br />
<img src="https://storage.googleapis.com/prd-engineering-asset/2024/07/7cfb729c-img_3486.jpg" alt="" /></p>
<h3>セッションについて</h3>
<p>スポンサーブースにいる時間が長かったのですが、合間にいくつかのセッションを見ることができました。個人的には「<a href="https://gocon.jp/2024/sessions/8/">GoのLanguage Server Protocol実装、『gopls』の自動補完の仕組みを学ぶ</a>」というセッションが非常に興味深かったです。このセッションでは、普段特に意識することなく利用しているgoplsの仕組みを詳しく知ることができ、とても面白かったです。<br />
<img src="https://storage.googleapis.com/prd-engineering-asset/2024/07/e91abd83-img_3533.jpg" alt="" /></p>
<p>メルカリからはメルコインVPoEのpoohさんが「<a href="https://gocon.jp/2024/sessions/6/">Go1.21から導入されたGo Toolchainの仕組みをまるっと解説</a>」というテーマでセッションを行いました。立ち見になる程盛り上がっていました。<br />
発表資料はこちらをご確認ください。<br />
<a href="https://speakerdeck.com/yamatoya/go1-dot-21karadao-ru-sareta-go-toolchainnoshi-zu-miwomarututojie-shuo/">https://speakerdeck.com/yamatoya/go1-dot-21karadao-ru-sareta-go-toolchainnoshi-zu-miwomarututojie-shuo/</a></p>
<h3>抽選会</h3>
<p>運営の企画でスポンサーブースを回ってスタンプを集めると抽選会に参加できるというイベントがありました。めちゃくちゃ可愛いGopherのキーホルダーが当たり、家宝にします!<br />
<img src="https://storage.googleapis.com/prd-engineering-asset/2024/07/ec841d63-img_3563.jpg" alt="" /></p>
<h2>まとめ</h2>
<p>私にとっては数年ぶりのオフラインでのカンファレンスだったので、どれくらいの方がブースに来てくださるか最初は不安でしたが、実際には100名以上の方々にメルカリのブースへお越しいただけました。クイズやアンケートにお答えいただき、本当にありがとうございました。このメルカリのテックブログを参考にしているというお声もいただき、非常に嬉しく思いました。</p>
<p>最後に、Go Conference 2024の運営の皆さま、本当におつかれさまでした。また次回も参加させていただきます!</p>
- mercari.go #26 を開催しました #mercarigohttps://engineering.mercari.com/blog/entry/20240628-mercarigo-26/https://engineering.mercari.com/blog/entry/20240628-mercarigo-26/<p>はじめに こんにちは、mercari.go スタッフの hiroebe です。 6月18日にメルカリ主催の Go 勉強会 mercari.go #26 を YouTube でのオンライン配信にて開催しました。この記事では […]</p>
Fri, 28 Jun 2024 11:30:10 GMT<h2>はじめに</h2>
<p>こんにちは、mercari.go スタッフの hiroebe です。</p>
<p>6月18日にメルカリ主催の Go 勉強会 <a href="https://mercari.connpass.com/event/320135/">mercari.go #26</a> を YouTube でのオンライン配信にて開催しました。この記事では、当日の各発表を簡単に紹介します。動画もアップロードされていますので、こちらもぜひご覧ください。</p>
<p><iframe loading="lazy" width="560" height="315" src="https://www.youtube.com/embed/lnd7vnBtG0M?si=7dc6JrT0L6cQhOG4" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe></p>
<h2>moq – gomockを使わないMock生成</h2>
<p>1つめのセッションは <a href="https://x.com/oinume">@oinume</a> さんによる「moq – gomockを使わないMock生成」です。</p>
<p>発表資料:<a href="https://speakerdeck.com/oinume/moq-gomockwoshi-wanaimocksheng-cheng">moq – gomockを使わないMock生成</a></p>
<p>Go の interface からモックを生成するためのツールである <a href="https://github.com/matryer/moq">moq</a> について紹介しました。Go のモックライブラリといえば <a href="https://github.com/golang/mock">gomock</a> が有名ですが、gomock は生成されるコードが Type Safe でなかったり、多機能ゆえに学習コストが高いといった問題があります。これに対して moq は生成されるコードが Type Safe であり、また機能が絞られているぶん学習コストが低いといった特徴があり、前述の gomock の問題点を解消してくれるツールとなっているそうです。機能が絞られているといっても必要最低限の機能は提供されていて、モックのメソッドが呼び出された回数や引数の値のチェックなどは実現できるとのことです。<br />
個人的にも moq は気になっていたものの使ったことがなかったので、この機会に触ってみたいと思いました。gomock だと too much に感じている方はぜひ試してみてはいかがでしょうか。</p>
<h2>Gobraで見る形式検証</h2>
<p>2つめのセッションは <a href="https://x.com/artoy5884">@kobaryo</a> さんによる「Gobraで見る形式検証」です。</p>
<p>発表資料:<a href="https://speakerdeck.com/artoy/gobra-dejian-ruxing-shi-jian-zheng-mercari-dot-go-number-26">Gobraで見る形式検証</a></p>
<p>形式検証についての概要説明と、Go プログラムの検証器である <a href="https://github.com/viperproject/gobra">Gobra</a> の紹介を行いました。形式検証とは「プログラムが仕様を満たしていることを数学的手法を用いて証明すること」で、私たちが普段利用しているプログラムの「型」も一種の軽量な形式検証であると紹介されています。Gobra はアノテーション付きの Go プログラムを入力する検証器で、インターフェースや Goroutine といった Go の主要な機能にも対応しているそうです。発表では、これらの機能が Gobra によってどのように検証されるかについて、具体例を交えて説明されています。<br />
形式検証というテーマは個人的にあまり馴染みのない内容だったので、とても新鮮でおもしろい発表でした。アノテーションの記述量の多さや仕様を定めることの難しさなど形式検証には欠点もあるそうですが、普段の開発でも活用できるような例があればぜひ知りたいと思いました。</p>
<h2>gRPC Federation から見る WebAssembly の実践活用法</h2>
<p>3つめのセッションは <a href="https://x.com/goccy54">@goccy</a> さんによる「gRPC Federation から見る WebAssembly の実践活用法」です。</p>
<p>発表資料:<a href="https://docs.google.com/presentation/d/16SWXksARvSGQgyp-rf2AeNIZ-uOfVCY_owxPjpCBwDM/edit?usp=sharing">gRPC Federation から見る WebAssembly の実践活用法</a></p>
<p>メルペイではリリースからの時間経過に伴って BFF (Backends for Frontends) の肥大化が問題となっており、これを解消するために BFF を複数に分割し、個々の BFF を <a href="https://github.com/mercari/grpc-federation">gRPC Federation</a> を用いて構築することで開発・運用コストを抑えるというアプローチをとっています。gRPC Federation は Protocol Buffers 上で記述した DSL から gRPC Server を自動生成する仕組みで、これによって個々のサービスの開発者はサービスそのもののロジックに集中できるという利点があります。今回の発表では WebAssembly を用いたプラグインの仕組みに焦点を当てて、コンパイラ・ランタイムの選定や、ホストとクライアント間のデータのやり取りについて紹介されています。<br />
WebAssembly の活用事例としてとても興味深い発表でした。gRPC Federation については goccy さんのブログ記事 <a href="https://engineering.mercari.com/blog/entry/20240401-4f426bd460/">gRPC Federation: gRPC サービスのための Protocol Buffers を進化させるDSL</a> もぜひ併せてご覧ください。</p>
<h2>おわりに</h2>
<p>今回は Go のツール紹介や WebAssembly の活用事例など、幅広い内容の発表をお送りしました。普段の開発では触れる機会の少ないような内容もあり、運営としても非常に興味深く聞かせていただきました。</p>
<p>ライブで視聴いただいた方も録画を観ていただけた方も本当にありがとうございました!</p>
<p>次回の開催もお楽しみに!<br />
イベント開催案内を受け取りたい方は、connpassグループのメンバーになってくださいね!<br />
<a href="https://mercari.connpass.com/">メルカリconnpassグループページ</a></p>
- メルカリ ハロ Webフロントエンドの開発スピードと品質両立の取り組みhttps://engineering.mercari.com/blog/entry/20240613-mercari-hallo-web-frontend/https://engineering.mercari.com/blog/entry/20240613-mercari-hallo-web-frontend/<p>こんにちは。メルカリのSoftware Engineerの@tanashoです。連載:Mercari Hallo, world! -メルカリ ハロ 開発の裏側-の6回目を担当させていただきます。 メルカリ ハロのWebア […]</p>
Thu, 13 Jun 2024 08:00:33 GMT<p>こんにちは。メルカリのSoftware Engineerの<a href="https://x.com/sho_tnk0513" title="@tanasho">@tanasho</a>です。<a href="https://engineering.mercari.com/blog/entry/20240524-mercari-hallo-world/" title="連載:Mercari Hallo, world! -メルカリ ハロ 開発の裏側-">連載:Mercari Hallo, world! -メルカリ ハロ 開発の裏側-</a>の6回目を担当させていただきます。</p>
<p>メルカリ ハロのWebアプリケーションは複数存在し、Webフロントエンドチームが横断的に開発をしています。本記事では、その前提を踏まえ、スピードと品質をどのように両立させて開発しているかを紹介します。</p>
<h2>プロジェクトの概要とWebフロントエンドの担当領域</h2>
<p>メルカリ ハロは「あたらしい出会いを繋ぎ、信頼と機会をひろげる」がミッションで、いますぐ働き手が欲しいパートナー (事業者) と、いますぐ働きたいクルー(働き手)を繋げるサービスです。クルーは自身のスキルや時間を活用して働くことができます。</p>
<p>メルカリ ハロは複数のアプリケーションが存在し、そのなかでWebフロントエンドが関わる領域として以下の3つがあります:</p>
<ol>
<li>はたらくタブ</li>
<li>事業者管理画面</li>
<li>CS Tool</li>
</ol>
<p>それぞれのアプリケーションの概要は以下の通りです。</p>
<h3>はたらくタブ</h3>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/06/3b377ef9-work-tab-1024x683.png" alt="" /></p>
<p>「はたらくタブ」は、メルカリアプリ内に用意されている、クルーを対象とした機能です。「はたらくタブ」は、スポットワークの経験がない人や、時々働きたいライトなユーザーに向けた機能で、この機能を使うことで、メルカリ ハロの専用アプリと同様に仕事を見つけて働くことができます。またすでにメルカリを使っているお客さまにも利用していただくことで、「メルカリ ハロ」の迅速な認知拡大が期待されています。</p>
<p>関連記事:<a href="https://mercan.mercari.com/articles/41558/" title="『メルカリ ハロ』の成長を加速させる最重要タッチポイント、「はたらくタブ」はこうしてつくられた">『メルカリ ハロ』の成長を加速させる最重要タッチポイント、「はたらくタブ」はこうしてつくられた</a></p>
<p>この「はたらくタブ」はiOSおよびAndroidのメルカリアプリ内のWebView上で動作します。各OSごとに開発する必要がなく、Web開発のみで対応できるため、開発工数を削減できました。<br />
また、リリース時にはアプリストアの審査が不要となるため、柔軟にリリースを行うことが可能です。<br />
さらに、既存のメルカリアプリ内に存在するためアクセス数が多い特徴もあります。</p>
<h3>事業者管理画面</h3>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/05/c57d5c72-mockup_-partner-portal-1024x751.png" alt="" /></p>
<p>事業者管理画面は、メルカリ ハロにおける求人や募集の作成など、パートナー向けのWebアプリケーションです。権限管理が必要な点や、フォームを使った登録・更新の機能が多い点が特徴です。</p>
<h3>CS Tool</h3>
<p>社内のメルカリ ハロのカスタマーサービス向けのWebアプリケーションです。<br />
お問い合わせに対応するために、クルー検索などの機能が備わっています。</p>
<h2>スピードと品質を両立して開発するための取り組み</h2>
<p>メルカリ ハロを展開するにあたって、連載の最初の記事でも紹介したように、開発のスピードを重要視しています。これはスポットワーク市場の拡大の波に沿って事業を推進するためです。また、最初から正しい仮説を持つことは難しく、仮説の立案、検証、改善のイテレーションを高速に回すことが大事だからです。</p>
<p>またスピード感をもった開発が求められる一方で、高い品質を保つことも重要です。</p>
<p>現在、メルカリ ハロのWebフロントエンドチームは「はたらくタブ」「事業者管理画面」「CS Tool」の3つのWebアプリケーションの開発を担当しています。</p>
<p>この状況下でどのように工夫をして、開発スピードと品質の両立を達成しているかについてご紹介します。</p>
<h3>柔軟なアサイン</h3>
<p>「はたらくタブ」「事業者管理画面」「CS Tool」を開発するにあたって、特定のWebアプリケーションに優先度の高い機能開発の案件が集中することがあります。これに対応するため、Webフロントエンドチームでは特定のWebアプリケーションに担当を固定せず、横断的にアサインが可能な体制にしています。例えば、「事業者管理画面」に優先度の高い機能開発の案件が多くなった場合、その開発にリソースを集中させます。状況に応じて柔軟にアサインを変えられる体制にすることで、優先度の高い機能をスピード感もって開発することができました。</p>
<h3>技術スタック・開発ルールを可能な限り統一</h3>
<p>柔軟なアサイン体制の下で生産性と保守性を上げるために「はたらくタブ」「事業者管理画面」「CS Tool」の技術スタックやアプリケーションの構造、開発ルールなどは可能な限り統一しております。<br />
ただし、技術的な問題、アプリケーションの仕様の違いなどにより、一部に差異が生じる箇所も存在しています。</p>
<p>「はたらくタブ」「事業者管理画面」「CS Tool」で扱っている主な技術スタックは以下の通りになります:</p>
<ul>
<li>Next.js (App Router)</li>
<li>React</li>
<li>TypeScript</li>
<li>ESLint</li>
<li>Apollo Client <sup>※</sup></li>
<li>Tailwind CSS</li>
<li>Storybook</li>
<li>React Hook Form</li>
<li>Jest</li>
<li>Playwright</li>
<li>pnpm</li>
<li>Datadog</li>
</ul>
<p>統一の結果、普段担当していない別のWebアプリケーションにアサインされた場合でもスムーズに開発を行うことができました。例えば「事業者管理画面」を開発していたメンバーが「はたらくタブ」の開発を任された場合でも、ほとんど同じ技術スタックや開発の作法を活用できるため、追加の学習コストを抑えて開発に取り組むことができました。また、開発ドキュメントもある程度共通化することができるため、ドキュメントの保守性も向上しました。</p>
<p>さらに、メルカリ ハロではmonorepoを採用(<a href="https://engineering.mercari.com/blog/entry/20240529-mercari-hallo-tech-stacks/" title="関連記事">関連記事</a>)し、異なるWebアプリケーションを同一リポジトリで管理しています<sup>※</sup>。これにより、Webアプリケーション間の行き来が容易になったり、lintのルールの設定などがpnpmのworkspaceを使って共有可能になりました。</p>
<div class="annotation">※ただし、CS Toolは除く</div>
<pre><code>...
├── dart
├── go
└── typescript
├── apps
│ ├── partner-portal-web //事業者管理画面
│ ├── work-tab //はたらくタブ
├── libs //共通の関数やlintのルールの設定
...</code></pre>
<p>これらの取り組みによって開発生産性と保守性が向上しただけでなく、誰かが開発で困ったときに他のメンバーが助けやすい体制が整い、チームとして支え合う文化が一層強まったことも大きなメリットだと思います。</p>
<p>以降、その統一された技術スタックでどのように開発しているか紹介します。</p>
<h4>UI開発</h4>
<p>UI開発において生産性を上げるために社内のメルカリデザインシステム(<a href="https://mercan.mercari.com/articles/41738/" title="関連記事">関連記事</a>)を採用しています。</p>
<p>メルカリデザインシステムはFigmaで提供されており、デザイナーはデザインシステム上のトークンやUIコンポーネントを参照して画面を作成しています。フロントエンド開発では、Figma上で使用すべきコンポーネントや色が明示されているため、迷わず開発できるようになっています。また、<a href="https://tailwindcss.com/docs/adding-custom-styles#customizing-your-theme" title="カスタムテーマ">カスタムテーマ</a>の設定で、メルカリのデザインシステムのトークンをTailwind CSSに統合しています。これによって、レイアウトの調整やデザインシステムが提供していないUIコンポーネントを作成する際に、簡単に一貫性のあるスタイルを適用することが可能です。</p>
<p>メルカリデザインシステムにもとづいて画面を作成することで、UIコンポーネントの開発工数を削減し、デザイナーと開発者間でUIの細かい挙動についての認識も一致させやすくなりました。</p>
<p>さらに、デザインレビュー時にはプルリクエストの段階でテスト環境にデプロイし、その環境で画面の動作を確認してもらうことができます。実際の画面を操作することで、デザイナーと開発者の認識を更に一致させることができました。</p>
<h4>バックエンドとのやり取り</h4>
<p>メルカリ ハロのフロントエンドとバックエンドとのやり取りはGraphQLで行っており、そのAPI Clientとして社内で利用実績が多く、キャッシュ管理などの豊富な機能を持つApollo Client を採用しています<sup>※</sup>。ここで取り入れた仕組みや活用している機能についていくつか紹介します。</p>
<div class="annotation">※ただし、CS Toolは除く</div>
<p></p>
<h5>Apollo ClientのHooksを自動生成</h5>
<p>ページに必要なAPIのフィールドをGraphQLのスキーマファイルをもとに定義し、Apollo ClientのHooksを自動生成しています。</p>
<p>monorepoを採用しているため、同一リポジトリ内のフロントエンドの開発ディレクトリからバックエンドの開発ディレクトリに配置されているGraphQLのスキーマファイルを直接参照でき、それをもとにApollo ClientのHooksを自動生成するといった仕組みです。</p>
<p>これにより、バックエンドからデータを取得する際には、その自動生成されたApollo ClientのHooksを呼び出すだけで済み、開発効率向上につながりました。また、ページに必要なデータのフィールドのみを取得し、APIの呼び出しも一回にまとめられるため、ネットワークのコストを抑えることができました。</p>
<p></p>
<h5>Apollo Clientの機能の活用</h5>
<p>Apollo Clientはデータ取得以外にも多くの機能を持っています。一例として、キャッシュの機能です。実行したqueryのレスポンスデータがローカルのインメモリ上にキャッシュされるため、そのキャッシュを活用することができます。例えば、useQueryの<a href="https://www.apollographql.com/docs/react/data/queries/#supported-fetch-policies" title="fetchPolicy">fetchPolicy</a>がデフォルトのcache-first の場合ですが、fetch時にメモリにキャッシュが存在していたらそのデータを返します。ただし、存在しない場合はサーバーから取得します。キャッシュを優先しているため速度を重視する場合には有効かと思います。またこのfetchPolicyの設定は用途に合わせて変更可能です。</p>
<p>「事業者管理画面」では、データの表示速度も重要ですが、募集の応募状況など最新の情報を求められることが多く、データの新鮮さも重要です。そのため、キャッシュを活用しつつ、同時にサーバーから最新のデータも取得・更新するcache-and-networkの設定をデフォルトでしています。</p>
<p>さらに、Apollo Clientには<a href="https://www.apollographql.com/docs/react/api/link/introduction/" title="Link">Link</a>という機能があります。これはApollo ClientとGraphQLサーバー間のデータフローをカスタマイズするための仕組みです。特にサーバーからのエラーハンドリングにおいてLinkを活用しています。例えば、Unavailableのエラーを返した場合にメンテナンスページにリダイレクトしたり、認証が切れたらtokenをリフレッシュする処理を実装しています。</p>
<p></p>
<h5>モック開発</h5>
<p>バックエンドのAPIが開発中であっても、フロントエンドの開発を進めたい場合があります。<br />
その際に、まず先にバックエンドからGraphQLのスキーマファイルを共有してもらい、そのスキーマをもとに<a href="https://mswjs.io/" title="MSW">MSW</a>を利用してAPIをモックし開発しています。この結果、フロントエンドとバックエンドが並列して開発でき、生産性の向上につながりました。</p>
<h4>自動テスト</h4>
<p>リグレッションの自動検知を目的に自動テストを導入しています。<br />
導入にあたって自動テストに費やせる時間と人手では限られているため、効率的・効果的にリグレッションを防ぐためのテスト設計が必要でした。</p>
<p>そこで一つの観点として、他のチームよって品質が担保されているテストと被らないことを重要視しました。以下のテストに関してはすでに他のチームで行われていました。</p>
<ul>
<li>UIコンポーネントのテスト
<ul>
<li>前述の通り、メルカリ ハロのUIコンポーネントはメルカリデザインシステムを使っており、そのチームよって自動テストが行われている</li>
</ul>
</li>
<li>E2Eテスト
<ul>
<li>QAチームが手動および自動で(フロントエンドからバックエンドまでの疎通を含む)E2Eテストを行っている</li>
</ul>
</li>
</ul>
<p>この観点のもと、Webフロントエンドでは以下の自動テストを設計・運用しました。結果として、他のテストでカバーできていないテストを書く運用になり、効率的に品質を担保することができました。</p>
<ul>
<li>単体テスト
<ul>
<li>Jestを利用</li>
<li>共通関数の詳細な挙動を担保するため、条件分岐を網羅したテストを実施</li>
</ul>
</li>
<li>ページ単位の統合テスト
<ul>
<li>Playwrightを利用</li>
<li>機能が仕様通り動いているかを主にページ単位でテストを実施</li>
<li>バックエンドへの通信を行わず、必要なAPIのデータはモックし、フロントエンドに閉じた形で実施</li>
</ul>
</li>
</ul>
<p>運用にあたって、単体テストについては、共通関数として切り出したときにテストを書く運用がチームメンバーに浸透していたため、特に問題はありませんでした。</p>
<p>一方でページ単位の統合テストに関しては、対象となるページ数が多く、様々なテストシナリオが考えられるため、どの統合テストを重視して書くべきかが課題でした。例えば、特定のレスポンスが返ってきた際のページの表示が正しいかどうか、入力したフォームのバリデーションが正しく動作するかどうかなどです。そのため、どのテストを優先して書けば大きな効果を発揮するか検討する必要がありました。</p>
<p>そこで「はたらくタブ」「事業者管理画面」「CS Tool」それぞれのアプリケーションの性質に合わせて統合テストの方針を決めました。</p>
<p>例えば「事業者管理画面」では、フォームをSubmitした後のAPIのリクエストが正しいかのテストを第一優先としました。これは管理画面の性質上、募集作成など重要なデータの作成機能が多いため、意図しないリクエストが送信されて登録されてしまうと大きな問題が発生するためです。</p>
<p>このように、他で品質が担保されていない箇所を単体テスト・統合テストで担保し、統合テストのなかでアプリケーションの性質に合わせて方針を決めることで、効率的・効果的にリグレッションを防ぐことができました。</p>
<h2>おわりに</h2>
<p>スピードと品質を両立させるためのWebフロントエンドの取り組みについて紹介しました。</p>
<p>「はたらくタブ」「事業者管理画面」「CS Tool」のアプリケーション間で可能な限り統一した技術を扱い、フロントエンドチームメンバー内で状況に応じた柔軟なアサインによって、お客さまが求める重要な機能をスピードと品質を両立させて開発し続けられる体制になりました。また、同じチームで性質の異なる3つのWebアプリケーションを経験することは、エンジニアにとって楽しく、やりがいのある挑戦でもありました。</p>
<p>更にこの体制によって、誰かが開発で困った場合でも他のメンバーが助けて支え合う「All For One」な文化が一層強まってると日々感じております。</p>
<p>その「All For One」な文化はメルカリグループ全体からも感じています。メルカリのデザインシステムがあらかじめ用意されていたり、 monorepo戦略がその一例です。自チームにとどまらず全体最適になるよう取り組んでくれたおかげで、この開発スピードと品質の両立が実現できてると感じてます。</p>
<p>この取り組みは終わりではなく、今後もチームやプロダクトの状況を見ながら、前向きに改善し続けることが重要です。引き続き、チームと話し合いを重ねて改善に努めていきたいと思います。</p>
<h2>Links</h2>
<p><a href="https://engineering.mercari.com/blog/entry/20240524-mercari-hallo-world/" title="連載:Mercari Hallo, world! -メルカリ ハロ 開発の裏側-">連載:Mercari Hallo, world! -メルカリ ハロ 開発の裏側-</a><br />
メルカリではメンバーを大募集中です。メルカリ ハロの開発やメルカリに興味を持った方がいればぜひご応募お待ちしています。詳しくは以下のページをご覧ください。</p>
<ul>
<li><a href="https://apply.workable.com/mercari/j/A53FA51E42/" title="Software Engineer, Frontend - Mercari/HR領域新規事業 (Mercari Hallo)">Software Engineer, Frontend – Mercari/HR領域新規事業 (Mercari Hallo)</a></li>
<li><a href="https://apply.workable.com/mercari/j/197FBA1617/" title="Software Engineer, Backend - Mercari/HR領域新規事業 (Mercari Hallo)">Software Engineer, Backend – Mercari/HR領域新規事業 (Mercari Hallo)</a></li>
<li><a href="https://apply.workable.com/mercari/j/436EAEC812/" title="Software Engineer, iOS/Android (Flutter) – Mercari/HR領域新規事業 (Mercari Hallo)">Software Engineer, iOS/Android (Flutter) – Mercari/HR領域新規事業 (Mercari Hallo)</a></li>
<li><a href="https://apply.workable.com/mercari/j/62287F2907/" title="Software Engineer, Site Reliability - Mercari/HR領域新規事業 (Mercari Hallo)">Software Engineer, Site Reliability – Mercari/HR領域新規事業 (Mercari Hallo)</a></li>
<li><a href="https://apply.workable.com/mercari/j/0086D4DA33/" title="QA Engineer - Mercari/HR領域新規事業 (Mercari Hallo)">QA Engineer – Mercari/HR領域新規事業 (Mercari Hallo)</a></li>
<li><a href="https://apply.workable.com/mercari/j/1103382B83/" title="Engineering Manager – Mercari/HR領域新規事業 (Mercari Hallo)">Engineering Manager – Mercari/HR領域新規事業 (Mercari Hallo)</a></li>
</ul>
- メルカリ ハロ アプリの技術スタックの紹介https://engineering.mercari.com/blog/entry/20240606-mercari-hallo-app-tech-stacks/https://engineering.mercari.com/blog/entry/20240606-mercari-hallo-app-tech-stacks/<p>こんにちは。メルカリ ハロでSoftware Engineerをしている @atsumoです。連載『Mercari Hallo, World! -メルカリ ハロ 開発の裏側-』の第5回を担当します。 メルカリ ハロではメ […]</p>
Mon, 10 Jun 2024 08:00:15 GMT<p>こんにちは。メルカリ ハロでSoftware Engineerをしている <a href="https://x.com/atsumo" title="@atsumo">@atsumo</a>です。連載『<a href="https://engineering.mercari.com/blog/entry/20240524-mercari-hallo-world/" title="Mercari Hallo, World! -メルカリ ハロ 開発の裏側-">Mercari Hallo, World! -メルカリ ハロ 開発の裏側-</a>』の第5回を担当します。</p>
<p>メルカリ ハロではメルカリアプリ内にある「はたらくタブ」とは別にクルー向けのアプリ(ストアで「メルカリ ハロ」と検索してみてください)を用意しています。本記事では、アプリ版のメルカリ ハロで使用している技術とその選定理由、さらにリリースまでの開発の進め方などを踏まえてご紹介したいと思います。</p>
<h2>この記事で得られること</h2>
<ul>
<li>メルカリ ハロのアプリの技術スタック</li>
<li>その技術選定の理由と効果</li>
<li>開発の進め方とヒント</li>
</ul>
<h1>技術スタック</h1>
<p>メルカリ ハロ アプリで使っている技術スタックをいくつかピックアップして紹介できればと思います。</p>
<h2>フレームワーク: Flutter</h2>
<p><a href="https://engineering.mercari.com/blog/entry/20240529-mercari-hallo-tech-stacks/" title="第2回の記事">第2回の記事</a>の中のメルカリ ハロのモバイルアプリの技術選定でも記載がありましたが、メルカリ ハロは マルチプラットフォームフレームワークのFlutterを採用しています。導入決定までの決断などは第2回で触れられているのでそちらをご覧いただければと思います。そちらの記事の中でも妥当な決断であったとありましたが、リリース後にアプリ開発に関わっているメンバーで振り返りを行った際も、開発効率の向上とサービスとしての品質の担保の両方行うことができ、良い選択だったという意見が多かったです。</p>
<p>開発人数が少ない中、両プラットフォームを同じタイミングでリリースすることができたのはFlutterを採用したことがとても大きかったです。</p>
<h2>CLI: Melos</h2>
<p><a href="https://melos.invertase.dev/" title="Melos">Melos</a>は複数パッケージをもつDartプロジェクトの管理に使用されるCLIツールです。<br />
メルカリ ハロではモノレポ(monorepo)を採用しており、下記のような言語ごとのディレクトリ構造になってます。</p>
<pre><code>├── dart
│ ├── analysis_options.yaml
│ ├── apps
│ │ ├── widgetbook
│ │ └── hallo_app (ハロ アプリ)
│ ├── melos.yaml
│ ├── packages
│ │ ├── hallo_design_system (デザインシステム)
│ │ ├── hallo_linter
│ │ └── ...
│ ├── pubspec.lock
│ └── pubspec.yaml
├── go
...
└── typescript</code></pre>
<p>開発当初、Flutterのプロジェクト自体は1つで複数のパッケージはなかったのですが、後でデザインシステムや社内のシステムを使うものはパッケージに分けていく構想があったため、開発開始時点からMelosを導入していました。初期は主にコマンドをまとめるための用途としてしか使っていなかったのですが、リリース後少し落ち着いたタイミングでデザインシステムをパッケージ分割したり、社内のサービスを使うためのパッケージが増えていったりと複数パッケージを持つプロジェクトとなり、引き続き力を発揮してくれています。</p>
<h2>通信: GraphQL</h2>
<p>モバイルアプリとバックエンドの通信にはGraphQLを採用しています。</p>
<p>メルカリ ハロのアプリでは GraphQLを使用するための packageとして <a href="https://pub.dev/packages/graphql_flutter" title="graphql_flutter">graphql_flutter</a>, <a href="https://pub.dev/packages/graphql_codegen" title="graphql_codegen">graphql_codegen</a> を使用しています。</p>
<p>バックエンド側で定義した graphqlのschemaとアプリ側でアクセスするgraphqlファイルを元に graphql_codegen でGraphQLのサーバーにアクセスするためのファイルを自動生成しています。</p>
<p>サーバリクエストとレスポンスデータのキャッシュはgraphql_flutterが行ってくれるため、次にお話しする状態管理において構成を単純化するのに大きく寄与しました。</p>
<h2>状態管理について</h2>
<p>公式ドキュメント <a href="https://docs.flutter.dev/data-and-backend/state-mgmt/ephemeral-vs-app" title="Differentiate between ephemeral state and app state">Differentiate between ephemeral state and app state</a> に ephemeral state(一時的な状態管理) と app state(それ以外の状態管理) と分かれて記載されているのでそれに沿って説明します。</p>
<h3>Ephemeral state (一時的な状態管理)</h3>
<p>Widget内やScreen内で完結するようなステートには flutter_hooks を使用しています。具体的には StateWidgetでのStateにもたせていたようなものには useStateを使用しています。画面を開いた際に画面の閲覧のログなどを送信したい場合には useEffectなどを使用しています。さらに必要に応じてカスタムHooksを作成しています。</p>
<h3>App state (サーバリクエストとレスポンスデータ / グローバルステート)</h3>
<h4>サーバサイドリクエストやキャッシュ</h4>
<p>GraphQLでのやり取りをするために、graphql_flutterを使用しています。graphql_flutterはApollo Clientをモデルとしたgraphqlのpackageです。</p>
<p><a href="https://www.apollographql.com/docs/" title="Apollo Client">Apollo Client</a>はローカルとリモートデータの両方をGraphQLで管理することができる包括的な状態管理のライブラリです。ただ現在、Apollo ClientはFlutter版のpackageがないため、その代わりとして Apollo Clientをモデルとして作られている grahql_flutter を選択しました。</p>
<p>GraphQL自体の仕組みや graphql_flutter が持つキャッシュの仕組みによって、クライアント側で独自の管理をする必要をなくし、状態管理の複雑性を削減しています。<br />
※ graphql_codegenが生成する hooksのメソッドを使用しています</p>
<h4>グローバルステート</h4>
<p>画面を構成するうえで必要な情報はgraphql_flutterの情報から取得するのですが、複数画面に使用されるものや認証情報などに関しては <a href="https://riverpod.dev/" title="Riverpod">Riverpod</a>を使用してグローバルステートとして管理しています。</p>
<h2>デザインシステム</h2>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/06/4f360a8e-hallo_design_system_figma_image.png" alt="デザインシステム_button" /></p>
<p>メルカリ ハロのアプリでは、メルカリのデザインシステムではなく、独自のデザインシステムを採用しています。ここでお話するデザインシステムは主にデザイントークンやUIコンポーネント、そのデザインデータや実装されたコンポーネントなどを指しています。 基本的にコンポーネントをベースに画面のUIデザインが行われています。立ち上げ時にはひたすらUIコンポーネントを実装してWidgetbook(後述)で確認し、一通りコンポーネントを実装し終わったあとに画面の実装に入っていきました。</p>
<p>Figma上のComponent propertiesでBooleanやTextだけでなくInstance SwapやVariantを用いてデザインしてもらっていたので、実装する際も他のWidgetがプロパティとして入ることが想像でき、デザインデータとして変更できるプロパティと実装上コンポーネントのプロパティのギャップが少ない状態を作ることができました。</p>
<p>各画面のデザインは主にコンポーネントを使った構成になってます。実装時にはコンポーネントを配置し、プロパティの値にFigmaのものを入れていくというシンプルな作業になり、生産性が非常に高く保たれたと感じています。</p>
<p>画面が増え、機能が増えることで、当時は想定できていなかった見せ方が必要になり、コンポーネント自体のアップデートであったり、新たなコンポーネントを追加することもありますが、初期の段階からある程度コンポーネントができていて画面実装に入れたのはとてもありがたかったです。このようにコンポーネントを先行して実装していく進め方は、他のプロジェクトでも参考にしていただけるのではないでしょうか。</p>
<p>開発したコンポーネントは、次に触れるWidgetbookを使用してカタログとして閲覧できるようにしています。</p>
<h2>UIカタログ: Widgetbook</h2>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/06/c065f10a-hallo_widgetbook_image.png" alt="widgetbook" /></p>
<p><a href="https://www.widgetbook.io/" title="Widgetbook">Widgetbook</a>はフロントエンドでよく使われるStorybookのFlutter版のようなツールで、デザインシステムで定義したUIコンポーネントや実装したWidgetなどをカタログのように表示することができます。</p>
<p>デザインシステムとして用意しているUIコンポーネントをカタログのように見れることで、途中からチームに参画したメンバーもUIコンポーネントとして用意してあるものを一覧で見ることができたので、画面実装する際もスムーズに行うことができたという声もありました。</p>
<p>画面実装が進むにつれて実際の画面で確認することなどが増えましが、特定条件でのみしか表示されないようなUIに関してはWidgetbookを使用して素早く確認することができとても重宝しております。</p>
<p>現在Widgetbook 3を使用していますが、Widgetbook 4では<a href="https://github.com/flutter/flutter/blob/master/docs/contributing/testing/Writing-a-golden-file-test-for-package-flutter.md" title="Goldenテスト">Goldenテスト</a>を再利用することができる仕組みやモノレポ(monorepo)での使い勝手などを考慮した改善などが検討されているようなので、次のバージョンを期待しています。</p>
<h1>まとめ</h1>
<p>メルカリ ハロのアプリ開発では、いくつかの技術選定が開発を進める上で大きな役割を果たしてくれました。</p>
<ul>
<li>Flutterの採用により、少人数でのスタートにも関わらず、iOSとAndroidの両プラットフォームを同時にリリースすることができた。</li>
<li>将来的なパッケージの分割も見据えて、モノレポ(monorepo)でのプロジェクト管理にMelosを導入したことで、複数パッケージの管理が容易になった。</li>
<li>バックエンドとの通信にはGraphQLを採用することでサーバーとのやり取りがスムーズになり、状態管理の構成をシンプルに保つことができた。</li>
<li>開発当初からデザインシステムの構築を最優先に進めたことで、デザイナーとの共通言語ができ、Widgetbookを使ってカタログ化することで開発効率を大幅に向上させることができた。</li>
</ul>
<p>私たち開発チームにとって、これらの技術選定と開発方針が、メルカリ ハロのアプリ開発を支える大きな力になったと感じています。</p>
<h1>さいごに</h1>
<p>メルカリ ハロアプリの開発では、FlutterやGraphQLなどをはじめとする多様な技術を採用しましたが、あくまで現在のチーム状況やサービス内容において適した選択でした。まだまだサービス的にもシステム的にも改良の余地があるので、常により良いものを探求していきたいと思っています。</p>
<p>プロジェクトごとにフィットする技術や方法は異なると思いますが、今回ご紹介したメルカリ ハロでの事例が、みなさんにとって最適解を見つけるためのヒントになれば幸いです。</p>
<h1>Links</h1>
<p><a href="https://engineering.mercari.com/blog/entry/20240524-mercari-hallo-world/">連載:Mercari Hallo, world! -メルカリ ハロ 開発の裏側-</a></p>
<p>メルカリではメンバーを大募集中です。メルカリ ハロの開発やメルカリに興味を持った方がいればぜひご応募お待ちしています。詳しくは以下のページをご覧ください。</p>
<ul>
<li><a href="https://apply.workable.com/mercari/j/A53FA51E42/">Software Engineer, Frontend – Mercari/HR領域新規事業 (Mercari Hallo)</a></li>
<li><a href="https://apply.workable.com/mercari/j/197FBA1617/">Software Engineer, Backend – Mercari/HR領域新規事業 (Mercari Hallo)</a></li>
<li><a href="https://apply.workable.com/mercari/j/436EAEC812/">Software Engineer, iOS/Android (Flutter) – Mercari/HR領域新規事業 (Mercari Hallo)</a></li>
<li><a href="https://apply.workable.com/mercari/j/62287F2907/">Software Engineer, Site Reliability – Mercari/HR領域新規事業 (Mercari Hallo)</a></li>
<li><a href="https://apply.workable.com/mercari/j/0086D4DA33/">QA Engineer – Mercari/HR領域新規事業 (Mercari Hallo)</a></li>
<li><a href="https://apply.workable.com/mercari/j/1103382B83/">Engineering Manager – Mercari/HR領域新規事業 (Mercari Hallo)</a></li>
</ul>
- メルカリ ハロ リリースのQA戦略https://engineering.mercari.com/blog/entry/20240603-mercarihallo-releaseqastrategy/https://engineering.mercari.com/blog/entry/20240603-mercarihallo-releaseqastrategy/<p>こんにちは。メルカリのQAエンジニアリングマネージャーの@____rina____ です。今回は、連載『Mercari Hallo, World! -メルカリ ハロ 開発の裏側-』の第4回を担当します。 本記事では、メル […]</p>
Thu, 06 Jun 2024 08:00:16 GMT<p>こんにちは。メルカリのQAエンジニアリングマネージャーの<a href="https://engineering.mercari.com/blog/author/underscore42rina/" title="@\_\_\_\_rina\_\_\_\_">@____rina____</a> です。今回は、連載『<a href="https://engineering.mercari.com/blog/entry/20240524-mercari-hallo-world/" title="Mercari Hallo, World! -メルカリ ハロ 開発の裏側-">Mercari Hallo, World! -メルカリ ハロ 開発の裏側-</a>』の第4回を担当します。</p>
<p>本記事では、メルカリ ハロのサービスローンチまでのQAプロセスを通じて、私たちはどのようにして安心・安全なプロダクトを迅速にリリースするための戦略を実行したか、具体的な方法とともに詳述しています。<br />
この記事を通じて、以下の点についての理解を深めていただけることを目指しています:</p>
<ul>
<li>QAの役割とプロジェクト概要</li>
<li>効率的なQAアサイン戦略</li>
<li>成果物の透明性と管理ツールの効果的な活用方法</li>
</ul>
<p>また、この記事を書くにあたり、私自身が学んだことや得た教訓についても触れています。これらの経験は、今後のプロジェクトにおいて更なる品質向上と効率化を目指す上で非常に貴重なものとなりました。</p>
<p><!--more--></p>
<h2>プロジェクト概要とQAの役割</h2>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/05/1001ba22-hallo_service-1024x575.png" alt="" /></p>
<p>「メルカリ ハロ」は、事業者や店舗である”パートナー”と、働き手である”クルー”を結びつけるサービスです。クルーは自身のスキルや時間を活用して働くことができます。「メルカリ ハロ」はメルカリのWorkチームという組織で開発をおこないました。また、このサービスにはWorkチーム以外にも多くのチームが密接に関わっています。例えば、事業者アカウントの作成やKYC、給与振込などはメルペイが担当し、経理はホールディングスのチームが、問い合わせ対応はメルカリが担当しています。<br />
このサービスにおけるQAの役割は、サービスローンチまでに開発を最短かつ安心・安全に進めるための戦略を実行することです。具体的には、品質保証アプローチを駆使し、プロダクトの全体的な品質を高めるとともに、スムーズなリリースを実現することです。</p>
<h2>QAエンジニアのアサイン戦略</h2>
<p>サービスローンチの当初の計画では、私がすべてのQA活動およびテスト実行を担当する予定でした。しかし、組織や開発の規模が想定以上に大きくなったため、追加のメンバーが必要となりました。その結果、QAエンジニアの採用に加え、業務委託と外部ベンダーの協力を得て、QA活動を以下の3つの主要な役割に分けて進行することにしました。<br />
なお、メルカリアプリにある「はたらくタブ」の開発はメルカリのメンバーが開発しました。</p>
<h3>メルカリ(Workチーム)のQAエンジニア</h3>
<p>WorkチームのQAエンジニアは、テストプロセス全般に加え、以下のタスクを担当しました:</p>
<ul>
<li>オンボーディング資料の作成:これから参画するQAエンジニアやチームのために、オンボーディング資料を作成しました。</li>
<li>テスト設計:各ユーザーストーリーにAcceptance Criteriaを追加しました。Acceptance Criteriaについては、こちらのブログもご参照ください。参照:<a href="https://engineering.mercari.com/blog/entry/20220912-cf3da857e5/" title="QAがAcceptance Criteriaにテストしたい項目を追加して、みんなでいつ何をつくるのか考えたよ">QAがAcceptance Criteriaにテストしたい項目を追加して、みんなでいつ何をつくるのか考えたよ</a></li>
<li>プロジェクト進捗把握:QAの進捗だけでなく、開発の進捗も管理することで、スムーズなリリースを支援しました。開発の進捗はJIRAで把握したかったので、チケットも率先して作成しました。</li>
<li>各種ドキュメント作成:テスト計画書、テスト完了報告書などを作成しました。</li>
<li>他カンパニーのQAとの連携:今回のサービスでは他のカンパニーとのシステム連携が多かったため、障害を未然に防ぎ、スムーズに開発を進めるためにQA間での連携が必要不可欠でした。定期的なミーティングやドキュメントの認識共有、進捗確認などを通じてコミュニケーションを密に行うことを心掛け、カンパニー間で連携の齟齬が発生しないよう努めました。<br />
各詳細については、後で詳しく説明します。</li>
</ul>
<h3>アルムナイの業務委託のQAエンジニア</h3>
<p>メルカリのことをよく知るアルムナイ(元社員)のQAエンジニアに業務を委託しました。本業との兼ね合いで時間が限られていたため、時間を最大限に活かしてもらう必要がありました。主に以下の役割を担いました。:</p>
<ul>
<li>オンボーディング資料の更新と作成:新しいチームメンバーがスムーズに参加できるよう、オンボーディング資料を作成および更新しました。</li>
<li>エピック単位のテストと探索的テスト:エピックとは、機能のまとまりを表す単位です。私たちはそれぞれのエピックにユーザーストーリーを紐づけます。通常はユーザーストーリーごとにテストを行いますが、業務委託のメンバーは稼働時間が限られているため、彼らの能力を最大限に活かすために探索的テストを中心に実行しました。<br />
この際、エピックごとにテストを実施し、エピックに対応するユーザーストーリーのテストを行いました。こうして、各機能がうまく連携して動作するかを確認しました。また、Acceptance Criteriaに縛られずに、探索的にテストすることで予期しないバグや問題を早期に発見することを目指しました。</li>
<li>リグレッションテストの作成:サービスローンチ前に多くの機種やOSでも正常に動作することを保証するためにリグレッションテストを実施する必要があります。また、開発中は一貫した開発環境が整っていなかったため、リリース前に一貫した環境で動作することも目的としています。サービスローンチ前の状況では、機能開発に追われてリグレッションテストのケースを作成する時間が取れません。また、記載に一定のルールがないと、テスト実行が難しくなってしまいます。業務委託のメンバーはアルムナイのため、メルカリ時代の知見があり、機能開発に引っ張られずにリグレッションテストの方針を理解し、作成することができます。これにより、多くの人が一緒にテストを実行できる体制を整えました。</li>
<li>テスト設計レビュー:Acceptance Criteriaのレビューおよび修正を行いました。</li>
</ul>
<h3>ベンダーのQAエンジニア</h3>
<p>以前メルカリに参画していたメンバーを中心に、外部ベンダーに参画していただき、以下の活動を行いました:</p>
<ul>
<li>テスト設計からテスト実行:テスト設計から実行を担当し、特定の分野における専門知識を活用しました。今回は開発期間もタイトであったため、ユーザーストーリー単位に実行できるものからテスト実行しました。また、他カンパニーとのシステム統合テストもキャッチアップからテスト実行まで実施しました。</li>
<li>リグレッションテストの設計から実行:システム全体の安定性を維持するためにリグレッションテストを実施しました。</li>
</ul>
<p>以上のように、多様なメンバーと役割分担を用いたQA戦略を採用し、組織の規模と開発の複雑さに対応しました。QA活動の効率化と品質の確保を両立させるために、このような工夫を取り入れました。</p>
<h2>成果物の透明性と管理ツールの利用</h2>
<p>ここでいう成果物とは、QA活動において作成したドキュメント類を指します。今回は主に以下の成果物を作成しました。</p>
<ul>
<li>テスト計画書</li>
<li>オンボーディング資料</li>
<li>テスト設計時のAcceptance Criteria</li>
<li>リグレッションテストのためのテストケース</li>
<li>QAダッシュボード</li>
<li>プロジェクト管理ダッシュボード</li>
<li>テスト完了報告書</li>
</ul>
<p>これらの管理はJIRA、Confluence、TestRail、およびGoogle Spreadsheetsを使用して行いました。すべての成果物はクラウド上に保存され、社内で誰でも参照できるようにしました。これにより、現在の状況などにアクセスしやすくし、いつでも参照できる環境を整えました。<br />
このようにして、成果物の透明性を高め、プロジェクト管理ツールを効果的に利用することで、プロジェクト管理の効率化と円滑なコミュニケーションを実現しました。<br />
次にそれぞれの成果物について具体的に紹介します。</p>
<h3>テスト計画書</h3>
<p>テスト計画は、サービスローンチの成功に不可欠です。前述のとおり、QAに関しても共通して参照できるドキュメントが必要でした。そのひとつがテスト計画書です。<br />
テスト計画書の作成には、国際標準である<a href="https://www.iso.org/standard/79429.html" title="ISO/IEC/IEEE 29119のPart3">ISO/IEC/IEEE 29119のPart3</a>を参考にしました。ISO/IEC/IEEE 29119はソフトウェアテストの国際規格で、ソフトウェアテストに関するプロセス、ドキュメント、技術などを定義しています。Part3はドキュメントについて定義しています。今回は、主に以下の構成で作成しました:</p>
<ul>
<li>用語集: プロジェクト内で使用される専門用語や略語の明確な定義を提供し、プロジェクト関係者間の誤解を防ぐためです。社内で使用されている他の用語集にないQA特有の用語も含めました。</li>
<li>テストの成果物: テストケース仕様、テストスクリプト、テストレポートなど、生成されるすべてのドキュメントをリストアップしました。</li>
<li>テスト環境: 「メルカリ ハロ」はリリース前ということもあり、開発中のコードはmainにマージして、開発(=テスト)環境でテスト実行をしました。一方で、事業アカウントや給与振り込みを担当するメルペイはすでにリリースされているサービスで、各開発チームで使用する環境を設定する必要がありました。そのため、「メルカリ ハロ」とのシステム統合テストで利用する環境は厳密に設定する必要がありました。そこで、テスト計画書に「いつからどの環境を使うか」を明確に記載しました。これにより、テスト環境の設定を誤ることのないようにしました。</li>
<li>テストの完了条件: テストが正式に完了するための具体的基準を定義しました。特にメルペイとのシステム統合テストにおけるテスト計画書は、多くのチームメンバーが参照する重要なドキュメントとなりました。</li>
</ul>
<h3>オンボーディング資料</h3>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/05/444fdceb-quest-870x1024.png" alt="" /></p>
<p>オンボーディング資料はいくつかのパートに分けて作成しました。ドキュメントは「オンボーディングクエスト」という名前で、参画したメンバーが各自で進められるようにしています。また、ドキュメントが古くならないように、参画したメンバーがいつでも更新できる仕組みを整えました。オンボーディングクエストについては以下も参考にしてください。参考:<a href="https://engineering.mercari.com/blog/entry/20220309-souzoh-onboarding-with-notion/" title="Notionを活用したエンジニア向けオンボーディング">Notionを活用したエンジニア向けオンボーディング</a></p>
<p>オンボーディング資料には以下の種類があります:</p>
<ul>
<li>全社共通のオンボーディング資料:会社全体で必要となる基本的な情報を提供します。</li>
<li>プロダクトに関わるメンバー用のオンボーディング資料:各プロダクトに関する詳細な情報を提供します。</li>
<li>QAエンジニア用のオンボーディング資料:QAエンジニアがテスト実行に使うための設定ページや、不具合発生時のチケット起票ルールなどを含みます。</li>
</ul>
<p>QAエンジニア用の資料には、主に以下の内容が含まれています:</p>
<ul>
<li>設定ページ:テスト実行に必要な設定方法を詳細に説明します。</li>
<li>チケット起票ルール:不具合発生時のチケット起票方法やルールを説明します。</li>
</ul>
<h3>テスト設計時のAcceptance Criteria</h3>
<p>テストケースはAcceptance Criteriaとして、すべてストーリーチケットに直接記載しました。これにより、エンジニアがセルフチェックに利用できるほか、ストーリー完了後のより大きなまとまり(エピック)単位でのテスト実行にも活用できます。テスト実行が完了した後には、QAレビューを行いますが、ストーリー単位でのレビューが可能なため、効率的に進めることができました。</p>
<h3>リグレッションテストのためのテストケース</h3>
<p>今回QAでは、ストーリー単位でも、エピック単位でも、それぞれでテスト実行をしてきましたが、サービスローンチ前にさまざまなOSやOSバージョンでの動作も担保する必要がありました。<br />
そのため、基本的なシナリオに基づいたテストを実施する必要があり、これを達成するためにリグレッションテストケースを作成しました。このリグレッションテストケースを用いることで、多くの環境での一貫性と安定性を確保しながら、テストを効率的に実行することができます。</p>
<h3>QAダッシュボード</h3>
<p>QAダッシュボードでは、進捗やバグの発生、解決状況を把握することを目的としています。このダッシュボードはJIRAのダッシュボード機能を使用して作成しました。主に以下の内容を表示しています:</p>
<ul>
<li>ユーザーストーリー毎のテストのステータス状況</li>
<li>システム統合テストの進捗状況</li>
<li>ユーザーストーリー毎の不具合対応状況</li>
<li>システム統合テストの不具合対応状況</li>
</ul>
<h3>プロジェクト管理ダッシュボード</h3>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/05/bf335396-backend.png" alt="" /></p>
<p>全体の進捗状況や各タスクの進捗はJIRAのチケットに記載し、ダッシュボードも作成しました。このダッシュボードでは、全体の予定工数、現在の開発ステータス、週ごとの完了工数などを表示できるようにしています。さらに、これらのデータをGoogle SpreadSheetsでシンプルなグラフに変換し、全社ミーティングで進捗を報告するようにしました。このようにすることで、各ロールの進捗を把握するだけでなく、他のロールの予定や課題も共有しやすくなりました。</p>
<h3>テスト完了報告書</h3>
<p>テスト完了報告書はConfluenceで作成しました。テスト完了報告書はリリース判定会議でも使用されます。リリース判定会議とは、新しいサービスや大規模なプロジェクトの際に行われる会議で、私はプロダクトの品質を機能面で評価し、報告する役割を担っていました。最終的な承認はエンジニアリングのVPが行います。事前に承認条件を設定し、テスト完了報告書を使用して適切な評価を提供することで、スムーズに承認を得ることができました。<br />
テスト完了報告書の項目の選定には、ISO/IEC/IEEE 29119のPart3を利用しました。この標準を採用することで、評価項目が適切に盛り込まれ、全体のテストプロセスの一貫性と透明性が確保されました。また、この報告書はJIRAと連携し、データが自動更新される仕組みを取り入れることで、報告の正確性とタイムリーな情報更新を実現しました。</p>
<h2>おわりに</h2>
<p>今回紹介した取り組みにより、「メルカリ ハロ」は大きな問題や遅延もなくサービスローンチをすることができました。今回の活動を通して、短い期間で様々な環境の変化に耐えうるQAの戦略を取ることができたのは大きな収穫でした。QAの役割は単なるテストの実施に留まらず、プロジェクト全体の品質と効率を向上させるための重要な貢献を果たしています。<br />
また、今までのQA活動の経験から得た知識に加えて、JIRAを駆使することでタスクの管理と進捗の可視化を効果的に行うことができました。そして、今まできちんと取り組んだことがなかったISO/IEC 29119標準を用いたドキュメント作成を行い、運用に支障をきたさずに混乱もなくプロジェクトを遂行できたことも大きな学びとなりました。このようなツールと標準の活用が、開発の成功に寄与したと確信しています。<br />
サービスリリース後、QAの役割は少しずつ変化していきました。各QAエンジニアは特定の領域を担当するようになり、Acceptance Criteriaの読み合わせを開始しました。<br />
Acceptance Criteriaの読み合わせを行うことで、PM、デザイナー、エンジニア、QAの間で、Specレビュー(要件レビュー)よりもさらに詳細な議論が可能になります。また、UIのエンドツーエンド(E2E)テストの自動化にも取り組み始めています。</p>
<p>今後も、今回得た経験や学びを今後の開発に活かし、さらなる品質向上と効率化を目指して取り組んでいきたいと思います。</p>
<h2>Links</h2>
<p><a href="https://engineering.mercari.com/blog/entry/20240524-mercari-hallo-world/">連載:Mercari Hallo, world! -メルカリ ハロ 開発の裏側-</a></p>
<p>メルカリではメンバーを大募集中です。メルカリ ハロの開発やメルカリに興味を持った方がいればぜひご応募お待ちしています。詳しくは以下のページをご覧ください。<br />
<a href="https://apply.workable.com/mercari/j/A53FA51E42/" title="Software Engineer, Frontend - Mercari/HR領域新規事業 (Mercari Hallo)">Software Engineer, Frontend – Mercari/HR領域新規事業 (Mercari Hallo)</a><br />
<a href="https://apply.workable.com/mercari/j/197FBA1617/" title="Software Engineer, Backend - Mercari/HR領域新規事業 (Mercari Hallo)">Software Engineer, Backend – Mercari/HR領域新規事業 (Mercari Hallo)</a><br />
<a href="https://apply.workable.com/mercari/j/436EAEC812/" title="Software Engineer, iOS/Android (Flutter) – Mercari/HR領域新規事業 (Mercari Hallo)">Software Engineer, iOS/Android (Flutter) – Mercari/HR領域新規事業 (Mercari Hallo)</a><br />
<a href="https://apply.workable.com/mercari/j/62287F2907/" title="Software Engineer, Site Reliability - Mercari/HR領域新規事業 (Mercari Hallo)">Software Engineer, Site Reliability – Mercari/HR領域新規事業 (Mercari Hallo)</a><br />
<a href="https://apply.workable.com/mercari/j/0086D4DA33/" title="QA Engineer - Mercari/HR領域新規事業 (Mercari Hallo)">QA Engineer – Mercari/HR領域新規事業 (Mercari Hallo)</a><br />
<a href="https://apply.workable.com/mercari/j/1103382B83/">Engineering Manager – Mercari/HR領域新規事業 (Mercari Hallo)</a></p>
- メルカリ ハロ立ち上げ時のSREhttps://engineering.mercari.com/blog/entry/20240603-mercari-hallo-sre/https://engineering.mercari.com/blog/entry/20240603-mercari-hallo-sre/<p>はじめに こんにちは。メルカリ ハロでSRE TLをしている@nakaです。連載:Mercari Hallo, world! -メルカリ ハロ 開発の裏側- の3回目を担当させていただきます。 この記事では、メルカリの新 […]</p>
Mon, 03 Jun 2024 08:00:18 GMT<h1>はじめに</h1>
<p>こんにちは。メルカリ ハロでSRE TLをしている<a href="https://x.com/gymnstcs">@naka</a>です。<a href="https://engineering.mercari.com/blog/entry/20240524-mercari-hallo-world/">連載:Mercari Hallo, world! -メルカリ ハロ 開発の裏側-</a> の3回目を担当させていただきます。</p>
<p>この記事では、メルカリの新規事業立ち上げにおけるSREの働きや役割に関して、紹介します。</p>
<p>メルカリでは、Platform Engineeringが提供するツールや仕組みを活用して、サービスを立ち上げていきます。新規事業立ち上げのチームだけで、完結するわけではありません。今回は、Platform Engineering時代の新規サービス立ち上げにおけるSREの役割と具体的な動きを、メルカリ ハロを例に取り上げて紹介します。SREが、Platform Engineeringとプロダクト開発チームと一丸となって「All For One」に動いてきた取り組みが少しでも臨場感を持って伝えられればと思います。</p>
<h1>Platform Engineering x メルカリShops爆速立ち上げの知見</h1>
<p>具体的な活動について触れる前に、まずは全体の背景について説明します。</p>
<p>今回のメルカリ ハロの立ち上げは、ソウゾウ時代の爆速立ち上げの経験とメルカリグループ全体の技術スタックを最大限活用する挑戦でした。</p>
<p>ソウゾウの爆速立ち上げの成功に寄与した技術やプロセスを活かしつつ、そこでの学びを踏まえメルカリグループ全体の知見の蓄積である共通の技術スタックを取り入れることで、新規事業の立ち上げスピードを最大限上げました。</p>
<h2>メルカリのPlatform Engineering</h2>
<p>メルカリでは、Platform Engineeringがプロダクト開発チームを支え、その成果を最大限引き出すための環境を提供しています。メルカリのPlatform Engineeringの詳細は、<a href="https://engineering.mercari.com/en/blog/tag/platform/">こちら</a>を御覧ください。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2022/01/5c0153d9-a-bd08c09f-blog-series-of-introduction-of-developer-productivity-engineering-at-mercari.png" alt="Blog Series of Introduction of Developer Productivity Engineering at Mercari" /></p>
<p>メルカリグループでは、Kubernetesを基盤に使っており、基本的にすべてのマイクロサービスはKubernetesクラスタ上で動いています。開発チームが、Kubernetesクラスタを簡単に使えるためのツールがPlatformチームによって整えられています。</p>
<h2>メルカリ ハロ初期開発メンバー</h2>
<p>メルカリハロの初期メンバーは、 <a href="https://engineering.mercari.com/blog/entry/20240527-mercari-hallo-engineering/">爆速で「あたらしい出会いを繋ぐ」を創った、メルカリ ハロのエンジニアリング</a> でも紹介があったように、私を含め、ほとんどが株式会社ソウゾウに所属していました。ソウゾウでは、メルカリグループとは異なる技術スタックを採用しており、この知識や経験も活かしてメルカリハロの開発を進めました。ソウゾウで開発していたメルカリShopsの技術スタックに関しては<a href="https://engineering.mercari.com/blog/entry/20210810-mercari-shops-tech-stack/">こちら</a>をご覧ください。 </p>
<p>ソウゾウではアプリケーション開発寄りの技術スタックにMonorepo、Go、GraphQL、Ent、PostgreSQL、Nextjsなどが採用されており、高いレベルの開発者体験が実現できていました。一方で、基盤に関しては、メルカリグループ全体で用いられているものとはやや異なる技術スタックが採用されていたため、全体として得られた知見や経験を活かしにくいこと、また技術スタックの違いにより人員配置における障壁となっていたことが課題として認識されていました。</p>
<h1>役割と活動</h1>
<p>「インフラ、ネットワーク、セキュリティ周りはお願いします」と言われて始まったSREとしての役割や活動は、多岐にわたります。今回は、その中でもメルカリらしく、「All For One」や「Be a Pro」を体現している部分をいくつか紹介したいと思います。</p>
<h2>アーキテクチャ設計</h2>
<p>初期メンバーであり、現在Engineering Headである<a href="https://x.com/____napoli">@napoli</a>と密に連携を取りながら、まずはアーキテクチャ設計やツール選定をしました。</p>
<p>技術スタックの詳細は、<a href="https://engineering.mercari.com/blog/entry/20240529-mercari-hallo-tech-stacks/">第2回のnapoliの記事</a>で取り上げられていますが、大まかには、メルカリの技術スタックにアラインしつつ、スピード重視で開発ができるような技術スタックを選択してきました。<br />
その中でも特にインフラ構成やツール選定などに関してはSREも積極的にリードしてきました。</p>
<p>最初のアーキテクチャ決めでは、チーム外との定例ミーティングを開き、メルカリ ハロの要件をまとめてソリューションのPros/Consを議論したり、ミーティング後持ち帰り調査をして、最終的なアーキテクチャが決まるまで、複数部署と連携しながら、メルカリグループの技術スタックのキャッチアップに注力しました。</p>
<p>冒頭で書いたように、もともと主な初期メンバーはソウゾウメンバーだったため、メルカリグループで使われている最新の技術スタックにあまり詳しくない部分もあるので、高速のキャッチアップが必要でした。</p>
<p>ここで、大きな助けとなったのが組織的なサポート体制です。メルカリ ハロのプロジェクトは、グループ内でも優先度が高く設定されたので、各Platformチームから手厚いサポートを受けることができました。<br />
また、Platform DX (Developer Experience) チームからメンバーが1名メルカリ ハロのプロジェクトにアサインされたので、毎週の1on1やSlack上で、素朴なPlatformへの疑問や今のメルカリ ハロでの課題などざっくばらんにディスカッションする機会を設けてもらい、毎週新しい学びを得ながら確実に進捗する事ができました。</p>
<p>また、具体的な要件に応じて、関連するチームのメンバーに声をかけて、ディスカッションしてアーキテクチャ設計をしました。Web Platform、Network、Architect、SRE、Platform DX、IDPなど、本当に多岐にわたりました。<br />
メルカリ ハロのアーキテクチャ設計やインフラ構築を通して30人近くのメンバーと一緒に働くことができ、メルカリの「Be a Pro」なPlatform Engineeringの「All For One」のサポートを最大限活かせたと感じています。</p>
<h2>環境構築</h2>
<p>アーキテクチャ設計や技術スタックの決定と同時並行で、プロダクトの開発は進んでいました。<br />
この時、スピーディな開発環境構築はメルカリ ハロチーム全体にとってとても重要でした。<br />
「機能実装はないものの全体が動くようになっていきている」というのを、なるべく早い段階でチーム全体に共有することで、さらに前進しようとする強い気持ちを後押しするためです。</p>
<p>Platform Engineeringで提供されているツールの中身を理解しながら、1から環境を設定していきました。現在広く使われている自社製ツールの導入に加えて、Platform Engineeringで新しく開発している将来スタンダードになるであろう新ツールの導入も積極的に行いました。今後、グループ全体で移行するPlatformツールをEarly Adoptorとしていち早く採用することで、Platform側のサポートをより多く受けることができると同時に、Platform側にとっても実際のユースケースからのFeedbackを得ることができるので、メルカリ ハロの立ち上げにとっても、メルカリグループ全体にとっても大きな意味を持つ決定だったと思います。各所のサポートや協力を取り付けることができ、開発環境の構築を無事に完了することができました。</p>
<p>一方、メルカリ ハロでは、一部の技術スタックはメルカリグループでも実績が多くないものもあります。例えば、Cloud SQL for PostgreSQLは、メルカリグループでも使用されているケースはまだ稀です。<br />
こういったケースでは、ソウゾウ時代の知見や、今回新たに時間を割いて検証した結果を用いて、より安全、且つシンプルで使いやすい設定やツールを選定しました。<br />
具体的には、IAM DB 認証のデータベースユーザを採用し、DB userをパスワードなしで管理することで、よりセキュアな設定にしました。<br />
また、DBのSchema 変更時に、意図しない変更の適用によるインシデントを未然に防ぐために、atlasという Database schema migration ツールを導入しました。<br />
atlasの導入により、毎回Schema変更からSQL fileを生成し最終的にApplyされるSQLをReviewerがPR上で確認できるので、より安全にDB schema変更を行うことができるようになります。</p>
<h2>ドキュメント整備</h2>
<p>アーキテクチャ設計、環境構築、ツール選定などの際には、ドキュメントに経緯を残すことを大事にしてきました。</p>
<p>新規事業の立ち上げは少人数でスタートするのと、スピードを重視するために、最初の設計時に考えたことや設定した作業記録がドキュメントに残らなかったり、ドキュメントはあるが分散してしまっているために、あとから入ってきた人が背景を理解するのが難しいという課題に直面することがあると思います。</p>
<p>例えば、アーキテクチャを一つとっても、最初は全員が認識できるくらいのシンプルなものから始まる事が多く、特にドキュメントにしなくても全員が頭の中で同じものを描く事ができます。しかし、開発人数が一気に増え、開発のスピードもあがると、全体のアーキテクチャがどうなっているのか、詳細に関しても、当初なぜこの選択をしたのかを全員が把握することがとても難しくなります。</p>
<p>そこで、アーキテクチャ設計や開発環境構築の作業と同時並行で、Wikiの初期構成を考えたり、今後のドキュメントの構成の枠組みを作ったりしました。</p>
<p>SREとして何か作業が必要になったものに関しては、基本的にすべて手順を残すようにしたり、アーキテクチャ設計時の細かい議論やSlack上でのやりとりも、なるべくあとから入った人が経緯を知れるように、Referencesにリンクを集約したりと、将来の生産性への投資を初期段階から行ってきました。</p>
<p>あとからJoinしたSREのメンバーも過去にやってきたことにキャッチアップしやすかったと言ってもらえて、ドキュメント整備は最初からやって良かったと思います。</p>
<h2>Production Readiness Check</h2>
<p>最後に、もう一つ今回のメルカリ ハロのリリースに関して、SREが積極的にリードしてスムーズなリリースに関与した任務を紹介します。<br />
メルカリでは新しいサービスをリリースする前には、事前にProduction Readiness Checkというチェックを通過する必要があります。(参考: <a href="https://github.com/mercari/production-readiness-checklist">Production readiness checklist used for Mercari and Merpay microservices</a>)</p>
<p>このProduction Readiness Check (以下 PRC)の項目は、Applicationの実装上の要件、Kubernetes、Database、Storageなどの基盤の設定項目、セキュリティなど100以上の項目に上ります。<br />
チェックの結果は、新しいサービスをリリースするために通過しなくてはいけないリリース判定の中の一つの項目の提出物として取り扱われています。つまり、PRCの項目がすべて完了していることが、本番環境構築完了の印になります。逆に言うと、PRCを完了していないとリリースができません。</p>
<p>PRCをすべて完了するためには様々なチームメンバーに協力してもらう必要があります。初期段階で専任のSREは一人しかいませんでしたが、途中からはバックエンドエンジニアと Marketplace事業のSREの方にも、兼務としてメルカリ ハロのSREの業務に加わってもらいました。</p>
<p>3名体制になったあとは各メンバーの担当項目を決め、担当者が各項目で関連するメンバーにアプローチしながら、同時並行で進めました。バックエンドエンジニアと兼務のメンバーはバックエンド周りの項目をリードし、Marketplace側のSREメンバーにはモニタリング周りの整備を進めていただき、Web Platform チームにはWeb周りのロードテストを行ってもらいました。</p>
<p>また、全体の進捗を確認するために、三人で定期的に進捗状況を共有し、具体的にやることが明確になっていない項目に関しては、一緒に議論しNext Actionを決めました。<br />
全員で何が何でも完了するぞという強い気持ちと落ちているボールは気づいた人が拾うという精神で、リリース期日までにすべての項目を完了することにつながったと思っています。<br />
三人一丸となって完遂したプロジェクトとして、ここでも「All For One」を強く感じることができました。</p>
<h1>学びと課題</h1>
<p>SREとして新規事業にゼロから携われたのは、個人としても学びが多く素晴らしい経験となりました。また、チームとしても、Platform Engineeringとプロダクト開発者の距離を近づけ、より早く価値をお客さまに届けることに貢献できたと思っています。</p>
<p>一方で、今回の立ち上げで課題も多く見つかりました。新しいサービス立ち上げのために、ゼロから環境構築を完了するまでに3ヶ月近くかかりました。Platformに対する理解のキャッチアップ、チーム内での要件の確定、他チームとの議論や意思決定など様々な不確実性の高い課題を突破していくために時間がかかってしまった部分や、Documentationの不足や複雑な手順など改善の余地がある程度明確な部分も多々ありました。</p>
<p>今後、メルカリ内で新規事業立ち上げの際にはもっとスピーディに立ち上げられるように、Platform全体の改善、Platform EngineeringへのFeedback、リリース前のプロセスの改善など新規事業立ち上げを経験した私達だからこそ、Platformチームと一緒に改善していきたいです。<br />
リリース後、熱が冷めないうちに、次の新規事業のために既存のプロセスを改善している真っ最中です。</p>
<h1>まとめ</h1>
<p>メルカリ ハロの爆速開発の裏側でどのようにSREが動いていたかを一部ではありますが、知っていただけたでしょうか。「新規サービス立ち上げ期にSREはこう動くべきだ」という明確な責務はないと思っています。だからこそ、柔軟に自分ができることなら何でもやるぞ!くらいのスタンスでプロジェクトに携わってきました。</p>
<p>リリース後の安定稼働を担保するために、リリース前の立ち上げ期にSREが様々な面でプロダクト開発に携わるのは、助走路として開発メンバーと同じ方向を向いて飛び立つことができ、とても有効だったと思っています。</p>
<p>今回は、メルカリグループ全体からのサポートがあってこそのメルカリ ハロのリリースだったと心から感じています。この環境に自分がいれたことにとても感謝しています。そしてその感謝の思いを存分にメルカリ ハロ、そしてメルカリグループ全体に還元していきたいです。</p>
<p>今回新しいサービスをリリースしただけではなく、メルカリグループとして今後もっとスピーディに新しいサービスを立ち上げていけるように組織全体を変えていく任務を担っていると感じています。</p>
<p>これからも、まだまだ改善したいところは山程あります。こんな熱いメルカリ ハロで一緒に働くSREメンバーを募集中です!!!!!!</p>
<h1>Links</h1>
<p><a href="https://engineering.mercari.com/blog/entry/20240524-mercari-hallo-world/">連載:Mercari Hallo, world! -メルカリ ハロ 開発の裏側-</a></p>
<p>メルカリではメンバーを大募集中です。メルカリ ハロの開発やメルカリに興味を持った方がいればぜひご応募お待ちしています。詳しくは以下のページをご覧ください。</p>
<ul>
<li><a href="https://apply.workable.com/mercari/j/A53FA51E42/">Software Engineer, Frontend – Mercari/HR領域新規事業 (Mercari Hallo)</a></li>
<li><a href="https://apply.workable.com/mercari/j/197FBA1617/">Software Engineer, Backend – Mercari/HR領域新規事業 (Mercari Hallo)</a></li>
<li><a href="https://apply.workable.com/mercari/j/436EAEC812/">Software Engineer, iOS/Android (Flutter) – Mercari/HR領域新規事業 (Mercari Hallo)</a></li>
<li><a href="https://apply.workable.com/mercari/j/62287F2907/">Software Engineer, Site Reliability – Mercari/HR領域新規事業 (Mercari Hallo)</a></li>
<li><a href="https://apply.workable.com/mercari/j/0086D4DA33/">QA Engineer – Mercari/HR領域新規事業 (Mercari Hallo)</a></li>
<li><a href="https://apply.workable.com/mercari/j/1103382B83/">Engineering Manager – Mercari/HR領域新規事業 (Mercari Hallo)</a></li>
</ul>
- メルカリの2024年新卒エンジニア向け研修 “DevDojo”の資料と動画をご紹介!https://engineering.mercari.com/blog/entry/20240530-ae7feb0542/https://engineering.mercari.com/blog/entry/20240530-ae7feb0542/<p>こんにちは、メルカリ Engineering Office チームの@yuki.tです。 メルカリでは「誰もが高い基準を志しながらお互いに成長できる組織」を目指し、メンバーが相互に学び合う仕組みや機会を大事にしています。 […]</p>
Fri, 31 May 2024 12:00:21 GMT<p>こんにちは、メルカリ Engineering Office チームの@yuki.tです。</p>
<p>メルカリでは「誰もが高い基準を志しながらお互いに成長できる組織」を目指し、メンバーが相互に学び合う仕組みや機会を大事にしています。</p>
<p>その仕組みの一つとして、社内技術研修「DevDojo」があります。<br />
DevDojoでは、社内の有志によってメルカリで使用されている技術に関する研修を新卒エンジニアの入社タイミングに合わせて毎年提供しています。</p>
<p>そしてDevDojoの一部コンテンツは<strong><a href="https://engineering.mercari.com/learning-materials/" title="Learning materials Website">Learning materials Website</a></strong>で外部公開しています。</p>
<p>今年も4月に様々な研修が提供されました。このブログでは今年の研修の一部をご紹介します。<br />
あらたに提供を開始したコンテンツもありますので、ぜひご覧ください。</p>
<h1>技術研修DevDojoとは</h1>
<p>新卒エンジニアのオンボーディングは、ビジネスマナーなどの働くうえで必要なことを学ぶ共通研修と、開発に関する技術的なことを学ぶ研修の2つで構成されています。<br />
新卒研修の全体像については<a href="https://engineering.mercari.com/blog/entry/20230512-127cd1f253/" title="こちらのブログ">こちらのブログ</a>でも紹介されています。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/05/bb3c72a3-1_2.png" alt="" /></p>
<p>メルカリでは新卒オンボーディングのうちの技術研修をDevDojoと呼んでいます。これは技術開発を学ぶ場として「Development」と「Dojo(道場)」をかけ合わせて名付けられた、完全In-houseの社内研修シリーズです。</p>
<p>DevDojoでは、メルカリ・メルペイのエンジニアが講師として、社内で使用されている技術についてトレーニングやオンボーディングを提供しており、新卒エンジニアは、自分の配属や技術領域に関わらず、プロダクトに関する技術を幅広く学ぶことができます。</p>
<p>メルカリでは、プロダクトに情熱を持って改善するためには、自分の技術領域だけでなくプロダクト全体の理解が必要という考えから、各自の技術領域に限定せずに研修を受講してもらい、研修の実施には組織全体で優先度高く取り組んでいます。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/05/e87e1974-2_2.png" alt="" /></p>
<p>また、研修は社内のメンバーであれば誰でも受講できるようにオープンにしており、こちらも技術領域や職務に関わらず興味のある内容に参加できます。</p>
<h1>公開コンテンツはこちら</h1>
<p><a href="https://engineering.mercari.com/learning-materials/" title="Learning materials Website">Learning materials Website</a>では、DevDojoで提供されている研修から一部のセッションを公開しています。<br />
今年は新しいテーマのセッションが2つ追加されました。</p>
<p>どちらも、新しくエンジニアとしてのキャリアを歩み始めたメンバーにとって、大事にしてほしい視点や考え方に関する内容となっています。<br />
<img src="https://storage.googleapis.com/prd-engineering-asset/2024/05/3a75e63d-3.png" alt="" /></p>
<p>そのほかの公開セッションもアップデートされています。<br />
メルカリのエンジニアリング組織は、半数以上が海外籍社員のため、いくつかのセッションは英語で提供されています。研修には同時通訳が入り、語学のサポートをしています。<br />
<img src="https://storage.googleapis.com/prd-engineering-asset/2024/05/e5fe70e1-4.png" alt="" /></p>
<p>こちらが今年のメルカリ、メルペイの研修コンテンツです!</p>
<h2>Problem Solving</h2>
<p>ソフトウェアエンジニアリングを純粋な問題解決として考え、問題の認識から解決までをステップに分け、過去のプロジェクトを参考にしながら解説します。<br />
DevDojoシリーズで初となる、<a href="https://mercan.mercari.com/articles/41238/" title="Principal Engineer">Principal Engineer</a>によるコンテンツです。<br />
<a href="https://speakerdeck.com/mercari/devdojo-problem-solving-2024" title="Slide">Slide</a><br />
<iframe loading="lazy" title="課題解決プロセス_DevDojo(日本語)_2024" width="580" height="326" src="https://www.youtube.com/embed/hF4dOOwrXQ4?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe></p>
<h2>Ship Code Faster</h2>
<p>様々なTech Companyで使用されている生産性指標を取り上げ、開発開始から機能リリースまでの時間を短縮するための開発およびエンジニアリングの実践方法について説明します。キャリアをスタートしたばかりのエンジニア向けに、キャリアの進展に関する具体的なステップも提供します。<br />
<a href="https://speakerdeck.com/mercari/devdojo-ship-code-faster-2024" title="Slide英語">Slide英語</a><br />
<iframe loading="lazy" title="エンジニアのための生産性向上プラクティス_DevDojo(日本語通訳)_2024" width="580" height="326" src="https://www.youtube.com/embed/gvYZPTLvKqI?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe></p>
<h2>Mercari Design Doc</h2>
<p>プロダクト開発に必要なDesign Docの基礎知識を解説します。また、良いDesign Docの書き方やメルカリでDesign Docをどのように使っているかについても説明しています。<br />
<a href="https://speakerdeck.com/mercari/devdojo-mercari-design-doc-2024" title="Slide英語">Slide英語</a><br />
<iframe loading="lazy" title="メルカリ Design Doc_DevDojo(日本語通訳)_2024" width="580" height="326" src="https://www.youtube.com/embed/ljLJEVXlZa0?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe></p>
<h2>Mercari Quality Assurance</h2>
<p>安心安全に早い開発サイクルでサービスを持続的に提供していくためには、Quality Assuaranceは非常に大切です。メルカリでどのようなQAのプロセス、ツール、テクニックを使って問題を迅速に特定し、解決しているのかを解説します。<br />
<a href="https://speakerdeck.com/mercari/devdojo-mercari-quality-assurance-2024" title="Slide英語">Slide英語</a><br />
<iframe loading="lazy" title="メルカリの品質保証(Quality Assurance)_DevDojo(日本語通訳)_2024" width="580" height="326" src="https://www.youtube.com/embed/0jzEZsUjytk?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe></p>
<h2>Merpay Quality Assurance</h2>
<p>メルペイでのQuality Assuaranceの考え方と重要性、そしてQAプロセスとして、開発プロセスのなかでのQAエンジニアの関わり方を解説します。QAエンジニアだけでなく、開発に関わる全員が品質について注意をはらうための取り組みも紹介します。<br />
<a href="https://speakerdeck.com/mercari/devdojo-merpay-quality-assurance-2024-ri-ben-yu" title="Slide日本語">Slide日本語</a> / <a href="https://speakerdeck.com/mercari/devdojo-merpay-quality-assurance-2024" title="Slide英語">Slide英語</a><br />
<iframe loading="lazy" title="メルペイの品質保証(Quality Assurance)_DevDojo(日本語)_2024" width="580" height="326" src="https://www.youtube.com/embed/amvOzXsuH8w?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe></p>
<h2>Mercari Incident Management</h2>
<p>メルカリにおけるインシデントマネジメントとそのベストプラクティスを紹介します。「インシデント前、インシデント中、インシデント後」の3つのフェーズを含む、インシデントジャーニーを説明します。また、インシデントレビューをどのように行い、レトロスペクティブの質を高めているのかについても取り上げています。<br />
<a href="https://speakerdeck.com/mercari/devdojo-mercari-incident-management-2024" title="Slide英語">Slide英語</a><br />
<iframe loading="lazy" title="メルカリ インシデントマネジメントプロセス_DevDojo(日本語通訳)_2024" width="580" height="326" src="https://www.youtube.com/embed/vW0H4wJ5LoU?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe></p>
<h2>[Basic] Machine Learning</h2>
<p>メルカリではAIを使い、メルカリAIアシストなどユニークな機能を提供しています。このコンテンツでは、一般的な機械学習の考え方や、AI・MLの基礎知識について解説しています。また、メルカリでは実際にMLをどう実装しているのか、実際のプロジェクトについても紹介しています。<br />
<a href="https://speakerdeck.com/mercari/devdojo-basic-machine-learning-2024" title="Slide英語">Slide英語</a><br />
<iframe loading="lazy" title="メルカリ Machine Learning 入門_DevDojo(日本語)_2024" width="580" height="326" src="https://www.youtube.com/embed/pdkcRt3kTcw?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe></p>
<h2>Mercari Mobile Development</h2>
<p>より使いやすいサービスを迅速に提供していくため、メルカリのモバイル開発はリリースサイクルや運用プロセスのルール化を行っています。メルカリのモバイルアプリ開発において実際に運用している開発サイクルとプロセスについて解説します。<br />
<a href="https://speakerdeck.com/mercari/devdojo-mercari-mobile-development-2024" title="Slide英語">Slide英語</a><br />
<iframe loading="lazy" title="メルカリ モバイル開発_DevDojo(日本語通訳)_2024" width="580" height="326" src="https://www.youtube.com/embed/NmpFNBU2Qlg?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe></p>
<h2>Mercari Design System for Mobile</h2>
<p>持続的に一貫したサービス体験をお客さまに提供できるよう、メルカリではDesign Systemにとても力を入れています。このコンテンツでは、モバイルにおけるDesign Systemの基礎知識から、メルカリで実際に行っているデザインの作り方、運用方法について解説します。<br />
<a href="https://speakerdeck.com/mercari/devdojo-mercari-design-system-for-mobile-2024" title="Slide英語">Slide英語</a><br />
<iframe loading="lazy" title="メルカリ Design System for Mobile_DevDojo(日本語通訳)_2024" width="580" height="326" src="https://www.youtube.com/embed/VBB-5L1k36Q?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe></p>
<h2>Auth Platform Onboarding</h2>
<p>メルカリグループが管理しているサービス間で安全に通信を行うために、認証と認可は切り離せません。本セッションでは、この認証基盤の基礎として、アクセストークンの役割や利用方法等について紹介します。<br />
<a href="https://speakerdeck.com/mercari/devdojo-mercarimerpay-auth-platform-onboarding-2024" title="Slide英語">Slide英語</a><br />
<iframe loading="lazy" title="認証基盤 基礎_DevDojo(日本語)_2024" width="580" height="326" src="https://www.youtube.com/embed/PFku_aDDyDo?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe></p>
<h1>最後に</h1>
<p>メルカリでは「<a href="https://careers.mercari.com/jp/culturedoc/#page-1" title="Trust &amp; Openness">Trust & Openness</a>」と「<a href="https://engineering.mercari.com/about/" title="Open Organization">Open Organization</a>」の企業文化に基づき、オープンなコラボレーションを奨励しています。</p>
<p>この考えのもと、新卒エンジニアには社内の有志エンジニアによってトレーニングやオンボーディングが提供されており、社内だけでなく社外にも組織や技術の情報を共有することで、業界全体へ貢献することを目指して研修コンテンツを公開しています。<br />
今年は2つの新しいテーマのセッションを追加して公開することができましたが、研修の実施と公開には、多くのエンジニアの方々、チームメンバー、関係チームが協力し取り組んでいます。</p>
<p>今後も引き続き、DevDojoシリーズのアップデートを行い、公開していきます。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/05/7df8e03d-9_2.png" alt="" /></p>
<p>最後に、メルカリグループでは、積極的にエンジニアを採用しています。ご興味ある方、ぜひご連絡お待ちしております!</p>
<p><a href="https://careers.mercari.com/jp/search-jobs/?dep=engineering" title="Open position – Engineering at Mercari">Open position – Engineering at Mercari</a></p>
- メルカリ ハロの技術スタックとその選定理由https://engineering.mercari.com/blog/entry/20240529-mercari-hallo-tech-stacks/https://engineering.mercari.com/blog/entry/20240529-mercari-hallo-tech-stacks/<p>こんにちは。メルカリ ハロのSoftware Engineer (Engineering Head)の@napoliです。連載:Mercari Hallo, world! -メルカリ ハロ 開発の裏側-の2回目を担当させ […]</p>
Wed, 29 May 2024 08:00:58 GMT<p>こんにちは。メルカリ ハロのSoftware Engineer (Engineering Head)の<a href="https://x.com/____napoli">@napoli</a>です。<a href="https://engineering.mercari.com/blog/entry/20240524-mercari-hallo-world/">連載:Mercari Hallo, world! -メルカリ ハロ 開発の裏側-</a>の2回目を担当させていただきます。</p>
<p>2024年3月上旬に<a href="https://hallo.mercari.com/">メルカリ ハロ</a>という新しいサービスが公開されました。メルカリ ハロは好きな時間に最短1時間から働ける「空き時間おしごとアプリ」です。</p>
<p>この記事ではメルカリ ハロを作るにあたり、どういった技術スタックやアーキテクチャを選定したのか、さらにその背景と意思決定をご紹介したいと思います。</p>
<h2>この記事で得られること</h2>
<ol>
<li>メルカリ ハロで採用されている技術スタックやアーキテクチャの全体像</li>
<li>その意思決定の理由とプロセス</li>
<li>これから新規サービスを立ち上げるうえでのヒント</li>
</ol>
<h2>主な技術スタック</h2>
<p>メルカリ ハロで利用されている主な技術スタックは以下のとおりです。</p>
<ul>
<li>
<p><strong>バックエンド</strong></p>
<ul>
<li>Go</li>
<li>Google Cloud Platform (GKE, Cloud SQL for PostgreSQLなど)</li>
<li>GraphQL</li>
<li>gqlgen</li>
<li>ent.</li>
</ul>
</li>
<li>
<p><strong>フロントエンド</strong></p>
<ul>
<li>React / TypeScript</li>
<li>Next.js</li>
<li>Apollo Client (React)</li>
</ul>
</li>
<li>
<p><strong>モバイルアプリ (メルカリ ハロ専用アプリ)</strong></p>
<ul>
<li>Flutter / Dart</li>
</ul>
</li>
</ul>
<p>また、バックエンドのアーキテクチャとしてはモジュラーモノリスを、リポジトリの管理方法としてはmonorepoを採用しています。</p>
<h2>モジュラーモノリス (Modular monolith)</h2>
<p>メルカリグループとして「スポットワーク領域」と呼ばれる領域に参入するため、2023年4月頃に新しいチームを立ち上げました。発足当初はあくまで「PoC (Proof of Concept)」という立ち位置で、この領域でメルカリならではの価値を提供できるかどうか、その検証をしてからサービスを成長させていくという戦略で進められたため、サービスを少ない人数で急速に立ち上げることが求められました。(最初期はエンジニアが1~2名ほどしかいませんでした)</p>
<p>こういった背景を踏まえ、バックエンド(サーバ)はモジュラーモノリスのアプローチを採用しました。メルカリグループの主力サービスであるフリマアプリ「メルカリ」は成長過程でモノリスからマイクロサービスに進化してきました。モジュラーモノリスはこれら2つのアプローチの中間に位置する戦略で、端的に言うとモノリスシステムの中にマイクロサービスの戦略を統合する考え方です。結果的に、この選択は正解だったと思います。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/05/1b1480af-ss1-1024x408.png" alt="" /></p>
<h3>容易なサービス間連携</h3>
<p>モジュラーモノリスではひとつのサーバにAPIサーバとして求められるすべての機能を含めます。すべての機能は同じプログラムで動いていますが、サーバの中では実際には「モジュール」という単位で機能は独立しています。そして各モジュールが連携することでAPIとしての機能を提供しています。(「モジュール」はメルカリ ハロでは実際には「サービス」と呼んでいます)</p>
<p>ここでいうひとつとは「サーバプログラムとしてひとつ」という意味です(「デプロイの単位としてひとつ」とも言えます)。ひとつのサーバにすべてのサービスが実装されているため、当然ながらサービス間のRPC (Remote Procedure Call)は不要です。ひとつのAPIを提供するために複数のRPCを利用する可能性のあるマイクロサービス アーキテクチャとは異なり、プログラム内で関数を呼ぶことで機能を完結させることができます。サービス間のプロトコル定義やネットワークエラー時のハンドリングなどを考慮する必要がなくなり、実装量や設計の難易度を大幅に減らすことができます。</p>
<h3>単一のデータベースとトランザクション</h3>
<p>バックエンドがモジュラーモノリスであることに加え、メルカリ ハロではメインとなるデータベースのインスタンスはひとつです。この構成の場合、データベースのトランザクション機能をフル活用することができます。メルカリ ハロにおいてもお給料に関する情報など、データの整合性が重要なケースがあります。その点でデータベースのトランザクション機構はやはり非常に強力で、マイクロサービスアーキテクチャで大きな悩みごとであったサービス間のデータ不整合の問題の大部分を気にしなくて良くなります。こちらも実装量と設計の難易度を大いに減らしてくれました。</p>
<h3>少ないインフラの記述量</h3>
<p>メルカリ ハロではIaaSとしてTerraformを採用しています。モジュラーモノリスは基本的に1つのサーバで運用するためマイクロサービスと比較してインフラ関連の記述量を減らすことができます。APIなどアプリケーションに専門性を持つエンジニアにとってはインフラの設定やその動作確認は思ったよりも時間が掛かることが多いと思います。少ない記述量でAPIの実装に集中できることはメルカリ ハロのクイックな立ち上げにおいて大きなメリットがありました。</p>
<h3>気をつけるべきこと</h3>
<p>モジュラーモノリスはメルカリ ハロにおいて良い選択肢だった一方で、気をつけるべきこともあります。</p>
<p>大きな懸念のひとつは初期設計の難易度が上がりやすいという点です。何も考えずに作ってしまうと、単なるモノリスなシステムになってしまう可能性が大いにあります。正確に言うとモノリスであること自体が問題なのではなく、システムのモジュールやサービスが適切な責任範囲で分離されていないことが大きな問題になり得ます。適切な分離が行われていないシステムでは機能の再利用が難しかったり、一部の改修が思ってもみないところに影響を及ぼしたりします。複雑に相互依存した(絡み合った)システムは理解も難しいですし、テストも難しくなります。理解もテストも難しいということは障害が発生する可能性も高くなるということです。結果として時間が経てば経つほどスピーディな機能開発が困難になってきます。</p>
<p>マイクロサービスアーキテクチャの利点の一つは、この「モジュール/サービスの分離」がインフラレベルで「強制される」点だと思います。プログラムの単位が違うことに加え、データベースも一般的にはマイクロサービスごとに独立していることが多いため、あるサービスの変更は別のサービスには直接影響を与えません。もちろんどの粒度でマイクロサービスを分けるかにも依りますが、開発者は半ば強制的に「モジュール/サービスの適切な単位」を考えなければいけません。サービスとして独立しているため、責任範囲が明確にもなりやすいです。大規模組織にも相性が良く、例えば「このマイクロサービスはこのチームがオーナーを持つ」と言った戦略を取りやすくなります。</p>
<p>一方で、モジュラーモノリスではこの「強制」が良くも悪くもありません。ですが「モジュール/サービスの適切な分離」はマイクロサービスと変わらず重要な関心事です。モジュラーモノリスでは初期の設計者が慎重に設計を行い、各開発者が節度と理解を持って機能を実装していく必要があります。この点に難しさがあると思っています。</p>
<p>とはいえ、メルカリ ハロのように一定規模の新規プロダクトをクイックに開発するうえではモジュラーモノリスはおすすめできるアプローチだと思います。最初から大規模になることが約束されたプロダクトを作るのであればマイクロサービスアーキテクチャのような分散システムを採用するのも効果的だと思いますが、ほとんどの場合、システムを物理的に分離させるのはビジネス的にもプロダクトの規模が大きくなってからでも遅くはないでしょう。</p>
<p>なお、メルカリの別プロジェクトにおいてもモジュラーモノリスの採用例があります。メルカリ ハロの事例とは異なる「既存のモノリスからモジュラーモノリスへ移行する」というアプローチですが、こちらも参考にして頂ければと思います。<br />
<a href="https://engineering.mercari.com/blog/entry/20220913-modular-monolithization-in-mercari-transaction-domain/" title="メルカリの取引ドメインにおけるモジュラーモノリス化の取り組み">メルカリの取引ドメインにおけるモジュラーモノリス化の取り組み</a></p>
<h2>monorepo</h2>
<p>メルカリ ハロではmonorepoを採用しています。monorepoは、バックエンドやフロントエンドなど、システムを構成する複数のコンポーネントの独立性を保ちつつ、全てのコンポーネントをひとつのリポジトリで管理する手法です。</p>
<p>monorepoを採用してよかったと思う点をいくつか挙げてみます。</p>
<h3>システム全体の見通しの良さ</h3>
<p>monorepoはシステムに必要なコンポーネントが全てひとつのリポジトリに集約されていることが大きな特徴です。これはシステム全体の見通しがとても良くなります。メルカリ ハロの立ち上げ期は開発メンバーの数が少なかったため、ひとりのエンジニアがバックエンドやフロントエンド、モバイルアプリを横断して実装することもありました。その際にコードが集約されていることは開発のしやすさに大きなアドバンテージがあります。「フロントエンドの仕様どうなっているんだろう?」といった確認を行いときに、自身のIDEやEditorのファイル検索機能を使えばすぐ該当の実装に辿り着きます。「別リポジトリに切り替えて、git pullして、ウインドウを切り替えて…」といったことをする必要がありません。言語の違いによる理解の難しさは当然ありますが、素早くシステム全体を調査することができます。</p>
<h3>コードレビューのやりやすさ</h3>
<p>ひとつのGitHubリポジトリ上にプルリクエストが集まるため、異なる職種間でもレビューがしやすくなります。専門的な実装に関しては専門の知識を持つメンバーがレビューしたほうが良いですが、簡単な修正なら他の職種でも可能なことが少なくありません。メルカリ ハロではmain branchへのマージはプルリクエストへのApproveを必須としているので、レビューの速さは重要です。mainにマージできるまでの時間が短いとコンフリクト解消に掛ける時間も減り、QAもしやすく、本質的な作業に集中しやすくなります。もちろん複数レポジトリ(Multi Repository)でもできないことはないですが、monorepoのほうがやりやすいのは間違いないと思います。</p>
<h3>GraphQLスキーマファイルの共有</h3>
<p>メルカリ ハロではバックエンドとフロントエンド/モバイルアプリとの通信に(後述する)GraphQLを採用しています。バックエンド側で生成したGraphQLのスキーマを同一のリポジトリで共有できるため、それをもとにフロントエンドのGraphQLのクライアントコードを自動生成したりなど、楽に連携をすることができました。GraphQLスキーマファイルに限らず「必要なファイルをリモートから取得する必要がない」というのは何かと便利ですし、開発環境が安定します。</p>
<h3>一緒に開発している感</h3>
<p>急にふんわりとした話になってしまいますが、バックエンドやフロントエンドといった職種間でもなんとなく「一緒に開発している感」が出るような気がします。「システム全体の見通しの良さ」にも繋がる話ですが、他の職種の人達がどういった頻度、温度感で日々作業しているかがわかりやすくなります。この感覚は密にコミュニケーションが必要な開発では意外と重要だと思います。目には見えなく数値化しづらいですが、メルカリ ハロの開発体制では良い効果をもたらしていたと思います。</p>
<h3>monorepoのリポジトリ構成</h3>
<p>メルカリ ハロではGo, dart, TypeScriptが主な言語として使われており、リポジトリルートの直下に各言語ごとにディレクトリを配置しました。これによりエコシステムやCI/CDの管理をしやすくなります。また普段の開発においても、例えばバックエンドを開発する人間は基本的にgoディレクトリ以下のみに絞って作業することができ、同じリポジトリでも独立した環境のように開発できるメリットがあると思います。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/05/83328b5d-ss2-e1716561477632.png" alt="" /></p>
<h3>独立したビルド環境</h3>
<p>メルカリ ハロのmonorepoではバックエンドやフロントエンドなどの各コンポーネントでのビルドの手段は基本的に独立しています。<a href="https://bazel.build/">Bazel</a>のようなビルドを一元的に管理できるツールもありますが、メルカリ ハロでは採用していません。幸いなことにメルカリ ハロの立ち上げ期には各職種で専門性の高いメンバーが居たため、馴染みのある(標準的な)ビルドの手法を採用していました。各メンバーにとっては追加の技術を学習するコストがない分、スムーズにビルド環境を構築できたと思います。運用面でも今のところ大きく困ったことはありません。コンポーネント間でビルドを一元的に管理する手法はメリットもありつつ、かなりの難しさもあるため、明確な理由がなければ各コンポーネントごとに独立してビルド環境を構築するアプローチのほうがおすすめできるかなと思います。</p>
<p>—</p>
<p>monorepoについて、いくつかのメリットやメルカリ ハロでの具体的な構成例を挙げました。monorepoの一般的なデメリットはリポジトリサイズが大きくなりやすいところですが、昨今のネットワーク環境やローカル環境を踏まえると相当に大規模なサービスにならない限りほぼ気にすることは無いかなと思います。他にも細かいデメリットはありますが、総じてメリットのほうが大きく上回っていると感じます。新規サービスの立ち上げにおいてはおすすめできるアプローチだと思います。</p>
<h2>インフラの全体像</h2>
<p>メルカリ ハロではインフラストラクチャにGoogle Cloud Platformを全面的に採用しており、簡単な全体像は以下のようになっています。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/05/22dbed37-ss3-1024x333.png" alt="" /></p>
<p>バックエンドとなるGraphQLサーバ(Go)はGoogle Kubernetes Engine (GKE)を利用してひとつのサーバとして動いています。同じくNext.jsと、APIの前段となるGateweyもGKE上で動いてます。データベースはCloud SQL for PostgreSQL、メモリストアにはRedisを利用しており、CDNはFastly、 画像最適化(変換)サービスにはCloudflareを採用しています。</p>
<h2>Google Kubernetes Engine (GKE)</h2>
<p>メルカリ ハロではバックエンドのインフラにGoogle Kubernetes Engine (GKE)を採用しています。主に下記の2つの理由で選定を行いました。</p>
<h3>メルカリグループでの実績とノウハウ</h3>
<p>メルカリグループでは多くのサービスをGKEにて運用しており、Platformと呼ばれるチームが運用・保守を行っています。一般的にGKE(kubernetes)はインフラを専門としないエンジニアにとっては難解なことも多いですが、メルカリグループでは初期設定や開発を効率的に行うためのツールやドキュメント、ベストプラクティスが充実しており、Platformチームからの手厚いサポートを受けることもできるため、この点において困ることは比較的少なかったと思います。</p>
<h3>エコシステムとの統合</h3>
<p>メルカリグループの多くのサービスがGKEを採用しており、同じクラスタ内でgRPCによるサービス間通信を行っています。これにより、既存のサービスをセキュアかつ効率的に利用することが可能です。メルカリ ハロはいくつか既存のメルカリのマイクロサービスを利用する必要があったため、簡単かつセキュアにサービスを利用できることには大きなアドバンテージがありました。</p>
<h3>他の選択肢</h3>
<p>上記のとおり、メルカリ ハロではグループ内のサポートが充実していたことと、既存のエコシステムとの連携が重要だったためGKEを選択しました。ただ、やはり構築と運用において難易度の高さはあるため、ゼロから独立したサービスを作る場合や、専門のエンジニアが居ない場合はCloud Runのような構築や運用が簡単なServerless環境も選択肢としては大いにありだと思います。</p>
<h2>バックエンド / Go</h2>
<p>バックエンドの実装はGoを採用しています。メルカリグループでの標準的な言語であり、メルカリ内で開発を行う上ではノウハウやリソースアロケーションの面で多言語と比べ圧倒的な優位性があります。また、API開発に適した言語であり、実行速度が速く、Go routineによる並列処理は非常に強力です。</p>
<p>シンプルで読みやすいことも良い点で、そのシンプルさゆえに誰が書いても同じようなコードになります。これは多人数開発ではメリットが大きく、実装の理解やコードレビューの負担を大きく下げてくれます。コードに問題がある場合も比較的気付きやすいと思います。</p>
<p>複雑なコードは、たとえ自分自身が書いたコードでも何を意図してそうしたのか、時間が経つとすぐ分かりづらくなります。そういった意味では「書き手より読み手にやさしい」言語かもしれません。読み手に優しいことは長期的にサービスを運営するうえで大きなアドバンテージになります。コードというのは時間が経てば経つほど「書かれている時間より読まれている時間のほうが長くなるもの」だからです。</p>
<p>個人的にもGoは好きな言語なので、仮にメルカリ以外でAPIを開発するとなっても当分は最有力候補になると思います。</p>
<h2>Cloud SQL for PostgreSQL / ent. / atlas</h2>
<p>DatabaseにはCloud SQL for PostgreSQLを採用しています。メルカリグループではGoogle CloudのSpannerを採用するケースが多いのですが、以下の理由でCloud SQL for PostgreSQLを採用しました。</p>
<h3>学習コストの低さ</h3>
<p>PostgreSQLのようなRDBMSの知識や経験を持つエンジニアは多く、そういったエンジニアとっては新たに学習すべきことは少ないです。それはつまり新しいメンバーが開発に入りやすく、即戦力になりやすいということに繋がります。</p>
<h3>充実したエコシステム</h3>
<p>PostgreSQLは歴史が長く、サードパーティ製のツールやライブラリが豊富です。ツールやライブラリが豊富であることは効率的な開発に繋がりやすく、小さくないアドバンテージとなります。高機能なGUIツールも提供されているため、データを直接調整しながらデバッグを行いたいときなどに非常に役に立ちます。</p>
<h3>ポータビリティ性</h3>
<p>PostgreSQLはDockerイメージとして提供されており、ローカルのDocker上で簡単に動かすことができます。そのためデータベースを利用するユニットテストもやりやすくなりますし、ローカルでもリモートの開発サーバと近い環境を構築しやすくなります。</p>
<h3>メルカリ ハロのサービス特性</h3>
<p>メルカリ ハロのサービスの特性上、Readが多く、Writeは比較的少ないです。そのため単一のインスタンスでも相当な期間、問題なくWriteを捌けるだろうと判断しました。ReadにおいてはRead replicaを必要に応じて増やしていくことでかなりのトラフィックを捌けるだろうと考えています。</p>
<p>—</p>
<p>一般的にはこれらに加えて「初期コストの低さ」もメリットになりうると思います。メルカリ ハロでは当初から一定以上の規模のお客さまを想定していたためあまり判断の基準にはなりませんでしたが、多くの新規サービスにとっては重要な観点ではないでしょうか。</p>
<h3>ORM</h3>
<p>ORM(Object-Relational Mapping)としては<a href="https://entgo.io/">ent.</a>を採用しています。Go言語向けの強力なORMフレームワークであり、メルカリグループ内でもいくつか採用事例があることから採用しました。コードファーストのアプローチを取っており、高度なクエリ生成機能もあるので、効率的にGoからDatabaseの操作を行えていると感じます。</p>
<p>一般的にはORMを採用すると最適化された柔軟なクエリを書きづらくなりますが、メルカリ ハロでは基本的に非常にシンプルなクエリの組み合わせでAPIを実現しています。その分クエリの発行数が冗長になることもありますが、一方で「理解しやすく、実装しやすい」という大きなメリットがあります。冗長なクエリが多いと心配なのはパフォーマンスですが、基本的にReadの処理はRead replicaを増やすことでスケールしますし、アクセスの頻度が相当多いAPIで無い限り、インデックスさえ適切に貼られていればクエリの数を多少増やしても問題になることはほぼありません。シンプルなクエリは正しくインデックスを貼るのも楽です。実際においても、いまのところメルカリ ハロはデータベースのパフォーマンスは大きな問題にはなっていません。</p>
<h3>データベース マイグレーション</h3>
<p>データベースのマイグレーションには<a href="https://atlasgo.io/">atlas</a>を採用しています。ent.もauto migrationの機能を持っており、差分のDDLを自動で適用してくれたりしますが、いざサービスの運用が始まるとent.のauto migrationだけでは要件を満たさないケースが多く、基本的にはatlasによる管理に統一しています(本番環境においてent.のauto migrationはOffにしています)。atlasはent.と連動してスキーマの差分を自動的に生成してくれるなど、強力な機能を持っており、効率的にmigration作業を行うことができます。DMLにおいてもatlasを使って一部migrationしています。</p>
<h2>GraphQL</h2>
<p>モバイルアプリ含むフロントエンドとバックエンドとの通信にはGraphQLを採用しています。GraphQLはAPI開発においてモダンな選択肢のひとつであり、世の中の多くのサービスでも採用されています。フロントエンド側でフェッチするデータを動的に制御することができ、フロントエンドの仕様が変更になった際もバックエンド側の修正が不要になるケースもあります。クエリをネストすることができるため、フロントエンドはその画面において必要な情報の多くをひとつのAPI Callで取得することができ、不要なAPI Callを減らすことができます。また、静的な型システムを持つスキーマによってI/Fが定義されるため、バックエンドとフロントエンド間で厳密なデータのやりとりすることができます。IDE/Editorによる補完が効きやすいところも嬉しいポイントです。</p>
<h3>gqlgen</h3>
<p>バックエンド側ではGoのGraphQLサーバ実装のひとつである<a href="https://gqlgen.com/">gqlgen</a>を採用しています。シンプルかつ必要十分な機能が揃っており、学習コストも低く使いやすいと感じます。</p>
<p>gqlgenはスキーマファーストのフレームワークであり、基本的にひとつのスキーマファイルでQuery/Mutationを定義するスタイルのため、メルカリ ハロでもひとつのSchemaファイルにすべてのQuery/Mutationが集約されています。そのため現時点でもかなり行数の多いファイルになっており若干の扱い辛さを感じるときもありますが、graphql-eslintを導入してファイルを自動整形したりアルファベット順にType/Query/Mutationを自動ソートするなどして、できるだけメンテナンス性が落ちないように工夫しています。</p>
<p>一方で、スキーマがひとつのファイルに集約されているメリットも多いと思います。見通しが良く、コード自動生成もしやすく、他チームにメルカリ ハロが持つAPIを紹介する際も「このスキーマファイルを見てください」と言えば済むこともあります。</p>
<p>シンプルで扱いやすいPlaygroundが提供されている点も大きいと思います。Playgroundを使うと実装したGraphQLのQuery/Mutationを実際に試すことができます。入力の補完が効いたりQuery/MutationのAPI Reference(Document)も自動で生成してくれます。これが非常に快適で、デバッグや動作確認でとても役立っています。gqlgenでは複雑な設定もなく簡単にPlaygroundを構築することができます。</p>
<p>一方で、gqlgenに限らずですが、GraphQLサーバ実装においてはいわゆるN+1問題に気をつける必要があります。この点においてはRESTなど比べて学習コストと実装コストはやや増えますが、GraphQLを採用する上でそこまで大きなデメリットにはならないかなと思います。対処法としてはdataloaderの採用が一般的で、メルカリ ハロでは<a href="https://github.com/graph-gophers/dataloader">graph-gophers/dataloader</a>を採用しています。</p>
<p>なお、メルカリグループで広く採用されているプロトコルにProtocol Buffers(gRPC)があり、こちらも優れた機能を持っています。ただ一般的なWebサービスを作るうえでは、フロントエンドとバックエンド間の通信プロトコルとしてはGraphQLのほうが総合的に扱いやすいのかなと感じます。(もちろんどういったサービスを作るかにも依りますが)</p>
<p>あとはRESTも候補になり得ますが、今の時代、明確な理由がない限り敢えてそれを選択するメリットは少ないかなと思います。</p>
<h2>React / TypeScript / Next.js</h2>
<p>メルカリ ハロではWebベースのフロントエンドも実装されています。メルカリアプリ内の「はたらく」タブはWebViewで提供されており、事業者様向けの「事業者管理画面」もPC向けにWebベースで提供されています。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/05/05b6d367-ss4-1024x543.png" alt="" /><br />
<em>メルカリアプリ内「はたらくタブ」と事業者管理画面</em></p>
<p>Reactの採用はすぐに決まりました。メルカリグループの他のプロジェクトで採用されているという点で、Vueも候補に挙がりましたが、初期の開発メンバーがReactに慣れていたことと、作りたいサービスに対して十分な機能を備えており、効率的に開発を進められるだろうと判断して採用しました。業界的なトレンド、人材のプールという意味でもReactにアドバンテージがあるだろうと判断しました。</p>
<p>TypeScriptについても迷うことはありませんでした。フロントエンドにおいても昨今の開発では静的型付けは必須と言って良いですし、開発を効率的に進めていく上でさまざまなメリットがあります。Javascriptと比較して若干の難しさはあるかもしれませんが、いまや情報も豊富ですし、一定以上の知識や経験がある開発チームにおいてはほぼ問題にならないでしょう。</p>
<p>Next.jsはメルカリグループでの利用実績や使いやすさ、Reactをベースとしていること、パフォーマンスの良さなどから採用を決めました。</p>
<p>「はたらく」タブに関しては、メルカリアプリとして高いレベルの体験の良さが求められるので、必然的に描画速度も求められます。いまはまだそこまでフル活用されていませんが、Next.jsはパフォーマンス向上のための設定が柔軟にできるため、今後必要に応じて積極的に活用していきたいと思います。</p>
<p>GraphQLのClientとしてはApollo Clientを採用しています。Web フロントエンドにおける人気のフレームワークのひとつで、優れた機能を豊富に持っており、効率的に開発を進めることができます。社内でも採用実績があったため今回も採用しました。Reactとの統合にはReact Hooksを利用しています。</p>
<h2>Flutter / Dart</h2>
<p>メルカリ ハロはメルカリアプリ内のサービスだけでなく、iOS/Android向けの独立したモバイルアプリも提供しています(ストアで「メルカリ ハロ」と検索してみてください)。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/05/adf1d103-ss5.png" alt="" /><br />
<em>メルカリとは独立した「メルカリ ハロ」アプリ</em></p>
<p>その基盤としてFlutter / Dartを採用しています。開発初期の検討時点では他にも以下の選択肢がありました。</p>
<ol>
<li>iOS / Androidネイティブ (Swift/Kotlin)</li>
<li>React Native</li>
<li>WebViewベースのアプリ</li>
</ol>
<p>モバイルアプリの技術選定にはWebフロントエンドに比べかなり時間がかかりました。それぞれ同じくらいメリット/デメリットがあり、特に1. 2.についてはメンバーや立場によって意見が様々で、メルカリ ハロのチームだけでなく、メルカリグループ横断的に議論を重ねる必要があったため、決断が難しかったです。主に以下のような点が論点となりました。</p>
<ul>
<li>開発コスト</li>
<li>メンバーの習熟度</li>
<li>パフォーマンス</li>
<li>社内的なリソースアロケーション</li>
<li>サードパーティ製のライブラリを含む、エコシステムの充実度</li>
<li>採用のしやすさ</li>
<li>メルカリグループとしてのノウハウの集約/分散</li>
</ul>
<p>色んな論点はありましたが、その中でもやはり「開発コスト」はとても大きな関心事でした。開発初期のチームのメンバーは少なかった一方で、やはり昨今の市場状況を踏まえてクイックなリリースが求められていました。iOS/Android両方をネイティブで開発するとなると単純に考えて2倍近くのコストが掛かりますし、両プラットフォームを同じタイミングでリリースできるとも限りません。チームとしては独立した専用のモバイルアプリの提供と、iOS/Android同時リリースはなんとしても達成したかったため、ネイティブでの開発はスケジュール面でのリスクが大きいと感じていました。</p>
<p>一方で(メルカリ ハロアプリではなく)メルカリアプリはiOS/Androidネイティブで実装されています。リソースアロケーションという意味ではメルカリグループ内では圧倒的な優位性がありました。社外にも当然ながら開発できる人は多いです。しかし前述のとおり初期メンバーは数が少なく、当時は様々な事情により他のチームからメンバーを確保できる保証もありませんでした。(なお、US版のメルカリはReact Nativeで実装されており、こちらもノウハウの面で優位性がありました)</p>
<p>パフォーマンスについて懸念する意見もでました。この点についてはiOS/Androidネイティブが一番優れていることに議論の余地はありません。「Flutterのようなクロスプラットフォームで作っても結局ネイティブで作り直す必要があるのではないか」といった指摘もありましたが、現時点では最高のパフォーマンスを追求するよりもまずはリーズナブルにサービスを立ち上げ、お客様に利用してもらうことが何より大事だと判断しました。幸いにもサービスの特性上、iOS/Androidのパフォーマンスをフルに求められるケースは今のところ多くありません。なお、「メルカリアプリ」もサービス開始してから4年ほど経った頃にフルスクラッチでアプリを再開発しています。まずはサービスがそこまで軌道に乗ることが大事ですし、数年後にやってくるかもしれない再開発のタイミングで必要があればiOS/Androidネイティブに切り替えるでも良いだろうという判断になりました。</p>
<p>最終的にはこれらの論点を総合的に踏まえ、Flutterがメルカリ ハロにとっては一番マッチしそうという判断をしました。</p>
<p>メルカリ ハロというサービスだけでなく、メルカリグループとしてみたときにこの判断が本当に正解だったかは今も分かりません。ただ現状から考えると開発コストやパフォーマンスなど、総合的にバランスの取れた開発環境になっていると感じるので、妥当な決断だったのかなと思っています。</p>
<h2>おわりに</h2>
<p>メルカリ ハロで使われている技術スタックやアーキテクチャ、その意思決定に至るプロセスについて、ほんの一部ではありますが簡単にご紹介しました。</p>
<p>新規サービスを立ち上げるうえで技術選定は非常に難しいことだと思います。ビジネスを成功させるために様々な観点から意思決定を行う必要があります。さらに一度決めたものをあとから変更するのは現実的に不可能なことがほとんどであるため、責任は重大です。ただ、同時に多くのエンジニアにとって「楽しく、やりがいのある瞬間」でもあるはずです。</p>
<p>会社の規模やそれぞれの状況によって判断の基準が変わってくるため正解はありませんが、メルカリ ハロではこのように技術選定してきました。これからサービスを立ち上げるみなさんにとって少しでも参考になれば幸いです。</p>
<h2>Links</h2>
<p><a href="https://engineering.mercari.com/blog/entry/20240524-mercari-hallo-world/">連載:Mercari Hallo, world! -メルカリ ハロ 開発の裏側-</a></p>
<p>メルカリではメンバーを大募集中です。メルカリ ハロの開発やメルカリに興味を持った方がいればぜひご応募お待ちしています。詳しくは以下のページをご覧ください。</p>
<ul>
<li><a href="https://apply.workable.com/mercari/j/1103382B83/">Engineering Manager – Mercari/HR領域新規事業 (Mercari Hallo)</a></li>
<li><a href="https://apply.workable.com/mercari/j/A53FA51E42/">Software Engineer, Frontend – Mercari/HR領域新規事業 (Mercari Hallo)</a></li>
<li><a href="https://apply.workable.com/mercari/j/197FBA1617/">Software Engineer, Backend – Mercari/HR領域新規事業 (Mercari Hallo)</a></li>
<li><a href="https://apply.workable.com/mercari/j/436EAEC812/">Software Engineer, iOS/Android (Flutter) – Mercari/HR領域新規事業 (Mercari Hallo)</a></li>
<li><a href="https://apply.workable.com/mercari/j/62287F2907/">Software Engineer, Site Reliability – Mercari/HR領域新規事業 (Mercari Hallo)</a></li>
<li><a href="https://apply.workable.com/mercari/j/0086D4DA33/">QA Engineer – Mercari/HR領域新規事業 (Mercari Hallo)</a></li>
</ul>
- 爆速で「あたらしい出会いを繋ぐ」を創った、メルカリ ハロのエンジニアリングhttps://engineering.mercari.com/blog/entry/20240527-mercari-hallo-engineering/https://engineering.mercari.com/blog/entry/20240527-mercari-hallo-engineering/<p>こんにちは。メルカリのVPoE Workの @godriccao です。『連載:Mercari Hallo, world! -メルカリ ハロ 開発の裏側-』の1回目を担当させていただきます。スピードがクリティカルであるメ […]</p>
Mon, 27 May 2024 09:00:18 GMT<p>こんにちは。メルカリのVPoE Workの <a href="https://engineering.mercari.com/blog/author/godric/" title="@godriccao">@godriccao</a> です。『<a href="https://engineering.mercari.com/blog/entry/20240524-mercari-hallo-world/" title="連載:Mercari Hallo, world! -メルカリ ハロ 開発の裏側-">連載:Mercari Hallo, world! -メルカリ ハロ 開発の裏側-</a>』の1回目を担当させていただきます。スピードがクリティカルであるメルカリ ハロの展開を支えるエンジニアリングを紹介いたします。</p>
<h2>実現したい世界は「あたらしい出会いを繋ぎ、信頼と機会をひろげる」</h2>
<p>メルカリのミッションは「あらゆる価値を循環させ、あらゆる人の可能性を広げる」です。フリマアプリ「メルカリ」でのモノの循環に始まり、これまで「お金」「信用」「暗号資産」を循環させてきました。</p>
<p>今年3月には空き時間おしごとサービス「メルカリ ハロ」を一都三県で提供を開始し、わずか1ヶ月で登録者数250万人に到達、<a href="https://about.mercari.com/press/news/articles/20240416_mercarihallo-nationwide/" title="4月16日より全国展開">4月16日より全国展開</a>しました。いつもお使いのメルカリに「はたらく」タブが追加されると共に、「働く」機能に特化した専用アプリもリリースし、「時間・スキル」を循環の輪に追加しました。</p>
<p>メルカリ ハロのミッションは「あたらしい出会いを繋ぎ、信頼と機会をひろげる」です。スポットワークの新しい働き方で、「人、場所、おしごと」と新しい出会いを作り、社会課題を解決しながら、新しい価値の循環を広げたいと考えています。</p>
<h2>とにかくスピードが重要</h2>
<p>スポットワーク市場はネットワーク効果が働いています。おしごとを探す側とおしごとを提供する側、数が多ければ多いほど需給のマッチング効率が上がり、おしごと成立のチャンスが増えます。</p>
<p>またスポットワーク市場は発展途上で、すでに一定のプレイヤーが存在しています。この市場では、ネットワーク効果の強さによって、初めて成長できるプレイヤーが勝つことができます。だからこそ、スピードが最も重要です。</p>
<p>新しいサービスを展開するとき、完璧に作ることよりも、お客さまの声を聞きながら高速にイテレーションするほうが大切です。</p>
<h2>爆速開発スピードと品質を両立したメルカリ ハロのエンジニアリング</h2>
<p>メルカリ ハロのサービス開発に着手したのは2023年の4月からです。2023年10月までは極少人数でサービスの基礎をしっかり作り、10月からチームの人数を一気に増やしました。その後、「メルカリ ハロ」アプリ、事業者管理画面、カスタマサポートツールの機能を開発させると同時に、フリマアプリ「メルカリ」の6つ目のタブである「<a href="https://mercan.mercari.com/articles/41558/" title="はたらくタブ">はたらくタブ</a>」も開発を鋭意に進めました。2024年3月6日には、上記のすべてのコンポーネントがメルカリ ハロサービスのローンチとともにリリースされ、更に多くの機能を追加し、4月16日に全国展開しました。</p>
<p>2200万超のMAUを持つフリマアプリ「メルカリ」のお客さま基盤をメルカリ ハロで活用するためには、機能面と性能面で一定以上の品質を担保することが必要です。では、なぜ爆速なリリースを実現しながらも品質を担保できたのでしょうか。</p>
<h3>メルカリグループの All For One な総力戦</h3>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/05/752482aa-hallo-supported-by-mercari-group.jpg" alt="hallo-supported-by-mercari-group" /></p>
<p>メルカリ ハロの成功は、メルカリグループ全体の総力戦によるものです。各チームの協力がなければ、短期間で高品質なサービスを提供することはできませんでした。具体的には、以下のチームが重要な役割を果たしました。</p>
<ul>
<li><strong>Architectチーム</strong>:開発初期のアーキテクチャー選定とリリース時の品質保証(PRC)に協力。彼らの専門知識と経験により、堅牢なシステム設計と高品質なリリースが実現しました。</li>
<li><strong>Platform、Network、SREチーム</strong>:インフラ構築やトラブルシューティングに協力し、スケーラブルで信頼性の高いインフラを提供。これにより、サービスのパフォーマンスと可用性が確保されました。</li>
<li><strong>メルペイとメルカリのFoundationチーム</strong>:IDP、KYC、加盟店基盤、決済基盤、- Growth基盤などのFoundation系サービスを提供。これにより、複雑なシステムも短期間にメルカリ ハロにインテグレートでき、品質も保証されました。</li>
</ul>
<p>これらのチームからの手厚い且つプロフェッショナルなサポートと、しっかり整えられた基盤サービスの提供があったからこそ、短期間で高い品質の意思決定とサービスレベルの担保が可能となりました。</p>
<h3>メルカリ ハロ組織の Move Fast と All For One</h3>
<p>メルカリ ハロの初期メンバーは、ほとんどが元々株式会社ソウゾウのメンバーです。ソウゾウのメンバーは、ベンチャー精神を誰よりも持ち、「<a href="https://engineering.mercari.com/blog/entry/20221101-souzoh-move-fast/" title="Move Fast">Move Fast</a>」というバリューを大切にしています。個々のエンジニアがオーナーシップを持って意思決定できるよう、ソウゾウのメンバーはメルカリ ハロのProduct、Design、Backend、Frontend、Mobile、QA、SREチームの骨組みを作り上げました。</p>
<p>メルカリ ハロ組織全体は初期から一丸となり、異なる職種間でも密に連携し、成功のためにあらゆる行動を取りました。エンジニアチーム内やProductチームとの連携はもちろん、Marketing、Customer Support、Sales、Partner Successチームとも密に連携できました。Customer SupportからのVoice of Customer、Partner SuccessからのVoice of Partnerを毎日シェアし、ソリューションを一緒に考えながら、開発の優先度を柔軟に調整してきました。</p>
<p>さらに、Salesの事業者商談にエンジニアも参加し、デモで企業のお客さまの心を捕まえた事例も、この組織の日常的なものになっています。このように一丸となることで、高速なイテレーションが実現しました。</p>
<h3>No “Major” Incident</h3>
<p>メルカリ ハロはリリース後、2200万超のMAUを持つフリマアプリ「メルカリ」のユーザーにリーチ可能なサービスです。これだけの規模でサービスを提供するためには、一定以上の品質が求められます。品質の低いリリースは、ネガティブインパクトも非常に大きくなります。</p>
<p>そこで、私たちはリリース目標として「No “Major” Incident」を掲げました。この目標の背後には、大きなインシデントを発生させないという意図もありますが、同時に、スピードと両立するために、あえて「小さなインシデントは起こしても良い」という方針をチームに宣言しました。これは、スピードを重視しつつ、重大な問題を未然に防ぐための戦略です。</p>
<p>この方針により、チームは細かいトラブルを通して学び、システム全体の可用性を高めることができました。結果として、リリース後には大きなインシデントは発生せず、一定の品質を維持することができました。</p>
<h2>メルカリ ハロエンジニアリングのこれから</h2>
<p>サービスローンチしたばかりなので、やりたいことはたくさんあります。</p>
<p>技術面から言うと、アルゴリズム、ML、LLMなどの技術を含めてうまく活用し、今まで存在しなかった感動的な「人、場所、おしごと」との「出会い」を作りたいと考えています。メルカリの強みであるお客さま基盤とデータをうまく活用し、メルカリ ハロで蓄積した新しいデータを含め、「新しい信頼と機会」をひろげたいと思います。</p>
<p>組織面から言うと、開発のボリュームと複雑度が指数的に増えている中、事業成長スピードと共にスケールする組織体制を作ることが個人的に一番楽しめる課題です。</p>
<p>長期的には、「働き方」や「雇用の方法」そのものが変わっていく転換期に突入していくと感じています。スポットワークを始め、「はたらく」という概念の転換を牽引するサービスになりたいです。そして、個人的にはメルカリ ハロ事業の海外進出の可能性にも期待しています。</p>
<h2>最後に</h2>
<p>メルカリ ハロ開発チームから、より具体的なエンジニアリングエピソードをこれからお送りいたしますので、お楽しみにしてください!</p>
<h2>Links</h2>
<p>連載:<a href="https://engineering.mercari.com/blog/entry/20240524-mercari-hallo-world">Mercari Hallo, world! -メルカリ ハロ 開発の裏側-</a></p>
<p>メルカリではメンバーを大募集中です。メルカリ ハロの開発やメルカリに興味を持った方がいればぜひご応募お待ちしています。詳しくは以下のページをご覧ください。</p>
<ul>
<li><a href="https://apply.workable.com/mercari/j/A53FA51E42/">Software Engineer, Frontend – Mercari/HR領域新規事業 (Mercari Hallo)</a></li>
<li><a href="https://apply.workable.com/mercari/j/197FBA1617/">Software Engineer, Backend – Mercari/HR領域新規事業 (Mercari Hallo)</a></li>
<li><a href="https://apply.workable.com/mercari/j/436EAEC812/">Software Engineer, iOS/Android (Flutter) – Mercari/HR領域新規事業 (Mercari Hallo)</a></li>
<li><a href="https://apply.workable.com/mercari/j/62287F2907/">Software Engineer, Site Reliability – Mercari/HR領域新規事業 (Mercari Hallo)</a></li>
<li><a href="https://apply.workable.com/mercari/j/050489ABC1/">Software Engineer, Machine Learning Leader – Mercari/HR領域新規事業 (Mercari Hallo)</a></li>
<li><a href="https://apply.workable.com/mercari/j/0086D4DA33/">QA Engineer – Mercari/HR領域新規事業 (Mercari Hallo)</a></li>
<li><a href="https://apply.workable.com/mercari/j/1103382B83/">Engineering Manager – Mercari/HR領域新規事業 (Mercari Hallo)</a></li>
</ul>
- 【5/27スタート】連載:Mercari Hallo, world! -メルカリ ハロ 開発の裏側-https://engineering.mercari.com/blog/entry/20240524-mercari-hallo-world/https://engineering.mercari.com/blog/entry/20240524-mercari-hallo-world/<p>こんにちは!メルカリのQA Engineering Managerの@____rina____です。先日、3月6日にメルカリグループの新規事業「メルカリ ハロ」がオープンされました。 メルカリ ハロは好きな時間に最短1時 […]</p>
Fri, 24 May 2024 08:00:42 GMT<p>こんにちは!メルカリのQA Engineering Managerの<a href="https://engineering.mercari.com/blog/author/underscore42rina/">@____rina____</a>です。先日、3月6日にメルカリグループの新規事業「<a href="https://hallo.mercari.com/" title="メルカリ ハロ">メルカリ ハロ</a>」がオープンされました。</p>
<p>メルカリ ハロは好きな時間に最短1時間から働ける「空き時間おしごとアプリ」です。仕事を探して、働いて、給与をもらうすべてをスマホで簡単に行うことができます。</p>
<p><a href="https://hallo.mercari.com/"><img src="https://storage.googleapis.com/prd-engineering-asset/2024/05/9399cfe5-crew-ogp-1024x538.png" alt="" /></a></p>
<p>またメルカリ ハロは、2023年4月に立ち上がったメルカリWorkチームという、メルカリグループの中の独立した組織で開発が行われています。システムもメルカリのメインのシステムからは独立したかたちで構成されており、メルカリグループとしての技術基盤を活かしつつ、さまざまな技術的なチャレンジを積極的に行っています。</p>
<p>そんなメルカリ ハロのオープンまでの約1年間の開発の裏側を、これから毎週公開していきます!</p>
<table>
<thead>
<tr>
<th>日付</th>
<th>担当</th>
<th>タイトル</th>
</tr>
</thead>
<tbody>
<tr>
<td>2024/05/27</td>
<td><a href="https://engineering.mercari.com/blog/author/godric/" title="@godric ">@godric </a></td>
<td><a href="https://engineering.mercari.com/blog/entry/20240527-mercari-hallo-engineering/" title="爆速で「あたらしい出会いを繋ぐ」を創った、メルカリ ハロのエンジニアリング">爆速で「あたらしい出会いを繋ぐ」を創った、メルカリ ハロのエンジニアリング</a></td>
</tr>
<tr>
<td>2024/05/29</td>
<td><a href="https://engineering.mercari.com/blog/author/napoli/" title="@napoli">@napoli</a></td>
<td><a href="https://engineering.mercari.com/blog/entry/20240529-mercari-hallo-tech-stacks/" title="メルカリ ハロの技術スタックとその選定理由">メルカリ ハロの技術スタックとその選定理由</a></td>
</tr>
<tr>
<td>2024/06/03</td>
<td><a href="https://engineering.mercari.com/blog/author/naka/" title="@NakaMasato">@NakaMasato</a></td>
<td><a href="https://engineering.mercari.com/blog/entry/20240603-mercari-hallo-sre/" title="メルカリ ハロ立ち上げ時のSRE">メルカリ ハロ立ち上げ時のSRE</a></td>
</tr>
<tr>
<td>2024/06/06</td>
<td><a href="https://engineering.mercari.com/blog/author/underscore42rina/" title="@\_\_\_\_rina\_\_\_\_">@____rina____</a></td>
<td><a href="https://engineering.mercari.com/blog/entry/20240603-mercarihallo-releaseqastrategy/" title="メルカリ ハロ リリースのQA戦略">メルカリ ハロ リリースのQA戦略</a></td>
</tr>
<tr>
<td>2024/06/10</td>
<td><a href="https://engineering.mercari.com/blog/author/atsumo/" title="@atsumo">@atsumo</a></td>
<td><a href="https://engineering.mercari.com/blog/entry/20240606-mercari-hallo-app-tech-stacks/" title="メルカリ ハロ アプリの技術スタックの紹介">メルカリ ハロ アプリの技術スタックの紹介</a></td>
</tr>
<tr>
<td>2024/06/13</td>
<td><a href="https://engineering.mercari.com/blog/author/tanasho/" title="@tanasho">@tanasho</a></td>
<td><a href="https://engineering.mercari.com/blog/entry/20240613-mercari-hallo-web-frontend/" title="メルカリ ハロ Webフロントエンドの開発スピードと品質両立の取り組み">メルカリ ハロ Webフロントエンドの開発スピードと品質両立の取り組み</a></td>
</tr>
</tbody>
</table>
<p>初日は、 <a href="https://engineering.mercari.com/blog/author/godric/" title="@godric ">@godric </a>が執筆予定です。</p>
<p>公開に関しては、メルカリ公式DevX(旧Twitter)<a href="https://twitter.com/mercaridevjp" title="@mercaridevjp">@mercaridevjp</a>jでも随時お知らせします。ハッシュタグ<a href="https://x.com/search?q=%23%E3%83%A1%E3%83%AB%E3%82%AB%E3%83%AA%E3%83%8F%E3%83%AD%E9%96%8B%E7%99%BA%E3%81%AE%E8%A3%8F%E5%81%B4&amp;src=typed_query&amp;f=top" title="#メルカリハロ開発の裏側">#メルカリハロ開発の裏側</a>で検索してみてください。</p>
<p>メルカリの新しい事業での技術的チャレンジを広く届けられたらと思っていますので、どうぞお楽しみに!</p>
- メルカリは #技術書典 16 にスポンサーをし、新刊を販売します!https://engineering.mercari.com/blog/entry/20240520-techbookfest16/https://engineering.mercari.com/blog/entry/20240520-techbookfest16/<p>こんにちは。メルペイ Engineering Engagement チームの mikichin です。 メルカリは、5月25日から開催される技術書典16にゴールドスポンサーをしています! メルカリ技術書典部では、有志メン […]</p>
Mon, 20 May 2024 14:08:52 GMT<p>こんにちは。メルペイ Engineering Engagement チームの <a href="https://twitter.com/chida_miki" title="mikichin">mikichin</a> です。<br />
メルカリは、5月25日から開催される技術書典16にゴールドスポンサーをしています!<br />
メルカリ技術書典部では、有志メンバー5名による業務や趣味の技術についてまとめたものとメルペイ立ち上げ当時に戻れたらどんな技術選択をしていたかを振り返るインタビューをまとめた、ここでしか手に入らない2つの新刊を準備しています。</p>
<p>本記事では、新刊とメルカリ技術書典部が販売している本の購入方法についてご紹介します。</p>
<h2>技術書典 について</h2>
<p>ITや機械工作とその周辺領域について書いた本を対象にした同人誌即売会。 技術者たちの「コミケ」とも言われています。<br />
メルカリでは、技術書典3からスポンサーをしており、直近3連続ゴールドスポンサーです。また、有志メンバーで構成されたメルカリ技術書典部では、定期的に新刊を販売しています。</p>
<h3>技術書典 16</h3>
<p>オンライン開催:5月25日(土)〜6月9日(日)<br />
オフライン開催:5月26日(日)池袋・サンシャインシティ 展示ホールD(文化会館ビル2F)</p>
<h2>新刊について</h2>
<p>技術書典16では、メルカリ技術書典部は新刊を2冊準備しています。<br />
<img src="https://storage.googleapis.com/prd-engineering-asset/2024/05/eac66adb--2024-05-19-17.38.08.png" alt="" /></p>
<h3>Unleash Mercari Tech! vol.3</h3>
<p>有志メンバー5名による業務や趣味の技術についてなどをまとめた一冊。<br />
それぞれが非常に濃い内容となっているので、どれか1つでも興味を持っていただけるとうれしいです。</p>
<p>〈目次〉<br />
第1章 AI時代にひねくれた選択? – 業務委託から正社員への変化球<br />
第2章 CUEでBrainfuckインタープリターを作る<br />
第3章 Goのエラーハンドリングを考える 2024<br />
第4章 真剣に商業登記簿APIを作った話<br />
第5章 OAuth 2.0 ClientをTerraform Custom Providerで宣言的に管理してみた</p>
<p>URL:<a href="https://techbookfest.org/product/4JE8riJdXX5y1vBEYq7v8L">https://techbookfest.org/product/4JE8riJdXX5y1vBEYq7v8L</a></p>
<h3>Unleash Mercari Tech! vol.4〜メルペイ立ち上げ当時に戻ったら?〜</h3>
<p>メルペイがリリースしてから丸5年。その当時、最善の選択をし開発をしてきていますが、メルペイのサービス拡充はもちろん、ビットコインが売買できるメルコインができるなど、(おそらく)想定していなかった状況に発展してきています。<br />
そこで、「今の知識を持ったまま、メルペイ立ち上げ当時に戻るとしたらどうしてたか?」をテーマにインタビューを行い、まとめました。<br />
各技術領域で、自社サービスの拡大や開発する上で提供されている機能のアップデートなど今の状況を踏まえ、今だったらあのときの開発をどのように進めていたかを振り返っています。</p>
<p>〈目次〉<br />
第1章 Payment Platform編<br />
第2章 iOS / Android編<br />
第3章 Engineering Manager編<br />
第4章 Platform Engineering編<br />
第5章 SRE編<br />
第6章 Architect編<br />
第7章 元メルペイCTO編</p>
<p>URL:<a href="https://techbookfest.org/product/uVmfrDWUZd5JD5wJkPndxL">https://techbookfest.org/product/uVmfrDWUZd5JD5wJkPndxL</a></p>
<h2>オフライン会場で販売します!</h2>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/05/a4b5244c--2024-05-19-17.40.12.png" alt="" /></p>
<p>5月26日、オフライン会場新刊含め、メルカリ技術書典部の本を販売いたします!入口入ってすぐの「協04」でお待ちしてます。<br />
オンラインでも購入が可能です。<br />
<a href="https://techbookfest.org/organization/47710001">https://techbookfest.org/organization/47710001</a></p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/05/5f931ed9--2024-05-19-17.58.15.png" alt="" /></p>
<p>執筆したメンバーの多くはブースにいる予定です。オフラインでご参加される方は、ぜひ会場でお会いしましょう!</p>
- ElasticsearchのFunction Score Queryのパフォーマンスを最適化するhttps://engineering.mercari.com/blog/entry/20240424-6f298aa43b/https://engineering.mercari.com/blog/entry/20240424-6f298aa43b/<p>search infra teamのmrkm4ntrです。我々の運用するElasticsearchにはFunction Score Queryを使ったリクエストが送られてきます。Function Score Queryは […]</p>
Wed, 24 Apr 2024 10:49:35 GMT<p>search infra teamのmrkm4ntrです。我々の運用するElasticsearchにはFunction Score Queryを使ったリクエストが送られてきます。Function Score Queryはサブクエリのスコアに任意の関数を適用できるというもので、とても便利な機能ですが、同時にTop K(スコアが大きいものからK個を取得する場合)クエリ処理の最適化の恩恵を受けられなくなるという欠点もあります。この記事では、Function Score Queryに用いる関数の性質を利用し、Function Score QueryとTop Kのクエリ処理の最適化を両立させる方法について説明します。本記事は読者が検索エンジンの仕組みにある程度詳しいことを想定しています。</p>
<h2>Top Kのクエリ処理の最適化</h2>
<p>Elasticsearchの検索機能を提供しているライブラリLuceneには、Top Kを取得する際に、Top Kに入る見込みのないもののスコア計算をスキップすることで、パフォーマンスの最適化を図る機能が存在します。</p>
<p>例えば( ”search” OR “engine”)のようなクエリがあり、”search”というtermに対応するposting listの最大スコアが5.0、”engine”というtermに対応するposting listの最大スコアが3.0だとします。</p>
<p>BM25 (<a href="https://ja.wikipedia.org/wiki/Okapi_BM25">https://ja.wikipedia.org/wiki/Okapi_BM25</a>) にて各termに対するドキュメントはスコア付けされるため、indexの構築時に最大スコアが決まります。両方を含む文書のスコアは5.0 + 3.0 = 8.0になります。</p>
<p>ここでスコアの高い順にTop 10を検索することを考えます。この時大きいものから10番目のスコアがmin competitive scoreと呼ばれるものになります。つまり、このmin competitive scoreよりも大きいスコアをとりえない文書はスコア計算する必要がありません。</p>
<p>仮にmin competitive scoreが3.0より大きい値とします。この場合”engine”のみを含む文書のスコアはmin competitive scoreより大きくはならないので”engine”のみを含む文書のスコア計算はスキップできます。本来ならばORはそれぞれのtermのposting listを全て走査する必要があるのですが、”search”のposting listに存在する文書のみのスコア計算をすれば良いことになります。このような手法によりクエリのパフォーマンスを向上させることができます。</p>
<h2>Function Score Query</h2>
<p>Function Score Queryは以下のようなクエリです。クエリを実行した結果はそのまま使うのではなく、サブクエリを実行した結果にfunctionsで指定された関数の戻り値を結合したスコアを最終スコアとして利用します。デフォルトの結合方法は乗算ですが、<code>score_mode</code>の値によって動作を変更できます。</p>
<pre><code class="language-json">{
"query": {
"function_score": {
"query": {
… // サブクエリ
},
"functions": […],
"score_mode": …
}
}
}</code></pre>
<p>上記のTop Kクエリ処理の最適化は最大スコアがindex構築時に決まることが前提でした。Function Score Queryを使った場合、サブクエリのスコアに任意の関数の戻り値を結合することができるため、文書の最終スコアがクエリの実行時に決まることになります。このような状況下では先ほど説明したTop Kクエリ処理の最適化を使うことができません。</p>
<p>Function Score Queryを使うとTop Kクエリ処理の最適化がされていないのは、コードを確認すると明白です。min competitive scoreをLuceneのScorerに伝えるには <code>setMinCompetitiveScore</code>というメソッド(<a href="https://github.com/apache/lucene/blob/releases/lucene/9.10.0/lucene/core/src/java/org/apache/lucene/search/Scorable.java#L48-L57">https://github.com/apache/lucene/blob/releases/lucene/9.10.0/lucene/core/src/java/org/apache/lucene/search/Scorable.java#L48-L57</a>) を使うのですが、ElasticsearchのFunction Score QueryのScorerである<code>FunctionFactorScorer</code>においては<code>setMinCompeitiveScore</code>を呼んでいません。これによりTop Kクエリ処理の最適化がされていないことがわかります。<br />
<a href="https://github.com/elastic/elasticsearch/blob/v8.13.2/server/src/main/java/org/elasticsearch/common/lucene/search/function/FunctionScoreQuery.java#L371-L487">https://github.com/elastic/elasticsearch/blob/v8.13.2/server/src/main/java/org/elasticsearch/common/lucene/search/function/FunctionScoreQuery.java#L371-L487</a></p>
<h2>TopKクエリ処理の最適化が可能な関数</h2>
<p>確かに任意の関数に対してTop Kクエリ処理の最適化を実現するのは不可能です。しかし、利用する関数によってはTop Kクエリ処理の最適化の恩恵を受けれるものも存在します。例えばよく使われる、作成日時からの時間経過でスコアを指数関数的に減衰させる以下のようなFunction Score Queryです。</p>
<pre><code class="language-json">{
"query": {
"function_score": {
"query": {
… // サブクエリ
},
"functions": [
{
"exp": {
"created_time": {
"scale": "10d",
"decay": 0.8
}
}
}
],
"score_mode": "multiply"
}
}
}</code></pre>
<p>このクエリでは、最終スコアは (指数関数的減衰 * サブクエリのスコア) となります。</p>
<p>重要なのはこの減衰は現在日時と作成日時の差において単調減少であるということです。</p>
<p>Luceneでは、posting listをあるフィールドの値で構築時にソートすることができます。posting listを作成日時の降順にソートすることで、上記の減衰関数を用いる際に、posting list内の前のドキュメントよりも必ず減衰値が大きくなることが保証できます。</p>
<p>これにより、サブクエリのみのmin competitive scoreが5.0、今の評価しているドキュメントの減衰が0.7だとすると、実質min competitive scoreを 5.0 / 0.7 = 7.14として扱うことができます。これは単にTop Kクエリ処理の最適化が使えるだけではなく、min competitive scoreがposting listを進むたびに増幅していくことになり、より多くのドキュメントの評価をスキップできる可能性が高まります。</p>
<h2>PoCの実装とLuceneへの貢献</h2>
<p>それをふまえて、前述の減衰関数を受け取りTop Kクエリ処理の最適化を実現するElasticsearchの新しいクエリをElasticsearchのpluginとして実装しました。基本的にはElasticsearchのFunction Score Queryと同じですが、サブクエリのScorerの<code>setMinCompetitiveScore</code>を適宜呼び出す部分が異なります。</p>
<p>実装自体は簡単でしたが、いざ動作確認すると全くパフォーマンスに変化がありませんでした。リモートデバッグで確認したところ、サブクエリ内のBoolean QueryにTop Kクエリ処理の最適化に必要な<code>WANDScorer</code>(<a href="https://github.com/apache/lucene/blob/releases/lucene/9.10.0/lucene/core/src/java/org/apache/lucene/search/WANDScorer.java">https://github.com/apache/lucene/blob/releases/lucene/9.10.0/lucene/core/src/java/org/apache/lucene/search/WANDScorer.java</a>) や<code>BlockMaxConjunctionScorer</code>(<a href="https://github.com/apache/lucene/blob/releases/lucene/9.10.0/lucene/core/src/java/org/apache/lucene/search/BlockMaxConjunctionScorer.java">https://github.com/apache/lucene/blob/releases/lucene/9.10.0/lucene/core/src/java/org/apache/lucene/search/BlockMaxConjunctionScorer.java</a>) が使われておらず、代わりに使われていたのは、それぞれ<code>DisjunctionSumScorer</code>(<a href="https://github.com/apache/lucene/blob/releases/lucene/9.10.0/lucene/core/src/java/org/apache/lucene/search/DisjunctionSumScorer.java">https://github.com/apache/lucene/blob/releases/lucene/9.10.0/lucene/core/src/java/org/apache/lucene/search/DisjunctionSumScorer.java</a>) と<code>ConjunctionScorer</code>(<a href="https://github.com/apache/lucene/blob/releases/lucene/9.10.0/lucene/core/src/java/org/apache/lucene/search/ConjunctionScorer.java">https://github.com/apache/lucene/blob/releases/lucene/9.10.0/lucene/core/src/java/org/apache/lucene/search/ConjunctionScorer.java</a>) でした。</p>
<p>調べてみたところ、<code>WANDScorer</code>や<code>BlockMaxConjunctionScorer</code>はオーバーヘッドが大きいためトップレベルの句の場合でしか利用されないようです。つまり ((A AND B) OR (C AND D)) のようなクエリの場合はORは<code>WANDScorer</code>が使われますが、ANDには<code>ConjunctionScorer</code>が使われることになります。サブクエリをトップレベルの句と認識させるためには、新しく追加したクエリからサブクエリの<code>ScorerSupplier</code>の<code>setTopLevelScoringClause</code>メソッド(<a href="https://github.com/apache/lucene/blob/695c0ac84508438302cd346a812cfa2fdc5a10df/lucene/core/src/java/org/apache/lucene/search/ScorerSupplier.java#L46-L54">https://github.com/apache/lucene/blob/695c0ac84508438302cd346a812cfa2fdc5a10df/lucene/core/src/java/org/apache/lucene/search/ScorerSupplier.java#L46-L54</a>) を呼ぶ必要があります。そのように修正したところ、無事に<code>WANDScorer</code>と<code>BlockMaxConjunctionScorer</code>が使われるようになりました。</p>
<p>これでパフォーマンスが改善するかと思われましたが、相変わらず変化がありません。さらに調べると、<code>WANDScorer</code>の下の<code>ConjunctionScorer</code> (ORの下のAND)と<code>BlockMaxConjunctionScorer</code>の下の<code>DisunctionSumScorer</code> (ANDの下のOR)が最大スコアとしてInfinityを返していました。</p>
<p>Luceneの実装を見ると確かにInfinityを返すようになっています。何故Infinityを返すのか全く意図が掴めずに頭を抱えましたが、トップレベルの句以外は最適化をしないという修正(<a href="https://github.com/apache/lucene/pull/12490">https://github.com/apache/lucene/pull/12490</a>) での漏れだということがわかりました。そこで、それぞれ最大スコアに正しい値を返すように修正したところ、ようやくパフォーマンスが大きく改善することが確認できました。同様にprofile APIにおいても修正漏れがあったため、以下のプルリクエストをLuceneのupstreamに送りました。今は全てmergeされています。<br />
<a href="https://github.com/apache/lucene/pull/13031">https://github.com/apache/lucene/pull/13031</a><br />
<a href="https://github.com/apache/lucene/pull/13043">https://github.com/apache/lucene/pull/13043</a><br />
<a href="https://github.com/apache/lucene/pull/13066">https://github.com/apache/lucene/pull/13066</a></p>
<p>上で実装したpluginを使って我々のワークロードを模したパフォーマンステストを実施したところ、既存のクエリの95pが約35%、99pが60%下がりました。さらにコストも約1/3削減できることがわかりました。</p>
<p>ただし、この最適化の恩恵を受けるためにはリクエストパラメータのtrack_total_hitsをfalseにする、もしくは十分小さい値(1,000以下)に設定する必要があります。というのも最低でもこの件数はヒットするドキュメントを検索する必要があるため、スキップ対象が少なくなるからです。歴史的経緯により、この値をすぐに小さくすることは難しく、また最近この最適化ができない形のクエリがテストされているため、この最適化を実際に我々の本番環境に適用できるかは検討中です。</p>
<h2>さいごに</h2>
<p>この記事ではElasticsearchのFunction Score Queryに使われる関数の単調減少性を利用してTopKスコア処理の最適化の恩恵を受ける方法について述べました。このタスクでLuceneのスコアリング周りの内部実装についての理解が深まりました。また、仮説を実装し、何度もうまくいかない原因を潰していくサイクルは大変でしたがエキサイティングでした。もし仮にこのような最適化を適用できるクエリを利用されている場合は、試してみていただけると幸いです。</p>
- Oktaアクセス権限のスケーラブルなレビュー方法https://engineering.mercari.com/blog/entry/20240131-ja-okta-access-review/https://engineering.mercari.com/blog/entry/20240131-ja-okta-access-review/<p>*Security & Privacy Divisionの原動力となっているバリュー、それは「By design, by default and at scale(設計で叶える、デフォルトに組み込む、スケールに対 […]</p>
Thu, 18 Apr 2024 10:12:50 GMT<p>*Security & Privacy Divisionの原動力となっているバリュー、それは「By design, by default and at scale(設計で叶える、デフォルトに組み込む、スケールに対応する)」です。</p>
<p>Oktaのユーザーアクセス権の棚卸し作業をPlatform Security Teamに率いてほしいという依頼が寄せられました。このプロジェクトを進める中、私たちは過去の設定や慣習と向き合わなければなりませんでした。なぜなら古いやり方が残っていることで「by design」と「by default 」な管理が難しい状態だったからです。そのような状況にも関わらず、私たちは「at scale」で組織全体を網羅した検査を実施する必要がありました。</p>
<p>この記事では、これらの課題に私達がどのように挑んだかを説明します。</p>
<p>使用したテクノロジー:</p>
<ul>
<li>Neo4j: <a href="https://neo4j.com/">https://neo4j.com/</a></li>
<li>Okta: <a href="https://www.okta.com/">https://www.okta.com/</a> </li>
<li>Slack: <a href="https://slack.com">https://slack.com</a></li>
</ul>
<h1>概要</h1>
<p>メルカリでは、従業員のSaaSへのアクセスのほぼすべてをOktaを使って認証しています。アクセス権とは、許可するのは簡単ですが取り消すことが難しいものです。<br />
不要なアクセス権を一掃するため、Neo4jを使用して組織とアプリケーションへのアクセスをグラフ化し、ユーザーインターフェースにはSlackを使って調査を実施しました。</p>
<ol>
<li>全社的に提供しているアプリケーション以外で、現在付与されているアクセス権がすべて必要なものかを全社員に聞き取り調査。</li>
<li>その後、各マネージャーにそられのアクセス権がそれぞれの職責と照らし合わせて妥当かを確認。</li>
<li>情報を集約後、自己申告に基づいて不要なアクセス権をOkta APIを通じて直接削除。</li>
</ol>
<p>これらをこれらを実装することで社内全体を対象とした大規模な検査を行うことができました。</p>
<h1>これまでの道のり</h1>
<p>メルカリは今年創業11年を迎えました。今でこそ中堅企業に成長したものの、多くの10代が思春期を通過するように、成長痛に似たいくつもの苦労を乗り越えてきました。会社の拡大に伴い新たな従業員の入退社を経験し、アクセス管理に関するニーズも変化していきました。新たに導入されるサービスもあれば、廃止されるサービスもありました。過去にアクセス権の付与を決定した根拠や理由も、現在に至る過程で失われてしまいました。</p>
<p>メルカリはSaaSに大きく依存しているため、IDを管理するソリューションとしてOktaとGoogle Workspaceを使用しています。今回、アクセスレビューのプロジェクトに着手した時点で、Oktaのみですでに約8000のユーザー、500のアクティブアプリケーション、1400のグループが存在していました。アクセス権の削除は退職のケースであれば比較的簡単です。しかし、社内異動の場合は細心の注意を必要とする作業です。また勤務年数の短い従業員であればアクセス権の整理も比較的簡単にできますが、勤務年数が長い場合は長年の間にアクセス権が増えてしまっており見直しが大変な場合もあります。その結果、秩序が失われ、そのせいで複雑さも増してクリーンな状態にするのが難しくなっていました。</p>
<h2>プロジェクトの目標</h2>
<p>Security teamの最終的な目標は、アクセス権の乱用よって引き起こされる潜在的な被害を可能な限り減らすことです。</p>
<p>アクセス権のクリーンアップによりさまざまな副次的効果が期待できます。</p>
<ul>
<li>認証システムにおける無秩序さを減らす</li>
<li>各従業員/チームがどのようなシステムを使用しているか、より明確に理解できるようになる</li>
<li>システムオーナーにその人のアクセスがまだ必要なのかについてヒアリングし、その調査結果をドキュメント化するというSecurity teamメンバーのストレスを軽減する</li>
<li>どのように管理されているのか、それはなぜなのかについて理解するための時間を減らす</li>
<li>もう必要ない可能性のあるSaaSを特定する</li>
<li>クリーンな状態に基づいて、より優れたアカウントライフサイクル管理のパターンを作成する</li>
<li>その他</li>
</ul>
<h1>考えうる戦略</h1>
<p>「最小特権の原則」は、事故やインシデントのリスクを軽減する最善の方法のひとつであるものの、その適用と維持には相当な労力が必要であることが予想できました。</p>
<p>「最小特権の原則」を適用して最終目標を達成できるということは、私たちが以下のことを理解している(または把握している)という意味でもあります。</p>
<ul>
<li>社内にどのようなシステムがあるか</li>
<li>それらのシステムオーナーと管理者は誰か</li>
<li>誰がどのアクセス権を使ってこれらのシステムにアクセスできるか</li>
<li>各システムが処理し保存しているデータの種類は何か</li>
<li>これらシステムが使用される可能性のあるビジネスプロセスは何か</li>
<li>各社員とシステム、また取り得る行動とそれに伴う結果との間にあるつながり</li>
</ul>
<p>Oktaのデータをもとに簡単に計算してみましょう。アプリケーションは500個あり、ユーザー数は8000です。それらが直接割り当てられている、または1400のグループを通じて割り当てられています。各アプリケーションには複数のユーザーがおり、各グループにも複数のユーザーがいます。アプリケーションによっては複数のグループが存在するものもあり、それを組織体制と全ユーザーにリンクさせると、メルカリ社内には20万を超える関係性が存在するという計算になります。この段階では、各ユーザーのアクセスレベル、各システムで処理・保存されるデータの種類、ユーザーにとってどのようなアクションが可能かすらも分かりません。</p>
<p>仮にOktaから得られる情報のみを起点としましょう。1秒間で判断を下すために必要な情報はすべて揃っているという前提の下、1件の関係性につき1秒かかるとします。それでも前述の20万件の関係性をレビューするには丸々55時間かかってしまいます。したがって、一人の人間が全員分のアクセス状況を見ることは明らかに合理的ではありません。</p>
<p>では、他にも実践できそうな方法はないか見ていきましょう。</p>
<h2>戦略1:重要なシステムのみにスコープを絞る</h2>
<p>重要なシステムはどれなのか?どのような条件に従って決めるのか?これらの条件を定義しようとすると、考えうる要素が多すぎて誰もが容易に迷子になってしまいます。でも魔法なんて存在しないのですから、どこかしら複雑さが残るのもやむを得ないことです。もし、重要なシステムや機密性の高い情報を含むシステムを特定するという方法を選んでも、誰か(またはどこかのチーム)がすべてのシステムに目を通し、それらが何に使われ、どのようなユーザーがアクセスすべきかを理解して分類しなければなりません。</p>
<p>ただ同時に、私たちは社内にあるシステムを大体把握できています。とりあえず手をつけて始めてみたほうが、一通り情報をかき集めてから目の前にそびえ立つ到底登れそうにない頂に絶望するよりも理にかなうはずです。そうでもしないと、いざ山頂に辿り着いたとしても、全員が疲れ果てているか、すでに会社を辞めた後かのどちらかになっていることでしょう。</p>
<p>もうひとつの問題は、このレビューを行っている間も社内の環境は変化し続けるということです。レビューが完了するまでの間に新たなシステムが導入され、そこにユーザーが追加され、それらシステムは新たなユースケースのために使用されることでしょう。川の流れを止め、その間に魚を数えるようにはいかないのです。</p>
<h2>戦略2:フルスコープ、システムオーナーに依頼する</h2>
<p>システムオーナーに依頼するというのはどうでしょう?アプリの数は500。ユーザー数は1人の場合もあれば全従業員+業務委託が含まれる場合もあります。各システムオーナーが平均10システムを担当するとしても、50人がそれぞれ約4000件のアクセスを確認し、職務内容やサービスの性質、アクセスされるデータに基づき、これらユーザーがアクセスすべきか否かを判断をしなければならないことになります。どこかの時点で、少なくともいくつかの重要なシステムにおいては必要かもしれませんが、秩序のない初期の状態においては有効なアプローチとは言えません。</p>
<p>また、システムオーナーの多くはマネージャーやディレクターです。彼らの時間は貴重です。時間のない人は優先順位を意識するため、この業務はどんなに重要でも後回しにされる可能性が高いでしょう。</p>
<h2>戦略3:まずユーザーに質問し、マネージャーにその回答を確認してもらう</h2>
<p>他の誰かに聞く前に、まだシステムへのアクセスが必要かどうかをユーザー本人に質問することは可能です。</p>
<p>今回採用した方法はまさにこれで、まずは従業員に以下のように聞いてみます。</p>
<blockquote>
<p>これらシステムすべて対してまだアクセス権は必要ですか?はい/いいえ/分からない</p>
</blockquote>
<p>回答が集まったら(または期限が過ぎたら)、彼らのマネージャーに質問します。</p>
<blockquote>
<p>各メンバーの役割と責任を考慮した上で彼らの回答をレビューし、それらアクセス権が適切かどうかを確認してください。</p>
</blockquote>
<p>今回はそこまで実施しなかったものの、3段階目のレビューとしてシステムオーナーへの質問も考えました。</p>
<blockquote>
<p>これらのチームはあなたの管理するシステムを使用しています。このシステムの用途を考えると、彼らがアクセスすることに問題はありませんか?</p>
</blockquote>
<p>この方法の場合、アクセス権を維持するか取り消すかの判断をアクセス権を実際に使用する人に委ねることになります。また、権限の確認を全従業員に割り振ることができるという利点もあります。残念ながらマネージャーにはメンバーがアクセスの必要性を主張しているアプリケーションをすべて確認してもらわなければなりませんが、求められているのは確認だけなので検査は比較的早く終わるはずです。妥当かどうかを簡単に確認するだけなら、通常ひとり5分もかかりません。場合によってはもう少しかかるかもしれませんが、DM(ダイレクトメッセージ)で確認することが可能です。</p>
<p>このプロセスを通して私たちは「Security TeamのAさんが給与システムのアクセス権を持っている」といった、本来であれば例外的なケースを発見したいと思っていました。もし本人が「必要だ」と言ったとしても、少なくともマネージャーにその妥当性を確認してほしいからです。</p>
<p>このプロセスを実施している間、「このアクセス権が付与されているなんて知らなかった」「そもそもこのサービスってなに?」といったコメントが数多く寄せられました。</p>
<p>Oktaの使われ方からして、今回選択した方法が完璧とはいえないことは分かっています。ですが、私たちはOktaでアプリケーションのアクセス権を付与しています。メルカリの場合、アプリケーション内で権限を付与することはほとんどありません。そしてこれはシステムオーナーに委ねられています。このようなやり方のため、そもそも最初からアクセスできる対象を制限することでかなりの違いが出てきます。さらに追加のクリーンアップは後からでもできます。その時に、いくつか重要なシステムを優先的に対応することも可能です。</p>
<h1>プロセスの実施方法</h1>
<p>さて、ここまでに「なぜ検査を行うのか <code>Why</code>」、「どのシステムを対象とするか<code>What</code>、「誰が回答し、誰がレビューするのか<code>Who</code>」が明確になりました。次は、「どうやって全員に質問し、回答を集めるのか<code>How</code>」です。</p>
<p>スプレッドシートでの検査(現実的ではありません)</p>
<ul>
<li>すべてのユーザー/グループ/アプリを含めると20万行になってしまいGoogleスプレッドシートには収まらないし、全員に開いてレビューするようお願いするのもばかげています。シートの完全性を確保することは可能ではあるものの、さらに多くの作業が必要となります。</li>
</ul>
<p>Webベースでの検査(現時点では見送る)</p>
<ul>
<li>うまくいくとは思いつつも、少なくともこの段階では検査を実施するためのウェブページは作らないことにしました。</li>
</ul>
<p>OktaのIdentity Governance Access Certificationキャンペーン機能(我々には有効ではありません)</p>
<ul>
<li>Oktaには<a href="https://help.okta.com/en-us/content/topics/identity-governance/access-certification/iga-ac-review-campaign.htm" title="Identity governance access certification">Identity governance access certification</a>という機能があります。Oktaが将来的にアクセスレビューとして使用されることを承知の上で一から設定されているのであれば、この機能を利用する方法はうまくいくでしょう。ここではオーナーは特定グループに割り当てられ、そのグループはアプリケーションに割り当てられます。キャンペーンを実施している間、グループオーナーはグループのメンバーがアクセス権を所有すべきかを確認するよう依頼されます。この方法は、グループオーナーがそのユーザーがアクセス権を持つべきかを判断できることを前提としています。グループは多くの場合チームを意味するため、メンバーの管理はマネージャーに委ねられるでしょう。そのチームグループは、アプリケーションオーナーから必要なアプリケーションに割り当ててもらう必要があります。しかし、Oktaには(現時点では)アプリケーションオーナーを定義する属性がありません。</li>
<li>通常のケースはこの方法で問題ないのですが、例外ケースの場合は他のグループを通じて管理する必要があり、その例外を理解できる人に割り当てる必要があります。</li>
<li>私たちの今の状態で考えると、グループ=チームではなく、通常(必ずではないものの)アプリケーションへのアクセスを許可するために使われているので、この方法は有効な策ではありませんでした。この状態は、これらグループにオーナーが割り当てられていないという意味でもあります。システムオーナーに</li>
</ul>
<p>Slack + バックエンド + Neo4j(選んだ方法)</p>
<ul>
<li>私たちはユーザーインターフェイスとしてSlackを、バックエンドデータベースとしてNeo4jを使うことに決めました。バックエンドにグラフデータベースを使うことで、チーム、メンバー、そのマネージャーに対する問い合わせと、彼らがどのグループを通じてどのようなアクセス権を持っているかを(比較的)簡単に照会できるからです。とりあえず今回は、アプリケーション内で付与されたアクセスのレビューは対象外にすることも決めました。</li>
</ul>
<p>このブログ記事の残り部分では、私たちが実施したプロセスを説明します。</p>
<p>検査を進めるためには、いくつかのステップを経る必要がありました。</p>
<ol>
<li>組織構造を復元する</li>
<li>Okta上のアプリケーション、グループ、ユーザー、すべてのメンバーシップとそれらの関係を復元する</li>
<li>組織とアクセスを記したグラフを作成する</li>
<li>各チームと従業員向け:Slackのフォームを作成し、どのアクセスがまだ必要かの確認を依頼する</li>
<li>ユーザーからの回答を集める</li>
<li>各マネージャー向け:Slackのフォームを作成し、メンバーが必要だと申告しているアプリに同意するかどうか質問する。ユーザーからの応答がない場合はマネージャーに決めてもらう</li>
<li>マネージャーからの回答を集める</li>
<li>妥当性の確認:明らかにおかしな回答がないかレビューする</li>
<li>Okta APIを通じてアプリケーションへのアクセスやグループメンバーシップを取り消す</li>
<li>変更を記録する</li>
</ol>
<p>ステップ8を除く上記のすべての操作はコードを通じて行います。そうすればこのプロセスを確実に再現することができるからです。</p>
<h1>組織構造とアクセス権をデータベースで表す</h1>
<p>Oktaのユーザーは、チームとマネージャーを示す属性を持つように設定することができますが、いくつか実際の組織構造との相違点が見られたため、最終的には別のソースから完全な構造を抽出し、その構造をOktaのユーザーとリンクさせなければなりませんでした。組織構造をグラフ化することで、Okta上の関係ではなく実際の組織構造を明らかにすることができたので非常に便利でした。</p>
<p>その後、Oktaから特定の組織単位や チームにおけるアプリ、グループ、ユーザー間の関係を抽出することができました。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/01/c785a1db-okta_access_review_image1.png" alt="image 1" /><br />
<em>イメージ1:Oktaと人事データをNeo4jグラフデータベースに統合し、Mermaid.jsで可視化</em></p>
<h2>スキーマ:組織、チーム、マネージャー、メンバー、グループ、アプリケーション間の関係性</h2>
<p>オーバーエンジニアリングを防ぐために、少なくとも最初のうちはいくつかショートカットを採用し、各従業員の単位としてOktaUserノードを使用することにしました。現実はもっと複雑な権限が付与された対象を特定する必要があるのですが、この段階ではこれで十分でした。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/01/58a6f997-okta_access_review_image2.png" alt="Image 2" /><br />
<em>イメージ2:Mermaid.jsを使って視覚化したデータベース内における関係性の概略図</em></p>
<p>Neo4jデータベースへの書き込みが終わると、組織、チーム、各チームが使用しているアプリケーションを照会できるようになりました。組織構造のグラフはこのような様子でした。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/01/a51ef125-okta_access_review_image3.png" alt="Image 3" /><br />
<em>イメージ3:Neo4jのウェブインターフェイスを使って作成したメルカリの組織構造図</em></p>
<p>以下のクエリは以下のような意味を表します:</p>
<ul>
<li>「Platform Security」チーム直下のメンバーで、有効なOktaアプリにアクセス権があるすべてのメンバーに対して:
<ul>
<li>マネージャーを取得する</li>
<li>直近90日間にこれらのアプリを使用したかどうかを取得する</li>
</ul>
</li>
<li>ユーザー・アプリ間の関係性のOrgノード、マネージャーノード、関係性のプロパティ、最終使用のプロパティ、およびアプリノードを返す</li>
</ul>
<p>これを元に再度、グループメンバーシップによるアプリへのアクセスを考慮します。</p>
<pre><code class="language-cypher">// 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</code></pre>
<p><em>Query1:Neo4j Cypherを使用して、特定のチームのアプリケーションとグループのアクセスリストを取得</em></p>
<h1>プロセスを開始する</h1>
<p>コントローラー(アプリ)はユーザーを特定するためにチームのリストを使用しています。チームの再帰的リストは、次のようなクエリでNeo4jデータベースから簡単に抽出できます。</p>
<pre><code class="language-cypher">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</code></pre>
<p><em>クエリ2:Neo4j CypherでSecurity & Privacyカテゴリの再帰的チーム階層を復元</em></p>
<p>ここからスコープ内のチームリストに基づいて、コントローラーから検査開始がマネージャーに通知されます。各チームメンバーにが作成され、SlackのDMで調査フォームが送信されます。</p>
<h2>メンバーに調査フォームを送信する</h2>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/01/1e5f3e5f-okta_access_review_image4.png" alt="Image 4" /><br />
<em>Image 4: Sequential flow chart detailing the member campaign process, illustrated with Mermaid.js.</em></p>
<p>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”.</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/01/dcd28006-okta_access_review_image5.png" alt="Image 5" /><br />
<em>イメージ4:メンバーのキャンペーンプロセスのフローチャート</em></p>
<h2>回答収集用のバックエンド</h2>
<p>調査フォームが送られたら、あとは回答を待つだけです。バックエンドで回答を受け取り、その回答に従ってNeo4jデータベースを更新する準備は整っています。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/01/6e83f51b-okta_access_review_image6.png" alt="Image 6" /><br />
<em>イメージ6:調査フォームからの回答を収集する手順のフローチャート</em></p>
<p>調査を実施している間、手動でマネージャーに進捗状況を送信し、未回答の場合はチームメンバーに確認してもらうよう依頼することができます。</p>
<h2>マネージャーによる回答のレビュー</h2>
<p>回答の回収が済んだら、未回答・未完了のメンバーがいたとしても、マネージャーにアクセスのレビューを依頼します。メンバーからの回答は一目瞭然であり、チームに関係するアプリケーションもよく知られているはずなのでこのステップは通常すぐに終わります。</p>
<p>マネージャーが対応しない場合は、その上司に進捗がないことを報告することができます。</p>
<p>マネージャーのレビューの流れは以下です:</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/01/9fd80af1-okta_access_review_image7.png" alt="Image 7" /><br />
<em>イメージ7:Mermaid.jsを使用して、マネージャーのレビュー作業のシーケンス図</em></p>
<p>マネージャーに送信されるフォームはユーザーに送られるフォームと似ていますが、必要だと回答されたアプリだけが表示されています。マネージャーはメンバーの回答を確認し、メンバーによりアクセスが必要だと判断されたアクセス権に対して、保持か削除を選択することができます。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/01/36241df7-okta_access_review_image8.png" alt="Image 8" /><br />
<em>イメージ8:Slack内のマネージャーレビューフォームのインターフェイスの例</em></p>
<h2>不要なアクセスのクリーンアップ</h2>
<p>この段階では、メンバーからの回答が集まり、マネージャーからの確認も回収済みです。個別のアクセスレビューではなく、チーム単位でのアクセス権付与に同意するかの確認をシステムオーナーに依頼することもできましたが、これは今後の検査に回すことにしました。</p>
<p>Okta APIによるアクセス取り消しフローは比較的シンプルです。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/01/4a177144-okta_acecss_review_image9.png" alt="Image 9" /><br />
<em>イメージ9:アクセス取り消しメカニズムに関するステップのフローチャート</em></p>
<h1>まとめ</h1>
<p>今回のプロジェクトを通して、従業員やマネージャーが正直に回答してくれると信じることで、従業員がどのアクセス権を持ち、どのアクセス権を必要としているかをレビューすることができました。多くの規格、フレームワーク、規制、ベストプラクティスでは、企業が定期的にこういったレビューを実施することが求められています。しかし、得てして複雑な組織構造や歴史的背景がからみあい、こういったレビューはすぐに手に負えなくなるものです。そこで、従業員とアプリケーション間の複雑な関係性をグラフデータベースに移行し、まず従業員にアクセス権が必要かどうかを質問することで、会社の規模に応じて検査の規模を拡大することができました。また、今回の検査は、システム分類作業に長い時間をかけることなく実施することができました。Oktaに大きく依存しているからこそ、Oktaに焦点を当てることで、大半のシステムをカバーすることができたのです。</p>
<p>このフローにもまだまだ改良の余地はあり、他のシステムへの拡張も可能だと考えています。アクセス許可のルールと確認をより厳格にし、プロビジョニングプロセスに組み込むこともできるかもしれません。</p>
<p>一方、今回私たちはすでに、アクセス中断のリスクを負うことなく、不要となった膨大な量のアクセス権を削除することができました。これは、アクセス権を取り消すか否かを判断する際に、こちらで決めたルールを使用するのではなく、従業員とマネージャーの回答に基づいて行ったからです。</p>
- LLMを活用した大規模商品カテゴリ分類への取り組みhttps://engineering.mercari.com/blog/entry/20240411-large-scale-item-categoraization-using-llm/https://engineering.mercari.com/blog/entry/20240411-large-scale-item-categoraization-using-llm/<p>こんにちは、メルカリの生成AIチームで ML Engineer をしている ML_Bear です。 以前の記事[1]では商品レコメンド改善のお話をさせていただきましたが、今回は、大規模言語モデル (LLM) やその周辺技 […]</p>
Thu, 11 Apr 2024 17:04:30 GMT<p>こんにちは、メルカリの生成AIチームで ML Engineer をしている <a href="https://twitter.com/MLBear2">ML_Bear</a> です。</p>
<p>以前の記事[1]では商品レコメンド改善のお話をさせていただきましたが、今回は、大規模言語モデル (LLM) やその周辺技術を活用して30億を超える商品のカテゴリ分類を行なった事例を紹介します。</p>
<p>ChatGPTの登場によりLLMブームに火がついたということもあり、LLMは会話を通じて利用するものだと認識されている方が多いと思いますが、LLMが有する高い思考能力はさまざまなタスクを解決するためのツールとしても非常に有用です。他方、その処理速度の遅さや費用は大規模なプロジェクトでの活用にあたっての障壁となり得ます。</p>
<p>本記事では、こうしたLLMの課題を克服するためにさまざまな工夫を施し、LLM及びその周辺技術のポテンシャルを最大限に引き出して大規模商品データのカテゴリ分類問題を解決した取り組みについて説明します。</p>
<h2>課題</h2>
<p>まずは今回のプロジェクトの背景と技術的な課題を簡単に説明します。</p>
<p>メルカリは2024年にカテゴリリニューアルを行い、階層構造を見直すとともに商品カテゴリの数を大幅に増やしました。しかしカテゴリ数やその階層構造がかわるということは、それに紐づく商品のデータも変更する必要があります。</p>
<p>通常であれば商品のカテゴリ分類は機械学習モデルやルールベースモデルを利用します。しかし今回のケースでは過去の商品に対する「新しいカテゴリ階層での正解カテゴリ」がわからないため、機械学習を使用した分類器を作成することができませんでした。また、カテゴリ数が非常に多いため、ルールベースモデルの構築も困難でした。そこで、この課題に対してLLMを活用できないかというアイディアが出てきました。</p>
<h2>解決策: LLMとkNNによる2ステージ構成の予測アルゴリズム</h2>
<p>結論としては以下のような2ステージ構成のアルゴリズムを組むことで今回の課題に対応しました。</p>
<ol>
<li>ChatGPT 3.5 turbo (OpenAI API[2])で過去商品の一部の正解カテゴリを予測する</li>
<li>1.を学習データとして過去商品のカテゴリ予測モデルを作成</li>
</ol>
<p>全てをChatGPTで予測できれば楽だったのですが、メルカリの過去商品は30億商品を超えるため[3]、全てをChatGPTで予測するのは処理時間的にもAPIコスト的にも不可能でした。そのため、紆余曲折を経てこのような2ステージのモデル構成としました。(すべての商品をChatGPT 3.5 turboで分類するとコスト見積もりは約100万ドル、処理時間見積もりは1.9年という非現実的な数字でした)</p>
<p>以下にモデルの内容を簡単に説明します。詳細については「工夫した点」で述べるため、一旦はシンプルな解説に留めます。</p>
<h3>1. ChatGPT 3.5 turbo (OpenAI API)で過去商品の一部の正解カテゴリを予測する</h3>
<p>まず、過去に出品された商品を数百万点サンプリングし、ChatGPT 3.5 turboにその商品の「新しいカテゴリ構成での正しいカテゴリ」を予測させました。 具体的には、各商品の商品名や商品説明文、元のカテゴリ名をもとに新しいカテゴリの候補を10個程度作成し、その候補の中から正解を答えさせました。</p>
<h3>2. 1.を学習データとして過去商品のカテゴリ予測モデルを作成</h3>
<p>次に、1. で作ったデータセットを正解データとして、シンプルな kNN モデル[4] を作成しました。</p>
<p>具体的には、まず、1.で正解カテゴリを予測した商品のEmbeddingと正解カテゴリをベクトルデータベースに保存しておきます。その後、予測したい商品のEmbeddingを元に、ベクトルデータベースから類似商品をX個抽出し、そのX個の商品の最頻カテゴリを正解カテゴリとしました。</p>
<p>Embeddingは各商品の商品名、商品説明文、メタデータ、元のカテゴリ名などを連結した文字列をもとに計算しました。より複雑な機械学習モデルも検討しましたが、シンプルなモデルで及第点の性能が出たためシンプルなモデルを採用しました。</p>
<h2>工夫した点</h2>
<p>さて、ここからは今回のプロジェクトで工夫した点をご紹介します。以下のような点を工夫したので、ひとつづつ説明します。</p>
<ul>
<li>OSSのEmbeddingモデルの活用</li>
<li>Sentence Transformers ライブラリによるMulti-GPUの活用</li>
<li>Voyager Vector DBによるCPU上での高速な近傍検索</li>
<li>max_tokensとCoTの活用によるLLM予測の高速化</li>
<li>Numba・cuDFの活用</li>
</ul>
<h3>1. OSSのEmbeddingモデルの活用</h3>
<p>第2ステージのモデル (kNN) では商品のEmbeddingの計算が必要でした。自前でニューラルネットワークを組むことも可能でしたが、OpenAI Embeddings API (<code>text-embedding-ada-002</code>) [5]で十分な精度が出ることが確認できたので、当初はこのAPIを利用する方針としていました。</p>
<p>しかし、試算してみたところ、すべての商品にOpenAI Embeddings APIを利用するのは処理時間的にもコスト的にも少し厳しいということがすぐにわかりました。</p>
<p>そんな中、MTEB[6]やJapaneseEmbeddingEval[7]を眺めていると英語以外の言語でもOpenAI Embeddings APIに匹敵するOSSのモデルが多数あることに気づきました。自分たちで評価用データセットを作って試してみたところ、OpenAI Embeddings API同等の精度が出たためOSSのモデルを利用することにしました。</p>
<p>私たちがこのプロジェクトを行なっていた2023年10月時点のデータでは、以下のモデルが高い精度を示しており、最終的には計算コストと精度のバランスを鑑み intfloat/multilingual-e5-base を利用しました。(MTEBのランキングは常時入れ替わっているため、2024年4月現在はもっと強いモデルがあると思います)</p>
<ul>
<li>intfloat/multilingual-e5-large [8]</li>
<li>intfloat/multilingual-e5-base [9]</li>
<li>intfloat/multilingual-e5-small [10]</li>
<li>cl-nagoya/sup-simcse-ja-large [11]</li>
</ul>
<p>このように、OSSでも非常に高性能なEmbeddingモデルが存在しているため、Embeddingを利用するプロジェクトを行う場合は、シンプルな問題を作成して、OSSでも十分な性能を持つモデルがあるかどうかを確認してみることをお勧めします。</p>
<h3>2. Sentence Transformers ライブラリによるMulti-GPUの活用</h3>
<p>OSSモデルを利用することで OpenAI Embeddings APIに比べて飛躍的に処理速度が上がったものの、数十億商品を処理するにはもう少し改善が必要でした。</p>
<p>A100などの強力なGPUを利用できれば話が早かったのですが、世界的なGPU枯渇の影響を受けてか、プロジェクト実施時の2023年11-12月時点では強いGPUを掴むことはなかなか困難でした。(現在もあまり状況は変わっていないかと思います)</p>
<p>そのため、V100やL4などのGPUを複数台並列で利用して対応することにしました。幸いなことに、Sentence-Transformers[12]ライブラリを利用すると以下のようなシンプルなコードで複数台のGPUを簡単に並列化できたため、非常に助かりました。</p>
<pre><code class="language-python">from sentence_transformers import SentenceTransformer
def embed_multi_process(sentences):
if 'intfloat' in self.model_name:
sentences = ["query: " + b for b in sentences]
model = SentenceTransformer(model_name)
pool = model.start_multi_process_pool()
embeddings = model.encode_multi_process(sentences, pool)
model.stop_multi_process_pool(pool)</code></pre>
<p>強力なGPUを大量に使えれば理想的ですが、それが難しい状況でも工夫次第で処理を高速化することができます。Sentence-Transformersのようなライブラリを活用して、限られたリソースを最大限に活用することが重要だと感じました。</p>
<h3>3. Voyager Vector DBによるCPU上での高速な近傍検索</h3>
<p>kNNを利用する際にはベクトルデータベースが必要でした。サンプリングしたとはいえ数百万商品の学習データになったため、GPUのメモリに載らない状況でした。A100 80GBなどの大きなメモリを持つGPUを使えば載ったかもしれませんが、前述の通り強力なGPUは確保が困難だったので試すことすらできませんでした。</p>
<p>そんな折、Spotify社製のVoyager[13]がCPUでも高速に動作すると聞いたので試してみたところ、実用に足る速度を簡単に実現できました。Embedding計算に比べると近傍探索の時間はそれほど影響が大きくなかったため厳密に他のプロダクトと比較していませんが、十分な速度を出すことができていました。</p>
<p>Voyagerにはメタデータ管理機能がなかったので自分たちでクライアントを書く必要がありましたが、それでも全体的には良い選択だったと思っています。</p>
<h3>4. max_tokensとCoTの活用によるLLM予測の高速化</h3>
<p>今回のプロジェクトでは ChatGPT 4 はコスト面から利用できず、ChatGPT 3.5 turboを使わざるを得ませんでした。ChatGPT 3.5 turboはコストの割に賢いとは思いますが、精度には少し不安がありました。そのため、Chain of Thoughts[14]を利用して説明を生成させることで精度向上を図りました。</p>
<p>皆さまもご存知かと思いますが、ChatGPTに説明を行わせるとずっと喋り続けることもあり、処理時間が問題となりました。そこで、<code>max_tokens</code>パラメータを利用して長い話を途中で打ち切ることで処理時間の短縮に努めました。</p>
<p>回答を打ち切ると(Function Callingの) JSONが壊れるので、LangChain[15]のllm.stream()を利用したり、もしくは自分でJSONを復元してパースする必要があり少し手間がかかります。厳密な比較はしていないものの、この手法によって処理時間短縮と精度向上の良いバランスを取れたと感じています。</p>
<p>以下がLangChainの<code>llm.stream()</code>を利用した場合のサンプルコードです。</p>
<pre><code class="language-python">from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from typing import Optional
from langchain_core.pydantic_v1 import BaseModel, Field
class ItemCategory(BaseModel):
item_category_id: int = Field(None, description="商品説明から予測したカテゴリID")
reason: Optional[str] = Field(None, description="このカテゴリIDを選択した理由を詳しく説明してください")
system_prompt = """
与えられる商品情報を元に、商品のカテゴリを予測してください。
商品のカテゴリは候補から選んでください。選んだ理由も説明してください。
"""
item_info = " (商品データと新カテゴリ候補などを入れる) "
llm = ChatOpenAI(
model_name="gpt-3.5-turbo",
max_tokens=25,
)
structured_llm = llm.with_structured_output(ItemCategory)
prompt = ChatPromptTemplate.from_messages(
[
("system", system_prompt),
("human", "{item_info}"),
]
)
chain = prompt | structured_llm
# streamingの最後の要素だけ取り出す
# - 通常、max_tokensで回答を打ち切るとjsonが壊れてパースの処理が必要
# - langchainのstreamで実行すると常にjsonを完成させてくれるため、
# max_tokensで回答を打ち切ってもjsonをパースする必要がない
for res in chain.stream({"item_info": item_info}):
pass
print(res.json(ensure_ascii=False)) # res: ItemCategory
# {"item_category_id": 1, "reason": "商品名に「ぬいぐるみ」が含まれ"}</code></pre>
<h3>5. Numba・cuDFの活用</h3>
<p>数十億商品を処理する際は些細な処理でも処理速度が気になるため、可能な限りすべての処理をcuDF[16]およびNumba[17]で高速化しました。</p>
<p>正直なところ Numba を書くのは苦手だったのですが、Pythonの素のコードをChatGPT 4に見せると書き直してくれるため、ほとんど自分で書く必要がなくコーディング工数を大幅に削減することができました。</p>
<h2>まとめ</h2>
<p>ChatGPTは会話形式で利用されることが多く注目を集めていますが、その高い思考能力を活用することで、これまで面倒だったり不可能であったタスクを簡単に解決できるようになります。私たちのプロジェクトでは、膨大な商品データを新しいカテゴリに短期間で分類し直すという面倒な課題を、ChatGPTを活用することで解決することができました。</p>
<p>また、OSSのEmbeddingモデルやマルチGPUの活用、高速な近傍検索が可能なベクトルデータベースの採用、ChatGPTでの予測の高速化、Numbaを用いた処理の高速化など、様々な工夫を行うことで、限られた時間とリソースの中でも最大限の成果を出すことができました。</p>
<p>今回の事例が、ChatGPTをはじめとする大規模言語モデルの可能性の一端を示し、皆様のプロジェクトの参考になれば幸いです。ぜひ、様々な場面でLLMを活用し、これまでは難しかった課題にチャレンジしてみてください。</p>
<h3>Refs</h3>
<ol>
<li><a href="https://engineering.mercari.com/blog/entry/20230612-cf-similar-item/">協調フィルタリングとベクトル検索エンジンを利用した商品推薦精度改善の試み</a></li>
<li><a href="https://platform.openai.com/docs/guides/text-generation">OpenAI API</a></li>
<li><a href="https://about.mercari.com/press/news/articles/20221128_threebillion/">フリマアプリ「メルカリ」累計出品数が30億品を突破</a></li>
<li><a href="https://en.wikipedia.org/wiki/K-nearest_neighbors_algorithm">k-nearest neighbors algorithm</a></li>
<li><a href="https://platform.openai.com/docs/guides/embeddings">OpenAI Embeddings API</a></li>
<li><a href="https://huggingface.co/spaces/mteb/leaderboard">Massive Text Embedding Benchmark (MTEB) Leaderboard</a></li>
<li><a href="https://github.com/oshizo/JapaneseEmbeddingEval">JapaneseEmbeddingEval</a></li>
<li><a href="https://huggingface.co/intfloat/multilingual-e5-large">intfloat/multilingual-e5-large</a></li>
<li><a href="https://huggingface.co/intfloat/multilingual-e5-base">intfloat/multilingual-e5-base</a></li>
<li><a href="https://huggingface.co/intfloat/multilingual-e5-small">intfloat/multilingual-e5-small</a></li>
<li><a href="https://huggingface.co/cl-nagoya/sup-simcse-ja-large">cl-nagoya/sup-simcse-ja-large</a></li>
<li><a href="https://www.sbert.net/">Sentence-Transformers</a></li>
<li><a href="https://github.com/spotify/voyager">Voyager</a></li>
<li><a href="https://arxiv.org/abs/2201.11903">Chain-of-Thought Prompting Elicits Reasoning in Large Language Models (Wei et al. 2022)</a></li>
<li><a href="https://github.com/langchain-ai/langchain">LangChain</a></li>
<li><a href="https://github.com/rapidsai/cudf">rapidsai/cudf</a></li>
<li><a href="https://numba.pydata.org/">Numba: A High Performance Python Compiler</a></li>
</ol>
- gRPC Federation: gRPC サービスのための Protocol Buffers を進化させるDSLhttps://engineering.mercari.com/blog/entry/20240401-4f426bd460/https://engineering.mercari.com/blog/entry/20240401-4f426bd460/<p>Merpay Engineering Productivity Team の goccy です。 gRPC Federation は、gRPC で通信する複数のサービスから得た結果を合成して返すようなサービスを簡単に作成 […]</p>
Tue, 02 Apr 2024 10:00:40 GMT<p>Merpay Engineering Productivity Team の <a href="https://twitter.com/goccy54">goccy</a> です。</p>
<p><a href="https://github.com/mercari/grpc-federation">gRPC Federation</a> は、gRPC で通信する複数のサービスから得た結果を合成して返すようなサービスを簡単に作成するための仕組みです。DSL ( Domain Specific Language ) を Protocol Buffers 上で記述することで利用します。まずは、<a href="https://www.apollographql.com/docs/federation/">GraphQL(Apollo) Federation</a> の gRPC 用のものだと考えるとわかりやすいと思います。2023年8月に OSS として公開し、先日 <a href="https://github.com/orgs/mercari/projects/1">Public Roadmap</a> を公開しました。2024/6月末を目標に Version 1.0 ( GA版 ) をリリースする予定です。また、最近は Protocol Buffers のエコシステムに参加しました。<a href="https://github.com/protocolbuffers/protobuf/blob/main/docs/options.md#:~:text=Extensions%3A%201186-,gRPC%20Federation,-Website%3A%20https">Protobuf Global Extension Registry への登録</a> や <a href="https://buf.build/mercari/grpc-federation">Buf Schema Registry への登録</a> 、<a href="https://buf.build/community/mercari-grpc-federation">Buf Plugin のサポート</a> が終わり、既存のエコシステムに従って gRPC Federation を利用できます。</p>
<p>本稿では、Version 1.0 を目前に控えた gRPC Federation をどのような思想のもとで設計したかを説明し、現在の gRPC Federation の表現力やプラグインシステム、周辺ツールなどの機能について触れ、今後の予定を紹介します。<a href="https://engineering.mercari.com/blog/entry/20231023-mmtf2023-day3-9/">2023年8月の Merpay & Mercoin Tech Fest で紹介したもの</a>から多くのアップデートがあります。ぜひ、新しいアーキテクチャを考える際の材料にしてください</p>
<h2>設計方針</h2>
<h3>Protocol Buffers を進化させる</h3>
<p>gRPC Federation は DSL を Protocol Buffers 上に記述することで利用します。本項では、私たちがこの選択を選んだ理由を説明します。</p>
<p>従来、Protocol Buffers は主にAPIやデータ構造を定義する設計用途で利用されてきました。コード生成と組み合わせることで、設計に対応した実装を生成でき、設計と実装を乖離させることなく保守・運用できることが強みです。さらに、プラグインの仕組みとそれを利用したツールによって、Protocol Buffers 上で定義されたAPIやデータ構造に対してカスタムオプションを利用して付加情報を与えることができ、これにより多様な自動生成が可能になっています。</p>
<p>gRPC Federation はこの点に着目し、gRPC サービスを動作させるために必要十分な実装を DSL として Protocol Buffers 上で記述できるようにしました。これによって、Protocol Buffers は自身のもつ情報だけで gRPC サービスを構築できる言語へと進化します。</p>
<p>DSL を Protocol Buffers 上で記述すべきか、別の専用のファイルで記述すべきかは議論を重ねました。DSL を専用のファイルで記述する場合、言語のシンタックスを自由に調整でき、書き味を向上させやすいメリットがあります。しかしその反面、独自の言語を利用する場合は Parser の実装が必要になり、ソフトウェアの複雑度が飛躍的に増加する懸念や、専用のファイルをどのように管理すべきか考える必要があります。Protocol Buffers と分離することで、設計と実装を乖離させることなく保守・運用できるという恩恵を受けづらくなるともいえます。</p>
<p>また、開発者が普段慣れ親しんだ汎用プログラミング言語でコードを書くことに比べて、gRPC Federation のような DSL を利用する効果とは何かについても考えました。<br />
DSL を利用することで必要最小限の記述でやりたいことを表現できるという側面はあります。ですが、DSL 自体に学習コストがあるため、慣れ親しんだ言語で書いた方が効率よく開発できそうな気がします。また、定型化できる部分をライブラリなどで提供すれば、より少ない記述で実装することもできそうです。<br />
私は、DSL のメリットは「多様な表現ができない」こと自体にあると考えています。DSL を利用する以上、汎用プログラミング言語のように自由にコードが書けるわけではありません。逆を言えば、DSL を利用して制約のある中で生成するコードはすべて予測可能で、知らないミドルウェアやサービスにアクセスしたり、ファイルシステムにアクセスするようなことはありません。これは DSL を利用して作成されたサービスのビルドやデプロイを管理する立場からすると重要な意味を持ちます。例えばビルド時に特別な依存がないことが保証されている場合、より高速にビルドしたりビルドプロセスを自動化したりといった手段が選択できます。同様にデプロイに関しても、アプリケーションが動作するために必要十分な環境を用意しやすい、動作環境をセキュアに保ちやすいといった側面があります。</p>
<p>こうした理由から、私たちは Protocol Buffers 上で DSL を書く方法を選択しました。シンタックスの融通が効かないデメリットを差し置いても、すでに Protocol Buffers 上で定義されている API やデータ構造をそのまま Protocol Buffers 上で参照できるメリットは大きいと考えています。また、gRPC Federation の利用過程でサービス間の依存関係が明示されることで、サービスの循環参照の有無や、問題発生時の影響範囲の特定、APIレベルでの実行コストの計算といった様々な解析を行うことが Protocol Buffers だけでできるようになります。</p>
<h3>DSL の限界とプラグインシステム</h3>
<p>gRPC Federation を作る上で、「DSL でどこまで表現できれば十分か」を考えることが一番難しい点でした。様々な機能をサポートしていく過程で DSL の表現力は向上していきますが、どこまでいっても DSL では実現不可能なロジックは存在します。また、DSL で表現できる範囲だったとしても、再実装せずに、すでにある3rd party製のライブラリを利用したい場合も考えられます。そこで私たちは、DSL には限界があることを理解した上で、Protocol Buffers 上で最低限記述すべき内容を決め、それ以外は DSL の外で実装する選択ができるようにしています。</p>
<p>Protocol Buffers 上で最低限記述すべき内容は「gRPC メソッド呼び出しの記述」としました。gRPC Federation の機能を簡潔に書くならば、「gRPC メソッドを呼び出す」ことと「メソッド呼び出しの結果を加工する」 ことを Protocol Buffers 上で書くことです。このとき、「どのgRPC メソッドを呼び出しているか」が Protocol Buffers 上に書かれなければ、Protocol Buffers を見ただけではどのサービス(のどのメソッド)に依存しているのかわからなくなってしまいます。私たちは経験上、マイクロサービス開発においてサービスの依存関係を把握することがとても重要であることを知っています。そのため、最低限「gRPC メソッド呼び出しの記述」は Protocol Buffers 上で行い、Protocol Buffers を解析するだけでサービス間の依存関係を把握できるようにしています(下図)。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/04/df0967cb-deps-e1711943919100.png" alt="deps" /></p>
<p>DSLの外で実装する手段として、いくつかの方法を用意しています。まず、gRPC Federation では DSL で表現できない部分だけを Go 言語によって実装することができます。しかし Go で実装する部分が多くなると Protocol Buffers と Go で実装が分離し、あまり嬉しくありません。そこで、もうひとつの選択肢としてプラグインの仕組みを提供しています。Go で書く場合と違う部分は、DSL で式の評価に利用している <a href="https://github.com/google/cel-spec">CEL( Common Expression Language )</a> の API を拡張できる点です。この仕組みを利用することで、Protocol Buffers 上で独自の API を使った表現が記述でき、Go で書く場合に比べて Protocol Buffers 上に実装を集中させやすくなります。また、複数の Protocol Buffers ファイルから共通の処理を利用したい場合にも有効です。</p>
<h2>gRPC Federation の活用場面</h2>
<p>gRPC Federation を利用することでサービス間の依存関係が明確になり、Protocol Buffers 上で把握できる情報を増やすことが可能です。また、gRPC Federation によって生成されたコードを利用することで、サービス開発における定型化された作業に割く時間を大きく減らし、ビジネスロジックの実装に集中できるようになります。</p>
<p>そのため、複数のマイクロサービスの結果を合成して返すことが主な責務である BFF ( Backends For Frontends ) や Public API のような toB 向けのサービスは gRPC Federation を採用する例として最も適していますが、通常のマイクロサービス開発でも十分に利用できると考えています。 </p>
<h2>gRPC Federation がもつ表現力</h2>
<p>次に、現状の gRPC Federation の表現力について、重要な機能をいくつか簡単に紹介します。</p>
<p>gRPC Federation では service / message / field など Protocol Buffers上の各要素に対して専用のオプションを用意しています。簡単な例を利用した説明は<a href="https://github.com/mercari/grpc-federation/blob/main/docs/getting_started.md#getting-started">こちらに記載しました</a>。<br />
本稿では、長くなりすぎてしまうので基本的な使い方については省略しますが、各項目の例を見ていただければ、なんとなく何ができるのか理解していただけると思います。</p>
<p><a href="https://github.com/mercari/grpc-federation/blob/main/docs/references.md#grpc-federation-feature-references">公式リファレンスはこちらです</a>。</p>
<h3>変数定義と式の評価</h3>
<p>gRPC Federation の開発を進めていくにあたって、変数や式の評価を行う仕組みが必要になりました。式の評価には、<a href="https://kubernetes.io/docs/reference/using-api/cel">Kubernetes の Custom Resource Definition でも利用されるようになった</a> 、Common Expression Language (CEL) を採用しました。<a href="https://github.com/google/cel-spec/blob/master/doc/langdef.md#language-definition">こちらに言語仕様がとまっています</a>。<br />
CEL は式を評価することに特化した言語で、小さくかつ洗練された仕様と豊富な拡張性をもっています。四則演算や論理演算、三項演算から関数、マクロまで様々な機能がある他、gRPC Federation では独自に CEL の機能を拡張し、例えば <code>google.protobuf.Timestamp</code> に対して <a href="https://github.com/mercari/grpc-federation/blob/main/docs/cel/time.md">Go の time ライブラリの機能を適応</a>したり、<a href="https://github.com/mercari/grpc-federation/blob/main/docs/cel/list.md"><code>reduce</code> や <code>first</code> といったマクロ</a>を使用できるようにしています。CEL は Protocol Buffers と親和性高く設計されており、gRPC Federation のように Protocol Buffers 上の定義を CEL の中で利用したい場合に適しています。ですが、CEL は変数の定義ができないため、gRPC Federation の仕様として 「CEL の評価結果を変数に代入できる機能」と「定義済みの変数をCELの評価式の中で参照できる機能」を追加しました。</p>
<p>次のように、 <code>def</code> キーワードを利用して式を評価した結果に名前を付けることで変数を定義できます。<code>grpc.federation.message</code> option で定義された変数は <code>grpc.federation.field</code> option で参照することができ、次のように参照した変数の値をそのままフィールドに代入することができます。</p>
<pre><code class="language-proto">message M {
option (grpc.federation.message) = {
def [
{
name: "t"
// 2024/4/01 00:00:00+0
by: "grpc.federation.time.date(2024, 4, 1, 0, 0, 0, 0, grpc.federation.time.UTC())"
},
{ name: "sum" by: "[2, 3, 4].reduce(accum, cur, accum + cur, 1)" }, // sum = 10
{ name: "v" by: "[1, 2, 3, 4].first(cur, cur % 2 == 0)" } // v = 2
]
};
google.protobuf.Timestamp time = 1 [(grpc.federation.field).by = "t"];
int64 sum = 2 [(grpc.federation.field).by = "sum"];
int64 first = 3 [(grpc.federation.field).by = "v"];
}</code></pre>
<p>このように、message option の中でフィールドに割り当てる値を作り、 field option でその値を参照して代入するというのが基本の使い方です。<br />
現在 gRPC Federation で利用可能な CEL API は <a href="https://github.com/mercari/grpc-federation/blob/main/docs/cel.md">こちらにまとめました</a>。</p>
<h3>gRPC メソッドの呼び出し</h3>
<p>必ず Protocol Buffers 上に記述してもらいたい、gRPC メソッドの呼び出し方法について説明します。<a href="https://github.com/mercari/grpc-federation/blob/main/docs/references.md#grpcfederationmessagedefcall">リファレンスはこちらです</a>。</p>
<p>使い方の前に、呼び出し対象のメソッドが次のように定義されているとします。<br />
メソッドへの FQDN は <code>foopkg.FooService/GetFoo</code> となり、メソッドを呼び出すためには <code>GetFooRequest</code> メッセージの内容を作る必要があります。返り値は <code>GetFooResponse</code> です。</p>
<pre><code>package foopkg;
service FooService {
rpc GetFoo(GetFooRequest) returns (GetFooResponse);
}
message GetFooRequest {
FooParam param = 1;
}
message FooParam {
string x = 1;
}
message GetFooResponse {
Foo foo = 1;
}
message Foo {
string bar = 1;
}</code></pre>
<p>このとき、メソッドを呼び出すには、次のように <code>call{}</code> を記述します。</p>
<pre><code class="language-proto">message M {
option (grpc.federation.message) = {
def {
name: "res"
call {
method: "foopkg.FooService/GetFoo"
request { field: "param" by: "foopkg.FooParam{x: 1}" }
}
}
def { name: "f" by: "res.foo" } // f = foopkg.Foo{}
};
string result = 1 [(grpc.federation.field).by = "f.bar"]; // assign foopkg.Foo.bar field to result field.
}</code></pre>
<p><code>method</code> に呼び出したいメソッドの FQDN を記述し、<code>request</code> で <code>GetFooRequest</code> メッセージの各フィールドの値を指定します。ここでは CEL を使って <code>foopkg.FooParam</code> の内容を作成しました。 メソッドの呼び出し結果は <code>res</code> 変数に格納します。<br />
次の変数定義で <code>res</code> 変数の <code>foo</code> フィールドへアクセスしているので、 <code>foopkg.Foo</code> の値が変数 <code>f</code> に代入されます。最後に、フィールドバインディング時に変数 <code>f</code> を参照し、<code>bar</code> フィールドの値を取り出して <code>result</code> フィールドに代入しています。</p>
<h3>メッセージへの依存</h3>
<p>メソッドを呼び出した結果を欲しい形に加工する上で重要になるのが、メッセージ間に依存関係を作る機能です。<a href="https://github.com/mercari/grpc-federation/blob/main/docs/references.md#grpcfederationmessagedefmessage">リファレンスはこちらです</a>。<br />
あるメッセージは別のメッセージに依存することができます。依存関係は gRPC Federation のオプションを利用して明示的に記述することができます。例えば、 次の例にある <code>M</code> というメッセージを構築することが目標である場合、<code>M</code> メッセージのフィールドに存在する <code>Dep</code> メッセージの値を作る必要があります。ここで、<code>Dep</code> メッセージが <code>GetFoo</code> メソッドの呼び出し結果の値を利用することで作れるとすると、次のように記述することができます。</p>
<pre><code class="language-proto">message M {
option (grpc.federation.message) = {
def {
name: "res"
call {
method: "foopkg.FooService/GetFoo"
request { field: "param" by: "foopkg.FooParam{x: 1}"
}
}
def {
name: "dep"
message { name: "Dep" args { name: "f" by: "res.foo" } }
}
};
Dep dep = 1 [(grpc.federation.field).by = "dep"];
}
message Dep {
string bar = 1 [(grpc.federation.field).by = "$.f.bar"];
}</code></pre>
<p><code>message{}</code> を利用することで他のメッセージの値を作ることができます。メッセージの値を作る際は <code>args{}</code> を利用して自由に依存先のメッセージに対して引数を渡すことができ、<code>name</code> で名前を指定することで、依存先のメッセージ側で <code>$.</code> というプレフィックスを付けて引数にアクセスすることができます。</p>
<p>この例では、 <code>res</code> 変数から取得した <code>foo</code> フィールドの値に対して、 <code>f</code> という名前の引数を作って <code>Dep</code> の値を作っています。<code>Dep</code> メッセージ側では、CEL の評価式の中で <code>$.f</code> と記述することで引数にアクセスしています。</p>
<h3>バリデーション</h3>
<p>サービスを実装する上で、メソッドを呼び出した結果に対するバリデーションは常に意識しなければいけません。バリデーションの結果、エラーを返す場合は gRPC の慣習に従ってエラーを作る必要もあります。Protocol Buffers でバリデーションと聞くと、<a href="https://github.com/bufbuild/protovalidate">protovalidate</a> が有名だと思います。これはリクエストパラメータのバリデーションに利用するものですが、 gRPC Federation の場合はリクエストに限らず、参照可能なあらゆる変数に対して行うことができます。また、gRPC エラーを返すために特化した機能も用意しています。<a href="https://github.com/mercari/grpc-federation/blob/main/docs/references.md#grpcfederationmessagedefvalidation">リファレンスはこちらです</a>。</p>
<p>例えば次の例のように、<code>GetFoo</code> メソッドを呼び出した結果が期待値かどうかを確認することが可能です。エラーは <a href="https://github.com/grpc/grpc/blob/master/src/proto/grpc/status/status.proto">google.rpc.Status</a> を作るようになっており、<a href="https://github.com/googleapis/googleapis/blob/master/google/rpc/error_details.proto">error_details.proto</a> で定義されているものがサポートされています。加えて、独自のメッセージを作ってエラーに含めることも可能です。</p>
<p>例えば Go 言語では、<a href="https://pkg.go.dev/google.golang.org/genproto/googleapis/rpc/errdetails">errdetails</a> パッケージを使って <a href="https://pkg.go.dev/google.golang.org/grpc/status#Status">grpc.Status</a> を作る処理に該当します。</p>
<pre><code class="language-proto">message M {
option (grpc.federation.message) = {
def {
name: "res"
call {
method: "foopkg.FooService/GetFoo"
request { field: "param" by: "foopkg.FooParam{x: 1}" }
}
}
def {
validation {
error {
if: "res.foo.bar != 'xxx'"
code: FAILED_PRECONDITION
message: "'unexpected foopkg.Foo.bar value'",
}
}
}
};
}</code></pre>
<p>ここで紹介した機能は全体のごくわずかです。gRPC Federation は他にも多くの機能が存在するので、お時間のある際にぜひ見てみてください。</p>
<h2>WebAssembly を利用したプラグインシステム</h2>
<p>gRPC Federation では、DSL 中に記述する CEL API や gRPC Federation がもつコード生成パイプラインを WebAssembly を利用して拡張することができます。プラグインを WebAssembly として実行することで、WebAssembly ランタイム側で制約を設けることができます。これにより、例えばネットワークやファイルシステムへのアクセスを禁止することで、プラグインによる予期しない動作を防止しています。</p>
<p>DSL からコードを生成する際に、Logger や gRPC Interceptor など、ドメイン固有の実装を同時に生成したい場合があります。そのような場合にコード生成パイプラインをプラグインによって拡張することで、gRPC Federation がもともとコード生成に使用している情報と全く同じものをプラグインで受け取り、自由にコード生成を行うことができるようになります。</p>
<p>Protocol Buffers からコード生成を行って gRPC サーバをビルドするまでの過程とプラグインの関係を図にすると次のようになります。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/04/00e4de01-plugin.png" alt="plugin" /></p>
<h2>周辺ツール</h2>
<p>DSL を提供する上で、周辺ツールの整備も重要だと考えています。今回は Protocol Buffers のプラグインとして動作するため、 <code>protoc</code> のプラグインを用意するのはもちろんですが、他にも専用の Linter や Language Server 、コード生成ツールを用意しています。今回はこの中から、Language Server と コード生成器について紹介します。</p>
<ul>
<li>protoc-gen-grpc-federation: protoc プラグイン</li>
<li>grpc-federation-linter: Linter</li>
<li>grpc-federation-language-server: Language Server</li>
<li>grpc-federation-generator: コード生成器</li>
</ul>
<h3>Language Server</h3>
<p>DSL を書いてもらう上で当初から Language Server の提供は必須だと考えており、 <a href="https://github.com/mercari/grpc-federation/tree/main/cmd/grpc-federation-language-server#grpc-federation-language-server">専用の Language Server</a> を提供しています。専用といっても、通常の Protocol Buffers の開発で最低限必要な Syntax Highlight や コードジャンプなどは実装済みなので、Protocol Buffers の Language Server としても利用することができます。</p>
<p>コードエディタによって Language Server の利用方法は様々ですが、VSCode では利用しやすいように、<a href="https://marketplace.visualstudio.com/items?itemName=Mercari.grpc-federation">すでに Extension を公開しています</a>。他の IDE 向けの対応も現在進めていますので、どうぞご期待ください。</p>
<p>Language Server によって Syntax Highlight された Protocol Buffers は次のようになります。文字列中の CEL の式などが適切にハイライトされているのが確認できると思います。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/03/c65de0fa-semantic_highlighting.png" alt="lsp" /></p>
<h3>コード生成器</h3>
<p>コード生成に関して、<a href="https://github.com/mercari/grpc-federation/blob/main/docs/installation.md#2-use-protoc-gen-grpc-federation">protoc を利用した方法</a> 以外に、<a href="https://github.com/mercari/grpc-federation/blob/main/docs/installation.md#1-use-buf-generate">Buf を利用する方法</a> や、<a href="https://github.com/mercari/grpc-federation/blob/main/docs/installation.md#3-use-grpc-federation-generator">gRPC Federation 独自のコード生成ツールによる方法</a> をサポートしています。</p>
<p>独自のツールを作った背景には、Protocol Buffers を編集した瞬間に gRPC サービスが立ち上がるような開発体験を提供したいという思いからでした。独自のツールには <code>-w</code> オプションを付けることで Protocol Buffers の変更を検知して即座にコンパイル、コード生成を実行する仕組みがあります。この機能と <a href="https://github.com/cosmtrek/air">Air</a> などのホットリローダを組み合わせることで、コード生成された側から Go のコンパイルを行う仕組みを作れるため、他に gRPC サービスを起動するために必要な情報をプラグインの形で外から与えさえすれば、Protocol Buffers を編集した瞬間に gRPC サービスが立ち上がる状態を作ることができます。<br />
個人的にはこれを <strong>Protocol Buffers Driven Development</strong> と呼んでおり、スキーマ駆動開発を促進できると考えています。図にすると以下のようになります。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/03/b1ff7c68-pdd.png" alt="pdd" /></p>
<h2>今後</h2>
<p>メルペイ社内では、 gRPC Federation を使ったサービスがそろそろ本番環境で稼働し始めようとしています。そこで、最終的な機能の精査を行い、6月末を目標に Version 1.0 ( GA版 ) を提供する予定です。 1.0 以降は、基本的に破壊的な変更を入れず後方互換性を保ち、どうしても変更したい場合は十分な変更期間をとるなど社外のユースケースを想定してメンテナンスしていくことを考えています。<br />
そのため、gRPC Federation の導入を考えるとてもいい機会だと考えています。導入のご相談は随時受け付けていますので、ぜひお気軽にご連絡ください。また、<a href="https://github.com/mercari/grpc-federation">OSS</a>に関しても積極的にコントリビューションを受け付けています。こちらもあわせてよろしくお願いします。</p>
- mercari.go #25 を開催しました #mercarigohttps://engineering.mercari.com/blog/entry/20240328-a7a826316e/https://engineering.mercari.com/blog/entry/20240328-a7a826316e/<p>はじめに こんにちは、mercari.go スタッフの hiroebe です。 3月21日にメルカリ主催の Go 勉強会 mercari.go #25 を YouTube でのオンライン配信にて開催しました。この記事では […]</p>
Fri, 29 Mar 2024 10:00:44 GMT<h2>はじめに</h2>
<p>こんにちは、mercari.go スタッフの hiroebe です。</p>
<p>3月21日にメルカリ主催の Go 勉強会 <a href="https://mercari.connpass.com/event/311847/">mercari.go #25</a> を YouTube でのオンライン配信にて開催しました。この記事では、当日の各発表を簡単に紹介します。動画もアップロードされていますので、こちらもぜひご覧ください。</p>
<p><iframe loading="lazy" width="560" height="315" src="https://www.youtube.com/embed/u6dekYm6kmk?si=j0QaelB8qBufsLWb" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe></p>
<h2>Learning TLS1.3 with Go</h2>
<p>1つめのセッションは @shu-yusa さんによる「Learning TLS1.3 with Go」です。</p>
<p>発表資料:<a href="https://speakerdeck.com/shuyusa/learning-tls1-dot-3-with-go-3966ee36-597e-4cd2-bd97-33b286fe45f8">Learning TLS1.3 with Go</a></p>
<p>TLS1.3 におけるハンドシェイクのプロセスについて、Go のコードを交えて説明しました。TLS1.3 では TLS1.2 から多くの変更が入っていて、ハンドシェイクの改善もそのうちの1つです。Go において TLS に関連する暗号技術は <a href="https://pkg.go.dev/crypto#section-directories">crypto/</a> 以下のパッケージで提供されていて、発表ではこれらのパッケージを用いたコード例が多く紹介されています。<br />
個人的に普段触れる機会の少ないパッケージも多く、とても興味深かったです。コードとともに理解することで、ハンドシェイクの各ステップにおける処理の流れがつかみやすくなっていると感じました。</p>
<h2>Exploring Go Runtime Metrics</h2>
<p>2つめのセッションは <a href="https://www.linkedin.com/in/chinming-huang">@Chin-Ming</a> さんと <a href="https://www.linkedin.com/in/mohitpokharna">@mohit</a> さんによる「Exploring Go Runtime Metrics」です。</p>
<p>発表資料:<a href="https://speakerdeck.com/mohitpokharna08/exploring-go-runtime-metrics">Exploring Go Runtime Metrics</a></p>
<p>Go の <a href="https://pkg.go.dev/runtime/metrics">runtime/metrics</a> パッケージについて、導入された背景や内部実装について紹介しました。Go における従来のランタイムメトリクスの取得方法にはいくつかの問題点があり、それが runtime/metrics パッケージによってどのように解決されたかについて説明されています。<br />
将来的なランタイムの変更にも対応するためにどのような API デザインとするか、という点は非常に興味深かったです。発表の後半では、現在サポートされているメトリクスの一覧とそれらのユースケースについても紹介されていました。</p>
<h2>Securing Code with Govulncheck</h2>
<p>3つめのセッションは同じく @Chin-Ming さんと @mohit さんによる「Securing Code with Govulncheck」です。</p>
<p>発表資料:<a href="https://speakerdeck.com/mohitpokharna08/securing-code-with-govulncheck">Securing Code with Govulncheck</a></p>
<p>Go プログラムの脆弱性チェックを行うための <a href="https://pkg.go.dev/golang.org/x/vuln/cmd/govulncheck">Govulncheck</a> というツールについて紹介しました。ツール自体の利用方法に加えて、チェック対象とする脆弱性が Go においてどのように収集・管理されているか、という点についても説明されています。<br />
<a href="https://vuln.go.dev/">Go Vulnerability Database</a> についての説明など個人的にも知らなかった点が多く、とても勉強になりました。Govulncheck は CI にも容易に組み込めるそうなので、興味のある方は試してみてはいかがでしょうか?</p>
<h2>おわりに</h2>
<p>今回は Go の標準ライブラリやツールを題材とした3つの発表をお送りしました。Go を通して TLS の仕組みやソフトウェアの脆弱性についても知ることができて、運営としても非常に勉強になりました。</p>
<p>ライブで視聴いただいた方も録画を観ていただけた方も本当にありがとうございました!</p>
<p>次回の開催もお楽しみに!<br />
イベント開催案内を受け取りたい方は、connpassグループのメンバーになってくださいね!<br />
<a href="https://mercari.connpass.com/">メルカリconnpassグループページ</a></p>
- #tryswift Tokyo 2024 に参加してきたよ!https://engineering.mercari.com/blog/entry/20240326-tryswift-2024/https://engineering.mercari.com/blog/entry/20240326-tryswift-2024/<p>こんにちは。メルペイ Engineering Engagement チームの mikichin です。 3月22日から3日間開催された「try! Swift Tokyo 2024」にメルカリはPLATINUMスポンサーを […]</p>
Tue, 26 Mar 2024 12:39:32 GMT<p>こんにちは。メルペイ Engineering Engagement チームの <a href="https://twitter.com/chida_miki" title="mikichin">mikichin</a> です。<br />
3月22日から3日間開催された「<a href="https://tryswift.jp/" title="try! Swift Tokyo 2024">try! Swift Tokyo 2024</a>」にメルカリはPLATINUMスポンサーをしており、会場ではブースを出していました。今回は参加レポートをお届けします!</p>
<h2>try! Swift Tokyo 2024 について</h2>
<p>try! Swift Tokyo は、Swiftを使った開発のコツや最新の事例を求めて、世界中から開発者が集うカンファレンスです。</p>
<h3>開催概要</h3>
<p>開催日時<br />
カンファレンス:2024年3月22日(金) 〜 23(土)<br />
ワークショップ:2024年3月24日(日)<br />
場所 ベルサール渋谷ファースト</p>
<h2>当日の様子をご紹介</h2>
<h3>メルカリブース</h3>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/03/e722d394-gju6xreaiaaht_h.jpeg" alt="" /><br />
メルカリブースでは、iOSメンバーでアイディアを出し合って作成したクイズを準備しました。<br />
クイズは全部で9問、平均の正答数は約5問でした。<br />
ご参加いただいた方々からは「難しかった」「勉強になった」「すごく楽しかった」と感想をいただきました。ご参加いただいたみなさま、ありがとうございました!</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/03/d367efc8-img_2007.png" alt="" /><br />
クイズとは別に、「ビルド時間が長い場合、どのような工夫をしますか?」というお題に対して、いろいろなアイディアを書いてもらいました。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/03/ee45e34b--2024-03-25-21.05.00.png" alt="" /><br />
<img src="https://storage.googleapis.com/prd-engineering-asset/2024/03/7bb50274-photo.png" alt="" /><br />
今回のイベントにあわせて、メルカリブースに遊びにきていただいた記念に撮影してもらえればとフォトフレームを準備。</p>
<p>本当に多くの方々にブースにお越しいただき、ありがとうございました 🙂</p>
<h3>スポンサーブース</h3>
<p>今回、17社のスポンサーブースがあり、わたしも全ブースに遊びにいきました!<br />
各社いろいろなコンテンツが準備されていて、とても楽しく、たくさんかわいいオリジナルグッズをいただきました。</p>
<p>中でも、わたしが個人的に印象に残ったのはZOZO社のコンテンツです。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/03/3a29c78b-img_1953-scaled.jpg" alt="" /></p>
<p>社内企画で、普段からSlackで共有されているというみんなの失敗。何かしらの失敗を投稿すると、トイレットペーパーをもらえるということでそれを今回のイベントでも実施していました。</p>
<p>ZOZO社の社風も伝わるコンテンツであり、失敗を水に流すということでトイレットペーパーをグッズにするというユーモアもくすっと笑えて素敵なコンテンツだなと思いました 🙂</p>
<h3>セッションについて</h3>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/03/5e5e7a89-img_2025.jpg" alt="" /><br />
わたしは基本的にずっとスポンサーブースにいたため、今回はひとつもセッションをきいておりません。(残念….)</p>
<p>ちょうどメルカリブースの目の前に「Ask the Speaker」のスペースがあり、毎回多くの人がきて情報交換をされている様子をみており、どのセッションも盛り上がったことを感じていました。次回はひとつくらいはきけるように、シフトを調整しようかなと思います。</p>
<h3>Party</h3>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/03/89c8879e-img_2009-scaled.jpg" alt="" /></p>
<p>try! Swift Tokyo で名物 @jollyjoester の「カー→ンパ↑ーイ!(※)」でスタート!</p>
<p>After Partyでは、いろいろな方と交流することができて楽しかったです。Xでつながっていたり、一方的に認知していたりする方と直接お会いしてお話ができて大満足。<br />
会場でもいたるところで会話が盛り上がっていました。特に、本イベントは海外から参加している方々も多かったので、コロナ前の日常を取り戻したんだなとしみじみ実感しました 🙂</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/03/8f12d01b-kanpai_jolleysan.png" alt="" /><br />
△※:弊社Slackのbotででてくる表現を引用。</p>
<h2>まとめ</h2>
<p>わたしはTech PRという仕事柄、いろいろなカンファレンスに参加しています。<br />
5年ぶりの開催ということもあり、try! Swift Tokyoは初めての参加で、こんなにも海外の方が多く参加するとは思っていなかったのでいい意味で驚きました!お話をしてみると、このイベントのために日本にきているという方も多かったです。<br />
個人的には最近英語の勉強をさぼっていたので(笑)、このイベントに参加したことで刺激になりました。</p>
<p>最後に、try! Swift Tokyo 2024の企画運営、おつかれさま & ありがとうございました!<br />
また、次回を楽しみにしています!</p>
- スムーズなリモートワークを実現するためのちょっとした工夫を iOS アプリに入れた話https://engineering.mercari.com/blog/entry/20240313-tip-for-smooth-remote-development-of-ios-app/https://engineering.mercari.com/blog/entry/20240313-tip-for-smooth-remote-development-of-ios-app/<p>iOSエンジニアのtakecianです。 株式会社メルカリでは YOUR CHOICE という「働く場所・住む場所」を自由に選択できる制度があります。そのため同僚とはリモートワークでコミュニケーションを取りながら仕事を進 […]</p>
Wed, 13 Mar 2024 11:54:49 GMT<p>iOSエンジニアの<a href="https://twitter.com/takecian">takecian</a>です。</p>
<p>株式会社メルカリでは <a href="https://careers.mercari.com/jp/your-choice/">YOUR CHOICE</a> という「働く場所・住む場所」を自由に選択できる制度があります。そのため同僚とはリモートワークでコミュニケーションを取りながら仕事を進めることが多いです。(六本木にオフィスはあるので出社して仕事をすることも可能です)</p>
<p>リモートワークで働いている時にアプリのバグを見つけたり、気になる挙動を見つけた時にアプリの画面を録画して共有することがあります。「ここの動作がおかしい気がする」「この順で操作すると画面表示が変になる」など、操作中の画面を録画してもらい、ビデオを受け取って確認してみます。ですが画面にはどこをタッチしたかは表示されないので、「どういう操作をしているか」「どこをタップしたか」が分かりにくいと思った経験が iOS エンジニアだと誰もがあるのではないでしょうか。</p>
<p>このエントリでは、iOSアプリで見つけたバグの再現手順をリモートワーク中にスムーズに共有するために行った取り組みについて紹介します。</p>
<p>例としてメルカリのアプリを操作をした画面を録画してみました。</p>
<div style="text-align: center;">
<video width="292" height="633" controls><source src="https://storage.googleapis.com/prd-engineering-asset/2024/03/0079fba3-takecian_withoutpointer.mp4" type="video/mp4">Your browser does not support the video tag.</video>
</div>
<p>この例ではマイページからいくつかの項目をタップして別の画面に遷移していますが、どこをタップしたのか分かりにくいですよね。</p>
<p>そこでタップした箇所が表示されるようにしてみたいと思います。iOS のタップイベントは <code>UIWindow</code> の <code>sendEvent</code> メソッドで送られてくるので <code>method_exchangeImplementations</code> を使って <code>sendEvent</code> メソッドを自前のメソッドに差し替えてみます。</p>
<pre><code class="language-swift">private static func swizzleSendEvent() {
guard let originalMethod = class_getInstanceMethod(UIWindow.self, #selector(sendEvent)),
let swizzledMethod = class_getInstanceMethod(UIWindow.self, #selector(swizzledSendEvent))
else {
return
}
method_exchangeImplementations(originalMethod, swizzledMethod)
}</code></pre>
<p>すると画面をタップした時に差し替えたメソッドが呼ばれるようになるので、そのメソッドの中で元の <code>UIWindow.sendEvent</code> を呼び出しつつ、画面に触れている場所の座標を取得します。(この処理をおこなわないとタップしたというイベントが伝搬せず止まってしまいます) </p>
<pre><code class="language-swift">@objc
func swizzledSendEvent(_ event: UIEvent) {
// 自身を呼び出すことで差し替え前のメソッド(`UIWindow.sendEvent`)を実行します
swizzledSendEvent(event)
guard case .touches = event.type, let touches = event.allTouches else {
return
}
// UITouch の画面に触れているものを集合に追加する(複数箇所の同時タップを想定)
let beganTouches = touches.filter { $0.phase == .began }
UIWindow.touches.formUnion(beganTouches)
// 画面から離れた分を集合から取り除く
let endedTouches = touches.filter { $0.phase == .cancelled || $0.phase == .ended }
UIWindow.touches = UIWindow.touches.subtracting(endedTouches)
// 座標に変換する
let touchLocations = UIWindow.touches.map { $0.location(in: self) }
// touchLocations に入っている座標が画面に触れている場所なので描画する。
// コードは省略。
}</code></pre>
<p>取得した座標上に UIView を表示することでタッチしている箇所が分かるようにしてみました。先ほどと同じ操作を録画してみたのがこちらです。</p>
<div style="text-align: center;">
<video width="292" height="633" controls><source src="https://storage.googleapis.com/prd-engineering-asset/2024/03/cf13b449-takecian_withpointer.mp4" type="video/mp4">Your browser does not support the video tag.</video>
</div>
<p>どういう操作をしているかが簡単に分かりますね。この機能を実現するために使用した <code>method_exchangeImplementations</code> はメソッドの呼び出し先を変更してしまうというとても強力なものなので、大人数で開発している環境ではできるだけ使うのは避けたいものです。そこでこの機能は <a href="https://docs.swift.org/swift-book/documentation/the-swift-programming-language/statements/#Conditional-Compilation-Block">compiler directives</a> (#if DEBUG という書き方で特定の環境でのみコードが動作する仕組み) を使って開発中のアプリでのみ動作するようにしています。</p>
<p>この機能を紹介したところ、特にQAチームの人から喜ばれました。Bug の再現手順を分かりやすく共有できるようになったのではないかと思います。</p>
<p>このようなちょっとした工夫を入れることでリモートで仕事をしていても効率的に仕事を進めることができます。</p>
<p>株式会社メルカリには全国様々な場所から働いている同僚がいて、新たな価値を生みだす世界的なマーケットプレイスを創るために日々楽しみながら開発しています。興味のある方は<a href="https://apply.workable.com/mercari/">こちら</a>から募集を見てみてください。</p>
- DeNA TechCon 2024 に参加してきたよ!https://engineering.mercari.com/blog/entry/20240307-report-dena-techcon-2024/https://engineering.mercari.com/blog/entry/20240307-report-dena-techcon-2024/<p>こんにちは。メルペイ Engineering Engagement チームの mikichin です。 2月29日に開催された「DeNA TechCon 2024」のオフライン会場にご招待いただきましたので、参加レポート […]</p>
Thu, 07 Mar 2024 10:00:51 GMT<p>こんにちは。メルペイ Engineering Engagement チームの <a href="https://twitter.com/chida_miki" title="mikichin">mikichin</a> です。<br />
2月29日に開催された「<a href="https://techcon2024.dena.dev/" title="DeNA TechCon 2024">DeNA TechCon 2024</a>」のオフライン会場にご招待いただきましたので、参加レポートをお届けします!</p>
<h2>DeNA TechCon 2024 について</h2>
<p>DeNA TechCon(テックコン) は、DeNA のエンジニアが業務で得た知見を発信することで社会の技術向上に貢献する目的で、2016年より開催している技術カンファレンスです。</p>
<p>今年はオンライン、オフラインの同時開催。「POLYPHONY」というテーマでゲーム、ライブストリーミング、AI、Web3、ヘルスケア、メディカルなど幅広いトピックに触れながら、各事業のチャレンジをご紹介。また、セッションだけではなく体験ブース、技術コミュニティイベントもありました。</p>
<p>オフラインに関しては、久しぶりの開催ということで招待制となっていました。招待制って特別感があって招待いただいたほうもとてもうれしいですね 🙂</p>
<p>参考記事:<a href="https://dena.com/jp/press/5084/">https://dena.com/jp/press/5084/</a></p>
<h2>当日の様子をご紹介</h2>
<p>オフライン会場は、渋谷ストリームホールでした。方向音痴のわたしには、駅チカでとても助かりました。</p>
<h3>セッションについて</h3>
<p>3トラック同時進行で24セッションありました。オフラインで参加していると、「体験ブースに行きたい」「技術コミュニティイベントにも行きたい」となり、ききに行くセッションを絞るのが大変でした。最終的にはアーカイブ動画が公開されると思うのでそちらも確認しようと思います。</p>
<p>個人的に一番おもしろかったのは、「<a href="https://techcon2024.dena.dev/session/session23/" title="LIGHTNING TALKS">LIGHTNING TALKS</a>」です。5分という時間制限の中、すべての内容をききたいという気持ちはありますがドラが鳴るかなというわくわく感も楽しいですよね…!<br />
LIGHTNING TALKSでは、3名の方がLTをされました。</p>
<h4>「新卒による全社横断コミュニティと社内外勉強会の運営への挑戦」</h4>
<p>わたしもTech PRとして社内外勉強会の企画をしています。「わたしも同じようなこと思っている!」「そこ、難しいよね…」など、話をきいていてすごく共感していました(笑)<br />
TechConでも5つの技術コミュニティイベントが開催されていました。現場のエンジニアが課題感を持ち、自発的に勉強会を企画して楽しんでいる方々が多いからこういった幅広い技術コミュニティのイベントが継続開催されているんだなぁと様子や社風が伝わる素敵なLTでした。</p>
<h4>「新人インターン生が海外 Web3 ハッカソンに参加した話」</h4>
<p>インターン生が海外で開催されているハッカソンに参加し、賞を受賞してくるというLT自体が素晴らしいのですが、顔出しがNGということで黒衣姿で登壇をしていたのがとても新鮮でおもしろかったです(笑)</p>
<h4>「TechCon 2024 ハイブリット開催の舞台裏:ネットワーク構築編」</h4>
<p>すべて内製で運営しているというDeNA TechConならではのLTだなと思います。今回のイベントでは、当たり前のようにWiFiが提供されていましたがどのフロアでも問題なく使用することができました。<br />
こういったイベントでWiFiが使えるというのは、全然当たり前ではなく素晴らしい準備があったからこそだなと思い、大変感謝しています。</p>
<h3>体験ブースやスタンプラリー</h3>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/03/0e99692f--2024-03-06-18.08.40.png" alt="" /><br />
6つの体験ブースがありました。どのブースも大変盛り上がっていました!(写真は人があまりいないときに撮影しました)<br />
音声変換AI体験ブースでは、男性の声になった自分の声をほぼリアルタイムで感じることができたり、新感覚Vtuberアプリ「IRIAM」の体験では自分の動きにあわせて動く様子を体験できたりとおもしろかったです。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/03/db9562f7--2024-03-06-18.09.34.png" alt="" /></p>
<p>セッションをきいたり、体験ブースに行くとスタンプを押してもらえます。スタンプラリーでは8つ集めるとClosing Keynoteで行われる豪華景品のあたる抽選会に参加できるということで、8つ集めました!(はずれました。残念…)</p>
<h3>After Party</h3>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/03/2e4700e2--2024-03-06-18.10.33.png" alt="" /></p>
<p>4Fは屋台、5FはDJブースにビュッフェ形式と違った雰囲気を楽しむことができました。<br />
After Partyもみなさんそれぞれ会話を楽しみ、すごく盛り上がっていました!</p>
<p>わたしも他社のTech PRの方とTechConの感想を共有しあうことはもちろん、いろいろ情報交換ができとても有意義な時間でした。</p>
<h2>まとめ</h2>
<p>最近、企業カンファレンスやコミュニティカンファレンスが完全オンラインからオフラインに移行してきています。オンラインはオンラインのよさ、オフラインはオフラインのよさがありますよね。TechConはハイブリットでそれぞれのよさを活かしながら開催されているように感じました。</p>
<p>昨年の「<a href="https://events.merpay.com/techfest-2023/" title="Merpay & Mercoin Tech Fest 2023">Merpay & Mercoin Tech Fest 2023</a>」では完全オンラインで開催しました。ハイブリッド開催をするということは考えなくちゃいけないことが膨大に増えるので、かなりのチャレンジになりますが、わたしもそろそろオフラインでメルペイの魅力を伝えていきたいなーと思っています 🙂</p>
<p>最後に、DeNA TechCon 2024の企画運営、おつかれさま & ありがとうございました!社員の方々がTechCon自体を楽しんでいる様子を直接感じることができ、とても素敵なイベントでした。また、次回を楽しみにしています!</p>
- eBPFのリバースエンジニアリング入門https://engineering.mercari.com/blog/entry/20240228-b47712375d/https://engineering.mercari.com/blog/entry/20240228-b47712375d/<p>目次 はじめに eBPFとは? eBPFのCTFチャレンジ Flagの獲得 おわりに はじめに 初めまして、Threat Detection and ResponseチームのChihiroです。昨年の7月に株式会社メルカ […]</p>
Wed, 28 Feb 2024 11:54:56 GMT<h2>目次</h2>
<ul>
<li>はじめに</li>
<li>eBPFとは?</li>
<li>eBPFのCTFチャレンジ</li>
<li>Flagの獲得</li>
<li>おわりに</li>
</ul>
<h2>はじめに</h2>
<p>初めまして、Threat Detection and ResponseチームのChihiroです。昨年の7月に株式会社メルカリに入社して、主にクラウド向けのDetection Engineeringや、インシデントレスポンスを担当しています。また、メルカリで自社開発している<a href="https://engineering.mercari.com/en/blog/entry/20220513-detection-engineering-and-soar-at-mercari/" title="SOAR">SOAR</a>(Secuirty Orchestration Automation and Response)プラットフォームの開発や運用も担当しています。</p>
<p>メルカリには、<a href="https://mercan.mercari.com/tags/circle/" title="部活">部活</a>を支援する社内制度が存在し、様々な部活があります。その部活の一環として、私は最近、CTF(Capture The Flag)と呼ばれるサイバーセキュリティの競技を楽しんでいます。そこで今回は、参加したCTFの中で面白かった<a href="https://ebpf.io/" title="eBPF">eBPF</a>に関するリバースエンジニアリングの問題を例にして、eBPFプログラムがどのように構成されており処理されていくのか解説します。</p>
<h2>eBPFとは?</h2>
<p>eBPFは、Linuxカーネル空間で動作し、パケットフィルタリングやパフォーマンス調査のためのトレーシングなどに活用されている技術です。また、近年はクラウドやセキュリティといった文脈でも活用されています。例えば、CNCFのプロジェクトとして有名なCNI(Container Network Interface)の1種である<a href="https://cilium.io/" title="Cilium">Cilium</a>や、コンテナのランタイムセキュリティのツールである<a href="https://falco.org/" title="Falco">Falco</a>などに利用されています。</p>
<p>eBPFのプログラムは、Linuxカーネル上にて、サンドボックスのような仮想マシン上で実行されるため、独自の命令仕様をもっています。そこで、簡単にeBPFのバイトコードを実行する仮想マシンの仕様についてご紹介します。詳しい仕様は、<a href="https://www.kernel.org/doc/html/v5.17/bpf/instruction-set.html" title="eBPF Instruction Set">eBPF Instruction Set</a>に記載されているので、合わせてご覧ください。</p>
<p>通常プログラム言語には、変数のような算出された数値を格納するための場所があります。今回の仮想マシンでは、それに相当するレジスタと呼ばれる小規模な記憶領域が利用されます。eBPFの命令セットでは10個の汎用レジスタが存在します。</p>
<ul>
<li>R0: 関数からの戻り値や、eBPFプログラムが終了するときのステータスコードを格納</li>
<li>R1 – R5: 関数の引数が格納される</li>
<li>R6 – R9: 汎用的に用いることができる</li>
<li>R10: スタックフレームのアドレスを格納する</li>
</ul>
<p>次に、命令について見ていきます。eBPFの命令はRISCアーキテクチャで使われる命令のように固定長です。1命令は、64bitになっています。具体的には、下記のように構成されています。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/02/e527bc3f-image2.png" alt="" /></p>
<p>オペコードとは、命令の種類を表しており、数値を転送先レジスタ代入する命令や、加減算をする処理、条件分岐のための処理などが存在します。そして、このオペコードはさらに細かく構成されています。即値には、実際に代入する数値のデータが格納されることがあります。</p>
<p>1つ例を見てみましょう。下記のような64bitの数値の命令を明らかにしていきます。リトルエンディアンの表記になっているため一番左が下位byteな点に注意してください。</p>
<pre><code>b7 01 00 00 44 04 05 1c</code></pre>
<p>まず、b7の部分がオペコードの8 bitsになります。b7を2進数に直すと1011 0111となります。下位3bitの111、つまり7はBPF_ALU64という命令の種類を表します。</p>
<p>1011は、BPF_ALU64においては、BPF_MOVという命令として定義されており、転送元レジスタから転送先レジスタへ代入をする命令となります。残りの4bit目の0は転送元レジスタが、32bitの即値であるかレジスタであるかを決めるパラメータとなっています。この値が0の場合は、即値が利用されます。</p>
<p>次に2byte目の01です。これは、2進数として表すと0000 0001となります。図に示したように、それらは4bitずつ転送元と転送先レジスタに分割されます。つまり、転送先が1すなわちR1レジスタ、転送元がR0レジスタとなっています。</p>
<p>しかしながら、先ほど見たように転送元はレジスタではなく即値を使うため、0x1c050444という即値をR1レジスタに代入する命令だと解釈することができます。</p>
<h2>eBPFのCTFチャレンジ</h2>
<p>本問題は、Backdoor CTFというCTFの初心者向けのリバースエンジニアリング問題になります。<a href="https://ctftime.org/" title="CTFtime.org">CTFtime.org</a>によると、Backdoor CTFは2013年頃から開催されているようです。</p>
<p>CTFでは、様々なコンピュータサイエンスやサイバーセキュリティに関するクイズを解いて、フラグと呼ばれる特定のフォーマットの文字列、<code>FLAG{COOL_FLAG_NAME}</code>を見つけ出すことがゴールになります。例えば、リバースエンジニアリングの問題では、バイナリファイルを解析することで、隠されているフラグを得ることができる問題が一般的です。</p>
<p>リバースエンジニアリングの問題では、LinuxやWindowsのバイナリファイルを解析することが多いです。しかし、別のファイルフォーマットを解析することもあります。そのため、最初に<code>file</code>コマンドを使って、ファイルタイプを特定することが有用です。下記の通り、このファイルは、eBPFのプログラムだと判明しました。</p>
<pre><code>root@6d1def7da3d3:~# file babyebpf.o
babyebpf.o: ELF 64-bit LSB relocatable, eBPF, version 1 (SYSV), not stripped</code></pre>
<p>この問題に対しては、2つのアプローチがあります。1つ目は実際にこのeBPFのコードを動かすことです。そしてもう1つは実際にどんな命令が記載されているのかを読んでいく手法です。今回は、興味のために、後者のアプローチでやっていこうと思います。</p>
<p>しかしながら、先ほど見たように、バイナリファイル内に含まれるすべての命令を手作業で解析していては大変です。そこで、これらの作業を自動化するための手法である逆アセンブルと呼ばれる変換作業をします。逆アセンブルでは、機械語を人間が読みやすい<a href="https://ja.wikipedia.org/wiki/%E3%82%A2%E3%82%BB%E3%83%B3%E3%83%96%E3%83%AA%E8%A8%80%E8%AA%9E#%E3%83%8B%E3%83%BC%E3%83%A2%E3%83%8B%E3%83%83%E3%82%AF" title="ニーモニック">ニーモニック</a>と呼ばれる機械語に対応する文字列命令に変換します。</p>
<p>eBPFバイトコードの場合は、<code>llvm-objdump</code>コマンドがおすすめです。<code>-d</code>フラグを使うことで対象ファイルの逆アセンブルをすることができます。通常、ニーモニックと同時に16進数も表示されるのですが、ここでは冗長なので<code>--no-show-raw-insn</code>フラグを使って非表示にしています。</p>
<pre><code>root@6d1def7da3d3:~# llvm-objdump --no-show-raw-insn -d babyebpf.o
babyebpf.o: file format elf64-bpf
Disassembly of section tp/syscalls/sys_enter_execve:
0000000000000000 <detect_execve>:
0: r1 = 0x1c050444
1: *(u32 *)(r10 - 0x8) = r1
2: r1 = 0x954094701340819 ll
4: *(u64 *)(r10 - 0x10) = r1
5: r1 = 0x10523251403e5713 ll
7: *(u64 *)(r10 - 0x18) = r1
8: r1 = 0x43075a150e130d0b ll
10: *(u64 *)(r10 - 0x20) = r1
11: r1 = 0x0
0000000000000060 <LBB0_1>:
12: r2 = 0x0 ll
14: r2 += r1
15: r2 = *(u8 *)(r2 + 0x0)
16: r3 = r10
17: r3 += -0x20
18: r3 += r1
19: r4 = *(u8 *)(r3 + 0x0)
20: r2 ^= r4
21: *(u8 *)(r3 + 0x0) = r2
22: r1 += 0x1
23: if r1 == 0x1c goto +0x1 <LBB0_2>
24: goto -0xd <LBB0_1>
00000000000000c8 <LBB0_2>:
25: r3 = r10
26: r3 += -0x20
27: r1 = 0x1c ll
29: r2 = 0x4
30: call 0x6
31: r0 = 0x1
32: exit</code></pre>
<p>簡単に逆アセンブル結果での命令の読み方を解説します。例えば、<code>r1 = 10</code>の場合は、r1レジスタに10を代入するという例です。他にメモリにデータを代入する際には<code>*(u32*)(r10) = r1</code>のような表記を用います。これは、r10レジスタの値をアドレスとして捉えて、そのアドレスが指すメモリにr1の値を代入するという意味になります。</p>
<p>では、実際に<code>detect_execve</code>関数から処理を読んでいきます。</p>
<pre><code>0000000000000000 <detect_execve>:
0: r1 = 0x1c050444
1: *(u32 *)(r10 - 0x8) = r1
2: r1 = 0x954094701340819 ll
4: *(u64 *)(r10 - 0x10) = r1
5: r1 = 0x10523251403e5713 ll
7: *(u64 *)(r10 - 0x18) = r1
8: r1 = 0x43075a150e130d0b ll
10: *(u64 *)(r10 - 0x20) = r1
11: r1 = 0x0</code></pre>
<p>はじめに、r1レジスタに0x1c050444(10進数で470090820)を代入しています。次に、そのr1をr10-8が指すアドレスのメモリに格納しています。なお、r10レジスタはスタックフレームのアドレスを指すレジスタであることに注意してください。そのため、この処理は関数のローカル変数に値を代入しているコードだと読み解くことができます。そして似たような、データの代入をするコードがその後続いているのがわかります。また、最後にr1レジスタに0が格納されています。この処理が終わった後のスタックのイメージは下記の図の通りです。</p>
<p align="center"><img src="https://storage.googleapis.com/prd-engineering-asset/2024/02/2a460294-image3-1024x844.png" alt="" width="400" class="aligncenter size-large wp-image-30707" srcset="https://storage.googleapis.com/prd-engineering-asset/2024/02/2a460294-image3-1024x844.png 1024w, https://storage.googleapis.com/prd-engineering-asset/2024/02/2a460294-image3-300x247.png 300w, https://storage.googleapis.com/prd-engineering-asset/2024/02/2a460294-image3-768x633.png 768w, https://storage.googleapis.com/prd-engineering-asset/2024/02/2a460294-image3-1200x989.png 1200w, https://storage.googleapis.com/prd-engineering-asset/2024/02/2a460294-image3.png 1456w" sizes="(max-width: 1024px) 100vw, 1024px" /></p>
<p>さらに、逆アセンブル結果を読み進めていきます。ここでは先に関数の末尾の方を見てみましょう。</p>
<pre><code>0000000000000060 <LBB0_1>:
12: r2 = 0x0 ll
14: r2 += r1
15: r2 = *(u8 *)(r2 + 0x0)
16: r3 = r10
17: r3 += -0x20
18: r3 += r1
19: r4 = *(u8 *)(r3 + 0x0)
20: r2 ^= r4
21: *(u8 *)(r3 + 0x0) = r2
22: r1 += 0x1
23: if r1 == 0x1c goto +0x1 <LBB0_2>
24: goto -0xd <LBB0_1></code></pre>
<p>そこには、<code>if</code>文があり、r1レジスタと0x1c(10進数で28)と比較しています。これらの値が等しかったら、LBB0_2ラベルにgotoします。そうでなければ、LBB0_1ラベルの先頭に戻ります。こうした処理は、高級言語におけるループ構文として認識することができます。事実、<code>if</code>文の前では、比較対象であるr1レジスタに1を加算する処理、つまりインクリメントが行われています。</p>
<p>では、このコードブロックにはループ文があるという前提で先頭から読んでいきます。まずr2レジスタに0を代入し、さらにr1レジスタの値を加算しています。初めはr1レジスタは<code>detect_execve</code>関数で言及したように0が格納されているため、r2は加算されても0のままです。次にr2レジスタをアドレスとして使って、脱参照しr2レジスタに格納されているメモリ上の実際のデータを格納しています。</p>
<p>次に、命令の対象はr3レジスタへと変わります。r3レジスタにr10レジスタ、つまりスタックフレームのアドレスを格納します。その後、32を減算しています。この32は、ちょうどスタックフレームのアドレスから、先ほど代入したローカル変数のアドレスへのオフセットとなっています。さらに、そのアドレスに対してr1レジスタの値を足して、脱参照し、ローカル変数の値をr4レジスタに格納しています。そして、r2レジスタとr4レジスタの値をXORして、その結果をr2レジスタに格納し、最終的にr3レジスタが指す先、つまりローカル変数のアドレスが指すメモリ上のデータを、計算結果で書き換えています。</p>
<p>それ以降は、先ほど述べたように、r1レジスタを加算して、ループ処理の<code>if</code>文へと続きます。これにより、1byteずつずれながら、メモリ上の二つのデータへアクセスして、各1byteをXORして、ローカル変数の中身を上書きする処理が実行されていきます。つまり、何かしらのデータに対して、ローカル変数を使ってデータをデコードしている処理がこのeBPFプログラムの本質だとわかります。また、r1が28と比較していることから、両者のデータの想定されるデータ長は28byteだと推定することができます。</p>
<p>さて、少し話を戻します。r2レジスタにはどんなデータが入っているのでしょうか。逆アセンブル結果だけだと判断ができないため、少し視点を変えてバイナリを調査してみます。一般に、バイナリファイルには、特徴的な文字列などが含まれていることが多いです。そこで、GNU Binary Utilitiesの<code>strings</code>コマンドを使って文字列を調査してみます。</p>
<pre><code>root@6d1def7da3d3:~# strings -tx -a babyebpf.o
5c G T {
148 marinkitagawamarinkitagawama
16e W>@Q2R
179 G T D
2a5 .text
2ab detect_execve.____fmt
2c1 _version
2ca .llvm_addrsig
2d8 detect_execve
2e6 .reltp/syscalls/sys_enter_execve
307 _license
310 baby_ebpf.c
31c .strtab
324 .symtab
32c .rodata
334 LBB0_2
33b LBB0_1
342 .rodata.str1.1</code></pre>
<p>いくつか特徴的な文字列はありますが、先ほど得た28byteというデータ長に着目して見ると、<code>marinkitagawamarinkitagawama</code>という文字列は興味深いです。実際、byte数を確認してみると28byteでした。</p>
<pre><code>root@6d1def7da3d3:~# echo -n marinkitagawamarinkitagawama | wc -c
28</code></pre>
<p>では、最後にLBB0_2ラベルの処理を読んでいきます。</p>
<pre><code>00000000000000c8 <LBB0_2>:
25: r3 = r10
26: r3 += -0x20
27: r1 = 0x1c ll
29: r2 = 0x4
30: call 0x6
31: r0 = 0x1
32: exit</code></pre>
<p>このコードブロックで注目すべきは、<code>call</code>命令です。本命令の引数は6となっています。<code>call</code>命令は、eBPFプログラム内で定義したローカル関数とは別に、引数の整数値によって特定の関数を実行することができます。それらの関数と整数値のマッピングは、Linuxの<a href="https://github.com/torvalds/linux/blob/b401b621758e46812da61fa58a67c3fd8d91de0d/include/uapi/linux/bpf.h#L5690" title="ソースコード">ソースコード</a>上で定義されており、6は<code>trace_printk</code>関数のようです。つまり、このコードは、何かしらデータを表示するコードだとわかります。また、r3レジスタに、ローカル変数のアドレスを格納しています。したがって、このプログラムは、XOR処理をしたデータを表示しようとするものだと推測することができます。</p>
<h2>Flagの獲得</h2>
<p>ここまでで、わかったことをスクリプトとして作成してみます。私は普段CTFで問題を解く際に、Rubyをよく使っているので、ここではRubyで書いたスクリプトを下記に示します。どんな言語でも問題ありません、ご自身の好きな言語で作成してみてください。</p>
<pre><code class="language-ruby">#!/usr/bin/env ruby
encoded = [
0x43075a150e130d0b,
0x10523251403e5713,
0x954094701340819,
0x1c050444
].pack('Q*').chars
key = "marinkitagawamarinkitagawama".chars
key.zip(encoded) do |k, e|
print (k.ord ^ e.ord).chr
end</code></pre>
<p>上記のRubyのスクリプトは、ローカル変数に代入されていた値と、バイナリファイル内に含まれていた文字列をバイト毎にXORした値を表示します。</p>
<p>これを実行すると、下記のように最終的にフラグを得ることができました。</p>
<pre><code>root@6d1def7da3d3:~# ruby solve.rb
flag{1n7r0_70_3bpf_h3h3h3eh}</code></pre>
<h2>おわりに</h2>
<p>この記事では、CTFの問題を題材に、eBPFプログラムの内部を解説しました。eBPFを間接的に使っている人は多いと思いますが、こうした裏側について知っている人は多くないと思います。本知識を直接的に、業務で使う機会は少ないかもしれませんが、デバッグやかなり細かい調査になってくると、もしかしたら役に立つ機会はあるかもしれません。</p>
<p>最後まで読んでくださってありがとうございました。本記事が何かの役に立てば幸いです。</p>
- Elasticsearchのパフォーマンス問題をプロファイラを使って解決するhttps://engineering.mercari.com/blog/entry/20240214-87df8d95e9/https://engineering.mercari.com/blog/entry/20240214-87df8d95e9/<p>search infra teamのmrkm4ntrです。我々のチームではElasticsearchをKubernetes上で多数運用しています。歴史的経緯によりElasticsearchのクラスタは全てElastics […]</p>
Wed, 14 Feb 2024 11:12:24 GMT<p>search infra teamのmrkm4ntrです。我々のチームではElasticsearchをKubernetes上で多数運用しています。歴史的経緯によりElasticsearchのクラスタは全てElasticsearchクラスタ専用のnode pool上で動作していました。ElasticsearchのPodは使用するリソースが大きいため、このnode poolのbin packingが難しくコストを最適化できないという問題がありました。そこで全てのElasticsearchクラスタを専用のnode poolから他のワークロードと共存可能なnode poolへ移行しました。ほとんどのクラスタが問題なく移行できたのですが、唯一移行後にlatencyのスパイクが多発してしまうものがありました。<br />
この記事では、その原因を調査する方法と発見した解消方法について説明します。</p>
<h2>発生した現象</h2>
<p>共用node poolへ移行後にピーク時間帯において95pのlatencyが下図の青線のようにスパイクしました。<br />
<img src="https://storage.googleapis.com/prd-engineering-asset/2024/02/b49eb2fb--2024-02-05-13.27.29.png" alt="" /></p>
<p>一旦このクラスタを専用node poolに戻すと、latencyは元どおりに落ち着きました。各メトリクスを見てもsearch thread poolのキューのサイズが上がっている他は特に怪しいものは見当たりません。CPUやmemoryのリソースが不足しているわけでもありません。search thread poolのキューのサイズが上がっているのはlatencyが上がったことによりキューのサイズが上がったと考えられるため原因ではなく結果だと思われます。該当クラスタのElasticsearchのversionは7.10.2でした。</p>
<h2>プロファイラの利用</h2>
<p>メトリクスを見ても原因がわからなかったため、プロファイラを使ってflame graphを表示することにしました。まずはkube-flame (<a href="https://github.com/yahoo/kubectl-flame">https://github.com/yahoo/kubectl-flame</a> )を使ってJVMのprofilerであるasync-profiler (<a href="https://github.com/async-profiler/async-profiler">https://github.com/async-profiler/async-profiler</a> )を動かします。以下が得られたflame graphです。<br />
<img src="https://storage.googleapis.com/prd-engineering-asset/2024/02/9b918966--2023-12-13-16.50.25.png" alt="" /></p>
<p>Elasticsearchの検索処理にはquery phaseとfetch phaseという二つのphaseがあり、query phaseでは各シャードにて転置インデックスを使って検索処理を行い、実際にヒットしたドキュメントのidのリストを取得します。一方fetch phaseではそのドキュメントのfieldを取得します。上のflame graphからはこのクラスタにおいてはfetch phaseが支配的ということがわかります。多くの場合はquery phaseが支配的になるため少々特殊な使用方法です。</p>
<p>何度かプロファイラを動かすと怪しそうなグラフが取得できました。<br />
<img src="https://storage.googleapis.com/prd-engineering-asset/2024/02/31bf3385--2023-12-13-16.53.04.png" alt="" /></p>
<p>黄色の箇所をズームするとCPUがNativeThreadSetのaddとremoveにおいてスピンロックを取得しようとしていることがわかります。<br />
<img src="https://storage.googleapis.com/prd-engineering-asset/2024/02/92ccfc39--2023-12-13-16.55.37.png" alt="" /></p>
<p>下記の <code>syncrhonized(this)</code>の箇所ですね。<br />
<a href="https://github.com/AdoptOpenJDK/openjdk-jdk15u/blob/49dc2dfcefa493a9143483e11144343e83038877/src/java.base/share/classes/sun/nio/ch/NativeThreadSet.java#L50">https://github.com/AdoptOpenJDK/openjdk-jdk15u/blob/49dc2dfcefa493a9143483e11144343e83038877/src/java.base/share/classes/sun/nio/ch/NativeThreadSet.java#L50</a><br />
<a href="https://github.com/AdoptOpenJDK/openjdk-jdk15u/blob/49dc2dfcefa493a9143483e11144343e83038877/src/java.base/share/classes/sun/nio/ch/NativeThreadSet.java#L75">https://github.com/AdoptOpenJDK/openjdk-jdk15u/blob/49dc2dfcefa493a9143483e11144343e83038877/src/java.base/share/classes/sun/nio/ch/NativeThreadSet.java#L75</a><br />
とはいえこのコード自体におかしいところはありません。</p>
<p>ここで調査が暗礁に乗り上げるかと思われましたが、async-profilerについて調べている際にLINEヤフー社のKafkaチームの方が発表された下記の資料を見つけました。<br />
<a href="https://speakerdeck.com/line_developers/time-travel-stack-trace-analysis-with-async-profiler">https://speakerdeck.com/line_developers/time-travel-stack-trace-analysis-with-async-profiler</a></p>
<p>こちらによるとasync-profilerによって出力されるJFRファイルを基に、各threadが各時点において何のメソッドを実行していたのかを可視化するツール(<a href="https://github.com/ocadaruma/jfrv">https://github.com/ocadaruma/jfrv</a> )を作って公開されたそうです。</p>
<p>早速async-profilerにJFR形式で出力させ、jfrvで読み込んでみました。<br />
<img src="https://storage.googleapis.com/prd-engineering-asset/2024/02/a741e86b--2023-12-13-21.44.03.png" alt="" /></p>
<p>NativeThreadSetでフィルタリングした結果、確かにlatencyのスパイクが発生した時点でNativeThreadSetのaddやremoveがロックを待機しています。</p>
<p>次はこれらのメソッドを呼び出している箇所でlatencyのスパイク中に出現回数が上がったものを探します。以下のとおり、LuceneのDataInputクラスのskipBytesというメソッドが見つかりました。<br />
<img src="https://storage.googleapis.com/prd-engineering-asset/2024/02/ff42b082--2023-12-13-22.11.17.png" alt="" /></p>
<p>これはElasticsearchのドキュメントの_sourceが入っているLZ4圧縮されたLuceneのStoredFieldを読み込む際に呼び出されています。<br />
<a href="https://github.com/apache/lucene-solr/blob/2dc63e901c60cda27ef3b744bc554f1481b3b067/lucene/core/src/java/org/apache/lucene/codecs/lucene87/LZ4WithPresetDictCompressionMode.java#L110-L118">https://github.com/apache/lucene-solr/blob/2dc63e901c60cda27ef3b744bc554f1481b3b067/lucene/core/src/java/org/apache/lucene/codecs/lucene87/LZ4WithPresetDictCompressionMode.java#L110-L118</a></p>
<p>ではなぜこのメソッドの出現回数が増加したのでしょうか?この現象が発生する直前に下図のように大きなmerge処理が走り、refreshによってそれが検索可能になったことがわかります。<br />
<img src="https://storage.googleapis.com/prd-engineering-asset/2024/02/f98f376b--2023-12-13-22.19.14.png" alt="" /></p>
<p>Elasticsearchにおいて新しく追加されたデータは、refreshによってセグメントと呼ばれるファイル(実際はpage cacheですが)に書き出されます。これらのファイルはimmutableであり、小さなセグメントがrefreshのたびに新しく次々に書き出されるのですが、バックグラウンドで複数のセグメントはmergeされ、新しく大きなセグメントとして書き出されます。このクラスタではインデックスは新しい順でソートされており、基本的にクエリにヒットするのは新しく追加されたばかりの小さなセグメントに入っているドキュメントでした。</p>
<p>ここで仮に新しく追加されたばかりのセグメントが、大きなセグメントにmergeされた場合を考えてみます。その場合、query phaseではインデックスはソートされているためlatencyは変わらないでしょう。しかし、fetch phaseではLZ4の辞書の後ろに_sourceが格納されているため、大きなセグメントでは辞書も大きくなり、ヒットしたドキュメントの_sourceを取得するためには毎回大きな辞書の分をskipする必要がでてきます。skipBytesは内部で1024バイトずつループでskipするため,これがskipBytesの出現回数を増やす原因だと考えました。</p>
<h2>MergePolicyのパラメータ変更</h2>
<p>Elasticsearchでは、LuceneのTieredMergePolicyというmerge policyを用いてどのセグメントをmergeするべきかどうかを選んでいます。このmerge policyではmergeするセグメントのサイズの差をskewという尺度で定義し、そのskewが小さいものを選択します。つまり基本的には上記のようなmergeはほとんど起きないはずです。</p>
<p>TieredMergeのパラメータを調べたところ、 <code>floor_segment</code>と <code>max_merge_at_once</code>というものを見つけました。前者はskewを計算する際にその値よりも小さいセグメントを <code>floor_segment</code>の値まで切り上げて計算するというもので、後者はその名のとおり一度にmergeできるセグメントの最大数を表します。</p>
<p>新しく追加されたセグメントが <code>floor_segment</code>より小さかった場合、 <code>floor_segment</code>(デフォルト値は2MB)のサイズとして計算されるため、より大きなセグメントにmergeされる可能性が上がってしまいます。またskew計算時の分母はmerge後のトータルサイズなので <code>max_merge_at_once</code>が大きければ小さいセグメントと大きいセグメントを含んだmergeのskewがあまり大きくならない可能性があり、そのようなmergeが選択されてしまう可能性が上がります。そこでこれらのパラメータの値を小さな値に変更することとしました。結果が下図です。<br />
<img src="https://storage.googleapis.com/prd-engineering-asset/2024/02/a7cfcf2c--2024-02-05-15.31.44.png" alt="" /></p>
<p>破線が変更前である前日のもの、実線が変更後です。見てのとおりスパイクが綺麗になくなっています。仮説が正しかったであろうことがわかりました。</p>
<h2>DataInputのskipBytesの詳細</h2>
<p>NativeThreadSetのaddとremoveはJVMからpread64システムコールを呼ぶ際に使われています。DataInputのskipBytesは不要な箇所をスキップするためにpread64で読んだものを捨てるという処理を実行しています。_sourceが格納されているStoredFieldのファイルはmemory mappedファイルなので不要な場所をスキップするためにファイルを読む必要など全くなく、現在のアドレスを加算するだけで事足りるはずです。実はこの修正は既にLuceneに入っており、Elasticsearchのv8以降にはその実装が使われています。<br />
<a href="https://github.com/apache/lucene/commit/84a35dfaea27581174c1104e239187112a1b5d43">https://github.com/apache/lucene/commit/84a35dfaea27581174c1104e239187112a1b5d43</a><br />
可能な限りElasticsearch v8を使いましょう。</p>
<p>先ほどはfetch phaseでパフォーマンス問題が発生する話でしたが、別のElasticsearch v7を使っているクラスタではquery phaseにおいてDataInputのskipBytesによりパフォーマンスが悪化する現象が起きていました。DataInputのskipBytesは転置インデックスのposting listをskipする際にも使われています。該当のクラスタのインデックスにはstatusがon_saleのものしか入っていなかったのですが、クエリのfilterにstatus=on_saleが指定されていました。これは全てのドキュメントが入っているposting listをスキャンすることを意味しますが、posting listはスキップリストで実装されているためそれほど高コストではないはずです(勿論ないに越したことはないですが)。ところがskipBytesはpread64を何度も呼ぶため非常に高コストな処理となってしまっていました。そこでstatus=on_saleのfilterをクエリから削除するとlatencyが以下のように劇的に改善しました。<br />
<img src="https://storage.googleapis.com/prd-engineering-asset/2024/02/143ea7c3--2024-02-05-16.15.55.png" alt="" /></p>
<h2>さいごに</h2>
<p>この記事ではJVMのプロファイラを用いてElasticsearchのlatencyのスパイクの原因を調査する方法と発見した原因とその対処法について述べました。jfrvを使って必要な部分のみ抜き出したflame graphは眺めていると色々な発見があり、またソースコードリーディングにも役立つのでおすすめです。</p>
<p>またlatencyスパイクの原因となったmergeについては発見できましたが、共用node poolに移行すると望ましくないmergeが発生する具体的な原因についてはまだ特定できていないので、今後究明していきたいと思います。</p>
<p>さいごにjfrvという素晴らしいツールを公開してくださったocadarumaさん(<a href="https://github.com/ocadaruma">https://github.com/ocadaruma</a> )ありがとうございました!</p>
- 人間によるKubernetesリソース最適化の”諦め”とそこに見るリクガメの可能性https://engineering.mercari.com/blog/entry/20240206-3a12bb1288/https://engineering.mercari.com/blog/entry/20240206-3a12bb1288/<p>Platformチームでエンジニアをしているsanposhihoです。メルカリのPlatformチームでオートスケーリング周りの課題の解決を担当しており、Kubernetes UpstreamでもSchedulingやA […]</p>
Tue, 06 Feb 2024 12:08:03 GMT<p>Platformチームでエンジニアをしている<a href="https://twitter.com/sanpo_shiho">sanposhiho</a>です。メルカリのPlatformチームでオートスケーリング周りの課題の解決を担当しており、Kubernetes UpstreamでもSchedulingやAutoscaling周りの開発に参加しています。</p>
<p>メルカリでは全社的にFinOpsに取り組んでおり、Kubernetesリソースは最適化の余地があるエリアです。<br />
メルカリではPlatformチームとサービスの開発チームで明確に責務が分かれています。Platformではサービス構築に必要な基礎的なインフラストラクチャを管理し、それらを簡単に扱うための抽象化された設定やツールなどの提供を行っています。サービスの開発チームは、それらを通してサービスごとの要件に応じたインフラストラクチャの構築を行います。<br />
サービスやチームの数も多く、そのような状況での全社的なKubernetesリソースの最適化には多くの課題がありました。</p>
<p>この記事ではメルカリにおいて、これまでPlatformが行ってきたKubernetesリソースの最適化の取り組みと、その取り組みの課題から生まれた<a href="https://github.com/mercari/tortoise" title="Tortoise">Tortoise</a>と呼ばれるオープンソースのツールの紹介をします。</p>
<h2>これまでの Kubernetes リソースの最適化の取り組み</h2>
<p>Kubernetesリソースの最適化は以下の2つに分解することができます。</p>
<ul>
<li>Podレベルの最適化: サービスの信頼性を損なわない範囲で、1Podあたりのリソース割り当て量やPod数を調節し、サービス全体で見た時の割り当てられるリソースの量を減らす。</li>
<li>Nodeレベルの最適化: 各Podから割り当て要求されたリソースをできるだけ安いコストで動作させる。</li>
</ul>
<p>後者に関しては、PlatformがKubernetesクラスターレベルの設定を変更することで最適化をできる部分が大きく、クラスター全体のスケジューリングの調節(bin packing)や価格の安いインスタンス(spot instance)への移行などが手法として存在します。直近のメルカリにおける施策だと、<a href="https://engineering.mercari.com/en/blog/entry/20230731-x86-is-dead-long-live-x86/">Instance TypeのT2Dへの変更</a>もありました。<br />
対して前者のPodレベルの最適化では、サービスごとのリソースの使用の仕方の特性に応じて、Resource Request/Limitを変更したり、オートスケーラーの設定を調整する必要があります。</p>
<p>リソース最適化には、サービスの信頼性を損なうことなく、リソースの使用を効率化することが求められ、そのように安全な最適化を行うためにはしばしばKubernetesに関わる深い知識が必要です。</p>
<p>他方、メルカリではマイクロサービスのアーキテクチャーを採用していることもあり、1000以上のDeploymentが存在し、マイクロサービスごとに開発チームも独立して存在しています。</p>
<p>このような状況で個々のサービスの開発者にKubernetesの深い知識を要求するのは難しく、その一方でPlatformが各サービスごとに最適化して回るには限界があります。</p>
<p>そのため、Platformチームがツールの提供やガイドラインの策定を行い最適化をできるだけ簡略化し、それぞれのサービスの開発チームはそれらに沿って最適化を行う、という形を取り全社的なKubernetesリソースの最適化を推進してきました。</p>
<h3>メルカリにおけるオートスケーラーの現状</h3>
<p>Kubernetesが公式に提供しているオートスケーラーには以下の二つが存在します。</p>
<ul>
<li>Horizontal Pod Autoscaler(HPA): Podのリソース使用量に応じて、Podの数を増減する。</li>
<li>Vertical Pod Autoscaler(VPA): Podのリソース使用量に応じて、Podが使用できるリソース量を増減する。</li>
</ul>
<p>メルカリではHPAがかなり普及しており、ある程度の規模を持ったDeploymentはほぼ全てHPAで管理されています。対して、VPAに関してはほとんど使用されていません。HPAはCPUに対してのみ設定されていることが多く、Memoryは手動で管理されているケースがほとんどです。</p>
<p>記事の理解が進みやすいように、HPAの設定についてのみ軽く紹介します。<br />
HPAではそれぞれのコンテナのそれぞれのリソースに対して、理想のリソース使用率(閾値)を設定することができます。以下の例では、<code>application</code>という名前のコンテナのCPUに対して、理想の使用率を60%と定義しており、HPAはPodの数をリソース使用率が60%に近くなるように調整します。</p>
<pre><code class="language-yaml">apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: <HPA_NAME>
namespace: <NAMESPACE_NAME>
//…
metrics:
type: ContainerResource
containerResource:
name: cpu
container: application
target:
type: Utilization
averageUtilization: 60</code></pre>
<p>その他、<code>minReplicas</code>と呼ばれる、Podの最低数を決めるパラメータなど、多くの補助的なパラメータが存在します。より詳細な内容は<a href="https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/">公式のドキュメント</a>を参照してください。</p>
<h3>Resource Recommender Slack Bot</h3>
<p>リソースの最適化に対して、メルカリのPlatformが内部で独自に提供している代表的なツールがResource Recommenderと呼ばれるものです。これはSlack Botで月に一度最適なリソースのサイズ (Resource Request) を計算し、サービス開発チームにお知らせします。これによりリソースの最適化を簡略化することを目的にしています。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/02/58679fa0-screenshot-2024-02-06-at-11.12.30.png" alt="Recommendation from the recommender" /></p>
<p>内部的には前述のVPAを使用しており、過去数ヶ月のVPAの推奨値から最適で安全な値を算出しています。</p>
<p>ただ、このResource Recommenderにはいくつかの課題点がありました。</p>
<p>まずは、<strong>推奨値の安全性</strong>です。推奨値は本来送られた瞬間が賞味期限で、時間が経つほど推奨値の正確性は薄れていきます。アプリケーションの実装の変更やトラフィックのパターンの変化によって、推奨値が大きく変わる可能性もあり、OOMKilledなどの危険な状況につながる危険性がありました。</p>
<p>そして、<strong>サービス開発者がこれらの推奨値を適応してくれるとは限らない</strong>点です。前述の危険性の観点から、開発者は推奨値を適応する前にその推奨値が安全か、適応後に何も問題が起こっていないかを注意深く確認する必要があり、エンジニアの時間を少なからず取ってしまうことになります。また、例えばメモリを3 GBから1 GBに減らすように推奨値が送られてきた場合、段階的に2GBを適応する、といったケースもあり、単純に推奨値がどれほど役に立っているのかの計測が難しいという観点もありました。</p>
<p>最後に、<strong>最適化はサービスが動き続ける限り終わらない</strong>点です。前述のように様々な状況の変化により、推奨される値というのは変化し続けます。開発者は一度Resource Recommenderに即してResource Requestを調整したら最適化が終了するのではなく、定期的に調整し続ける必要があります。</p>
<h3>HPA の最適化</h3>
<p>上記のResource Recommenderの課題とは別に、大きな問題点となっているのがHPAの最適化です。<br />
HPAに管理されているリソースに関しては、基本的にリソースのサイズではなく、HPAの設定を最適化する必要があります。しかし、Resource RecommenderはHPAの設定の推奨値の算出に対応していません。<br />
前述のように、メルカリでは規模の大きなサービスはほぼHPAを持っており、CPUをターゲットにしていることから、クラスターで使用されているCPUのほとんどはResource Recommenderによって最適化できないことを意味しています。</p>
<p>まず、最適化のためにはHPAに設定している理想のリソース使用率(閾値)をサービスの信頼性を損なわない範囲で上げる必要があります。<br />
また、設定された閾値が十分に高いとしても、実際のリソース使用率が閾値に達していないというシナリオは多く存在し、その場合閾値以外のパラメータやResource Requestなどを調節する必要が出てきます。</p>
<p>HPAの最適化はかなり奥が深く、別でもう一本記事がかけるくらいにはかなりの知識を要します。(<a href="https://speakerdeck.com/sanposhiho/dont-try-to-tame-your-autoscalers-tame-tortoises">このスライド</a>ではHPAの最適化について難しさと考慮すべきシナリオが軽く説明されています。興味のある方は確認してみてください。)<br />
その複雑性からResource Recommenderに単純に組み込むことは難しく、とはいえ膨大な数のHPAに対して多くのチームに定期的に手動の最適化を行い続けてもらう、というのは現実的ではありません。</p>
<p>…ここまで辿り着いて私たちは気がつきました。「…無理じゃね?」と。</p>
<p>現状のHPAとResource Recommenderの構成では、クラスターを最適化された状態に維持するにはどうしても<strong>手動</strong>で<strong>複雑</strong>な作業が全てのチームで<strong>定期的に</strong>、そして<strong>永遠に</strong>必要になります。</p>
<h2>Tortoiseを用いたリソース最適化</h2>
<p>そこで開発されたのが、<a href="https://github.com/mercari/tortoise">Tortoise</a>です。(Tortoise: 日本語でリクガメの意味です)</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/02/2d7be0f8-tortoise_big-scaled.jpg" alt="Tortoise" /></p>
<p>このTortoiseは可愛いだけではなく、Kubernetesのリソース管理と最適化を全て自動で行なってくれるように訓練されています。</p>
<p>Tortoiseは過去のリソースの使用量や過去のレプリカの数を記録しており、それを元にHPAやResource Request/Limitを最適化し続けます。詳しいリコメンデーションのロジックが知りたい方は、<a href="https://github.com/mercari/tortoise?tab=readme-ov-file#documentations">公開されているドキュメント</a>を参照してみてください。Tortoiseが単なるHPAやVPAのラッパーではないことが理解できると思います。</p>
<p>前述のようにこれまでサービスの開発チームがリソース/HPAの設定や最適化を行なっていましたが、Tortoiseはそれらの責務をサービスの開発チームからPlatformチームに完全に移すことを意図しています。サービス開発チームはTortoiseを一度セットアップすることでリソースの管理のことを完全に忘れることができ、もしTortoiseによって十分に最適化されていないマイクロサービスがあればPlatformがTortoiseの改善を行います。<br />
Platformでは、メルカリの全てのPodをTortoiseによって最終的に管理することを目標にしています。</p>
<p>ユーザーは以下のようにCRDを通して、Tortoiseを設定します。</p>
<pre><code class="language-yaml">apiVersion: autoscaling.mercari.com/v1beta3
kind: Tortoise
metadata:
name: lovely-tortoise
namespace: zoo
spec:
updateMode: Auto
targetRefs:
scaleTargetRef:
kind: Deployment
name: sample</code></pre>
<p>Tortoiseは非常にシンプルなユーザーインターフェースにデザインされており、ほとんどのサービスに対する設定は上記で完了します。その後、Tortoiseは自動でHPAやVPAなどの必要なものを作成し、オートスケールを開始します。</p>
<p>HPAは複数のパラメーターがユーザーに対して公開されています。これはユーザーに対して柔軟な設定を可能にする一方、現状のメルカリのように、HPAの設定やResource Requestを改善しないとHPAが本来のパワーを発揮できない、という状況に繋がり得ます。<br />
メルカリでは運の良いことに、ほとんどのマイクロサービスがGoで書かれており、gRPC/HTTP サーバーであり、内部で公開されているマイクロサービスのテンプレートをベースに作成されています。そのため、HPAの設定もほとんどのサービスで非常に似ており、サービスのリソース使用量の変化やレプリカ数の変化などの特性も非常に似ています。<br />
そのため、HPAの複数のパラメーターをTortoiseの背後に隠し、Tortoise側で共通のデフォルト値を与え、内部のリコメンデーションのロジックを通してそこから最適化をし続ける、というのがうまく働いています。</p>
<p>また、シンプルなユーザーインターフェース(CRD)とは打って変わり、Tortoiseは<a href="https://github.com/mercari/tortoise/blob/main/docs/admin-guide.md">クラスター管理者向けの多くの設定</a>を備えています。<br />
これによって、そのクラスターにおけるサービスの振る舞いを元に、クラスター管理者が全てのTortoiseの挙動を管理するということが可能になっています。</p>
<h3>Tortoiseへの安全な移行と検証</h3>
<p>前述のようにTortoiseはHPAやVPAの代替となるツールです。Tortoiseを作成することでHPAは必要がなくなる一方で、前述のようにMercariには非常に多くの数のDeploymentがHPAと共にすでに動作しています。<br />
この状況でHPAからTortoiseに移行するには、Tortoiseの作成からHPAの削除など、煩雑なリソース操作を安全に行う必要がありました。</p>
<p>そのような移行をできるだけ簡略化し安全な移行を確保するために、Tortoiseには「既存のHPAをTortoiseに管理させる」ための機能が実装されています。</p>
<pre><code class="language-yaml">apiVersion: autoscaling.mercari.com/v1beta3
kind: Tortoise
metadata:
name: lovely-tortoise
namespace: zoo
spec:
updateMode: Auto
targetRefs:
# 既存のHPAを指定することで、Tortoiseは新たなHPAを作成する代わりに、このHPAを最適化し続ける。
horizontalPodAutoscalerName: existing-hpa
scaleTargetRef:
kind: Deployment
name: sample</code></pre>
<p><code>horizontalPodAutoscalerName</code>を使用することで、既存のHPAをTortoise-managedなHPAにシームレスに移行することができ、移行のコストを下げています。</p>
<p>現在私たちはメルカリの開発環境で複数のサービスをTortoiseに移行して、安全性の検証を行っています。TortoiseはDryRunを行うための<code>updateMode: Off</code>を備えており、<a href="https://github.com/mercari/tortoise/blob/main/docs/user-guide.md#updatemode-off">Tortoise Controllerから公開されているメトリクス</a>を通して、推奨値の妥当性を検証することができます。</p>
<p>開発環境では、かなり多くの数のサービスですでにOffモードのTortoiseによる検証が始まっており、50ほどのサービスではすでにTortoiseを用いたオートスケーリングが使用され始めています。<br />
本番環境での検証、そしてTortoiseへの移行も近い将来に計画されており、Tortoiseはより洗練されたツールとなっていくことでしょう。</p>
<h2>まとめ</h2>
<p>この記事ではメルカリのこれまでのKubernetesリソース最適化の取り組みと、そこに見えた課題から生まれたTortoiseと呼ばれるツールを紹介しました。</p>
<p>メルカリではPlatformで一緒に働く仲間を探しています。<br />
一緒にCI/CDを改善したり、抽象化を色々作ったり、リクガメを飼育したり(!?)しませんか?<br />
興味のある方は<a href="https://apply.workable.com/mercari/j/111722DA96/">こちら</a>からどうぞ!</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2024/02/3ecbac89-screenshot-2024-02-06-at-11.16.02.png" alt="Have a better life with cute tortoises" /></p>
- 品質の可視化への取り組み:バグ管理の事例紹介https://engineering.mercari.com/blog/entry/20240122-7865286e4d/https://engineering.mercari.com/blog/entry/20240122-7865286e4d/<p>こんにちは、メルカリのQAエンジニアのFunakiです。今回は品質改善と可視化のための取り組み、特にバグ管理(Bug Management)に焦点を当てて、QAチームがどのような活動を行っているのかをご紹介します。 我々 […]</p>
Tue, 23 Jan 2024 11:00:40 GMT<p>こんにちは、メルカリのQAエンジニアのFunakiです。今回は品質改善と可視化のための取り組み、特にバグ管理(Bug Management)に焦点を当てて、QAチームがどのような活動を行っているのかをご紹介します。<br />
我々は2018年頃からバグ管理の取り組みを始め、試行錯誤を重ねてきました。製品の品質に関する課題を抱えた方や、品質の可視化を進めたいと考えている方にとって、当ブログが現状を改善するきっかけになれば幸いです。</p>
<div style="text-align: center">
<img src="https://storage.googleapis.com/prd-engineering-asset/2024/01/9de564f6-738-300x300.jpeg" width="300" /></p>
<p>(出典:<a href="https://loosedrawing.com/">https://loosedrawing.com/</a>)</p>
</div>
<h2>なぜBug Managementを実施しているのか?</h2>
<p>我々はプロダクトの品質を推測するために、バグチケットの管理や可視化するすることを目指しています。品質を推測するために、品質の可視化するための環境構築(ダッシュボード)や、バグのチケット管理ルール(Bug Management Guideline)を作成しています。</p>
<p>もともと、メルカリでは各開発チームが独自にバグの管理をしていました。多くのチームではJIRAを使用してしましたが、JIRA以外で管理をしているチームもありました。<br />
また、チームストラクチャの再編により軽微なバグの担当者がいなくなり、長期間未対応のまま放置されることがありました。<br />
それらの影響でバグチケットの全容が十分に把握できなくなっていました。</p>
<p>これらの問題を改善していくためにBug Mnagement を実施しています。</p>
<h2>Bug Management Guideline とは?</h2>
<p>我々はバグチケットを健全に管理出来るようにして、品質の見える化をするために、Bug Management Guideline を作成して開発チームへ展開をすることにしました。<br />
ルールを作ると、守らなければならない事項が多くなりがちで、結果として誰もルールを守れなくなることがあり得ます。そうならないよう、私たちは以下の最低限の目標を設定しました。</p>
<pre><code>目標:
1. バグ管理環境をJIRAへ統一
2. バグの発生状況や修正の優先順位が判断できること
3. バグが長期間放置されないこと</code></pre>
<p>目標1を達成するために、まずは各開発チームが使用しているバグ管理ツールを調査し、JIRAを使用していないチームにはJIRAへ変更をお願いしていきました。</p>
<p>目標2を達成するために、バグ修正の優先順位やバグの発生傾向などを分析が出来るように、バグチケットに情報を記載するフィールドを追加しました。</p>
<p>目標3を達成するために、バグチケットの有効期限を設定しました。有効期限が切れたバグチケットが無いか定期的チェックし、期限が切れたチケットはクローズするか、優先順位を上げてすぐにに修正するかを判断するルールを策定しました。</p>
<p>Bug Management Guideline を作成し、各チームが共通の環境とルールを使用することで、Bugチケットの全容が把握するための準備が整いバグチケットの状態の可視化をすることが出来るようになりました。</p>
<h2>バグチケットの状態の可視化</h2>
<p>JIRAにもチケットの情報を可視化するダッシュボードの機能がありますが、我々がチェックしたい情報を可視化する事は出来ませんでした。そのため、当初はJIRAで管理されたバグチケット情報をLookerで可視化していました。<br />
JIRAのバグチケットの情報は直接Lookerで利用することが出来ないため、JIRAのバグチケット情報を<a href="https://trocco.io/lp/index.html" title="trocco">trocco</a>を利用してBigQueryにインポートし、BigQueryの情報からLookerで様々なグラフを作成してダッシュボーを構築していました。<br />
以下の画像は、取り込まれたデータの流れと作成したダッシュボードのサンプルです。</p>
<div style="text-align: center">
<img src="https://storage.googleapis.com/prd-engineering-asset/2024/01/b81a2423--2024-01-22-11.34.12.png" />
</div>
<p>過去の取り組みについて少し紹介した関連記事については、以下のURLをご覧ください。<br />
関連記事: <a href="https://engineering.mercari.com/blog/entry/20201221-79f9540f3a/" title="メルカリのQAエンジニアの取り組み2020">メルカリのQAエンジニアの取り組み2020</a></p>
<h2>可視化(ダッシュボード)の改善</h2>
<p>ダッシュボードは開発チーム毎に作成し運用していましたが、メルカリの開発体制やメンバーが頻繁に変動するため維持管理が大変でした。さらに、troccoやBigQueryの環境はQAチームが構築した環境では無かったため、環境のメンテナンスやアクセス権管理、グラフの更新・追加のためのデータ変更が複雑になってしまいました。そのため、メンテナンスがしやすい環境に切り替えることを検討しました。</p>
<p>バグ管理を更に効率化する方法を調査していたところ、データ取得が手軽で、グラフの資料作成も簡単に行える新しい手法を見つけました。具体的には、「<a href="https://marketplace.atlassian.com/apps/1220382/jira-cloud-for-google-sheets-official?tab=overview&amp;hosting=cloud" title="Jira Cloud for Sheets">Jira Cloud for Sheets</a>」というスプレッドシートのアドオンと、「<a href="https://cloud.google.com/looker-studio?hl=ja" title="Looker studio">Looker studio</a>」 というデータの分析や管理、レポート作成が簡単に行えるBIツールを使うことに決めました。</p>
<p>Jira Cloud for Sheetsは、JIRAの開発元であるAtlassianが提供している拡張機能で、JIRAで管理しているバグ情報をスプレッドシートに直接取り込むことが可能になります。スプレッドシートに取り込んだJIRAの情報は、関数を使い情報を分類したり、集計することで自分たちの知りたい情報を作成することができま。</p>
<p>またJIRAは今の情報しか取得することが出来ません。そこでGoogle Apps Script(GAS)を使用して集計データの履歴を日別に作成しました。履歴を作成したことで、バグチケットの作成や対応件数の傾向を確認することが出来るようになりました。<br />
Looker Studioは、スプレッドシートからデータを直接読み込んでグラフや表を好きなレイアウトでダッシュボードを作成することが出来ました。そのため、他チームに依存することが無くなったため、任意のタイミングでダッシュボードメンテナンスが可能にりました。<br />
また、データの取得や表示データの更新は、アドオンの機能やGASのスケジューリング機能を使って定期的に実行しているため、毎日自動的に情報が更新されるようになっています。</p>
<p>これらの改善により、マニュアルでのメンテナンスが最小限になり、バグ追跡と分析もスムーズに行えるようになりました。</p>
<div style="text-align: center">
<img src="https://storage.googleapis.com/prd-engineering-asset/2024/01/74087b01--2024-01-22-11.35.13.png" />
</div>
<h2>今後の Bug Management</h2>
<p>Bug Management Guidelineを作成し、Looker Studioでの可視化のおかげで、バグチケットが修正されずに残っている場合や、いつ何件のバグチケットが作成され、クローズされたかなどが一目でわかるようになりました。定期的にバグチケット作成からの経過時間をチェックし、優先順位の見直しをすることで、バグチケットが長期間放置されなくなりました。</p>
<p>これらの取り組みにより、適切にバグチケットが管理することができるようになりつつあります。しかし、開発体制の再編や新しいメンバーの増加など影響で、取り組みがリセットされないようにBug Managementの周知が必要です。さらに、バグ発見の傾向や件数から製品の品質を推測し、バグの作り込みを防止する施策の検討などを続けていく予定です。</p>
- メルカリEngineering Roadmapの作成とその必要性https://engineering.mercari.com/blog/entry/20231225-creating-mercari-engineering-roadmap/https://engineering.mercari.com/blog/entry/20231225-creating-mercari-engineering-roadmap/<p>はじめに こんにちは、メルカリの日本リージョンのCTOを担当している@kimuras と申します。2023年4月にCTOに就任して現在Marketplace、Merpay、Mercoinの技術的な責任者を担当しています。 […]</p>
Mon, 25 Dec 2023 16:30:48 GMT<h2>はじめに</h2>
<p>こんにちは、メルカリの日本リージョンのCTOを担当している@kimuras と申します。2023年4月にCTOに就任して現在Marketplace、Merpay、Mercoinの技術的な責任者を担当しています。本稿では、この1年間で注力してきた、Engineering Roadmapの作成についてお話したいと思います。内容によっては、ある程度の組織の規模感にならないと適さない内容となってしまうかもしれませんが、サービスの方向性やそれに合わせたエンジニアリング組織の作成について、今後整理しなければならない局面でご参考にしていただけたら幸いです。</p>
<h2>メルカリのロードマップとは</h2>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/a0cc4591-mercari-roadmap-image.png" alt="" /></p>
<p>メルカリには、グループ全体の指針となるグループロードマップ(以下ロードマップと呼びます)があります。このロードマップのおかげで、私たちは今後進むべき方向が明確になり、社員全員が提供したい価値についての共通の認識を持つことができます。ロードマップは単なる実現したい事項のTODOリストではなく、私たちのミッションやビジョンを正確に理解するための重要なツールです。メルカリのロードマップについては、こちらの<a href="https://mercan.mercari.com/articles/38932/">メルカンの記事</a>を参照してください。</p>
<h2>Engineering Roadmapの必要性</h2>
<p>ロードマップがうまく運用されていることで、わたしたちはこれまでに多くの新しい価値を提供してきました。その中にはメルカードやMerpayのような時間もかかり、難易度も高いプロジェクトも含まれています。しかし、エンジニアリング組織としては、このロードマップに対してより先行して技術的な準備ができていたら、より高速かつ計画的にビジネス展開をできたのではないかと感じることがありました。</p>
<p>事業の未来がロードマップで示されているので、エンジニアリングとしてはその道標に対して、それを実現するための<a href="https://engineering.mercari.com/blog/entry/20220908-robust-foundation-for-speed-series-present-and-future/">Foundation</a>やPlatformを事前に提供できることが理想的です。しかし、これまでメルカリグループでは各Divisionごとに個別のEngineering Roadmapが存在していたものの、全社横断でのものは存在しませんでした。(※ 12/26 10:30 初稿ではロードマップが一切存在しないようにとれる表現になっていましたが、正しく修正しました)</p>
<p>メルカリではビジネスや開発者をスケールさせるためにMicroservices Architectureを導入したり、<a href="https://about.mercari.com/press/news/articles/20220511_indiacenterofexcellence/">インドの開発拠点</a>を作ったりと、チャレンジングなことを通じて継続的なエンジニアリングの改善を行ってきました。しかし、事業のロードマップに対して、Engineering Roadmapも同時に用意することで、よりエンジニアリングも含めたVisionがクリアーになり、効率性が上がるのではないかと考えました。</p>
<h2>Engineering Roadmapがあることのメリット</h2>
<p>前提として、私たちの開発のレイヤーは主にProduct、Foundation、Platformの3つのレイヤーに分かれています。Product開発は主にBFF、BackendやFrontendの開発を含めたFeature開発となります。そのひとつ下のレイヤーであるFoundationはLogisticsやTrsansactionやPaymentなどのProductとは疎結合ではあるものの、さまざまなサービスから呼ばれる重要なバックエンドのAPI群となります。そして、Platformはさらに一番下のレイヤーであり、Microserviceを容易に作るためのMicroservices PlatformやCI/CD、Infrastracture、Networkなどのすべてのサービスを支える基盤となっています。したがって、下のレイヤーになるほど支えているサービスが多くなるため、PlatformやFoundationは上位レイヤーのことを考慮しなくてはならないことが多く、開発や変更の時間軸は長くなってしまいます。</p>
<p>このような私たちの状態を前提として、Engineering Roadmapが存在することの意義を以下に述べていきます。なので、序盤にも述べたように、スタートアップのような開発の初期段階のフェーズやFoundation領域が小規模なサービスでは、本稿で述べるEngineering Roadmapの作成する意義や戦略とは違った打ち手の方が良い可能性があることをご容赦ください。</p>
<div align="center">
<img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/f0e13b19-mercari-structure-layers.png" />
</div>
<h3>スケジュールに対する期待値調整が容易になる</h3>
<p>抽象的な表現となってしまいますが、何か新しい価値提供を実現するためのリアーキテクチャや、新たにFoundation/Platformを開発をするには、想定以上に時間がかかってしまうことが一般的に多くあります。開発を計画的に行わず、間に合わせでライブラリを少し修正するだけですませてしまったり、本来であればアーキテクチャを改修しなければならないところを、改修せずに無理に既存のアーキテクチャに新機能を詰め込んでしまったがゆえに、後のメンテナンス性が落ちてしまったり、リファクタリングが困難になることが起こりがちです。</p>
<p>したがって、エンジニアリングとしては極力新しい要件仕様に対して、適切なFoundation/Platformを新規で開発したり、リアーキテクチャをしたうえで新規機能を実装することが理想的です。しかし、これらの開発には調査や設計、実装方針について関係者とコンセンサスをとるなど、実現するのに数日どころか数ヶ月、あるいは年単位で時間がかかってしまうという問題があります。</p>
<p>このため、新規サービスの開発を始めるタイミングで、Product開発と並行してリアーキテクチャやFoundation/Platform改善をおこなうと、時間軸が合わなかったり、スペックの調整をしながら開発することで要件漏れや大きなバグを作ってしまうことの原因となってしまいます。加えてProduct開発に対してFoundation/Platform側の対応が遅れてしまい、リリーススケジュールに悪影響を与えてしまうこともしばしば発生してしまいます。</p>
<p>ただ、上述のように事業のロードマップが示されている状況においては、エンジニアリングとしてもそれを実現するためのFoundation/Platform開発を事前に計画性をもって行うことができれば、よりスムーズに開発することができるし、メンテナンス性や安全性もより担保された開発を行うことができます。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/bf5304ac-mercari-planning-with-roadmap.png" alt="" /></p>
<h3>Engineeringの改善施策のコンセンサスを得ることができる</h3>
<p>上述のようにリアーキテクチャやリファクタリング、Foundation/Platform開発などのエンジニアリングに関する改善施策は中長期にわたることがしばしばあります。このため、明確な目的意識を持って施策を実施しなければ、途中経過でプロジェクトの意義を問われることや、プライオリティを下げざるを得ない状況となってしまうことが、残念ながらよく発生します。エンジニアリングには各改善プロジェクトの意義について説明責任はあるものの、事前にコンセンサスがとれておらずに説明の難易度が上がったり、プライオリティが変更されてしまうことは生産性に悪影響があるし、モチベーションにも大きな影響を与えてしまいかねません。</p>
<p>しかし、エンジニアリング主導の改善施策についても、始める前にそれぞれの意義やゴールを明確化して、かつロードマップにアラインできていれば、たとえ中長期な開発であってもステークホルダーからも賛同を得られ、サポートを得ることができるはずです。時には事業のロードマップにアラインすることが難しい中長期の改善施策、例えばMicroservices Architectureの根本的なアーキテクチャの改善や、BCPの改善などについても、ゴール設定と得られるメリットを明確化して、Engineering Roadmapとして事前にステークホルダーや経営から同意を得られていれば、ストレスなく改善プロジェクトを継続することができます。</p>
<h3>先を見通したアーキテクチャを作ることができる</h3>
<p>基本的にシステムアーキテクチャはビジネスの成長やエンジニアリング組織の規模感、ビジネスの方向性などにあわせて常に改善を続けなければなりません。加えて、極力メンテナンス性や拡張性を高くすることで継続的に新たなニーズに応えられることが理想的です。</p>
<p>しかし、ビジネスの方向性が定まっていなければ、ある程度は想像でシステムの拡張性を担保しなければならず、仮にニーズを満たすことができなれけば、近い将来にリアーキテクチャを実施しなければならなくなります。</p>
<p>一方、事業ロードマップやEngineering Roadmapが作成されていれば、3年ほどの近い将来については概ね方向性がわかっているため、拡張性の観点で確度の高い設計をすることができます。これは、設計を担当するアーキテクトやTech Lead(技術的なリーダーのことであり、以下TLと呼ぶ)に限らず、エンジニアが日々のコーディングでの細かい意思決定を手助けすることができるため、すべてのエンジニアが意識的に将来を見据えた設計を心がけられるようになることが好ましい。</p>
<p>例えばIDに関する設計をしているときに、将来的にどのような事業展開をするのか、またパートナー企業が存在するようなビジネスをするときにパートナーアカウント、あるいはID連携が必要になる事業計画がある、といった計画が事前にわかっていれば、それらのニーズに合わせたアーキテクチャの設計ができます。これはFoundation/Platformやインフラストラクチャなどさまざまな要素技術にとっても重要であり、ビジネス成長には欠かせないことです。</p>
<h3>Visionに対する解像度が深まる</h3>
<p>Visionを作ることは、組織にとってとても大事なことです。Visionを示すことによって、これから先に新たにお客さまに提供したい価値や、組織のありたい姿などを掲げて、組織で一体感を持ってタスクに取り組むことができます。</p>
<p>しかし、Visionだけではそれをどういう手順や手段で実現していくかはわからず、説明される方もうまく咀嚼できないことがあります。ありたい姿をVisionで示し、それに対してどのようにそれを実現していくかをEngineering Roadmapに記載することで、Visionに到達するまでのストーリーが各エンジニアにも伝わり、より理解を得ることができます。</p>
<p>これは説明する側のコストも下がりますし、ミスコミュニケーションを防ぐためにも重要だと考えています。</p>
<h2>Engineering Roadmapを作るためのTips</h2>
<p>Engineering Roadmapの必要性や効果がわかったところで、次に実際にロードマップを作るためのTipsについて説明します。ここでは主に2通りのアプローチを突き合わせる手法について紹介します。</p>
<h3>まずは大胆な理想像とVisionを作る</h3>
<p>自分の場合は、あまり多くのことを気にしすぎて進められなくなるよりも、まずは実現可能性や周りの考えなどは考慮せずに、大胆な理想像を決めてしまいます。</p>
<p>実際にVisionやEngineering Roadmapを作ることは容易ではありません。理想的なゴールは何なのか、ステークホルダーはどのようにゴールを考えているのか、お客さまは何を求めているのか、それを実現することが可能なのか。それらの多くの関連する要素を考慮すると、なかなかVisionやロードマップを定めることができなくなってしまいます。</p>
<p>ただ自分は、あえて実現することが難しいのではないかと思うくらいの大胆で理想的なゴールを決めます。それから、それを実現するためのロードマップを作りながら実現可能性を考慮して、Visionを少しずつ現実的なものに落とし込んでいくことで、多少難易度が高いが、納得感のある形に落ち着くことができます。万人には当てはまらないとは思いますが、まずはあまり固くならずに、大胆で理想的なVisionを書き出してみると良いと思っています。</p>
<h3>TLとのコミュニケーション強化</h3>
<p>ある程度の組織規模のCTOの立場になると責務のスコープが広くなり、開発現場での解くべき課題や理想的な状態などが把握しづらくなってしまうことがあります。</p>
<p>普段からVPoEやEMとのコミュニケーションを取ることで、組織課題を把握することができますが、より開発現場に近い課題感を把握するためにはTL(TLを指定していない場合はエンジニアチームをリードしている立場の方が良いでしょう)とのディスカッションをすることで情報を得ることができます。</p>
<p>TLとEMとのディスカッションをすることで開発現場でも納得感の高く、かつ的確に組織課題を捉えたRoadmapを作成することができると、自身の経験から強く感じています。</p>
<h3>理想は「現実的でワクワク感」があること</h3>
<p>ここまで2つのアプローチについて紹介しましたが、進め方としては、まずはフィージビリティを気にせずに、技術的にチャレンジングでかつビジネスに貢献するようなVisionを「トップダウンのアプローチ」で作成してみます。しかし、これだけでは現実離れしすぎてしまうかもしれませんし、本質的な開発現場の課題をとらえられず多くのエンジニアから共感を得られないかもしれません。そこで、各領域での本質的な課題を把握しており、強いVisionを持っているTLや、組織課題を理解しているVPoEやEMからの「ボトムアップ」の意見をぶつけあうことで、チャレンジングでかつ現実的なVisionやEngineering Roadmapを作成できることが理想的です。</p>
<p>このトップダウンとボトムアップの意見をすり合わせることによって、私たちのこれからの開発を一人一人のエンジニアが自分事として捉えて、積極的にコメントをくれるようになり、かつコミットしてくれるようになります。CTOとしては、エンジニアがこれまでに挑戦したかったけど、挑戦できなかったような難しくもおもしろい課題に挑戦するための理詰めを支え、最終的にその挑戦に対してスポンサーとなって一緒に実現しようとする姿勢が大事なのだと思います。このように難しい課題であっても、みんなで同じ方向を見て、一緒に解決していくことで、エンジニアたちのワクワク感が生まれ、結果的に自信を持って自分たちで誇りに思える技術を使い、お客さまに新たな価値を提供できるのだと信じています。</p>
<h2>最後に</h2>
<p>最後までお読みいただき、ありがとうございました。Engineering Roadmapは作っただけではなくて、今後どのように運用していくか、進捗させていくか、あるいはEngineering Roadmap自体を更新していくかもとても大事だと思います。運用していく中での困難や発見があれば、また記事にしてまとめてお伝えしていきたいと思います。</p>
- Offsitesのワークショップでの4つの工夫https://engineering.mercari.com/blog/entry/20231224-a4046d214c/https://engineering.mercari.com/blog/entry/20231224-a4046d214c/<p>この記事は Merpay Advent Calendar 2023 の 24 日目の記事です。 こんにちは、メルコインの @pooh です。 メルカリグループでは金融事業を営んでいるメルペイとメルコインのEngineer […]</p>
Sun, 24 Dec 2023 10:00:25 GMT<p>この記事は <a href="https://engineering.mercari.com/blog/entry/20231124-merpay-advent-calendar-2023/">Merpay Advent Calendar 2023</a> の 24 日目の記事です。</p>
<p>こんにちは、メルコインの @pooh です。</p>
<p>メルカリグループでは金融事業を営んでいるメルペイとメルコインのEngineering Manager(EM)で普段とは別の場所に集まって1日集中して議論をするOffsitesを定期的に実施しています。</p>
<p>この投稿ではOffsitesそのものを紹介するのではなく、Offsitesでよく実施されるワークショップ(参加型作業)についての4つの工夫を紹介します。<br />
複数人が集まって、何かのテーマについて意見を出し合い、意見をまとめて発表するというワークショップはよくあると思います。これから紹介する方法を使用することでより活発な成果が望めます。</p>
<p>本記事では私の経験とメルカリという組織での実践上の知見を書いています。そのため、組織ごとに別のよりよいやり方もあると思いますので、参考程度にそういう考えもある、ぐらいの気持ちで読んでください。</p>
<h1>1.付箋に書いてから発表する</h1>
<p>ワークショップではチームに分かれて、チーム内でディスカッションをして意見やアイディア出しをしていきます。例えば、EM Offsitesでは「2023年10月〜12月を振り返って良かったこと・悪かったこと」といったテーマを設定し意見を出しあいました。このときに思いついた人から口頭で順次発表していくことがあります。ここで1つ目の提案となります。</p>
<p><strong>最初に時間をとって各自で意見やアイディアを手元の付箋に書く</strong></p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/9da1844b-img_0925-225x300.jpg" alt="" /></p>
<p>各自で書き出しをした後に発表をしていくとよいかもしれません。最初に付箋に書き出すことによる期待効果は次の通りです。</p>
<ul>
<li>考えて書き出しているので意見がまとまる</li>
<li>発表に時間がかからない</li>
<li>書いている間は他人の意見が見えないこと</li>
</ul>
<p>1人ずつ書かずに口頭で発表する場合、2、3人目からは「私もそうなんですけど…」という意見が出やすくなったり、他の人の意見に左右される可能性があります。最初に付箋に書いて発表することで、主体的に自分で考えたアイディアや意見を発表できるようになります。<br />
付箋にあらかじめ書いてあることを読み上げるので、1つの発表が長くなったり、発表の始めと終わりで内容が異なる状態を防ぐことができます。</p>
<h2>オンラインとのハイブリッド開催の時</h2>
<p>オフラインとオンラインのハイブリッド開催をする時には、付箋ではオンライン参加者には見えずに不便でした。ハイブリッド開催の時にはオンラインホワイトボードを使いました。オンラインホワイトボードを使う場合でも、各自で考えている時には別のファイルやPC上のエディタを使って他の人から見えないようにすると付箋に手書きと同じ効果を得られそうです。</p>
<p>この投稿の本題ではないのですが、ハイブリッド開催の時にはPCのマイクとスピーカーでは音量面でオンライン、オフライン相互に聞き取りにくいことがあります。外付けのスピーカーとマイクの用意をお勧めします。マイクロソフトの「Modern USB-C Speaker」は持ち運びがしやすく、音量も大きく良かったです。</p>
<h1>2.順番に1つずつ発表する</h1>
<p>グループディスカッションでは付箋に書き出して発表します。発表する際には1人ずつ順番に発表していき、最終的に各自で書いたものをまとめてグループの成果として発表する形式を取ります。ここで2つ目の提案となります。</p>
<p><strong>順番に1つずつ自分の書いた付箋を読む。1つ読んだら次の人が読んで、を繰り返して何周か回す。自分の番がきて、もうすべて自分の書いたものを読んでしまった人はパスしていい</strong></p>
<p>この方法で発表していくと1人の意見で全体の雰囲気が動くのではなく、みんなの意見が順番に出てくるので発言の機会が均一になります。意見が平均的に出せるようになるため雰囲気が悪くなりません。「けっこういい」とみんなが共感できる意見が色々な人から出てくるため、雰囲気がよくなります。</p>
<p>発表するときには書いたことを発表し時間をかけないようにします。書いた理由や背景などの説明はせずに付箋に書いたことを発表します。小さな付箋を使うと単語しか書けないので大きな付箋を使い単語ではなく発表する内容を書いておきます。</p>
<p>EM Offsitesでは「良かったこと」ではつぎのような発表がありました。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/e01ede67-img_0926-300x225.jpg" alt="" /></p>
<ul>
<li>メルペイのTech PRで新しい取り組みができている</li>
<li>リリースした口座入金経由で直接メルコイン口座に残高反映する機能が使われている</li>
<li>メルコインのEngineer All-Hands(エンジニアメンバーを対象とした毎月開催している全社会)が前回評判よかった気がする</li>
</ul>
<p>「悪かったこと」ではつぎのような発表がありました。</p>
<ul>
<li>リソース不足でPJのスケジュールが遅延したり厳しいスケジュールになった</li>
<li>プラットフォーム機能開発が進まなかった</li>
<li>情報共有が不足していた</li>
</ul>
<h1>3.問題を「どのようにすれば」に置き換える</h1>
<p>ワークショップでは「xxxxに関してどんな問題点や懸念点があるか」や「悪い点」などをリストアップすることがあります。それをリストアップしていくと、当然ながら「なかなか難しいね」となることがあります。ここで3つ目の提案になります。</p>
<p><strong>問題を発表した後、それぞれを「どのようにすれば〜〜〜か?」の疑問に言い換える</strong></p>
<p>効果を説明する前に具体例を出してみます。<br />
「プラットフォーム機能開発が進まなかった」という問題を発表した場合、「どのようにすればプラットフォーム機能開発ができるか?」となります。</p>
<p>このように「どのようにすれば」の質問文にすることで、答えを考えられるようになります。「問題なのはわかったから、改善策を言って欲しい」という言葉をたまに耳にすることがあります。課題を質問文に言い換えてもらうことで、自然と答えを考え始められます。</p>
<h1>4.もっと面白い質問にする</h1>
<p>「どのようにすれば」の質問文にすることで、答えを考えてしまう状況を作れました。もう一歩進められるようにします。</p>
<p><strong>もっと面白い質問のかたちにして、もっといろんな人が考えてくれるようにする</strong></p>
<p>もっと面白い質問にするために次のようにします。</p>
<ul>
<li>「これが起こったらいいな〜」と思うような文章にする</li>
<li>「日本一」「世界一」と言った言葉をいれる</li>
</ul>
<p>先ほどの質問文「どのようにすればプラットフォーム機能開発ができるか」を「どのようにすれば日本一便利なプラットフォーム機能を開発できるか?」に変えてみます。これでただ単に課題を解決するだけではない、ベストな素晴らしい解決策を多くの人が考え始めたはずです。</p>
<h1>まとめ</h1>
<p>この記事では、つぎの4つの工夫を紹介しました。</p>
<ol>
<li>付箋に書いてから発表する</li>
<li>順番に1つずつ発表する</li>
<li>問題を「どのようにすれば」に置き換える</li>
<li>もっと面白い質問にする</li>
</ol>
<p>これらの工夫のうち、1つ目と2つ目は実際に私がワークショップでファシリテーターをする時にしていることです。EM Offsitesのときにも利用しました。3つ目はOffsitesで使ったわけではないですが、日頃心がけています。自分で使用したり、問題を提起してくれた人に使ったりしてます。4つ目はなかなかできていないです。</p>
<p>実際に1つ目と2つ目について利用したメンバーに感想を聞いてみました。</p>
<ul>
<li>みんなの意見を聞くことができる & たくさん話したい人は後半話す時間がある、ということで時間効率を最大化できてると思いました</li>
<li>参加者の意見を満遍なく聞けることと、意見の数によってはスキップする自由みたいなところもあったので、意見が出やすくてよかったのでは無いかと思いました</li>
<li>ワークショップの目的の認識合わせが不十分だったので、手法以前の改善ポイントがあったように思います。その点からワークショップとしては消化不良でした。</li>
</ul>
<p>手法としてはポジティブなフィードバックをもらえました。一方でワークショップのゴールが何か、ワークショップが終わった後に何を達成したいのかの認識合わせを最初に実施することの重要性を再認識しました。</p>
<p>ここで紹介したことは、大橋禅太郎氏の「すごい会議」で紹介されていたものになります。この投稿を書くにあたって改めて読みましたが、同僚にも勧めたいと思える書籍です。</p>
<p>書籍では手法も紹介していますが、ワークショップの目的の認識合わせをする「このワークショップが終わったときにどんな成果をあげることを期待しているか」から意見出しをしていました。</p>
<p>会議進行は方法によって効果的になったり非効率になったりします。効果的になる方法については全員で共有していければと思います。</p>
<p>明日の記事は kimurasさんです。引き続きお楽しみください。</p>
- メルカリの中長期技術投資 プロジェクトRFS: 約2年の振り返りhttps://engineering.mercari.com/blog/entry/20231223-rfs-lookback-after-two-years/https://engineering.mercari.com/blog/entry/20231223-rfs-lookback-after-two-years/<p>こんにちは。メルカリMarketplace, Foundation EngineeringのDirector, @mtsukaです。日々新しい技術を追い求め、挑戦を続けるMercari Engineeringですが、そん […]</p>
Sat, 23 Dec 2023 11:00:40 GMT<p>こんにちは。メルカリMarketplace, Foundation EngineeringのDirector, @mtsukaです。日々新しい技術を追い求め、挑戦を続けるMercari Engineeringですが、そんな部門にしては少し毛色の違った部類のチームです。どちらかというと、中長期の視点から、より良いビジネス貢献であったり、より良い開発体験を支える基盤開発を中心に、じっくり腰を据えた仕事をしています。</p>
<p>この記事は、<a href="https://engineering.mercari.com/blog/entry/20231124-mercari-advent-calendar-2023/">Mercari Advent Calendar 2023</a> の23日目の記事です。</p>
<p>メルカリは2021年10月から既存のシステムの解析、改善を大規模かつスピーディに行うという、難易度の高い全社的なリファクタリングプロジェクトRobust Foundation for Speed (RFS) に中期的に取り組んできました。本取り組みは、2023年7月末に各ドメインの改善が無事一段落し、プロジェクトという形は一旦解散としました。こういった取り組みの主要な結果は、具体的な成果として認知されるまでに、数ヶ月数年を要することもままあります。幸運なことに、すでにいくつか具体的に成果として示せることがありますので、昨年に引き続き、うまくいったところ、うまくいかなかったところ、今後の方針など、RFS全体をプロジェクトのオーナーの視点で振り返っていきたいと思います。また、各ドメインについては、ドメイン知識・疎結合化・文化醸成の観点からプロジェクトの成果を解説しています。</p>
<h2>プロジェクト発足の背景</h2>
<p>改めて、RFSプロジェクトは2021年10月に正式な中期プロジェクトとして発足しました。詳細な説明は <a href="https://engineering.mercari.com/blog/entry/20220908-robust-foundation-for-speed-series-present-and-future/">連載:技術基盤強化プロジェクト「RFS」の現在と未来 | メルカリエンジニアリング</a>に譲りますが、事業に関わる共通基盤をうまく抽象化していく、保守性を良くしていくことで、機能実装のリードタイムを一定以下に維持し、これによって間接的に事業貢献をしていこうという取り組みです。このプロジェクトへ参画したいという意思表明が多くのドメインからありましたが、結果的にビジネスインパクトなどを鑑み、Transactions & Checkout(以降Transactionsと呼称)、CSTool, Logistics, ID Platform(以降IDPと呼称)の4ドメインでこの取り組みを行うことにしました。</p>
<h2>プロジェクトスコープの設定</h2>
<p>リファクタリングなどの改善プロジェクトを発足するときに、改めて気付かされるのはあらゆる資源は有限であるということです。こういったプロジェクトは、ともすれば全てを作り替えてしまいたい衝動に駆られるのが人の性でしょう。特にエンジニアであればそういう気持ちになる方は多いのではないでしょうか。もちろん気持ちとしてはとてもよくわかるのですが、やはりこの取り組みも事業の一環ですから、あれもこれも全てに資源投下というわけには参りません。また、<a href="https://ja.wikipedia.org/wiki/%E4%BA%BA%E6%9C%88%E3%81%AE%E7%A5%9E%E8%A9%B1">人月の神話で言われているセカンドシステム症候群</a>のようなことも避けねばなりません。これらのポイントを考慮して、最終的にはシステムの変更頻度や他システムとの結合度合いを考慮してスコープを決めました。この点については、人によっては不満もあったと思いますし、実際に一部ドメインの調査・分析や関連する議論が収束するまでには半年位の期間がかかってしまいました。個人的には、現場のメンバーが一番知見をお持ちなので、それを尊重しつつ納得感をある程度持ってもらいたかったのですが、正直少し時間をかけすぎてしまったので、明確なしきい値や基準など、もう少し具体的に事前に提示したほうが良かったと感じています。</p>
<h2>プロジェクトの定点観測</h2>
<p>スコープが決まればロードマップを引いて、OKRを設定し、あとは手を動かしていくだけですが、ビジネスグロースの案件などとバランスをとりながら、うまく説明責任を果たしていく必要がありました。このため、あまり好まれるやり方ではないにせよ、週に一度の定期チェックインミーティングを用意し、CTO/VP同席のもとプロジェクトの進捗を管理しました。原則としてOKRとその進捗をDivision全体で共有し、トラッキングすることでプロジェクトの透明性担保や早期のブロッカー除去に務めました。また、最初期にはカンパニーのOKRとして経営層への定期的な進捗インプットも行いました。 担当チームが解散してしまっていたり、メイン開発者が退職していてドキュメントも存在しないようなコンポーネントを含むドメインでの作業なので、透明性を担保しながら情報を共有し続けることは極めて重要であったと思います。一方で、忘れられた仕様が発見されたりするなど、スケジュールを遵守するという観点では苦労も多かったです。スケジュールマネジメントの観点ではThe Six Week Cycleの<a href="https://3.basecamp-help.com/article/413-tracking-work-on-the-hill-chart">Tracking Work on the Hill Chart</a>の考え方を大いに参考にしました。</p>
<h2>プロジェクト全体の振り返り</h2>
<p>さて、このようなプロジェクトでは成果を既存事業への貢献として評価することはとても難しいです。定量的に計測したものは、リードタイムの増減、データベース分離数、マイグレーション数、削除したコードや廃止したAPI, それぞれの費用対効果などを計測しました。その他、定性的にはリファクタリングのマイルストーン達成状況や実際のチームの体感等などを集計しました。このような取り組みを経て、RFSが会社の期待にどのように答えたのか、振り返りを実施しました。かんたんなプロジェクトの総括としては、今後の事業計画・成長を見据えた基盤そのものと基盤維持の仕組みがある程度構築されたので、この取り組み自体は将来へ繋がる意味のある投資であったと確信しています。その旨をMercari Engineering Boardへ報告する形で説明責任を果たしました。個別の詳細については、後述の「各ドメインの振り返り」を参照ください。</p>
<h2>各ドメインの振り返り</h2>
<p>以下に、各ドメインでの取り組みと振り返りをまとめていますので、ご覧ください。</p>
<h3>Transactions</h3>
<p>Transactionsはお客様が商品を購入してから手元に届くまでの各ステップを司るメルカリのビジネスを構成するAPI郡(以降mercari-apiと呼称)のいちコンポーネントです。複雑化したモノリシックなmercari-apiからこれら関連するコンポーネントを切り離し、保守性を担保しながら抽象化、単純化していくことでプロダクト開発の後押しをするために、チームの組成から着手しました。チームの成り立ちがRFS起因のため、スコープの決定や抽象化のプランの検討は比較的スムースでした。</p>
<p>Transactionsドメインとして mercari-apiから切り離された機能は以下のとおりです:<br />
チェックアウト (モジュール化完了)<br />
購入履歴 (モジュール化完了)<br />
チェックアウト料金計算機能 (Golangの独立したマイクロサービスとして実装)<br />
配送 (モジュール化完了)</p>
<p>これらのモジュール化やリファクタリングを通じて下記のような成果を得ました。</p>
<h4>ドメイン知識</h4>
<p>綿密なコード解析とリバースエンジニアリングを行い、アプリケーション全体の中で最もビジネス上複雑で重要なドメインに関する知識を、組織として得ることができました。</p>
<h4>疎結合化</h4>
<p>TransactionsドメインはC2C Marketplaceシステムの中心的なコンポーネントなので、多くの新機能が恒常的にTransactionsドメインの連携を必要とします。このMonolithicな実装のサブコンポーネント郡をモジュラーモノリスとしてリファクタリングすることで、その後の開発に多くの良い影響をもたらしました。例えば機能境界が明確になったので、不具合の発見やリスクのコントロールがしやすくなり、Transactionsドメインに変更を加える成果物の品質が向上しました。また、本取り組みにおける調査の結果が知見として蓄積されたことに加え、認知的負荷の軽減により、オンボーディングも比較的簡単になりました。</p>
<p>上記から派生した効果として、新しい機能や要件実装のリードタイムを大幅に短縮できるようになりました。実例をあげると、チェックアウト料金計算機能によって料金管理が一本化されたため、料率の変更や新しい決済方法の導入などの実装工数が最大3ヶ月から1週間未満に短縮されました。また、CSToolやLogisticsなどの他のドメインとの依存関係を切り離すことができたので、より独立してシステムを維持していくことができるようになりました。</p>
<h4>文化醸成</h4>
<p>リバースエンジニアリングとリファクタリングと並行して、チームには "reading parties" という独創的で魅力的なコード分析の文化が生まれました。ここから生まれたドキュメントはオンボーディングへ応用されるだけでなく、他のチームとドメイン知識を共有するためにも活用されています。また、今後もTransactionsドメインの変更には多くのチームが関与し続けていくことになるため、将来に渡って意味のある成果になるでしょう。</p>
<h4>参考記事</h4>
<ul>
<li><a href="https://engineering.mercari.com/en/blog/entry/20220909-understanding-and-modernizing-a-legacy-codebase/">Understanding and Modernizing a Legacy Codebase</a></li>
<li><a href="https://engineering.mercari.com/blog/entry/20220913-modular-monolithization-in-mercari-transaction-domain/">メルカリの取引ドメインにおけるモジュラーモノリス化の取り組み</a></li>
<li><a href="https://engineering.mercari.com/blog/entry/microservices-for-computational-logic/">クライアント・サーバサイドに分散する計算ロジックのマイクロサービス化</a></li>
</ul>
<h3>Logistics</h3>
<p>メルカリは多様な配送手段をサポートしています。Logisticsは言葉通りこれらの配送方法を司るコンポーネントです。ご存知の通り、配送方法はメルカリというサービスの成長とともに時間をかけて増えていったものなので、Logisticsコンポーネントも時間の経過とともに複雑さが増してきました。そのため、スコープの確定は早かったものの、他ドメインと比較してゴールの設定難易度が非常に高かったです。最終的にLogisticsドメインでは、重複しているクラスを排除してシンプルにするなど、主にシステムの再設計を行いました。具体的には、メルカリの歴史とともに育ってきた22のコンポーネントの疎結合化を目指したかったのですが、これらすべてを疎結合化するには現実的な時間が足りませんでした。そのため、将来につながるメンテナンスの一環としてインターフェースやデザインを極力共通化することにしました。すでに動いており、しかもビジネスの根幹を担うシステムを改善するわけなので、そういう観点でも難易度は相当なものです。成果自体は次に繋がる良いものでありつつも、残念ながら、この取り組みの見た目的な成果はドメインの中では一番物足りないものでもありました。このあたりは、より良いアプローチを模索していきたいです。</p>
<p>取り組みを通じて下記を達成しました。</p>
<h4>ドメイン知識</h4>
<p>重複排除などの作業を通じて新しい配送方法の追加、配送料金の変更方法などが統一され、結果としてチームのドメイン知識が増しました。配送手段の仕様はパートナー企業の仕様に依存するものの、社内で扱うインターフェースをある程度共通化することで、全体の把握がしやすくなりました。また、共通化の恩恵として学習コストも格段に下がりました。</p>
<h4>疎結合化</h4>
<p>Logisticsドメインはパートナー企業との関係もあるので、利用料金の変更や提供プランの変更などが発生した場合、直ちに対応を行わなければなりません。このため、システムの複雑さを解消することは極めて重要な取り組みでした。結果として、疎結合化自体は進みませんでしたが、デザインパターンの適用と再設計を通じて、機能追加や変更が簡単になりました。実際に、配送サービス利用料改定に関わるリードタイムは、チェックアウト機能との連携も完了し、期間も約3ヶ月から1/3の約1ヶ月に短縮されました。今後も新しい配送方法の追加、料金改定、その他将来発生するであろうユースケースについても同じような対応ができることでしょう。</p>
<h4>文化醸成</h4>
<p>チームがリファクタリング用のバックログを持つようになりました。このバックログは定期的に内容の確認と改善の検討が行われ、必要に応じてメンバーがアサインされるようになりました。</p>
<h3>CSTools</h3>
<p>CSToolsはいわゆる顧客対応ツールです。お客さま対応のためのツールですから、その機能やサポート範囲は多岐にわたり、システムは年々複雑化していく一方でした。このため、スコープの策定議論は一番紛糾したのではないでしょうか。そもそもお客さま対応のためのデータベース数が膨大なため、これをどこまで疎結合化するのかなどが論点になってしまい、スケジュール的にもマイルストーン的にも難しい展開が発生していましたが、最終的に他3ドメインの改善に関わるブロッカー除去を優先するという前提でスコープを設定することで、議論を収束しました。</p>
<h4>ドメイン知識</h4>
<p>重複排除などの作業を通じて新しい配送方法の追加、配送料金の変更方法などが統一され、結果としてチームのドメイン知識が増しました。配送手段の仕様はパートナー企業の仕様に依存するものの、社内で扱うインターフェースをある程度共通化することで、全体の把握がしやすくなりました。また、共通化の恩恵として学習コストも格段に下がりました。<br />
疎結合化<br />
詳細は後述のブログポストに譲りますが、注力ドメインとの関係性と変更頻度を軸にDBの疎結合化を行いました。これによりCSTools開発に関わる調整相手が減って、クイックに改善活動ができるようになりました。また、追加で古くから一部のQA業務が依存していたシステムのGKEマイグレーションとサービスアウトを実施できました。これにより、QAやテスト環境の統合が進み、システムのコスト面でも貢献することができました。</p>
<h4>文化醸成</h4>
<p>Post RFSの一環としてCSToolsドメインにFoundationチームが組成されました。このチームはCSToolsドメインの共通基盤やフレームワークをパッケージとして各エンティティに提供することで、個別のエンティティに個別のツールを作らなくても良い状況をもたらすことに責任を負っています。正式にこういった組織を持つことを認められたのも成果の一つと言って良いかもしれません。</p>
<h4>参考記事</h4>
<ul>
<li><a href="https://engineering.mercari.com/blog/entry/loosely-coupled-db-in-cstools/">メルカリCSツールにおけるDBの疎結合化への取り組み</a></li>
<li><a href="https://engineering.mercari.com/blog/entry/migrate-for-gke/">GKE 移行を進める上で発見したシステムの問題をどの様に解決したか</a></li>
<li><a href="https://engineering.mercari.com/blog/entry/20230112-frontend-replacement/">CS Toolのフロントエンドのリプレイスプロジェクトについて</a></li>
</ul>
<h3>ID Platform</h3>
<p>ID Platform(以降: IDP)は、メルペイ創業前後にmercari-apiから認証認可の機能を中心にスピンオフしてPlatform化したものです。チームの組成当初からビジネスプランを後押しすべく、然るべきタイミングで然るべきことをやっていくという方針でチームが運営されていたと記憶しています。一方で、どうしても急ぎの実装や設計が先行しがちなことは変わりません。内外各ステークホルダーとのアカウント連携など、常に現状のビジネスを改善する業務に追われている状況で、後々に判明した考慮漏れの修正、リファクタリングなどの時間を確保することが簡単ではない状況でした。RFSでの注力対象にピックアップされたタイミングのIDPチームは、当時メルコインサービスの開発をサポートしていました。もともとチームの思想がRFSに近く、ある程度成熟していたチームなので、RFSとして直接何かの機能改善やリファクタリングをお願いするようなことはせず、メルカリグループ全体の方針などをシェアしながら、現在の設計や実装が今後の抽象化にうまく繋がるように支援しました。</p>
<h4>ドメイン知識</h4>
<p>IDと認証認可の領域は比較的専門家が少ないため、知見が偏る傾向があります。現在チームはTLの育成と知見の共有などを行いやすい構造になりました。RFSの取り組みと考え方は、この文化醸成の一助になったと信じています。</p>
<h4>疎結合化</h4>
<p>IDPはもともとはメルカリとメルペイというサービスだけが存在する世界線で実装されたものですから、比較的明確に密結合な場所がありました。日々メルカリを利用してくれるお客さまに新しい価値を提供するためには、この密結合が段々と足かせになりつつあります。今後の取組次第でどうなるかはわかりませんが、この結合度合いをある程度疎に維持できるようになりました。</p>
<h4>文化醸成</h4>
<p>元来IDPでは、一部の専門家が特定のユースケースを基に知恵を絞って将来を視野に入れたデザインを行う傾向が強いのですが、外部の専門家も交えたドメイン知識の共有や議論などを行えるようになりました。直接は関係ないですがFIDO AllianceのAuthenticate 2023 Conferenceにてメルカリの取り組みを紹介するなどの機会にも恵まれました。</p>
<h4>参考記事</h4>
<ul>
<li><a href="https://engineering.mercari.com/en/blog/entry/20230130-applying-oauth-2-0-and-oidc-to-first-party-services/">Applying OAuth 2.0 and OIDC to first-party services</a></li>
<li><a href="https://engineering.mercari.com/en/blog/entry/20230414-using-the-oauth-2-token-exchange-standard-for-managing-the-identity-platform-resources/">Using the OAuth 2 token exchange standard for managing the identity platform resources</a></li>
</ul>
<h2>まとめ</h2>
<p>さて、ここまで日々進化するメルカリのアプリケーションを支える基盤開発のエピソードを、長期プロジェクトの振り返りを通じてお伝えしました。<br />
メルカリのFoundation Engineeringチームは、これからもRFSで得られた知見やユースケースを参考に、重要な共通基盤技術の保守性を維持ながらし、プロダクト開発エンジニアがサービス開発を行っていく上で不可欠なコンポーネントを提供し続けます。</p>
<p>明日の記事はQAチームのjyeさんです。引き続きお楽しみください。</p>
- メルコインにおけるGitHub Actions活用術https://engineering.mercari.com/blog/entry/20231223-mercoin-github-actions/https://engineering.mercari.com/blog/entry/20231223-mercoin-github-actions/<p>こんにちは。メルコインのバックエンドエンジニアのiwataです。 この記事は、Merpay Advent Calendar 2023 の23日目の記事です。 私はいまメルコインのCoreチームに属しています。Coreチー […]</p>
Sat, 23 Dec 2023 10:00:28 GMT<p>こんにちは。メルコインのバックエンドエンジニアの<a href="https://twitter.com/mobcov">iwata</a>です。<br />
この記事は、<a href="https://engineering.mercari.com/blog/entry/20231124-merpay-advent-calendar-2023/">Merpay Advent Calendar 2023</a> の23日目の記事です。</p>
<p>私はいまメルコインのCoreチームに属しています。Coreチームでは主にお客さまからの暗号資産の売買注文を受け付ける部分のマイクロサービスを開発運用しています。</p>
<p>メルコインではCI環境として<a href="https://engineering.mercari.com/blog/entry/20220203-defense-against-novel-threats-redesigning-ci-at-mercari/">GitHub Actions self-hosted runner</a>を使用しています。またCIだけでなく、さまざまな自動化のためのワークフローの構築もこの環境を用いて実行しています。この記事では私の所属しているCoreチームにおいてGitHub Actions上に構築しているオートメーションについて紹介したいと思います。</p>
<h2>PR-Agent</h2>
<p><a href="https://github.com/Codium-ai/pr-agent">PR-Agent</a>はOpenAI APIを使って、PRのコードレビューなどを自動化してくれるActionです。<a href="https://tech.layerx.co.jp/entry/2023/09/01/102612">LayerXさんの紹介記事</a>を読んで導入しました。<br />
機能はたくさんあるのでここでは詳細は割愛しますが、主に活用しているのはPR作成時にコメントしてくれるコードレビューと<code>/describe</code>コマンドで生成されるPRのタイトルと説明の自動生成です。<br />
<img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/1f6fb937-cleanshot-2023-12-15-at-16.49.37.png" alt="PR Analytics" /><br />
PR-Agentによるコードレビュー</p>
<p>あまり具体的な例を記事中にだすことができませんが、例えば上記画像のような内容をコメントしてくれます。これによりレビュアーがぱっとこのPRの内容を理解するのに役立つことができます。また<code>/describe</code>を使うと自分のようにSSIAなどで説明文を端折ってしまう面倒くさがりな人でもいい感じのタイトルと説明文をAIが考えてくれて非常に便利です。<code>/add_docs</code>を使うとコードコメントをSuggestしてくれてこれも便利です。</p>
<p>OpenAIのAPI Keyさえあれば簡単に導入できる点もよいです。一方でGitHub自体にも<a href="https://githubnext.com/projects/copilot-for-pull-requests">似たような機能</a>がリリースされているので試してみたいなと思っています。</p>
<h2>Lint</h2>
<p>いくつかのLintツールを併用していますが、ここではYAMLで記述されるGitHub Actions(GHA)のワークフローファイルに対するLintについて紹介します。実際のワークフローは以下です。</p>
<pre><code class="language-yaml">name: Actions Lint
on:
pull_request:
paths:
- ".github/workflows/*.yml"
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
actionlint:
runs-on: self-hosted
permissions:
checks: "write"
contents: "read"
pull-requests: "write"
steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- uses: reviewdog/action-actionlint@82693e9e3b239f213108d6e412506f8b54003586 # v1.39.1
with:
fail_on_error: true
filter_mode: nofilter
level: error
reporter: github-pr-review
ghalint:
runs-on: self-hosted
permissions:
contents: "read"
steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- name: Setup aqua
uses: ./actions/setup-aqua
with:
aqua_version: v2.21.0
- name: ghalint run
run: ghalint run</code></pre>
<p>このワークフローでは<a href="https://github.com/rhysd/actionlint">actionlint</a>と<a href="https://github.com/suzuki-shunsuke/ghalint">ghalint</a>の2つのLinterを実行しています。それぞれセキュリティを含めたベストプラクティスに則っているかをチェックしてくれるので非常に有益です。</p>
<p>ワークフローについては<a href="https://engineering.mercari.com/blog/entry/20230609-github-actions-guideline/">社内のセキュリティガイドライン</a>に準拠する形で記述しています。3rd Party ActionはFull Change Hashで固定(このフォーマットでも<a href="https://github.blog/changelog/2022-10-31-dependabot-now-updates-comments-in-github-actions-workflows-referencing-action-versions/">Dependabot</a>および<a href="https://docs.renovatebot.com/modules/manager/github-actions/#additional-information">Renovate</a>を使うことで自動更新可能) し、 <code>permissions</code>は最低限の権限を使うようにしています。(以後記載するワークフローファイルはすべてこのガイドラインに則って記述してあります。)</p>
<p>また<code>ghalint</code>のバージョン管理には<a href="https://github.com/aquaproj/aqua">aqua</a> を使っています。<code>aqua</code>はCLIツールのバージョンマネージャで<a href="https://aquaproj.github.io/docs/guides/checksum">チェックサムの検証</a>ができたり、Lazy Installなど便利な機能もあるため使用しています。<code>ghalint</code>以外にも<a href="https://github.com/golangci/golangci-lint">golangci-lint</a>や<a href="https://github.com/daixiang0/gci">gci</a>など開発に必要なさまざまなCLIツールを<code>aqua</code>で管理しています。(<code>aqua</code>についてより詳しく知りたい方は<a href="https://zenn.dev/shunsuke_suzuki/books/aqua-handbook">aqua CLI Version Manager 入門</a>をご参照ください) したがって<code>aqua</code>は他のさまざまなワークフローで利用することになるため、以下の<a href="https://docs.github.com/ja/actions/creating-actions/creating-a-composite-action">Composite Action</a>を作って再利用しやすいように工夫しています。</p>
<pre><code class="language-yaml">name: Setup aqua with caching
describe: Install tools via aqua and manage caching
inputs:
aqua_version:
required: true
description: |
aqua version for installer, e.g. v2.9.0
aqua_opts:
required: false
default: -l
description: |
aqua i's option. If you want to specify global options, please use environment variables
policy_allow:
required: false
default: ""
description: |
If this is true", the aqua policy allow command is run. If a Policy file path is set, aqua policy allow "policy_allow" is run
require_checksum:
required: false
default: "true"
description: |
Set an environment variable as `AQUA_REQUIRE_CHECKSUM`
cache_version:
description: The prefix of cache key
required: false
default: "v1"
runs:
using: "composite"
steps:
# ref. https://aquaproj.github.io/docs/products/aqua-installer/#-caching
- name: Restore aqua tools
uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84 # v3.3.2
id: restore-aqua
with:
path: ~/.local/share/aquaproj-aqua
key: ${{ inputs.cache_version }}-aqua-installer-${{hashFiles('.aqua/*.yaml')}}
restore-keys: |
${{ inputs.cache_version }}-aqua-installer-
- name: Aqua install
uses: aquaproj/aqua-installer@928a2ee4243a9ee8312d80dc8cbaca88fb602a91 # v2.2.0
with:
aqua_version: ${{ inputs.aqua_version }}
aqua_opts: ${{ inputs.aqua_opts }}
policy_allow: ${{ inputs.policy_allow }}
env:
AQUA_REQUIRE_CHECKSUM: ${{ inputs.require_checksum }}
- name: add path
shell: bash
run: |
echo "$HOME/.local/share/aquaproj-aqua/bin" >> "$GITHUB_PATH"</code></pre>
<h2>Auto Correct</h2>
<p>Lintとともに<code>goimports</code>などのコードフォーマッタの活用も重要です。<code>golangci-lint</code>によって<code>goimports</code>などのフォーマッタのかけ忘れを弾くことは可能ですが、GHA上でフォーマットしてあげて自動でコミットをしてあげるとさらに便利です。コードフォーマッタだけでなく、<a href="https://github.com/google/wire">wire</a>など自動生成ツールも使っているのでそれらもあわせて実行し、差分があればGHA上でコミットするようにしています。使っているツールをまとめると以下のようになります。</p>
<ul>
<li>コードフォーマッタ
<ul>
<li><code>goimports</code></li>
<li><a href="https://github.com/mvdan/gofumpt">gofumpt</a></li>
<li><a href="https://github.com/daixiang0/gci">gci</a></li>
</ul>
</li>
<li>Linter
<ul>
<li><code>golangci-lint</code> (auto fix)</li>
</ul>
</li>
<li>自動生成
<ul>
<li><code>wire</code></li>
<li><a href="https://github.com/sanposhiho/gomockhandler">gomockhandler</a></li>
<li><a href="https://github.com/cloudspannerecosystem/yo">yo</a></li>
</ul>
</li>
<li>GitHub Workflow
<ul>
<li><a href="https://github.com/suzuki-shunsuke/pinact">pinact</a></li>
</ul>
</li>
</ul>
<p>これらのツールはすべて<code>aqua</code>でバージョン管理しています。<code>pinact</code>はワークフローファイル内のバージョンをFull Change Hashに自動で固定してくれるツールでとても有用です。Auto Correctのワークフローは以下になります。</p>
<pre><code class="language-yaml">name: Correct codes by auto generation
on:
pull_request:
paths:
- ".github/**/*.ya?ml"
- "**.go"
- "**/go.mod"
- "**.sql"
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
defaults:
run:
shell: bash
jobs:
auto-correct:
runs-on: self-hosted
permissions:
contents: "read"
steps:
- name: Check out
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0
with:
go-version-file: "go.mod"
cache-dependency-path: "**/go.sum"
- name: Setup aqua
uses: ./actions/setup-aqua
with:
aqua_version: v2.21.0
- name: Auto generation
run: make gen # make taskでformatter, linter, code generationを実行
- name: pinact run
run: pinact run
- name: Generate token
id: generate_token
uses: suzuki-shunsuke/github-token-action@350d7506222e3a0016491abe85b5c4dd475b67d1 # v0.2.1
with:
github_app_id: ${{ secrets.GH_APP_ID }}
github_app_private_key: ${{ secrets.GH_APP_PRIVATE_KEY }}
- name: Push diff
run: |
set -euo pipefail
if git diff --quiet; then
echo "::notice :: There is no difference."
exit 0
fi
echo "::notice :: There are some differences, so a commit is pushed automatically."
if ! ghcp -v; then
echo "::error :: int128/ghcp isn't installed. To push a commit, ghcp is required."
exit 1
fi
branch=${GITHUB_HEAD_REF:-}
if [ -z "$branch" ]; then
branch=$GITHUB_REF_NAME
fi
git diff --name-only |
xargs ghcp commit -r "$GITHUB_REPOSITORY" -b "$branch" \
-m "chore(gen): auto correct some files"
env:
GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }}</code></pre>
<p>このワークフローは複雑になっているので順をおって説明しようと思います。</p>
<h3>Protected Branchの設定</h3>
<p>マージ先のブランチは<a href="https://docs.github.com/ja/repositories/configuring-branches-and-merges-in-your-repository/managing-protected-branches/about-protected-branches">Protected Branch</a>で保護されています。このワークフローに関連する設定としては、<a href="https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-protected-branches/about-protected-branches#require-signed-commits">署名つきコミット</a>と<a href="https://docs.github.com/ja/repositories/configuring-branches-and-merges-in-your-repository/managing-protected-branches/about-protected-branches#require-status-checks-before-merging">ステータスチェック</a>を必須にしている点です。すなわちAuto Correctによるコミットがこれらを満たせるようにワークフローを構築しておかないとマージできなくなってしまいます。</p>
<h3>署名つきコミット</h3>
<p>ワークフロー内でgitコマンドを使ってコミットをすると署名がつきません。これを簡単に回避する方法としては<a href="https://docs.github.com/ja/rest/commits?apiVersion=2022-11-28">GitHub APIを使う方法</a>があります。GitHub API で生成したコミットにはGitHubが署名してくれます。<a href="https://github.com/int128/ghcp">ghcp</a>を使うとGitHub APIを使ったコミットを簡単に作成できるのでこれを使ってコミットするようにします。次のコードが実際にコミットをしている部分になります。</p>
<pre><code class="language-sh">git diff --name-only |
xargs ghcp commit -r "$GITHUB_REPOSITORY" -b "$branch" \
-m "chore(gen): auto correct some files"</code></pre>
<p>差分がでたファイル名をパイプで渡してコミットを生成しています。</p>
<h3>GitHub Appを使ったトークンの生成</h3>
<p>コミットに使うGitHubトークンにも注意が必要です。<a href="https://docs.github.com/en/actions/security-guides/automatic-token-authentication#using-the-github_token-in-a-workflow">GitHubのドキュメント</a>に以下のような記述があります。</p>
<blockquote>
<p>When you use the repository’s GITHUB_TOKEN to perform tasks, events triggered by the GITHUB_TOKEN, with the exception of workflow_dispatch and repository_dispatch, will not create a new workflow run.</p>
</blockquote>
<p>つまりよく使われる<code>secrets.GITHUB_TOKEN</code>を使ってコミットをするとそのコミットをトリガーに他のワークフローを起動できません。ワークフローが起動しないということはCIが実行されず、したがってProtected Branchのステータスチェックをパスすることができません。</p>
<p>これを回避するためにGitHub Appから生成したトークンを使ってコミットをする必要があります。上記のワークフローでは<a href="https://github.com/suzuki-shunsuke/github-token-action">suzuki-shunsuke/github-token-action</a>を使ってトークンを生成しています。 </p>
<pre><code class="language-yaml"> - name: Generate token
id: generate_token
uses: suzuki-shunsuke/github-token-action@350d7506222e3a0016491abe85b5c4dd475b67d1 # v0.2.1
with:
github_app_id: ${{ secrets.GH_APP_ID }}
github_app_private_key: ${{ secrets.GH_APP_PRIVATE_KEY }}</code></pre>
<p>このGitHub Appには次の権限が必要になります。</p>
<ul>
<li>contents:write</li>
<li>workflows:write</li>
</ul>
<p>リポジトリに自分で用意したGitHub Appをインストールして使います。GitHub Apoの準備は最初は面倒ですが、一度設定すると自動化ができることが格段に増えるので便利になります。</p>
<h2>aquaの自動更新</h2>
<p>CLIツールは<code>aqua</code>で管理しています。バージョンの更新は公式に提供されているRenovate Presetを使うことで可能です。詳細は<a href="https://zenn.dev/shunsuke_suzuki/books/aqua-handbook/viewer/checksum-verification">Renovateによる自動update</a>を参照してください。(<a href="https://docs.github.com/ja/code-security/dependabot/working-with-dependabot">GitHubのDependabotl</a>にはPresetのような機能がないため、<code>aqua</code>の自動更新はRenovate前提になっています。) 前述しましたが、<code>aqua</code>ではチェックサムの検証ができます。<code>aqua</code>では<code>aqua-checksums.json</code>でチェックサムを管理しており、バージョン更新時でもチェックサム検証をパスするためには、一緒にこのファイルのチェックサムも更新する必要があります。便利なことにそのための<a href="https://aquaproj.github.io/docs/products/update-checksum-workflow/">Reusable Workflow</a>が公式に提供されているのでこれを使うことでチェックサムの更新も自動化することができます。</p>
<pre><code class="language-yaml">name: Update aqua-checksums.json automatically
on:
pull_request:
paths:
- .aqua/aqua.yaml
- .aqua/aqua-checksums.json
- .github/workflows/update-aqua-checksums.yaml
jobs:
update-aqua-checksums:
uses: aquaproj/update-checksum-workflow/.github/workflows/update-checksum.yaml@3598c506108a2e0e9e31a0c6ef9c202c77049420 # v0.1.9
permissions:
contents: read
with:
aqua_version: v2.21.0
prune: true
secrets:
gh_app_id: ${{ secrets.GH_APP_ID }}
gh_app_private_key: ${{ secrets.GH_APP_PRIVATE_KEY }}</code></pre>
<p>このワークフローにおいても前述のコミットの問題が発生するので、GitHub Appを使う必要があります。このAppには<code>contents:write</code>権限があれば十分です。</p>
<h2>リリースフローに関する自動化</h2>
<h3>ブランチ管理</h3>
<p>Coreチームでは<a href="https://www.atlassian.com/ja/git/tutorials/comparing-workflows/gitflow-workflow#:~:text=Gitflow%20%E3%81%A8%E3%81%AF%E3%80%81%E3%83%95%E3%82%A3%E3%83%BC%E3%83%81%E3%83%A3%E3%83%BC%20%E3%83%96%E3%83%A9%E3%83%B3%E3%83%81,%E8%A6%8F%E6%A8%A1%E3%81%AA%E3%82%B3%E3%83%9F%E3%83%83%E3%83%88%E3%81%8C%E3%81%82%E3%82%8A%E3%81%BE%E3%81%99%E3%80%82">git-flow</a>を簡素化したブランチ管理を採用しています。main、develop、feature、hotfixブランチはそのままですが、releaseブランチは作成せず、リリースの際にdevelopブランチをmainブランチにマージしてリリースするようにしています。これらのブランチのうち、Protected Branchの設定しているのはmainとdevelopブランチになります。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/b2e34487-github-flow.png" alt="git flow" /><br />
オリジナルのgit-flow、releaseブランチに違いがある (出典: <a href="https://www.atlassian.com/ja/git/tutorials/comparing-workflows/gitflow-workflow#:~:text=Gitflow%20%E3%81%A8%E3%81%AF%E3%80%81%E3%83%95%E3%82%A3%E3%83%BC%E3%83%81%E3%83%A3%E3%83%BC%20%E3%83%96%E3%83%A9%E3%83%B3%E3%83%81,%E8%A6%8F%E6%A8%A1%E3%81%AA%E3%82%B3%E3%83%9F%E3%83%83%E3%83%88%E3%81%8C%E3%81%82%E3%82%8A%E3%81%BE%E3%81%99%E3%80%82">atlassian.com</a>)</p>
<p>定期的なリリースタイミングでdevelopをmainブランチにマージし、タグを作成すると本番環境にデプロイできるようになっています。</p>
<h3>develop to mainのPull Request作成</h3>
<p>リリース時にはdevelop to mainのPRが必要になるため、developブランチへPushがあると自動でmainブランチへのPRを作成するようにしています。</p>
<pre><code class="language-yaml">name: git-pr-release
on:
push:
branches:
- develop
jobs:
git-pr-release:
runs-on: self-hosted
permissions:
contents: read
pull-requests: write
container:
image: ruby:3.2@sha256:e3f503db7f451e6fd48221ecafbf1046ad195cddec98825538b35a82538b8387
steps:
- name: Check out
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
with:
fetch-depth: 0 # git-pr-release needs the git histories
- name: Install git-pr-release
run: gem install --no-document git-pr-release --version 2.2.0
- name: Update git config
run: git config --global --add safe.directory "$(pwd)"
- name: Create PR
run: git-pr-release --squashed
env:
GIT_PR_RELEASE_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GIT_PR_RELEASE_BRANCH_PRODUCTION: main
GIT_PR_RELEASE_BRANCH_STAGING: develop
GIT_PR_RELEASE_LABELS: Release
GIT_PR_RELEASE_TEMPLATE: .github/PR_RELEASE_TEMPLATE.erb
TZ: Asia/Tokyo</code></pre>
<p>PRの作成には<a href="https://github.com/x-motemen/git-pr-release">git-pr-release</a>を使っています。このワークフローにより次の画像のようなPRが自動で生成されるようになります。各PR毎にチェックボックスがつくので、リリース時にPR内容を確認してもらって問題なければチェックをいれるようにしてからリリースしています。<br />
<img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/11ef7e96-cleanshot-2023-12-15-at-17.04.32.png" alt="mainへのPR" /></p>
<h3>リリースの作成</h3>
<p>mainブランチにマージした後はGitHub UI上からリリースをパブリッシュすることでタグを作成します。この際のリリース作成も自動化しています。</p>
<pre><code class="language-yaml">name: Release Drafter
on:
pull_request:
branches:
- main
types:
- closed
jobs:
release-draft:
runs-on: self-hosted
if: github.event.pull_request.merged
permissions:
contents: write
pull-requests: write
steps:
- name: release drafter
uses: release-drafter/release-drafter@09c613e259eb8d4e7c81c2cb00618eb5fc4575a7 # v5.25.0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}</code></pre>
<p>mainブランチへのPull Requestがマージされると、<a href="https://github.com/release-drafter/release-drafter">release-drafter</a>を使ってリリースをドラフト状態で作成します。これはかなり便利で、<code>release-drafter</code>導入前はマニュアルでリリースを作成していましたが次のような課題がありました。</p>
<ul>
<li>バージョン番号を自分でインクリメントしないといけないのが地味に面倒</li>
<li>デフォルトブランチがdevelopになっているので、ターゲットブランチをmainに切り替え忘れるとインシデントになってしまう</li>
</ul>
<p>自動化によりこれらは解消することができました。</p>
<h3>Hotfixに関する自動化</h3>
<p>hotfixブランチはdevelopブランチを経由せず、直接mainブランチにマージします。hotfixブランチのマージの際はpatchバージョンをインクリメントするようにしています。</p>
<pre><code class="language-yaml">name: Release Drafter Label
on:
pull_request:
branches:
- main
types:
- opened
jobs:
release-draft-label:
runs-on: self-hosted
if: github.event.pull_request.head.ref != 'develop'
permissions:
contents: read
pull-requests: write
steps:
- name: detect version label
uses: actions-ecosystem/action-add-labels@18f1af5e3544586314bbe15c0273249c770b2daf # v1.1.3
with:
labels: patch</code></pre>
<p>このワークフローによってhotfixブランチのPRが作成されると<code>patch</code>ラベルがつくようになっています。このラベルがつくと<code>release-drafter</code>がpatchバージョンをあげるように<a href="https://github.com/release-drafter/release-drafter?tab=readme-ov-file#configuration">設定して</a>あります。</p>
<p>またhotfixの差分はdevelopブランチにもマージする必要があります。この作業は面倒は意外と面倒です。なぜかというと、直接mainからdevelopへのPRを作成することができないためです。またこの作業はよく忘れてしまうので自動化しておくのが得策です。それを実現するのが以下のワークフローです。hotfixがmainにマージされると起動します。</p>
<pre><code class="language-yaml">name: Create a pull request to merge hotfix into develop
on:
pull_request:
branches: [main]
types: [closed]
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
defaults:
run:
shell: bash
jobs:
create-pull-request:
runs-on: self-hosted
if: github.event.pull_request.merged == true && github.head_ref != 'develop'
permissions: {}
steps:
- name: Generate token
id: generate_token
uses: suzuki-shunsuke/github-token-action@350d7506222e3a0016491abe85b5c4dd475b67d1 # v0.2.1
with:
github_app_id: ${{ secrets.GH_APP_ID }}
github_app_private_key: ${{ secrets.GH_APP_PRIVATE_KEY }}
- name: Decide a branch name
id: decide-branch
run: |
branch=main-to-develop/hotfix-${{ github.event.pull_request.head.sha }}
echo "branch=${branch}" >> "$GITHUB_OUTPUT"
- name: Create a pull request
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
with:
github-token: ${{ steps.generate_token.outputs.token }}
script: |
const {owner, repo} = context.repo
const mainBranch = "main"
const devBranch = "develop"
// fetch commit sha of develop branch
const {data} = await github.rest.git.getRef({
owner,
repo,
ref: `heads/${devBranch}`,
})
// create a new branch
const branch = "${{ steps.decide-branch.outputs.branch }}"
await github.rest.git.createRef({
owner,
repo,
ref: `refs/heads/${branch}`,
sha: data.object.sha,
})
const {actor, payload} = context
const {title, number} = payload.pull_request
// merge main into a new branch
await github.rest.repos.merge({
owner,
repo,
base: branch,
head: mainBranch,
commit_message: `Merge ${title}`,
})
// create a pull request
const pull = await github.rest.pulls.create({
owner,
repo,
base: devBranch,
head: branch,
title: `Merge hotfix to ${devBranch}: ${title}`,
body: `Merge #${number} for ${devBranch} branch, too`,
})
// assign an actor as reviewer
github.rest.pulls.requestReviewers({
owner,
repo,
pull_number: pull.data.number,
reviewers: [actor],
})</code></pre>
<p><a href="https://github.com/actions/github-script">github-script</a>用のScriptが長いですが次のことをやっています。</p>
<ol>
<li>developブランチからPR用のブランチを作成</li>
<li>作成したブランチにmainブランチをマージ</li>
<li>上記ブランチからdevelopへのPRを作成</li>
<li>hotfixをマージしたアカウントをPRのレビュワーにアサイン </li>
</ol>
<p>このワークフローにおいてもコミットの問題が発生するので、GitHub Appからトークンを生成しています。このAppでは次の3つの権限が必要になります。</p>
<ul>
<li>contents:write</li>
<li>pull-requests:write</li>
<li>workflows:write</li>
</ul>
<p>レビューワーも設定しているのでマージ忘れがないように工夫しています。</p>
<h2>まとめ</h2>
<p>GitHub Actionsを使った自動化の事例を紹介してきました。セキュリティと自動化とは相反するところもあるので、両立するためにはバランス感覚と知識の更新が不可欠だなと思っています。だいぶ長い記事になってしまいましたが、そういった面で参考になれば幸いです。</p>
<p>明日の記事は poohさんです。引き続きお楽しみください。</p>
- 言語モデルを用いたQuery Categorizationhttps://engineering.mercari.com/blog/entry/20231222-language-model-based-query-categorization-for-query-understanding/https://engineering.mercari.com/blog/entry/20231222-language-model-based-query-categorization-for-query-understanding/<p>こんにちは。Mercari USの検索エンジニアの@pakioです。 この記事は、Mercari Advent Calendar 2023 の22日目の記事です。 Query Understandingは検索システム最も […]</p>
Fri, 22 Dec 2023 11:00:44 GMT<p>こんにちは。Mercari USの検索エンジニアの<a href="https://twitter.com/paki0o">@pakio</a>です。<br />
この記事は、<a href="https://engineering.mercari.com/blog/entry/20231124-mercari-advent-calendar-2023/">Mercari Advent Calendar 2023</a> の22日目の記事です。</p>
<p>Query Understandingは検索システム最も重要なシステムの一つで、検索意図を解釈し、また正しい検索を促すためのコンポーネントです。例えば検索ボックスでのクエリの提案やスペル修正、クエリの意図解釈、類似した検索条件の提案などシステム側・ユーザとの対話含めて様々な技術が用いられています。<br />
Mercari USでは日々35万件以上の新しい商品が出品されています。それに比例して検索対象の商品も分増えていくため、お客さまの検索ニーズを正しく理解し、適切な商品を提案するためにもQuery Understandingが重要な課題と捉えています。今回はそんなQuery Understandingの中でもQuery Categorizationについての手法比較と、弊チームで実際に検証した結果についてご紹介します。</p>
<p>Query Categorizationの定義は様々あるかと思いますが、本記事の中では「特定の検索クエリから、お客さまが求めている検索結果がどの事前に定義されたタクソノミ(分類)に当てはまるか推測する」と定義します。</p>
<h2>ルールベースのアプローチ</h2>
<p>ルールベースのアプローチは最もシンプルに実装ができ、かつ変更もしやすく説明可能性にも優れた手法です。<br />
<a href="https://www.algolia.com/doc/guides/managing-results/rules/rules-overview/">Algolia</a>や<a href="https://docs.vespa.ai/en/query-rewriting.html">Vespa</a>など一部の検索エンジンではこの機能がデフォルトで提供されていることからも重要度が高いことがわかりますし、また実際に導入しているサービスも多いことでしょう。ここでは例として単純にカテゴリフィルタ条件を追加する変換を挙げていますが、実装方法によっては更に複雑な、例えばフィルタリングの代わりにスコアのブースティングを行ったり、複数の条件を追加するなども考えられます。</p>
<div style="text-align: center;">
<img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/1b2e0091-diagram-1.png" alt=""/> ルールベースのQuery Categorization </>
</div>
<p>その簡単さからとても魅力にも思える手法ですが、一方のデメリットとしてメンテナンス性が挙げられます。<br />
もっとも単純なルールの生成方法として手動で辞書をメンテナンスする方法が考えられますが、確実な変換だけに対象を絞れる一方で入力の多様性に対応するためには莫大なメンテナンスコストがかかります。これについてはマスターデータからの生成などである程度自動化することは可能ですが、例えば同義語への対応や名称同士のコンフリクトなどイレギュラーなケースにはある程度人の手が必要となります。運用にあたってはその人的コストをあらかじめ織り込んでおかなければなりません。実際に弊チームでもこの辞書型のアプローチを数年ほど前から運用していますが、リスティングのトレンドの変化や新製品の対応などに伴う人手による定期的な見直しが必要とされている状況です。</p>
<h2>機械学習的なアプローチ</h2>
<p>ルールベースからもう少し発展した手法として、クエリログやそれに付随するクリックログ、検索結果に表示されたドキュメントの統計情報を用いる方法などが提案されてきました。この手法はデータ量が膨大になりがちであるため、ルールベースなアプローチの代わり<br />
に機械学習的なアプローチと組み合わせて利用される事例を多く見かけます。</p>
<p><a href="https://ieeexplore.ieee.org/document/8622008">2018年末に公開されたLinらの論文</a>では、実際にECのプロダクト検索においてQuery Categorizationにクリックログを用いた手法が紹介されています。<br />
ここでは約4000万件のクエリに対して、実際に検索結果に表示され行動(クリック/カートに追加/購入)が起こされたアイテムのカテゴリを取得し、クエリからカテゴリを予測するテキスト分類タスクとして学習を行わせています。<br />
ここで使用されたカテゴリは階層構造になっているとのことですが、最も優れたモデルで1階層目の予測がmicro-F1スコア 0.78、最下層の予測が0.58程度とある程度高い精度で予測できていることがわかります。</p>
<div style="text-align: center;">
<img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/b1653801-table-1.png" alt="" width="1282"/> TABLE I: Best micro-F1 score of multi-class single-label LR (logistic regression), SVMs, XGBoost, fastText and Attentional CNN classifier at different levels. – E-commerce Product Query Classification Using Implicit User’s Feedback from Clicks, Lin et al., Source: https://ieeexplore.ieee.org/document/8622008
</div>
<p>条件・モデル構造は異なりますが弊チームでも同様にクエリログ及びクリックログを用い、クエリから商品の各カテゴリのクリックされやすさを予測するマルチクラス分類予測の学習をさせた機械学習モデルを作成しました。その結果、我々のテストデータではmicro-F1スコア 0.72となりました。</p>
<h2>言語モデル的なアプローチ</h2>
<p>上記の論文は2018年末に発表されたものでしたが、同じく2018年末に発表された言語モデル <a href="https://arxiv.org/abs/1810.04805">BERT</a>が様々な分野で優れた性能を発揮しているのは皆さんご存知のことでしょう。BERTの特徴として、そのアーキテクチャにより上記で比較されていたACNNなどの従来のモデルと比較してもよりコンテキスト情報に強く、また様々な事前学習済みモデルが公開されている為手軽に試せることが挙げられます。また利用する事前学習済みモデルによっても異なりますが、自社のクエリログなどから学習したモデルと異なり一般的な語彙が用いられていることも特徴の一つです。これには未知のクエリに強い、汎用性があるなどのメリットもありますが、一方でドメイン固有の単語などには弱いといったデメリットも考えられます。</p>
<p>ここでQuery Categorizationのタスクに対して、このBERTの軽量派生モデルである<a href="https://arxiv.org/abs/1910.01108">DistilBERT</a>を用いて弊チームにて実装した手法をご紹介します。<br />
大まかなアーキテクチャとしては ①query embeddingsを取得するためのDistilBERT ②クエリ-カテゴリ分類器 で構成されています。</p>
<div style="text-align: center;">
<img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/12799c7e-diagram-2.png" alt=""/> DistilBERTを用いたQuery Categorization
</div>
<p>前段部分にあたるDistilBERTは事前学習済みのモデルから自社のデータでFine Tuningしたものを流用しており、今回の検証では後段の分類器のみを先述の機械学習的アプローチと同様にクエリログ及びクリックログから学習させた形になります。学習させたモデルのパフォーマンスは、我々のテストデータでの評価ではmicro-F1スコア 0.80となりました。<br />
実際に本モデル及び前項に記載した機械学習モデルをオンラインテストで比較したところ、変換対象となったキーワードのカバレッジが本構成において2倍になっており、今後改善を行う上で汎用性の高い言語モデルであるBERTを用いるメリットが確認できました。</p>
<h2>まとめ</h2>
<p>本記事では弊チームで実装・検証を行ったQuery Categorizationに対しての複数アプローチについて紹介しました。特に最後のDistilBERTをベースとした手法に関しては、既存の言語モデルを流用することが可能で学習自体も1日未満で完了と、省エネながら確かな結果が得られる点が興味深かったです。当初の目的であった「お客さまの検索ニーズを正しく理解し、適切な商品を提案する」については、統計的有意差のある結果にはならなかったものの、検索結果上位のアイテムのCTRが増加したことが確認できました。より優れた検索体験を提供できるよう、更なる改善を今後も継続していきます。<br />
検索エンジニアとして面白みを感じる分野である一方、今後ベクトルベースの検索がメジャーになったシーンにおいて既存のQuery Understanding技術がどう適用されるのか、進化していくのかがとても興味深いところです。</p>
<p>明日は@mtsukaさんが担当します。お楽しみに!</p>
- お手軽な検索API構築 その2 ~マルチコア・ベクトル・分散検索https://engineering.mercari.com/blog/entry/20231222-simple-search-api-2/https://engineering.mercari.com/blog/entry/20231222-simple-search-api-2/<p>こんにちは。株式会社メルペイのSolutionsチームのデータエンジニアの@orfeonです。 この記事は、Merpay Advent Calendar 2023 の22日目の記事です。 Solutionsチームは、社内 […]</p>
Fri, 22 Dec 2023 10:00:30 GMT<p>こんにちは。株式会社メルペイのSolutionsチームのデータエンジニアの<a href="https://twitter.com/orfeon">@orfeon</a>です。<br />
この記事は、<a href="https://engineering.mercari.com/blog/entry/20231124-merpay-advent-calendar-2023/">Merpay Advent Calendar 2023</a> の22日目の記事です。</p>
<p>Solutionsチームは、社内向けの技術コンサルや技術研修、部門を跨いだ共通の問題を発見して解決するソリューションの提供などを行っています。<br />
私は主に社内のデータ周りの課題を解決するソリューションを提供しており、一部の成果はOSSとして公開しています。<br />
<a href="https://engineering.mercari.com/blog/entry/20210906-46976c788c/">過去の記事</a>では全文検索OSSである<a href="https://solr.apache.org/">Apache Solr</a>を<a href="https://cloud.google.com/run">Cloud Run</a>上で利用して手軽に検索APIを構築する構成を紹介しました。<br />
社内向けのソリューションの一つとして社内向けの検索APIを使ったサービスなど小規模な検索システムの構成に役立てています。<br />
前回の記事の時点では、検索対象として搭載できるデータサイズなどにいくつかの制約がありました。<br />
今回の記事では、構成をブラッシュアップすることで機能を追加したり、制約を一部克服できるようになりましたので、その実現方法と構成を紹介します。</p>
<h3>はじめに</h3>
<p>新しい構成を紹介するにあたって、まずは過去の記事で紹介したSolr検索サーバをCloud Runにデプロイする構成をおさらいします。<br />
この構成を大雑把に説明すると、事前に作成した検索インデックスをSolrのコンテナイメージに直接同梱してそのままCloud Runにデプロイしてしまうというアイデアになります。<br />
以下、定期的にデータソースからインデックスを生成して同梱したSolrコンテナをCloud Runにデプロイする構成図の例です。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/a91bfc68--2023-12-19-9.36.44-1024x532.png" alt="" /></p>
<p>大きく分けて、検索インデックスファイルを指定したデータソースから生成するバッチジョブと、Solrのコンテナイメージに完成した検索インデックスを追加したイメージを作成し、Cloud Runにデプロイする2つのステップから構成されます。<br />
検索インデックスファイルの生成には<a href="https://cloud.google.com/dataflow">Cloud Dataflow</a>を、コンテナイメージの生成とCloud Runへのデプロイには<a href="https://cloud.google.com/build">Cloud Build</a>を利用しています。<br />
Cloud DataflowとCloud Buildを<a href="https://cloud.google.com/scheduler">Cloud Scheduler</a>から定期実行することで、指定したデータソースを元に検索インデックスをビルドし、Solr検索APIサーバとしてCloud Run上に自動的に反映される仕組みが構築できます。</p>
<p>Cloud Run上で動いているSolrサーバでの逐次的な検索インデックスの更新は行わない想定のため、同一で不変のインスタンスが負荷に応じてスケールするというとてもシンプルな構成になります。<br />
一方で以下のような制約があります。</p>
<ul>
<li>検索インデックスのサイズがコンテナイメージに載せられる量に制限される</li>
<li>データの更新頻度はそれほど高くはできない(1日数回程度)</li>
</ul>
<p>今回の記事ではこの構成をベースとして追加した新しい機能や、上に挙げた制約を回避するための構成として次の項目について紹介します。</p>
<ul>
<li>複数コア検索対応</li>
<li>ベクトル検索インデックス構築支援</li>
<li>分散検索対応</li>
</ul>
<h3>複数コア検索</h3>
<p>最初に紹介するのは複数コア検索対応です。</p>
<p>Apache Solrでは検索対象となるデータセットを<a href="https://solr.apache.org/guide/solr/latest/configuration-guide/core-discovery.html">コア</a>という単位で管理しています。<br />
コアは検索データセットのインデックス、スキーマ、設定情報を管理しており、RDBにおけるテーブルのような位置付けになります。<br />
検索時に複数のコアを利用することで異なるデータセットを横断した検索を手軽にできるようになります。</p>
<p>例えばECマーケットで自分がお気に入りに登録したショップの商品だけ検索したい場合を考えます。<br />
お気に入りのショップの数が少ない場合は、Solr APIを呼び出すアプリケーション側でお客さまのお気に入りショップを取得して、検索時のフィルタ条件に追加することで実現することもできます。<br />
しかし、フィルタ条件を動的に組み立てる仕組みをアプリケーション側が管理しないといけません(お気に入りショップをDBから取得しORのフィルタ条件を組み立てるなど)。またショップの数が多いとリクエストサイズの制限に引っかかる可能性も出てきます。</p>
<p>そこで商品検索用のコア(Items)とは別に、お客さまのお気に入りショップ情報を管理するコア(FavoriteStores)を用意しておきます。<br />
商品検索用のコアとお気に入りショップのコアを検索時にショップIDで結合することで、検索結果をお気に入りショップのみを対象に絞り込んだ上で該当するショップの取り扱っている商品だけを検索することができます。<br />
Solrでは検索時にコア間の関係を正規化するためのクエリパーサーとして<a href="https://solr.apache.org/guide/solr/latest/query-guide/join-query-parser.html">Join Query Parser</a>が提供されています。<br />
以下はJoin Query Parserを利用した検索リクエストの例です。</p>
<pre><code>https://{solr url}/solr/Items/select?q=GCP&fq={!join from=ShopID fromIndex=FavoriteStores to=ShopID}UserID:0123456789</code></pre>
<p>上記の検索リクエストは以下のようなSQLクエリと同等のものになります。</p>
<pre><code class="language-sql">SELECT *
FROM Items
WHERE ShopID IN (
SELECT ShopID
FROM FavoriteStores
WHERE UserID = "0123456789"
)</code></pre>
<p>過去に紹介した記事では単一のCloud Dataflowパイプラインでは単一のコアのインデックスのみ生成することができました。<br />
そこでSolrのインデックスを生成するMercari Dataflow Templateの<a href="https://github.com/mercari/DataflowTemplate/blob/master/docs/config/module/sink/localsolr.md">localsolr sinkモジュール</a>を機能拡張して、複数のコアを一度に作成できるように対応しました。<br />
これにより異なるデータセットを横断検索できるSolrサーバを手軽に構築できるようになりました。</p>
<p>以下はMercari Dataflow Templateで2つのBigQueryデータソースからそれぞれ対応する2つのコアの検索インデックスファイルを生成するsinkモジュールの設定の例になります。<br />
コアごとに入力とスキーマ等の設定ファイルを指定します。</p>
<pre><code class="language-json"> "sinks": [
{
"name": "LocalSolr",
"module": "localSolr",
"inputs": ["BigQueryItems", "BigQueryFavoriteStores"],
"parameters": {
"output": "gs://${bucket}/output/index.zip",
"cores": [
{
"name": "Items",
"input": "BigQueryItems",
"schema": "gs://${xxx}/Items/schema.xml"
},
{
"name": "FavoriteStores",
"input": "BigQueryFavoriteStores",
"schema": "gs://${xxx}/FavoriteStores/schema.xml"
}
]
}
}</code></pre>
<p>Mercari Dataflow Templateのlocalsolr sinkモジュールは生成したSolrのインデックスファイルをzipファイルとしてoutputで指定されたCloud Storageのパスに保存します。</p>
<p>複数のコアをコンテナイメージに同梱するDockerfileは以下のようになります。<br />
インデックスファイルはコアごとにディレクトリが分かれています。<br />
zipを解凍してコアごとのインデックスのディレクトリをSolrのデータディレクトリにそれぞれコピーします。</p>
<pre><code class="language-Dockerfile">FROM solr:9.4.0
USER solr
COPY --chown=solr:solr Items/ /var/solr/data/Items/
COPY --chown=solr:solr FavoriteStores/ /var/solr/data/FavoriteStores/
ENV SOLR_PORT=80</code></pre>
<h3>ベクトル検索インデックス構築支援</h3>
<p>次に紹介するのはベクトル検索インデックスの構築支援についてです。<br />
Solr 9.0から<a href="https://solr.apache.org/guide/solr/9_0/upgrade-notes/major-changes-in-solr-9.html#querying-and-indexing">ベクトル検索がサポート</a>されました。<br />
ベクトル検索により検索キーワードが含まれているコンテンツだけでなく、検索キーワードに意味的に似ているコンテンツを検索することができるようになります。<br />
しかし、コンテンツの内容を表すベクトルは検索インデックス構築時に自分で用意する必要があります。<br />
テキストや画像などのコンテンツからベクトルを生成するには、自前のembeddingモデルを用意して推論したり、embedding用のAPIを利用するなどいくつか方法があります。<br />
しかし検索インデックス構築パイプラインに案件ごとでこうしたコンテンツのベクトル化の処理を挟み込むのは少し面倒です。</p>
<p>そこで検索インデックスを生成するCloud Dataflowで、あらかじめデータをベクトル化するために作成した<a href="https://onnx.ai/">ONNXモデル</a>を使って、入力データをベクトル化するための<a href="https://github.com/mercari/DataflowTemplate/blob/enhance-spanner-changestream/docs/config/module/transform/onnx.md">onnx transformモジュール</a>を開発しました。<br />
これにより、データ取得からベクトル化、検索インデックス構築を一筆書きのパイプラインで実現できるようになりました。<br />
以下、Mercari Dataflow Templateで入力データの指定したフィールドをベクトル化するonnx transformモジュールの設定の例になります。<br />
あらかじめ作成してGCSに保存しておいたONNXファイルをモデルとして指定して、入力データのフィールドやベクトル化出力とONNXモデルの入出力のマッピングを指定しています。</p>
<pre><code class="language-json"> "transforms": [
{
"name": "OnnxInference",
"module": "onnx",
"inputs": [
"BigQueryContentInput"
],
"parameters": {
"model": {
"path": "gs://example-bucket/multilingual_v3.onnx",
"outputSchemaFields": [
{ "name": "outputs", "type": "float", "mode": "repeated" }
]
},
"inferences": [
{
"input": "BigQueryContentInput",
"mappings": [
{
"inputs": {
"inputs": "Content"
},
"outputs": {
"outputs": "EmbeddingContent"
}
}
]
}
]
}
}
]</code></pre>
<p>検証では、TensorFlow Hubで公開されている <a href="https://www.tensorflow.org/hub/tutorials/semantic_similarity_with_tf_hub_universal_encoder?hl=ja">universal-sentence-encoder-multilingual/v3</a> モデルをONNX化して、Solr検索インデックス構築時のテキストデータのベクトル化に利用しました。<br />
2,000 程度の日本語のPDFファイル(250MB)のベクトル化を6vCPU程度のリソースコストで完了することができました。<br />
現状ではONNX推論はCPU環境のみ対応ですが、今後はGPU環境対応なども検討していきたいと思っています。</p>
<p>※ちなみにこの機能を追加した後に、<a href="https://cloud.google.com/blog/products/data-analytics/introducing-bigquery-text-embeddings">BigQueryの機能追加</a>によりSQLでテキストデータから手軽にベクトルを生成できるようになりました。<br />
BigQueryではGoogleが構築済みのembeddingモデルをすぐに利用することができます。<br />
手軽にベクトル検索を試してみたい方はまずこちらの機能を利用してみると良さそうです。</p>
<h3>分散検索</h3>
<p>最後に紹介するのは分散検索対応です。<br />
過去の記事ではSolrのスタンドアロンモードでの起動を前提としていました。<br />
スタンドアロンモードの通常の検索だと、検索インデックスは単一の検索ノード上に閉じるため、検索インデックスのサイズにはCloud Runインスタンスに載せられるだけという上限があります。<br />
しかしSolrではスタンドアロンモードでも複数ノードにまたがった分散検索に対応しています。<br />
そこでCloud RunでもSolr分散検索に対応した構成にすることで、単一のCloud Runインスタンスに載らない大規模なデータセットも検索できるようにしました。</p>
<h4>Solrの分散検索</h4>
<p>まず前提となるSolrのスタンドアロンモードでの<a href="https://solr.apache.org/guide/solr/latest/deployment-guide/user-managed-distributed-search.html#distributing-documents-across-shards">分散検索機能</a>を紹介します。<br />
Solrでは1つの巨大なインデックスをシャードと呼ばれる小さなインデックスに分割して、複数のノードに分散配置することができます。<br />
分散検索では、これらの複数のノードに分散配置されたシャードに対して一括検索することができます。<br />
分散検索の実行には特別な設定は必要なく、検索対象としたいシャードを持つノードのエンドポイントを検索リクエストのshardsパラメータで指定することで実現します(複数ノード指定も可)。<br />
分散検索では最初に検索リクエストを受け付けたノードが、shardsパラメータで指定されたノードに対して同じ検索リクエストを発行して検索結果を受け取り、マージして最終的な検索結果として返す仕組みになっています。</p>
<p>以下、3つのエンドポイントへの分散検索リクエストの例です。</p>
<pre><code>https://{solrShardA}/solr/Items/select?q=GCP&shards=https://localhost:8983/solr/Items,https://{solrShardB}/solr/Items,https://{solrShardC}/solr/Items</code></pre>
<h4>Cloud Runへの分散検索の適用</h4>
<p>Solrの分散検索の仕組みをCloud Run上で実現する構成を考えます。<br />
先に紹介した通り、Solrの分散検索ではシャードごとに異なるエンドポイントを持つ必要があります。<br />
Cloud Runでは役割に応じてサービスという単位でエンドポイントを分けることができます。<br />
そこでシャードごとにサービスを分割して、リクエスト時にこれらのシャードに対応するサービスのエンドポイントをshardsパラメータで指定することで分散検索を実現しました。<br />
Cloud Runではサービスごとにノード数をスケールさせることができます。<br />
そのためデータセットが不均衡で一部検索処理が重いシャードがあっても、そのサービスのノードだけ自動でスケールさせることができます。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/5c7d3c4e--2023-12-22-9.52.14.png" alt="" /></p>
<p>Solr分散検索をCloud Run上で運用するに当たって、検索インデックスの生成ステップでは追加の開発は特に必要ありません。<br />
Cloud Runのサービスをシャードごとにデプロイするようにするだけです。<br />
そのためにCloud Dataflowによるインデックスの生成をシャードごとに生成するように変更します。<br />
Cloud BuildによるCloud Runへのデプロイもシャードごとにサービスを分けてデプロイするようにします。</p>
<p>Solrの分散検索の注意点ですが、検索結果のX件目から10件取得するといったオフセットを指定して取得する場合、オフセットに比例して消費メモリや処理が重くなることが挙げられます。<br />
これは複数ノードから取得した検索結果を一箇所に集めてソートするために起こります。<br />
分散検索はなるべくこうした問題が顕在化しない、トップX件のみ利用するようなケースに適用するのが望ましいでしょう。<br />
別の注意点としては、検索リクエストを送る側や各サービスはシャードごとのエンドポイントを把握しておく必要があることが挙げられます。<br />
データセットを分割するシャードが変わらない場合は問題にならないのですが、頻繁にシャードが追加されたり変更されるような場合は、シャードと紐づくエンドポイントの情報をサービスやアプリケーション間で共有するための工夫が必要になります。</p>
<h3>おわりに</h3>
<p>今回の記事では、Cloud Run上で手軽に検索APIを構築するための構成について、前回の記事から新しく追加した機能や構成を紹介しました。<br />
<a href="https://engineering.mercari.com/blog/entry/20230622-easy-solution-for-utilizing-graph-database/">過去の他の記事</a>でもCloud RunでNeo4jを動かす構成を紹介しました。<br />
Cloud Runはフルマネージドなサービスであり、比較的小規模なデータを扱うAPI手軽に構築するにはとても便利なサービスだと思っています。<br />
今後もCloud Runなどを通じて様々なデータを手軽に扱う仕組みを検証して、社内のデータ活用に役立てていきたいと思います。</p>
<p>今回紹介したSolrの検索インデックスの生成に用いた<a href="https://github.com/mercari/DataflowTemplate">Mercari Dataflow Template</a> はOSSとして公開しており、技術書典の全文検索にも活用されています。<br />
もしCloud RunでSolrを使った検索APIを手軽に構築してみたい方はぜひお試しもらえればと思います。</p>
<p>また今回の記事に向けて、データの更新頻度をニアリアルタイムに近づけるための仕組みも検証中だったのですが、残念ながら間に合いませんでした。<br />
次回の記事でニアリアルタイム検索の仕組みについても紹介できればと思います。</p>
<p>明日の記事は @iwata さんによるGitHub Actionsを使った自動化です。引き続きお楽しみください。</p>
- iOSDC2023で発表した「メルカリ10年間のiOS開発の歩み」のトークスクリプトを公開しますhttps://engineering.mercari.com/blog/entry/20231222-mercari-ios-10yrs-development-talk-script/https://engineering.mercari.com/blog/entry/20231222-mercari-ios-10yrs-development-talk-script/<p>はじめに こんにちは。メルカリ Director of Engineering の @motokiee です。この記事は、Mercari Advent Calendar 2023 の21日目の記事です。 メルカリのサービ […]</p>
Thu, 21 Dec 2023 11:00:27 GMT<h1>はじめに</h1>
<p>こんにちは。メルカリ Director of Engineering の @motokiee です。この記事は、<a href="https://engineering.mercari.com/blog/entry/20231124-mercari-advent-calendar-2023/">Mercari Advent Calendar 2023</a> の21日目の記事です。</p>
<p>メルカリのサービス開始から10周年ということで、2023年9月に<a href="https://iosdc.jp/2023/">iOSDC Japan 2023</a> カンファレンスで「メルカリ10年間のiOS開発の歩み」について<a href="https://engineering.mercari.com/blog/entry/20230920-iosdc_2023_mercari_ios_10years_history/">発表を行いました</a>。</p>
<p>この発表は、10年間のiOS開発の歴史を40分のトークにまとめたものです。メルカリはこの10年多くの技術的なチャレンジをして断続的にアプリケーションをアップデートしてきました。自分が見てきた歴史と、見ていない歴史については git log を手繰りながら調査した集大成となっています。</p>
<p>サービスの歴史が長くなると、アプリケーションのリファクタリングはもちろん、作り直す話も出てくると思いますが、そういった意思決定の際の参考になればと思い作成しています。</p>
<p>なお<a href="https://www.youtube.com/watch?v=MyyPwfgdMcg">発表のアーカイブ動画</a>もありますが、動画を見るのも以外と腰が重かったりするため、文章のほうが自分の都合で見やすく、良い選択である場面もあると思います。また、テキストの方がChatGPTなどLLMでサマリを作るコストも低くなりタイパ(タイムパフォーマンス)重視の方には良いのではないかと思い、トークスクリプトを公開してみると良いのではないか、と考えました。</p>
<p>ぜひご覧ください。</p>
<p><!--more--></p>
<h1>トークスクリプト全文</h1>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/dea84d4e-output-001.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/dea84d4e-output-001.png" alt="" /></a><br />
よろしくお願いします。「メルカリ10年間のiOS開発の歩み」というタイトルで発表します。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/4fbce909-output-002.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/4fbce909-output-002.png" alt="" /></a><br />
自己紹介です。motokieeといいます。<br />
現在は株式会社メルカリで Director of Engineering をしています。メルカリには2016年に入社し、丸7年が経過しました。<br />
メルカリでは、メルカリ本体や新規事業にエンジニアやエンジニアリングマネジャーとして携わってきました。<br />
現在はMobile, Web, Backend の アーキテクトチームをDirector of Engineeringとして管轄しています。ちなみに現在はiOSの開発はしていません。なのでお手柔らかにお願いします。<br />
iOSDCは2016年から2019年までコアスタッフをしていました。スピーカーとしての参加も久しぶりなのでとても緊張しています。よろしくお願いします。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/0a1b0f39-output-003.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/0a1b0f39-output-003.png" alt="" /></a><br />
まずはこのトークでオーディエンスのみなさんが得られるものについて簡単にご紹介します。<br />
メルカリはこの度10周年を迎えることができました。<br />
これもひとえに使っていただいたお客さまのおかげではありますが、この10年間でどのように会社、サービス、そしてiOS関連技術が変化してきたかをご紹介します。<br />
また10年間のメルカリアーキテクチャやTech Stackの変遷についてもご紹介します。これまでの10年に負けないくらいの変化が今後も起こるはずだと考えており、エンジニアとしてこれからの変化にどう対応していくかのヒントが得られるのではないかと思います。<br />
そして最後に、昔からiOS開発をしている方々には温故知新、少し懐かしい気持ちになってもらえるのではないかと思います。<br />
最近iOS開発を始めた方々には、昔の出来事を振り返って、自分たちがこれから取り組むかもしれない開発へのヒントにしていただければ幸いです。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/5fb8a441-output-004.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/5fb8a441-output-004.png" alt="" /></a><br />
それではトークに移りますが、メルカリについて簡単にご紹介させてください。<br />
私達はミッションとバリューをとても大切にしています。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/164deeb2-output-005.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/164deeb2-output-005.png" alt="" /></a></p>
<p>まずミッションですが、今年10年を迎えミッションが「あらゆる価値を循環させ、あらゆる人の可能性を広げる」にアップデートされました。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/53871881-output-006.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/53871881-output-006.png" alt="" /></a><br />
そしてバリューです。Go Bold, All for One, Be a Proです。<br />
日本語に訳すと、大胆にやろう、全ては成功のために、プロフェッショナルであれ、をValueとして掲げています。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/ee2ba5b1-output-007.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/ee2ba5b1-output-007.png" alt="" /></a><br />
続いてはサービスについて、特にフリマ事業がサービス開始から10年でどのような立ち位置にいるか簡単にご紹介します。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/e9a3143c-output-008.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/e9a3143c-output-008.png" alt="" /></a><br />
2023年7月時点で、メルカリの月間利用者数は2200万人以上となっています。累計でメルカリに出品された商品は30億品以上、さらに 2022年の取引件数を1年間の秒数で割ったところ、1秒間に7.9個売れていることがわかりました。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/924e4327-output-009.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/924e4327-output-009.png" alt="" /></a><br />
サービス開始当初の2013年は20-40代の方を中心に使われていましたが、現在ではシニア層の方も含め幅広くバランスよくご利用されています。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/1977bb2e-output-010.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/1977bb2e-output-010.png" alt="" /></a><br />
続いて取扱いカテゴリですが、2014年にはレディースファッションカテゴリが最もシェアが大きかったのですが、現在は、本・ゲーム・おもちゃといったインドア向けアイテムがトップシェアを占めています。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/75ceaa5e-output-011.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/75ceaa5e-output-011.png" alt="" /></a><br />
メルカリはアメリカでも事業を展開していますが、日本のフリマから国境を超えて取引が展開されています。代理購入サービスで海外のお客さまでもメルカリの商品を購入できるという取り組みが行われており、世界110か国以上の国・地域のお客さまに「メルカリ」でのお買い物をお楽しみいただけるようになっています。</p>
<p>以上、簡単なメルカリのフリマサービスについてのご紹介でした。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/7dd05b8f-output-012.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/7dd05b8f-output-012.png" alt="" /></a><br />
続いて今日のトークの全体像についてご紹介します。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/2a282c51-output-013.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/2a282c51-output-013.png" alt="" /></a><br />
今回、10年分の歴史を振り返るにあたって独自に年表を作成しました。使用されていた技術、重要なプロジェクト、その時々のスクリーンショットを集めて参考資料として作成しました。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/f58956c7-output-014.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/f58956c7-output-014.png" alt="" /></a><br />
少し文字が小さいですが、ざっくりと流れをご紹介します。<br />
1年ごとの取り組みを分析してみてタイトルを付けてみました。2013年から2015年は Build 期 だったと言えそうです。</p>
<p>このころはフリマサービスに必要な機能を次々と実装していた期間でもありますが、同時に新しい事業・技術ともに新しい領域への探索がスタートした時期でもありました。立ち上げ期、Buildにフォーカスした時期だったのかなと思います。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/5799559c-output-015.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/5799559c-output-015.png" alt="" /></a><br />
続いて2015年から2017年あたりは、Explore, 探索期ですね。次々と新規事業が生まれていった時期だったと思います。<br />
Swiftはもちろん、Reactive Programming の導入も始まっていて、新しい技術の探索を始めた時期だったのかなと思います。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/fcdf3f69-output-016.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/fcdf3f69-output-016.png" alt="" /></a><br />
2018年から数年は Re-architecture and Foundation 期です。<br />
2018年には Re-architecture が始まり、2019年頃から Design System, Weekly Release, ログの改善など、アプリ開発周辺基盤の強化に力を入れていた期間でもありました。この間、開発基盤のために事業を止めていたわけではなく、スマホ決済のメルペイもサービスローンチされたりしています。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/d566ad8e-output-017.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/d566ad8e-output-017.png" alt="" /></a><br />
そして2020年-2022年はRewrite期です。<br />
日本のメルカリアプリをRewriteする取り組みの期間でしたし、US、新規事業でもフルスクラッチで開発を行っていました。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/69e02b52-output-018.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/69e02b52-output-018.png" alt="" /></a><br />
2023年現在、いまは Post Rewrite と呼べる時期で、また新しいことに取り組んでいたりします。<br />
以上が10年をフェーズに分けた全体像となりますが、ここから各年掘り下げてご紹介していきたいと思います。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/20b2ae98-output-019.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/20b2ae98-output-019.png" alt="" /></a><br />
まずは2013年です。この年はメルカリが誕生した年です。最初にiOS周辺技術での出来事を簡単に振り返りましょう。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/14fe59aa-output-020.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/14fe59aa-output-020.png" alt="" /></a><br />
2013年はiOS7が発表された年です。いわゆるスキューモフィズムからフラットデザインへの大きな変更が行われた年と言っても良いでしょう。<br />
iPhone 5s、iPhone 5c が発売リリースといった出来事がありました。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/16749670-output-021.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/16749670-output-021.png" alt="" /></a><br />
メルカリのサービスとしては、2013年7/2にAndroid版が、少し遅れて2013年7/23にiPhone版の提供がスタートしたようです。<br />
iPhone版はわずか半年後に 「App Store Best of 2013 今年のアプリ」を受賞していて、急速にサービスが伸びていったことがうかがえます。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/ade94836-output-022.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/ade94836-output-022.png" alt="" /></a><br />
ちなみにメルカリで最初に売れた商品は「ドット柄のカットソー」みたいです。<br />
サービス開始当初はどんなUIだったかというと…</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/840dd41b-output-023.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/840dd41b-output-023.png" alt="" /></a><br />
こちらは当時のプレスリリースに掲載されていた画像です。ロゴやUIに時代を感じますね。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/d5279868-output-024.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/d5279868-output-024.png" alt="" /></a><br />
また、 App Store Connect API を使ってApp Storeに設定されたスクショを全て取得しています。こちらは2013年7月にリリースされた際、App Store に設定されていたスクリーンショットです。2013年って感じですね。<br />
続いて メルカリのiOSリポジトリの git log から2013年がどんな年だったか見てみましょう。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/24e8582b-output-025.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/24e8582b-output-025.png" alt="" /></a><br />
主要なデータとして、コミッター数、コミット数、そしてdiffを1年ごとに集計しています。コミッター数は重複を含むため、正確な数字ではありませんが、スタートアップらしい少人数体制で開発をしていた時期です。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/c3b86646-output-026.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/c3b86646-output-026.png" alt="" /></a><br />
そしてこちらが記念すべき最初のコミットログです。タイムゾーンがなぜか(アメリカ・カナダ)の山岳部標準時 – MSTになっているのですが、JSTでは2013年03月15日(金) 21:17でした。ちなみに調べたところ大安でした。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/681cf254-output-027.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/681cf254-output-027.png" alt="" /></a><br />
また、メルカリiPhone版が提供されたのは7/23のv1.0.1ですが、それより以前に App Store で v1.0.0が審査を通過しています。メルカリ最初のiOSエンジニアのoobaさんに背景を伺ったところ、当時 App Store の審査に 2週間から1ヶ月かかることもあったため、rejectされないかの確認のためのサブミットを行った、とのことでした。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/f362537d-output-028.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/f362537d-output-028.png" alt="" /></a><br />
先程のv1.0.1の配信開始が2013年7/18、その後7/23にプレスリリースを出しています。2013年3月の最初のコミットから約4ヶ月の開発期間を経てのリリースでした。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/f95f101d-output-029.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/f95f101d-output-029.png" alt="" /></a><br />
ちなみに7.23のリリース初日はわずか2000ダウンロードでした。これが2013年末までの半年弱で100万ダウンロードを突破することになるので、すごいスピードだと思います。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/3bcf939a-output-030.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/3bcf939a-output-030.png" alt="" /></a><br />
2013年の技術トピックをまとめてみました。<br />
このころは Objective-C かつ MVC でアプリケーションが書かれていました。なぜならSwiftは2014年発表だからですね。ちなみに iOS4~iOS7がサポートバージョンとなっていました。<br />
メルカリの商品リストは CollectionView で実装されましたが、UICollectionViewは iOS6で登場したAPIだったため、それ以前のバージョンにはPSTCollectionViewというOSSが使われていました。昔からiOS開発をしている皆さんにはおなじみではないでしょうか。<br />
また、AFNetworking, SVProgressHUD などお馴染みのライブラリに加え、まだ Apple に買収される前のTestflight SDK も利用されていました。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/cea1fd3e-output-031.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/cea1fd3e-output-031.png" alt="" /></a><br />
あとはスキューモフィズムデザインですね。本物の物質に寄せてディテールを細かく施すデザインですかね。メルカリiOSの最初のPull Request をチェックしてみたら、こんな感じで立体感のあるデザインになっていました。<br />
この点もoobaさんに伺ったところ、iOS7が発表されて「やばい」となって急いでフラットデザイン対応をされたとのことでした。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/1e06448f-output-032.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/1e06448f-output-032.png" alt="" /></a><br />
また最初期はWebViewベースのガワアプリを検討していたようですが、結果的に体験を重視してネイティブアプリの開発に切り替えています。<br />
WebViewベースで押し切った時にメルカリがサービスとしてどうなったのか知るすべはありませんが、とても気になりますね。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/b205db46-output-033.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/b205db46-output-033.png" alt="" /></a><br />
続いて2014年です。この年、初のTV CM放映がされました。2013年末までに100万ダウンロードを突破していましたが、さらに加速的にサービスが成長していきます。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/fff1e850-output-034.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/fff1e850-output-034.png" alt="" /></a><br />
先に2014年のiOS周辺技術の出来事を見てみましょう。<br />
この年Swiftが発表されます。iOSアプリの開発に携わる皆さんにとってエポックメイキングな出来事だったと思います。メルカリも例外ではなく、この後数年、Swiftを軸に様々な技術的な取り組みが続くことになります。<br />
またiPhone6, iPhone6 Plusが発売された年でした。フォームファクターが増えたことは大きな出来事ですが、@3x 画像の登場で画像アセットの更新が大変だったり、AutoLayoutに対応せず 3.5inch と 4inch 画面で分岐するようなコードを書いていた方には思い出深いできごとではないでしょうか。僕もたくさんの画面のAutoLayout対応を行った覚えがあります。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/2ae79a09-output-035.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/2ae79a09-output-035.png" alt="" /></a><br />
サービス、会社として2014年の大きな出来事はこちらです。なんと500万ダウンロードを突破します。またUSでのサービススタートなど2年目にしてかなりの打ち手がありました。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/6c73ee5e-output-036.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/6c73ee5e-output-036.png" alt="" /></a><br />
App Store のスクショはこんな感じになりました。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/7abcccd1-output-037.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/7abcccd1-output-037.png" alt="" /></a><br />
まずGit log を見てみましょう。<br />
そこまで大きな変化はないですね。コミッター数としては増えていますが、ユニーク数ではないので実際にエンジニアが増えたどうかは分かりません。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/01262471-output-038.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/01262471-output-038.png" alt="" /></a><br />
このスクショはv3にメジャーアップされた後のものです。v3へのメジャーバージョンアップはデザインリニューアルを主な理由としています。<br />
2014年7月にリリースされた v3系 は2019年にメルペイがリリースされるまで5年弱続くことになるとても長寿なバージョンとなりました。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/bbb0edcf-output-039.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/bbb0edcf-output-039.png" alt="" /></a><br />
引き続きObjective-Cが使われていました。2014年はSwiftが発表された年ですが、この年のコミットにSwiftのコードは入っていませんでした。<br />
一方この年、メルカリでReactiveCocoaがライブラリとして取り入れられ、一部の画面がMVVMで実装され始めていました。また cocoapods が package manager として取り入れられていました。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/0406067c-output-040.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/0406067c-output-040.png" alt="" /></a><br />
また、USのサービス開始に伴い、日本とアメリカのサービスでソースコードが共有されるようになったことも大きな出来事です。コードは共有しながら、国ごとにターゲットを分けて別バイナリを配布するアプローチを取っていました。<br />
以上が2014年です。サービスとしてはかなり伸びていましたが、まだまだ技術を見直すようなタイミングにはなっていません。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/c30e8350-output-042.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/c30e8350-output-042.png" alt="" /></a></p>
<p>そして2015年です。<br />
この年は commit log や チケットなどをたどるとフリマサービスとしての基礎体験の磨き込みに力を入れていた時期だったように思います。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/9b0ed16b-output-043.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/9b0ed16b-output-043.png" alt="" /></a><br />
iOS周辺技術としてはこんな感じです。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/a66e90f3-output-044.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/a66e90f3-output-044.png" alt="" /></a><br />
サービスとしては2015年1月に1000万ダウンロードを突破します。機能的には、「らくらくメルカリ便」という便利な配送方法の提供を開始した時期でもあります。<br />
また、2015年後半には新規事業を手掛ける子会社ソウゾウが設立されました。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/09969027-output-045.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/09969027-output-045.png" alt="" /></a><br />
App Store のスクショはあまり変化がないですね</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/f0394949-output-046.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/f0394949-output-046.png" alt="" /></a><br />
Git log もそこまで大きな変化はありませんが、コミッターが増えています。<br />
この頃からiOSの勉強会に行くと、メルカリで働いているという人を見かけるようになった覚えがあります。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/4b97eeb2-output-047.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/4b97eeb2-output-047.png" alt="" /></a><br />
技術トピックとしては、この年からSwiftが実戦投入され始めます。新しい画面や Extension がSwiftで実装され始めています。<br />
Git logをたどると、機能開発ですごく忙しかったような印象を受けましたが、チケットのタイトルを見てもUXを向上させるような施策に集中して数多く実装していた時期だったようです。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/1af14a28-output-048.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/1af14a28-output-048.png" alt="" /></a><br />
また新規事業でフルSwift, RxSwiftでの開発が始まり、新しい技術の探索が始まったタイミングとも言えるのではないかと思います。<br />
以上が2015年のできごとです。<br />
このあと数年メルカリの規模に合わせた開発を模索していくことになるのですが、振り返ってみるとその礎がこの2015年あたりに築かれたような気がしています。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/220198a4-output-049.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/220198a4-output-049.png" alt="" /></a><br />
続いて2016年です。この年はUSへのフォーカスと、メルカリ初の新規事業がローンチした年でもあります。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/cb0bb347-output-050.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/cb0bb347-output-050.png" alt="" /></a><br />
そんな2016年はiOS10, iPhone 7 が発表されました。ジェットブラックありましたね。<br />
そして、 第一回 iOSDCである iOSDC Japan 2016 が開催された年でもあります。ちなみに第一回は早稲田キャンパスではなく、練馬のココネリホールでの開催だったんですね。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/1d91e621-output-051.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/1d91e621-output-051.png" alt="" /></a><br />
僕も当時スタッフとして関わっていたのですが、「みんな来てくれるかな〜」「スポンサーさん集まるのかな〜」<br />
「まぁでも、誰も来てくれなかったら会場費用自腹でもくもく会をやればいいだけだしね」と度々主催者の長谷川さんが言っていました。<br />
ちなみにそんなiOSDCをメルカリは第一回はもちろん、かれこれもう8年連続でスポンサーとして応援しております!<br />
というわけで本題に戻ります。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/702127f6-output-052.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/702127f6-output-052.png" alt="" /></a><br />
2016年は匿名配送の提供開始、あとはアメリカのApp StoreでUS版メルカリがTop3にランクインするという出来事もありました。<br />
それからメルカリアッテというクラシファイドサービスのリリースですね。こちらのサービスはすでにクローズしております。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/24b34bf3-output-053.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/24b34bf3-output-053.png" alt="" /></a><br />
スクショはこんな感じです。ちょっとだけ変わりました。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/9ab41f4a-output-054.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/9ab41f4a-output-054.png" alt="" /></a><br />
git log はこんな感じです。なお新規事業のリポジトリは含んでいません。<br />
Diff がかなり多いのですが、ちょっとなぜこんなに多いのかまでは追いきれませんでした。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/853214d1-output-055.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/853214d1-output-055.png" alt="" /></a><br />
この年から、メルカリ本体でも新規事業でも リアクティブライブラリを使っての開発が行われるようになり、リアクティブライブラリの知見が社内に溜まっていくことになります。<br />
メルカリ本体はObjective-CとSwiftの併用、新規事業がこのあと続々立ち上がっていくのですが、そちらはフルSwift + RxSwift での開発となっていきました。また Carthage がこの年導入されていました。<br />
以上が2016年のできごとでした。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/6b6efa78-output-063.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/6b6efa78-output-063.png" alt="" /></a><br />
続いて2017年ですが、この年は新規事業がたくさん立ち上がります。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/a289c563-output-064.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/a289c563-output-064.png" alt="" /></a><br />
iOS周辺技術の出来事としては、iPhone Xが登場します。ノッチの登場ですね。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/00a1aa8e-output-065.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/00a1aa8e-output-065.png" alt="" /></a><br />
サービスとしてはAI出品機能、「ゆうゆうメルカリ便」が提供開始となり、さらにアプリは世界1億ダウンロードを突破します。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/93e16ee7-output-066.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/93e16ee7-output-066.png" alt="" /></a><br />
この年はニュースが多くて、USメルカリアプリが書き直されてリニューアルされます。<br />
またイギリスでもサービスがスタート、他にも新サービス・新機能が続々とリリースされますがこれはすでにクローズされています。後半にはメルペイが設立され、数年後のスマホ決済サービスの準備がスタートします。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/0f7bf832-output-067.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/0f7bf832-output-067.png" alt="" /></a><br />
スクショはこんな感じです。あんまり変わらないですね。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/287dc0ea-output-068.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/287dc0ea-output-068.png" alt="" /></a><br />
Git log はというと、増えてはいますが、これもそこまで変わりません。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/686842d5-output-069.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/686842d5-output-069.png" alt="" /></a><br />
この年はUSアプリを書き直す・リライトする “Double” というプロジェクトがUSメルカリアプリで行われました。Swiftで書き直されたのですが、ネイティブのコードに加えて React Native も導入されていました。<br />
また、2015年に立ち上がったメルカリ アッテの設計をベースとして、Swift/RxSwift/MVVMでいくつも姉妹アプリが立ち上がりました。<br />
メルカリ本体のメルカリNow、メルカリチャンネルのような新機能もSwiftがメインで開発されるようになりました。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/016e733c-output-070.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/016e733c-output-070.png" alt="" /></a><br />
このころから少しずつ技術的な課題が出てきます。<br />
Objective-C と Swift だったり、新しい画面と古い画面が混在するようになってきたため、コンテキストスイッチのコストが高くなってて少しずつ課題となってきていました。また、事業として重要なコンポーネントや画面のメンテナンスがかなり困難になってきていました。<br />
エンジニアの人数も順調に増えていたので、複数人が同じ画面に改修を入れるケースも増えていき、結果コンフリクトが発生しやすくなり、他の人の作業に自分の作業がブロックされるようなことも増えていき、結果として開発の速度が上がりづらくなっていました。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/de6d73ab-output-071.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/de6d73ab-output-071.png" alt="" /></a><br />
そして2018年、ここから技術基盤を強化するプロジェクトがいくつも走っていくことになります。その最初のプロジェクトが Re-Architecture でした。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/da47d579-output-072.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/da47d579-output-072.png" alt="" /></a><br />
2018年のiOS周辺技術の主な出来事としては、iOS12, iPhone Xsの発売ですかね。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/d3c59878-output-073.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/d3c59878-output-073.png" alt="" /></a><br />
サービスとしてはシェアサイクルサービスであるメルチャリがリリースします。こちらは現在事業譲渡済みです。<br />
また6月にマザーズ上場、メルカリロゴのリニューアル、2016年から2017年で立ち上げたサービスが2018年の間にいくつもクローズされました。<br />
そして日本のメルカリチームでも海外からの採用が加速してきます。僕もこのころから仕事で英語を使う機会がかなり多くなりました。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/044d0c8b-output-074.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/044d0c8b-output-074.png" alt="" /></a><br />
Git log は激変しました。2017年は6000台だったコミット数が3倍強に増えています。<br />
コミッター数も90を超えましたが、これはユニークではないため数十人いた、くらいに捉えていただければ良いと思います。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/9f2c0738-output-075.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/9f2c0738-output-075.png" alt="" /></a><br />
そしてロゴはこのようにリニューアルされました。2013年当初から続いていた箱が開くデザインから変更されました。このロゴは現在も使われています。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/c6775b46-output-076.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/c6775b46-output-076.png" alt="" /></a><br />
こちらは2018年7月ごろ、ロゴが変わる前のApp Storeのスクショです。ロゴが変わった後の2018年10月のものをみてみましょう。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/c975c18e-output-077.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/c975c18e-output-077.png" alt="" /></a><br />
ちょーーーっと変わりました。翌年に大きなサービスローンチを控えていたため、この段階で大幅なデザインのアップデートまでは行いませんでした。文字ロゴが変わっただけでそこまで大きな変化はないですね。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/899ffc10-output-078.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/899ffc10-output-078.png" alt="" /></a><br />
この年の大きな技術トピックは Re-architecture です。<br />
方針としてはアプリのフル書き換えは選択せず、王道の少しずつ画面を書き換えていくアプローチをとりました。MicroViewController と読んでいたのですが、コンポーネントベースで同時並行での開発を可能にするアーキテクチャへのアップデートでした。<br />
複雑な画面の書き換えを目的とし、テストや仕様書を充実させながらプロジェクト進行させていきました。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/7466cdec-output-079.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/7466cdec-output-079.png" alt="" /></a><br />
このときのアーキテクチャについては、<a href="https://fortee.jp/iosdc-japan-2018/proposal/ef7c210d-d4dc-4a91-9601-e9d1edba441a">2018年にtarunonさんがiOSDCで発表を行っています</a>。ご興味のある方はぜひご参照ください。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/c73c6054-output-080.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/c73c6054-output-080.png" alt="" /></a><br />
Re-architecture のロールアウトプランについてもご紹介します。<br />
書き換えを行う際、どのようにロールアウトしていくかは判断の難しい問題だと思います。<br />
我々のアプローチは全く同じ画面を実装し、新旧でA/Bテストを行いながらKPIに劣後が出ないかを確認しながらロールアウトしていきました。<br />
全く同じ画面だったので、細かすぎる微妙な仕様の差を知っていないと自分の端末にどちらが表示されているのか本当に分かりませんでした。<br />
また、Feature Flag で新旧画面の比率を調整しながら徐々に公開していきクラッシュやエラーを監視しました。<br />
クラッシュ等以外のビジネス指標は、BIチームとも連携してトラッキング、KPIに異常が出たらすぐにFeature Flagで旧画面に切り戻すという運用を行いました。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/54cab882-output-081.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/54cab882-output-081.png" alt="" /></a><br />
Re-architecture は全体として良い結果をもたらしました。主要画面の書き換えを完了できたことはもちろん、一部の画面では旧画面よりもパフォーマンスが向上し、事業KPIに良い影響を与えたことも分かりました。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/f9a7df74-output-082.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/f9a7df74-output-082.png" alt="" /></a><br />
赤いドットがRe-architecture 後の画面、青いドットが旧画面のある指標です。なにが良かったかは公開できませんが、パフォーマンス向上によってビジネス上の指標に良い影響があったとご理解いただければよいかと思います。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/5769e2d3-output-083.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/5769e2d3-output-083.png" alt="" /></a><br />
取り組みとしては結果的に1年を掛けてターゲットとしていたすべての画面の書き換えが完了することができました。また、テストも Re-architecture 前に比べてかなり充実しました。<br />
特にロジックを含むようなコンポーネントは80%のカバレッジを持つようOKR(Objectives and Key Results)を設定して達成していきました。<br />
残念な点としては、仕様書については継続的にアップデートが行われず、数年後に行われるリライトプロジェクトでも課題となりました。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/7621e620-output-084.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/7621e620-output-084.png" alt="" /></a><br />
エンジニア観点で一番大きな効果はスケーラブルな開発体制を構築できたことではないでしょうか。エンジニアの人数も増えたのですが、並行して開発ができるようになったこともあり、コミット数が前年比3倍に増えています。<br />
Re-architectureで画面を書き換えていったことも大きいと思いますが、コードの追加・削除もかなり増えています。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/aa35dbbe-output-085.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/aa35dbbe-output-085.png" alt="" /></a><br />
GitHubのContributersのグラフを見ても、Re-architectureを前後でトレンドが大きく変わっていることが分かります。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/ca5652bd-output-086.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/ca5652bd-output-086.png" alt="" /></a><br />
また、この年から全員プロダクト開発を行うエンジニア、という体制に変化が訪れます。<br />
横断的な改善の重要度が上がり、 iOS Coreチームが組成されます。<br />
2023年現在、このCoreチームは iOS Architect チームとして継続しています。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/2515a616-output-087.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/2515a616-output-087.png" alt="" /></a><br />
この年はバックエンドでも大きな変化がありました。PHPのモノリスアプリケーションからマイクロサービスアーキテクチャへの移行を目指すMicroservice Migrationがスタートしています。バックエンドでgRPCが使われ始めたこともあり、クライアントでは Protocol Buffersが利用され始めました。<br />
2018年は技術的な取り組みとしてはRe-architectureという大きな動きがありました。会社全体としても技術刷新に取り組む環境へと大きく変わった年でもありましたが、2019年も大きな変化が起こることになります。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/57577d46-output-088.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/57577d46-output-088.png" alt="" /></a><br />
それがメルペイというスマホ決済事業のスタートです。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/cbbee096-output-089.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/cbbee096-output-089.png" alt="" /></a><br />
メルペイは2019年2月にスタートしました。タイムラインとしては、2018年にはかなり本格的に開発が行われていていました。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/d7eb6b1b-output-090.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/d7eb6b1b-output-090.png" alt="" /></a><br />
iOS周辺技術においては、SwiftUIが発表され、これもエンジニアリングとして後に重要な出来事となります。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/8fc00cab-output-091.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/8fc00cab-output-091.png" alt="" /></a><br />
こちらがメルペイリリース時のApp Storeのスクリーンショットです。<br />
これまでフリマアプリがメインでしたが、スマホ決済機能を強く打ち出しています。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/dfb9e41e-output-092.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/dfb9e41e-output-092.png" alt="" /></a><br />
2018年はRe-architectureが進行していましたが、メルペイはどのように開発を進めていたのでしょうか?<br />
メルカリ社内ではRe-architectureと同時進行で “Merpay Integration” というスマホ決済機能をメルカリのアプリに取り込むプロジェクトが2018年頃から進行していました。<br />
Re-architecture への影響を考慮し、 Merpay 機能を SDK としてモジュール化して提供する手法を選択し、Re-architectureもメルペイの開発も止まらないようプロジェクトが進行されました。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/57947e58-output-093.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/57947e58-output-093.png" alt="" /></a><br />
またアプリ上のUIの大きな変化として、メルペイスタートと同時に、メルカリアプリは下タブUIへと変更されています。<br />
2013年のリリース当初からハンバーガーメニューのUIが続いていましたが、メルペイリリースとともに現在も続く下タブへのアップデートが行われました。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/5e4a8984-output-094.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/5e4a8984-output-094.png" alt="" /></a><br />
Re-architeture後も開発はさらに加速してきました。<br />
なお Merpay は別リポジトリで管理されていたので、メルカリグループ全体としてはさらに大きい数字になっていたと思います。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/f6fb5639-output-095.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/f6fb5639-output-095.png" alt="" /></a><br />
主要画面以外のRe-architectureも完了、さらに 下タブ化をともなう Merpay Integration が終了し、Re-architectureは約1年で一区切りとなりました。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/b181246b-output-096.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/b181246b-output-096.png" alt="" /></a><br />
Re-architecture によって大部分が書き換えられました。Re-architecture前の2018年と2019年末を比較すると、プロジェクト内の Swift 比率は約20%から約85%にまで高まりました。<br />
Objective-Cは75%から15%に減少していますが、それでもObjective-Cはプロジェクト内に残っていました。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/17f8c549-output-097.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/17f8c549-output-097.png" alt="" /></a><br />
また、Re-architecture をベースとして Design System プロジェクトがスタートしました。<br />
Design System を進めた理由としては、スケーラブルな開発の実現と一貫したデザインと体験の両立と、そのためのPM/Designer/SWEの共通言語の導入、の必要性があがっていったためです。<br />
この年は、Re-architecture 済みの画面に対して Design System コンポーネントを全社で適用していきました。<br />
以上が2019年のできごとでした。</p>
<p>2018年以降の流れとして、スケーラブルな開発の重要性が上がった、ということが上げられます。採用も日本だけではなく海外にも目を向け、より広い市場にアプローチしていくことになりました。<br />
一方でスケールする開発を実現するためのアプリ開発基盤のアップデートが重視された期間であり、この流れはいまに至るまで続くことになります。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/c12b3bae-output-098.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/c12b3bae-output-098.png" alt="" /></a></p>
<p>2020年。Re-architectureが一旦の終わりを迎え、Design System などアプリ開発基盤の強化に力を入れ始めたタイミングで、GroundUpというプロジェクトが始動します。これは何かというと、アプリをゼロから書き直すプロジェクト です。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/5b7f495e-output-099.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/5b7f495e-output-099.png" alt="" /></a><br />
2020年はiOS14, iPhone12等、あとはApple Silicon が発表された年です。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/047b1a29-output-100.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/047b1a29-output-100.png" alt="" /></a><br />
2020年7月 App Storeのスクショがこちらです。2019年に引き続きメルペイを前面に据えています。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/0240c9a9-output-101.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/0240c9a9-output-101.png" alt="" /></a><br />
引き続きかなりたくさんのコミットが行われていました。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/1ab83b89-output-102.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/1ab83b89-output-102.png" alt="" /></a><br />
2020年の技術トピックとしては、先程も触れた通りアプリ開発周辺基盤の強化が挙げられます。<br />
2019年にスタートした Design System に続いて、 Test Automation強化、Weekly Release の検討開始、 Client Event Logging の刷新などがプロジェクト化され、投資が行われました。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/c72c2cb2-output-103.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/c72c2cb2-output-103.png" alt="" /></a><br />
これらを進める理由として、エンジニアを取り巻く環境が変わったことも挙げられます。<br />
サービスとしてはシングルアプリですが、メルカリとメルペイは別の会社になっています。スマホ決済事業が導入されたことにより、結果として、メルカリ・メルペイのグループ会社をまたぐ活動が増えました。<br />
両者で求められるガバナンスも異なるのですが、足並みをそろえ、機動力を維持しながら開発する体制が求められていました。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/99d08d8a-output-104.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/99d08d8a-output-104.png" alt="" /></a><br />
そのような動きもありますが、2020年はGroundUp App の始動が最も大きな出来事であったと言えるでしょう。<br />
リーアキテクチャのようなリファクタリングを行うアプローチではなく、アプリをゼロから書き直し、”式年遷宮”を行う意思決定でした。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/f6c6e94c-output-105.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/f6c6e94c-output-105.png" alt="" /></a><br />
2019年に発表されたSwiftUIで書き直すことが方針として設定されました。<br />
また、Re-architectureを選択しなかった理由として、今後数年、プラットフォームの提供する新機能に素早く対応していけるようにベースから書き直す判断をしました。<br />
このプロジェクトは、プロダクト開発を行うチームから独立して開発がスタートしました。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/92afb00e-output-106.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/92afb00e-output-106.png" alt="" /></a><br />
また GroundUp では Bazel をビルドツールとして採用し、 Bazel のビルドキャッシュなどの強みを生かした Micro Modular Architecture を採用しています。<br />
この <a href="https://fortee.jp/iosdc-japan-2020/proposal/28a29ff0-37e1-41fd-82d4-74f20c4df864">Micro Modular Architecture については、 いまも iOS Lead Architect を務める Aoyama さんが iOSDC Japan 2020 で発表を行っている</a>ので、興味があればそちらをご参照ください。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/d6f796e0-output-108.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/d6f796e0-output-108.png" alt="" /></a></p>
<p>さて、git log をこの年から2種類見ていくことにしましょう。</p>
<p>これまで見てきた初代iOSリポジトリはレガシーリポジトリと呼んでみましょう。引き続きすごい数のコミットが行われています。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/22a6c159-output-107.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/22a6c159-output-107.png" alt="" /></a></p>
<p>こちらは Ground Up リポジトリです。まだまだ産声を挙げたばかりのプロジェクトと言えそうですが、コミッターはそれなりにいたように見えます。</p>
<p>以上が2020年のできごとでした。</p>
<p>俯瞰してみると、Re-architecture が終わった後すぐに Rewrite プロジェクトが開始されており、とても決断が早かったように感じます。やはり、2019年に発表されたSwiftUIはメルカリのiOS開発においては大きな転換点だったと言えます。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/aacd2a44-output-109.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/aacd2a44-output-109.png" alt="" /></a><br />
さて、2021年は再チャレンジが行われた年と言えるかもしれません。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/2f1368c9-output-110.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/2f1368c9-output-110.png" alt="" /></a><br />
まず iOS周辺技術では、iOS15などが発表されました。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/d3ed47ca-output-111.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/d3ed47ca-output-111.png" alt="" /></a><br />
2021年7月のスクリーンショットはこちらです。フリマ機能が再度押し出されています。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/6f7eac21-output-112.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/6f7eac21-output-112.png" alt="" /></a><br />
レガシーリポジトリは少しコミット数が落ち着いてきます。前年3万近くあったコミットから1万6千にまで減少しています。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/dca2debf-output-113.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/dca2debf-output-113.png" alt="" /></a><br />
一方、GroundUpはコミット数こそあまり変化がありませんが、コミッター数が増えているように見えます。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/8468a67e-output-114.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/8468a67e-output-114.png" alt="" /></a><br />
この年からメルカリは<a href="https://engineering.mercari.com/blog/entry/20211210-b5d0a7dc9c/">アプリのリリース周期を2週に一回から毎週アップデートに頻度を上げる改善を行いました。</a><br />
Delivery の頻度を増やすということが目的だったのですが、これを実現するためにはいろいろなものを整備する必要がありました。<br />
約半年ほど掛けてプロセスやオペレーションのアップデート、QA期間短縮のための自動化などの準備を行い実現されました。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/9e55fd79-output-115.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/9e55fd79-output-115.png" alt="" /></a><br />
サービス的には事業者向けのメルカリShopsが立ち上がりました。<br />
メルカリShopsは、クライアントアプリだけでなくバックエンドもフルスクラッチで開発しました。この機能はネイティブではなくWebViewでメルカリアプリ内に提供されています。<br />
WebViewへのチャレンジは2013年にWebViewベースでの開発を諦めてからの再チャレンジとも言えるものでした。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/4d20c8f6-output-116.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/4d20c8f6-output-116.png" alt="" /></a><br />
メルカリUSでは、2017年のDouble以来、2度目の書き直しプロジェクトである <a href="https://medium.com/mercari-engineering/why-we-decided-to-rewrite-our-ios-android-apps-from-scratch-in-react-native-9f1737558299">Denali</a> がスタートします。<br />
以前の Double プロジェクトでは部分的に採用していた React Native をフルで使って書き直すプロジェクトです。プロジェクト名のDenaliは、北アメリカ大陸の最高峰の山の名前らしいです。<br />
以上が2021年の出来事です。<br />
振り返ってみると、USメルカリ、日本のメルカリ、メルカリShops という3つのプロジェクトがフルスクラッチで開発を行っていたことになります。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/3eb58ad4-output-117.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/3eb58ad4-output-117.png" alt="" /></a><br />
そして2022年はエンジニアリングとしても会社としてもGroundUpにフォーカスした年となりました。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/49ebcb46-output-118.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/49ebcb46-output-118.png" alt="" /></a><br />
この年はiOS16, iPhone14が発表されました。PassKey についてもこの年WWDCで発表が行われました。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/69b82e23-output-119.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/69b82e23-output-119.png" alt="" /></a><br />
2022年は<a href="https://mercan.mercari.com/articles/34173/">メルカリIndia</a>が設立されたり、メルカードの提供を開始したりと、組織、サービスとしてもさらなる広がりを持った年になりました。<br />
そしてメルカリアプリのリプレースの完了です。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/c4d2a921-output-120.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/c4d2a921-output-120.png" alt="" /></a><br />
こちらが GroundUp リリース前の 最後のv4系、4.106.0 のスクリーンショットです。GroundUp でリプレイスされた v5系を見てみましょう。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/98131558-output-121.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/98131558-output-121.png" alt="" /></a><br />
はい、何も変わってません。でも裏側は全部変わっているんですね。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/1a4a4572-output-122.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/1a4a4572-output-122.png" alt="" /></a><br />
Git log チェックしましょう。<br />
2022年途中でレガシーリポジトリにはコードフリーズが入ったため、コミット数が16,000から十分の1以下に減っています。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/d2d08cd2-output-123.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/d2d08cd2-output-123.png" alt="" /></a><br />
Ground Up は逆に約2000から倍以上に増えています。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/d82124e8-output-124.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/d82124e8-output-124.png" alt="" /></a><br />
GroundUpのリリースについてご紹介します。<br />
先程説明した通り、Legacy リポジトリにコードフリーズを実施しました。これまで Legacy で機能開発に取り組んでいたエンジニアも全員が GroundUp の開発に移りリリースを目指しました。会社としては GroundUp を前提に取り組んでいたサービスもあったため、全社を挙げての取り組みとなりました。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/0cf46a89-output-125.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/0cf46a89-output-125.png" alt="" /></a><br />
ロールアウトプランですが、Re-architecture のときのように画面ごとにロールアウトしていくという戦略は取れません。<br />
4月からTestflight で外部テスターを募り、βテストを実施し、ここでバグリポートを集め修正を行っていきました。<br />
その後、 7月にApp Store で実際にGroundUpアプリをリリースするフェーズに移ります。ここでは Weekly Release は維持しつつ、v4系のレガシーアプリのストアリリースを停止、v5系のGroundUpを実際にストアにリリースしていきます。<br />
この際、段階リリースを行い 1%, 2% など小さいパーセンテージでリリースを停止し、バージョン浸透率をコントロールしながら徐々にロールアウトを実行していきました。もちろん、この段階ではKPIの監視も行いました。</p>
<p>これを1ヶ月ほど続け、9.20に v5系GroundUpアプリを100%公開し、2020年にスタートしたリライトプロジェクトであるGroundUpが2年をかけて完了しました。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/eed085d1-output-126.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/eed085d1-output-126.png" alt="" /></a><br />
このリライトプロジェクトにより、Objective-Cはメルカリのアプリから完全になくなりました</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/ce19505c-output-127.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/ce19505c-output-127.png" alt="" /></a><br />
さらにこれまで別リポジトリで管理されていた Merpay SDK などを、 GroundUpリポジトリに統合する モノレポ化が実施されました。<br />
また、USアプリの React Native への書き換えも4月に完了しています。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/9b8cbe44-output-128.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/9b8cbe44-output-128.png" alt="" /></a><br />
GroundUpプロジェクトについては、<a href="https://mercan.mercari.com/articles/35887/">CTO</a>や <a href="https://mercan.mercari.com/articles/36183/">Lead Architect</a> のインタビュー記事が出ていますので、興味があればご参照ください。<br />
以上が2022年のできごとでした。<br />
日本とUSどちらもリライトプロジェクトが完了したという年で、モバイルアプリに関わるチームにとってはハードな1年となりました。<br />
しかし、書き換えて終わりというわけではありません。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/604d56ab-output-129.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/604d56ab-output-129.png" alt="" /></a><br />
そして、2023年、メルカリは10周年を迎えました。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/ac10c807-output-130.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/ac10c807-output-130.png" alt="" /></a><br />
今年はiOS17が発表されましたね。<br />
そして Apple Vision Pro, visionOSも発表されました。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/446172e1-output-131.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/446172e1-output-131.png" alt="" /></a><br />
メルカリはすでに次の10年に向けて動き出しています。<br />
ビットコインが買えるようになりました。<br />
また、パスキーの対応も開始しています。ChatGPTプラグインの提供も開始などなど、 Go Bold にチャレンジを続けていきます。<br />
そして、7月にアプリローンチ10年を迎えました。GroundUp が終わったあと、レガシーリポジトリはどうなったのか見てみましょう。こちらです。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/5fc93cd8-output-132.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/5fc93cd8-output-132.png" alt="" /></a><br />
はい、全て0です。<br />
GroundUp でのコードフリーズ以降、レガシーアプリでの開発がストップしたため、2023年には誰もコミットを行っていません。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/9c612622-output-133.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/9c612622-output-133.png" alt="" /></a><br />
レガシーアプリであるv4系アプリはサポートが続いていたため、メンテナンスのためにリポジトリは残されていました。<br />
しかし、2023年に入り、v4系アプリのサポートを切る強制アップデートが実施されました。これにより、 レガシーiOSリポジトリは役目を終え、アーカイブされることになりました。<br />
2013年3月の Initial Commit から10年を経てその歴史に幕を降ろしました。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/35ad1493-output-134.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/35ad1493-output-134.png" alt="" /></a><br />
10年間の歴史を振り返ってみました。<br />
通算コミッターはボットや重複を含みますが213、通算コミット数152,456、通算Pull Request 35,969 という数字でした。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/a5d5757c-output-135.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/a5d5757c-output-135.png" alt="" /></a><br />
GroundUpはこの様になっています。7月時点での数字です。<sup>※</sup><br />
元気に開発が続けられていますね。</p>
<div class="annotation">※GroundUpリポジトリ は Squash and merge で運用しているため、Squash and merge を使っていなかったレガシーリポジトリよりもコミット数が少なくなっています。</div>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/1583c064-output-136.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/1583c064-output-136.png" alt="" /></a><br />
7月時点のApp Store上のスクリーンショットはこの様になっています。<br />
2013年と比べてみましょう。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/9c867ac3-output-137.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/9c867ac3-output-137.png" alt="" /></a><br />
デザインを見てもかなり歴史、月日の流れを感じますね。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/5a91211f-output-138.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/5a91211f-output-138.png" alt="" /></a><br />
2023年、いまiOS開発として力を入れていることをご紹介します。<br />
Architecture v2という、すでに新しいアーキテクチャに取り組み始めています。GroundUpという取り組みが終わってすぐのように見えますが、いまの設計自体は3年前の2020年に考えられたものなんですね。<br />
これまでの歴史を振り返ってみると3年という月日は決して早すぎるわけではないとも思っています。</p>
<p>それから2022年にWWDCで共有されたPassKeysも重要な取り組みの一つ。<a href="https://about.mercari.com/press/news/articles/20230414_passkeys/">すでにメルカリのプロダクションで導入が開始</a>されていますが、シームレスな認証を提供していきたいと考えていて、これからサポートを増やしていきたいと考えています。</p>
<p><a href="https://engineering.mercari.com/en/blog/entry/20231129-performance-monitoring-in-mercari-mobile-apps/">アプリの Observability の強化</a>にも取り組んでいます。これは DataDog Real User Monitoring (DataDog RUM) を使い、エラーやクラッシュはもちろん、API Latency 含めてe2eの読み込み速度の計測などを行う取り組みです。</p>
<p>またリリースサイクルについても、週一回をキープしているものの、人の手で解決していることが多い状況で、<a href="https://engineering.mercari.com/en/blog/entry/20231212-the-art-of-streamlining-mobile-app-releases/">改善</a>に取り組んでいます。</p>
<p>まだ2023年は終わっていませんが、7月までの動きを振り返りました。</p>
<p>かなり長かったですが、以上が10年間の振り返りです。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/e65f030a-output-139.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/e65f030a-output-139.png" alt="" /></a><br />
最後にまとめていきたいと思います。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/d3af2f33-output-140.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/d3af2f33-output-140.png" alt="" /></a><br />
10年振り返ってみて感じたことは、変化は徐々に起こることも、突然表れることもある、ということです。技術の変化はもちろん、プロダクトやビジネス、そして組織の変化もあります。<br />
エンジニアはどのようにこれらの変化に適応していけば良いのでしょうか?ということについて考えてみました。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/773378b3-output-141.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/773378b3-output-141.png" alt="" /></a><br />
まずは技術変化への適応ですが、幸い、iOSアプリ開発では、一定のリズムがあります。だいたい2-3年を掛けて新しいスタンダードへの適応が行われていきます。<br />
メルカリの場合、2014年にSwift が発表されてから2年後の2016年にフルSwift アプリが登場しています。<br />
また、2019年にSwiftUI 発表されてから3年後の2022年にアプリが SwiftUIへ書き換えられています。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/ea922f9d-output-142.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/ea922f9d-output-142.png" alt="" /></a><br />
プロダクト、ビジネス、組織による環境の変化への適応はどうでしょうか。<br />
ここに関しては会社や組織によって課題感が異なると思いますが、メルカリではご覧のような取り組みが行われてきました。<br />
Re-design, re-architecture, re-write, さらに横断的な取り組みを行うチームの設立や、周辺基盤の強化が環境の変化に適応するための取り組みでした。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/88ed70ed-output-143.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/88ed70ed-output-143.png" alt="" /></a><br />
今回あらためて振り返ってみて、メルカリは変化に対してかなりプロアクティブに対応してきたことを再認識しました。<br />
ただ、振り返ってみると当たり前のように感じるターニングポイントも、当時はそこまで確信を持って意思決定が行われたわけではなかったと感じています。</p>
<p>Q. Re-architecture が終わって間もなくゼロから書き直す判断ができるか?すべきか?<br />
Q. いま Cross Platform や WebView を選択すべきか?</p>
<p>もしかしたらプロダクトにいま携わっている方は、いままさにこのような問いにさらされているかもしれません。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/d3f4e75c-output-144.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/d3f4e75c-output-144.png" alt="" /></a><br />
完璧な答えはないものの、それでもエンジニアとして最善と思える答えを僕らは一つに絞って出さなければならない。きっと迷うこともあると思います。<br />
そんなとき、「メルカリはあんなことやっていたな」「この課題にはこうやってアプローチしたのか」「ちょっと参考にしてみるか」と言う感じでですね、この10年の振り返りが少しでもみなさんの力になれば嬉しいと思っています。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/7b1f69bd-output-145.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/7b1f69bd-output-145.png" alt="" /></a><br />
はい、ということで以上になります。<br />
今回スライドの中で紹介できなかった取り組みもたくさんあります。今回紹介した取り組みも全てが大成功だったわけではありません。<br />
たくさんの失敗もありましたが、そういった失敗を糧にこれまでメルカリは取り組んできています。<br />
これからもメルカリは Go Bold, All for One, Be a Pro を掲げながら チャレンジを続けていきます!<br />
もしこのトークを聞いてメルカリに興味持っていただけたら、ぜひお気軽にお声がけください。</p>
<p><a href="https://storage.googleapis.com/prd-engineering-asset/2023/12/f4a8667b-output-146.png"><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/f4a8667b-output-146.png" alt="" /></a><br />
以上となります。それではご清聴ありがとうございました!</p>
<h1>おわりに</h1>
<p>以上、「メルカリ10年間のiOS開発の歩み」でした。</p>
<p>プレゼンテーションは40分と比較的長いトーク時間のように感じますが、10年の歴史をまとめるには40分は非常に短く、初期段階のトーク時間は60分を超えてしまっていました。余計な内容を削り規定の時間内で終わらせるべくトークスクリプトを準備し、本番ではきっちり40分でトークを終わらせることができました。</p>
<p>文章以外でのフォーマットで参照したい方は、当日発表を行った際のスライドと動画を<a href="https://fortee.jp/iosdc-japan-2023/proposal/8a98b1b8-ab0a-49e9-8651-f890587f1428">こちら</a>から参照することができます。</p>
<p>2023年はありがとうございました。2024年もよろしくお願いいたします!</p>
- AWS Transfer Family で SFTPサーバーを作ってみたら便利だった話https://engineering.mercari.com/blog/entry/20231221-f221b835d1/https://engineering.mercari.com/blog/entry/20231221-f221b835d1/<p>メルペイSREの @myoshida です。この記事は、Merpay Advent Calendar 2023 の21日目の記事です。 メルカリグループではGoogle Cloud Platform(GCP) を広く利用 […]</p>
Thu, 21 Dec 2023 10:00:57 GMT<p>メルペイSREの @myoshida です。この記事は、<a href="https://engineering.mercari.com/blog/entry/20231124-merpay-advent-calendar-2023/" title="Merpay Advent Calendar 2023">Merpay Advent Calendar 2023</a> の21日目の記事です。</p>
<p>メルカリグループではGoogle Cloud Platform(GCP) を広く利用しており、一般的にはGCPを利用したシステム構築が推奨されています。しかし、他のプラットフォームを利用した方が要件を実現しやすかったり、よりスマートに構築できる場合はAmazon Web Services(AWS)なども利用することあります。</p>
<p>今回は<a href="https://aws.amazon.com/jp/aws-transfer-family/" title="AWS Transfer Family">AWS Transfer Family</a>を利用してSFTPでファイルを送受信する環境を構築した件について簡単にお伝えできればと思います。</p>
<h2>SFTPでのファイル送受信について</h2>
<p>SFTP(SSH File Transfer Protocol)は、その名の通り、SSHを利用してファイル転送を行います。SSHを利用して暗号化通信が行えるため、FTPと比べて安全に利用できます。<br />
ログインには、SSHで使用する鍵をそのまま認証に利用できます。鍵認証でログインできるため、パスワードは不要です。</p>
<p>一方でFTP(File Transfer Protocol)は、IDとパスワードでログインします。また、暗号化がサポートされていないため、セキュリティ面で問題があり、利用は推奨されません。</p>
<p>SFTPは昔から存在する枯れた方式だと思いますが、業務の現場では今も根強く採用されています。日次のバッチで処理して作られたCSVを、連携先の外部企業に渡すといった場面で利用されたりします。</p>
<h2>AWS Transfer Family での SFTP環境構築</h2>
<p>AWS Transfer Family を利用したシステム構成は以下のようになります。</p>
<h4>構成図</h4>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/cba95ac4-awsアイコン-1024x697.png" alt="" /></p>
<p>SFTPサーバーに該当する Transfer Server を用意し、利用するサブネットの数だけEIPを払い出し、Transfer Serverに紐づけます。それによりTransfer Serverに専用のエンドポイントが割り当てられ、ユーザーはそれを指定してSFTPクライアントで接続できます。</p>
<h4>エンドポイントが割り当てられた様子</h4>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/39d34fe4-_2023-12-18_21_21_08_02-1024x184.png" alt="" /></p>
<p>SFTPユーザーはTranfer Serverに紐づいており、ユーザーごとに公開鍵を複数持つことができます。IAMユーザーを作成する必要はありません。</p>
<p>ストレージはS3バケットを利用します。1つのS3バケットにユーザーごとのホームディレクトリを定義して共用することも可能ですし、ユーザーごとにS3バケットを用意して、ログインするユーザーごとに専用のS3バケットに接続させることも可能です。今回は後者を採用しました。</p>
<p>構築にはTerraformを利用します。locals を利用してユーザー名を変数とすることで、S3バケット・SFTPユーザー・SFTPユーザーが利用するIAMロールなどをまとめて作成することが可能です。</p>
<h4>localsの定義例</h4>
<pre><code class="language-json"> sftp_name = "merpay-foo-bar"
sftp_users = {
test-user-1 = {
ssh_keys = [
"ssh-rsa dummy",
]
}
test-user-2 = {
ssh_keys = [
"ssh-rsa dummy",
]
}
}
sftp_user_keys = flatten([
for user, attrs in local.sftp_users : [
for ssh_key in attrs["ssh_keys"] : {
user = user
ssh_key = ssh_key
}
]
])
}</code></pre>
<p>ログインに利用する公開鍵は、上記terraform内の ssh-keys にリストで列挙することでterraform経由でSFTPユーザーに保持させることも可能ですが、今回はユーザー作成後にAWSにログインして、手動で登録することにしました。</p>
<h4>S3バケットの定義例</h4>
<pre><code class="language-json">resource "aws_s3_bucket" "sftp_bucket" {
for_each = local.sftp_users
bucket = "${local.sftp_name}-${each.key}"
versioning {
enabled = true
}
logging {
target_bucket = aws_s3_bucket.sftp-bucket-log[each.key].id
target_prefix = "log/"
}
tags = {
}
}
</code></pre>
<h4>IAMポリシーの定義例</h4>
<pre><code class="language-json">resource "aws_iam_policy" "s3_read_write" {
for_each = local.sftp_users
name = "s3_rw_merpay-sftp-${each.key}"
path = "/system/"
description = "for enabling file tansfer to buckets"
policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:ListBucket",
"s3:GetBucketLocation"
],
"Resource": "arn:aws:s3:::${local.sftp_name}-${each.key}"
},
{
"Effect": "Allow",
"Action": [
"s3:PutObject",
"s3:GetObject",
"s3:DeleteObject",
"s3:GetObjectAcl",
"s3:PutObjectAcl",
"s3:GetObjectVersion",
"s3:DeleteObjectVersion"
],
"Resource": "arn:aws:s3:::${local.sftp_name}-${each.key}/*"
}
]
}
EOF
}</code></pre>
<h4>IAMロールの定義例</h4>
<pre><code class="language-json">resource "aws_iam_role" "sftp_user" {
for_each = local.sftp_users
name = "transfer-server-user-role-${each.key}"
assume_role_policy = <<-EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "transfer.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
EOF
}
resource "aws_iam_role" "transfer_server_to_cloudwatch" {
name = "transfer-server-to-cloudwatch-role"
assume_role_policy = <<-EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "transfer.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
EOF
}
</code></pre>
<h4>IAMロールのポリシーアタッチメントの定義例</h4>
<pre><code class="language-json">resource "aws_iam_role_policy_attachment" "s3_bucket_read_write" {
for_each = local.sftp_users
role = aws_iam_role.sftp_user[each.key].name
policy_arn = aws_iam_policy.s3_read_write[each.key].arn
}
</code></pre>
<h4>Transfer Serverの定義例</h4>
<p>"aws_transfer_server" は endpoint_type を “VPC” にし、endpoint_details ブロック内でEIPを割り当てることで、マネージドなドメインが生成されます。</p>
<pre><code class="language-json">resource "aws_transfer_server" "sftp" {
identity_provider_type = "SERVICE_MANAGED"
endpoint_type = "VPC"
logging_role = aws_iam_role.transfer_server_to_cloudwatch.arn
endpoint_details {
address_allocation_ids = [for eip in aws_eip.sftp : eip.id]
subnet_ids = aws_subnet.sftp_subnet[*].id
vpc_id = aws_vpc.sftp.id
}
tags = {
Name = local.sftp_name
}
lifecycle {
ignore_changes = all
}
}</code></pre>
<h4>SFTP Userの定義例</h4>
<p>SFTPユーザーのホームディレクトリは、"aws_transfer_user" 内の home_directory で、S3バケットのルートを指定しました。localsを参照してユーザーごとに作られるS3バケットをそのまま指定しているので、ユーザーごとに別のS3バケットを利用できるようになります。</p>
<pre><code class="language-json">resource "aws_transfer_user" "sftp_user" {
for_each = local.sftp_users
server_id = aws_transfer_server.sftp.id
user_name = each.key
role = aws_iam_role.sftp_user[each.key].arn
home_directory = "/${aws_s3_bucket.sftp_bucket[each.key].id}/"
}
</code></pre>
<h2>環境を構築してみて感じた利点</h2>
<p>SFTPサーバの環境を作るにあたって、AWS Transfer FamilyとTerraformで利用することで、以下のようなメリットがあると感じました。</p>
<h3>手動管理の量が少ない</h3>
<p>マネージドな環境ですので、一度構築してしまえば、かなりメンテナンスフリーな感じで利用することができます。EC2などのサーバインスタンスを用意することもないため、管理がラクです。<br />
アカウント追加・削除の作業もTerraformを更新することで行なうので、GitHubのPull Requestを通じてチーム内で確認を取りながら進められて安全です。<br />
S3にはライフサイクルを指定しているため、古いファイルを削除するといった作業も発生しません。</p>
<h3>横展開がしやすい</h3>
<p>これは単純にTerraformの利点なのですが、.tfファイルにほぼすべての構築内容が定義されているため、類似の案件が発生した場合に流用しやすいです。</p>
<h3>他のシステムとのつなぎ込みがしやすい</h3>
<p>ファイルはS3に保存されるため、AWSのAPIを利用してファイルを取得したりすることで、業務の後続処理もスムーズに行わせることができます。</p>
<h2>おわりに</h2>
<p>今回はメルカリグループでは利用例が少ないAWSを利用したSFTP環境の構築について説明しました。既存のSFTP環境のリプレイスなどのお役に立てば幸いです。<br />
Google Cloud Platformでも同様のサービスが登場してほしいなと思います。</p>
<p>明日の記事は @orfeonさんです。引き続きMerpay Advent Calendar 2023をお楽しみください。</p>
- GCSのリソース最適化の取り組みで得た知見https://engineering.mercari.com/blog/entry/20231220-gcs-resource-optimization/https://engineering.mercari.com/blog/entry/20231220-gcs-resource-optimization/<p>こんにちは。メルカリ Accounting Productsチーム Software Engineerのayanekoです。 この記事は、Mercari Advent Calendar 2023 の20日目の記事です。 […]</p>
Wed, 20 Dec 2023 11:00:33 GMT<p>こんにちは。メルカリ Accounting Productsチーム Software Engineerのayanekoです。<br />
この記事は、<a href="https://engineering.mercari.com/blog/entry/20231124-mercari-advent-calendar-2023/">Mercari Advent Calendar 2023</a> の20日目の記事です。</p>
<p>私たちAccounting Productsチームは会計システムの開発、運用をしています。会計データを扱うという特性上、以下にあげる理由から大量のデータを保持しており、多額の費用がかかっていました。</p>
<ul>
<li>会計データは法律上一定期間の保持が必要であること</li>
<li>一時ファイルやログファイルなども含めて保守的にすべてのデータを保存していたこと</li>
</ul>
<p>そこで、<a href="https://engineering.mercari.com/blog/entry/20221205-02701ed73d/">FinOps</a>観点で<a href="https://cloud.google.com/learn/what-is-cloud-storage?hl=ja">Cloud Storage</a>(以下GCS)や<a href="https://cloud.google.com/spanner?hl=ja">Cloud Spanner</a>(以下Spanner)のリソース最適化のPJを始めました。リソース最適化とは、必要なリソースはしっかりと保存し、更新され古くなったデータは必要な期間のみ保存してデータの総量から余剰分を取り除けるようにする取り組みのことです。</p>
<p>この投稿では、その一環として行ったGCSのリソース最適化の取り組みで得た知見についてご紹介したいと思います。</p>
<h2>利用環境</h2>
<p>本題に入る前に、私たちが普段利用している環境について少し触れておきたいと思います。</p>
<ul>
<li>Dev環境
<ul>
<li>開発環境</li>
</ul>
</li>
<li>QA環境
<ul>
<li>テスト環境(ステージング環境の扱いに近い)</li>
</ul>
</li>
<li>Prod環境
<ul>
<li>本番環境</li>
</ul>
</li>
</ul>
<p>システムに変更を加える際は、Dev環境、QA環境の順に検証し、最終的にProd環境へ適用します。<br />
また、<a href="https://cloud.google.com/docs/overview?hl=ja">GCP</a>のリソースはほぼすべて<a href="https://developer.hashicorp.com/terraform/intro">Terraform</a>で管理しています。<br />
以上のことを踏まえて本題に入りたいと思います。</p>
<h2>オブジェクトのバージョニングを有効にするときは適切なライフサイクルを設定する</h2>
<p>今回リソース最適化をしたい<a href="https://cloud.google.com/storage/docs/buckets?hl=ja">バケット</a>は最初から<a href="https://cloud.google.com/storage/docs/object-versioning?hl=ja">オブジェクトのバージョニング</a>が有効の状態でしたが、<a href="https://cloud.google.com/storage/docs/lifecycle?hl=ja">ライフサイクル</a>の設定がされておらず大量のオブジェクトが保存され、多額の費用がかかっていました。</p>
<p>バケットのバージョニングを有効にするとライブオブジェクトバージョンを置換または削除するたびに非現行オブジェクトバージョンが保持されるようになるため、非現行オブジェクトバージョンをどの程度保持するかをライフサイクルにより管理することが重要になってきます。</p>
<p>そこで、<strong>特定の日数が経過後に非現行バージョンのオブジェクトを削除するライフサイクルの設定</strong>をすることで、本当に保持しなければならないオブジェクトのみが残るようにしました。</p>
<h2>オブジェクトを削除するときは量やタイミングに注意する</h2>
<p>ライフサイクルの設定を適用し大きなコスト削減につながると喜んだのもつかの間、この対応の直後に大きな問題が発生しました。これにはオブジェクト削除の量やタイミングが関係していることがわかりました。</p>
<p>ライフサイクルにより<strong>一度にPB単位のオブジェクトが削除</strong>されることとなったのですが、それが引き金となって同バケットのDeleteObjectやRewriteObject.FromがUnavailableを返すようになるという問題が発生しました。<br />
<img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/1fb210ce-gcs_bucket_metrics_unavailable.png" alt="" /></p>
<p>社内の有識者とともにいろいろ調査を尽くし結果的に1週間後に問題は解消しましたが、この経験からあまりにも大量のオブジェクトを一度に削除することは今後は避けるべきという教訓を得ました。</p>
<p>さらに、一時的に<strong>コストが跳ね上がっていた</strong>ことに気が付きました。</p>
<p>削除されたオブジェクトが保存されていたバケットの<a href="https://cloud.google.com/storage/docs/storage-classes?hl=ja">ストレージクラス</a>がArchiveストレージであったために、多くのオブジェクトに対して<a href="https://cloud.google.com/storage/pricing?hl=ja#early-delete">早期削除料金</a>がかかっていることがわかりました。</p>
<p>各ストレージクラスには最小保存期間が設定されており、Archiveストレージの場合は365日です。<br />
最小保存期間が経過していないオブジェクトに対して削除、置換、移動をした場合は<strong>早期削除料金がかかってしまう</strong>のです。</p>
<div style="text-align: center">
<img src="https://storage.googleapis.com/prd-engineering-asset/2023/11/72c928fb-storage_class.png" style="width:355" alt="storage class">
</div>
<p>(上記の内容は2023年11月時点のもので、将来ストレージクラスの種類や最小保存期間が変更になる可能性があります)</p>
<p>一時的にコストがかかってしまうことは仕方ないとしても事前に予測することは可能であったため、そこまで考えが至らなかったことは反省すべき点でした。</p>
<h2>Rewrite時のストレージクラスの違いによる影響を考慮する</h2>
<p>バケットから別のバケットへ<a href="https://cloud.google.com/storage/docs/json_api/v1/objects/rewrite">Rewrite</a>が行われる場合には、両者のストレージクラスの違いに注意したほうが良いということがわかりました。</p>
<p>会計システムの一部で<a href="https://airflow.apache.org/">Airflow</a>を使ってデータをExportしている処理があり、その中の一時ファイル用のバケットとデータの保存先のバケットを別々にする対応をしました。<br />
QA環境での実行では問題がなかったのですが、Prod環境での実行で一時ファイル用のバケットからデータの保存先のバケットへ<strong>Rewriteが行われている箇所で処理が失敗</strong>していました。</p>
<p>このとき一時ファイル用のバケットがStandardストレージ、データの保存先のバケットがArchiveストレージであり、<strong>両者のストレージクラスが異なっている状態</strong>でした。<br />
また、問題なく動いたQA環境とProd環境の違いとして、扱うデータ量がProd環境の方がかなり多いという点があげられます。</p>
<p>そこで一時ファイル用のバケットとデータの保存先のバケットのストレージクラスを、<strong>両者とも同じStandardストレージに</strong>しました。<br />
そうするすることでProd環境でも問題なく処理が完了することがわかりました。</p>
<p><a href="https://cloud.google.com/storage/docs/json_api">Cloud Storage JSON API</a>の<a href="https://cloud.google.com/storage/docs/json_api/v1/objects/rewrite">Rewrite methodのリファレンス</a>に記載されている注意点として、Rewrite元とRewrite先のバケットの<a href="https://cloud.google.com/storage/docs/locations?hl=ja">ロケーション</a>とストレージクラスが同じ場合は1回のリクエストでRewriteが完了するとの記載があります。</p>
<p>このことから、<strong>ロケーションやストレージクラスの違いがRewriteの処理に影響する</strong>ということが推測できます。</p>
<h2>バケットは種類ごとに分けて管理する</h2>
<p>今回のリソース最適化の対象のバケットには、<strong>いくつもの異なる保持ポリシーのオブジェクトが一緒くたに保存されていた</strong>ことも最適化までの道のりを困難にした要因の一つでした。<br />
たとえば「オブジェクトを削除するときは量やタイミングに注意する」で発生した問題のさなかにも、削除対象外のオブジェクトにもかかわらず同じバケットにあるというだけで影響を受けてしまうということがありました。</p>
<p>本来<strong>オブジェクトの種類によって選択すべきストレージクラスや設定すべきライフサイクルは異なる</strong>ため、保存期間やアクセスの頻度などを考慮しバケットを分けたほうが扱いやすいです。<br />
たとえば保存期間が2年であり頻繁にアクセスすることがないオブジェクトの場合は、1日経過後にストレージクラスをArchiveストレージにするライフサイクルと、2年経過したオブジェクトを削除するライフサイクルをバケットに設定します。また保存期間が1日のオブジェクトの場合は1日経過したオブジェクトを削除するライフサイクルをバケットに設定します。そのため両者のバケットは別の方が扱いやすいです。</p>
<p>デフォルトストレージクラスはStandardにし、他のストレージクラスへの変更は基本的にはライフサイクルで行う構成は、Merpay社員かつGoogle Developers Expertでもある@sinmetalさんからのアドバイスと、今回の取り組みを通しての私自身の見解としても、この方法が理に適っていると実感しています。</p>
<p>オブジェクトをバケットにアップロードすると、明示的に設定しない限りそのオブジェクトにはバケットのデフォルトのストレージ クラスが割り当てられます。<br />
オブジェクトのアップロード後にオブジェクトのストレージクラスを変更したい場合は、ライフサイクルによる変更や、<a href="https://cloud.google.com/storage/docs/changing-storage-classes?hl=ja">オブジェクトの書き換えによる変更</a>などの方法があります。</p>
<p>従ってデフォルトストレージクラスがArchiveストレージの場合オブジェクトがアップロードされると即座にArchiveストレージになるため、たとえば以下のような難点があります。</p>
<ul>
<li>システム修正後の動作確認でシステムからExportされたオブジェクトの中身を見たい場合にオペレーション料金が高い</li>
<li>誤って不要なオブジェクトをバケットに保存してしまい削除をしたい場合に早期削除料金がかる</li>
</ul>
<p>このようなコスト面での難点を回避するため、Standardストレージ以外のストレージクラスの設定は基本的にライフサイクルで行っています。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/f3054b42-gcs_operation_cost.png" alt="" /><br />
(上記の内容は2023年11月時点のもので、将来ストレージクラスの種類やオペレーション料金が変更になる可能性があります)</p>
<p>バケットを目的ごとに分けた後は、バケットごとに<a href="https://cloud.google.com/billing/docs/concepts?hl=ja#labels">ラベル</a>を設定すると請求を確認する際にも<strong>バケットごとに把握することが可能</strong>になります。<br />
ラベルはKeyValue形式で、メルカリでは<code>bucket={$bucket-name}</code>の形式でラベルを設定しています。<br />
ラベルを設定することで、たとえば早期削除料金が発生しているバケットを容易に特定できるようになります。</p>
<h2>ポリシーに基づき運用をする</h2>
<p>目的に合わせてバケットの作成やライフサイクルの設定をするにあたり、どのデータをどのくらいの期間保持する必要があるのかという基準を定めたドキュメントである<strong>データの保持ポリシー</strong>を作成しました。<br />
私たちは会計データを扱うため、そのデータが会計帳簿保存の対象となるデータかどうかの判断が必要になってきます。<br />
その判断をするにあたり、内部監査、経理、外部監査法人と協議しながらポリシーを作成しました。<br />
たとえばSpannerの特定の日のバックアップは何年保存する必要がある、それ以外は何年保存する必要がある、というように、データの種類ごとに保存すべき期間を定めていきます。<br />
このような基準に沿った運用ができるようバケットの作成やライフサイクルの設定をしていきます。</p>
<p>このポリシーを作成する際にバケット内にあるオブジェクトを一覧化するために活用した機能として、<a href="https://cloud.google.com/storage/docs/insights/inventory-reports?hl=ja">Storage Insights のインベントリ レポート</a>というものがあります。<br />
Storage Insights のインベントリ レポートにはオブジェクトのストレージクラスなどのオブジェクトに関するメタデータ情報が含まれています。<br />
今回はこのインベントリレポートを<a href="https://cloud.google.com/bigquery/docs/introduction?hl=ja">BigQuery</a>に取り込みました。</p>
<p>ライフサイクルの設定だけでカバーできない不要なオブジェクトの削除の際には、削除対象のオブジェクトをクエリにて抽出し、その情報を元にスクリプトでオブジェクトを削除しました。</p>
<h2>おわりに</h2>
<p>リソース最適化前から最適化後を比較すると、おおよそ<strong>54%ものコストを削減</strong>することができました。</p>
<p>この取り組みを始めた時点ではGCSに関しての知識が不足していたこともあり多くの問題に直面しましたが、問題を1つ1つ解決していく中でGCSやその周辺に関する知識を深めることができ、得るものが大きかったと感じています。<br />
またリソースを目的ごとに最適な状態で管理することの大切さを実感し、そのコストのインパクトの大きさをひしひしと感じられた取り組みでもありました。</p>
<p>今後は今回のリソース最適化の取り組みの対象外だった部分も含めてコストを削減できる余地がないかどうか、継続的に見直しを行っていきたいと思います。</p>
<p></p>
<hr />
<p>Accounting Productsチームでは、メルカリのミッション・バリューに共感できるSoftware Engineerを募集しています。一緒に働ける仲間をお待ちしております!<br />
<a href="https://apply.workable.com/mercari/j/B12170DC99/">採用情報</a></p>
<p>明日の記事はpakioさんです。引き続きお楽しみください。</p>
- 決済基盤の Observability を向上するための Datadog Dashboard の進化https://engineering.mercari.com/blog/entry/20231220-datadog-dashboard-for-observability/https://engineering.mercari.com/blog/entry/20231220-datadog-dashboard-for-observability/<p>この記事は Merpay Advent Calendar 2023 の 20 日目の記事です。 こんにちは。メルペイの Payment Core チームでバックエンドエンジニアをしている komatsu です。 普段はメ […]</p>
Wed, 20 Dec 2023 10:00:36 GMT<p>この記事は <a href="https://engineering.mercari.com/blog/entry/20231124-merpay-advent-calendar-2023/">Merpay Advent Calendar 2023</a> の 20 日目の記事です。</p>
<p>こんにちは。メルペイの Payment Core チームでバックエンドエンジニアをしている komatsu です。<br />
普段はメルカリ・メルペイが提供するさまざまな決済機能を支えるための決済基盤の開発・運用をしています。<br />
この記事では、我々が開発している決済基盤マイクロサービスである Payment Service を適切に監視するために、Datadog の Dashboard を大きく刷新した背景や方法について紹介します。</p>
<h2>Observability と Datadog Dashboards</h2>
<p>本題に入る前に、Observability と Datadog Dashboards について簡単に説明します。<br />
Observability はシステムの内部状態を適切に監視し、外部から可視化することでシステムを理解する能力およびその考え方を指します。<br />
適切に可視化して監視することで、既知の問題のみならず、未知の問題に対しても、より迅速に検知・解決することが可能になります。<br />
Observability を実現するには、次の 3 つの Telemetry の要素が重要だと考えられています。</p>
<ul>
<li>Metrics – CPU 使用率やメモリ消費、ネットワークトラフィックなど、システムリソースの使用状況などを示す定量的なデータ</li>
<li>Trace – システム内を遷移する各リクエストのトランザクションの経路と処理時間を追跡し、E2E でパフォーマンスを可視化するデータ</li>
<li>Logging – 操作の履歴やエラーメッセージなど、アプリケーションが生成する時系列のイベントデータ</li>
</ul>
<p>Datadog においても、Metrics は <a href="https://docs.datadoghq.com/metrics/">Datadog Metrics</a>、Trace は <a href="https://docs.datadoghq.com/tracing/">Datadog APM</a>、Logging は <a href="https://docs.datadoghq.com/logs/">Datadog Log Management</a> というサービス名でそれぞれ提供されています。<br />
これらのサービスはそれぞれの Telemetry を可視化するためのものですが、3 つすべてを一箇所に集約して可視化するために利用されるのが <a href="https://docs.datadoghq.com/dashboards/">Datadog Dashboards</a> です。<br />
任意の Telemetry を任意のメトリクスや自由度の高いクエリを組み合わせて Widget を作成し、それを自由に並べ替えることで、あらゆる Telemetry データを 1 つのページに可視化することができます。</p>
<p><img src="https://imgix.datadoghq.com/img/dashboarding/dashboards-section1-2021-12-16.png?ch=Width,DPR,Save-Data&auto=format&w=%3Cnil%3E&q=55" alt="" /><br />
(<a href="https://www.datadoghq.com/product/platform/dashboards/">https://www.datadoghq.com/product/platform/dashboards/</a> より引用)</p>
<p>基本的な機能は Grafana や New Relic Dashboards、Splunk Dashboards などと同様ですが、メルカリグループでは Datadog を主なクラウド監視ツールとして導入しているため、Payment Core チームでも各マイクロサービスの状態を可視化するために Dashboard を利用しています [1]。<br />
また、Payment Core チームが管理する最も大きなマイクロサービスが Payment Service です。<br />
<a href="https://engineering.mercari.com/blog/entry/2019-06-07-155849/">マイクロサービスにおける決済トランザクション管理</a> からも分かるように、決済に関するほぼすべてのリクエストは Payment Service を経由して下位のマイクロサービスに伝播します。<br />
そのため、Payment Service の Observability を向上することはメルカリグループ全体のサービスの安定化につながります。</p>
<h2>Payment Service の Dashboard が抱えていた問題と刷新の動機</h2>
<p>Payment Service には元々システム全体を可視化する Datadog Dashboard がありました。<br />
ある程度グループで分類されてはいますが、300 を超える Widget が貼られており、かなりカオスな Dashboard であることは誰の目に見ても明らかでした。<br />
多くのチームメンバーが Dashboard に不満を抱える一方で、それをリファクタリングしていく作業は地味であり、長い間放置されていました。</p>
<p>この Dashboard が抱えていた課題には次のようなものがありました。<br />
次の 3 つのカテゴリに分類した上で問題点を紹介します。</p>
<h3>可視性 (Visibility) の欠陥</h3>
<p>可視性の欠陥は、Dashboard 上の可視化されたさまざまな値を見ても理解することが困難であったり、そもそも情報に欠損があるといった問題を意味します。<br />
私たちのチームでは以下のような可視性に関する課題を持っていました。</p>
<ul>
<li><strong>一目でマイクロサービスの健康状況を把握することができない</strong>
<ul>
<li>この Dashboard はエンジニアだけでなく PdM も確認するため、より簡潔にシステムの状態を表現する Widget の需要がありました。</li>
</ul>
</li>
<li><strong>時系列データが示す値が正常なのか異常なのかを判断することが難しい</strong>
<ul>
<li>Datadog Monitors で管理している Monitor ではしきい値を確認することで “どのくらい危険な状態なのか” を確認できる一方で、しきい値を持たない Widget は現状の値は表現できても、危険度を表現することはできませんでした。</li>
</ul>
</li>
<li><strong>API のレイテンシを表す Widget において、処理時間に大きく差が生じるパラメータによってグラフが区別されていない</strong>
<ul>
<li>レイテンシを表現する Widget はありましたが、Payment Service が提供する API は、内部で同期処理にするか非同期処理にするかのリクエストパラメータによってレイテンシが大きく異なったり、決済手段の組み合わせによって速度に差があるため、それらを区別しないグラフは信頼性に欠けていました。特に残高やメルペイのあと払い、チャージ払いなどの決済手段はそれぞれ異なるマイクロサービスに依存しているため、決済手段ごとのレイテンシを表現する必要性がありました。</li>
</ul>
</li>
<li><strong>canary release 時に既存のデータとの区別がつかない</strong>
<ul>
<li>私たちのチームでは、マイクロサービスのリリース時に一部のトラフィックにのみ新しいバージョンの pod を割り当てる canary release を採用しています。しかし多くの Widget は canary の pod やバージョンによってフィルタできるように整備されておらず、ノイズが多いことでリリース時の影響確認が困難でした。</li>
</ul>
</li>
</ul>
<h3>診断性 (Diagnosability) の欠陥</h3>
<p>診断性の欠陥は、可視化された Dashboard から問題を適切に区別し、解決に向けたアクションが取りにくいことを意味します。<br />
私たちのチームでは以下のような診断性に関する課題を持っていました。</p>
<ul>
<li><strong>異常な状態を示す Widget があっても次のアクションにつなげにくい</strong>
<ul>
<li>仮に異常値を発見しても、APM やログを細かく確認するといった次のアクションにつなげにくい状態でした。</li>
</ul>
</li>
<li><strong>マイクロサービス内の問題か外部起因の問題かの区別がつかない</strong>
<ul>
<li>ある異常値が自分たちのマイクロサービス (i.e., Payment Service) に起因するものなのか、依存している他のマイクロサービスや外部の API なのかを区別することが困難でした。Payment Service は多くのプロダクト側のマイクロサービスから呼ばれると同時に、多くのマイクロサービスに依存しているため、次のアクションにつなげるために、どこに原因があるかをすぐに判断できる仕組みが必要でした。</li>
</ul>
</li>
</ul>
<h3>メンテナンス性 (Maintainability) の欠陥</h3>
<p>メンテナンス性の欠陥は、新しい API や機能の追加やしきい値の変更に Dashboard が追従できず、必要十分な状態に保てないことを意味します。<br />
私たちのチームでは以下のようなメンテナンス性に関する課題を持っていました。</p>
<ul>
<li><strong>そもそもメンテナンスされていない Widget がある</strong>
<ul>
<li>Dashboard は Payment Service リリース時に作成されたものであり、基本的にメンバーが自由に変更できるため、統一感がなく、template variables のような機能が適切に設定されていない Widget も散見されました。</li>
</ul>
</li>
<li><strong>適切に Widget がグルーピングされていない</strong>
<ul>
<li>無造作に Widget が追加されていった結果、どこに何があるのかが分かりにくくなるだけでなく、新たに Widget を追加するときにどこに置くべきか判断しにくい状態でした。</li>
</ul>
</li>
</ul>
<p>このように、私たちの Dashboard は多くの問題を抱えながらも、長い間放置されていました。<br />
その中で、今年の 1-3 月にこのようなコードべース以外の負債をまとめて解消する時間をチームで作ることができたため、その一環で Dashboard の刷新を行いました。<br />
次の章では、どのようなアプローチによって問題を解決し、どのように新しい Dashboard v2 を実現したかを説明します。</p>
<h2>Dashboard の刷新</h2>
<h3>Critical User Journey を意識する</h3>
<p>Dashboard v2 を作る上で大事にした思想が “CUJ を意識する” ということでした。<br />
CUJ は Critical User Journey の略で、ユーザ体験を設計する上で、プロダクトのユーザがそのプロダクトを利用して達成するタスクやプロセス、またはそのシナリオを意味します。<br />
ここで、私たちの CUJ におけるユーザは、メルカリアプリを使用するお客さまではなく、決済基盤である Payment Service を利用するプロダクト側のマイクロサービスの開発者を意味します。<br />
CUJ を意識した Dashboard を作ることで、例えばアラートが発生したときや依存されているマイクロサービスの開発者から問い合わせを受けたときに、Dashboard のどこを見ればよいのか、他にどこに影響が出ているのかなど、決済基盤が知っておくべき状況を理解しやすくすることができます。</p>
<p>CUJ を Dashboard に落とし込む際の考え方として、以下のような流れに沿って行いました。</p>
<ol>
<li>CUJ を考える
<ul>
<li>残高を使って決済をする、クレジットカードの登録をする、決済をキャンセルする、など</li>
</ul>
</li>
<li>CUJ を満たす基準を考える
<ul>
<li>SLO の考え方に近い</li>
<li>99.9% の決済は成功する、99.9% のクレカ登録は 0.1 秒以内に完了する、など</li>
</ul>
</li>
<li>CUJ を満たせない場合に発火するアラートを作成する</li>
<li>アラートと同様の定義を Dashboard の Widget として表現する</li>
</ol>
<p>このような流れで適切な粒度で CUJ を監視できる形に変化させます。</p>
<h3>どのように Dashboard を刷新したか</h3>
<p>CUJ を意識した上で、前章の問題点についてそれぞれ次のような仕組みや機能によってアプローチしました。</p>
<h4>可視性の向上 – 健康状態の可視化</h4>
<p>私たちは前述の考え方から、“システムが健康である” ことを、”アラートが発生していない状態” と定義しました。<br />
これは、GitHub や Slack を始めとする多くの Web アプリケーションが status ページを持っていることを参考に、アラートベースで健康状態を定義することがもっともシンプルだからです。<br />
Dashboard が担当するドメインはあくまで可視化であるべきなので、すでに持っている Datadog Monitors や蓄積されている Metrics を用いることが合理的です。<br />
Datadog Monitors がすでに整備されていることが条件ではありますが、チーム内では同時期に Datadog Monitors の整備やインフラ関連の定義の CUE 言語への置き換え [2] などを行っていたため、タイミングがとても良かったです。</p>
<p>下の図は、Dashboard の一番上に位置している System-wide status の中の 1 つの Widget です。<br />
Datadog Monitors を 1 つの Widget にまとめてリッチに表示することができる Monitor Summary Editor を利用しています。<br />
各 Monitor はどのマイクロサービスのものなのかという情報をタグで持っているため、フィルタを設定することで Payment Service のアラート状況のみをまとめることができます。<br />
エンジニアであれば他の方法でアラート状況の確認ができる場合もありますが、PdM や他のチームの開発者が見たとしても理解しやすく、Payment Service の status ページの役割も兼ねていると言えるでしょう。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/124e5653-monitor-summary-editor.png" alt="" /></p>
<h3>可視性の向上 – しきい値の可視化</h3>
<p>ある API のレイテンシや DB のタイムアウト数を表現する時系列データが “問題になり得るレベルより安全側にいるのか” や “問題になり得るレベルと現状の差” を表現するために、下図のように各 Widget にマーカーを設定しました。<br />
これによって Widget を見た人は "12 月 17 日の朝にレイテンシが少し高くなったが、アラートレベルではない" ということを一目で理解することができます。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/8dbe3bf3-marker.png" alt="" /></p>
<p>各しきい値は同様の Monitor がある場合はその値と同じ値を採用しています。<br />
Dashboard は Monitor と違って手で編集しているため、Monitor の定義が変更されると Dashboard と差分が生じる問題も議論の中ではありましたが、しきい値の変更は頻繁にはないことを理由に許容しています。<br />
また、当初は時系列の Widget をそれぞれ作成するのではなく、Alert Graph (Moitor をひとつ選択して Dashboard に貼ることができる Widget の種類) を利用することを検討していました。<br />
これによって過半数の Widget はその定義を Monitor に移譲することができるからです。<br />
しかし、Monitor は本番環境全体を監視するものしか持っていなかったため、他の問題点でもある canary pod の状態のみを表示したり、本番環境ではなく開発環境でフィルタしたいときに不都合でした。</p>
<h4>可視性の向上 & 診断性の向上 – APM resource の細分化</h4>
<p>Payment Service の Dashboard には元々レイテンシを計測する指標として各 API の Trace がありましたが、前述の通り決済手段の組み合わせやその他のリクエストパラメータによってレイテンシが大きく異なるため、CUJ に沿ってこれを細分化しました。<br />
具体的には、gRPC interceptor に Trace を細分化する処理を追加し、決済手段の組み合わせごとに別の APM resource として認識させることで、Dashboard からも別々のレイテンシを取得できるようにしました。<br />
これによって残高払いのみを利用した時のレイテンシ、あと払いのみを利用した時のレイテンシ、2 つを組み合わせた時のレイテンシを区別することができるようになりました。<br />
この利点は単に Widget が示す値の信頼性を高めるということだけでなく、例えば残高払いのレイテンシが跳ねたときにあと払いのレイテンシも跳ねていれば DB やネットワークの問題などの共通部分の問題を疑うことができ、片方だけであれば依存するマイクロサービスや周辺の実装を疑うことができるため、調査もより楽になりました。</p>
<p>リクエストパラメータの中には今回の支払手段のように実行時間に大きく影響を与えるものもあれば、内部の if 文に影響があるような小さいもの、まったく与えないものがあります。<br />
どのレベルまで分けるかというのはそのマイクロサービスの役目やドメインによって異なるものですが、マイクロサービスの依存関係や主要な CUJ を意識することで適切なレベルで分割が可能になります。</p>
<h4>可視性の向上 & メンテナンス性の向上 – 適切なタグ管理と template variables の整備</h4>
<p>canary 環境のみを可視化することは、私たちが安全にソフトウェアをデリバリーする上で非常に重要な機能でした。<br />
canary 環境かどうかという情報は、インフラ観点では Kubernetes の stack として保持していますが、Metrics をフィルタする上では能動的にタグを付与する必要があります。<br />
そのため環境変数として Deployment に stack 情報を記載し StatsD [3] に Metrics を送信する段階で stack の情報も付与するようにしました。<br />
これによって、Dashboard 上の Widget を stack でフィルタすることが可能になりました。<br />
各 Widget は Metrics を選択する際の変数の指定方法として、直接 <code>stack:canary</code> のように記述することも可能ですが、Dashboard 全体で変数を定義できる template variables を利用することで、 各 Widget 内では <code>stack:$stack</code> として定義しています。<br />
この機能を使うことで、すべての Widget の stack タグを変更してフィルタしたり、その設定を View として保存することができます。<br />
メンテナンス性の観点からも、新しい stack が追加されるなどの変更に追従しやすい設計が可能となります。<br />
Dashboard v2 では次のような template variables と View を持っています。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/f597450a-template-variables.png" alt="" /><br />
<img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/b5884757-views.png" alt="" /></p>
<h4>診断性の向上 – Context Links による Widget と Logs や Traces の接続</h4>
<p>Dashboard の Widget のグラフをクリックすると、下図のようなポップアップが表示されます。<br />
この例では、”View related traces” をクリックすることで、このグラフに関連する Datadog APM Traces を一覧で表示してくれます。<br />
これによって Widget 内で異常な値があったときにすぐにリクエストのどこに問題があるかを調査する次のステップに進むことができます。<br />
一方で、この例では “No related logs” となっていて、Datadog Logs に飛んでログを確認することはできません。<br />
これらの機能は Widget に設定されている条件 (from 句) を参考に自動で生成されていますが、Metrics と Logs で同じフィールドを持っていないと正しくヒットしなかったからです。<br />
そのため、アプリケーション内の logger に APM と同じタグを付与したり、Context Links を編集して APM や Logs と適切にリンクされるようにしました。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/1a950d4f-context-links.png" alt="" /></p>
<h4>メンテナンス性の向上 – 適切なグルーピング</h4>
<p>Dashboard のメンテナンス性は引き出しに整理整頓していくようなもので、その引き出しがなんのためのものかがわからなければ新しい物を置くときに困ってしまいます。<br />
メンバーが誰でも手動で編集できてしまうため、シンプルに保つことが大切です。<br />
Datadog Dashboard は Empty Group と呼ばれる Widget によって複数の Widget を 1 つのまとまりとして視覚的にグルーピングできます。<br />
2 段階以上のグルーピングができない点は不便ですが、Dashboard v2 では Text Widget と組み合わせてサブグループも表現しました。<br />
ここで意識したのは Widget を追加するときにどこに追加すればよいかが明示的であるように視覚的なブロックを作成することで、誰が追加しても同じ様になるような簡潔さとグルーピングを実現しました。<br />
例えば以下は簡単な例ですが、縦軸にマイクロサービスが、横軸に Metrics が並んでいることは誰でも一目で理解できます。<br />
ある開発で新しいマイクロサービスへの依存が増えたとき、一行下に追加すれば良いことは明らかで、ただ 9 つの Widget を端から並べるより可視性もメンテナンス性も向上します。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/fc3d73d0-grouping.png" alt="" /></p>
<p>これらは今回実施した改善の一例ですが、新しい Dashboard は on-call 対応時やインシデントへの反応速度、PdM などの開発者以外のステークホルダーとのコミュニケーションがより早く、より円滑になりました。<br />
多くの不要な Widget を削除することができた結果、300 を超えるWidget は 113 個まで減り、検索性も向上しました。</p>
<h2>今後の展望</h2>
<h3>Widget の CUE 化</h3>
<p>今回のプロジェクトでは多くの Widget を新しく作り直す必要があったことから、GUI 上で可視化しながら編集をしました。<br />
私たちのチームでは Datadog Monitors を CUE 言語で管理していることもあり、既存の Widget も同様に CUE 言語で定義し、Dashboard から参照するような形が理想的だと思っています。<br />
これは IaC の考え方と同じですが、Widget が意図せず編集されてしまうことを避けることができます。<br />
また、複数の Widget を編集するときなど、統一的な操作をしたいときにコードとして定義されていることは大きな恩恵をもたらすでしょう。</p>
<h3>Monitor の整理と調整</h3>
<p>Payment Service の状態を監視する Monitor は 1408 個ありますが、一部の Monitor は設定の不備や厳しすぎるしきい値設定によってアラートが常に発火しているなど、正しくシステムの正常性を表現できていないものもあります。<br />
これは Dashboard の展望とは異なりますが、システムの状態の可視化はすべての Monitor が正しく設定され動いていることが前提にあります。<br />
そのため、チーム内で継続的に Monitor を見直し、しきい値の調整などを通して “正常とは何か” ということを常に定義し続けていく必要があります。</p>
<h2>おわりに</h2>
<p>今回の記事では私たちのチームにおいて、より安定した決済基盤を社内に提供するために、柔軟性が高く、可視性と診断性に強い Dashboard を作成した話を紹介しました。<br />
マイクロサービス利用者の CUJ を意識しながら、多様な決済手段の組み合わせや依存関係を可視化する仕組みを作成できたことは、今後のより堅牢な決済基盤の開発を支えてくれると信じています。</p>
<p>明日の記事は myoshida さんです。引き続きお楽しみください。</p>
<h2>注釈</h2>
<p>[1] メルカリグループでは Production Readiness Checklist が存在し、Dashboard を整備することも一定のマイクロサービスをリリースするための条件となっています。<br />
[2] メルカリグループでは Kubernetes のマニフェストを始めとし、Datadog の Monitor や Widget も CUE 言語で定義できる環境が整備されています (ref. <a href="https://engineering.mercari.com/blog/entry/20220127-kubernetes-configuration-management-with-cue/">https://engineering.mercari.com/blog/entry/20220127-kubernetes-configuration-management-with-cue/</a>)。<br />
[3] 正確には DogStatsD。</p>
- モダリティを考慮したiOSアプリのナビゲーションの再設計https://engineering.mercari.com/blog/entry/20231219-b613d5daad/https://engineering.mercari.com/blog/entry/20231219-b613d5daad/<p>こんにちは。メルペイのiOSエンジニアの@kenmazです。 この記事は、Merpay Advent Calendar 2023 の19日目の記事です。 概要 iOSアプリ開発において、お客さまにより良い体験を提供する上 […]</p>
Tue, 19 Dec 2023 10:00:27 GMT<p>こんにちは。メルペイのiOSエンジニアの<a href="https://twitter.com/kenmaz">@kenmaz</a>です。<br />
この記事は、<a href="https://engineering.mercari.com/blog/entry/20231124-merpay-advent-calendar-2023/">Merpay Advent Calendar 2023</a> の19日目の記事です。</p>
<h2>概要</h2>
<p>iOSアプリ開発において、お客さまにより良い体験を提供する上でナビゲーションの設計は非常に重要なトピックです。特にメルペイのように「決済」「申し込み」「登録」といった自己完結型のタスクを提供する画面が多いアプリでは、iOSのモーダル表示を活用した設計手法である「モダリティ」を意識することが <a href="https://developer.apple.com/design/human-interface-guidelines/modality">Apple Human Interface Guideline</a> において推奨されています。これにより、お客さまを迷わせることのない使いやすいアプリを構築でき、またコードの保守性も向上します。</p>
<p>本記事では、メルペイiOSチームが既存機能のリライトプロジェクトを進める中で発見した既存の画面設計の問題点を、モダリティの設計手法に基づいて解決した事例をご紹介します。</p>
<h2>背景</h2>
<p>メルペイでは現在、メルカリで採用しているSwiftUIベースのアーキテクチャと最新のデザインシステムライブラリを使って、メルペイが提供する全ての画面を書き換えるプロジェクトを進めています。プロジェクト自体の詳細については先日開催された<a href="https://engineering.mercari.com/blog/entry/20231023-mmtf2023-day1-4/">Merpay & Mercoin Tech Fest 2023</a>での発表の書き起こし記事をご覧ください。</p>
<p>このリライトプロジェクトでは、単にコードを書き換えるだけではなく、同時に既存の機能の見直しや、設計上の問題点なども可能な限り同時に改善しながら進めています。その中で見つかったのが、今回のテーマであるナビゲーションの設計上の問題です。</p>
<h2>メルペイのUIとモダリティ</h2>
<p>冒頭でも述べた通り、メルペイでは「決済」「チャージ」「登録」「申し込み」といったような自己完結型のタスクを提供する機能が多いのが特徴です。対照的に、メルカリでは商品の検索や閲覧など「情報探索」が体験の中心にあり、そこに「購入」「出品」といった自己完結型タスクが付随する構造になっています。</p>
<p>WWDC2022の <a href="https://developer.apple.com/videos/play/wwdc2022/10001">Explore navigation design for iOS</a> というビデオでは、iOSでは自己完結型のタスクを提供する画面は「モーダル表示」の使用を推奨しています。モーダル表示とは、現在表示しているコンテンツやタブバーなどを意図的に覆い隠すように画面下からせり上がって画面を表示する方法のことです。これにより、元々表示していたコンテンツの情報階層を一時的に切り離し、特定のタスクに焦点を絞ることで、お客さまに「今自分が何をやっているか」をわかりやすく伝えることができます。このようなアプリの設計手法のことを<a href="https://developer.apple.com/design/human-interface-guidelines/modality">「モダリティ」</a>と呼びます。</p>
<p>また上記ビデオでは、モーダルで表示するにふさわしい自己完結型タスクとして、</p>
<ol>
<li>イベントの作成やリマインダーの設定などのシンプルなタスク</li>
<li>複雑なステップを伴うマルチステップのタスク</li>
<li>動画の再生などのフルスクリーンコンテンツの表示</li>
</ol>
<p>の3種類が挙げられています。</p>
<p>メルペイはまさに上記1および2の機能を多く提供しており、そのような機能にはモーダル表示を適用するのが好ましいことがわかります。</p>
<h2>課題事例:銀行口座接続</h2>
<p>さて、メルペイのリライトプロジェクトを進める中で、モダリティの設計手法に反している画面がいくつか見つかりました。その一つが「銀行口座接続」機能です。ここからは既存の銀行口座接続機能のナビゲーション設計の問題点とその解決策について紹介します。</p>
<p>銀行口座接続機能とは、お客さまの銀行口座をメルペイのアカウントに登録するための機能です。銀行口座を登録することで、メルペイでのお支払いに使える残高をお客さまの銀行口座からチャージできます。</p>
<p>銀行口座機能はメルカリアプリのさまざまな箇所から呼び出されます。例として、残高チャージ画面から銀行口座接続機能を呼び出すナビゲーション(改善前のもの)を示します。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/0c1ad18d-kenmaz-1.png" alt="銀行口座接続フロー(改善前)" /><br />
<em>銀行口座接続フロー(改善前)</em></p>
<p>上図は、銀行口座が一つも登録されていない状態でメルペイ残高にチャージしようとする際のナビゲーションを示しています。大まかな流れは以下の通りです(説明を簡単にするため、いくつかの画面は省略しています)</p>
<ol>
<li>支払い画面でチャージボタンをタップすると、チャージ画面がモーダルで表示</li>
<li>チャージ画面でチャージ方法を選択すると、チャージ方法画面がプッシュ遷移で表示</li>
<li>「お支払い用銀行口座を登録する」をタップすると、モーダル画面が閉じ、支払い画面に戻る</li>
<li>銀行口座接続のイントロダクション画面がプッシュ遷移で表示</li>
<li>「次に進む」ボタンをタップすると、銀行の選択画面がプッシュ遷移で表示</li>
<li>接続したい銀行を選択すると、口座情報の入力画面がモーダル表示</li>
<li>口座情報を入力し「銀行サイトへ」ボタンをタップすると、各銀行のwebサイトにアクセスし、認証が完了したら登録完了画面にプッシュ遷移</li>
<li>登録完了画面の「OK」ボタンをタップするとモーダル画面が閉じ、支払い画面に戻る</li>
</ol>
<p>一見何の問題もないように見えますが、いくつかの課題が存在します。それらの課題を解決した改善後のフローを以下に示します。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/cf09d230-kenmaz-2.png" alt="銀行口座接続フロー(改善後)" /><br />
<em>銀行口座接続フロー(改善後)</em></p>
<p>どのような課題があり、どのように解決したのかを詳しく見ていきましょう。</p>
<h2>課題</h2>
<h3>課題1:銀行口座接続フローの一部画面が非モーダルで表示されている</h3>
<p>上述の通り、銀行口座接続のような「複雑なステップを伴うマルチステップの自己完結型タスク」はモーダル表示することが推奨されています。しかし上のナビゲーション図を見ると「イントロダクション」画面と「銀行の選択」画面はモーダル表示ではなく、支払い画面からプッシュ遷移で表示されています。</p>
<p>先に述べた通り、モーダル表示のメリットのひとつは「タブバーなどを意図的に覆い隠すように画面下からせり上がって画面を表示」することにあります。あえてタブバーを隠すことによって「いまは銀行口座接続の作業が進行中ですよ」ということを表現し、現在のタスクへの集中をお客さまに促すことができます。</p>
<p>しかし「イントロダクション」画面や「銀行の選択」画面はモーダル表示ではないので、下部のタブバーは表示されたままで、操作することも可能です。銀行口座接続の処理中に、誤ってタブバーを操作してしまい、意図せずタスクから離脱させてしまう危険性もあります。</p>
<p>理想的には、これら二つの画面を含め銀行口座接続タスクの画面全体(水色の枠で囲まれた部分)はモーダル表示にすべきでしょう。</p>
<h3>課題2:残高チャージの中断</h3>
<p>今回示した例は、銀行口座が未登録の状態で残高チャージを行う際のナビゲーションを示しています。つまり本来行いたかったタスクは「残高チャージ」なのですが、銀行口座が未登録だったため、まずサブタスクとして「銀行口座接続」タスクに誘導している状況です。</p>
<p>理想的にはサブタスクである「銀行口座接続」タスクが完了したら、本来のタスクである「残高チャージ」タスクに制御を戻したいところですが、実際はそうはなっていません。</p>
<p>現状の銀行口座接続フローはモーダル表示されることを想定しておらず、銀行口座接続フローを表示する際は、まず全てのモーダルを閉じた後に非モーダルとして表示することを前提として設計されてしまっています。そのため、チャージ画面が閉じられてしまい、本来の目的である「残高チャージ」タスクが中断されてしまっているのです。</p>
<p>理想的には、チャージ方法画面で「お支払い用銀行口座を登録する」をタップした際は、チャージ画面を閉じるのではなく、表示したままにしておくべきです。その上にさらに銀行口座接続フローをモーダル表示し、接続が完了したら単にモーダルを閉じて元のチャージ方法画面に制御を戻せばよいのです。「残高チャージ」タスクを中断すべきではありません。</p>
<h3>課題3:コードの再利用性</h3>
<p>現状のナビゲーションの設計はコードにも問題を引き起こします。メルペイiOSでは銀行口座接続フローのようなアプリ内のさまざまな箇所から呼び出される画面に対して、以下のようなインタフェースを用意しています。</p>
<pre><code class="language-swift">public enum MerpayScene {
case connectBank(completion: (Result) -> Void, ..)
case ...
}
public protocol MerpaySceneRouterProtocol {
func viewController(scene: MerpayScene) -> UIViewController
}
// Caller
let vc = sceneRouter.viewController(scene: .connectBank(...))
navigationController.pushViewController(vc, animated: true)</code></pre>
<p>各画面は <code>MerpayScene</code> のenum値として定義されており、それを <code>MerpaySceneRouter</code> に渡すことで対応するViewControllerを取得できます。上記例では、<code>MerpayScene.connectBank</code>を指定することで、銀行口座接続フローのエントリーポイントとなる画面のViewControllerを取得しています。</p>
<p>ただし、このように取得したViewControllerを <a href="https://developer.apple.com/documentation/uikit/uinavigationcontroller/1621887-pushviewcontroller">pushViewController(_:animated:)</a> で遷移させると、銀行口座接続のようなマルチステップで構成されるタスクの場合、そのタスクが完了した後の処理の実装が面倒になるという問題があります。</p>
<p>タスクがモーダルとして表示されるのであれば、以下のように呼び出し側は単に <a href="https://developer.apple.com/documentation/uikit/uiviewcontroller/1621380-present">present(_:animated:completion:)</a> で対象画面をモーダル表示し、タスクが完了したら呼び出された側で <a href="https://developer.apple.com/documentation/uikit/uiviewcontroller/1621505-dismiss">dismiss(animated:completion:)</a> を呼べば、呼び出し元の画面にスムーズに戻ることができます。また、completion引数を指定することで、タスクの実行結果に応じて呼び出し元で処理を分岐させる、といったことも容易に実現できます。</p>
<pre><code class="language-swift">let vc = sceneRouter.viewController(
scene: MerpayScene.connectBank(
completion: { success in
if success {
...
} else {
...
}
}
)
)
present(vc, animated: true)</code></pre>
<p>一方、タスクをプッシュ遷移で表示している場合は、やや制御が難しくなります。モーダルのように <a href="https://developer.apple.com/documentation/uikit/uiviewcontroller/1621505-dismiss">dismiss(animated:completion:)</a> を呼び出すだけ、とはいかずに、たとえば呼び出し元のViewControllerをメモリに保持しておき、<a href="https://developer.apple.com/documentation/uikit/uinavigationcontroller/1621871-poptoviewcontroller">popToViewController(_:animated:)</a> で呼び出し元の画面に戻すなど、やや特殊な実装が必要になる場合があります。</p>
<p>またタスクの実行結果に応じて呼び出し元でなんらかの処理を行いたい場合、 <a href="https://developer.apple.com/documentation/uikit/uiviewcontroller/1621505-dismiss">dismiss(animated:completion:)</a> とは違って、<a href="https://developer.apple.com/documentation/uikit/uinavigationcontroller/1621871-poptoviewcontroller">popToViewController(_:animated:)</a> や <a href="https://developer.apple.com/documentation/uikit/uinavigationcontroller/1621886-popviewcontroller">popViewController(animated:)</a> には、呼び出し元の画面への遷移が完了したことをフックするための <code>completion</code> 引数などは用意されていないので、<a href="https://stackoverflow.com/questions/54207143/detect-when-view-controller-appears-from-pop">呼び出し元の <code>viewWillAppear</code> に追加の処理を仕込んで検知する、といったような余計なハック</a>が必要になることもあります。</p>
<p>銀行口座接続のような自己完結型のタスクは素直にモーダル表示することを前提とし、呼び出す側としては単に <a href="https://developer.apple.com/documentation/uikit/uiviewcontroller/1621380-present">present(_:animated:completion:)</a> で表示、タスクが完了したら <a href="https://developer.apple.com/documentation/uikit/uiviewcontroller/1621505-dismiss">dismiss(animated:completion:)</a> で呼び出し元に制御が戻ってくる設計にすることで、理解しやすく再利用しやすいコードを保つことができます。</p>
<h2>モダリティを考慮した再設計</h2>
<p>これらの課題を解決する方法は、銀行口座接続フロー全体をモーダル表示を前提としたものに再設計することです。再設計を行い改善したナビゲーションは、先に示した 銀行口座接続フロー(改善後) の通りです。</p>
<p>上図の通り、改善後のナビゲーションでは銀行口座接続フロー全体がモーダル表示となっていることがわかります。銀行口座の登録が完了したらチャージ方法画面に制御が戻ってくるので、「残高チャージ」タスクが中断されることはありません。あとは登録した口座を選択して、残高チャージを実行するだけです。非常にシームレスな体験を実現できました。</p>
<p>注)上記改善は2024年初旬にリリース予定です</p>
<h3>余談:モーダル on モーダル</h3>
<p>ところで、冒頭で紹介したWWDCのビデオでは<a href="https://developer.apple.com/videos/play/wwdc2022/10001/?time=1399">「モーダルの上に表示するモーダルは乱雑で 複雑に感じるため、制限すべし」</a>といった説明がありました。上記の改善後のナビゲーションはまさに「残高チャージ」モーダルの上に「銀行口座接続フロー」モーダルを表示している状態にあたります。このような設計は避けるべきなのでしょうか?</p>
<p>しかし、同ビデオの中ではさらに<a href="https://developer.apple.com/videos/play/wwdc2022/10001/?time=1476">「サブビューの一貫性と 集中力を高めるために複数のモダリティタスクが必要な場合もあります」</a>という説明もありました。ビデオ内で例として示されていたのは旅行の行程を編集するモーダルの画面から、iOS標準の写真選択画面をモーダル表示で呼び出すような事例でした。そのようなケースでは全く違和感は感じません。</p>
<p>個人的には、銀行口座接続や写真の選択といった、十分に自己完結的で独立したタスクであれば許容可能であると考えます。プロダクトチーム内で慎重に判断して導入することをお勧めします。</p>
<h2>まとめ</h2>
<p>以上、メルペイiOSチームで既存機能のリライトプロジェクトを進める中で発見した既存の画面設計の問題点を、モダリティの設計手法に基づいて再検討し、改善した事例をご紹介しました。</p>
<p>なお、私の同僚の @kris も冒頭で紹介したWWDCのビデオからインスピレーションを受けて、メルカードのUIに取り組んでいます。その内容は<a href="https://engineering.mercari.com/en/blog/entry/20230627-designing-ios-screen-navigation-for-best-ux/">Merpay Tech Openness Month 2023のブログ記事</a>として公開されているので、興味のある方はそちらも合わせてご参照ください。</p>
<p>メルペイには数多くの機能があり、全ての画面についてリライトプロジェクトが完了するのはもう少し時間がかかりそうです。ただのリファクタリングプロジェクトとして終わらせるのではなく、本記事で紹介したような改善ポイントを見つけ、可能な限り改善し、プロダクト全体の品質向上に貢献できるように、iOSチーム一丸となって改善に取り組んでいきたいと考えています。</p>
<p>明日の記事は@komatsuさんです。引き続きお楽しみください。</p>
- Onboarding施策を成功させるポイントhttps://engineering.mercari.com/blog/entry/20231218-mercari-advent-calendar-day18/https://engineering.mercari.com/blog/entry/20231218-mercari-advent-calendar-day18/<p>この記事は、Mercari Advent Calendar 2023 の18日目の記事になります。 こんにちは!メルカリ Engineering Office チームの@aisakaです。 私達のチームは「Establi […]</p>
Mon, 18 Dec 2023 12:00:24 GMT<p>この記事は、<a href="https://engineering.mercari.com/blog/entry/20231124-mercari-advent-calendar-2023/">Mercari Advent Calendar 2023</a> の18日目の記事になります。</p>
<p>こんにちは!メルカリ Engineering Office チームの@aisakaです。</p>
<p>私達のチームは「Establish a Resilient Engineering Organization」というミッションを元に、様々な活動を行なっています。先日のAdvent calendarでマネージャーのhiroiさんがチームの活動の内容、目的の紹介をしているので、ぜひこちらも読んでみてください。</p>
<p><a href="https://engineering.mercari.com/blog/entry/20231206-4e4f1e2323/" title="強いエンジニア組織に必要な、6つの技術以外のこと – メルカリ編">強いエンジニア組織に必要な、6つの技術以外のこと – メルカリ編</a></p>
<p>私はEngineering Officeがカバーする領域の中でもOnboardingを担当していて、よりよいOnboarding体験を提供していくための戦略や仕組みづくりに携わっています。</p>
<p>OnboardingやトレーニングといったHR領域に近い施策というのは、KPIを立てづらく、かかるコスト(人的コストやお金)に対する効果を測定しづいといった悩みが一般的ですよね。</p>
<p>本記事では、メルカリのエンジニアリング組織がどのようにKPIをたて、効果測定を実施しているのか、またOnboarding施策を成功させるためのポイントを紹介していきます。</p>
<p>エンジニア組織の組織課題に取り組んでいる方や施策づくりをしている方におすすめです。</p>
<h1>費用対効果の最大化</h1>
<p>組織の施策を企画し実施、運用するうえで最も大事なポイントは、いかにROI(費用対効果)を意識し、その最大化に繋げられるかです。ここでは、メルカリが実際に実施している4つのポイントを紹介していきます。</p>
<h2>コンテンツの集約</h2>
<p>組織が大きくなると、蓄積される知識や情報量が多くなる反面、点在しやすく正しい情報にリーチしづらいというダウンサイドもあります。最適な量の正しい情報へのガイドがOnboardingを成功させるために重要だと考えているため、メルカリではOnboardingコンテンツの集約には力をいれて取り組んでいます。冗長なコンテンツは一つにまとめ、コンテンツを置く場所を一箇所に集約することで、入社者が何か分からないことがあった際に自力で検索して探し出せるような導線を作っています。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/70369d39--2023-12-11-14.08.55.png" alt="" /></p>
<h2>継続的なアップデートサイクル</h2>
<p>コンテンツというのは、一定期間アップデートがされないとすぐに古い情報となってしまい使えないという側面ももっています。メルカリは中途採用、新卒採用を通年行っているため、Onboardingで必要なコンテンツは比較的利用頻度が高く、古いコンテンツにならないようにすることが重要です。</p>
<p>Onboardingで必要な作業の文書化やコンテンツの見直しに貢献してくれるエンジニアを半年ごとに公募で募集し、有志メンバーで資料のアップデートや作成を継続的に実施しています。また、新入社員の方も自身のOnboardingの過程で、情報のアップデートや文書化へのコントリビューションを奨励しています。</p>
<p>またコントリビューションは可視化し、貢献してくれたかたへの評価に繋がるように運営を工夫しています。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/2f5a1f7f--2023-12-11-13.54.46.png" alt="" /></p>
<h2>利用者数の可視化</h2>
<p>せっかく質の高いコンテンツを整備しても、実際に使ってもらえないと意味がありません。コンテンツが見られているのか、使われているのかを評価するため、MAU(Monthly Active Users)とPageviewsをトラッキングし、資料の利用率を評価しています。</p>
<p>一般的にコンテンツに関する指標は、サーベイで満足度を入社者にヒアリングするケースが多いですが、サーベイは回答負荷が高く充分な回答数が得られなかったり、回答者の主観が強すぎたりするため、自動でとれて客観性が高いものを指標として評価しています。</p>
<p>以前、マネージャーのGrahamさんが、サーベイ疲れを最小限にしつつフィードバックをもらうための方法をブログで書いていたので、ぜひ参考にしてみてください。</p>
<p><a href="https://engineering.mercari.com/blog/entry/20230718-surveys-survey-fatigue-and-getting-feedback/" title="アンケート疲れから考えるフィードバック獲得の改善方法">アンケート疲れから考えるフィードバック獲得の改善方法</a></p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/26af9081--2023-12-11-13.42.47.png" alt="" /><br />
Looker Studioのスクリーンショットより</p>
<p>「安易にサーベイに頼らない。」という心がけは、不要な負荷を生み出さないという点において施策づくりの際にとても重要だと感じています。</p>
<h2>オペレーションの自動化</h2>
<p>運営側のコストを削減する視点もとても重要です。HR領域の施策はどうしてもマニュアルで管理する場合が多いですが、できるかぎりプロセスの一部を自動化し、運営側のオペレーションコスト削減にも力をいれています。</p>
<p>メルカリでは、OnboardingのアクションアイテムをJIRAチケットで提供していますが、入社者ごとにカスタマイズしたチケットを自動でJIRAに払い出すシステムを内製し運用しています。</p>
<p>以前は複数のチェックリストがHR、Engineering組織、各チームで点在していて分かりづらいといった課題があったのですが、それをJIRAで一元管理できるようにしています。</p>
<p>こうしたコスト削減や効率化をはかるための自動化システムの内製もEngineering Office内では積極的に実施しています。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/958f8bad--2023-12-12-16.45.44.png" alt="" /></p>
<h1>デリバリーの最大化</h1>
<p>良いコンテンツを社内で作ったら、それをより多くの方へ届けることで、効果を最大化することができます。どのように届け、その効果を大きくするために、実践している2つのポイントを紹介していきます。</p>
<h2>他部署、専門外の技術領域を学びたい人へ届ける</h2>
<p>適切にアップデートされた良いコンテンツは、新入社員だけではなく、既存のメンバーのラーニングにも役立ちます。コンテンツを誰もがアクセスできる場所に集約させ、他部署や専門外の技術領域を学びたい方も必要な情報にアクセスできるようになっています。実際、MAUをみてみると既存メンバーからのアクセスは新入社員の人数の数倍近くあり、幅広い方に利用されています。</p>
<p>また、メルカリでは年に1~2回、<a href="https://engineering.mercari.com/blog/entry/20221223-showcasing-devdojo-a-series-of-mercari-developed-learning-content-for-engineering/" title="DevDojo">DevDojo</a>と呼ばれる技術研修期間を設けています。もともとは新卒向けのOnboarding トレーニングとして設計され企画されたものでしたが、新卒以外の既存社員も受講できるように社内でオープンにしています。毎回、部署を超えた50名近くの既存社員が参加しトレーニングを受講しています。</p>
<h2>社外発信に繋げ、コンテンツ作成者のキャリアップに繋げる</h2>
<p>持続的にコンテンツを作成、アップデートし、社内で展開していくうえで最も重要なことは、コンテンツ作成者からの協力を常に得られる状態にすることです。社内向けのコンテンツ作成というのはボランティアベースになってしまうケースがよくあるパターンです。しかし、この運用方法ではコンテンツ作成者にメリットがなく労力を無駄にしてしまうリスクがあります。メルカリでは、社内コンテンツを一部エンジニア組織のカルチャーや人を紹介する<a href="https://youtube.com/c/MercariGears" title="Mercari Gears YouTubeチャンネル">Mercari Gears YouTubeチャンネル</a>において外部公開することで、コンテンツ作成がTech PR (技術発信)と個人のビジビリティの向上といったキャリアアップに繋がるように工夫しています。</p>
<p><a href="https://engineering.mercari.com/learning-materials/" title="技術トレーニングDevDojo">技術トレーニングDevDojo</a></p>
<p>こうした、コンテンツ作成者、コンテンツ受講者の両方がWin-Winとなるように施策づくりをすることで、持続的なサービスを提供できています。<br />
<img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/d24a77e2--2023-12-11-14.07.20.png" alt="" /></p>
<h1>今後力をいれていきたい分野</h1>
<p>エンジニアという職種は比較的転職サイクルが早いため、メルカリは中途採用での入社者が多いです。そこで、前職までの環境からメルカリのエンジニア組織への移行をいかにスムーズにするかという視点がとても重要です。</p>
<p>メルカリでは、新入社員がインプットする情報、知識の量とクオリティのレベルをある程度統一し、入社直後の時期から標準化された知識を学習できるようにしています。こうした、健全な組織を維持、発展していく体制をOnboardingという一番最初の段階から整えていくことに力をいれています。Onboardingの時期は過去のやり方から脱却し、新しいことを比較的受け入れやすい時期でもあるため、今後最も力をいれて作っていきたい分野です。</p>
<h1>最後に</h1>
<p>これまで、3年ほどエンジニア組織のOnboarding施策を担当しました。成功に必要なポイントをまとめます。</p>
<ul>
<li>コンテンツは一箇所に集約することで、利用者がリーチしやすくする</li>
<li>コンテンツを継続的にアップデートし続ける仕組みをつくる</li>
<li>KPIはサーベイに頼らず、自動で取れるものを指標にする</li>
<li>オペレーションは自動化し、運営コストを削減する</li>
<li>より多くの人に届ける</li>
<li>コンテンツ作成者のキャリアアップや評価に繋がる仕組みにする</li>
<li>健全な組織づくりのため、ガバナンス強化という視点をもつ</li>
</ul>
<p>こうした施策づくりというのは一朝一夕ではできず、トライアンドエラーを繰り返し、他のエンジニアの皆さんのサポートを得ながら皆なで少しづつ作ってきました。</p>
<p>今回ご紹介したポイントは決してOnboardingだけでなく、多くの施策づくりに応用が効くと感じます。何か少しでも参考になるものがあれば嬉しいです。</p>
<p>また、メルカリグループでは、積極的にエンジニアを採用しています。ご興味ある方、ぜひご連絡お待ちしております!</p>
<p><a href="https://careers.mercari.com/jp/search-jobs/?cat=software-engineering-jobs" title="Open position – Engineering at Mercari">Open position – Engineering at Mercari</a></p>
<p>長文となりましたが、最後までお読みいただき、ありがとうございました。</p>
- Merpay Enabling Client チームが目指すことhttps://engineering.mercari.com/blog/entry/20231218-what-the-merpay-enabling-client-team-aims-for/https://engineering.mercari.com/blog/entry/20231218-what-the-merpay-enabling-client-team-aims-for/<p>こんにちは。メルペイ Engineering Managerの@masamichiです。 この記事は、Merpay Advent Calendar 2023 の18日目の記事です。 この記事では私がマネージャーを務めてい […]</p>
Mon, 18 Dec 2023 10:00:53 GMT<p>こんにちは。メルペイ Engineering Managerの<a href="https://x.com/masamichiueta" title="@masamichi">@masamichi</a>です。<br />
この記事は、<a href="https://engineering.mercari.com/blog/entry/20231124-merpay-advent-calendar-2023/" title="Merpay Advent Calendar 2023">Merpay Advent Calendar 2023</a> の18日目の記事です。<br />
この記事では私がマネージャーを務めているMerpay Enabling Clientチームの役割や今後進めていくことについて紹介します。</p>
<h2>Merpay Enabling Client Team</h2>
<p>メルペイの組織構造は現在Program型組織となっており、その中でもEnabling ProgramはArchitectやSRE、Data Platformなど、横断的な技術課題の解決や生産性向上など開発全体を支援する組織です。Program型組織の詳細については2日目の@keigow さんの記事をご覧ください。</p>
<ul>
<li><a href="https://engineering.mercari.com/blog/entry/20231202-merpay-program-organization/" title="メルペイのProgram型組織への移行">メルペイのProgram型組織への移行</a></li>
</ul>
<p>Merpay Enabling Clientチームはその中でWeb/Android/iOSから構成されるチームで、Client領域の横断的なプロジェクトを推進しています。<br />
2023年の10月まではClient領域のチームはWeb/Android/iOSのプラットフォームごとに分かれており、私はMerpay iOSチームのマネージャーを担当していました。Program組織体制への移行を経て、現在はMerpay Enabling Clientチームのマネージャーを担当しています。</p>
<p>チームのVisionは</p>
<p><strong>“Enable continuous product improvement through client engineering excellence”</strong><br />
<strong>“クライアントの卓越したエンジニアリングを通じて、プロダクトの継続的な改善を可能にする”</strong></p>
<p>としており、チームとしてプロダクトの成長に貢献することを意識しています。Excellenceという言葉には、2009年に前Apple CEOの故Steve Jobs氏が療養中に、現Apple CEOのTim Cook氏が述べた言葉</p>
<blockquote>
<p>“We don’t settle for anything less than excellence in every group in the company — and we have the self honesty to admit when we’re wrong and the courage to change.”<br />
“社内のどのグループについても卓越未満で満足するつもりはありませんし、間違っている時にはそれを自分に対して正直に認める勇気と、間違いを正す勇気も我々にはあります”</p>
</blockquote>
<p>からチームでも同じマインドを持とうという意図を込めました。</p>
<p>チームの責務は</p>
<ul>
<li>メルペイ内のClient技術方針の検討, および規律の構築</li>
<li>メルカリグループで最適化されたArchitctureの構築</li>
<li>メルペイプロダクトチームへのベストプラクティスのインストール</li>
</ul>
<p>としており、プロダクトの成長に貢献すべく横断的な技術課題の解決に取り組んでいます。</p>
<p>現在は少人数の体制ですが日本語・英語話者が混在していて、チームの言語ポリシーはニュートラルになるように心がけています。例えば週次でのチームミーティングは週ごとにメインの言語を日本語と英語で切り替えるようにしています。メルカリグループには多様なメンバーがいるので、横断的なプロジェクトを進めるには言語も中立である必要があると考えています。</p>
<h2>Projects</h2>
<p>現在は中期のロードマップとして <strong>Zero Legacy & Group Optimized Architecture</strong>を掲げていくつかのプロジェクトを進めています。</p>
<p>1つめは認証基盤のアップデートです。これはメルカリグループ全体で推進しているプロジェクトで、アプリで使っている認証の仕組みの刷新に取り組んでいます。Mercari Mobile Architect チームリードのもと、Merpay Enabling Clientチームでは特に メルペイ関連の機能を提供するAPIとアプリのやりとり、およびアプリ内WebViewやiOSのApp Extensionsの認証方式のアップデートに取り組んでいます。</p>
<p>2つめはiOS/AndroidアプリのUI Frameworkのアップデートです。<br />
昨年メルカリアプリはGroundUP Appプロジェクトによってフルスクラッチで書き換わり、全面的にSwiftUI/Jetpack Composeの宣言的UI Frameworkで作られた内製のDesignSystemを採用しています。</p>
<ul>
<li><a href="https://mercan.mercari.com/articles/35887/" title="メルカリの事業とエコシステムをいかにサステナブルなものにするか?かつてない大型プロジェクト「GroundUp App」の道程">メルカリの事業とエコシステムをいかにサステナブルなものにするか?かつてない大型プロジェクト「GroundUp App」の道程</a></li>
<li><a href="https://mercan.mercari.com/articles/36183/" title="これからメルカリのエンジニアリングはもっと面白くなる──iOS&Androidのテックリードが振り返る、すべてがGo Boldだった「GroundUp App」">これからメルカリのエンジニアリングはもっと面白くなる──iOS&Androidのテックリードが振り返る、すべてがGo Boldだった「GroundUp App」</a></li>
</ul>
<p>メルペイの領域の機能についてはある程度ポータブルな設計になっておりプロジェクト進行中も並行して機能開発を続けていたことから、GroundUP App プロジェクト後の新アプリでも既存の機能はUIKit/Android Viewベースの技術スタックとなっていました。</p>
<ul>
<li><a href="https://engineering.mercari.com/blog/entry/20221213-ground-up-app/" title="メルカリアプリのコードベースを置き換える GroundUP App プロジェクトの話">メルカリアプリのコードベースを置き換える GroundUP App プロジェクトの話</a></li>
</ul>
<p>メルカリグループ全体での技術スタック統一とアプリ全体のユーザーエクスペリエンス統一を目指して、現在メルペイでも全社横断的に既存機能や新規開発機能へのDesignSystemの適用を進めています。私自身、本プロジェクトのリードを担当しており、全体の進捗管理やスケジューリング、 VPへのレポートなどプロジェクトの達成に向けて尽力しており、すでに新しいDesignSystemが採用された機能もいくつかリリースされています。<br />
新しいDesignSystemを適用することでSwiftUIやJetpack Composeといった宣言的UI Frameworkによる開発の恩恵に加えて、これまでは対応していなかったダークモードへの対応やアクセシビリティへの対応も容易になりました。まだ適用されていない機能もありますが、今後より適用率を高めていくことで最終的には全ての機能がマイグレーションされた状態を目指しています。</p>
<ul>
<li><a href="https://engineering.mercari.com/blog/entry/20231023-mmtf2023-day1-4/" title="【書き起こし】Merpay iOSのGroundUP Appへの移行 – kenmaz【Merpay &amp; Mercoin Tech Fest 2023】">【書き起こし】Merpay iOSのGroundUP Appへの移行 – kenmaz【Merpay & Mercoin Tech Fest 2023】</a></li>
<li><a href="https://engineering.mercari.com/blog/entry/20221018-mtf2022-day2-1/" title="【書き起こし】段階的Jetpack Compose導入〜メルペイの場合〜 – Junya Matsuyama【Merpay Tech Fest 2022】">【書き起こし】段階的Jetpack Compose導入〜メルペイの場合〜 – Junya Matsuyama【Merpay Tech Fest 2022】</a></li>
</ul>
<p>3つめはWeb Frameworkの更新です。<br />
メルペイではカスタマーサポート用のツールや加盟店さま向けのツール、各種キャンペーン用のページなどさまざまなWebサービスを運営しています。<br />
それらのWebサービスではVueとNuxt.jsをメインのFrameworkとして使っていますが、Vue2は2023年12月, Nuxt2は2024年6月にそれぞれサポート終了が計画されています。セキュリティ対策やブラウザの互換性を維持しながらプロダクト開発を継続するためには、End of Lifeまで次のバージョンにアップグレードする必要があり、既存サービスのVue3, Nuxt3への移行を進めています。<br />
移行後は各種サービス内のVue技術スタックの標準化や、メルカリグループの技術アセットを活用してReactのような他の技術も取り入れていくなど新しいチャレンジをしていきたいと思っています。</p>
<p>それ以外にもWebViewの最適化や新しいArchitectureへの移行など、いくつか横断的なプロジェクトを今後進めていく予定です。プロジェクトの進め方やプロジェクト内で得た技術的な知見については今後個別に紹介していく機会を設けていきたいと思っています。</p>
<h2>おわりに</h2>
<p>Merpay Enabling ClientチームではFintechドメインでの規律を保ちつつ、Mercari Mobile & Web Architectチームとも連携をしながら、Zero Legacy & Group Optimized Architectureを目指していきます。<br />
同じように横断的な技術課題の解決や生産性向上など開発全体を支援するチームをリードされている方の参考になれば幸いです。</p>
<p>明日の記事は同じチームの @kenmaz さんの “モダリティを考慮したiOSアプリのナビゲーションの再設計” です。引き続きお楽しみください。</p>
- 品質要件が厳しいLLMアプリケーションのトライアル評価を通じて得た知見https://engineering.mercari.com/blog/entry/20231216-06b3fe8d0f/https://engineering.mercari.com/blog/entry/20231216-06b3fe8d0f/<p>こんにちは。メルペイ Machine Learning エンジニアの@gucciです。 この記事は、Merpay Advent Calendar 2023 の16日目の記事です。 はじめに 2023年3月、OpenAI社 […]</p>
Sat, 16 Dec 2023 10:00:18 GMT<p>こんにちは。メルペイ Machine Learning エンジニアの@gucciです。<br />
この記事は、<a href="https://engineering.mercari.com/blog/entry/20231124-merpay-advent-calendar-2023/">Merpay Advent Calendar 2023</a> の16日目の記事です。</p>
<h2>はじめに</h2>
<p>2023年3月、OpenAI社がChatGPTを発表して以来、大規模言語モデル(LLM)の可能性に世界中が注目しています。企業や個人がLLMをどのように活用できるかを模索する中、実際にLLMを用いたプロダクトが市場に登場し始めています。メルカリグループでも、社内向け・プロダクト向けの両面でユースケースを探索してきました。</p>
<p>その一環として、7月に実施したぐげん会議<a href="https://mercan.mercari.com/articles/39144/">[1]</a>で入賞した返済相談チャットシミュレーターの一部分について、トライアルでオフラインの品質評価を実施しました。この記事では、その結果とそこから得られた学びについて共有します。</p>
<h2>品質評価における課題意識</h2>
<p>各種の学術試験やベンチマークテスト等、汎用的な知識・言語能力においてLLMが大きく進歩してきたことは疑いようがありません。一方で、LLMを用いたアプリケーションの品質に関する情報は、まだ十分に蓄積されていないと感じています。</p>
<p>OpenAI社によるGPT-4 Technical Report<a href="https://arxiv.org/pdf/2303.08774.pdf">[2]</a>や各種のベンチマークテストは参考になりますが、あくまでLLM本体の、汎用的な問題における評価結果です。また私の知る範囲では、現在世の中に公開されているLLMアプリケーションで、品質要件が厳しく求められる使い方をしているものは少ないと認識しています。</p>
<p>そのため、<strong>特に事実性・リスク(定義は後述)の面で一定の品質が要求されるドメイン向けのLLMアプリケーションを構築する場合、どの程度の品質が得られそうか</strong>について参考になる資料は少なく、未知数だと感じていました。</p>
<h2>問題設定</h2>
<p>この章では、今回のアプリケーションの問題設定について説明します。</p>
<p>システム全体像<img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/b7398584-pj-merco_スライド集.jpg" alt="システム全体像" /></p>
<p>ここでは返済相談チャットシミュレーターの一部分として、<strong>お客さまのお問い合わせに対して社内のドキュメントを参照しながら文章で回答を行う</strong>RAG(Retrieval-Augmented Generation)ベースのQ&Aアプリケーションを想定します。このユースケースでは、回答に一定の事実性が要求され、また<strong>回答次第で法令リスクに抵触してしまう可能性のある領域(以下、NG領域)</strong>が存在します。</p>
<p>なおRAGとは、LLMに参照させたいデータを事前に取り込んでindex化しておき、質問が入力された際にそこから関連するデータを検索してLLMに渡す仕組みのことです。</p>
<p>以下は、各構成要素の概要です。</p>
<ul>
<li>RAGパート
<ul>
<li>検索エンジン(VectorStoreIndex)
<ul>
<li>464件のドキュメント</li>
<li>LlamaIndexでシンプルにindex構築(chunk_size = 1024, separator = “。”)</li>
<li>indexのチューニングはあまり実施していません</li>
</ul>
</li>
<li>類似度検索
<ul>
<li>similarity(質問とドキュメント内容の類似度)で検索したうち<strong>上位1件</strong>を取得</li>
</ul>
</li>
</ul>
</li>
<p></p>
<li>回答生成パート
<ul>
<li>RAGで取得した情報をコンテキストとして、LLM(gpt-4)で質問に対する回答を生成</li>
<li>用語の定義等の基本的なドメイン知識をsystem promptに指定</li>
</ul>
</li>
<p></p>
<li>リスク防御パート
<ul>
<li>NG領域に関する回答を防ぐため、プロセス全体を通じて以下3層のリスク防御策を実装
<ul>
<li><strong>①input 防御(スコープ判定)</strong>: 入力された質問が対象スコープ内かをLLM(gpt-4)で判定し、対象外の場合は回答しない</li>
<li><strong>②prompt 防御</strong>: NG領域について回答しない旨の指示を回答生成のpromptに埋め込む</li>
<li><strong>③output 防御(回答添削)</strong>: 回答にNG領域の話題を含むかをLLM(gpt-4)でチェックし、該当部分の記述を削除する</li>
</ul>
</li>
</ul>
</li>
</ul>
<p>
また以下は、簡単なQ&Aのイメージです。</p>
<table>
<thead>
<tr>
<th>質問例</th>
<th>誤りを含まない回答例</th>
<th>誤りを含む回答(誤答)例</th>
</tr>
</thead>
<tbody>
<tr>
<td>メルカードで購入後の支払いはどうすればいいですか?</td>
<td>メルカードのご利用分は、以下3つの方法から選んでお支払いいただけます。</p>
<p>1. メルペイ残高での支払い<br />2. 銀行口座からの自動引き落とし<br />3. コンビニやATMでの支払い</p>
<p>より詳細を知りたい場合は、お気軽にお尋ねください。</td>
<td>例1. 誤った方法を案内</p>
<p>メルカードでご購入いただいた場合、購入した商品の代金が即時でメルペイ残高から引かれる形となります。</p>
<p>====<br />例2. 架空のアプリ操作方法を案内</p>
<p>メルカードのご利用分は、以下の手順でお支払いいただけます。</p>
<p>1. メルカリアプリを開く<br />2. マイページを開く<br />3. 「メルペイ」を選択<br />4. 「支払う」を選択</td>
</tr>
</tbody>
</table>
<h2>評価のアプローチ</h2>
<p>この章では、今回実施した評価のアプローチについて説明します。</p>
<h4>評価の目的</h4>
<p><strong>「そもそも世に出しても問題ないレベルか」という防御的な観点</strong>から、<strong>事実性・リスクの面で</strong>求められる品質水準が見込めそうかを評価することが主な目的です。</p>
<h4>評価観点</h4>
<p>ここでは評価の手法を網羅することが目的ではないため、基本的な考え方としてOpenAI社の評価観点を参考にしました。InstructGPTの論文<a href="https://arxiv.org/pdf/2203.02155.pdf">[3]</a>およびGPT-4 Technical Report<a href="https://arxiv.org/pdf/2303.08774.pdf">[2]</a>をまとめると、LLMの評価観点として以下が挙げられている理解です(学術試験等の能力評価は割愛。また各観点の説明は筆者理解)</p>
<ul>
<li><strong>有用性(helpfulness)</strong>
<ul>
<li>どれだけ質問者にとって有用な回答をしたか(≒課題を解決できたか)の評価</li>
</ul>
</li>
<p></p>
<li><strong>事実性(factuality)</strong>: 真実性(truthfulness)と言われることもある
<ul>
<li>質問に対して正しい回答ができるか(事実でない内容を回答してしまわないか)の評価</li>
<li>事実性には、<strong>参照データの質とその検索精度</strong>、および<strong>ハルシネーション</strong>が主に影響します。</li>
</ul>
</li>
<p></p>
<li><strong>リスク</strong>: 有害性(harmlessness)を含む
<ul>
<li>センシティブな領域または回答が許されない領域において望ましくない回答をしてしまうリスクおよび、過剰に拒否してしまう度合いの評価</li>
<li>リスクには、<strong>ハルシネーション</strong>および<strong>プロンプトインジェクション</strong>が主に影響します。</li>
</ul>
</li>
</ul>
<p>ハルシネーションとは、LLMが事実ではない内容を回答してしまう現象のことです。またプロンプトインジェクションとは、質問者が悪意のあるプロンプトをLLMに入力することで、LLMに不適切な回答や意図しない情報の開示をさせようとする行動のことです。この2つはLLMを使ったアプリケーション特有の点になります。</p>
<p><strong>評価の目的に照らして、今回は2点目の「事実性」と3点目の「リスク」の観点で評価した結果を紹介します。</strong>1点目は施策効果の観点では非常に重要ですが、今回は主に防御的な観点で評価したいため、除きます。<br />
この章の以降では、評価方法の詳細について説明していきます。(詳細が不要な方は飛ばし読みで大丈夫です)</p>
<h4>評価の前提</h4>
<p>今回の評価では、以下のことを前提としています。</p>
<ul>
<li>Q&Aの形式
<ul>
<li> 一連の会話のやり取りではなく、一問一答形式で評価しています(1度の問合せに複数の質問を含む場合もある)</li>
</ul>
</li>
<p></p>
<li>事実性とリスクは独立に評価
<ul>
<li> 事実性を評価する際、上述のリスク防御①〜③を入れない状態で評価しています。実際のプロダクションではリスク防御との組み合わせになりますが、今回は単体評価です。</li>
</ul>
</li>
<p></p>
<li>チューニングの度合いや評価件数について
<ul>
<li>今回、時間や人手の制約があったことと、特にリスク評価を優先して対応したことから、事実性評価のチューニングや人手評価の件数は限定的なものとなっています</li>
<li>特にRAGの検索精度がチューニング不足なところは理解していますが、得られた示唆に大きな影響は無いものと考えています</li>
</ul>
</li>
<p></p>
<li>人手評価か自動評価か
<ul>
<li>文章生成を定性的な基準で評価する際、厳密な評価は人手でなければ難しいです。今回は人手評価を信頼しつつ、参考として事実性評価でLLMを用いた自動評価も試してみました</li>
</ul>
</li>
</ul>
<h4>評価観点別のアプローチ詳細</h4>
<p>今回実施した事実性評価とリスク評価の詳細は、以下の比較表のとおりです。</p>
<table>
<thead>
<tr>
<th>切り口</th>
<th>事実性評価</th>
<th>リスク評価</th>
</tr>
</thead>
<tbody>
<tr>
<td>評価のポイント<br /> </td>
<td>お客さまの質問に対して<strong>誤った回答をしない</strong>こと</td>
<td>法令リスクに抵触してしまう可能性のある領域(NG領域)に関する回答を<strong>徹底的に排除しつつ、かつ答えて良い質問にはなるべく答える</strong>こと</td>
</tr>
<tr>
<td>評価用データ</td>
<td><strong>過去のQ&A事例100件</strong><br />(約20個のカテゴリーに関する質問)</p>
<p>※ただし、<strong>人手評価はこのうち30件のみ</strong>で実施</td>
<td><strong>答えてはいけない質問57件</strong><br /> ・ 左記のQ&A事例のうち、NG領域に関する15件<br /> ・ 敢えてNG領域を引き出す目的で今回作成した42件</p>
<p><strong>答えてよい質問80件</strong><br /> ・ 左記のQ&A事例のうち、答えてよい質問</td>
</tr>
<tr>
<td>評価指標</td>
<td>【人手評価】<br /><strong>誤答率(30件中)</strong>:<br />= 回答文の中に事実と異なる内容を1つでも含む回答の割合<br />= 事実と異なる内容を1つでも含む回答数/全回答数</p>
<p>【[参考] 自動評価(100件中)】<br /><strong>a. 質問に対して回答がどれだけ関連しているか</strong>(質問 vs 回答)<br /><strong>b. 質問に対して参照データがどれだけ対応しているか</strong>(質問 vs 参照)<br /><strong>c. 回答がどれだけ参照データに依拠しているか</strong>(回答 vs 参照)<br /><span style="color:gray">※今回は「正解の回答データ」を用意できず、誤答率の自動評価が難しかったため、上記の代理指標で評価して簡易的に傾向を確認(より詳細は後述)</span></td>
<td>【人手評価】<br /><strong>防御率(57件中)</strong>:<br />= 敢えてNG領域を引き出そうとする質問に対し、どれだけ回答を防げるか<br />= 回答を防げた質問の件数/答えてはいけない質問の件数</p>
<p><strong>阻害率(80件中)</strong>:<br />= 答えてよい質問をどれだけ誤って止めてしまうか<br />= 誤って回答を防いでしまった質問の件数/答えてよい質問の件数</td>
</tr>
</tbody>
</table>
<p></p>
<h4>補足:事実性の自動評価指標の詳細</h4>
<p>今回LLMを用いて実施した自動評価の評価基準は以下のとおりです。(Azure Machine Learningのメトリクスを一部参考にしました<a href="https://qiita.com/nohanaga/items/b68bf5a65142c5af7969">[4]</a>)。</p>
<table>
<thead>
<tr>
<th>指標名</th>
<th>評価基準の概要(5点満点)</th>
</tr>
</thead>
<tbody>
<tr>
<td>a. 質問に対して回答がどれだけ関連しているか(質問 vs 回答)<br /><span style="color:gray">※Azure MLではQnA Relevance Evaluationに相当</span></td>
<td>質問に対して過不足無く答えているほど点が高くなる。5点で完全に質問とマッチした回答。</td>
</tr>
<tr>
<td>b. 質問に対して参照データがどれだけ対応しているか(質問 vs 参照) <br /><span style="color:gray">※Azure MLの記事では特に該当無し</span></td>
<td>質問に対して参照データの充足性が高いほど点が高くなる。5点で全ての質問に答え得る参照データ。</td>
</tr>
<tr>
<td>c. 回答がどれだけ参照データに依拠しているか(回答 vs 参照) <br /><span style="color:gray">※Azure MLではQnA Groundedness Evaluationに相当</span></td>
<td>回答内容が参照データ内の事実にだけ基づいているほど点が高くなる。5点で完全に参照データ準拠。</td>
</tr>
</tbody>
</table>
<h2>評価結果と課題</h2>
<p>ここまでで、アプリケーションの問題設定と評価アプローチについて説明してきました。この章では今回の品質評価の結果をご紹介します。<br />
まず、今回の総評および取り組んで分かった課題についてまとめたうえで、各結果の詳細に触れていきます。</p>
<h4>サマリ:総評及び取り組んで分かった課題</h4>
<p>今回の評価結果を整理すると、以下のとおりになります。</p>
<table>
<thead>
<tr>
<th>切り口</th>
<th>事実性評価</th>
<th>リスク評価</th>
</tr>
</thead>
<tbody>
<tr>
<td>今回の結論<br /> </td>
<td>△(難しい or 開発・運用コスト大)</td>
<td>◯(十分な精度)</td>
</tr>
<tr>
<td>総評</td>
<td><strong>RAGで適切なドキュメントを参照できさえすれば、誤答はかなり抑えられるよう</strong>です。<br />しかし、下段に記載したような課題があり、<strong>安定的に適切なドキュメントを参照させ、回答品質を維持するには相応の開発・運用コストがかかる</strong>と思われます。</td>
<td>複数の防御を重ねることで、<strong>阻害を最低限に抑えながらほぼ100%近くNG領域の回答を防ぐことができ</strong>、良い精度が得られました(100%を保証できるわけではない)。<br />ユースケース次第ですが、<strong>人間が読んでも判別できるような限定的な領域が対象であれば、事実性と比べてリスクはより対処がしやすい</strong>と思われます。</td>
</tr>
<tr>
<td>課題(開発観点)</td>
<td><strong>複雑なコンテキストがある場合、similarity検索だけでは不十分</strong><br /> ・ similarityだけで必要なドキュメントを特定することは難しい<br /> ・ 多めに検索してLLMにどれを使うかを選ばせる、検索結果を別の手法で並べ替える等、何らかの追加的な機構が恐らく必要</p>
<p><strong>適切な参照データが無い場合の取り扱い</strong><br /> ・ 事実を問う質問の場合は答えないのが適切だが、そうでない場合(例. 挨拶、前の発言の確認など)も含めて一律で「回答しない」とするとコミュニケーションに齟齬が生じる<br /> ・ 一方で正しくない参照データでも回答させると、ハルシネーションを起こしやすくなる</p>
<p><strong>複数の質問の混在</strong><br /> ・ 一度に複数の質問をされた場合に、質問を分解する等の機構が恐らく必要</td>
<td><strong>ユースケースによって防御の難易度は変わる</strong><br /> ・ 例えばNG領域の判別が人間でも難しい場合や、細かいたくさんのNG領域がある場合は難易度が高くなる<br /> ・ OpenAI社のようにあらゆるリスクに対応するのは非常に難しい</p>
<p><strong>レスポンス速度への影響</strong><br /> ・ 防御策を重ねるほど、レスポンス速度が悪化する。防御精度とレスポンス速度のトレードオフの最適化は課題</td>
</tr>
<tr>
<td>課題(運用観点)</td>
<td><strong>ドキュメントの品質・網羅性</strong><br /> ・ 何もかもドキュメントがあるわけではないし、ドキュメントが常に最新であることを保証することも容易でない</td>
<td><strong>継続的なメンテナンス</strong><br /> ・ リリース後にうまく判別できない新しい質問が来たときに、漏れたものを後追いでpromptに追加していく運用が必要となる</td>
</tr>
</tbody>
</table>
<h4>事実性評価の詳細</h4>
<p>事実性評価で得られた結果は以下のとおりでした。</p>
<ul>
<li>人手評価
<ul>
<li><strong>誤答率(30件中):47%</strong>
<ul>
<li>間違った14件のうち、<strong>参照するドキュメントを間違えたものが10件</strong>、そもそも<strong>適切なドキュメントが無かったものが4件</strong>ありました
<ul>
<li>前者については、検索時に取得するドキュメント数を増やせば一定改善すると思われます(現状は上位1件)</li>
<li>ただし、正解ドキュメントが上位20件でも出てこないケースもあり、一筋縄ではいかなさそうです</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<ul>
<li>[参考] 自動評価(100件)
<ul>
<li>LLMによる評価結果は1件1件を見ると若干ブレがあるため、あくまで傾向値としてだけ参考にします</li>
</ul>
</li>
</ul>
<table>
<thead>
<tr>
<th>指標</th>
<th style="text-align: right;">平均評価値 (5点満点)</th>
<th>解釈</th>
</tr>
</thead>
<tbody>
<tr>
<td>a. 質問に対して回答がどれだけ関連しているか</td>
<td style="text-align: right;">4.9</td>
<td>質問に合わせて回答する能力は高水準<br />(これがハルシネーションの要因でもある)</td>
</tr>
<tr>
<td>b. 質問に対して参照データがどれだけ対応しているか</td>
<td style="text-align: right;">2.9</td>
<td>質問に対して適切な参照データを取れていないことが多い</td>
</tr>
<tr>
<td>c. 回答がどれだけ参照データに依拠しているか</td>
<td style="text-align: right;">1.8</td>
<td>bの結果として、参照データに依拠しない回答をする傾向が見られた</td>
</tr>
</tbody>
</table>
<h4>リスク評価の詳細</h4>
<p>リスク評価で得られた結果は以下のとおりでした。</p>
<table>
<thead>
<tr>
<th>防御パターン</th>
<th style="text-align: right;">防御率 (57件中)</th>
<th style="text-align: right;">阻害率 (80件中)</th>
</tr>
</thead>
<tbody>
<tr>
<td>①input + ②prompt</td>
<td style="text-align: right;">100%</td>
<td style="text-align: right;">4%</td>
</tr>
<tr>
<td>③output + ②prompt</td>
<td style="text-align: right;">98%</td>
<td style="text-align: right;">1%</td>
</tr>
<tr>
<td>全て(① + ② + ③)</td>
<td style="text-align: right;">100%</td>
<td style="text-align: right;">5%</td>
</tr>
</tbody>
</table>
<ul>
<li>各防御策の違い
<ul>
<li>①のinput防御は、<strong>防御率を高めやすい反面、答えてよい質問を誤って止めてしまう阻害が起きやすい</strong>傾向がありました
<ul>
<li>(参考までに、防御用promptをチューニングする前の初版では約70%の阻害が発生)</li>
</ul>
</li>
<li>③のoutput防御は、<strong>防御率と阻害率のバランスが良いですが、防御に若干不安が残ります</strong></li>
<li>なお、②のprompt防御は<strong>ほぼ効果無し</strong>でした
<ul>
<li>すでにsystem promptが長文(約1,500文字)であるため、追加の指示が効きづらかった可能性あり</li>
</ul>
</li>
</ul>
</li>
</ul>
<h2>まとめと知見</h2>
<p>今回まじめに品質評価に取り組んだことで、LLMおよびRAGの特性について理解が深まり、今後他のユースケースを考える際にも役立つ色々な学びを得ることができました。</p>
<p>最後に、今回のトライアル評価を通じて得たいくつかの知見をまとめます。<br />
<span style="color:gray">※あくまで一つのユースケースにおける、限られたチューニング範囲での評価結果に基づく私見です</span></p>
<ul>
<li><strong>「正解がある」 + 「複雑なコンテキスト」がある問題に対しての、RAG精度の限界</strong>
<ul>
<li>個別のユースケースにもよると思いますが、このような問題に対して十分なRAG精度を実現するためには開発・運用面で非常にコストがかかると思われます</li>
<li>検索結果のRerankやSelf-RAG<a href="https://arxiv.org/abs/2310.11511">[5]</a>のような工夫も出てきていますが、APIコストやドキュメント整備の大変さ等も加味すると、<strong>個人的にはLLMが本領発揮できるのは、むしろzero-shot〜few-shotで済むような複雑なコンテキストが要らない領域(例. 商品説明文からメタデータを抽出する)や、正解がない領域(例. エンタメ)なのではないか</strong>と感じています</li>
</ul>
</li>
<p></p>
<li>LLMプロジェクトの難しさ
<ul>
<li>本件は、ミッションクリティカル性が高めな領域、かつ既存の人の仕組みをリプレースするものであり、関係者が多かったり、法律が影響するものでありました</li>
<li>その上で、<strong>LLMは汎用性が高いゆえに、問題設定の絞り込みが難しい、あるいは多くの要件を織り込めてしまう特性</strong>があります。これは利点でもありますが、一方で広範な問題設定になるほど<strong>芋づる式に考慮すべき要素が増え、品質の担保が難しくなる</strong>と感じます</li>
</ul>
</li>
<p></p>
<li>人手評価の大変さ
<ul>
<li><strong>1件当たり評価に10-15分かかった</strong>
<ul>
<li>「事実かどうか」を確かめるには、回答文章の中でソースが必要な要素を抜き出した上で、各要素についてドキュメント等からソースを探す必要があります</li>
<li>もしくは、事実が頭に入っているドメインエキスパートが必要</li>
</ul>
</li>
<li>なお、評価用データに対して「正解の回答データ」を用意することができれば、LLMを用いてある程度は事実性を自動評価できるかもしれません</li>
</ul>
</li>
</ul>
<p>それでは、ここまで読んでいただきありがとうございました。<br />
明日の記事はtenlingpさんです。引き続きお楽しみください!</p>
<h2>参考文献</h2>
<p>[1] <a href="https://mercan.mercari.com/articles/39144/" title="LLMを活用してなにがつくれるか?——「ぐげん会議」開催から見えてきた、AI活用の新たな可能性">LLMを活用してなにがつくれるか?——「ぐげん会議」開催から見えてきた、AI活用の新たな可能性</a><br />
[2] <a href="https://arxiv.org/pdf/2303.08774.pdf" title="OpenAI (2023). GPT-4 Technical Report. ArXiv, abs/2303.08774.">OpenAI (2023). GPT-4 Technical Report. ArXiv, abs/2303.08774.</a><br />
[3] <a href="https://arxiv.org/pdf/2203.02155.pdf" title="Ouyang, L., Wu, J., Jiang, X., Almeida, D., Wainwright, C.L., Mishkin, P., Zhang, C., Agarwal, S., Slama, K., Ray, A., Schulman, J., Hilton, J., Kelton, F., Miller, L.E., Simens, M., Askell, A., Welinder, P., Christiano, P.F., Leike, J., & Lowe, R.J. (2022). Training language models to follow instructions with human feedback. ArXiv, abs/2203.02155.">Ouyang, L., Wu, J., Jiang, X., Almeida, D., Wainwright, C.L., Mishkin, P., Zhang, C., Agarwal, S., Slama, K., Ray, A., Schulman, J., Hilton, J., Kelton, F., Miller, L.E., Simens, M., Askell, A., Welinder, P., Christiano, P.F., Leike, J., & Lowe, R.J. (2022). Training language models to follow instructions with human feedback. ArXiv, abs/2203.02155.</a><br />
[4] <a href="https://qiita.com/nohanaga/items/b68bf5a65142c5af7969" title="Azure Machine Learning の Prompt flow の評価メトリクス紹介 ― ChatGPT どう評価する?">Azure Machine Learning の Prompt flow の評価メトリクス紹介 ― ChatGPT どう評価する?</a><br />
[5] <a href="https://arxiv.org/abs/2310.11511" title="Asai, A., Wu, Z., Wang, Y., Sil, A., & Hajishirzi, H. (2023). Self-RAG: Learning to Retrieve, Generate, and Critique through Self-Reflection. ArXiv, abs/2310.11511.">Asai, A., Wu, Z., Wang, Y., Sil, A., & Hajishirzi, H. (2023). Self-RAG: Learning to Retrieve, Generate, and Critique through Self-Reflection. ArXiv, abs/2310.11511.</a></p>
- Merpay Frontend のこれまでとこれから: 2023年版https://engineering.mercari.com/blog/entry/20231215-the-past-and-future-of-merpay-frontend-2023/https://engineering.mercari.com/blog/entry/20231215-the-past-and-future-of-merpay-frontend-2023/<p>こんにちは。メルペイのフロントエンドエンジニアの@tokuda109です。 この記事は、Merpay Advent Calendar 2023 の15日目の記事です。 Merpay Advent Calendar 202 […]</p>
Fri, 15 Dec 2023 10:00:08 GMT<p>こんにちは。メルペイのフロントエンドエンジニアの<a href="https://twitter.com/tokuda109">@tokuda109</a>です。<br />
この記事は、<a href="https://engineering.mercari.com/blog/entry/20231124-merpay-advent-calendar-2023/">Merpay Advent Calendar 2023</a> の15日目の記事です。<br />
Merpay Advent Calendar 2020 の「<a href="https://engineering.mercari.com/blog/entry/20201222-merpay-frontend/">Merpay Frontend のこれまでとこれから</a>」という記事で、メルペイのフロントエンドチームが2020年までに取り組んできたチーム組成やプロダクトの品質改善の話が紹介されました。(以下、前回の記事)<br />
早いもので前回の記事が公開されてから3年が経ち、当時からチームの状況は大きく変わり、チームメンバーの人数が半数以下になるという危機的状況も経験しました。<br />
この記事は、前回の記事の続編として、2020年以降にフロントエンドチームが取り組んできたことを紹介すると共に、危機的状況を乗り越えた経験から長期的に安定したチーム運営を行う上で重要だと感じたことを説明します。</p>
<h2>Merpay Frontend のこれまで</h2>
<h3>OKRの目標分類</h3>
<p>フロントエンドチームのこれまでを振り返る前に、OKR(四半期ごとに設定する目的とその筋道)の目標分類表を最初に紹介します。<br />
この表は、フロントエンドチームがこれまでに設定してきたチームOKRの目標(Objective)を、いくつかの区分に分類したものになります。これにより、フロントエンドチームがどのようなことに取り組んできたかを時系列で把握しやすくなります。<br />
フロントエンドチームのこれまでのOKRを振り返ってみて、以下の区分に分けることができました。分類した区分は長期的なチーム運営をする上で重要な要素になるため、後ほど詳しく説明します。</p>
<ul>
<li><code>採用</code>: 何人採用するといった具体的な採用活動や、社外への認知度をあげて採用につなげる活動</li>
<li><code>プロダクト品質</code>: フロントエンドチームで保守・運用しているプロダクトの品質(パフォーマンス、テスト、アクセシビリティ、セキュリティ)に関する取り組み</li>
<li><code>プロダクトリリース</code>: メルペイリリースやキャンペーン等のビジネス上の理由で開発完了時期が決まっている開発タスクの締切</li>
<li><code>生産性</code>: フロントエンドチームの生産性改善を目的としたタスクや、基盤技術を更新することで生産性の改善を図るもの。Nuxt.js / Vue.js のバージョン更新はここに含む</li>
<li><code>ロードマップ策定</code>: フロントエンドチームの長期的なロードマップを策定するための取り組み</li>
<li><code>チームビルド</code>: フロントエンドチーム内のコミュニケーション改善やチーム内連携の改善する取り組み</li>
</ul>
<p>目標分類表の見方を説明します。<code>目標1、2、3</code> の番号は優先度を指し、1の方がより重要な目標であることを意味します。また、<code>主な出来事 / 関連記事</code> には、その時期にフロントエンドチームに関係する重要な出来事やブログ記事、イベント登壇等の技術発表の情報を掲載しています。</p>
<table>
<caption style="text-align: center">目標分類表</caption>
<thead>
<tr>
<th style="font-size: 1.4rem;padding: 8px;min-width: 90px">四半期</th>
<th style="font-size: 1.4rem;padding: 8px;min-width: 120px">目標1</th>
<th style="font-size: 1.4rem;padding: 8px;min-width: 120px">目標2</th>
<th style="font-size: 1.4rem;padding: 8px;min-width: 120px">目標3</th>
<th style="font-size: 1.4rem;padding: 8px">主な出来事 / 関連記事</th>
<th style="font-size: 1.4rem;padding: 8px;width: 1em"></th>
</tr>
</thead>
<tbody>
<tr>
<td style="font-size: 1.2rem;padding: 8px">2018/07 – 09</td>
<td style="font-size: 1.2rem;padding: 8px;background-color:#EECDCD">採用</td>
<td style="font-size: 1.2rem;padding: 8px;background-color:#EECDCD">採用</td>
<td style="font-size: 1.2rem;padding: 8px;background-color:#DCE9D5">プロダクト品質</td>
<td style="font-size: 1.2rem;padding: 8px"></td>
<td style="font-size: 1.2rem;padding: 8px" rowspan="11">チーム組成期</td>
</tr>
<tr>
<td style="font-size: 1.2rem;padding: 8px">2018/10 – 12</td>
<td style="font-size: 1.2rem;padding: 8px;background-color:#A9C2F0">プロダクトリリース</td>
<td style="font-size: 1.2rem;padding: 8px;background-color:#EECDCD">採用</td>
<td style="font-size: 1.2rem;padding: 8px;background-color:#DCE9D5">プロダクト品質</td>
<td style="font-size: 1.2rem;padding: 8px">
<ul style="padding-left: 1.4rem">
<li style="line-height: 1.4"><a href="https://vuefes.jp/2018/" target="_blank" rel="noopener">Vue Fes Japan 2018</a> のスポンサーシップ</li>
</ul>
</td>
</tr>
<tr>
<td style="font-size: 1.2rem;padding: 8px">2019/01 – 03</td>
<td style="font-size: 1.2rem;padding: 8px;background-color:#A9C2F0">プロダクトリリース</td>
<td style="font-size: 1.2rem;padding: 8px;background-color:#FDF2D0">生産性</td>
<td style="font-size: 1.2rem;padding: 8px;background-color:#DCE9D5">プロダクト品質</td>
<td style="font-size: 1.2rem;padding: 8px">
<ul style="padding-left: 1.4rem">
<li style="line-height: 1.4">メルペイリリース (<a href="https://jp.merpay.com/news/2019/02/ios_release/" target="_blank" rel="noopener">iOS</a>, <a href="https://jp.merpay.com/news/2019/02/android_release/" target="_blank" rel="noopener">Android</a>)</li>
</ul>
</td>
</tr>
<tr>
<td style="font-size: 1.2rem;padding: 8px" rowspan="2">2019/04 – 06</td>
<td style="font-size: 1.2rem;padding: 8px;background-color:#FDF2D0">生産性 (安定運用)</td>
<td style="font-size: 1.2rem;padding: 8px;background-color:#EECDCD">採用</td>
<td style="font-size: 1.2rem;padding: 8px" rowspan="2"></td>
<td style="font-size: 1.2rem;padding: 8px" rowspan="2"></td>
</tr>
<tr>
<td style="font-size: 1.2rem;padding: 8px;background-color:#DCE9D5">プロダクト品質</td>
<td style="font-size: 1.2rem;padding: 8px;background-color:#D8D3E7">ロードマップ策定</td>
</tr>
<tr>
<td style="font-size: 1.2rem;padding: 8px" rowspan="2">2019/07 – 09</td>
<td style="font-size: 1.2rem;padding: 8px;background-color:#FDF2D0">生産性</td>
<td style="font-size: 1.2rem;padding: 8px;background-color:#EECDCD" rowspan="2">採用</td>
<td style="font-size: 1.2rem;padding: 8px" rowspan="2"></td>
<td style="font-size: 1.2rem;padding: 8px" rowspan="2"></td>
</tr>
<tr>
<td style="font-size: 1.2rem;padding: 8px;background-color:#DCE9D5">プロダクト品質</td>
</tr>
<tr>
<td style="font-size: 1.2rem;padding: 8px" rowspan="2">2019/10 – 12</td>
<td style="font-size: 1.2rem;padding: 8px;background-color:#FDF2D0">生産性 (DevOps)</td>
<td style="font-size: 1.2rem;padding: 8px;background-color:#EECDCD" rowspan="2">採用</td>
<td style="font-size: 1.2rem;padding: 8px" rowspan="2"></td>
<td style="font-size: 1.2rem;padding: 8px" rowspan="2"></td>
</tr>
<tr>
<td style="font-size: 1.2rem;padding: 8px;background-color:#DCE9D5">プロダクト品質</td>
</tr>
<tr>
<td style="font-size: 1.2rem;padding: 8px" rowspan="2">2020/01 – 03</td>
<td style="font-size: 1.2rem;padding: 8px;background-color:#FDF2D0">生産性 (CI/CD)</td>
<td style="font-size: 1.2rem;padding: 8px;background-color:#D8D3E7" rowspan="2">ロードマップ策定</td>
<td style="font-size: 1.2rem;padding: 8px" rowspan="2"></td>
<td style="font-size: 1.2rem;padding: 8px" rowspan="2">
<ul style="padding-left: 1.4rem">
<li style="line-height: 1.4"><a href="https://jp.merpay.com/news/2020/01/origami/" target="_blank" rel="noopener">Origamiからメンバージョイン</a></li>
<li style="line-height: 1.4">外国籍のメンバージョイン</li>
</ul>
</td>
</tr>
<tr>
<td style="font-size: 1.2rem;padding: 8px;background-color:#DCE9D5">プロダクト品質</td>
</tr>
<tr>
<td style="font-size: 1.2rem;padding: 8px">2020/04 – 06</td>
<td style="font-size: 1.2rem;padding: 8px;background-color:#A9C2F0">プロダクトリリース</td>
<td style="font-size: 1.2rem;padding: 8px;background-color:#FDF2D0">生産性</td>
<td style="font-size: 1.2rem;padding: 8px"></td>
<td style="font-size: 1.2rem;padding: 8px"></td>
<td style="font-size: 1.2rem;padding: 8px" rowspan="10">プロダクト品質改善期</td>
</tr>
<tr>
<td style="font-size: 1.2rem;padding: 8px">2020/07 – 09</td>
<td style="font-size: 1.2rem;padding: 8px;background-color:#DCE9D5">プロダクト品質 (E2E)</td>
<td style="font-size: 1.2rem;padding: 8px;background-color:#DCE9D5">プロダクト品質 (品質可視化)</td>
<td style="font-size: 1.2rem;padding: 8px"></td>
<td style="font-size: 1.2rem;padding: 8px"></td>
</tr>
<tr>
<td style="font-size: 1.2rem;padding: 8px">2020/10 – 12</td>
<td style="font-size: 1.2rem;padding: 8px;background-color:#DCE9D5">プロダクト品質 (E2E)</td>
<td style="font-size: 1.2rem;padding: 8px;background-color:#DCE9D5">プロダクト品質 (品質可視化)</td>
<td style="font-size: 1.2rem;padding: 8px;background-color:#F2CCA2">チームビルド (英語)</td>
<td style="font-size: 1.2rem;padding: 8px">
<ul style="padding-left: 1.4rem">
<li style="line-height: 1.4"><a href="https://engineering.mercari.com/blog/entry/20201207-cypress-testrail-frontend-e2e-automation/" target="_blank" rel="noopener">[Merpay Advent Calendar 2020]: Cypress + TestRail による Frontend E2E テストの効率化について</a></li>
<li style="line-height: 1.4"><a href="https://engineering.mercari.com/blog/entry/20201222-merpay-frontend/" target="_blank" rel="noopener">[Merpay Advent Calendar 2020]: Merpay Frontend のこれまでとこれから</a></li>
</ul>
</td>
</tr>
<tr>
<td style="font-size: 1.2rem;padding: 8px">2021/01 – 03</td>
<td style="font-size: 1.2rem;padding: 8px;background-color:#DCE9D5">プロダクト品質</td>
<td style="font-size: 1.2rem;padding: 8px;background-color:#F2CCA2">チームビルド (英語)</td>
<td style="font-size: 1.2rem;padding: 8px"></td>
<td style="font-size: 1.2rem;padding: 8px"></td>
</tr>
<tr>
<td style="font-size: 1.2rem;padding: 8px">2021/04 – 06</td>
<td style="font-size: 1.2rem;padding: 8px;background-color:#DCE9D5">プロダクト品質 (E2E)</td>
<td style="font-size: 1.2rem;padding: 8px;background-color:#FDF2D0">生産性</td>
<td style="font-size: 1.2rem;padding: 8px"></td>
<td style="font-size: 1.2rem;padding: 8px"></td>
</tr>
<tr>
<td style="font-size: 1.2rem;padding: 8px">2021/07 – 09</td>
<td style="font-size: 1.2rem;padding: 8px;background-color:#EECDCD">採用 (認知度向上)</td>
<td style="font-size: 1.2rem;padding: 8px;background-color:#DCE9D5">プロダクト品質 (セキュリティ)</td>
<td style="font-size: 1.2rem;padding: 8px"></td>
<td style="font-size: 1.2rem;padding: 8px">
<ul style="padding-left: 1.4rem">
<li style="line-height: 1.4"><a href="https://engineering.mercari.com/blog/entry/20211027-4f4045b053/" target="_blank" rel="noopener">Frontend Tech Talk 〜 Quality of Merpay Frontend 〜</a></li>
<li style="line-height: 1.4"><a href="https://speakerdeck.com/mercari/frontend-testing-cypress-as-a-testing-platform" target="_blank" rel="noopener">[Merpay Tech Fest 2021]: Frontend Testing: Cypress as a Testing Platform</a></li>
<li style="line-height: 1.4"><a href="https://engineering.mercari.com/blog/entry/20210914-fb8ff85b9a/" target="_blank" rel="noopener">[Merpay Tech Openness Month 2021]: Frontend E2Eテストの安定化の取り組み</a></li>
</ul>
</td>
</tr>
<tr>
<td style="font-size: 1.2rem;padding: 8px">2021/10 – 12</td>
<td style="font-size: 1.2rem;padding: 8px;background-color:#EECDCD">採用</td>
<td style="font-size: 1.2rem;padding: 8px;background-color:#DCE9D5">プロダクト品質</td>
<td style="font-size: 1.2rem;padding: 8px"></td>
<td style="font-size: 1.2rem;padding: 8px">
<ul style="padding-left: 1.4rem">
<li style="line-height: 1.4"><a href="https://engineering.mercari.com/blog/entry/20211208-test-automation-policy-in-merpay-frontend/" target="_blank" rel="noopener">[Merpay Advent Calendar 2021]: メルペイフロントエンドのテスト自動化方針</a></li>
<li style="line-height: 1.4"><a href="https://engineering.mercari.com/blog/entry/20211217-856683a917/" target="_blank" rel="noopener">[Merpay Advent Calendar 2021]: WebFrontendローカルパフォーマンス改善支援ツールを作ってみた。</a></li>
</ul>
</td>
</tr>
<tr>
<td style="font-size: 1.2rem;padding: 8px">2022/01 – 03</td>
<td style="font-size: 1.2rem;padding: 8px;background-color:#FDF2D0">生産性</td>
<td style="font-size: 1.2rem;padding: 8px;background-color:#DCE9D5">プロダクト品質 (セキュリティ)</td>
<td style="font-size: 1.2rem;padding: 8px"></td>
<td style="font-size: 1.2rem;padding: 8px">
<ul style="padding-left: 1.4rem">
<li style="line-height: 1.4"><a href="https://engineering.mercari.com/blog/entry/20220118-e040ab3dd3/" target="_blank" rel="noopener">メルペイフロントエンドチームで行っているパフォーマンス改善の取り組み紹介</a></li>
<li style="line-height: 1.4"><a href="https://engineering.mercari.com/blog/entry/20220209-7dbffabb5e/" target="_blank" rel="noopener">テスト・パフォーマンス・アクセシビリティ・セキュリティの4大品質に取り組むメルペイのフロントエンドチーム</a></li>
</ul>
</td>
</tr>
<tr>
<td style="font-size: 1.2rem;padding: 8px">2022/04 – 06</td>
<td style="font-size: 1.2rem;padding: 8px;background-color:#DCE9D5">プロダクト品質</td>
<td style="font-size: 1.2rem;padding: 8px"></td>
<td style="font-size: 1.2rem;padding: 8px"></td>
<td style="font-size: 1.2rem;padding: 8px"></td>
</tr>
<tr>
<td style="font-size: 1.2rem;padding: 8px">2022/07 – 09</td>
<td style="font-size: 1.2rem;padding: 8px;background-color:#DCE9D5">プロダクト品質</td>
<td style="font-size: 1.2rem;padding: 8px;background-color:#FDF2D0">生産性 (Nuxt/Vue移行)</td>
<td style="font-size: 1.2rem;padding: 8px"></td>
<td style="font-size: 1.2rem;padding: 8px">
<ul style="padding-left: 1.4rem">
<li style="line-height: 1.4"><a href="https://speakerdeck.com/mercari/tools-and-strategies-for-frontend-ui-libraries" target="_blank" rel="noopener">[Merpay Tech Fest 2022]: Tools and Strategies for Frontend UI Libraries</a></li>
</ul>
</td>
</tr>
<tr>
<td style="font-size: 1.2rem;padding: 8px" rowspan="2">2022/10 – 12</td>
<td style="font-size: 1.2rem;padding: 8px;background-color:#D8D3E7">ロードマップ策定</td>
<td style="font-size: 1.2rem;padding: 8px" rowspan="2"></td>
<td style="font-size: 1.2rem;padding: 8px" rowspan="2"></td>
<td style="font-size: 1.2rem;padding: 8px" rowspan="2"></td>
<td style="font-size: 1.2rem;padding: 8px" rowspan="6">チーム再組成期</td>
</tr>
<tr>
<td style="font-size: 1.2rem;padding: 8px;background-color:#FDF2D0">生産性 (Nuxt/Vue移行)</td>
</tr>
<tr>
<td style="font-size: 1.2rem;padding: 8px">2023/01 – 03</td>
<td style="font-size: 1.2rem;padding: 8px;background-color:#FDF2D0">生産性 (Nuxt/Vue移行)</td>
<td style="font-size: 1.2rem;padding: 8px"></td>
<td style="font-size: 1.2rem;padding: 8px"></td>
<td style="font-size: 1.2rem;padding: 8px"></td>
</tr>
<tr>
<td style="font-size: 1.2rem;padding: 8px">2023/04 – 06</td>
<td style="font-size: 1.2rem;padding: 8px;background-color:#FDF2D0">生産性 (Nuxt/Vue移行)</td>
<td style="font-size: 1.2rem;padding: 8px"></td>
<td style="font-size: 1.2rem;padding: 8px"></td>
<td style="font-size: 1.2rem;padding: 8px"></td>
</tr>
<tr>
<td style="font-size: 1.2rem;padding: 8px" rowspan="2">2023/07 – 09</td>
<td style="font-size: 1.2rem;padding: 8px;background-color:#FDF2D0">生産性 (Nuxt/Vue移行)</td>
<td style="font-size: 1.2rem;padding: 8px" rowspan="2"></td>
<td style="font-size: 1.2rem;padding: 8px" rowspan="2"></td>
<td style="font-size: 1.2rem;padding: 8px" rowspan="2">
<ul style="padding-left: 1.4rem">
<li style="line-height: 1.4"><a href="https://speakerdeck.com/mercari/improving-the-skill-test-evaluation-system-of-our-frontend-team" target="_blank" rel="noopener">[Merpay Tech Fest 2023]: フロントエンドチームのスキルテスト評価システム改善の取り組み</a></li>
</ul>
</td>
</tr>
<tr>
<td style="font-size: 1.2rem;padding: 8px;background-color:#FDF2D0">生産性改善 (ドキュメンテーション)</td>
</tr>
<tr>
<td style="font-size: 1.2rem;padding: 8px">2023/10 – 12</td>
<td style="font-size: 1.2rem;padding: 8px"></td>
<td style="font-size: 1.2rem;padding: 8px"></td>
<td style="font-size: 1.2rem;padding: 8px"></td>
<td style="font-size: 1.2rem;padding: 8px">
<ul style="padding-left: 1.4rem">
<li style="line-height: 1.4"><a href="https://vuefes.jp/2023/" target="_blank" rel="noopener">Vue Fes Japan 2023</a> のスポンサーシップ</li>
<li style="line-height: 1.4">組織体制がProgram組織に変わってチームOKRはなくなった (詳しくは後述)</li>
</ul>
</td>
</tr>
</tbody>
</table>
<p>目標を分類分けすることで、フロントエンドチームがこれまでに取り組んできたことを、時系列として次の3つの時期に分けることができました。</p>
<ul>
<li><code>チーム組成期</code>: 2018年〜2020年</li>
<li><code>プロダクト品質改善期</code>: 2020年〜2022年</li>
<li><code>チーム再組成期</code>: 2022年〜2023年</li>
</ul>
<p>この3つの時期のうち、<code>チーム組成期</code> と <code>プロダクト品質改善期</code> は前回の記事で詳しく書かれているため、この記事では内容を簡単に振り返るだけにします。</p>
<h3>チーム組成期</h3>
<p>チーム組成期は2018年から2020年1月〜3月期にあたります。目標分類表から2019年2月のメルペイリリース前後で取り組みが大きく変わってることが分かります。リリース前は、採用やメルペイリリースに向けた開発が目標として設定されています。一方、リリース後は採用の優先度が少し下がり、プロダクト品質や生産性の改善が目標として設定されています。</p>
<p>2020年1月〜3月期には、Origamiからフロントエンドチームにメンバーが合流し、外国籍のメンバーもジョインしました。フロントエンドチームに多様なメンバーが揃ったことが、次のプロダクト品質改善期につながります。</p>
<p>参考: <a href="https://jp.merpay.com/news/2020/01/origami/">株式会社Origamiのメルカリグループ参画に関するお知らせ</a></p>
<h3>プロダクト品質改善期</h3>
<p>チーム組成期を経て、フロントエンドチームとしてプロダクト品質の改善に取り組むことができる状況が整いました。フロントエンドチームがプロダクト品質の指標として掲げている <code>パフォーマンス</code>、<code>アクセシビリティ</code>、<code>テスト</code>、<code>セキュリティ</code> の4つの品質指標の改善に取り組んだのがプロダクト品質改善期になります。</p>
<p>プロダクト品質改善期には、日々の開発サイクルの中にプロダクト品質の検証をどのように組み込んだかや、指標改善の成果報告がブログ記事として数多く公開されたり、技術イベントで発表されました。</p>
<p>参考: <a href="https://engineering.mercari.com/blog/entry/20220118-e040ab3dd3/">メルペイフロントエンドチームで行っているパフォーマンス改善の取り組み紹介</a></p>
<h3>チーム再組成期</h3>
<p>ここからが前回の記事の続きの話になります。<br />
プロダクト品質の改善に数年取り組み、プロダクト品質を最低限保障する体制が構築できつつあるなか、徐々にフロントエンドチームのメンバーが少なくなりました。<br />
採用活動をしていましたが、チームを離れるメンバーの方が多く、最も少ない時でチームメンバーが最大人数の半数しかいない時期がありました。<br />
この人数で以前と同様にマイクロサービスを保守・運用をしていくことは極めて困難であり、この危機的状況を立て直しているのがチーム再組成期になります。</p>
<p>フロントエンドチームがこのような状況に陥ったのはなぜなのか。当時のフロントエンドチームの状況について振り返ってみました。</p>
<ul>
<li>ロードマップがなく、チームとしてどのようなことに取り組んでいくべきかの話ができていなかったため、個人の優先度に基づいた行動になっていた。</li>
<li>プロダクト品質の仕組みが大体完了した後、次の新しい目標を決めることができず、Flakyテストの修正といった改善系の作業を長期間やって精神的に疲弊した。</li>
<li>ドキュメンテーションの品質が低く、ナレッジの属人化が発生し、開発の生産性が低くなっていた。</li>
<li>自社の技術イベントやブログ記事以外の活動ができていなかった。技術コミュニティとの関わりや外部カンファレンスの登壇等、外部情報発信が不十分でメルペイのフロントエンドチームの社外認知度が低下していた</li>
<li>採用の評価基準が整備されておらず、安定した評価ができていなかった</li>
<li>メルペイリリースから数年経ち、プロダクト品質の改善も落ち着いてきて、次のキャリアを計画したり、新しい挑戦をすることを検討するメンバーが増えるタイミングだった。採用活動はしていたが、補うことはできていなかった</li>
<li>チームビルディング不足で、チームメンバーが基本自宅からの作業になって、Slack上で業務報告するだけの関係になっていた。技術的な会話や、その他雑談をすることもなくなっていた。</li>
</ul>
<p>ここに記載したものは、危機的状況に陥った原因として結びつけることができるものではありません。しかし、当時フロントエンドチームに対して課題を感じていたということは、チームとして解決しておくべきだったと言えることも事実です。</p>
<h2>長期的に安定したチーム運営をするために必要な取り組み</h2>
<p>先程の振り返りの内容を改めると、チームOKRと同じ分類を当てはめることができることに気がつきました。特に採用、生産性、プロダクト品質の区分に該当する目標は、これまでにチームOKRで何度も繰り返し設定されたものになります。それだけ<strong>採用、生産性、プロダクト品質は、チームとして定常的に取り組むことが重要である</strong>ことが分かります。</p>
<p>プロダクト品質に対する取り組みは、メルペイを使う多くのお客さまの体験に直接影響するため、放置するわけにはいきません。しかし、それと同時にプロダクト品質を担保するためのチームの <code>生産性</code> やチーム力の元となる <code>採用活動</code> や <code>チームビルド</code> も重要です。つまり、<code>ロードマップ</code> から導き出される中長期視点で、これらの取り組みをバランス良く計画的に行う必要があるということを示唆しています。</p>
<p>ロードマップを策定し、チームの将来のあるべき姿を示した上で、採用、生産性、プロダクト品質、チームビルドに取り組むことが持続可能なチームを運営するために必要不可欠なことだと改めて知ることができました。</p>
<p>次に各区分毎にフロントエンドチームとして取り組んだことを紹介します。</p>
<h3>ロードマップ</h3>
<p>危機的状況の改善に向けて2022年10月〜12月期の目標1でロードマップの策定と、Nuxt 3 / Vue 3への移行の長期的なスケジュールが計画されました。そして、それを支えるための採用活動が計画されました。その計画の元で新しいメンバーを採用することができ、フロントエンドチームは落ち着きを取り戻すことができました。現在は新しいメンバーと共に新しいフロントエンドチームを組成し、Nuxt 3 / Vue 3への移行作業をしています。</p>
<p>ただ、メルペイではProgram組織という新たな組織構造に移行するのに伴い、メルペイのフロントエンドチームという組織単位がなくなりました。また、Nuxt 3 / Vue 3への移行後の計画はまだ練られていないため、いずれ計画する必要があります。</p>
<h3>採用</h3>
<p>新たなフロントエンドチームを組成するために、採用では次のことに取り組んできました。</p>
<ul>
<li>書類選考の評価基準の整備</li>
<li>スキルテスト評価システムの改善
<ul>
<li><a href="https://engineering.mercari.com/blog/entry/20231023-mmtf2023-day1-7/">フロントエンドチームのスキルテスト評価システム改善の取り組み – Merpay Tech Fest 2023</a></li>
</ul>
</li>
<li>Vue Fes Japan 2023への参加
<ul>
<li><a href="https://vuefes.jp/2023/#sponsors">https://vuefes.jp/2023/#sponsors</a></li>
</ul>
</li>
</ul>
<p>フロントエンドチームはチーム組成期から継続して採用活動を行ってきました。しかし、Merpay Tech Fest 2023の発表でも述べたとおり、適正な評価を行う体制が整備されていなかったため、うまく新しいメンバーを迎えることができず、チーム力を維持できませんでした。書類選考の評価基準やスキルテスト評価システムが整備されたのが、2023年になってからです。</p>
<p>また、チーム組成期には活発に行われていた外部イベントへの登壇は少なくなりました。自分たちのチームのことで精一杯になるあまり、Vue Fes Japan Online 2022の開催にイベントが終わってから気づくありさまです。もう少し外部コミュニティへの関わりを増やしたいと思い、2023年からVue Fes Japan 2023にイベントスタッフとして参加したり、会社としてスポンサーになることを始めました。(2018年のVue Fes Japan 2018でスポンサーになっていましたが、復活させました)</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/f5e49fa0-img_1489-1024x768.jpg" alt="Vue Fes Japan 2023会場のクリエイティブウォール (筆者撮影)" /><br />
Vue Fes Japan 2023会場のクリエイティブウォール (筆者撮影): 壁面一番左にメルペイロゴを描きました</p>
<p>来年は自社ブログやイベント以外にも外部コミュニティに対する活動を増やしたいです。社外認知度を高めることで、メルペイのフロントエンドチームに興味を持ってもらえるようにしたいです。</p>
<h3>生産性</h3>
<p>フロントエンドチームが生産性の改善で取り組んだ内容は次のとおりです。</p>
<ul>
<li>モジュラーディレクトリ構成への移行
<ul>
<li><a href="https://engineering.mercari.com/blog/entry/20220518-aaa18f6b00/">Monorepo開発におけるツール選定 – Merpay Tech Talk</a></li>
<li><a href="https://engineering.mercari.com/blog/entry/20221206-a9b78f523f/">スケーラブルで保守性の高いモジュラーディレクトリ構成へのフロントエンドリポジトリ移行 – Merpay Advent Calendar 2022</a></li>
</ul>
</li>
<li>Nuxt 3 / Vue 3 への移行</li>
<li>GitHub IssuesとGitHub Projectsを使ったフロントエンドタスクの管理</li>
<li>GitHub ActionsでWorkflowの共有化</li>
<li>ドキュメントの整備
<ul>
<li>GitHub Discussionsを使ってADR(Architecture Decision Records)を残す</li>
<li>READMEフォーマットの統一</li>
<li>オンボーディング資料の整備</li>
</ul>
</li>
</ul>
<p>フロントエンドチームは利用パッケージの更新に課題を抱えていましたが、モジュラーディレクトリ構成への移行によって、以前よりはスムーズに行えるようになりました。そして、今はフロントエンドチーム総出で Nuxt 3 / Vue 3への移行作業をしています。この移行作業には、GitHub IssuesとGitHub Projectsを使って進行管理をしています。GitHub Issuesに登録したタスクをGitHub Projectsに登録し、1画面で全リポジトリの進捗を確認できるようにしています。</p>
<p>次にドキュメンテーションですが、以前はADRを記録していなかったため、口頭で議論された意思決定が残っておらず、過去の意思決定に対する振り返りコストがかかっていました。今はGitHub Discussionsを使って議論し、チームとして決定するプロセスで運用することにしました。CIにはGitHub Actionsを使っていて、ワークフローを再利用して一元管理をしています。</p>
<p>ここで紹介したように、基本的に開発で必要なツールは、GitHubで提供されている機能に極力寄せたことで、ツールを横断する時のフリクションを少なくしています。</p>
<h3>プロダクト品質</h3>
<p>今までに沢山の時間をかけてプロダクト品質の改善に取り組んできました。一部の不安定なテストの改善が必要であったりするものの、日々の開発サイクルの中で自動的に品質の検証が行われる体制を整えられました。具体的に言うと、ソース変更をプッシュするとテストやアクセシビリティの検証が実行され、全てパスするまでマージすることができません。セキュリティに関しても同様で、セキュリティ警告の通知を担当者が処理し、どのような対応をするべきかを主導します。改善するべきところはまだありますが、最低限の品質を保障する体制は構築できています。</p>
<h3>チームビルド</h3>
<p>チームビルドの取り組みは、前回の記事で紹介されている通り、チーム内コミュニケーションの英語化になります。2021年1月〜3月期を最後に、チームビルドが目標として設定されていません。しかし振り返りで課題として出されたように、フロントエンドチームのメンバー間のコミュニケーション量はリモートワーク前と比べて大分減りました。これについては何かしらのアクションが必要で、単に量を増やせばいい訳ではありません。フロントエンドチームが組成された当初から、フロントエンドチームのメンバーが週に1回集まって技術的なトピックについて話したり、その他雑談をするWebWednesdayというミーティングがありますが、再度仕組みの設計が必要になりそうです。</p>
<h2>Merpay Frontend のこれから</h2>
<p>Merpay Advent Calendar 2023の<a href="https://engineering.mercari.com/blog/entry/20231202-merpay-program-organization/">2日目の記事</a>で、@keigowさんからProgram組織についての紹介がありました。Program組織への移行は2022年10月から実施されましたが、フロントエンドチームは例外的に案件ごとにメンバーをアサインする形を当初取っていました。<br />
現在はチーム状況が改善したため、2023年7月からフロントエンドチームもProgram組織へ移行し、メルペイのフロントエンドチームという建て付けは存在しなくなりました。</p>
<p>Program組織への移行によって、各Programが担当するドメインを深く理解し、プロダクト開発に取り組むことができます。また、Enablingは組織横断で取り組む必要がある基盤技術へのオーナーシップを持っています。Nuxt 3 / Vue 3への移行はEnablingが主導し、デザインシステムや共通ライブラリの更新を行ったり、難易度の高い技術調査をサポートしてくれます。これによって、プロダクト開発と基盤技術刷新における役割が明確になりました。(フロントエンドエンジニアも所属しているEnablingのClientチームの取り組みは、<a href="https://engineering.mercari.com/blog/entry/20231218-what-the-merpay-enabling-client-team-aims-for/">Merpay Advent Calendar 2023の18日目の記事</a>で紹介されています。)</p>
<p>フロントエンドチームという建て付けはなくなり、フロントエンドチームとして設定するチームOKRはなくなりましたが、ロードマップ、採用、生産性、プロダクト品質、チームビルドはProgram組織への移行後も引き続き計画的に行っていく必要があると考えています。</p>
<h2>最後に</h2>
<p>この記事を書いた目的は、新しくフロントエンドチームにジョインしたメンバーに向けて、これまでのフロントエンドチームがやってきたことやフロントエンドチームの現在地を紹介するというのが半分、読者や社内のフロントエンドチーム外の方に向けては危機的状況から得られた知見を知ってもらうというのが半分になります。</p>
<p>長い記事になってしまいましたが、ここまで読んで頂きありがとうございます。<br />
明日の記事は@gucciさんです。引き続きMerpay Advent Calendar 2023をお楽しみください。</p>
- 多国籍メンバーで構成されたメルペイ決済基盤チームが言語の壁を突破するために取り組んだことhttps://engineering.mercari.com/blog/entry/20231213-9d14d5cde6/https://engineering.mercari.com/blog/entry/20231213-9d14d5cde6/<p>こんにちは。メルペイのPayment Coreチーム Engineering Managerの@abcdefujiです。 この記事は、Merpay Advent Calendar 2023 の13日目の記事です。 ダイバ […]</p>
Wed, 13 Dec 2023 10:00:39 GMT<p>こんにちは。メルペイのPayment Coreチーム Engineering Managerの<a href="https://twitter.com/_abcdefuji">@abcdefuji</a>です。<br />
この記事は、<a href="https://engineering.mercari.com/blog/entry/20231124-merpay-advent-calendar-2023/">Merpay Advent Calendar 2023</a> の13日目の記事です。</p>
<h2>ダイバーシティを推進するメルカリグループ</h2>
<p>メルカリグループは、ダイバーシティ&インクルージョンに価値を置いており、多様なバックグラウンドを持つメンバーの経験・知識・意見を結集し、一人ひとりがバリューを発揮できる組織を目指しています。<br />
参考: <a href="https://careers.mercari.com/di-statement/">Diversity & Inclusion Statement</a></p>
<p>今回は私たちPayment Coreチームが、どのように言語の壁を乗り越えダイバーシティ&インクルージョンを推進しやすい環境を作ったかを紹介します。</p>
<h2>Payment Coreチームについて</h2>
<p>私たちPayment Coreチームの責務は「決済基盤としてプロダクトチームに決済機能を提供し、プロダクト・サービスのミッション達成を実現する」です。<br />
2023年12月時点では、図のようにプロダクトに機能を提供しています。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/bff1aa60-component-1024x506.png" alt="" /></p>
<p>より詳細に決済基盤について知りたい方は以下の記事を参照してください</p>
<ul>
<li><a href="https://engineering.mercari.com/blog/entry/2019-06-07-155849/">マイクロサービスにおける決済トランザクション管理</a><br />
–<a href="https://engineering.mercari.com/blog/entry/20231023-mmtf2023-day3-6/">メルコイン決済基盤の実践話</a></li>
</ul>
<p>そしてPayment Coreチームは、多数の国籍を持つメンバーで構成されており、母国語が日本語でないメンバーが約半数を占めています。この多様なメンバーシップは、私たちのチームの力を高める一方で、日々の開発業務においてもコミュニケーションの課題が出てきました。</p>
<h2>高くて厚い言語の壁</h2>
<p>PaymentCoreチームでは異なる言語を話す人々が集まった際に、意思疎通を図る際に生じる障壁のことを<strong>言語の壁(Language barrier)</strong>と呼び、具体的には以下のような問題が発生しました。</p>
<h3>言語や文化の違いによるコミュニケーションの課題</h3>
<p>異なる国籍を持つメンバーが集まるチームでは、母国語が異なるため、コミュニケーションにおいて言語による壁が生じる可能性があります。それによりお互いの意思疎通が上手くいかないことによる認識の齟齬や、それによるパフォーマンスの低下に繋がる課題が潜在的に存在していました。<br />
また、言語の違いだけではなく、コミュニケーションのスタイルや表現の違いにより意図が正確に伝わらず誤解が生じることもあります。<br />
例えば、日本語のコミュニケーションでは間接的に意見を表現することがあると思います。これは日本語が母国語ではない人にとって意図を正確に理解することが難しくなります。</p>
<h3>技術用語や業界特有の言葉の理解の困難</h3>
<p>開発業務には特定の技術用語や業界特有の言葉が使用されることがありますが、言語の違いにより、それらの言葉の理解が困難になる可能性があります。特に決済に関連する法律用語や専門用語は代表的な例です。<br />
例えば、決済ドメイン中には「法定帳簿」「資金決済法」「管理会計」「オーソリ」「あと払い」等、英語話者にとって理解が難しい言葉に対して用語が統一されていない場合には、コミュニケーションコストが増大します。英語学習中の日本語話者にとっても同様です。決済の文脈の中で登場する「Payment」「Transaction」「Settlment」「Topup」「Payout」等の用語を正しく区別して理解するのは非常に困難です。</p>
<h3>言語の壁に直面して</h3>
<p>実際、私もこれらの課題を非常に痛感しました。私はmerpayの英語環境を理解した上で入社しましたが、最初の頃はミーティングでの英語の聞き取りがうまくできず、また、自分の意思を英語で表現することもできずに困ることがありました。当時のスピーキングスキルはほんの簡単な自己紹介がやっとで、非常にチャレンジングな環境でした。</p>
<p>そんな私のような英語学習者を含んだPaymentCoreチームがどのように言語の壁と付き合っているのかを紹介します。</p>
<h2>言語の壁との付き合い方</h2>
<p>まず、私たちチームでは英語を主体としてコミュニケーションしていますが、ポリシーとして<strong>特定の言語をメンバーに強制することはせず、それぞれの言語でもパフォーマンスが出せることを理想</strong>としています。<br />
その実現のためにメルカリに存在するさまざまなサポートを活用しながら日々の業務に当たっています。</p>
<p>細かいツールやtipsの話は沢山ありますが、今回は以下の4つを紹介したいと思います。</p>
<h3>Global Operation Teamによる通訳・翻訳のサポート</h3>
<p>言語の壁の中で最も苦労したのは、リアルタイムのコミュニケーションでした。オンライン会議ツールの字幕機能などもありますが、不慣れなメンバーにとってリスニングとスピーキングは最初の大きな壁でした。それを解決してくれたのはGlobal Operation Team(以下、GOT)です。<br />
メルカリグループにはGOTというチームが存在します。GOTは主に翻訳と通訳の二つの職務を担当してくれているチームであり、私たちのチームは主に通訳でのサポートをしていただいております。GOTのおかげで言語が異なる場合でも会議中のコミュニケーションの橋渡しを実現してくれております。</p>
<p>参考: <a href="https://mercan.mercari.com/articles/37731/">言語を活用してメルカリのビジネスやD&Iをサポート!──Global Operations Teamが提供する通訳・翻訳業務以上の価値</a></p>
<h3>Slack上でのコミュニケーションの自動翻訳</h3>
<p>リアルタイムではないコミュニケーションだとしても問題は存在していました。Slack上で複数の言語(日本語と英語)でコミュニケーションを行う場合、メンバーによっては都度翻訳ツールを利用する必要があり、コミュニケーションに小さなストレスが生じることがありました。<br />
そのため、私たちはZapierを用いた自動翻訳ツール(JP <-> EN)を導入しました。</p>
<p>Zapierは複数のアプリ(Webアプリケーション)を連携させてワークフローを作り、業務を自動化させることができるツールです。<br />
WebUI上からアカウント連携・ワークフロー作成ができるため、ノンプログラマーでも簡単に使うことができます。<br />
<a href="https://zapier.com/l/home" title="https://zapier.com/l/home">https://zapier.com/</a></p>
<p>このツールを利用することで、言語を自動的に判定し、翻訳結果をSlackのThreadに投稿することが可能です。これにより、どちらの言語でも気軽に投稿できるようになり、事前に翻訳を用意したり、母国語以外のコミュニケーションへのハードルを下げることができるようになりました。<br />
以下のように自動的に翻訳が投稿されます。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/5277d57f--2023-12-12-21.05.52.png" alt="" /></p>
<p>Zapierでは複数のアプリを連携させて作ったワークフローの単位を「Zap」と呼びます。<br />
実際に今回のZapを簡単にご紹介します。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/6a147eb4--2023-12-12-21.06.42.png" alt="" /></p>
<ol>
<li>Slackの投稿をトリガーする(図内 1)</li>
<li>翻訳対象の選別のために投稿のフィルタリングを行う(図内 2,3)</li>
<li>言語の特定(図内4)</li>
<li>翻訳(図内 7, 11)</li>
<li>Slackへ投稿(図内 9,13)</li>
</ol>
<p>まだまだフィルタリング機能が不十分な点等改善点はありますが、現在社内の複数のチャンネルで利用されています。</p>
<h3>言語学習プログラム</h3>
<p>GOTによる翻訳サポートなど、さまざまなサポートがメルカリグループには存在していますが、メンバー自身の言語スキルが向上しなければパフォーマンスを向上していくことは困難です。メルカリグループでは業務の必要性に応じて言語学習プログラムに参加することができます。私を含めた一部Payment Coreチームメンバーは外部のオンライン英会話練習プログラムや社内の言語学習プログラム(日本語/英語)を受講し、それぞれの言語に対しての習熟度/理解度を高めています。<br />
チーム内のミーティングにおいても週一で日本語でコミュニケーションする日を作る等、日々学習プログラムを通してInputしたものをOutputする機会もチームの中に存在しています。</p>
<h3>チームメンバー同士の文化の理解・尊重</h3>
<p>異なる国籍を持つメンバー同士がお互いの文化を理解し、尊重することも重要です。<br />
お互いに完璧な英語や日本語を話せるようになることを求めるのではなく、お互いのことを理解するというコミュニケーションの本質を大切にし相手に合わせてコミュニケーションできるように努めることが私たちの考え方です。<br />
例えば、意識的に「英語学習者にとって難しい英語」や「日本語学習者にとって難しい日本語」を使わずにコミュニケーションする事はとても有益な方法です。<br />
参考: <a href="https://careers.mercari.com/jp/language/">やさしいコミュニケーション</a></p>
<h2>Payment Coreチームの成長と変化</h2>
<p>上記サポートを活用する事で結果として、Payment Coreチームではいくつか変化が起こりました。</p>
<h3>言語習熟度の成長</h3>
<p>日々のコミュニケーション + 言語学習プログラムの結果、Payment CoreチームのCEFRの定義(後述)に基づいた言語習熟度が格段に向上しました。<br />
私自身も英語に関してA2レベル(サポートがあれば会話ができる)からB2レベル(自分の仕事に関する会話が支障なくできる)まで向上しました。もちろんペラペラに話せるわけではないですが、簡単な自己紹介が出来る程度のレベルからSlack上、 オンライン、 オフラインの場で普段の業務に関するトピックに関して英語でなんとかコミュニケーションする事ができるレベルまで成長する事ができました。</p>
<h4>CEFR定義について(引用元: <a href="https://careers.mercari.com/jp/language/">https://careers.mercari.com/jp/language/</a>)</h4>
<table>
<thead>
<tr>
<th>レベル</th>
<th>定義</th>
<th>英語または日本語を使ってできること</th>
</tr>
</thead>
<tbody>
<tr>
<td>Basic</td>
<td>(CEFR – A2)</td>
<td>– 会話する相手のサポートや、より簡単な言葉への言い換えがあれば、自分の専門分野において、基本的なやりとりができる <br /> – マネージャーと1-on-1ミーティングをする際、相手からサポートしてもらいながらミーティングすることができる <br /> – 相手からサポートしてもらいながら、同僚と1対1で仕事に関する簡単な意見交換や雑談ができる</td>
</tr>
<tr>
<td>Independent</td>
<td>(CEFR – B2)</td>
<td>– 会話する相手からのサポートや、より簡単な言葉への言い換えがほぼなくても、自分の専門分野において複雑な情報のやりとりができる <br /> – 言語がコミュニケーションの妨げになることなく、1-on-1ミーティングができる <br /> – 自分の専門分野において、母語話者を含む複数名での議論に参加することができる</td>
</tr>
<tr>
<td>Proficient</td>
<td>(CEFR – C1)</td>
<td>– 会話する相手からのサポートや、より簡単な言葉への言い換えがなくても、自分の専門分野内外の複雑な情報のやりとりを自立して行うことができる <br /> – 抽象的な話題や不慣れな分野でも、複数名の議論に参加できる</td>
</tr>
</tbody>
</table>
<p>※CEFRに関する詳しい情報は<a href="https://www.coe.int/en/web/common-european-framework-reference-languages/table-1-cefr-3.3-common-reference-levels-global-scale">こちら</a> (外部リンク:Council of Europe)</p>
<h3>円滑で迅速なコミュニケーション</h3>
<p>チームの言語習熟度が向上した事でGOTによる通訳サポートが不要になりました。<br />
これにより、緊急もしくは即席のミーティングを通訳サポートなしで開催できる点がチームのコミュニケーションスピードを向上させる事につながりました。</p>
<p>さらに、同僚と気軽にちょっと話したい時にすぐ会話できる点・自身の言いたいことを表現できる点はチームの雰囲気自体を明るくする事にもつながりました。具体的には、会話中の沈黙はほとんどなくなりました。もし会話がわからなければ、わかる箇所からブレイクダウンしてコミュニケーションしていく方法を多くのメンバーがチームの成長と共に学んでいきました。</p>
<p>また、チーム内だけではなく、チーム外のコミュニケーションとしても英語/日本語を使えるようになる事でチームとしての可能性も広がりました。</p>
<h3>メンバーのキャリア創出の可能性</h3>
<p>メンバーそれぞれの言語習熟度の向上によって、社内以外での活動にもつながりました。<br />
例えば、一部チームメンバーは海外のカンファレンスに参加し、そこで得たInputをコミュニティで発表する等、活躍の場を広げてくれています。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/2e7c9266--2023-12-12-23.27.10-780x1024.png" alt="" /><br />
(GopherCon 2023 in San Diego)</p>
<p>Shunta KomatsuさんによるGoコミュニティへの貢献<br />
<a href="https://speakerdeck.com/iamshunta/recap-the-future-of-json-in-go">https://speakerdeck.com/iamshunta/recap-the-future-of-json-in-go</a></p>
<p>このように外国語のスキルを磨くことはチームのパフォーマンスを上げるだけではなく、メンバーの将来におけるキャリアの幅を広げていく事にもつながる可能性があると考えています。</p>
<h3>変化は簡単には起こらない</h3>
<p>このような変化が起こりましたが、もちろん容易にかつ即座に達成したわけではありません。PaymentCoreチームは半年以上もの時間を費やしてきました。そしてまだまだ理想的な環境とは言えない状況です。今後も包括的な環境を構築し続けることが不可欠です。</p>
<h2>ダイバーシティの力を活かして</h2>
<p>言語の壁は私たちにとって挑戦でありながら、同時に成長の機会でもあります。異なる言語や文化を持つメンバーが集まることで、さまざまなアイデアや視点が生まれ、よりクリエイティブな問題解決が可能になります。</p>
<p>私たちのチームは、言語の壁を乗り越えるための努力を惜しまず、お互いを尊重しながら協力しています。そして、多様なバックグラウンドを活かし、より良いプロダクトを提供するためにこれからも取り組み続けていきます。</p>
<p>明日の記事は @ntkさんです。引き続きお楽しみください。</p>
- チームワークと効率向上のカギ!メルカリが成功する大人数iOS開発のための手法とは?https://engineering.mercari.com/blog/entry/20231211-large-team-development-at-mercari-ios/https://engineering.mercari.com/blog/entry/20231211-large-team-development-at-mercari-ios/<p>こんにちは。メルカリ iOSエンジニアの@saeです。この記事は、Mercari Advent Calendar 2023 の11日目の記事です。 私は株式会社メルカリに入社してから早6ヶ月が経ちましたが、日々の業務を通 […]</p>
Mon, 11 Dec 2023 11:00:36 GMT<p>こんにちは。メルカリ iOSエンジニアの<a href="https://www.linkedin.com/in/saenuruki/" title="@sae">@sae</a>です。この記事は、<a href="https://engineering.mercari.com/blog/entry/20231124-mercari-advent-calendar-2023/" title="Mercari Advent Calendar 2023">Mercari Advent Calendar 2023</a> の11日目の記事です。</p>
<p>私は株式会社メルカリに入社してから早6ヶ月が経ちましたが、日々の業務を通じて、さまざまな技術の素晴らしさに感銘を受けています。</p>
<p>その中でも特に驚くべきことは、大多数のiOSエンジニアが在籍している大規模なチームが、一つのプロジェクトに携わりながら、円滑に開発が進んでいることです。これまでに私は6つの企業で働いてきましたが、どの組織も最大でも5人のiOSエンジニアがアプリ開発に関与しており、プロジェクトファイルやXcodeのバージョンなどの問題がある場合でも、直接のコミュニケーションを通じて解決してきました。</p>
<p>果たして、メルカリは大規模なiOSエンジニアチームが円滑な開発を行うためにどのような取り組みをしているのでしょうか。私が感銘を受けた様々な観点をTipsとしてご紹介したいと思います。</p>
<h1>マイクロモジュール化</h1>
<p>現在、メルカリ iOSアプリには数百以上の非常に多数のモジュールが存在しています。各画面ごとにモジュールが独立しており、依存関係なしに動作します。さらに、各機能も1機能につき1モジュールとなっており、複雑なロジックは複数のモジュールから成り立っています。</p>
<p>このようなマルチモジュールアーキテクチャにより、依存関係が明確になり、他のチームへの影響度も把握しやすくなっています。マルチモジュールアーキテクチャは、さまざまな現場で実施されている手法ですが、メルカリでは徹底的に細分化されているため、大規模な開発チームにおいて、そのメリットがより明確に実感できます。<br />
<img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/931703a9-module_dependencies_image.png" alt="" /><br />
さらに、<a href="https://engineering.mercari.com/blog/entry/20221215-16cdd59909/" title="メルカリ iOSアプリではBazelを使用">メルカリ iOSアプリではBazelを使用</a>しています。Bazelは更新のないモジュールを再ビルドする必要がないため、効率的な開発を支援しています。また、既に成功したテストも再度実行する必要がないため、検証のスピードも向上しています。さらに、一度ビルドしたモジュールは他の開発者が再ビルドする必要がないため、リソースの無駄を防ぎます。</p>
<p>マルチモジュールアーキテクチャとBazelの積極的なキャッシュ機能により、メルカリのiOSエンジニアは大規模な開発プロジェクトを円滑に進めることができており、チーム全体の生産性向上に貢献しています。</p>
<p>ただし少人数の開発チームや変更の多いスタートアッププロジェクトでは、必ずしも生産性が向上するとは限りません。マルチモジュール化は管理コストが増加する傾向がありますので、大規模な開発プロジェクトならではの非常に大きな恩恵を実感しました。</p>
<h1>コードオーナーによる品質管理</h1>
<p>メルカリでは、Ready for ReviewにPRを設定すると、自動的に適切なレビュワーがコードオーナーの設定に基づいて割り当てられ、レビュー作業を委ねることができます。もしPRがアーキテクチャの変更やグループ企業の機能に広範な影響を及ぼす場合は、専任のArchitectチームが影響を確認します。</p>
<p>メルカリは大規模な組織ですが、各担当箇所に責任を持つコードオーナーが存在し、品質管理を徹底することで、高品質なコードの提供と開発プロセスの円滑化を実現しています。コードオーナーからの適切なフィードバックは、開発者に貴重な指摘や改善アドバイスを提供し、より高水準な開発を促進します。また、コードオーナーの存在は他のチームとの連携をスムーズに行い、プロジェクト全体の一貫性と効率性を向上させることができます。この取り組みにより、大規模な開発環境でもチームワークと品質管理を重視し、優れた開発成果を生み出しています。</p>
<p>小規模なチームでは、通常1人がテクニカルリードを担当しますが、例え少人数でも全員が個々の機能についてコードオーナーであり、責任を持つ仕組みは様々な現場で有効だと感じます。</p>
<h1>トランクベース開発</h1>
<p>メルカリでは、トランクベース開発という手法を採用しています。この開発手法では、開発者がプロジェクトごとにフィーチャーブランチを作成して機能を追加するのではなく、機能ごとにメインブランチである「トランクブランチ」に対してPull-Requestを作成していきます。そのため、機能ごとのPull-Requestは明確な内容となり、レビュー時間の短縮や他のブランチとの衝突の回避が可能となります。</p>
<p>トランクブランチは常にリリース可能な状態を保つため、開発中の機能は<a href="https://youtu.be/DrFsm7IagAE?feature=shared" title="フィーチャーフラグを使用">フィーチャーフラグを使用</a>して非表示にし、ユーザーに早期に公開されないようにします。そのためには、回帰テストを充実させることが重要です。十分な自動テストを実施することで、堅牢なトランクブランチを維持し、開発者は既存の機能への影響を考慮しながら新しい機能をトランクブランチにマージすることができます。<br />
<img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/815adbad-trunkbased_development_image.png" alt="" /><br />
この開発手法により、コードの品質と安定性を保ことができ、メルカリのiOSエンジニアは迅速かつ効率的に開発を進めることができます。また、フィーチャーフラグを使用して機能をリモートで制御するため、公開されているアプリに問題があった場合でも、次のバージョンのリリースを待たずに機能を無効化することができるなど、様々な恩恵があります。</p>
<h1>FormatterやLinterによる確認の自動化</h1>
<p>メルカリでは、<a href="https://danger.systems/swift" title="Danger">Danger</a>を使用して自動的にコード修正を行います。FormatterやLinterによる確認は、新しい開発者がプロジェクトに参加する際に役立ちます。新しい開発者はメルカリのコードベースにすばやく適応することができ、コードの統一性を保つことができます。</p>
<p>また、開発者は記述手法の統一を心配する必要がなくなり、より高度な開発タスクに集中することができます。これにより、開発者はより効率的にプロジェクトを進めることができ、最終的には高品質なアプリケーションを提供することができます。</p>
<h1>まとめ</h1>
<p>今回ご紹介した手法は、メルカリのiOS開発における一部に過ぎませんが、非常に多くの効率化の仕組みや自動化のテスト、そして多くの優秀なエンジニアのアウトプットに触れる日々は、私にとって非常に刺激的でワクワクが止まりません。</p>
<p>今回の記事では、メルカリの先輩エンジニアたちが築いてきたノウハウや開発手法を、大人数での開発の観点で、様々な方々に参考になるようにまとめさせていただきました。</p>
<p>引き続き来年のAdvent Calendarに向けて、私自身がメルカリで挑戦した記録を記事にしていけるよう頑張りますので、どうぞお楽しみにしていてください。メルカリでの成長や新たなチャレンジについて、皆さんに共有できることを心から楽しみにしています。</p>
- Cypress + Gmail APIでメール+SMSの2FA認証をテスト自動化する(気合&パワー)https://engineering.mercari.com/blog/entry/20231208-38f8f2075b/https://engineering.mercari.com/blog/entry/20231208-38f8f2075b/<p>こんにちは!QA Engineerの@fukutomiです。 この記事は、Merpay Advent Calendar 2023 の11日目の記事です! メルカリエンジニアリングブログに寄稿するのは初めてなので緊張します […]</p>
Mon, 11 Dec 2023 10:00:44 GMT<p>こんにちは!QA Engineerの<a href="https://twitter.com/FukuromiQA" title="@fukutomi">@fukutomi</a>です。<br />
この記事は、<a href="https://engineering.mercari.com/blog/entry/20231124-merpay-advent-calendar-2023/" title="Merpay Advent Calendar 2023">Merpay Advent Calendar 2023</a> の11日目の記事です!<br />
メルカリエンジニアリングブログに寄稿するのは初めてなので緊張しますが、よろしくお願いします。</p>
<h2>はじめに(この記事はなんなのか)</h2>
<p>今回のテーマは、弊社が運営している<a href="https://moments.pacificleague.com/" title="パ・リーグ Exciting Moments β">パ・リーグ Exciting Moments β</a>(略してPEM)におけるログイン処理をテスト自動化してみよう、です。</p>
<blockquote>
<p>※パ・リーグ Exciting Moments βとは<br />
「パ・リーグ Exciting Moments β」は、パ・リーグ6球団の記憶に残る名場面やメモリアルシーンを捉えた動画コンテンツを自分だけのコレクションとして保有できるパ・リーグ6球団公式のサービスです。</p>
</blockquote>
<p>PEMはログインしないと大抵の機能が利用できず、テスト自動化をしたいならログイン処理の突破は必須。。。<br />
後述する通りPEMのログイン処理は結構複雑なのですが、気合&パワーでなんとか実装したので、よかったら見てやってください。</p>
<h2>PEMのログイン構造</h2>
<p>最初にPEMのログイン処理について簡単に説明します。<br />
PEMはE-mailとSMSの2要素認証(2FA)を採用しています。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/0f4c71da-drawio.png" alt="PEMのログイン構造" /></p>
<p>お客さまが行う作業としては</p>
<ol>
<li>ログイン画面でE-mailアドレス入力</li>
<li>サービスからメールが届くので、メール内のリンクを開く</li>
<li>リンクを開くと登録されている電話番号にSMSが届く</li>
<li>同時にSMS認証番号入力画面を開くので、SMSに記載されている認証番号を入力</li>
<li>(場合によってはここでreCAPTHA認証が入りますが、テスト環境では表示しない設定にしているので割愛)</li>
<li>ログイン完了!</li>
</ol>
<p>こんな形で結構複雑でして、今回はこれをCypress+Gmail APIですべて自動化しよう、という話です。</p>
<h2>どんな仕組みで自動化するのか</h2>
<p>今回はPEMを自動で動かすツールとしてCypressを、GmailにアクセスするためにGmail APIを、またテスト用電話番号の準備のためFirebaseを利用します。<br />
上記ログイン構造のお客さまが行う作業をもとに、下記の感じで自動化してみます。</p>
<ol>
<li>CypressでPEMのログイン画面を開く</li>
<li>ログイン画面でE-mailアドレスを入力して送信</li>
<li>サービスからメールが届くので、GoogleにログインしGmail APIを利用してメールを検索</li>
<li>メール本文からログイン用のリンクを抜き出す</li>
<li>抜き出したリンクをCypressで開く</li>
<li>SMS認証番号入力画面に遷移するので、あらかじめFirebaseで設定しておいたテスト用電話番号の確認コードを入力</li>
<li>ログイン完了!</li>
</ol>
<p>それでは実際にやってみましょう!</p>
<h2>下準備</h2>
<h3>Cypressのインストール</h3>
<p>まずは下記を参照にCypressをインストールしましょう。</p>
<blockquote>
<p>※Cypressとは<br />
ウェブアプリケーションをフロントエンドで自動で動かすことができる、オープンソースソフトウェアのテストツールです。</p>
</blockquote>
<p>(詳細は本題から逸れちゃうので割愛します)<br />
<a href="https://docs.cypress.io/guides/getting-started/installing-cypress" title="Installing Cypress - Cypress.io">Installing Cypress – Cypress.io</a><br />
<a href="https://docs.cypress.io/guides/getting-started/opening-the-app" title="Opening the App - Cypress.io">Opening the App – Cypress.io</a></p>
<h3>GCPの準備</h3>
<p>次にGCPのプロジェクトを作成します。<br />
こちらも本題から逸れるので割愛!<br />
<a href="https://cloud.google.com/resource-manager/docs/creating-managing-projects?hl=ja" title="プロジェクトの作成と管理 - Google Cloud">プロジェクトの作成と管理 – Google Cloud</a></p>
<h3>Gmail APIの準備とid,secretの確認</h3>
<p>GCPのプロジェクトを作成したら、次はGmail APIを準備します。</p>
<ol>
<li>サイドメニューから「APIとサービス」を選択、画面遷移</li>
<li>「+APIとサービスの有効化」を押下し、ライブラリへ</li>
<li>「Gmail API」で検索し、APIの詳細画面へ</li>
<li>有効化</li>
</ol>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/ed1436b9--2023-11-24-17.03.36-1024x580.png" alt="GCP上のGmail APIの画面" /></p>
<p><center>(出典:Google Cloud Platform)</center></p>
<ol>
<li>Gmail APIを有効化できたら認証情報を作成します。</li>
<li>API管理画面を開き、認証情報タブを選択</li>
<li>「+認証情報を作成」を押下、OAuth クライアント IDを選択</li>
<li>作成画面に遷移するので、下記の情報を入力して作成</li>
</ol>
<p>入力する内容はこんな感じ。</p>
<ul>
<li>アプリケーションの種類
<ul>
<li>ウェブアプリケーション</li>
</ul>
</li>
<li>承認済みのリダイレクトURI
<ul>
<li><a href="https://developers.google.com/oauthplayground">https://developers.google.com/oauthplayground</a></li>
<li><a href="http://localhost:3000">http://localhost:3000</a></li>
</ul>
</li>
</ul>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/98c85b13--2023-11-24-17.04.31-1024x401.png" alt="Gmail APIの画面" /></p>
<p><center>(出典:Google Cloud Platform)</center></p>
<p>作成完了後、詳細画面を開くとAdditional informationエリアが表示されます。<br />
「クライアント ID」「クライアント シークレット」をあとで利用します。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/cc5eb180--2023-11-24-17.06.03-1024x457.png" alt="認証情報の詳細" /></p>
<p><center>(出典:Google Cloud Platform)</center></p>
<h3>Firebaseの準備(テスト用電話番号の準備)</h3>
<p>PEMのログイン情報はFirebaseで管理しています。<br />
FirebaseのAuthenticationでは、テストで使用できる電話番号ならびに確認コードをセットすることができるので、そちらを登録しておきます。<br />
登録したテスト電話番号と確認コードはあとで利用するのでメモしておくとよいでしょう。<br />
<img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/59b2f0fb--2023-11-24-21.27.52.png" alt="" /></p>
<p><center>(出典:Firebase)</center></p>
<h3>リフレッシュトークンの発行</h3>
<p>(参照:<a href="https://docs.cypress.io/guides/end-to-end-testing/google-authentication" title="Google Authentication - Cypress.io">Google Authentication – Cypress.io</a>)<br />
Googleにログインするためにリフレッシュトークンを発行します。<br />
<a href="https://developers.google.com/oauthplayground/" title="Google Developpers OAuth 2.0 Playground">Google Developpers OAuth 2.0 Playground</a>にアクセスして、リフレッシュトークンを発行しましょう。</p>
<p>まず事前設定として、上記のGmail API認証情報をセットします。<br />
右上の歯車マークから設定可能です。<br />
「Use your own OAuth credentials」にチェックを入れると認証情報の入力欄が表示されます。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/e094f47f--2023-11-24-20.22.04-1024x639.png" alt="" /></p>
<p><center>(出典:Google Developpers OAuth 2.0 Playground)</center></p>
<p>それが終わったらScopeを選択してAuthorizeします。<br />
自分はこんな感じで設定しました。</p>
<ul>
<li>Scope
<ul>
<li><a href="https://www.googleapis.com/auth/gmail.readonly">https://www.googleapis.com/auth/gmail.readonly</a></li>
<li><a href="https://mail.google.com/">https://mail.google.com/</a></li>
</ul>
</li>
</ul>
<p>AuthorizeするとAuthorization codeが表示されます。<br />
今回用があるのはリフレッシュトークンなので、「Exchange authorization code for tokens」を押下してリフレッシュトークンを生成してください。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/ccb83d2f--2023-11-24-17.14.49.png" alt="" /></p>
<p><center>(出典:Google Developpers OAuth 2.0 Playground)</center></p>
<p>さて、これで事前準備が整いました。<br />
ここからは実際に自動テストのコーディングに入っていきます。</p>
<h2>コーディング</h2>
<p>まずは環境変数をCypress.env.jsonに定義しておきましょう。<br />
セキュリティ的な観点でも、上記のトークンとかはベタ書きするわけにはいかないですからね!</p>
<pre><code class="language-json">{
"google_client_id": "xxxxxxxxxx",
"google_client_secret": "yyyyyyyyyy",
"google_refresh_token": "zzzzzzzzzzzzzzz",
"sign_in_email": "[email protected]",
"test_phone": "07000000000",
"test_phone_sms": "123456",
"from_email": "hogehoge"
}</code></pre>
<p>次はほんとにログイン処理を書いていきましょう。<br />
まずはログイン画面に遷移して、メールアドレスを入力します。</p>
<pre><code class="language-javascript"> it("ログインページに遷移、メールアドレスを入力して送信", () => {
// ログインページに遷移
cy.visit("/signin/");
// メールアドレスで登録画面に遷移
cy.contains("メールアドレスでログイン").click();
// ログインページにいることを確認
cy.contains("h1", "ログイン").should("be.visible");
// メールアドレス入力
cy.get("input[name=email]").type(Cypress.env("sign_in_email"));
// フォームを送信
cy.get("form").submit();
// メッセージ確認
cy.contains("メールをチェックしてください").should("be.visible");
// メールが来るまでちょっと待つ(ほんとはメールが来るのをキャッチしたい
cy.wait(15000);
});</code></pre>
<p>メールアドレス送信後、メールが届くまでちょっと待って、メール内からリンクを引っ張ってアクセスする作業に入ります。</p>
<pre><code class="language-javascript">
it("受け取ったメールからリンクを読み取ってアクセス", () => {
//Googleへのアクセストークンを生成する
cy.request({
method: "POST",
url: "https://www.googleapis.com/oauth2/v4/token",
body: {
grant_type: "refresh_token",
client_id: Cypress.env("google_client_id"),
client_secret: Cypress.env("google_client_secret"),
refresh_token: Cypress.env("google_refresh_token"),
},
}).then(({ body }) => {
const access_token = body.access_token;
// 件名にサインインを含む、未読、Toがログインメールアドレスになっているメールを1件だけ抽出
cy.request({
method: "GET",
url: "https://content-gmail.googleapis.com/gmail/v1/users/me/messages",
headers: {
Authorization: `Bearer ${access_token}`,
},
qs: {
q: `from:${Cypress.env("from_email")} subject:サインイン is:unread to:${Cypress.env("sign_in_email")}`,
maxResults: 1,
},
}).then(({ body }) => {
const mailID = body.messages[0].id;
// 取得したメールIDをもとにメールの詳細を取得する
cy.request({
method: "GET",
url: `https://content-gmail.googleapis.com/gmail/v1/users/me/messages/${mailID}`,
headers: {
Authorization: `Bearer ${access_token}`,
},
}).then(({ body }) => {
// 取得したメール詳細をデコードしつつ本文を抜きだす
var mailBody = decodeURIComponent(
escape(
atob(
body.payload.parts[1].body.data
.replace(/-/g, "+")
.replace(/_/g, "/")
)
)
);
// URLを囲むコーテーションがシングルだったりダブルだったりするので、ダブルに統一
mailBody = mailBody.replace(/'/g, '"');
// 文中最初のURLだけを抽出する
const accessUrl = mailBody
.substring(mailBody.indexOf("http"), mailBody.indexOf('">'))
.trim();
// 抽出したURLにvisit
cy.visit(accessUrl);
});
});
});
});
</code></pre>
<p>リンクにアクセスすると電話番号入力画面に遷移するので、予め設定しておいたテスト電話番号と確認コードを入力し、ログイン完了!というわけですね。</p>
<pre><code class="language-javascript"> it("電話番号を入力してログイン完了", () => {
// 描画が完了し、画面がSMS認証番号入力に切り替わるまで待つ
cy.wait(5000);
// SMS認証番号入力画面に切り替わったことを確認
cy.contains("電話番号に届いた6桁の確認コードを入力してください").should(
"be.visible"
);
// SMS暗証番号入力
cy.contains("電話番号に届いた6桁の確認コードを入力してください")
.parent("form")
.within(($form) => {
cy.get('input[name="verificationCode"]').type(Cypress.env("test_phone_sms"));
// 続行する!
cy.contains("送信する").click();
});
});
</code></pre>
<h2>実際の動作</h2>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/50ca6db2-blog3.gif" alt="" /></p>
<p><center>(出典:Cypress(左)、パ・リーグ Exciting Moments β(右))</center></p>
<h2>あとがき</h2>
<p>いかがだったでしょうか。<br />
自分で言うのもなんですが、すんごい力業だったと思います。<br />
まあでも、ログイン処理が自動化できたことでその後のMoment購入処理やマイページのテストを自動化することができました。<br />
可読性や保守性ももちろん大事なんですが、目的を果たすことが第一ということで。</p>
<p>ちなみに今回はCypressを利用しましたが、別の他のツールでもできると思うのでよかったら試してみてください。</p>
<p>さて、パ・リーグ Exciting Moments βは2024年3月31日をもってサービス終了することになりました。<br />
あと少しではありますが、パ・リーグ Exciting Moments βのことをよろしくお願いします。</p>
<p>以上です!</p>
<p>明日はLiuさんが担当します。お楽しみに!</p>
- 加盟店精算のインボイス対応https://engineering.mercari.com/blog/entry/20231210-support-for-merchant-invoices/https://engineering.mercari.com/blog/entry/20231210-support-for-merchant-invoices/<p>はじめに こんにちは。メルペイでBackend Engineerをしている Ryu Yamadaです。この記事は、Merpay Advent Calendar 2023 の10日目の記事です。 2022年4月に新卒で入社 […]</p>
Sun, 10 Dec 2023 10:00:10 GMT<h1>はじめに</h1>
<p>こんにちは。メルペイでBackend Engineerをしている Ryu Yamadaです。この記事は、<a href="https://engineering.mercari.com/blog/entry/20231124-merpay-advent-calendar-2023/">Merpay Advent Calendar 2023</a> の10日目の記事です。</p>
<p>2022年4月に新卒で入社してから、メルペイの加盟店管理や加盟店精算を行うサービスの開発に携わっています。</p>
<p>2023年のハイライトは何と言ってもインボイス制度です。この記事を読んでいるみなさんも、経費精算などで大変な思いをしているのではないでしょうか。この記事では、メルペイの加盟店精算におけるインボイス対応について振り返ります。</p>
<h1>ざっくり加盟店精算</h1>
<p>メルペイでは月に1回や2回などの決められた精算サイクルごとに加盟店に対して発生した売上を精算して入金しています。そして、加盟店に提供している管理画面から入金の詳細をCSVファイルとしてダウンロードできるようにしています。<br />
入金詳細ファイルには、売上金額、日次、売上のあった店舗情報や決済手数料などが記載されていて、各行が一つの取引に対応しています。</p>
<h1>インボイス対応</h1>
<p>さて、2023年10月1日からインボイス制度が始まりました。<br />
メルペイがインボイス対応をしないと加盟店がメルペイを通して決済した代金の消費税を控除できなくなってしまうため、以下の対応が必要となりました。</p>
<p>メルペイの適格請求書発行事業者としての登録<br />
メルペイが発行する入金詳細ファイルに消費税額やメルペイの登録番号等を記載し、適格請求書にする<br />
メルペイが発行した請求書を7年間保存する</p>
<p>上2つの対応は軽微だったものの、請求書を長期間に渡って保存する要件へどう対応するかは検討する必要がありました。</p>
<h1>加盟店情報の履歴テーブル</h1>
<p>メルペイでは月1回などのサイクルで精算を行っていますが、入金詳細ファイルの作成は加盟店の管理画面からの請求をトリガーにして行っていました。また、事業者名や店舗名の変更履歴を保持する仕組みがなかったため、入金詳細ファイルの請求が行われた時点での値を記載していました。</p>
<p>しかしこの方式では、例えば5年前の入金詳細ファイルを請求された場合に、5年の間に事業者名や店舗名の変更があると正しくない請求書が作成されてしまう問題がありました。</p>
<p>そこで、インボイス対応として事業者名や店舗名の変更履歴を保存する履歴テーブルが必要になりました。<br />
Spanner Change Streamを選択<br />
加盟店の情報を保存するテーブルのスキーマのイメージは以下のとおりです。</p>
<pre><code class="language-sql">CREATE TABLE Partners (
PartnerID INT64 NOT NULL,
Name STRING(MAX) NOT NULL, // 事業者名
// ・・・住所等・・・
UpdatedAt INT64 NOT NULL, // Unixtime
CreatedAt INT64 NOT NULL, // Unixtime
) PRIMARY KEY(PartnerID);
</code></pre>
<p>この変更を保持する履歴テーブルのスキーマはこのようになります。</p>
<pre><code class="language-sql">CREATE TABLE PartnerHistories (
PartnerID INT64 NOT NULL,
Name STRING(MAX) NOT NULL, // 事業者名
// ・・・住所等・・・
UpdatedAt INT64 NOT NULL, // Unixtime
CreatedAt INT64 NOT NULL, // Unixtime
HistoryCreatedAt TIMESTAMP NOT NULL, // Timestamp 履歴作成時刻
) PRIMARY KEY(PartnerID, HistoryCreatedAt);</code></pre>
<p>今回、Partnersテーブルに変更があったときに履歴テーブルであるPartnerHistoriesテーブルへの書き込みを行う方法を2通り検討しました。<br />
アプリケーションで元(Partners)テーブルのレコードを挿入や更新した場合に履歴(PartnerHistories)テーブルへの書きこみも行う方法<br />
Spanner Change Streamを利用し、DBレベルで、元(Partners)テーブルの変更をトリガーに履歴(PartnerHistories)テーブルへの書き込みを行う方法</p>
<p>さらに、インボイス対応にあたっては、Partnersテーブルだけではなく他のいくつかのテーブルにも履歴の作成が必要でした。</p>
<p>前者のロジックを作り込む方法では、元(Partners等)テーブルに書き込みを行うロジックすべての修正を行う必要があり修正範囲が広いこと、将来元テーブルを操作するようなロジックを追加する際に履歴テーブルへの書き込みを忘れると影響範囲がかなり大きくなってしまうことなどがネックでした。</p>
<p>後者のSpanner Change Streamを使う方法では、ロジックの改修から独立したDBレベルの機能として実現できること。また、加盟店精算ではメルペイ内で精算してから実際に入金を行うまでに数日以上開くため、履歴テーブルの要件として元テーブルと履歴テーブルの書き込みを同じトランザクションで行うことが求められなかったこともあり、最終的にこちらの方法を選択することにしました。</p>
<h1>Spanner Change Streamで履歴テーブル構築</h1>
<p>Dataflowを通してSpanner Change Streamを利用しました。<br />
メルペイではこれまでDBのバックアップ用途などでは利用実績がありましたが、プロダクトでの利用は初めてでした。</p>
<p>履歴テーブルの作成に当たっては、元テーブルへの挿入(INSERT)と更新(UPDATE)の両方が履歴テーブルに対しては挿入としなくてはならない点に注意が必要でした。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/463db67a-draw1.jpg" alt="" /></p>
<h1>ハマった点</h1>
<p>最も困難だった点は、元テーブルのUpdatedAtがUnixtimeであったことでした。<br />
元テーブルにUnixtimeの最小単位である1秒以内に複数の変更が行われた場合に、履歴テーブルにはUpdatedAtが同一の複数のレコードが挿入されますが、どのレコードが元テーブルの最終的な状態と一致しているかがわからない点が問題でした。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/0b5f0d01-draw2.jpg" alt="" /></p>
<p>この例ではIDが1のレコードに対して1秒以内に2回更新をしています。挿入順序とHistoryCreatedAtの順序は必ずしも一致しないので、履歴テーブルからは”メルペイ2”と”メルペイ3”のどちらが最新の履歴なのかがわかりません。</p>
<p>この問題を解決するために暫定対応として以下のアプローチを取りました。</p>
<p>履歴テーブルのUpdatedAtにUnique Key制約をかけて、1秒以内に複数の変更があった場合には2つ目移行の挿入を失敗にする<br />
履歴テーブルへの挿入失敗を監視するアラートを設定し、発生時には手動で確認する</p>
<p>インボイス制度の施行が迫っていたため暫定的な対応となりましたが、加盟店情報が短時間に複数回更新されることが少ないため、この対応でクリティカルな問題は起きていません。<br />
恒久的な対応としてUpdatedAtのUnixtimeからTimestampへのマイグレーションを予定しています。</p>
<h1>おわりに</h1>
<p>ニュースでインボイス対応という言葉を知ったときには、経理ではない自分にはあまり関係がないだろうと思っていましたが、当事者として対応することになりました。<br />
メルペイが成長してきた中で返しきれていない、UpdatedAtの型といった負債にも苦しみましたが、インボイス対応を完了することができました。今後もメルペイと加盟店をなめらかにつなぐプロダクトを作っていきたいです。</p>
<p>明日の記事は @fukutomiさんです。引き続きお楽しみください。</p>
- Gitブランチ戦略 Stacking手法のケーススタディhttps://engineering.mercari.com/blog/entry/20231209-git-branch-strategy-stacked-diffs-case-study/https://engineering.mercari.com/blog/entry/20231209-git-branch-strategy-stacked-diffs-case-study/<p>こんにちは。メルカリのBackendエンジニアの@osari.kです。 この記事は、Mercari Advent Calendar 2023 の9日目の記事です。 一般に大きなプルリクエストはレビューが大変で、マージまで […]</p>
Sat, 09 Dec 2023 11:00:56 GMT<p>こんにちは。メルカリのBackendエンジニアの<a href="https://github.com/cloverrose">@osari.k</a>です。<br />
この記事は、<a href="https://engineering.mercari.com/blog/entry/20231124-mercari-advent-calendar-2023/">Mercari Advent Calendar 2023</a> の9日目の記事です。</p>
<p>一般に大きなプルリクエストはレビューが大変で、マージまでに時間がかかります。一方で複数の小さいプルリクエストに分割するとコードレビュー待ちの間、関連する開発がブロックされることがあります。今回は機能の開発時間を短くするために、チームで試したGitのブランチ戦略の1つであるStacking手法をケーススタディを交えて紹介します。</p>
<h2>大きなプルリクエストがもたらす問題点</h2>
<p>大きなプルリクエストがもたらす問題とは何でしょうか?</p>
<ol>
<li>コードレビューで読むサイズが増える</li>
<li>コードレビュー中の修正回数が増える(可能性が増える)</li>
<li>コードレビューで必要な知識の範囲が広がる(可能性が増える)</li>
<li>変更箇所が多いのでリリースのリスクが増加する</li>
</ol>
<p>プルリクエストが大きいということは、含まれる変更箇所が多いということです。それはつまり、コードレビューで読むサイズが増え、レビューの観点も増え、レビューにあわせて行われる修正も多くなるでしょう。<br />
モノリシックなレポジトリの場合コード全体に精通しているエンジニアは少なく、一般に複数のドメインにまたがる変更の場合、複数のチームからレビューの承認を貰わなければなりません。<br />
大きな変更を一度にリリースすると、障害が発生する可能性が増加し、障害が発生したときに原因の特定も難しくなります。</p>
<p>一方で、関連する変更を複数の小さいプルリクエストに分割すると、コードレビューの間関連する次の作業が進められないという課題もあります。</p>
<h2>Stacking手法による小さいプルリクエストの推進</h2>
<p>本記事ではこれまでに述べた問題を改善するために私達のチームが試してみたStacking手法について得られた知見を共有したいと思います。</p>
<p>Stacking手法は以下のBlogで紹介されています。<br />
<a href="https://newsletter.pragmaticengineer.com/p/stacked-diffs">Stacked Diffs (and why you should know about them)</a></p>
<p>関連する部分を要約すると、Stacking手法は、1つの大きな変更を分割して管理できるようにするプロセスです。ある変更が別の変更に依存している場合、それらは変更差分のスタックとして組み立てられ、正確な順序でマージされます。プルリクエストに対してさらに機能を追加するプルリクエストを作る様をスタックと例えています。これにより、変更をレビューしやすくなります。<br />
合わせて、Stacking手法は複数の作業を並行して行うのに役立つという利点もあります。各段階で作業を終了させることができるため、時間を節約できます。以上のように、Stacking手法はコードの変更の複雑さを管理するための強力な手段であり、上手く実装した場合、生産性と効率性を高めることができます。</p>
<p>本来は上記のBlogで紹介されているスタック間の差分を見やすくするツール(<a href="https://reviewstack.dev/">ReviewStack</a> など)と組み合わせて使うのがいいと思います。<br />
この手法はチームメンバーがSlackで共有してくれて、一度チームで試してみようということでGitHubのプルリクエスト機能のみを使い試しました。</p>
<h2>Stacking手法の目的</h2>
<p>なぜこの手法を試すのか、それはコードレビューで開発者の他の開発をブロックしないためです。<br />
例えばある機能を実装するときにそれを部分タスク PR1,PR2,PR3に分割し、PR2がPR1に依存、PR3がPR2に依存といった依存関係があるとしましょう。</p>
<div style="text-align: center">
<img loading="lazy" src="https://storage.googleapis.com/prd-engineering-asset/2023/12/80056a00-2.png" alt="プルリクエストの依存関係" width="980" height="auto" class="size-full wp-image-29177" srcset="https://storage.googleapis.com/prd-engineering-asset/2023/12/80056a00-2.png 980w, https://storage.googleapis.com/prd-engineering-asset/2023/12/80056a00-2-300x64.png 300w, https://storage.googleapis.com/prd-engineering-asset/2023/12/80056a00-2-768x165.png 768w" sizes="(max-width: 980px) 100vw, 980px" /><br />
<br />図1: プルリクエストの依存関係
</div>
<p></p>
<p>通常のプルリクエストの作り方の場合、PR1のマージが終わるまでPR2, PR3の開発はブロックされます。</p>
<p>※実際には開発者はローカルでの開発は可能ですし、PR1がマージされる前にPR2のコードレビューを依頼することもできます。GitHubのプルリクエスト機能のみで試す場合に重要なのはチームで認識を揃えることだと思います。Stacking手法を使うことを共有しておくことで、混乱なくコードレビューがスムーズになります。</p>
<p>この方法は1人の開発者が自分のプルリクエストに依存した開発をコードレビューにブロックされずに行うためのものなので、複数人で関連機能を開発する場合には向きません。<br />
というのも、Stacking手法は定期的にRebaseが発生します。自分だけなら、Rebaseの影響範囲や、影響が出るタイミングをコントロールできますが、複数人の場合頻繁なRebaseは開発効率を落とします。</p>
<h2>モノリシックなレポジトリに実装されているマーケティングシステムの改善での実例</h2>
<p>今回Stacking手法を試した一連の機能変更について紹介します。</p>
<p>今回はマーケティングシステムのスケーラビリティ改善に対してStacking手法を適用しました。今回改善を行うマーケティングシステムはモノリシックなレポジトリに実装されています。<br />
ディスカウントを適用する前に商品のいくつかのデータをチェックします。そのうちの一つは別のマイクロサービス(Validation MS)の持つデータを用います。<br />
Validation MSへのクライアント機能、その結果を使いやすいように変換する関数がモノリシックなレポジトリに実装されています。<br />
MSごとに別々のチームがメンテナンスしており、意図した変更を達成するにはマーケティングシステムとは異なるValidation MSのドメイン知識が必要になります。</p>
<p>マーケティングシステムは定期的に実行されるCronjobとWorkerがQueueで接続されています。処理すべきデータは複数あり、Cronjobから処理すべき商品のIDをQueueに送り、Workerで個別にValidation MSのAPIを利用して処理を行います(図2)。</p>
<div style="text-align: center">
<img loading="lazy" src="https://storage.googleapis.com/prd-engineering-asset/2023/12/c85edb7c-2.png" alt="改善前のシステム構成" width="906" height="auto" class="size-full wp-image-29178" srcset="https://storage.googleapis.com/prd-engineering-asset/2023/12/c85edb7c-2.png 906w, https://storage.googleapis.com/prd-engineering-asset/2023/12/c85edb7c-2-300x239.png 300w, https://storage.googleapis.com/prd-engineering-asset/2023/12/c85edb7c-2-768x612.png 768w" sizes="(max-width: 906px) 100vw, 906px" /><br />
<br /> 図2:改善前のシステム構成
</div>
<p></p>
<p>Validation MSのAPIはBatch呼び出しもサポートしているので、Cronjob側でBatch APIを用いて事前処理をし、Worker側ではValidation MSへのアクセスをしない形にすることでスケーラビリティの改善を試みました(図3)。</p>
<div style="text-align: center">
<img loading="lazy" src="https://storage.googleapis.com/prd-engineering-asset/2023/12/fd22dab3-2.png" alt="改善後のシステム構成" width="906" height="auto" class="size-full wp-image-29179" srcset="https://storage.googleapis.com/prd-engineering-asset/2023/12/fd22dab3-2.png 906w, https://storage.googleapis.com/prd-engineering-asset/2023/12/fd22dab3-2-300x240.png 300w, https://storage.googleapis.com/prd-engineering-asset/2023/12/fd22dab3-2-768x615.png 768w" sizes="(max-width: 906px) 100vw, 906px" /><br />
<br />図3: 改善後のシステム構成
</div>
<p></p>
<p>簡易検証を行った後に、プルリクエストを作成しました。<br />
Stacking手法で作成したプルリクエストは6つです</p>
<ol>
<li>不要な変数の削除</li>
<li>不要なロジックの削除</li>
<li>不要なメソッドの削除</li>
<li>Validation MS関連機能のユニットテストの改善</li>
<li>Validation MSのBatch エンドポイントの結果変換メソッドの修正</li>
<li>WorkerからCronjobにValidatio MSの呼び出しとチェック機能の移動</li>
</ol>
<p>プルリクエスト1~3では4以降で安全に変更するために、不要なコードを削除をして全体の見通しを良くしています。不要な変数を削除(プルリクエスト1)すると、不要なロジックが削除(プルリクエスト2)できて、不要なメソッドの削除(プルリクエスト3)に辿り着くという依存関係があります。</p>
<p>プルリクエスト4と5はValidation MSの開発チームと協力して実装・コードレビューをしてもらいました。メソッドのシグネチャが定まれば、マーケティングシステムチーム内で並行してプルリクエスト6を実装・コードレビューをしてもらうことができます。</p>
<div style="text-align: center">
<img loading="lazy" src="https://storage.googleapis.com/prd-engineering-asset/2023/12/8ad4762e-stacking手法を用いた場合の開発フロー2.png" alt="Stacking手法を用いた場合の開発フロー" width="904" height="auto" class="size-full wp-image-29180" srcset="https://storage.googleapis.com/prd-engineering-asset/2023/12/8ad4762e-stacking手法を用いた場合の開発フロー2.png 904w, https://storage.googleapis.com/prd-engineering-asset/2023/12/8ad4762e-stacking手法を用いた場合の開発フロー2-300x222.png 300w, https://storage.googleapis.com/prd-engineering-asset/2023/12/8ad4762e-stacking手法を用いた場合の開発フロー2-768x568.png 768w" sizes="(max-width: 904px) 100vw, 904px" /><br />
<br />図4: Stacking手法を用いた場合の開発フロー
</div>
<p></p>
<p>上述のように各プルリクエストは自分より小さい番号のプルリクエストに依存しています。<br />
そのため、Stacking手法でない場合、開発者は毎回コードレビューでブロックされてしまいます。</p>
<h2>Stacking手法を用いた開発タイムライン</h2>
<p>Stacking手法で実際に開発がどうなったかをイメージしやすいように、今回の事例について一連の変更のデータをGitHubとSlackから取得しタイムラインとしてまとめました(図5)。</p>
<p>開発フェーズを以下の3つに分類しました</p>
<ol>
<li>準備:コーディング前に情報収集や実装方針を議論する期間</li>
<li>コーディング:コードを書き始めてからレビューを依頼するまでの期間
<ul>
<li>今回は最初のCommitをコーディング開始時刻としました。</li>
</ul>
</li>
<li>コードレビュー:Slackでコードレビューを依頼してからGitHub上でApproveされるまでの期間</li>
</ol>
<p>図5の開発タイムラインのように、開発がコードレビューにブロックされていないことと、並行してプルリクエストのレビューをしてもらった様子がわかります。</p>
<p>今回の開発ではPoCにより大枠の方針を定めた後に実際の開発を進めており、結果として各プルリクエストのコーディングフェーズが短くなっています。</p>
<div style="text-align: center">
<img loading="lazy" src="https://storage.googleapis.com/prd-engineering-asset/2023/12/7370afbf-stacking手法を用いた開発タイムライン2.png" alt="Stacking手法を用いた開発タイムライン" width="2522" height="auto" class="size-full wp-image-29181" srcset="https://storage.googleapis.com/prd-engineering-asset/2023/12/7370afbf-stacking手法を用いた開発タイムライン2.png 2522w, https://storage.googleapis.com/prd-engineering-asset/2023/12/7370afbf-stacking手法を用いた開発タイムライン2-300x199.png 300w, https://storage.googleapis.com/prd-engineering-asset/2023/12/7370afbf-stacking手法を用いた開発タイムライン2-1024x678.png 1024w, https://storage.googleapis.com/prd-engineering-asset/2023/12/7370afbf-stacking手法を用いた開発タイムライン2-768x509.png 768w, https://storage.googleapis.com/prd-engineering-asset/2023/12/7370afbf-stacking手法を用いた開発タイムライン2-1536x1017.png 1536w, https://storage.googleapis.com/prd-engineering-asset/2023/12/7370afbf-stacking手法を用いた開発タイムライン2-2048x1356.png 2048w, https://storage.googleapis.com/prd-engineering-asset/2023/12/7370afbf-stacking手法を用いた開発タイムライン2-1200x795.png 1200w, https://storage.googleapis.com/prd-engineering-asset/2023/12/7370afbf-stacking手法を用いた開発タイムライン2-1980x1311.png 1980w" sizes="(max-width: 2522px) 100vw, 2522px" /><br />
<br /> 図5: Stacking手法を用いた開発タイムライン
</div>
<p></p>
<h2>Stacking手法の評価</h2>
<p>この一連の変更でStacking手法を使ったことで、得られたメリットは以下になります。</p>
<h3>1: プルリクエストのサイズを小さく保てた</h3>
<p>評価のために、プルリクエストを分割しなかった場合のプルリクエストサイズとStacking手法の各プルリクエストのサイズを比較してみます(表1)。ここでは以下の定義で比較します</p>
<ul>
<li>Diff: 追加行数と削除行数</li>
<li>プルリクエストサイズ: 追加行数と削除行数の合計値</li>
</ul>
<p>比較すると、一番大きなPR4で40%のサイズになっており、他は13%〜22%のサイズになりました。</p>
<p>Stacking手法ではプルリクエストのレビューに後続の開発がブロックされないため、待ち時間の増加を気にせず、適切な粒度でプルリクエストを作成できました。<br />
特に顕著なのがプルリクエスト1,2,3の分割だと思います。不要なコードの削除を1つのプルリクエストにまとめず、レビュアーにも理解がしやすい形で作成できました。</p>
<p>また、分割した各プルリクエストサイズの合計についても分割しなかった場合と比較して102%のサイズで、わずかに増加していますが分割のメリットを考慮すると許容範囲でしょう。</p>
<p></p>
<div style="text-align: center">
表1: Stacking手法各プルリクエストサイズの比較
</div>
<table>
<thead>
<tr>
<th>プルリクエスト</th>
<th>Diff追加行数</th>
<th>Diff削除行数</th>
<th>プルリクエストサイズ</th>
<th>分割無しと比較したプルリクエストサイズ</th>
</tr>
</thead>
<tbody>
<tr>
<td>分割無し</td>
<td>+615</td>
<td>-837</td>
<td>1452</td>
<td>—</td>
</tr>
<tr>
<td>PR1</td>
<td>+2</td>
<td>-5</td>
<td>7</td>
<td>0.5%</td>
</tr>
<tr>
<td>PR2</td>
<td>+48</td>
<td>-268</td>
<td>316</td>
<td>22%</td>
</tr>
<tr>
<td>PR3</td>
<td>+0</td>
<td>-186</td>
<td>186</td>
<td>13%</td>
</tr>
<tr>
<td>PR4</td>
<td>+318</td>
<td>-269</td>
<td>587</td>
<td>40%</td>
</tr>
<tr>
<td>PR5</td>
<td>+114</td>
<td>-72</td>
<td>186</td>
<td>13%</td>
</tr>
<tr>
<td>PR6</td>
<td>+149</td>
<td>-53</td>
<td>202</td>
<td>14%</td>
</tr>
<tr>
<td>PR1〜6の合計</td>
<td>+631</td>
<td>-853</td>
<td>1484</td>
<td>102%</td>
</tr>
</tbody>
</table>
<h3>2: レビュー依頼開始からApproveをもらうまでの時間を短く保てた</h3>
<p>Validation MSのBatch エンドポイントの結果変換メソッドの修正を行うPR5はValidation MSのドメイン知識が必要だったため、今回はSlack上で大枠の説明をしてもらったあとに、プルリクエストレビューを通じてドメイン知識の獲得を行う方法で進めました。そのためPR5のレビューには時間がかかっています。<br />
それ以外のプルリクエストはレビュー依頼の翌日にはレビューが完了してマージできたため、効率よく開発を進められました。</p>
<h3>3: コードレビュー中に開発を並行して進めることができた</h3>
<p>図5の開発タイムラインを見るとレビュー中に最初のcommitを行っていることがわかります。<br />
実際には最初のCommit前から開発をしているため、コードレビューにブロックされることなく後続の開発を進められていることが確認できます。</p>
<p>コーディングと同様にコードレビューも並行して行われていることがわかります。<br />
通常の分割手法を行った場合はレビューが完了するまで後続の開発を行わないため、コードレビューも並列で行われません。そこで、各プルリクエストのレビュー時間の合計を通常の分割手法の場合のレビュー時間と仮定します。<br />
Stacking手法によるレビュー時間をPR1のレビュー依頼からPR6のApproveとして比較すると、Stacking手法によりレビュー時間を7%削減できたことになります。</p>
<p>実際には並列でコードレビューを行わないことで、各プルリクエストのコードレビュー時間が短くなる可能性もありますが、プルリクエストはGitHub上のコメントによる非同期なコミュニケーションが主であることから、相手からの返信を待つ時間が支配的となるため複数のプルリクエストを並列で行うメリットは大きいと考えられます。</p>
<h3>4: コードレビューを専門性のある別々のチームに依頼できる</h3>
<p>Validation MSドメインにフォーカスした小さいプルリクエスト(PR4, PR5)はマーケティングシステムの詳細を知らないValidation MSチームがレビューしやすくなっています。結果としてValidation MSチームの複数名の開発者がレビューに参加してくれて、コードの質が向上しました。<br />
具体的には、当初はマーケティングシステムに実装されていたValidation MSのSingle IDエンドポイントの変換メソッドとBatchエンドポイントの変換メソッドの詳細が異なっており、マーケティングシステムの挙動を変えないために、Single IDエンドポイントと同じ変換を行う新規Batchエンドポイントの変換メソッドの作成を考えていました。それがValidation MSチームとSlack上で議論していく中で、この実装の詳細の差分は、既存のBatchエンドポイントの変換メソッドの修正を行うことで安全に解消できました。</p>
<h3>5: 小さいプルリクエストは開発者自身も思い出しやすい</h3>
<p>プルリクエストのサイズが小さくなることで、レビュアーがレビューしやすくなるだけではなく、開発者自身にもメリットがありました。マーケティングシステム開発の他に別の機能の開発や、メンバーのコードレビュー、ミーティングへ参加をしていると、開発した際の記憶が薄れていて、レビューコメントに対応するために再度思い出す作業が必要になります。プルリクエストが小さいことで思い出しやすく、コンテキストスイッチが多い中でもレビューコメントに対応しやすかったです。またメリット2で述べたレビュー時間が短くなったこともあり記憶が薄れずに対応できた割合も多かったです。</p>
<h2>結論</h2>
<p>本記事ではGitのブランチ戦略の1つであるStacking手法をチームで試した結果を整理しました。レビュー待ちの課題を解消することで、プルリクエストの分割を推進し各プルリクエストのサイズを13%〜40%と小さくすることができました。また並行して開発し、レビュー依頼をすることでレビュー時間を7%削減できました。このようにコードレビューにブロックされることなく開発ができるメリットを得ることができました。</p>
<p>機能開発をする中でコードベースへの理解が深まり、小さい改善点を見つけることがよくあります。そうした場合に、改善を後回しにせず、小さいプルリクエストとして分割してレビュー依頼できる点も開発者体験が上がったと感じました。</p>
<p>また想定外の恩恵として専門ドメインで閉じたプルリクエストを作り、専門チームにレビューをしてもらうことで、コードレビューでフォーカスする点が明確になり、最終的なコードの質が上がりました。この点はStacking手法を使わない場合でも継続して意識していきたいと思います。</p>
<p>明日の記事は reyさんです。引き続きお楽しみください。</p>
- 新卒エンジニアが Airflow のバグを発見してからコントリビュートするまでhttps://engineering.mercari.com/blog/entry/20231209-airflow-oss-contribution/https://engineering.mercari.com/blog/entry/20231209-airflow-oss-contribution/<p>この記事は、Merpay Advent Calendar 2023 の9日目の記事です。 こんにちは。今年の春に新卒でメルペイに入社し、Credit Platform Team でバックエンドエンジニアをしている@cha […]</p>
Sat, 09 Dec 2023 10:00:08 GMT<p>この記事は、<a href="https://engineering.mercari.com/blog/entry/20231124-merpay-advent-calendar-2023/">Merpay Advent Calendar 2023</a> の9日目の記事です。</p>
<p>こんにちは。今年の春に新卒でメルペイに入社し、Credit Platform Team でバックエンドエンジニアをしている<a href="https://champonian.com/">@champon</a>です。Credit Platform Team では主に ML(いわゆるAI与信) を用いた与信枠の算出を行っていますが、その中でも自分はワークフローエンジンである Airflow を用いたデータパイプラインの開発・運用を行っています。<br />
今回は、業務中に Airflow のバグを見つけてからその原因を調査し、実際にコントリビュートするまでの過程をお話したいと思います。</p>
<h1>Airflow とは</h1>
<p>まず簡単に、Airflow について説明します。<br />
Airflow とは、ワークフローエンジンの一種であり、Apache Software Foundation が管理する OSS です。<br />
DAG と呼ばれる有向非巡回グラフの形式でワークフローを定義し、それぞれのノードは Task と呼ばれるワークフロー処理の構成要素となっています。<br />
Task には、Airflow から提供されている様々な Operator を使用することができ、例えば BashOperator や PythonOperator などがあり、それぞれ Bash コマンドや Python プログラムを実行できます。<br />
また、Amazon Web Service (AWS) や Google Cloud Platform (GCP) のサービス・プロバイダも公開されているため、クラウドサービス上のデータを容易に扱うことができます。<br />
自分のチームでは、GCP 上の Cloud Composer で Airflow 環境を構築し、BigQuery や Dataflow と連携しながらデータパイプラインとしてメルペイの与信枠計算の一部を管理しています。</p>
<h1>予期せぬエラーの発生</h1>
<p>QA Engineer によるテスト実施中に、Dataflow を使っている Task で以下のエラーが発生しはじめました。</p>
<pre><code>Exception: Google Cloud Dataflow job <xxx> is in an unexpected terminal state: JOB_STATE_DONE, expected terminal state: JOB_STATE_DONE</code></pre>
<p>直訳すると、「予期していた終着状態は JOB_STATE_DONE でしたが、Google Cloud Dataflow job が予期せぬ終着状態 JOB_STATE_DONE となりました」でしょうか。<br />
明らかに筋が通っていないこちらの1文を読んで、もしかしたら Airflow 側に何かバグがあるかもなと思い、Airflow のソースコードを探ることにしました。</p>
<h1>エラーの原因調査</h1>
<p>こういうときはまず、該当箇所の直近 commit を見ることにします。<br />
スタックトレースもエラーメッセージと一緒に出力されていたため、それを頼りに該当ファイルにたどり着きました。<br />
このファイルの最新 commit を見てみると、<a href="https://github.com/apache/airflow/pull/34217">PR #34217</a> が merge されていることがわかりました。<br />
さらに深掘ってみると、どうやら apache-airflow-providers-google==10.9.0 のリリースに入った変更で、expected_terminal_state という引数を DataflowHook に加える対応のようです。</p>
<p>この expected_terminal_state というのは、<a href="https://github.com/apache/airflow/pull/8553#discussion_r416136954">こちら</a>で議論されており、Dataflow job が完了したとみなすステートをユーザーが設定できるというものです。<br />
(Airflow には Dataflow job のステートがいくつか定義されており (※1)、どれを job 完了状態とみなすか、といったもの)</p>
<p>話を戻しますが、この PR #34217 の変更を見てみると、ちょうどエラー発生箇所に変更が加えられていました。<br />
また、念のため Cloud Composer の package 一覧を確認したところ、該当環境の apache-airflow-providers-google のバージョンが 10.9.0 となっていたので、原因はこちらで間違いなさそうです。</p>
<pre><code class="language-shell">gcloud composer environments list-packages <your environment> –project <your project> –location <your location></code></pre>
<p>原因はわかったので、対症療法としてバージョンを 10.8.0 に落とせばエラーをなくすことができますが、せっかくなので自分で直すことにしました。</p>
<p>(※1) <a href="https://github.com/apache/airflow/blob/providers-google/10.9.0/airflow/providers/google/cloud/hooks/dataflow.py#L130-L141">https://github.com/apache/airflow/blob/providers-google/10.9.0/airflow/providers/google/cloud/hooks/dataflow.py#L130-L141</a></p>
<h1>Issue, PR の作成</h1>
<p>とりあえず <a href="https://github.com/apache/airflow/issues/34767">Issue</a> を出しました。<br />
Issue テンプレートの下部に “Are you willing to submit PR?” という文とともにチェックボックスが添えてあったので、チェックをして PR 作成に取り掛かります。</p>
<p>修正箇所は前述の通り、expected_terminal_state の挙動によるものと思われます(正確には、DataflowJobsController の check_dataflow_job_state メソッド (※2))。</p>
<p>特に、expected_terminal_state = None (デフォルト値) のときに考慮漏れがありました。<br />
expected_terminal_state がデフォルトのときに関係するコードを次に抜き出します(今回は Dataflow のバッチ処理なのでストリーミング処理に関係するコードは省きます)。</p>
<pre><code class="language-python">AWAITING_STATES = {
JOB_STATE_RUNNING,
JOB_STATE_PENDING,
JOB_STATE_QUEUED,
JOB_STATE_CANCELLING,
JOB_STATE_DRAINING,
JOB_STATE_STOPPED,
}
def _check_dataflow_job_state(self, job) -> bool:
current_state = job["currentState"]
if self._expected_terminal_state is None:
self._expected_terminal_state = DataflowJobStatus.JOB_STATE_DONE
if not self._wait_until_finished and current_state == self._expected_terminal_state:
return True
if current_state in DataflowJobStatus.AWAITING_STATES:
return self._wait_until_finished is False
raise Exception(
f"Google Cloud Dataflow job {job['name']} is in an unexpected terminal state: {current_state}, "
f"expected terminal state: {self._expected_terminal_state}"
)</code></pre>
<p>ここで、wait_until_finished という要素が新たに登場します。<br />
このパラメータは expected_terminal_state が導入される以前から存在したもので、簡単に言うと “Dataflow job が終了するまで処理を待機するかどうか” のフラグです。<br />
これを踏まえて上記のコードを解釈すると、例えば次の全てを満たす状態のときに Exception が返ってしまうことがわかります。</p>
<ul>
<li>wait_until_finished = True</li>
<li>current_state = DataflowJobStatus.JOB_STATE_DONE</li>
<li>expected_terminal_state = DataflowJobStatus.JOB_STATE_DONE</li>
</ul>
<p>ここでようやく、今回のエラー発生時の状態にたどり着きました。<br />
後は修正するだけです。<br />
<code>if not self._wait_until_finished and current_state == self._expected_terminal_state</code> の分岐処理を以下のように変更します。</p>
<pre><code class="language-python">if current_state == self._expected_terminal_state:
if self._expected_terminal_state == DataflowJobStatus.JOB_STATE_RUNNING:
return not self._wait_until_finished
return True</code></pre>
<p>wait_until_finished の条件が悪さをしていたので、expected_terminal_state が DataflowJobStatus.JOB_STATE_RUNNING のときの分岐を増やし、それ以外の場合は current_state == self._expected_terminal_state であれば True となるようにしました。<br />
詳細は<a href="https://github.com/apache/airflow/pull/34785">修正 PR</a>を御覧ください。</p>
<p>(※2) Helper method to check the state of one job in dataflow for this task if job failed raise exception: <a href="https://github.com/apache/airflow/blob/providers-google/10.9.0/airflow/providers/google/cloud/hooks/dataflow.py#L389-L433">https://github.com/apache/airflow/blob/providers-google/10.9.0/airflow/providers/google/cloud/hooks/dataflow.py#L389-L433</a></p>
<h2>余談: このような実装になった原因</h2>
<p>このようなエラーが引き起こされた原因として、wait_until_finished と expected_terminal_state という似たようなパラメータが共存することが大いに関係あると考えられます。<br />
どちらも Dataflow job の完了状態を考慮する必要があるため、完了判定条件がより複雑になってしまったことが考えられます。<br />
また、wait_until_finished = True は、expected_terminal_state = DataflowJobStatus.JOB_STATE_DONE と実質同じ意味なのかなと考えており、将来的には wait_until_finished を廃止することでより簡潔な実装になるのかなと思いました(一応 PR 内のコメントで提案しておきました (※3))。</p>
<p>(※3) <a href="https://github.com/apache/airflow/pull/34785#discussion_r1348054361">https://github.com/apache/airflow/pull/34785#discussion_r1348054361</a></p>
<h1>まとめ</h1>
<p>今回は、Airflow におけるバグ発見から PR を作成するまでの課程を、自分の思考を振り返りながら記事にしました。<br />
その後、無事 apache-airflow-providers-google==10.12.0 にてリリースされたので、今後は同様のエラーが起こることはないはずです。<br />
普段は OSS 等へコントリビュートはあまりしない(バグ見つけたら Issue 書くか、時間があったら PR 出すくらい)ですが、久々に結構楽しめたので、今後もちょくちょく Issue 見つつ手伝えそうであればコントリビュートしていこうかなと思いました。</p>
<p>明日の記事は @ryuyama さんです。引き続きお楽しみください。</p>
- メルペイでのインターンを2ヶ月経験してみてhttps://engineering.mercari.com/blog/entry/20231208-internship-shion/https://engineering.mercari.com/blog/entry/20231208-internship-shion/<p> こんにちは!横浜国立大学理工学部情報工学EP3年の @shion1305 です。今年の10月から株式会社メルペイ Settlementチームにてバックエンジニアのインターンを始め、12月初めでちょうど2ヶ月となります。 […]</p>
Fri, 08 Dec 2023 10:00:47 GMT<p> こんにちは!横浜国立大学理工学部情報工学EP3年の <a href="https://x.com/shion1305">@shion1305</a> です。今年の10月から株式会社メルペイ Settlementチームにてバックエンジニアのインターンを始め、12月初めでちょうど2ヶ月となります。<br />
この記事は、 <a href="https://engineering.mercari.com/blog/entry/20231124-merpay-advent-calendar-2023/">Merpay Advent Calendar 2023</a> の8日目の記事です。</p>
<p> 今回は、自分のインターンの振り返りも含めて以下について書きたいと思います。</p>
<ul>
<li>インターン2ヶ月の振り返り</li>
<li>働く環境</li>
<li>メルカリグループのインターンの特徴</li>
</ul>
<h1>インターン2ヶ月の振り返り(自分の中での主なイベントまとめ)</h1>
<h2>入社オリエンテーション</h2>
<p> インターン生は、同月入社の新入社員と一緒にオリエンテーションを受講します。メルカリでは働くにあたってカルチャーやバリューを共有することをとても大切にしていて、最初の1週間はメルカリとしてのカルチャーやバリューに対する考え方についての社内の学習教材に取り組んだり、他の新入社員とディスカッションをしたりしていました。</p>
<p>入社日翌日にはウェルカムランチがあり、そこで新入社員やメンターの方と交流を深めました。(ランチについては後述)</p>
<h1>所属チームでのインターン</h1>
<p> 入社オリエンテーションが終わると本格的にチームのタスクに入っていくことになります。決済システムにはたくさんのドメイン知識が必要になるため、最初は決済システムのコンテクストの薄いタスクを行いながら、過去の資料を参照して決済システムならではの必要知識の理解や現在のレポジトリの状況の把握を行っていました。<br />
11月に入り、ある程度慣れてからは、本格的に決済に直接関連するタスクを任されました。これまで以下のようなタスクに取り組みました。</p>
<ul>
<li>Go言語のLinter golangci-lint の設定見直し</li>
<li>検証環境で発生しているトラブルの原因調査</li>
<li>Go言語のエラーハンドリングライブラリ <code>pkg/errors</code> の置き換え</li>
<li>APIサーバーが稼働するPodの設定調整
<ul>
<li>PodDisruptionBudget / HorizontalPodAutoscaler / Resources (cpu limits) の設定値見直し</li>
<li>Kustomizeのリファクタリング</li>
</ul>
</li>
<li>マイクロサービスの改修
<ul>
<li>改修範囲の特定と修正</li>
<li>テストケースの修正・改良</li>
<li>protoファイルの更新</li>
<li>リファクタリング</li>
</ul>
</li>
</ul>
<h1>Office Week</h1>
<p> メルカリグループでは普段リモートで働いている人が多いのですが、メルペイ・メルコインではだいたい半期に1度Office Weekがあります。11月中旬にメルペイ・メルコインにてOffice Weekが実施され、基本社員全員が六本木オフィスに出社しました。</p>
<p> 11月のOffice Weekは3日間で、初日からイベントやLT大会が盛りだくさんでした。自分は大学の都合上初日のみの参加でしたが、メルペイ全社が集まるキックオフイベントに参加したり、十数のLTの発表を聞いたりしました。LT会では、メルペイの他のチームではどのようなことをしているのかを知ることができたり、自分が普段あまり触れていない技術についての知見を深めることができました。普段オンラインのみでしか会っていない方やメンターランチで交流したインターン生と話したりと、対面ならではの体験がたくさんできました。</p>
<p>Office Weekの様子はこちらから!👇<br />
<a href="https://engineering.mercari.com/blog/entry/20231205-fintech-techtalk/">Fintech Tech Talk at Office Week を開催したよ! | メルカリエンジニアリング</a></p>
<h1>インシデントを起こしてしまった</h1>
<p> 11月後半、インターンのタスクの一つとしてデータベースのマイグレーションの見直しに取り組んでいました。その作業中にコマンド1つを誤ってしまい、ステージング環境のデータベースを消去してしまったことがありました。本番環境ではなかったものの、メルペイにて関連するマイクロサービスが多くあり、社内で他のチームに影響が出てしまいました。</p>
<p> 失敗してしまった時はとても不安と後悔でいっぱいで気が気でありませんでした。しかしさまざまな方に支えられたことによって、非常に前向きに乗り越えることができました。このインシデントは、対策の不備とオペレーションミスが重なった結果でしたが、失敗に対する自分やチームとしての向き合い方、そしてオペレーションに対するリスク管理など、エンジニアの一人として得た学びがたくさんありました。貴重な苦い経験として今後のエンジニアリング人生の教訓としていきたいです。</p>
<h2>12月・Advent Calendar ← 現在地</h2>
<p> Office WeekにてAdvent Calendarの存在を知り、インターン生としてAdvent Calendarに急遽参加させていただくことになりました。合計2つ枠を頂き、息を切らしながらも頑張って執筆しています ✏️</p>
<p>この記事は2本目で、1本目は以下です!<br />
<a href="https://engineering.mercari.com/blog/entry/20231204-k8s-understanding-pdb/">動作例からKubernetes PDBの挙動を理解する | メルカリエンジニアリング</a></p>
<h1>働く環境</h1>
<p> メルカリグループでは、 <a href="https://careers.mercari.com/jp/your-choice/">YOUR CHOICE</a>というワークスタイルを採用していて、「バリュー発揮がもっとも高まるワークスタイルを、自ら選択して決めることができる」というポリシーのもと、完全フレックス制で働く環境を選んだり時間を調整できたりします。インターン生の中でも働き方はさまざまなようで、基本オンラインで働いている方もいます。</p>
<p> 私はオフィス出社が好きなので、週1回六本木オフィスに出社しています。オフィスの居心地はなかなか快適だと思います。メルカリグループのオフィスは基本的にフリーアドレスなので、時々場所を変えたり必要に応じて個室を利用したりと、自由にスペースを利用させてもらっています。</p>
<p> 自分は大のコーヒー好きなのですが、社内にカフェがあり本格的でさまざまなブレンドコーヒーを低価格で飲むことができるので、私のコーヒー欲求は簡単に満たすことができます。社内の自動販売機は無料で利用できるので基本飲み物には困らないはずです。</p>
<p><iframe loading="lazy" width="560" height="315" src="https://www.youtube.com/embed/vSFwI9HFwFA?si=oFfsPWY1dqYEqctz" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe></p>
<p>メルカリ本社オフィスがアップデート! Mercari Base Tokyoに潜入!新しい時代のオフィスの形とは?</p>
<h1>メルカリグループでのインターンの特徴</h1>
<h2>プロフェッショナル性が求められる</h2>
<p> メルカリバリューの一つに「Be a Pro」というものがあり、インターン生も例外なくプロ意識を持って自ら考え、適切な判断を下すことが求められます。原則ある程度の知識やスキルがある状態で自分の知識を活かしてタスクに取り組むことが求められるため、かなり緊張感を持って取り組むことができます。<br />
メルカリバリューには他に「Go Bold」「All for One」があり、社員と同様これらを意識してインターンに取り組むことが求められます。</p>
<h2>多様なコミュニケーションの機会</h2>
<p> メルカリグループでは社内のコミュニケーションの場を非常に大切にしていて、そのためのイベントや制度が多く存在します。基本的にインターン生は社員と同じように活動でき、社内のさまざまなイベントに参加したり制度を利用したりすることができます。社員のコミュニケーションを促進するための制度が多くあるため、インターン生にとっては非常に貴重な機会だと思います。<br />
制度の一つに、食事代を会社が補助する制度があります。インターン生は以下の制度が利用できます。</p>
<ul>
<li>メンターランチ<br />
新入社員やインターン生がメンターと一緒に社内の色々なチームの人とランチ。</li>
<li>ウェルカムランチ<br />
入社日直後に実施されるランチ会。</li>
<li>インターン生ランチ会<br />
インターン生のみで毎月4名程度でランチ会が開催されます。メルカリグループのインターン生の人数はかなり多いので、インターン生同士で情報交換をする機会に恵まれています。</li>
<li>チームビルティング<br />
自分の所属するチームでは毎月1回、所属するチームで食事会があり、他のチームの人を招くこともあります。</li>
</ul>
<p> また、部活動というものがあります。部活動は趣味を通じてさまざまな人と良い関係性を築くことを目的としたもので、誰もが設立したり参加したりすることができます。自分はつい先日CTF(capture the flagというセキュリティコンテスト)のグループに参加していて、早速社員の方と12月中旬にCTFに参加することになり、とてもワクワクしています!<br />
その他にも社内ではさまざまなイベントが存在します。</p>
<h2>英語でのコミュニケーションに挑戦できる</h2>
<p> メルカリグループのエンジニアリング組織の約半分は外国籍です。チームによって英語を使ったり日本語を使ったりさまざまです。自分のチームは日本人が多いですが、週替わりでEnglish Weekという英語を推奨する期間を設けています。</p>
<p> 特に、英語でSlackで返信を打つ時や開発でPull Requestを出す時などは、どのように表現したら相手に伝わりやすいかを必死に考えるので英語を鍛える良い機会になると思います。他にも英語でディスカッションすることができたり、一部の会議では同時通訳を聞くことができたり、などなど、なかなか経験することのできない機会が盛りだくさんです。</p>
<h2>大規模かつ運用年数のあるシステム開発ならではの経験</h2>
<p> メルカリグループのインターンでは、フルスクラッチで何かを開発することは少ないかもしれません。私のチームでは、運用されてから数年が経過したマイクロサービスを扱い、過去の歴史的経緯による技術的負債に多く直面しました。割り振られたタスクでは、その負債の影響を正しく理解し、最適な解決策を見つけることが求められます。</p>
<p> 私が初期に担当したタスクの一つでは、技術的負債が絡む部分があり、それに対して関連する箇所を全て洗い出し根本から修正する方針で進めました。その結果、変更箇所が大きくなりすぎてタスクの収拾がつかない状況になったことがありました。技術的負債が存在する場合でも、一気にすべて解消しようとすると、レビューが困難になったり、変更ミスが生じるリスクがあります。メンターからのアドバイスを受けたことで適切に選択を行い、解決策を提案することができました。</p>
<p> また、メルペイのマイクロサービスはかなり大規模なので、仕様を変更する時は、各マイクロサービスの担当チーム間で適切なコミュニケーションを取り連携していく必要があります。大きな組織の中で複数のチームをまたいでのコミュニケーションを実践できる機会があるのも魅力だと思います。</p>
<h1>終わりに</h1>
<p> 改めてメルカリグループでのインターンは自分次第でたくさんの挑戦ができる、最高の機会だと思います。技術的なノウハウのみならず、コミュニケーションや仕事に対する考え方など、一人前のエンジニアとしての成長を促す多様な経験が得られます。</p>
<p> あと1ヶ月弱期間が残っていますが、今回のAdvent Calendarでの振り返りを元に残りの期間で、チームメンバーの一員としてプロ意識を持って積極的に貢献するとともに、最大限機会を活用してメルペイでのインターンを楽しみたいと思います。</p>
<p> 今回のこの記事がメルカリグループのインターンを検討している方の参考になれば幸いです。</p>
<p> 現在、インターンを通年募集していますので、興味を持たれた方はぜひ以下からぜひ申し込んでみてください!</p>
<p><a href="https://careers.mercari.com/jp/students/">Students | 採用情報</a></p>
<p>明日の記事は champon さんです。引き続きお楽しみください。</p>
- 英語が苦手なエンジニアがメルカリに入ってどうなったかhttps://engineering.mercari.com/blog/entry/20231207-mercari-advent-calendar-day7/https://engineering.mercari.com/blog/entry/20231207-mercari-advent-calendar-day7/<p>この記事は、Mercari Advent Calendar 2023 の7日目の記事です。 こんにちは!メルカリの Search Middleware チームで Software Engineer をしている @otte […]</p>
Thu, 07 Dec 2023 11:00:47 GMT<p>この記事は、<a href="https://engineering.mercari.com/blog/entry/20231124-mercari-advent-calendar-2023/">Mercari Advent Calendar 2023</a> の7日目の記事です。</p>
<p>こんにちは!メルカリの Search Middleware チームで Software Engineer をしている <a href="https://twitter.com/omohayui">@otter</a> です。</p>
<p>ご存じの方も多いとは思いますが、メルカリのエンジニア組織ではグローバル化が進んでおり、チームにもよりますがコミュニケーションやドキュメントではほぼ英語が必須な環境になっています。<br />
そのような環境のメルカリに英語がほとんど話せない私が入社してから4年が過ぎました。会社の環境も私自身も変わってきており、そこで得られたものや感じたものを紹介していきたいと思います。</p>
<p>どんな人にこの記事を読んでほしいか?</p>
<ul>
<li>英語を使った環境で仕事をしてみたい人</li>
<li>仕事で英語を使っているがコミュニケーション方法の参考にしたい人</li>
<li>組織のグローバル化を検討している人</li>
</ul>
<h2>入社前のモチベーション</h2>
<p>前職では某IT企業でゲームプラットフォームの開発をしていました。コミュニケーションは全て日本語で、技術関連の英語のドキュメントを読むことはよくあるのですが、ドキュメント作成もほとんどが日本語でした。</p>
<p>ただ、エンジニアとして成長するには英語は必要だし、英語を使う環境で働きたいなと考え始め、元同僚からのお誘いもあり転職を決めました。</p>
<p>今考えると、全然話せもしないのに転職しようとした自分は少し無鉄砲だったと思いますが、当時はグローバル化推進への転換時期だったので日本語面接で採用されました。<br />
また、転職を考え始めたタイミングでオンライン英会話を始め、有給消化期間中にニュージーランドのクライストチャーチで3週間だけ語学学校に通いました。</p>
<h2>入社直後どうやって切り抜けたか</h2>
<p>直前の語学学校やオンライン英会話はあまり役に立ちませんでした。<br />
まず、入社当日のオリエンテーションや全体向けの会議にはありがたいことに GOT (Global Operations Team、メルカリの翻訳および通訳を行うチーム) の同時通訳が入っており、日本語で説明を聞くことができました。また、配属先のマネージャーとメンターが日本人だったので個別に相談するときは日本語を使っていました。</p>
<p>ただ、入社当日のウェルカムランチでは多様な出身のメンバー同士の会話が、それぞれの訛りもあり、早すぎてついていけず本当に面食らいました。当たり前ですがオンライン英会話の先生のように話す人なんて現場にはいません。近年はインド人の同僚と一緒に仕事することが多いので、Indian English には慣れていますが、最初の頃は全く聞き取ることができませんでした。</p>
<p>では、どうすればいいのか?一朝一夕でリスニング力をあげることはできません。私はツールに頼りました。Google Meet には英語字幕を付ける機能があるので、会議中は字幕をひたすら読みます。最近は自動翻訳機能も追加されましたが、そちらはまだ精度が高くないのでおすすめできません。</p>
<p>またオンライン会議ではなく対面のオフライン会議の場合でも、とりあえず自分のPCで Google Meet を開いておけばマイクが周りの会話を拾ってくれるので字幕を参考にしながら会議に参加することができます。目の前に人がいるのに画面ばかり見るのは難しいので、おすすめ度は低いですが参考までに。</p>
<h2>メルカリの言語学習サポート</h2>
<p>メルカリでは公用語を英語と決められている訳ではありません。そのため言語学習は強制されるものではなく業務上必要と判断されたメンバーが受けられるサポートという位置づけです。また、日本語を学びたいメンバーが受けられる日本語学習サポートも同様にあります。</p>
<p>「やさしい日本語」や「やさしい英語」といったカルチャーもメルカリならではで、全員の言語スキルを上げさせるのではなく言語ギャップをお互い埋めていこうという方針があります。</p>
<p>私が今まで英語学習に関して受けた恩恵は下記です。</p>
<ul>
<li>オンライン英会話の費用全負担</li>
<li>MECT (Mercari English Communication Test)
<ul>
<li>コミュニケーションスキルレベルを知るための独自のスピーキングテスト</li>
</ul>
</li>
<li>独自の英語学習プログラム
<ul>
<li>専任の先生とメルカリでの仕事に合わせた教材でレッスン</li>
</ul>
</li>
<li>English Chat Lunch
<ul>
<li>ネイティブスピーカーのチームリーダー1名と学習者3名で毎週ランチ</li>
</ul>
</li>
<li><a href="https://quizlet.com/merletlists/folders/engineer-vocabulary-lists/sets">エンジニアのための英語・日本語ボキャブラリーリスト</a>
<ul>
<li>社内ミーティングの会話から抽出されたエンジニアがよく使う語彙のリスト</li>
<li>日本語学習者にも最適な学習ツール</li>
</ul>
</li>
</ul>
<p>各サポートは都度内容見直され更新されているので現在と異なるものもあり、私が受けたことがないものもあります。</p>
<p><a href="https://careers.mercari.com/jp/language/">(参考) 言語学習プログラム</a></p>
<h2>コミュニケーション力をどうやってあげていくか</h2>
<p>あえて英語力ではなくコミュニケーション力と書きました。私はUSで働いている訳でも外資系企業で働いている訳でもありません。本当にネイティブな英語話者は少なく、ほとんどのメンバーが英語を第二言語としている人たちばかりです。なのであまり難しい単語やフレーズを覚える必要はなく、実際に周りが使っている言葉を真似していく方が近道です。とくに仕事で使う内容は限られているので、各種学習サポートはあるのですが、実際に仕事で積極的に使って行く方がコミュニケーション力は上がると思います。</p>
<p>おすすめは少人数のミーティングに頻繁に参加することです。以前、インド人の新卒メンバーと毎日ミーティングの時間を作って、話しながら一緒に仕事をしていました。1対1だと「やさしい英語」で話してもらえるし、自分の発言するタイミングが増えるので、話す練習にもなります。</p>
<p>また大事なのは、説明が難しい内容のときは必ずドキュメントを作って挑むということです。<br />
これは英語に限らずだと思いますが、たとえば、他チームに複雑なシステムの説明を1からするときは、予め詳細なドキュメントを準備してから説明します。そして上手く説明できなかったところは後から Slack で補足したり、こう話せばよかったという反省は次回への文章づくりに活かしたりします。</p>
<p>前述のようにコミュニケーションは口頭の会話だけではありません。Slack や PullRequest 上でも、周りから学ぶことが多々あります。入社してすぐ驚いたのは飛び交っている Acronyms (頭字語、イニシャルを並べた略語の一種) の多さです。初めて見るものばかりだったので、ググってはリスト化していました。その一部を紹介します。</p>
<p>【頻出 Acronyms in メルカリ】</p>
<ul>
<li>OOO = out of office (Slack名やステータスで使う、例: @otter – Dec 7th OoO)</li>
<li>PTAL = please take a look (このPullRequestを見て!というときに)</li>
<li>BTW = by the way (話を切り替えたいときに)</li>
<li>IMHO = In my humble opinion (私の率直な意見では)</li>
<li>TIL = today I learned (それ初めて知った、というときに)</li>
<li>TBH = to be honest (正直なところ)</li>
<li>TBD = to be determined (仕様書や設計書の未定義の箇所に使う)</li>
<li>BRB = be right back (会議の途中で一時的に抜けるときに)</li>
<li>AFAIK = as far as I know (私の知る限りは)</li>
<li>IIUC = if I understand correctly (私の理解が正しければ)</li>
<li>IIRC = if I remember correctly (私の記憶が正しければ)</li>
<li>SSIA = subject says it all (タイトルだけで説明が不要な小さなPullRequestのDescriptionに使う)</li>
</ul>
<p>これ以外にも略語ではないのですがよく使う言葉としては NIT/NITS (PullRequest上で些細な指摘をするときに使う) などがあり、思っていたよりたくさんの新しい用語を覚える必要がありました。</p>
<h2>そして現在は</h2>
<p>この4年間で直属のマネージャーは日本人→スペイン人→フランス人→スウェーデン人と変遷し、日本語が飛び交っていたチームも9人中6人がグローバルメンバーになりチーム会議も100%英語となりました。未だに私の英語はお世辞でも上手とは言えませんが、コミュニケーションの工夫と周りの温かいサポートのおかげで今ではそのグローバルなチームのTL(Tech Lead)もしています。</p>
<p>また英語を使う機会が増えるとともに人脈も仕事の種類も増えてきました。言語の壁があると他のチームとコラボレーションにも支障が出るし、新規プロジェクトからの声もかかりづらくなります。私の観測した限りでは両言語とも流暢に話せてコミュニケーション力が高いエンジニアは各所から引く手あまたです。私も現在全社規模のプロジェクトに参加しており、今まで自分の領域である検索機能まわりを中心に仕事をしていましたが、違う領域のチームに参加したり、別事業のヘルプに呼ばれることもあり、充実した働き方ができています。</p>
<p>メルカリ全体としても英語だけをサポートするわけではなく、言語のギャップをなくそうという取り組みがさらに進んでいる実感があります。以前は広範囲にアナウンスされる内容が日本語だけだったり、英語だけだったりということがよくありましたが、今では両言語でアナウンスされることが徹底されており、片方の言語だけだったとしても瞬時にbotが自動翻訳してくれる仕組みがあります。</p>
<p>さらに私事ですが、昨年はイタリア人のエンジニアと結婚し長男を出産しました。なので家でも基本は「やさしい英語」です。業務では絶対に使わないだろうという単語を頻繁に使うことになるので、それはそれで面白いです。息子もそろそろ言葉を覚えていく時期なので一緒に学んでいこうと思いますが、きっとあっという間に追い抜かされるでしょう。今から楽しみです。</p>
<p>最後になりますが、この4年の間に私が感じた、これから英語を扱う環境に飛び込んでいこうとする方たちの英語学習に関して重要だと思った点をあげておきます。</p>
<ul>
<li>入社前のオンライン英会話は業務の英語にはあまり役に立たない</li>
<li>聞き取れないときはツールに頼ろう (Google Meet の英語字幕機能)</li>
<li>難しい英単語やフレーズを覚える必要はない</li>
<li>実際に周りが使っている言葉を真似していく方が近道</li>
<li>言語のギャップをなくそうという取り組みが大事</li>
</ul>
<p>それでは最後まで読んでいただきありがとうございました!何かひとつでも参考になれば幸いです。</p>
<p>明日の記事は @wills さんです。引き続きお楽しみください!</p>
- 強いエンジニア組織に必要な、6つの技術以外のこと – メルカリ編https://engineering.mercari.com/blog/entry/20231206-4e4f1e2323/https://engineering.mercari.com/blog/entry/20231206-4e4f1e2323/<p>はじめに メルカリ Engineering Office マネージャーのhiroiです。 我々のチームでは「Establish a Resilient Engineering Organization」というミッションを […]</p>
Wed, 06 Dec 2023 11:00:55 GMT<h2>はじめに</h2>
<p>メルカリ Engineering Office マネージャーのhiroiです。<br />
我々のチームでは「Establish a Resilient Engineering Organization」というミッションを元に、エンジニアリングにおける、組織横断課題の解決を目指しています。</p>
<p>組織横断というと、Platformチームや、インフラ周りのチームを想像する方も多いと思いますが、我々のチームでは、<strong>プロダクト開発における技術的な課題を除く</strong>、組織課題や横断的な取り組みを推進しています。</p>
<p>具体的には、各技術領域ごとの研修プログラムの構築、エンジニア向けのイベント企画運営、技術広報(このEngineering Websiteも我々の活動の一つです)、ナレッジマネジメント、エンジニア文化の言語化や醸成、技術戦略策定、果てはインド開発支部の立ち上げのプロマネなどをしています。</p>
<p>この記事ではそんな我々の主な活動の内容、目的の紹介をします。<br />
開発や技術力の高い組織を目指すために、その裏でどんな技術外の仕事や工夫を、メルカリではしているのか、そんなちょっとニッチな領域における話を書いてみます。</p>
<h2>こんな人におすすめ</h2>
<p>エンジニア組織の技術以外の課題に取り組んでいる方はもちろん、そういった取り組みに興味があるマネジメント職の方におすすめです。特に上記に記載したような仕事(研修、イベント企画、技術広報、ナレッジマネジメントなどなど)のワードが気になる方、是非ご一読ください。</p>
<p>規模が一定以上の組織固有で発生する課題もありますが、どんな組織にも共通している取り組みも含まれています。特に規模が近い会社の方におすすめですが、どのサイズの組織でも、「その課題わかる!その仕事あるよね!」とある程度共感いただける内容を目指します。</p>
<h2>6つの重点領域</h2>
<p>Engineering Officeの仕事は、大きく以下の6つの領域にカテゴライズされています</p>
<p>Tech Branding<br />
Internal Communication<br />
Onboarding<br />
Career Development<br />
Knowledge Management<br />
Strategy</p>
<p>メンバーはジェネラリストですが、それぞれの領域に対して専門性をもって取り組んでいます。各領域ごとに3~5年の中長期計画があり、その達成を目指して日々の仕事を進めるというやり方をとっています。今回は領域ごとのミッション、主な仕事の2点を説明していきます</p>
<h2>Tech Branding</h2>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/75d17a63-image1-1024x602.png" /></p>
<div style="text-align: center">
<a href="https://www.youtube.com/c/MercariGears">Mercari Gears – Youtube。</a>手前味噌で恐縮ですが、本当に良いコンテンツを発信しています
</div>
<h3>ミッション</h3>
<p>技術広報です。技術発信や、カンファレンスやOSSのスポンサーもこの領域です。主に以下の3つを目的としています。<br />
・採用のためのブランディング<br />
・技術コミュニティへの貢献<br />
・エンジニアのキャリアアップ</p>
<p>1点目、一番わかりやすいゴールですが、採用のためのブランディングがあげられます。採用候補者となるエンジニアにメルカリが使っている技術や、エンジニア文化に興味・関心をもってもらい、「技術力が高いエンジニア組織だ」「こういった文化のところで働きたい」といった認知を獲得することを目的としています。</p>
<p>2点目は技術コミュニティへの貢献です。エンジニアという業界の発展のスピードの大元には、そのコミュニティの強さ、知識や成果物の共有文化があります。実際、メルカリのアウトプットは、過去のエンジニアの経験や知見やOSSといった、技術コミュニティが築いてきた資産の上に成り立っています。私たちの経験や知見も同様に、誰かのアウトプットに繋げることで、コミュニティの発展に貢献、恩返しが出来ます。そのため、コミュニティへの貢献を2つ目の重要な目的としています。</p>
<p>最後に、エンジニアのキャリアアップです。エンジニアは、技術発信をする中で、思考の整理が行われ、学びを深めることができます。また、発信したものはそのエンジニアの社外へのアウトプットとなり、社内外の自身のブランディングに繋がります。社内においても、外部へのアウトプットは明確に評価の対象物になりうるため、キャリアに繋がります。これを3つ目の目的としています。</p>
<p>以上3点を中心に、メルカリでは、技術発信によって会社、発信者、コミュニティの三方良しの状態を目指しています。</p>
<h3>主な仕事</h3>
<p>・技術発信の媒体運営、管理(Engineering Website、Gears Youtubeチャンネル、Twitterを始めとするSNSアカウントなど)<br />
・結果の見える化とPDCA(来訪者数、チャンネル登録者数、国別アクセス、etc…)<br />
・Engineering Blogのレビューの仕組み化<br />
・技術発信に対する広告運用<br />
・OSSや技術コミュニティ、カンファレンスへのスポンサーシップ<br />
・発信内容の企画、制作</p>
<p>技術発信はメルカリにおいて仕事として認められますが、もちろん強制するわけではないですし、何を書くかといったのも個々のエンジニアの裁量です。Engineering Officeは、エンジニアが発信しやすい環境、そしてエンジニアの発信の価値が最大化されるような環境作りを行っています。</p>
<p>また、スポンサーシップとして、メルカリがお世話になっている<a href="https://www.youtube.com/watch?v=nwkLfN0RNfU" title="技術カンファレンスへの協賛">技術カンファレンスへの協賛</a>はもちろん、<a href="https://engineering.mercari.com/blog/entry/20220315-mercari-now-sponsoring-python-and-php/" title="PHP FoundationやPython Foundaitionといった団体へのスポンサー">PHP FoundationやPython Foundaitionといった団体へのスポンサー</a>などに関しても、我々の方で提案、実施を進めています。</p>
<p>ちなみに写真にある<a href="https://www.youtube.com/c/MercariGears" title="Mercari Gears - Youtube">Mercari Gears – Youtube</a>はYouTube Channelですが、最近登録者数が7万人を超えました。国内のテックカンパニーが運営している技術チャンネルの中では最大規模です。豪華なコンテンツが盛りだくさんなので、見たことがない方は是非。</p>
<h2>Internal Communication</h2>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/98d27ab5-image4-1024x576.png" alt="" /></p>
<div style="text-align: center">
技術の祭典であるHack Fest。毎回デザイナーさんが素敵な扉絵を作ってくれています
</div>
<h3>ミッション</h3>
<p>サイロ化の防止、チーム間のコラボレーションの増加、強固な文化形成による、全体最適をミッションとしています。組織を見渡した際、チーム間での協力がスムーズに行われ、ナレッジのシェア、同じ文化の形成が出来ている状態が理想です。</p>
<p>一定以上の規模の組織になると、サイズが大きくなればなるほど、個別最適が発生しやすくなります。カルチャーや考え方も、チーム間で少しづつズレが生まれやすくなります。チームの一員という誇りは重要ですが、隣のチームが同じ目標、ミッションのために働いている仲間だという意識も同じく重要です。</p>
<p>これに対する一番の解決策はコミュニケーションだと考えています。チーム間、もしくはレイヤー間で発生する課題は、見えているものや、抱えている背景が違うことに起因することが多く、多くの場合、丁寧なコミュニケーションによる相互理解を進めることで、解消可能です。</p>
<p>Engineering Officeでは、普段接点を持ちづらい人同士の対話、コミュニケーションや、リーダーからの発信が進んでいく仕掛け作りをしています。</p>
<h3>主な仕事</h3>
<p>・Engineering All Hands(全エンジニアが参加するMeeting)の企画運営<br />
・グループ横断の技術会議の企画運営<br />
・EM向けオフサイトの企画運営<br />
・Hack Festの企画運営<br />
・Ask Me Anything(トップエンジニアとのOpen Discussion)の企画運営</p>
<p>さまざまな会議の企画運営に加え、全エンジニアが開発を止め、自身のアイデアを元に好きなものを作る、<a href="https://engineering.mercari.com/blog/entry/20230410-b286fe9577/" title="技術の祭典、Hack Festの運営">技術の祭典、Hack Festの運営</a>。社内のDistinguished Engineerをはじめとする、トップ層のエンジニアと、カジュアルに技術についてDiscussionを行う場の提供などをしています。技術に関して横断的に話す場だけでなく、リーダー達の考え方、思いを直接聞ける、話せる場を作るというのが重要です。</p>
<h2>Onboarding</h2>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/e47dccee-image2-1024x473.png" alt="" /></p>
<div style="text-align: center">
メルカリのOnboardingの主な区分け。詳しくは<a href="https://engineering.mercari.com/blog/entry/20220623-sustainable-remote-onboarding-for-engineers/">こちら</a></div>
<h3>ミッション</h3>
<p>入社後の戦力化までの時間の最短化、共通化やクオリティの向上による学習コンテンツの価値向上と効率化、ガバナンスの強化に加え、入社時に最高の従業員体験を提供することを目的としています。</p>
<p>特定のドメインナレッジ等、チーム特有で学ばなければいけないことはもちろんありますが、開発環境の作り方、コーディングのお作法、リリースサイクル、QAの考え方、インシデントマネジメントなど、チームを超えて共通する内容も多く、横断チームによる管理運営が適しています。コンテンツの集約により、重複や再作成の防止に加え、クオリティの向上が見込めます。</p>
<p>また、エンジニアという比較的転職サイクルが早い職種においては、いかに早く戦力化するか、という重要性が他の職種と比べ高いと考えています。一般的にOnboardingは3ヶ月〜6ヶ月かかると言われています。この最長と最短の差分である3ヶ月の差について、もし社員が3年勤続する場合、Onboarding終了後の実働が33ヶ月と30ヶ月になり、誤差がなんと10%程もあります。勤続年数が短い傾向にあるエンジニアにとって、Onboardingの速度は大きなインパクトがあります。</p>
<p>また、ガバナンスの強化においてもOnboardingは重要です。特に中途採用を行う際は、過去の組織の働き方から新しい組織の働き方へと移行するためのアンラーニングをきちんと行わないと、複数のお作法が意図せずして組織に定着し、さまざまな弊害を生む原因となります。出社自由といった制度などもそうですが、メルカリは自由度が高い会社なので、多くを厳格に標準化するわけではないですが、一定の標準化は必要です。入社してすぐは、比較的フレッシュなマインドで新しいことを受け入れ、過去の組織のやり方から脱却しやすい時期です。標準化が必要なものに関しては、この時期にきちんと学習できるプログラムを提供できることを目指しています。</p>
<p>最後に、第一印象、つまり入社時の体験はとてもとても重要です。メルカリに入社した全てのエンジニアが、入って良かった、歓迎されている、誇りをもてる、そういった気持ちになれるようなプログラムを目指しています。</p>
<h3>主な仕事</h3>
<p>・全エンジニア向けオンボーディングプログラム作成<br />
・各技術領域(Backend、iOS、Androidなど)ごとのオンボーディングプログラム作成<br />
・EMのオンボーディングプログラム作成<br />
・オンボーディングプログラムの社外発信</p>
<p>基本的には技術、職種ごとに必要なOnboardingの集約、プログラムの作成、提供を行っています。それぞれの技術ごとにコミッティーを形成し、様々なチームのエンジニアが協力してコンテンツの作成を担当しています。多くのコンテンツはメルカリでないエンジニアが見ても面白いものになっています。そのため、一部のConfidentialな情報をのぞいた上で、出来る限り社外にオープンにしていくというチャレンジを最近では推し進めています。コンテンツ作成に協力してくれているエンジニアにとっても、社外のオーディエンスが増えるのはプラスになるので、この取り組みは今後も強化していきます。</p>
<h2>Career Development</h2>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/4cf9bdcd-image3-1024x383.png" alt="" /></p>
<div style="text-align: center">
エンジニアの成長段階を<a href="https://engineering.mercari.com/ladder/">Engineering Ladder</a>によって言語化をすすめています
</div>
<h3>ミッション</h3>
<p>エンジニアのキャリアの可視化や、キャリアアップのための環境整備を進めている領域です。<br />
エンジニアは自分で積極的に学習する人が非常に多いため、学習を我々が推し進めるのではなく、その学習のサポートとなるような環境、制度を提供しています。また、キャリアアップも同じく、推し進めるというよりは、キャリアアップを目指す人たちをどのようにサポートできるか、という観点から、環境の整備を行っています。</p>
<h3>主な仕事</h3>
<p>・<a href="https://engineering.mercari.com/ladder/">Engineering Ladder</a>の作成・保守<br />
・<a href="https://engineering.mercari.com/blog/entry/20211206-15c9c9dc16/">Continuous Feedback</a>の仕組み化、推進<br />
・職種(EMなど)の定義、言語化<br />
・オンライン学習コンテンツの提供<br />
・社外カンファレンス参加の推進、ポリシー作成</p>
<p>グレードや職種の言語化に加え、外部のエンジニア向け学習サービスの管理、社外カンファレンス参加におけるポリシー作成などを行っています。特にマーケットプレイスのエンジニアは、海外国籍のエンジニアが半分を超えているため、海外カンファレンスの参加も多く、費用もそれなりに高額になりやすいため、各カンファレンスの参加人数の上限を設ける、カンファレンス参加後にナレッジをシェアしてもらう、といったポリシーを定めています。</p>
<p>社内のキャリアアップに関しては、一番重要なのは現在地を正しく知ることだと考えています。今の自分に何が足りないのか、次のグレードにはどんなスキル、行動が求められるのかを知ることで、どんな学習、チャレンジが必要なのかというアクションを考えられます。そのため、キャリアの言語化、可視化に加え、Continuous Feedbackのような、マネージャーから適切なフィードバックを得られやすいようにするための仕組み作りを目指しています。</p>
<h2>Knowledge Management</h2>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/28fd8a23-image5-1024x506.png" alt="" /></p>
<div style="text-align: center">
エンジニア用の社内辞書。多くのエンジニアがコントリビュートしてくれています
</div>
<h3>ミッション</h3>
<p>社内におけるナレッジの最大化をミッションとする領域です。Engineering Officeは多くのメンバーがジェネラリストですが、この領域に関しては、Technical Writerという職種のメンバーが専属で推し進めてくれています。</p>
<p>メルカリではオンライン中心で働くエンジニアが非常に多いため、以前のような「隣の人にちょっと聞く」というコミュニケーションから「社内のWikiで調べる」といった行動が増えており、ナレッジをドキュメント化し、蓄積する重要性が高まっています。</p>
<p>また、プロダクトも10年目ということもあり、一目ソースコードを見ただけではわからないような、いわゆる歴史的経緯などが多く存在します。ナレッジは言語化を進める事で、特定の人に依存する状態から、社内のナレッジへと変わります。社内のナレッジが必要な時に、必要な人のもとに届く状態を目指しています。</p>
<h3>主な仕事</h3>
<p>・社内のエンジニア向けポータルの管理<br />
・ポータルの利用、コントリビューションの可視化と最大化<br />
・ナレッジの収集、発信<br />
・ポータルのポリシーの仕組み作り、運用</p>
<p>一番メインとなっている活動が、社内におけるエンジニア向けのポータルの管理です。そこに価値のあるナレッジを収集し、必要としている多くのエンジニアに届けられる状態を作っています。例えば社内のエンジニア向けの辞書であったり、Onboardingコンテンツ、キャリアアップのために有用な情報や、エンジニア採用のプロセスやノウハウなどが整理されています。</p>
<p>もちろん、Knowledge Managementの担当者だけが全てのコンテンツを作ったり、アップデートしたりするのは不可能なため、一定のポリシーやルールを作り、運用するというのも大きな活動の一つです。どれくらいのエンジニアが見て、どれくらいのエンジニアがコンテンツにコントリビューションをしてくれているのか、というのを重要な指標としており、エンジニアが自然に日々の業務の中で、ナレッジをシェア、ドキュメント化しようと思えるような文化、環境を作ろうとしています。</p>
<h2>Strategy</h2>
<h3>ミッション</h3>
<p>エンジニアリングにおける戦略の言語化をすすめ、外部に発信したり、他部署に理解してもらえる状態を目指しています。Engineering Officeにおいて一番新しい取り組みです。</p>
<p>ビジネスは数年単位の戦略が存在するように、エンジニアリングにおいても、年単位の投資が必要な活動が多く存在します。過去の例でいうと、MobileとWebのリファクタリングプロジェクトや、ビジネスの共通基盤ドメインの大幅アップデートである<a href="https://mercan.mercari.com/articles/36640/">RFS</a>などがそれにあたります。こういった開発は、売上にすぐに繋がるような、緊急性の高いものではありませんが、将来の開発速度や、メンテナンスコストなどを踏まえると、非常に重要です。</p>
<p>これらの開発が考える重要な投資をエンジニア以外の方が理解しやすいような形で言語化し、エンジニア戦略として発信を行っています。それにより、他部署やプロダクトマネージャーにその重要性を理解を得て、売上をあげるような新規開発と、中長期を見据えた機能を直接増やすわけではない開発の適切なバランスが取れている状態を目指しています。</p>
<h3>主な仕事</h3>
<p>・技術課題における中長期計画作成のサポート・発信・運用<br />
・Engineering Principlesの作成のサポート、発信<br />
・Engineering OKRの作成のサポート・運用</p>
<p>もちろんこういった計画の中身自体はCTOをはじめとしたエンジニア部門のリーダーが中心となって考えますが、ボトムアップで必要な情報を集めたり、更新サイクルの作成、適切な発信を行っていくためのサポートをEngineering Officeでは行っています。</p>
<p>今回紹介した6つの領域はそれぞれ強く結びついています。例えばOnboardingで作成したコンテンツはTech Brandingを通して外部に発信されます。Strategyで決められた方向性やカルチャーは、Internal Communicationを通してEMやエンジニアに発信され、Onboardingを介して新しく入社するエンジニアに届けられます。また、新しい目指すべきカルチャーに沿って評価の定義が微調整され、Engineering Ladderによって、その見える化が進み、そのドキュメントはKnowledge Managementの元、管理、更新が徹底されます。メルカリにおけるEngineering Officeの強みは、これらの活動のシナジーを意図的に生み出せるところにあるかなと思っています。</p>
<h2>課題と展望</h2>
<p>以上がEngineering Officeがメルカリエンジニアリング組織横断で行っている仕事です。</p>
<p>どの活動もまだまだ道半ばで、やれること、やらなければいけないことが非常に多いです。特にオンラインとオフラインが入り混じるハイブリッド環境下でのコミュニケーションの促進、改善はいまだ試行錯誤しており、いまだに新しいチャレンジを繰り返しています。</p>
<p>また、Knowledge Managementの最適化は十分なスピードで行えておらず、コンテンツが過去のまま更新されずに利用されていたり、どこにドキュメントを書けばいいかわからないといった問い合わせがあったり、場合によってはフロー情報であるはずのSlackが、Wiki代わりに使われてしまって、誤った過去の情報を元に仕事が進められてしまったり、ということも発生しています。</p>
<p>プロダクトも大きくなり、フィンテック領域が黒字化し、会社としても少しづつステージや雰囲気が変わってきたなと内部からも感じることが増えています。今後も既存プロダクト、新規プロダクト開発での新しい技術的チャレンジは増えていきます。少しでも価値が早く提供できるように、それまでの道のりがエンジニアにとって良い体験になるように、今後も足元の課題解決を進めていければと思います。</p>
<p>長文になりましたが、読んでいただきありがとうございます。読者の方にとって、少しでも何かプラスになれば幸いです。メルカリ編と書きましたが、こういった話は中々表に出てこない領域でもあるので、他社の方で同じような取り組みをしている方がいれば是非書いてみてください!</p>
- メルペイに新卒入社して1年目にやったことhttps://engineering.mercari.com/blog/entry/20231206-what-i-did-in-my-first-year-after-joining-merpay-as-a-new-graduate/https://engineering.mercari.com/blog/entry/20231206-what-i-did-in-my-first-year-after-joining-merpay-as-a-new-graduate/<p>この記事は、Merpay Advent Calendar 2023 の6日目の記事です。 はじめに こんにちは、MerpayでBackend Engineerをしている@panoramaと申します。 今年(2023年)の […]</p>
Wed, 06 Dec 2023 10:00:31 GMT<p>この記事は、<a href="https://engineering.mercari.com/blog/entry/20231124-merpay-advent-calendar-2023/" title="Merpay Advent Calendar 2023">Merpay Advent Calendar 2023</a> の6日目の記事です。</p>
<h2>はじめに</h2>
<p>こんにちは、MerpayでBackend Engineerをしている<a href="https://twitter.com/panorama32_" title="@panorama">@panorama</a>と申します。<br />
今年(2023年)の4月に新卒として入社しました。<br />
今回は「メルペイに新卒入社して1年目にやったこと」という内容で、入社後から現在にかけてどのようなことをしてきたかご紹介したいと思います。<br />
今後入社される方やメルペイ新卒エンジニアの1年目の動きに興味がある方の参考になればと思います。</p>
<p>(一部プロジェクトは社外秘で内容は伏せています🙏)</p>
<p>それでは早速始めていきます。</p>
<h2>4月~6月</h2>
<h3>研修期間</h3>
<p>4月~5月の頭にかけてはメルカリグループの新卒研修である「DevDojo(※1)」を受けていました。<br />
DevDojoでは特定の分野に限らず Backend、 Frontend、 セキュリティ、 アーキテクチャ、 Spanner 、・・・ などさまざまな技術について学びます。<br />
また今年は新しい試みとしてマナー研修もありました。<br />
「人生に1度きりの新卒研修なのでマナーについて学んでおこう」というものです。<br />
名刺の渡し方や席の座り方(※2)、社内の人間の呼称など基本的なマナーを勉強しました。<br />
今年のDevDojoは取材も入っていて、上記のマナー研修の様子もYouTubeに上がっているのでご興味があればぜひ見てみてください。</p>
<p></p>
<div style="text-align: center;">
<iframe loading="lazy" width="560" height="315" src="https://www.youtube.com/embed/4a_PjEtKr7g?si=gZOvGpdQMMBtIFBz&controls=0" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
</div>
<h3>業務開始</h3>
<p>DevDojoが終わってからは本格的に業務に入っていきました。<br />
そのとき進んでいたプロジェクトとして「メルカード審査完了後に即座にカード番号を利用できるようにする」というものがあり、その機能の一部の実装を担当しました。<br />
今まではカードを利用するためにカードの到着を待つ必要があったのですが、上記のプロジェクトによってオンラインでの決済であれば審査後すぐにご利用いただけるようになりました。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/771e77f1-feature-to-enable-immediate-use-of-card-number-after-screening2.png" alt="審査完了後すぐカード番号を利用できる機能" /></p>
<p style="text-align: center;">審査完了後すぐカード番号が利用可能に</p>
<p>そのプロジェクトが終了してからは他の機能も実装しつつ、少し大きめの改善系のタスクなども実施していました。<br />
これに関しては以前記事(※3)を書いたため、気になる方はそちらをご覧ください。<br />
また上記の期間を通してドメイン知識のキャッチアップや運用の理解、QA環境の用意の仕方やリリース手順などBackendが関わる基本的な作業についても学んでいました。</p>
<p>※1 技術トレーニングDevDojoで実際に使用されている学習コンテンツを公開しています。<a href="https://engineering.mercari.com/learning-materials/" title="こちら">こちら</a>をご参照ください。<br />
※2 ちなみに余談ですが、筆者のpanoramaはこのマナー研修後にExec(役員)と新卒のランチ会に30分遅刻(😇)し、なぜか上座が空けられていたので座ったという体験をしています。そしてなぜか誰もそれを気にしていませんでした。(補足すると遅刻の理由は寝坊ではなく直前のMTGの終了時刻がランチの開始時刻であったことと、ヒルズを出た後迷子になったというダブルパンチによるものです。)<br />
※3 <a href="https://engineering.mercari.com/blog/entry/20230626-cloud-tasks-external-api-traffic-control/" title="Cloud Tasksで外部APIへの流量制御をするときに考えたこと">Cloud Tasksで外部APIへの流量制御をするときに考えたこと</a></p>
<h2>7月~9月</h2>
<p>7月からは業務の幅を広げて、お問い合わせの調査なども積極的に拾うようになりました。<br />
私のチームでは、お客さまからのお問い合わせは基本的にはCS(カスタマーサポート)チームが対応するのですが、サポートツール上からはわからない事象だった場合はBackendが調査を行う体制になっています。</p>
<p>また8月からはオンコールのシフトにも入るようになりました。<br />
オンコールとは「サービスのパフォーマンスが悪化したり、停止が疑われたりする場合に備えて担当者が常時対応できるようにしておく仕組み」です。<br />
メルカリグループではPagerDutyを使用してオンコールシフトを管理しており、PagerDutyに紐づけられた特定のDatadog Monitorが閾値を超えた場合、自動で担当者を呼び出す仕組みになっています。<br />
5月~6月にかけて知識のキャッチアップや運用の理解を進めてきたため、(難なくとは言いませんが)お問い合わせやオンコールに対応できるようになりました。<br />
(※またオンコールは万が一手に負えない場合、エスカレーションする仕組みがあります。)</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/e7ba52fe-oncall-tools.png" alt="オンコールで使用しているツール" /></p>
<div style="text-align: center;">
オンコールで使用しているツール<br />
(左: Datadog, 右: PagerDuty)<br />
(Datadog 画像 出典: https://www.datadoghq.com/ja/about/resources/)<br />
(PagerDuty 画像 出典: https://www.pagerduty.com/brand/)<br />
DatadogのロゴになっているワンちゃんはBitsという名前みたいです
</div>
<p>合わせて8月1日から入ってこられたインターン生(※4)のメンターにもなりました。<br />
メンターの主な役割はインターン生のサポートですが、メルカリグループでは新しくチームに入られた方に対してメンターランチ(※5)やチービルランチ(※6)を行う文化があるためそういったサポートも実施しました。<br />
インターン生の方はみなさんとても優秀ですが、社内の知識に関しては初めてのことばかりなので、メンターとしてタスクを適切に分割してお渡ししたり、実装に必要な知識をお伝えしたり、場合によっては巻き取って進められるように自分もタスクを追ったりしていました。</p>
<p>8月中旬以降は新しいプロジェクトに参加しました。<br />
そのプロジェクトは今までに経験したことのない別のチームと連携して行うもので、かつ延期が難しいハードスケジュールのものでした。<br />
自分のチームから担当としてアサインいただいたのですが、開発終盤で別のチームとの依存関係を見落としている箇所があることが発覚し、ギリギリで実装を追加することで乗り切りました。<br />
(他の開発チームと特にQAの方にご協力をいただいてなんとか無事故・不具合なしで期日内にリリースできました。)<br />
この経験以来、他チームとの調整がある場合にはなるべく早い段階で変更箇所を確認し、定期的にsyncしていく必要があることを意識するようになりました。</p>
<p>上記はプロジェクトの内容を伏せてしまっていますが、他にはFIDOの対応などもやっていました。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/61aada7f-fido2.png" alt="FIDO" /></p>
<p style="text-align: center;">FIDO認証画面</p>
<p>※4 参考: <a href="https://careers.mercari.com/jp/students/" title="mercari careers / Students">mercari careers / Students</a><br />
※5 メンターランチ: メンターとメンティー(新入社員・インターン生)とのランチ。チームメンバーや他チームを紹介がてら行います。<br />
※6 チービルランチ: 目的はメンターランチと同じでrelationship buildingですが、関係性はメンターとメンティーに限りません。</p>
<h2>10月~現在</h2>
<p>10月の中頃でメンティーだった方のインターン期間が終了しました。<br />
そして新しく動き始めたプロジェクトにBackendの担当としてアサインされました。<br />
このプロジェクトではまず機能自体の調査とそれを実現するための調査が必要でした。<br />
機能に関する資料と調査結果をチーム内で読み合わせ、ある程度固まったら関連するチームも巻き込んで共有会を行いました。<br />
担当のBackendとしてこの調査や会議のリードを任せていただき、現在は設計に入っています。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/101bc235-unipos.png" alt="unipos" /></p>
<p style="text-align: center;">ありがたきUnipos (社内投げ銭サービス(※7))</p>
<p>今まではタスクがアサインされた時点でやることが決まっていることが多かったのですが、今回は「何をどこまでやるのか」「どうすれば実現可能か」という段階からPMと一緒に関わらせていただいているので非常に新鮮に感じています。<br />
先述の通りまだ設計中で、その後にbreakdownして実装に入るので、引き続きプロジェクト達成に向けて努力していく所存です。<br />
また上記のプロジェクト以外だとBe a Pro Days(※8)と呼ばれる取り組みで自動化のためのBotを作ったり、キャンペーン施策のBackendの実装をいくつかやったりしました。</p>
<p>※7 参考: <a href="https://mercan.mercari.com/articles/24588/" title="メルカリのピアボーナス制度「メルチップ」、メッセージ累計数が100万を突破しました〜!#メルカリな日々">メルカリのピアボーナス制度「メルチップ」、メッセージ累計数が100万を突破しました〜!#メルカリな日々</a><br />
※8 参考: <a href="https://mercan.mercari.com/articles/36412/" title="「自分自身が発揮できる価値」に向き合う──1人ひとりがオーナーシップを持って課題解決を取り組むために導入した「CD Be a Pro Days」">「自分自身が発揮できる価値」に向き合う──1人ひとりがオーナーシップを持って課題解決を取り組むために導入した「CD Be a Pro Days」</a></p>
<h2>まとめ</h2>
<p>今回は私が新卒入社後にどのようなことをしていたか、(振り返りの意味も込めて)記事化してみました。<br />
入社前には自分が1年目でインターン生のメンターになったり、一部のプロジェクトのリードを任せてもらえるとは思っていなかったため本当に「Go Bold」の考え方が実践される素晴らしい環境だと感じました。<br />
また多少の修羅場(?)やさまざまなチームの方とのコミュニケーションの機会を通じて、技術一辺倒だった学生時代よりも色々なスキルを身につけた気がします。<br />
内容としては個人ブログのような部分も多かったかもしれませんが、社内レビューを通っているので”公認”で社内の話についていろいろ言及できた気がします。<br />
メルペイ(メルカリグループ)の新卒入社後について気になっている方がいて、具体例として参考になっていれば幸いです。<br />
それでは、ありがとうございました。</p>
<p>明日の記事は @Malli さん, @ben.hsieh さんです。引き続きお楽しみください。</p>
- Fintech Tech Talk at Office Week を開催したよ!https://engineering.mercari.com/blog/entry/20231205-fintech-techtalk/https://engineering.mercari.com/blog/entry/20231205-fintech-techtalk/<p>こんにちは。メルペイ Engineering Engagement チームの mikichin です。 この記事は、Merpay Advent Calendar 2023 の5日目の記事です。 11月のOffice We […]</p>
Tue, 05 Dec 2023 10:00:37 GMT<p>こんにちは。メルペイ Engineering Engagement チームの <a href="https://twitter.com/chida_miki" title="mikichin">mikichin</a> です。<br />
この記事は、<a href="https://engineering.mercari.com/blog/entry/20231124-merpay-advent-calendar-2023/">Merpay Advent Calendar 2023</a> の5日目の記事です。<br />
11月のOffice Weekで「Fintech Tech Talk at Office Week」を開催したので、その様子をお届けします。</p>
<h1>Fintech Tech Talk at Office Week について</h1>
<h2>Office Week とは?</h2>
<p>メルカリグループでは、「YOUR CHOICE」という制度があり、約90%以上のメンバーがリモートワークで働いています。<br />
関係性が薄くならないようにメルカリグループのFintech領域では「Office Week」というイベントをだいたい半期に1度開催し、なるべく出社し集まることでチーム内・他部署・経営とのコミュニケーション機会の創出、関係強化を図っています。</p>
<p><a href="https://about.mercari.com/press/news/articles/20210901_yourchoice/" title="メルカリ、多様な働き方を尊重した 「メルカリ・ニューノーマル・ワークスタイル “YOUR CHOICE”」の活用状況を公開">メルカリ、多様な働き方を尊重した 「メルカリ・ニューノーマル・ワークスタイル “YOUR CHOICE”」の活用状況を公開</a></p>
<h2>Fintech Tech Talk とは?</h2>
<p>エンジニアリング組織全体で得た知見を共有するLT大会です。</p>
<p>発表時間は5分、話すテーマは技術的な話はもちろん、趣味の話など何でもOKというゆるいルールです。<br />
リモートワークが中心となった今、オフラインで全体に知見を共有する機会も少なくなりました。そこで、他チームのメンバーや取り組みを知り、交流のきっかけになればと考えて運営しています。</p>
<h1>当日の様子について</h1>
<h2>どんなLTがあったのか</h2>
<p>内容は業務にとどまらず、興味のある領域も対象です。キーボードの話や直近チームで取り組んだプロジェクトについての話、メルカリグループの部活として行っている<a href="https://techbookfest.org/organization/47710001" title="技術書典">技術書典</a>の話など、さまざまなテーマのLTがありました。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/f0c8a79b-ltについて.png" alt="" /><br />
<img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/a88a0805--2023-12-01-18.11.52.png" alt="" /><br />
△メルペイVPoEのkeigowさんが「Introduction of Competitive Programming」の発表をしている様子</p>
<p>メルペイ Frontendエンジニアの @yutaro さんは、以前 <a href="https://mercari.connpass.com/event/298140/" title="社外イベント">社外イベント</a>で発表した「内製UIコンポーネントのアクセシビリティテストを支えるOSS」を社内用にアレンジして発表をしました。社外イベントの資料は公開していますので、興味がある方はぜひこちらをご確認ください。</p>
<p><iframe class="speakerdeck-iframe" frameborder="0" src="https://speakerdeck.com/player/fecafe40fdde4591ae6dd5342358aef1" title="内製UIコンポーネントのアクセシビリティテストを支えるOSS" allowfullscreen="true" style="border: 0px; background: padding-box padding-box rgba(0, 0, 0, 0.1); margin: 0px; padding: 0px; border-radius: 6px; box-shadow: rgba(0, 0, 0, 0.2) 0px 5px 40px; width: 100%; height: auto; aspect-ratio: 560 / 315;" data-ratio="1.7777777777777777"></iframe></p>
<p>メルペイ Backendエンジニアの @Liu さんが発表した「LINE 公式アカウントPJ」については、下記記事をご確認ください。</p>
<p><a href="https://engineering.mercari.com/en/blog/entry/20231212-flow-control-challenges-in-mercaris-line-integration/" title="Flow Control Challenges in Mercari’s LINE Integration">Flow Control Challenges in Mercari’s LINE Integration</a></p>
<h2>参加者の反応について</h2>
<p>イベントには、メルペイ新CEO @Takeshiさんをはじめエンジニアリング組織以外のメンバーも含め、100名以上が参加しました。</p>
<p>現在のメルカリグループは、約50カ国のメンバーで構成されています。本イベントに全員が参加できるよう、Global Operation Team(GOT)※ の同時通訳を入れ、登壇者の言語にあわせた言語で司会進行しました。</p>
<p>※:社内の通訳・翻訳を担当する専門チーム GOTの取り組みはぜひ、メルカン記事「<a href="https://mercan.mercari.com/articles/37731/" title="言語を活用してメルカリのビジネスやD&Iをサポート!──Global Operations Teamが提供する通訳・翻訳業務以上の価値">言語を活用してメルカリのビジネスやD&Iをサポート!──Global Operations Teamが提供する通訳・翻訳業務以上の価値</a>」をご確認ください。</p>
<p>イベント中、Slackには「アクセシビリティ、大事だよねぇ」「全然知らない内容でおもしろい」といった内容についての反応や「日本語だけではなく、英語のLTもあってよかった」「おもしろかった」といったイベント全体への感想が投稿されていました。</p>
<p>Office Weekにあわせて出社した社員はもちろん、Fintech Tech Talkはオンラインでの参加者もたくさんいました(Google Meetで社内向けに配信していました)。</p>
<h1>おわりに</h1>
<p>社内向けLT大会は、参加者が発表をきいて何かを得ることも大事ですが、それよりもわいわいみんなで楽しむことができること、気軽に発表をするという経験をすること、が大事かなと思っています。</p>
<p>今回、イベント当日の朝に飛び込みで登壇が決まったLTがありました。プレゼン資料はなく、完成したばかりのシステムの画面を使ってアップデート箇所を紹介するLTだったのですが、このLTもとても盛り上がっていました。こういうふうに気軽に発表でき、みんなで盛り上がる場所を今後もつくっていきたいなと思います。</p>
<p>明日の記事は panoramaさんです。引き続きお楽しみください。</p>
- 動作例からKubernetes PDBの挙動を理解するhttps://engineering.mercari.com/blog/entry/20231204-k8s-understanding-pdb/https://engineering.mercari.com/blog/entry/20231204-k8s-understanding-pdb/<p> こんにちは!横浜国立大学理工学部情報工学EP3年の @shion1305 です。今年の10月から株式会社メルペイ Settlementチームにてバックエンドエンジニアのインターンとして所属しています。 この記事は、Me […]</p>
Mon, 04 Dec 2023 10:00:14 GMT<p> こんにちは!横浜国立大学理工学部情報工学EP3年の <a href="https://twitter.com/shion1305">@shion1305</a> です。今年の10月から株式会社メルペイ Settlementチームにてバックエンドエンジニアのインターンとして所属しています。</p>
<p>この記事は、<a href="https://engineering.mercari.com/blog/entry/20231124-merpay-advent-calendar-2023/">Merpay Advent Calendar 2023</a> の4日目の記事です。</p>
<p>Settlementチームでは、メルペイ加盟店で発生した売上金を加盟店に振り込む際に、指示を出したりデータの管理を行ったりするマイクロサービスの開発を行っています。Settlementのマイクロサービスについては <a href="https://engineering.mercari.com/blog/entry/20231023-mmtf2023-day2-5/">こちらの記事</a> でも紹介されています。</p>
<p>私がJoinして早々担当したタスクの一つが、Settlementサービスに対するKubernetesのPod Distruption Budget(PDB)設定の見直しでした。Kubernetesについてはこれまで個人開発で少し調べたことがある程度でした。今回PDB見直しを行う中で仕様についてリサーチをしていたのですが、多くの記事がある中で実際の挙動について自分が思うような記事があまり見つかりませんでした。今回のAdvent Calendarを機に、自分なりにわかりやすくまとめてみることにしました。</p>
<p>本記事では、特に以下にフォーカスします。</p>
<ul>
<li>PDBの挙動</li>
<li>パーセント指定を用いた場合のPDBの動き</li>
<li>minAvailable maxUnavailable それぞれのポイント</li>
</ul>
<h1>Pod Disruption Budgetの概要</h1>
<p> PDB(Pod Disruption Budget)は、 <code>Voluntary Disruptions(システムの計画的な中断)</code> からアプリケーションの可用性を保護するKubernetesの機能です。 <code>Voluntary Disruption</code> の具体例としては以下が挙げられます。</p>
<ul>
<li><code>kubectl drain</code> でのNodeからのPod退避</li>
<li>Nodeスケールダウン時のPod退避</li>
<li>Node間のPodの移動</li>
</ul>
<p>(NodeからPodを他のNodeに退避させる操作を、以降Drain操作と表記します。)<br />
例えば、以下のように記述すると、 Voluntary Disruptions において、使用できないPodの割合を33%までとなるように指定できます。</p>
<pre><code class="language-yaml">apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: pdb-sample
namespace: namespace-sample
spec:
maxUnavailable: 33%
selector:
matchLabels:
app: pdb-tester</code></pre>
<p> PDBの設定においては、 <code>minAvailable</code> または <code>maxUnavailable</code> のいずれか一方を指定することが可能です。 <code>minAvailable</code> では、指定したPod群の中で常に稼働していなければならない最小限のPodの数または割合を指定でき、 <code>maxUnavailable</code> は、一度に中断または利用不可となっても良いPodの最大数または割合を指定できます。</p>
<p>自動スケールによってPod数が変動する場合において固定値を設定するのみではPod数に応じたPDBの挙動を設定することができないため、パーセント指定が用いられることがあります。実際、メルカリグループのマイクロサービスで設定されているPDBの多くでは、パーセント指定が用いられています。<br />
しかし、パーセント指定を行う場合、PDBがどのような挙動をとるのか、分かりにくくなってしまいがちです。</p>
<h1>パーセント指定におけるPDBの挙動</h1>
<h2>公式ドキュメントでの記載</h2>
<p>公式ドキュメントには以下のように、パーセント指定で中途半端な数になった時には数が繰り上げられることが明記されています。</p>
<blockquote>
<p>When you specify the value as a percentage, it may not map to an exact number of Pods.<br />
(中略..)<br />
Kubernetes rounds up to the nearest integer, so in this case, 4 Pods must be available. When you specify the value maxUnavailable as a percentage, Kubernetes rounds up the number of Pods that may be disrupted. Thereby a disruption can exceed your defined maxUnavailable percentage.<br />
<a href="https://kubernetes.io/docs/tasks/run-application/configure-pdb/#specifying-a-poddisruptionbudget">https://kubernetes.io/docs/tasks/run-application/configure-pdb/#specifying-a-poddisruptionbudget</a></p>
</blockquote>
<h2>対応するソースコードを見てみる</h2>
<p> 中途半端な数になった際の繰り上げの振る舞いについて、実際のソースコードを確認してみます。 <code>minAvailable</code> と <code>maxUnavailable</code> の読み取りに対応する部分は以下のようになっています。<br />
<a href="https://github.com/kubernetes/kubernetes/blob/32fdb551920270fd14263468235120c816f7be1b/pkg/controller/disruption/disruption.go#L797-L800">/pkg/controller/disruption/disruption.go L797-L800 (2023/12/1時点)</a></p>
<pre><code class="language-go">maxUnavailable, err = intstr.GetScaledValueFromIntOrPercent(pdb.Spec.MaxUnavailable, int(expectedCount), true)
if err != nil {
return
}
minAvailable, err = intstr.GetScaledValueFromIntOrPercent(pdb.Spec.MinAvailable, int(expectedCount), true)
if err != nil {
return
}</code></pre>
<p>ここで用いられている <code>GetScaledValueFromIntOrPercent</code> の中身の実装はこのようになっていて、上記では パラメータとして <code>roundUp: true</code> が指定されているため、繰り上げ処理がされていることが確認できます。</p>
<pre><code class="language-go">func GetScaledValueFromIntOrPercent(intOrPercent *IntOrString, total int, roundUp bool) (int, error) {
(中略...)
if isPercent {
if roundUp {
value = int(math.Ceil(float64(value) * (float64(total)) / 100))
} else {
value = int(math.Floor(float64(value) * (float64(total)) / 100))
}
}
return value, nil
}</code></pre>
<h2>PDB設定時の動作例</h2>
<p>前述で繰り上げ処理が行われることはわかりましたが、受け付けた値に対してPDBがどのような動作をするのかについて、いくつかケースを想定して検証しました。</p>
<p>今回想定した流れは以下の通りです。</p>
<ol>
<li>Nodeが2つ存在している</li>
<li>片方にPodが入っていて、PDBが設定されている</li>
<li>Podが存在しているNodeに対して <code>kubectl drain</code> が実行され、Podの退避処理が実行される</li>
</ol>
<h3>ケース1: Pod数:2, maxUnavailable:50%</h3>
<p>maxUnavailableのPod数は1Podとして計算されます。その結果、図のように可用性を担保した状態で Drain操作ができることが確認できました。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/51684b4c-pdb1.webp" alt="ケース1 Pod数:2 maxUnavailable:50%の時の挙動説明図" /></p>
<h3>ケース2: Pod数:1, maxUnavailable:50%</h3>
<p> maxUnavailableのPod数は1となります。そのためDrain操作が可能となりますが、退避するPodの中断処理と新しいPodの起動処理は同時に実行されるため、Drain操作中に利用可能なPod数が0となる可能性があります。<br />
このようにmaxUnavailableを用いる場合、最小pod数を考慮する必要が出てきます。(Pod数が1の時にあえてDrainできないようにする手段もあるようです。 <a href="https://kubernetes.io/docs/tasks/run-application/configure-pdb/#:~:text=tolerate%20occasional%20downtime.-,Possible%20Solution%202%3A,-Set%20PDB%20with">公式ドキュメント</a>)</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/a46e662c-pdb2-1.webp" alt="ケース2 Pod数:2 maxUnavailable:50%の時の挙動説明図" /></p>
<h3>ケース3: Pod数:2, maxUnavailable: 80%</h3>
<p> maxUnavailableのPod数は2となります。そのため2Podに対して同時に退避処理が実行されます。そのため、drain操作中に利用可能なPod数が0となる可能性があり、可用性に問題が出る可能性があります。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/4c4dca0f-pdb3.webp" alt="ケース3 Pod数:2 maxUnavailable:80%の時の挙動説明図" /></p>
<h3>ケース4: Pod数:1, minAvailable: 50%</h3>
<p> minAvailableのPod数は1となります。PDBはdrain操作のためにPod数の調節を行うことはしないため、PDBの条件を満たすDrain操作ができず、Drain操作は止まったままの状態となります。<br />
このように、minAvailableを用いる場合でも最小pod数を意識する必要が出てきます。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/bf68a35c-pdb4.webp" alt="ケース4 Pod数:1 minAvailable:50%の時の挙動説明図" /></p>
<h1>minAvailableとmaxUnavailableの整理</h1>
<p> これまで具体的なケースにおけるPDBの影響を確認しました。<br />
PDBではminAvailableとmaxUnavailableどちらかのみしか設定することができません。それぞれを採用することによるメリット、考慮すべき点について以下にまとめます。</p>
<h2>minAvailable</h2>
<h3>メリット</h3>
<ul>
<li>0より大きい数を指定しておけば、Drain操作時において必ず1Pod以上動作させて可用性を持たせることができる。</li>
</ul>
<h3>考慮が必要な点</h3>
<ul>
<li>(パーセント指定ではなくPod数で指定した場合) スケールアップして多くのPodがPDBの管理対象となっている場合、Drain操作で一度に多くのPodが利用できない状態となり、パフォーマンスに影響が出る可能性がある。</li>
<li>適切に値を設定しないとDrain操作ができなくなってしまう。(特に管理対象のPodが少数である場合は注意が必要)</li>
</ul>
<h3>Drain操作ができなくなる条件</h3>
<ul>
<li>パーセント指定の場合、現存のPod数に対して計算を行い繰り上げ処理が行われた結果、minAvailableのPod数がPDBが対象とするPod数と同数になる時。</li>
<li>Pod数指定の場合、minAvailableのPod数がPDBが対象とするPod数より多くなってしまう場合。</li>
</ul>
<h2>maxUnavailable</h2>
<h3>メリット</h3>
<ul>
<li>Drain操作ができなくなる状態は基本発生しない。<br />
<h3>考慮が必要な点</h3>
</li>
<li>適切に数値を設定しないとDrain操作中に全てのPodが中断してしまう可能性がある。<br />
<h3>Drain操作ができなくなる条件</h3>
</li>
<li>maxUnavailableを0に設定する。</li>
<li>Pod数が1の状態のままでは可用性を持ってDrain操作をすることができないため、あえて <code>maxUnavailable: 0</code> を設定して警告を出すために設定する方法もあるようです。<br />
<a href="https://kubernetes.io/docs/tasks/run-application/configure-pdb/#:~:text=tolerate%20occasional%20downtime.-,Possible%20Solution%202%3A,-Set%20PDB%20with">公式ドキュメント</a></li>
</ul>
<h1>まとめ</h1>
<p> PDBの設定に関しては、minAvailable と maxUnavailable のどちらが良いという一般的な答えは存在しません。システムによって要件は異なるのでPodの最小稼働数や中断許容範囲を理解し、それに基づいて適切な設定をすることが重要です。今回の記事が、皆さんのPDBに関する理解を深め、より効果的なKubernetesの運用に役立つ一助となれば幸いです。</p>
<p>明日の記事はmikichinさんです。引き続きお楽しみください!</p>
- メルペイのProgram型組織への移行https://engineering.mercari.com/blog/entry/20231202-merpay-program-organization/https://engineering.mercari.com/blog/entry/20231202-merpay-program-organization/<p>はじめに こんにちは。メルペイVPoEの@keigowです。 この記事は、Merpay Advent Calendar 2023 の2日目の記事です。 今年の3月まではソウゾウでHead of Engineeringとし […]</p>
Sat, 02 Dec 2023 10:00:07 GMT<h2>はじめに</h2>
<p>こんにちは。メルペイVPoEの<a href="https://twitter.com/keigow">@keigow</a>です。<br />
この記事は、<a href="https://engineering.mercari.com/blog/entry/20231124-merpay-advent-calendar-2023/">Merpay Advent Calendar 2023</a> の2日目の記事です。</p>
<p>今年の3月まではソウゾウでHead of Engineeringとして働いていましたが、4月にメルペイに2年ぶりに戻ってきました。本日はメルペイのProduct組織の改善とProgram組織への移行の取り組みについてご紹介します。</p>
<h2>以前のProject Matrix型組織</h2>
<p>2022年9月までは、Microservicesの単位をベースとして構成されたFunctionチーム(Growth、Platform、与信領域など)から、3ヶ月ごとに決めているProject(新機能開発、他社連携など)に各メンバーをアサインしていくという形でProduct開発を推進していました。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/7541e99e-project_matrix.png" alt="Project Matrix" /> </p>
<p>会社のOKRとして合意した目標に沿って、Productチーム内でProjectの優先度を決めて左から順に並べ、Project毎にProject LeaderとTechnical Project Manager(Engineeringのカウンターパート)を置くことで、以下のような良い点があったと考えています。</p>
<ul>
<li>事業としてフォーカスされている点が理解しやすい</li>
<li>優先順位が明確なためアサインの判断がしやすい</li>
<li>結果として、会社として重要なProject(チーム横断な動きが必要なものであっても)をスピード感を持って推進できる</li>
</ul>
<p>一方で以下のような課題感もありました。</p>
<ul>
<li>組織の成長と人員増加に伴い、優先順位の調整コストが高まってきた</li>
<li>3ヶ月単位でProjectの見直しがあり、アサインの変更も随時行っていたため、チームビルディングのコストが高い</li>
<li>今後の運用や改善までを想定した中長期の計画づくりが難しくなっていた</li>
</ul>
<p>これらの課題感を踏まえて、2022年10月より新しいProgram型組織への移行を開始しました。</p>
<h2>Program型組織の導入</h2>
<p>特定のドメインごとに組織を分けて、より小さな単位で意思決定をできるようにしたチームをProgramと呼んでいます。現在メルペイには6つのProgramがあり、その役割ごとに大きくProduct / Foundation / Enablingという3つの領域に分けています。</p>
<ul>
<li>Product: 支払いや与信、還元の仕組みなどお客さま体験のコアとなる部分の提供を担う。</li>
<li>Foundation: 各Product領域のProgramに対して汎用的に利用可能なPlatform機能を提供する。決済の中心となるマイクロサービスや、KYC(本人確認)、ポイント付与の仕組みなど。</li>
<li>Enabling: ArchitectやSRE、Data Platformなど、横断的な技術課題の解決や生産性向上など開発全体を支援する。</li>
</ul>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/5f234905-program.png" alt="Program System" /></p>
<p>各ProgramにはProduct HeadとEngineering Headを置いています。Product Headは施策の優先順位の決定や、戦略・ロードマップの策定とその推進に責任を持ち、Engineering Headは中長期を見据えた技術的な戦略や方針の策定と推進に責任を持っています。</p>
<p>基本的にはOKRなど会社方針をベースにProgram内で意思決定が出来るようにしていますが、3ヶ月ごとにProgram HeadメンバーとCPO/CTO/VPsを交えたStrategy Reviewを行うことで、Program組織の戦略と経営の戦略の方向性を揃えられるようにしています。</p>
<h2>Engineering組織のアップデート</h2>
<p>ドメイン毎のProgram組織を作ることで、Program内のアサインに柔軟性が生まれ、運用や改善も踏まえた、中長期の戦略への投資がしやすい環境になりました。一方でEngineering組織のレポートラインは職種別となっており、Backend、Frontend、iOS、Androidなど各職種別にEngineering Manager(EM)、Manager of Managers(MoM、EMのManager)が居るという体制になっていました。その結果として一部のEMやMoMが各Programにフォーカスすることが難しいという課題が有りました。</p>
<p>この問題を解決するため、今年の10月からProgram組織に合わせたレポートラインに変更し、それぞれのEMやMoMが自身の担当するProgramにフォーカスできるような体制変更を行いました。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/cd286174-mom_em.png" alt="EM Org Structure" /></p>
<h2>振り返りと今後</h2>
<p>Program組織にトライし始めたのは1年ほど前ですが、Engineering組織のアップデートも含め、細かい改善を積み重ねることで、少しずつ動きやすい状況が作れてきたと思います。とはいえ、まだまだ課題も多いため、1つずつ課題と向き合って解決していきたいと思っています。</p>
<p>余談になりますが、今回のような組織体制はメルペイとしては初めてではなく、Project Matrixをベースとした体制に移る前には、(当然今と違う部分はありますが)Program組織に近い体制を取っていた時期もありました。</p>
<p>組織変更はコストが高いので、頻繁に変えすぎるのは良くないと思っていますが、一定のタイミングでその時の状況やニーズに合わせて、変化をしていくこと自体は必要だし、やっていきたいと思っています。どのような組織もPros/Consがあるものだと思うので、ある意味自然な姿だとも感じます。</p>
<h2>おわりに</h2>
<p>明日の記事はkeigoandさんの「Merging teams for a Growth Platform」です。引き続きお楽しみください。</p>
- メルペイ VPoE による2023年の振り返りhttps://engineering.mercari.com/blog/entry/20231201-merpay-looking-back-to-2023/https://engineering.mercari.com/blog/entry/20231201-merpay-looking-back-to-2023/<p>この記事は、Merpay Advent Calendar 2023 の1日目の記事です。 hello hello hallo how high? メルペイで5月からVPoE の @nu2です。 はじめに 2023年6月、 […]</p>
Fri, 01 Dec 2023 10:00:51 GMT<p>この記事は、<a href="https://engineering.mercari.com/blog/entry/20231124-merpay-advent-calendar-2023/">Merpay Advent Calendar 2023</a> の1日目の記事です。</p>
<p>hello hello hallo how high? メルペイで5月からVPoE の <a href="https://twitter.com/nu2_jp" title="@nu2">@nu2</a>です。</p>
<h1>はじめに</h1>
<p>2023年6月、メルペイはエンジニアリング組織の新体制を発表しました。<br />
<a href="https://mercan.mercari.com/articles/39414/">https://mercan.mercari.com/articles/39414/</a><br />
(新体制になったメルカリグループのFintechのエンジニアリング。新CTOとVPたちが語る、グループLTV最大化のためにFintech事業が果たすべき役割)</p>
<p>今年の Merpay Advent Calendar はエンジニアリング組織新体制の元、オープナーとしてメルペイのこの1年を淡々と振り返ってみたいと思います。<br />
特に私は5月に入社しましたので新鮮な視点をお伝えできればよいなと考えています。</p>
<p><a href="https://engineering.mercari.com/blog/entry/20230704-merpay-techasset-first-impression/">https://engineering.mercari.com/blog/entry/20230704-merpay-techasset-first-impression/</a><br />
(New Member として見たMerpay Tech Asset First Impression)</p>
<h1>LLM</h1>
<p>入社日当日にLLM を活用した「ぐげん会議」を開催するからVP としてチームのスポンサーになってくれと言われました。全く前知識とコンテキストがないまま、これまでの経験から障害報告書を障害の状況から規定の形式に自動生成して書記をするAI を開発するチームをスポンサードする事に決めました。</p>
<p><a href="https://mercan.mercari.com/articles/39144/">https://mercan.mercari.com/articles/39144/</a><br />
(LLMを活用してなにがつくれるか?——「ぐげん会議」開催から見えてきた、AI活用の新たな可能性)</p>
<p>私がスポンサードしたチームは見事にCTO 賞を受賞しました。<br />
また、MVPを受賞した返済相談チャットシミュレーターを開発したチームはその品質の高さとともに、会社の決算資料にも報告を開始したクレジットサービスの債権残高 / 回収率にとても大きなインパクトを残しそうなポテンシャルを感じるAI プロダクトでした。</p>
<h1>メルカード</h1>
<p>2022年12月から提供を開始したメルカードの発行枚数が200万枚を突破しました。</p>
<p><a href="https://jp.merpay.com/news/2023/11/2million/">https://jp.merpay.com/news/2023/11/2million/</a><br />
(「メルカード」、すべてのお客さまへの提供開始から1年足らず(約11か月)で発行枚数200万枚突破)</p>
<p>提供開始からわずか半年で機能をアップデート、その約3ヶ月後に更にアップデートをしています。</p>
<p><a href="https://jp.merpay.com/news/2023/06/20230627seisan/">https://jp.merpay.com/news/2023/06/20230627seisan/</a><br />
(メルペイ、清算機能のアップデートで清算後すぐに「あと払い利用枠」が回復する機能を追加)</p>
<p><a href="https://jp.merpay.com/news/2023/10/20231016seisan/">https://jp.merpay.com/news/2023/10/20231016seisan/</a><br />
(メルペイ、清算機能のアップデートで清算後すぐにポイントが付与される機能を追加)</p>
<p>このような体験向上をデザイン面、エンジニアリング面から支えた結果がグッドデザイン賞を受賞した事につながっているのではないかと思います。<br />
エンジニアリングとしてこのように短期間でアップデートをかけられる決済機能をこれまで日本国内で見た事はありません。<br />
なぜこのような事が可能なのか?という事はMerpay & Mercoin Tech Fest 2023 の<a href="https://engineering.mercari.com/blog/entry/20231023-mmtf2023-day2-4/">こちらのセッション</a>にて発表されています。</p>
<p>グッドデザイン賞<br />
<a href="https://about.mercari.com/press/news/articles/20231005_gdesign/">https://about.mercari.com/press/news/articles/20231005_gdesign/</a><br />
(メルカリグループのサービス、「メルカード」と「ビットコイン取引サービス」が2023年度グッドデザイン賞をW受賞)</p>
<h1>Merpay & Mercoin Tech Fest 2023</h1>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/9b2700e3--2023-11-30-11.20.50.png" alt="" /></p>
<p>そしてエンジニアリング組織の新体制後初のTech Fest を開催しました。<br />
Fintech 領域の業務経験やドメイン知識が不十分な状態で入社しましたので<br />
個人的にもこのTech Fest は自分のインプットを増やす上で非常に有用でした。</p>
<p><a href="https://mercan.mercari.com/articles/39180/">https://mercan.mercari.com/articles/39180/</a><br />
(8月22日より3日間にわたって開催!Fintech CTOと2名の新VPoEに聞く、「Merpay & Mercoin Tech Fest 2023」の見どころと意気込み)<br />
<a href="https://engineering.mercari.com/blog/entry/20231023-mmtf2023-day1-2/">https://engineering.mercari.com/blog/entry/20231023-mmtf2023-day1-2/</a><br />
(【書き起こし】How to Unleash Fintech – Shunya Kimura / Keigo Watanabe / Noriaki Utsunomiya 【Merpay & Mercoin Tech Fest 2023】)</p>
<p>現在配信したセッションの動画を全てテキストに書き起こしてくれているので予習、復習に最適です。<br />
<a href="https://engineering.mercari.com/blog/entry/20231023-mmtf2023-list/">https://engineering.mercari.com/blog/entry/20231023-mmtf2023-list/</a><br />
(Merpay & Mercoin Tech Fest 2023 セッション書き起こしまとめ)</p>
<h1>求人プラットフォーム「メルカリ ハロ」</h1>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/12/a902b48f-image-1.png" alt="" /><br />
そしてTech Fest のトークセッションで「まだ言えない事がある」とCTO が言及していたのがこのスポットワーク事業に参入する話です。</p>
<p><a href="https://about.mercari.com/press/news/articles/20231113_mercarihallo/">https://about.mercari.com/press/news/articles/20231113_mercarihallo/</a><br />
(メルカリ、2024年初春にスポットワーク事業に参入、本日より求人募集パートナーの先行受付を開始)<br />
“メルペイを通じて、給与デジタル払いを実現し、「メルカリ ハロ」をご利用いただいた際、「メルペイ」で給与を受け取れる体験を提供することを目指します。”</p>
<p>プレスリリースから引用した文章のとおり、この体験の提供を実現するため現在急ピッチかつ慎重にプロジェクトを進行しています。</p>
<h1>貸金業務取扱主任者制度</h1>
<p><a href="https://www.j-fsa.or.jp/chief/howto/">貸金業務取扱主任者制度</a>とは貸金業法で定められている国家試験です。<br />
我々は貸金業を営んでいますので、所属する社員は誰でも会社の経費で受験する事ができます。ドメイン知識を習得する絶好の機会なので受験してきました。<br />
毎年試験にチャレンジするエンジニアはかなりおり、試験前後は試験対策用に立てられたSlack チャンネルがとても盛り上がってました。<br />
なぜこのような法律ができて、貸金業を事業とする事業者は資格の保有を義務付けられるのか?というこれまでの歴史的背景から学ぶ事も多くあり私は既に来年の試験に向けて毎日少しずつ学習を繰り返そうと考えています。</p>
<h1>経済安全保障推進法</h1>
<p>株式会社メルペイは経済安全保障推進法の特定社会基盤事業者として指定されました。<br />
給与デジタル払いを実現するためにも必要なことであります。</p>
<p><a href="https://www.fsa.go.jp/news/r5/economicsecurity/231117infrastructure.html">https://www.fsa.go.jp/news/r5/economicsecurity/231117infrastructure.html</a><br />
(金融分野における経済安全保障対策)</p>
<p>“経済安全保障推進法第50条第1項及び第2項の規定に基づき、特定社会基盤事業者を令和5年11月16日に指定し、同年11月17日に公示しましたので、別添をご確認ください。”<br />
<a href="https://www.fsa.go.jp/news/r5/economicsecurity/tokuteishakaikiban.pdf">https://www.fsa.go.jp/news/r5/economicsecurity/tokuteishakaikiban.pdf</a><br />
(特定社会基盤事業者として指定した者)</p>
<p>私が入社後ちょうど半年を経過したタイミングで国から社会インフラの一部として指定され、入社後丸一年経過する日までに法令に準拠する体制を構築しなければなりません。<br />
私個人の経歴としてもインターネットポータル、検索、通信とICT の社会インフラ企業で経験を積んできましたので運命めいたものを感じてしまいました。</p>
<p>「守り」あってこその「攻め」を来年は実践していく所存です。</p>
<h1>おわりに</h1>
<p>2023年のメルペイは前年にリリースをした大きな機能を磨き上げる年になったと個人的に感じました。またその大きな機能に対してさまざまな反響をいただきました。<br />
今後もお客さまの安心・安全を維持しながら新たな価値をお届けすることを、エンジニアリングで達成していきたいと思います。</p>
<p>明日の記事は VPoE のkeigowさんです。引き続きお楽しみください。</p>
<h1>おまけ</h1>
<p>今年無事。</p>
<p>所属する会社が変わってもこのフレーズをAdvent Calendar に投稿する事は私の恒例行事なので、<br />
今年もILL-BOSSTINO のこの言葉を捧げます。<br />
特に我々の業界は月跨ぎ、年跨ぎのタイミングでインシデントが発生しやすいです。<br />
皆さま良いお年をお迎えください。</p>
<p><iframe loading="lazy" width="560" height="315" src="https://www.youtube.com/embed/7bNeUDSf1qs?si=EGGZvJjirBP8ieF4" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe></p>
<p>(THA BLUE HERB “今年無事” @ 東京 contact – 2019.12.29)</p>
- 「Merpay Advent Calendar 2023」開催のお知らせhttps://engineering.mercari.com/blog/entry/20231124-merpay-advent-calendar-2023/https://engineering.mercari.com/blog/entry/20231124-merpay-advent-calendar-2023/<p>こんにちは。メルペイ Engineering Engagement チームの mikichin です。 早いもので来週から12月ということで、Advent Calendarの季節がやってきます!今年も、メルカリとメルペイ […]</p>
Fri, 24 Nov 2023 10:00:57 GMT<p>こんにちは。メルペイ Engineering Engagement チームの <a href="https://twitter.com/chida_miki" title="mikichin">mikichin</a> です。<br />
早いもので来週から12月ということで、Advent Calendarの季節がやってきます!今年も、メルカリとメルペイ2社で Advent Calendar を実施します!<br />
<br />
▶<a href="https://engineering.mercari.com/blog/entry/20231124-mercari-advent-calendar-2023/">Mercari Advent Calendar 2023 はこちら</a><br />
</p>
<h1>Merpay Advent Calendar とは?</h1>
<p>Advent Calendar の習慣にもとづいて、12月1日から25日までの期間毎日ブログ記事を投稿する、というブログ公開型イベントです。</p>
<p>メルペイ・メルコインのエンジニアがプロダクトや会社で利用している技術、興味のある技術分野やちょっとしたテクニックなど知見をアウトプットしていきます。このAdvent Calendarを通じてクリスマスまでの毎日を楽しく過ごしていただければと思っています。</p>
<h3>2022年のMercari / Merpay Advent Calendar はこちら</h3>
<ul>
<li><a href="https://engineering.mercari.com/blog/entry/20221124-mercari-advent-calendar-2022/">Mercari Advent Calendar 2022</a> </li>
<li><a href="https://engineering.mercari.com/blog/entry/20221124-merpay-advent-calendar-2022/">Merpay Advent Calendar 2022</a><br />
</li>
</ul>
<h1>公開予定表 (こちらは、後日、各記事へのリンク集になります)</h1>
<table>
<thead>
<tr>
<th style="text-align: left;">Date</th>
<th style="text-align: left;">Theme / Title</th>
<th style="text-align: left;">Author</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: left;">12/1</td>
<td style="text-align: left;"><a href="https://engineering.mercari.com/blog/entry/20231201-merpay-looking-back-to-2023/" title="メルペイ VPoE による2023年の振り返り">メルペイ VPoE による2023年の振り返り</a></td>
<td style="text-align: left;">@nu2</td>
</tr>
<tr>
<td style="text-align: left;">12/2</td>
<td style="text-align: left;"><a href="https://engineering.mercari.com/blog/entry/20231202-merpay-program-organization/" title="メルペイのProgram型組織への移行">メルペイのProgram型組織への移行</a></td>
<td style="text-align: left;">@keigow</td>
</tr>
<tr>
<td style="text-align: left;">12/3</td>
<td style="text-align: left;"><a href="https://engineering.mercari.com/en/blog/entry/20231201-merging-teams-for-a-growth-platform/" title="Merging teams for a Growth Platform">Merging teams for a Growth Platform</a></td>
<td style="text-align: left;">@keigoand</td>
</tr>
<tr>
<td style="text-align: left;">12/4</td>
<td style="text-align: left;"><a href="https://engineering.mercari.com/blog/entry/20231204-k8s-understanding-pdb/" title="動作例からKubernetes PDBの挙動を理解する">動作例からKubernetes PDBの挙動を理解する</a></td>
<td style="text-align: left;">@Shion (Intern)</td>
</tr>
<tr>
<td style="text-align: left;">12/5</td>
<td style="text-align: left;"><a href="https://engineering.mercari.com/blog/entry/20231205-fintech-techtalk/" title="Fintech Tech Talk at Office Week を開催したよ!">Fintech Tech Talk at Office Week を開催したよ!</a></td>
<td style="text-align: left;">@mikichin</td>
</tr>
<tr>
<td style="text-align: left;">12/6</td>
<td style="text-align: left;"><a href="https://engineering.mercari.com/blog/entry/20231206-what-i-did-in-my-first-year-after-joining-merpay-as-a-new-graduate/" title="メルペイに新卒入社して1年目にやったこと">メルペイに新卒入社して1年目にやったこと</a></td>
<td style="text-align: left;">@panorama</td>
</tr>
<tr>
<td style="text-align: left;">12/7</td>
<td style="text-align: left;"><a href="https://engineering.mercari.com/en/blog/entry/20231207-enhancing-collaboration-and-reliability-the-journey-of-version-history-in-our-page-editor-tool/" title="Enhancing Collaboration and Reliability: The Journey of Version History in our Page Editor Tool">Enhancing Collaboration and Reliability: The Journey of Version History in our Page Editor Tool</a></td>
<td style="text-align: left;">@Malli , ben.hsieh</td>
</tr>
<tr>
<td style="text-align: left;">12/8</td>
<td style="text-align: left;"><a href="https://engineering.mercari.com/blog/entry/20231208-internship-shion/" title="メルペイでのインターンを2ヶ月経験してみて">メルペイでのインターンを2ヶ月経験してみて</a></td>
<td style="text-align: left;">@Shion (Intern)</td>
</tr>
<tr>
<td style="text-align: left;">12/9</td>
<td style="text-align: left;"><a href="https://engineering.mercari.com/blog/entry/20231209-airflow-oss-contribution/" title="新卒エンジニアが Airflow のバグを発見してからコントリビュートするまで">新卒エンジニアが Airflow のバグを発見してからコントリビュートするまで</a></td>
<td style="text-align: left;">@champon</td>
</tr>
<tr>
<td style="text-align: left;">12/10</td>
<td style="text-align: left;"><a href="https://engineering.mercari.com/blog/entry/20231210-support-for-merchant-invoices/" title="加盟店精算のインボイス対応">加盟店精算のインボイス対応</a></td>
<td style="text-align: left;">@ryuyama</td>
</tr>
<tr>
<td style="text-align: left;">12/11</td>
<td style="text-align: left;"><a href="https://engineering.mercari.com/blog/entry/20231208-38f8f2075b/" title="Cypress + Gmail APIでメール+SMSの2FA認証をテスト自動化する(気合&パワー)">Cypress + Gmail APIでメール+SMSの2FA認証をテスト自動化する(気合&パワー)</a></td>
<td style="text-align: left;">@fukutomi</td>
</tr>
<tr>
<td style="text-align: left;">12/12</td>
<td style="text-align: left;"><a href="https://engineering.mercari.com/en/blog/entry/20231212-flow-control-challenges-in-mercaris-line-integration/" title="Flow Control Challenges in Mercari’s LINE Integration">Flow Control Challenges in Mercari’s LINE Integration</a></td>
<td style="text-align: left;">@Liu</td>
</tr>
<tr>
<td style="text-align: left;">12/13</td>
<td style="text-align: left;"><a href="https://engineering.mercari.com/blog/entry/20231213-9d14d5cde6/" title="多国籍メンバーで構成されたメルペイ決済基盤チームが言語の壁を突破するために取り組んだこと">多国籍メンバーで構成されたメルペイ決済基盤チームが言語の壁を突破するために取り組んだこと</a></td>
<td style="text-align: left;">@abcdefuji</td>
</tr>
<tr>
<td style="text-align: left;">12/14</td>
<td style="text-align: left;"><a href="https://engineering.mercari.com/en/blog/entry/20231214-tns-platform-team-past-present-and-future/" title="TnS Platform Team, past, present, and future">TnS Platform Team, past, present, and future</a></td>
<td style="text-align: left;">@ntk</td>
</tr>
<tr>
<td style="text-align: left;">12/15</td>
<td style="text-align: left;"><a href="https://engineering.mercari.com/blog/entry/20231215-the-past-and-future-of-merpay-frontend-2023/" title="Merpay Frontend のこれまでとこれから: 2023年版">Merpay Frontend のこれまでとこれから: 2023年版</a></td>
<td style="text-align: left;">@tokuda109</td>
</tr>
<tr>
<td style="text-align: left;">12/16</td>
<td style="text-align: left;"><a href="https://engineering.mercari.com/blog/entry/20231216-06b3fe8d0f/" title="品質要件が厳しいLLMアプリケーションのトライアル評価を通じて得た知見">品質要件が厳しいLLMアプリケーションのトライアル評価を通じて得た知見</a></td>
<td style="text-align: left;">@gucci</td>
</tr>
<tr>
<td style="text-align: left;">12/17</td>
<td style="text-align: left;"><a href="https://engineering.mercari.com/en/blog/entry/20231217-2023-gophercon-review/" title="2023 GopherCon Review">2023 GopherCon Review</a></td>
<td style="text-align: left;">@tenlingp</td>
</tr>
<tr>
<td style="text-align: left;">12/18</td>
<td style="text-align: left;">(JA)<a href="https://engineering.mercari.com/blog/entry/20231218-what-the-merpay-enabling-client-team-aims-for/" title="Merpay Enabling Client チームが目指すこと">Merpay Enabling Client チームが目指すこと</a><br />(EN)<a href="https://engineering.mercari.com/en/blog/entry/20231218-what-the-merpay-enabling-client-team-aims-for/" title="What the Merpay Enabling Client Team aims for">What the Merpay Enabling Client Team aims for</a></td>
<td style="text-align: left;">@masamichi</td>
</tr>
<tr>
<td style="text-align: left;">12/19</td>
<td style="text-align: left;"><a href="https://engineering.mercari.com/blog/entry/20231219-b613d5daad/" title="モダリティを考慮したiOSアプリのナビゲーションの再設計">モダリティを考慮したiOSアプリのナビゲーションの再設計</a></td>
<td style="text-align: left;">@kenmaz</td>
</tr>
<tr>
<td style="text-align: left;">12/20</td>
<td style="text-align: left;"><a href="https://engineering.mercari.com/blog/entry/20231220-datadog-dashboard-for-observability/" title="決済基盤の Observability を向上するための Datadog Dashboard の進化">決済基盤の Observability を向上するための Datadog Dashboard の進化</a></td>
<td style="text-align: left;">@komatsu</td>
</tr>
<tr>
<td style="text-align: left;">12/21</td>
<td style="text-align: left;"><a href="https://engineering.mercari.com/blog/entry/20231221-f221b835d1/" title="AWS Transfer Family で SFTPサーバーを作ってみたら便利だった話">AWS Transfer Family で SFTPサーバーを作ってみたら便利だった話</a></td>
<td style="text-align: left;">@myoshida</td>
</tr>
<tr>
<td style="text-align: left;">12/22</td>
<td style="text-align: left;"><a href="https://engineering.mercari.com/blog/entry/20231222-simple-search-api-2/" title="お手軽な検索API構築 その2 ~マルチコア・ベクトル・分散検索">お手軽な検索API構築 その2 ~マルチコア・ベクトル・分散検索</a></td>
<td style="text-align: left;">@orfeon</td>
</tr>
<tr>
<td style="text-align: left;">12/23</td>
<td style="text-align: left;"><a href="https://engineering.mercari.com/blog/entry/20231223-mercoin-github-actions/" title="メルコインにおけるGitHub Actions活用術">メルコインにおけるGitHub Actions活用術</a></td>
<td style="text-align: left;">@iwata</td>
</tr>
<tr>
<td style="text-align: left;">12/24</td>
<td style="text-align: left;"><a href="https://engineering.mercari.com/blog/entry/20231224-a4046d214c/" title="Offsitesのワークショップでの4つの工夫">Offsitesのワークショップでの4つの工夫</a></td>
<td style="text-align: left;">@pooh</td>
</tr>
<tr>
<td style="text-align: left;">12/25</td>
<td style="text-align: left;"><a href="https://engineering.mercari.com/blog/entry/20231225-creating-mercari-engineering-roadmap/" title="メルカリEngineering Roadmapの作成とその必要性">メルカリEngineering Roadmapの作成とその必要性</a></td>
<td style="text-align: left;">@kimuras</td>
</tr>
</tbody>
</table>
<p>Merpay Advent Calendar 2023 の1日目は、 VPoE の <a href="https://twitter.com/nu2_jp">@nu2</a> が執筆予定です。<br />
ひとつでも気になる記事がある方は、この記事をブックマークしておくか、 <a href="https://twitter.com/mercaridevjp">エンジニア向け公式X(旧Twitter)</a>をフォロー&チェックしてくださいね!</p>
- 「Mercari Advent Calendar 2023」開催のお知らせhttps://engineering.mercari.com/blog/entry/20231124-mercari-advent-calendar-2023/https://engineering.mercari.com/blog/entry/20231124-mercari-advent-calendar-2023/<p>こんにちは。メルカリ Engineering Officeの yasu_shiwaku です。 またまたこの季節がやってきましたね!来週から12月ということで、Advent Calendarがはじまります。今年もメルカリ […]</p>
Fri, 24 Nov 2023 10:00:38 GMT<p>こんにちは。メルカリ Engineering Officeの <a href="http://https://twitter.com/yaccho0101" title="yasu_shiwaku">yasu_shiwaku</a> です。<br />
またまたこの季節がやってきましたね!来週から12月ということで、Advent Calendarがはじまります。今年もメルカリとメルペイ・メルコインで2本のAdvent Calendarを実施します!<br />
<br />
▶<a href="https://engineering.mercari.com/blog/entry/20231124-merpay-advent-calendar-2023/">Merpay Advent Calendar 2023 はこちら</a><br />
</p>
<h1>Mercari Advent Calendar とは?</h1>
<p>Advent Calendar の習慣にもとづいて、12月1日から25日までの期間毎日ブログ記事を投稿する、というブログ公開型イベントです。</p>
<p>メルカリグループのエンジニアがプロダクトや会社で利用している技術、興味のある技術分野やちょっとしたテクニックなど知見をアウトプットしていきます。このAdvent Calendarを通じてクリスマスまでの毎日を楽しく過ごしていただければと思っています。</p>
<h3>2022年のMercari / Merpay / メルカリ Shops Advent Calendar</h3>
<ul>
<li><a href="https://engineering.mercari.com/blog/entry/20221124-mercari-advent-calendar-2022/">Mercari Advent Calendar 2022</a></li>
<li><a href="https://engineering.mercari.com/blog/entry/20221124-merpay-advent-calendar-2022/">Merpay Advent Calendar 2022</a></li>
<li><a href="https://engineering.mercari.com/blog/entry/20221031-souzoh-flying-advent-calendar-2022/">メルカリShops [フライング]アドベントカレンダー2022</a></li>
</ul>
<p></p>
<h1>公開予定表 (こちらは、後日、各記事へのリンク集になります)</h1>
<table>
<thead>
<tr>
<th style="text-align: left;">Date</th>
<th style="text-align: left;">Theme / Title</th>
<th style="text-align: left;">Author</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: left;">12/1</td>
<td style="text-align: left;"><a href="https://engineering.mercari.com/en/blog/entry/20231128-the-bitter-lesson-about-engineers-in-a-chatgpt-world/">The Bitter Lesson about Engineers in a ChatGPT World</a></td>
<td style="text-align: left;">@darren</td>
</tr>
<tr>
<td style="text-align: left;">12/2</td>
<td style="text-align: left;"><a href="https://engineering.mercari.com/en/blog/entry/20231129-how-did-we-save-75-of-our-cost/">How We Saved 75% of our Server Costs</a></td>
<td style="text-align: left;"><a href="https://in.linkedin.com/in/ipratikraut">@pratik</a></td>
</tr>
<tr>
<td style="text-align: left;">12/3</td>
<td style="text-align: left;"><a href="https://engineering.mercari.com/en/blog/entry/20231130-how-we-reduced-response-latency-by-over-80/">How we reduced response latency by over 80%</a></td>
<td style="text-align: left;"><a href="https://github.com/rclarey">@rclarey</a></td>
</tr>
<tr>
<td style="text-align: left;">12/4</td>
<td style="text-align: left;"><a href="https://engineering.mercari.com/en/blog/entry/20231129-performance-monitoring-in-mercari-mobile-apps/">Performance monitoring in Mercari mobile apps</a></td>
<td style="text-align: left;"><a href="https://www.linkedin.com/in/francescopretelli/">@fp</a></td>
</tr>
<tr>
<td style="text-align: left;">12/5</td>
<td style="text-align: left;"><a href="https://engineering.mercari.com/en/blog/entry/20231205-the-spirit-of-giving-a-year-end-roundup-of-our-open-source-contributions/">The Spirit of Giving: A Year-End Roundup of Our Open Source Contributions</a></td>
<td style="text-align: left;"><a href="https://github.com/adbutterfield">@adbutterfield</a></td>
</tr>
<tr>
<td style="text-align: left;">12/6</td>
<td style="text-align: left;"><a href="https://engineering.mercari.com/blog/entry/20231206-4e4f1e2323/">強いエンジニア組織に必要な、6つの技術以外のこと – メルカリ編 —</a></td>
<td style="text-align: left;">@thiroi</td>
</tr>
<tr>
<td style="text-align: left;">12/7</td>
<td style="text-align: left;"><a href="https://engineering.mercari.com/blog/entry/20231207-mercari-advent-calendar-day7/">英語が苦手なエンジニアがメルカリに入ってどうなったか</a></td>
<td style="text-align: left;"><a href="https://twitter.com/omohayui">@otter</a></td>
</tr>
<tr>
<td style="text-align: left;">12/8</td>
<td style="text-align: left;"><a href="https://engineering.mercari.com/en/blog/entry/20231208-t9n-i18n-l10n-g11n/">t9n, i18n, l10n, g11n ?!</a></td>
<td style="text-align: left;"><a href="https://www.linkedin.com/in/williams-kwan-3a984414a/">@wills</a></td>
</tr>
<tr>
<td style="text-align: left;">12/9</td>
<td style="text-align: left;"><a href="https://engineering.mercari.com/blog/entry/20231209-git-branch-strategy-stacked-diffs-case-study/">Gitブランチ戦略 Stacking手法のケーススタディ</a></td>
<td style="text-align: left;"><a href="https://github.com/cloverrose">@osari.k</a></td>
</tr>
<tr>
<td style="text-align: left;">12/10</td>
<td style="text-align: left;"><a href="https://engineering.mercari.com/en/blog/entry/20231210-knowledge-management-silver-bullet/">In search of a knowledge management silver bullet</a></td>
<td style="text-align: left;">@rey</td>
</tr>
<tr>
<td style="text-align: left;">12/11</td>
<td style="text-align: left;"><a href="https://engineering.mercari.com/blog/entry/20231211-large-team-development-at-mercari-ios/">チームワークと効率向上のカギ!メルカリが成功する大人数iOS開発のための手法とは?</a></td>
<td style="text-align: left;"><a href="https://www.linkedin.com/in/saenuruki/">@sae</a></td>
</tr>
<tr>
<td style="text-align: left;">12/12</td>
<td style="text-align: left;"><a href="https://engineering.mercari.com/en/blog/entry/20231212-the-art-of-streamlining-mobile-app-releases/">The art of streamlining mobile app releases</a></td>
<td style="text-align: left;"><a href="https://www.linkedin.com/in/francescopretelli/">@fp</a></td>
</tr>
<tr>
<td style="text-align: left;">12/13</td>
<td style="text-align: left;"><a href="https://engineering.mercari.com/en/blog/entry/20231213-leading-a-team-of-lead-engineers/">Leading a team of lead engineers</a></td>
<td style="text-align: left;"><a href="https://www.linkedin.com/in/francescopretelli/">@fp</a></td>
</tr>
<tr>
<td style="text-align: left;">12/14</td>
<td style="text-align: left;"><a href="https://engineering.mercari.com/en/blog/entry/20231214-current-microservices-status-challenges-and-the-golden-path/">Current Microservices Status, Challenges, and the Golden Path</a></td>
<td style="text-align: left;"><a href="https://www.linkedin.com/in/ayman-imam-67568922/">@ayman</a></td>
</tr>
<tr>
<td style="text-align: left;">12/15</td>
<td style="text-align: left;"><a href="https://engineering.mercari.com/en/blog/entry/20231207-bigquery-unleashed-a-guide-to-performance-data-management-and-cost-optimization/">BigQuery Unleashed: A Guide to Performance, Data Management and Cost Optimization</a></td>
<td style="text-align: left;"><a href="https://twitter.com/sathyasarathi90">@sathiya</a></td>
</tr>
<tr>
<td style="text-align: left;">12/16</td>
<td style="text-align: left;"><a href="https://engineering.mercari.com/en/blog/entry/20231224-closing-the-visual-testing-gap-on-android-with-screenshot-tests/">Closing the visual testing gap on Android with screenshot tests</a></td>
<td style="text-align: left;">@lukas</td>
</tr>
<tr>
<td style="text-align: left;">12/17</td>
<td style="text-align: left;"><a href="https://engineering.mercari.com/en/blog/entry/20231217-the-new-mercari-master-api/">The new Mercari Master API</a></td>
<td style="text-align: left;"><a href="https://twitter.com/cafxx">@cafxx</a></td>
</tr>
<tr>
<td style="text-align: left;">12/18 ①</td>
<td style="text-align: left;"><a href="https://engineering.mercari.com/en/blog/entry/20231218-the-frontend-infrastructure-monorepo/">The Frontend Infrastructure Monorepo</a></td>
<td style="text-align: left;">@jon</td>
</tr>
<tr>
<td style="text-align: left;">12/18 ②</td>
<td style="text-align: left;"><a href="https://engineering.mercari.com/blog/entry/20231218-mercari-advent-calendar-day18/">Onboarding施策を成功させるポイント</a></td>
<td style="text-align: left;">@aisaka</td>
</tr>
<tr>
<td style="text-align: left;">12/19</td>
<td style="text-align: left;"><a href="https://engineering.mercari.com/en/blog/entry/20231219-leveraging-llms-in-production-looking-back-going-forward/">Leveraging LLMs in Production: Looking Back, Going Forward</a></td>
<td style="text-align: left;"><a href="https://www.linkedin.com/in/andre-r-2a401875/">@andre</a></td>
</tr>
<tr>
<td style="text-align: left;">12/20</td>
<td style="text-align: left;"><a href="https://engineering.mercari.com/blog/entry/20231220-gcs-resource-optimization/">GCSのリソース最適化の取り組みで得た知見</a></td>
<td style="text-align: left;">@ayaneko</td>
</tr>
<tr>
<td style="text-align: left;">12/21</td>
<td style="text-align: left;"><a href="https://engineering.mercari.com/blog/entry/20231222-mercari-ios-10yrs-development-talk-script/">iOSDC2023で発表した「メルカリ10年間のiOS開発の歩み」のトークスクリプトを公開</a></td>
<td style="text-align: left;"><a href="https://twitter.com/motokiee">@motokiee</a></td>
</tr>
<tr>
<td style="text-align: left;">12/22①</td>
<td style="text-align: left;"><a href="https://engineering.mercari.com/en/blog/entry/20231222-making-of-your-mercari-history/">Making of "Your Mercari History"</a></td>
<td style="text-align: left;"><a href="https://www.linkedin.com/in/manoj036/">@manoj</a></td>
</tr>
<tr>
<td style="text-align: left;">12/22②</td>
<td style="text-align: left;"><a href="https://engineering.mercari.com/blog/entry/20231222-language-model-based-query-categorization-for-query-understanding/">言語モデルを用いたQuery Categorization</a> (<a href="https://engineering.mercari.com/en/blog/entry/20231222-language-model-based-query-categorization-for-query-understanding/">EN</a>)</td>
<td style="text-align: left;"><a href="https://twitter.com/paki0o">@pakio</a></td>
</tr>
<tr>
<td style="text-align: left;">12/23①</td>
<td style="text-align: left;"><a href="https://engineering.mercari.com/blog/entry/20231223-rfs-lookback-after-two-years/">メルカリの中長期技術投資 プロジェクトRFS: 約2年の振り返り</a></td>
<td style="text-align: left;">@mtsuka</td>
</tr>
<tr>
<td style="text-align: left;">12/23②</td>
<td style="text-align: left;"><a href="https://engineering.mercari.com/en/blog/entry/20231223-fine-tuned-clip-better-listing-experience-and-80-more-budget-friendly/">Fine-Tuned CLIP: Better Listing Experience and 80% More Budget-Friendly</a></td>
<td style="text-align: left;">@andy971022</td>
</tr>
<tr>
<td style="text-align: left;">12/24</td>
<td style="text-align: left;"><a href="https://engineering.mercari.com/en/blog/entry/20231224-renovate-web-e2e-tests-with-playwright-runner/">Renovate Web E2E tests with Playwright Runner</a></td>
<td style="text-align: left;"><a href="https://github.com/rueyaa332266">@jye</a></td>
</tr>
<tr>
<td style="text-align: left;">12/25</td>
<td style="text-align: left;"><a href="https://engineering.mercari.com/blog/entry/20231225-creating-mercari-engineering-roadmap/">メルカリEngineering Roadmapの作成とその必要性</a></td>
<td style="text-align: left;"><a href="https://twitter.com/kimuras">@kimuras</a></td>
</tr>
</tbody>
</table>
<p>Mercari Advent Calendar 2023 の1日目は、 Data Engineeringチームのdarren が執筆予定です。<br />
ひとつでも気になる記事がある方は、この記事をブックマークしておくか、 <a href="https://twitter.com/mercaridevjp">エンジニア向け公式Twitter</a>をフォロー&チェックしてくださいね!</p>
- mercari.go #24 を開催しました #mercarigohttps://engineering.mercari.com/blog/entry/20231113-220c429cdb/https://engineering.mercari.com/blog/entry/20231113-220c429cdb/<p>はじめに こんにちは、mercari.go スタッフの hiroebe です。 11月1日にメルカリ主催の Go 勉強会 mercari.go #24 を YouTube でのオンライン配信にて開催しました。 今回は G […]</p>
Tue, 14 Nov 2023 10:00:15 GMT<h2>はじめに</h2>
<p>こんにちは、mercari.go スタッフの hiroebe です。</p>
<p>11月1日にメルカリ主催の Go 勉強会 <a href="https://mercari.connpass.com/event/299331/">mercari.go #24</a> を YouTube でのオンライン配信にて開催しました。 今回は <a href="https://www.gophercon.com/">GopherCon 2023</a> に焦点を当てた特別回として、サンディエゴで開催された GopherCon 2023 に実際に現地参加したメルカリエンジニアが、セッション内容を要約して発表しました。この記事では、当日の各発表を簡単に紹介します。動画もアップロードされていますので、こちらもぜひご覧ください。</p>
<p><iframe loading="lazy" width="560" height="315" src="https://www.youtube.com/embed/qILDmTVzcII?si=UJ4Svmqi15bClDY9" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe></p>
<h2>GopherCon 2023 Recap</h2>
<p>1つめのセッションは <a href="https://twitter.com/nsega">nsega</a> さんによる「GopherCon 2023 Recap」です。</p>
<p>発表資料:<a href="https://docs.google.com/presentation/d/1P3cnabxCvKd1g64IwKtUUThj3UQ9FIVv6WhNjgy6s4A/edit#slide=id.g12176ea5b66_0_164">GopherCon 2023 Recap at mercari.go#24</a></p>
<p>このセッションでは、はじめに GopherCon の概要と今年の GopherCon 2023 の様子について紹介し、後半のパートでは GopherCon 2023 から「The Future of JSON」というセッションについて振り返りを行いました。<br />
Go の <a href="https://pkg.go.dev/encoding/json">encoding/json</a> パッケージは、長年使われてきた中で機能の不足やインターフェースの欠陥、パフォーマンスの制約といった問題を抱えていることがわかっていて、これを解決するために新たなメジャーバージョンである <a href="https://github.com/golang/go/discussions/63397">encoding/json/v2</a> パッケージが提案されています。セッションでは、これまでに挙げられた問題点が encoding/json/v2 パッケージによってどのように解決されるかについて詳細に説明されていました。<a href="https://pkg.go.dev/github.com/go-json-experiment/json">json</a> と <a href="https://pkg.go.dev/github.com/go-json-experiment/json/jsontext">jsontext</a> の2つのパッケージを用意する話など、個人的にもとても興味深かったです。<br />
またセッションの最後には、このような大きな変更を伴う開発をどのように進めていくか、という点についても触れられていました。オープンに議論をしながら合意形成をしたり、パフォーマンスに関してはベンチマークを取りながら進めるといった手法は、nsega さん自身も今後開発を行っていく上で参考にしたいと仰っていました。</p>
<h2>Concurrent Data Structures and CPU Caching with Go</h2>
<p>2つめのセッションは derrick さんによる「Concurrent Data Structures and CPU Caching with Go」です。</p>
<p>発表資料:<a href="https://docs.google.com/presentation/d/1I6ZJcMk_wRtdVbmhTAEc3h42pezQaH5hAThA-x4xcu8/edit?usp=sharing">Concurrent Data Structures and CPU Cache with Go</a></p>
<p>セッションの前半は、GopherCon 2023 の中から「Building A Highly Concurrent Cache in Go」というセッションの振り返りを行いました。このセッションでは Reddit でのキャッシュの活用事例について話されていて、Redis でのキャッシュとは別にローカルなキャッシュを導入することでコスト削減に成功した事例や、キャッシュのための独自のデータ構造を作成した事例などが紹介されていました。データ構造はシンプルなものから始めること、それを変更する際には都度プロファイルやベンチマークをとることといったノウハウが詰まった発表でした。<br />
セッションの後半では、前半のセッションで言及のあった Cache Line について掘り下げて解説を行いました。サードパーティの <a href="https://github.com/puzpuzpuz/xsync">xsync.Map</a> は標準パッケージの sync.Map と比べても高いパフォーマンスを見せていて、その理由は Cache Line を考慮したデータ構造にあるそうです。Cache Line を理解するための前提知識として CPU のキャッシュについてもわかりやすく説明されていて、個人的にもとても勉強になりました。</p>
<h2>Recap: Automatically Instrument Your Go Source Code with Orchestrion</h2>
<p>3つめのセッションは komatsu さんによる「Recap: Automatically Instrument Your Go Source Code with Orchestrion」です。</p>
<p>発表資料:<a href="https://speakerdeck.com/iamshunta/recap-automatically-instrument-your-go-source-code-with-orchestrion">Recap: Automatically Instrument Your Go Source Code with Orchestrion</a></p>
<p>このセッションでは Datadog 社が開発している <a href="https://github.com/DataDog/orchestrion">orchestrion</a> という CLI ツールについて紹介されました。orchestrion は Datadog で APM を計測するための Go のコードを自動計装 (code instrumentation) するためのツールです。Go は Java でいうアノテーションや Python でいうデコレータのような構文を持ちませんが、一方で文法がシンプルで AST のパースが簡単という特徴があり、それを踏まえたアプローチになっているそうです。また AST の解析には標準パッケージの go/ast ではなく <a href="https://github.com/dave/dst">dave/dst</a> を使っているそうで、その理由についても触れられていました。シンプルなツールであるため導入が簡単である一方、大規模なリポジトリに導入するにはまだいくつかの課題があると komatsu さんは考えているそうで、今後のさらなる開発に期待したいです。</p>
<h2>GopherCon 2023 Overview</h2>
<p>4つめのセッションは <a href="https://twitter.com/tenlingp">tenling</a> さんによる「GopherCon 2023 Overview」です。</p>
<p>セッションの前半では、GopherCon 2023 で行われた CTF について紹介しました。GopherCon で CTF が行われたのは今年が初めてで、セキュリティやコードに関してだけでなく、以前の GopherCon で発表されたテーマに関する出題もあったそうです。このセッションでは、CTF で出題された中から tenling さんがお気に入りの問題を2つ紹介しています。CTF は来年も実施を予定しているそうなので、今回の発表で興味を持った方はぜひチェックしておきましょう。<br />
セッションの後半では、GopherCon 2023 から「From Zero to Hero: Launch Your Own Game in 45 Minutes」というセッションについて紹介しました。<a href="https://github.com/eaddingtonwhite/feed-the-gopher">feed-the-gopher</a> というゲームを開発した事例の紹介で、<a href="https://www.gomomento.com/">Momento</a> というサービスを利用することで素早い開発を実現しているとのことでした。また、ゲーム開発では Unity などを利用するのが一般的になっていますが、Go でゲーム開発をする人がもっと増えてほしいという思いもこのセッションには込められているそうです。</p>
<h2>Navigating the Seas of Data: A Migration Journey</h2>
<p>5つめのセッションは mann さんによる「Navigating the Seas of Data: A Migration Journey」です。</p>
<p>発表資料:<a href="https://docs.google.com/presentation/d/1QNTiAVz3mwGUkA_6zcH7-E3fpFruE7kY/edit?usp=sharing&amp;ouid=106069324730307559717&amp;rtpof=true&amp;sd=true">GopherCon 2023 Bitly _ Migrating 80 billion records from MySQL to Bigtable</a></p>
<p>このセッションでは、Bitly がセルフマネージドな MySQL から Google Cloud の Bigtable へマイグレーションを行った事例が紹介されました。Key-Value 形式の膨大なデータを持つ Bitly 社では、従来の MySQL を利用した仕組みにおいてスケーラビリティやバックアップに関する課題を抱えていたそうです。それらの課題を解決するために Bigtable へのマイグレーションが実施され、結果として高いスケーラビリティや強固なバックアップが実現されました。セッションではマイグレーションの手順について順を追って説明されていて、中でも古いレコードのバックフィルなどを行うマイグレーションスクリプトが Go で記述されているそうです。マイグレーションは手順だけ見るとシンプルですが、これを膨大なデータを抱えるプロダクションサービスで実際にやりきったという事例はとても参考になると思います。</p>
<h2>GOOOPs – Talking about Go and OOPs</h2>
<p>6つめのセッションは amit-kumar さんによる「GOOOPs – Talking about Go and OOPs」です。</p>
<p>このセッションでは Go と OOP (オブジェクト指向プログラミング) の関係について説明しました。開発者の多くは Go に触れる以前に C++ や Java といった他のオブジェクト指向のプログラミング言語を扱った経験があるかもしれませんが、それらの言語におけるオブジェクト指向の考え方をそのまま Go に適用するべきではありません。そのような誤った方法で Go に OOP が適用された状態をこのセッションでは GOOOP (= GO + OOP) と呼び、GOOOP を疑うべき兆候としてどのようなものがあるか、具体例とともに紹介されています。またセッションの後半では、OOP とセットで語られることの多い SOLID 原則についても紹介しました。SOLID の各原則が Go ではどのように実現されるかについて、1つずつ順番に説明されています。従来の OOP の考え方をそのまま適用するのではなく、その背景にある原則を正しく理解し Go らしい書き方で実現することが重要であると結論づけていて、個人的にもとても勉強になるセッションでした。</p>
<h2>おわりに</h2>
<p>今回は GopherCon 2023 の活動報告会として、実際に現地参加したエンジニアからセッションの振り返りの発表をお送りしました。GopherCon 2023 に参加できなかった方も、イベントの雰囲気を感じていただけたのではないでしょうか?</p>
<p>ライブで視聴いただいた方も録画を観ていただけた方も本当にありがとうございました!</p>
<p>次回の開催もお楽しみに!<br />
イベント開催案内を受け取りたい方は、connpassグループのメンバーになってくださいね!<br />
<a href="https://mercari.connpass.com/">メルカリconnpassグループページ</a></p>
- Merpay & Mercoin Tech Fest 2023 セッション書き起こしまとめhttps://engineering.mercari.com/blog/entry/20231023-mmtf2023-list/https://engineering.mercari.com/blog/entry/20231023-mmtf2023-list/<p>Merpay & Mercoin Tech Fest 2023は、事業との関わりから技術への興味を深め、プロダクトやサービスを支えるエンジニアリングを知ることができるお祭りで、2023年8月22日(火)からの3日 […]</p>
Mon, 23 Oct 2023 10:11:07 GMT<p><a href="https://events.merpay.com/techfest-2023/">Merpay & Mercoin Tech Fest 2023</a>は、事業との関わりから技術への興味を深め、プロダクトやサービスを支えるエンジニアリングを知ることができるお祭りで、2023年8月22日(火)からの3日間、開催しました。セッションでは、事業を支える組織・技術・課題などへの試行錯誤やアプローチを紹介していきました。</p>
<p>今回、より多くの方に知ってもらい役立ててもらうため、Merpay & Mercoin Tech Fest 2023の全セッションを書き起こした記事を用意しました。セッションがたくさんあるので、その記事リンクまとめを作成したのが本記事です。お役に立てば幸いです。</p>
<h2>▼Merpay & Mercoin Tech Fest 2023 書き起こし一覧</h2>
<p>※各記事にYoutubeのセッション動画が埋め込まれています。</p>
<style>
.author_icon{width:50px; height:auto; margin:1px; border-radius:20px}
.day1{ border: 2px solid #ff0211 !important}
.day2{ border: 2px solid #4dc9ff !important}
.day3{ border: 2px solid #00c1aa !important}
</style>
<table>
<thead>
<tr>
<th style="text-align: center;">Author</th>
<th style="text-align: left;">Session Title</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: center;"><img src="https://events.merpay.com/techfest-2023/assets/kimuras-b65337cc.png" class="author_icon day1"></td>
<td style="text-align: left;"><a href="https://engineering.mercari.com/blog/entry/20231023-mmtf2023-day1-1/"><strong>Keynote</strong></a><br />Shunya Kimura</td>
</tr>
<tr>
<td style="text-align: center;"><img src="https://events.merpay.com/techfest-2023/assets/kimuras-b65337cc.png" class="author_icon day1"><br /><img src="https://events.merpay.com/techfest-2023/assets/keigow-d98347ba.png" class="author_icon day1"><br /><img src="https://events.merpay.com/techfest-2023/assets/nu2-db55c3e4.png" class="author_icon day1"></td>
<td style="text-align: left;"><a href="https://engineering.mercari.com/blog/entry/20231023-mmtf2023-day1-2/"><strong>How to Unleash Fintech</strong></a><br />Shunya Kimura , Keigo Watanabe , Noriaki Utsunomiya</td>
</tr>
<tr>
<td style="text-align: center;"><img src="https://events.merpay.com/techfest-2023/assets/kenken_merpay-32a2782c.png" class="author_icon day1"><br /><img src="https://events.merpay.com/techfest-2023/assets/shinmiy-5baa44f3.png" class="author_icon day1"></td>
<td style="text-align: left;"><a href="https://engineering.mercari.com/blog/entry/20231023-mmtf2023-day1-3/"><strong>1週間リリースを支えるAndroid自動テスト運用のその後</strong></a><br />Kenta Takahashi , Shintaro Miyabe</td>
</tr>
<tr>
<td style="text-align: center;"><img src="https://events.merpay.com/techfest-2023/assets/kenmaz-7524f598.png" class="author_icon day1"></td>
<td style="text-align: left;"><a href="https://engineering.mercari.com/blog/entry/20231023-mmtf2023-day1-4/"><strong>Merpay iOSのGroundUp Appへの移行</strong></a><br />kenmaz</td>
</tr>
<tr>
<td style="text-align: center;"><img src="https://events.merpay.com/techfest-2023/assets/takeshi-4ce6c27c.png" class="author_icon day1"></td>
<td style="text-align: left;"><a href="https://engineering.mercari.com/blog/entry/20231023-mmtf2023-day1-5/"><strong>Merpay iOSにおけるSwift Concurrency対応の挫折と今後</strong></a><br />Takeshi Sato</td>
</tr>
<tr>
<td style="text-align: center;"><img src="https://events.merpay.com/techfest-2023/assets/andooown-6454a46a.png" class="author_icon day1"></td>
<td style="text-align: left;"><a href="https://engineering.mercari.com/blog/entry/20231023-mmtf2023-day1-6/"><strong>SwiftUIでビットコインの価格チャートを改善・再実装した話</strong></a><br />andooown</td>
</tr>
<tr>
<td style="text-align: center;"><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAbgAAAG4CAMAAAAAFAKBAAAAwFBMVEX////+//////79/v38/fz+//77/Pv5+vr3+fj6+/v29/cLDAvu8O9DRETy8/MZGxoWFxcSExL09fXGyMeJi4p9fn0+Pz4rLSwOEA8ICgnn6Ofk5eXY2tkgIiHr7OzV19bR0tHMzc2qrKtLTEwFBwaxs7KRkpGCg4NnaWhVV1ba29u8vbynqaieoJ+YmplvcXBaW1tQUlEnKSgCAwL9/f3g4eF0dnVrbWxiZGO4ubmio6N3eXheYF84Ojk0NjUxMjFIzS4nAAAONklEQVR42uzBgQAAAACAoP2pF6kCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGB2zWi3YRAIgrP5/4/uU3S+7EET4kp92FFVmwtWJE8XA3UIIYQQQgghhBDCP0MS0vM3CJBUJfU2CNHrEpe+MF8tJKouENTHAI8H4T1Ud7cBlM1y0izXWQEvJdTBCxF3LE5QYbj4cgNUn5JUh2eRct8ChwmDJO5YXJeGBixqHpptFFllMUPlKULiemMRmAOEm7FTFta8UO2IO6EpwXy4LZdKNQ3YhrBKEHHnz7iKBxU8pijBiwA8deWca39XCZIi7kN84Cp5FsQOi8dbeUIIEPOMpAuOuM+HypKkCZqW0SH+CX6g/iZo3ckz7mM03esd0GX1J6Grey0i+pdG3An91g2CEOtpCL0baqyW69Q6ncwq71vH7RduVXBRLs6vABkkcUfm6MGqhg9zkwuWTsEjOkKWA0fefCd5Xib3PjSXDmIMrINI4g7NFUznXRhXxaa+VcViNM3OyZdYsOaUUYbb2Q5GORF3C9Nox27uAeNmFqxTWoAi7h42png7Sp5Z8JUdjN+S/8ed4OusPebZ4Pnj+5vUsZO9yhMsMluYBMKqm7vK5OQmhCx11Vyfekzx5qzMFZPEfcp1zcZyS8OKPjHZ2/WtE0jivuH3zStsP7ldU9KRRidmFTJU/tVbXjDf8u6yy6U+r5PyyWxdeVnoDLFcfME1a0u19GM58nI/JHFfIKbX72DImGmadMrLZd+AvFf5w765IzEMhDD0cf9LpyQYsXZwSqnIeDab6g0xH7FU848IJTg6r2pOIBgTSl0fALYuLJT/bGNBJ5rH6edLrAh/UANKAR7YELuVWgqIwbaljXoliCBAhpw8we+4pbotQVsqCcYlj4qS6PS5OE4o1MDgflfJO3qirwOQSCLoTJIr/YRo68JfVHr5WgBoC4m2IKh7BIAoIzC4jaLHSaplHsy+hNmZoiY87py81VANtEPZ3Jqz/QTIUM+5c/JGj1aq4GYmxzjPy2QVRK6KzUJrRQ06TZJ8QH8LVxbfD8DZg+RB6r6OyzShfMrQ44JTE609bIZ3o9esdoqujERdpmlxOKSe4Qn4fyPu+Hx/EUnjAWIX4AvJfPAhLggt9AodesHY+3EbTQyYizrpmYXbn4oVR1wO7BWgwIXUyKRfIuJc7nmQ+kq6pEYmkun2qSO4BoJEfOhdp1zHfdi5mt62YRj6hEdKsZ0gS4MN84buA0uxw9ZDFwzYLu3//1ftqS5BqnDsHvUuhgwIOjxQJMVHXozZWuXpAy8bmmVT1bPQfNxa4jzghQaISEGaDTRB7FsTh7g7FbGD8wFjtSnLab7QiFuJIBOwZgXDHkyI/7y/K0nJ7JSv7kcoU2lX5QJUC6ZxqBIn3MInbEStrs8hmmDUxh6uJy6+FDF9jObVejrpqZpUNSHYbQvslq52VS5DZdJdzeIQ6pqZEpNmGoYCuPwObbLQYtRqbKb2PS0QPU1qTlLUaP2icTQwdOFFsa5Z3OVIqEfzqFuOXeimU2XQMO5NGvD70co6F6M6Gg3JKVpRYzJvfx2v+r6IULWigIi9aXtkXgr3SOwyOR9hOLFdub1++N3nXDoR1lIAe0eaR7NmcQtQVZvPGKcnQlJUPt2N44fv26uyEZFEJouX57Q5J2/q40zCHcWGQFzTFkrh5ng9noZ//28+S86ceEM0zNKLadF0lQtgrKDaVoVKvqDCrsj252Echv2P3U2fM0VSBBjRbOsdWIs4Tnz9npyg1Cz8e/fxvH837O9ve3aFfHXyIRC2g7Tg5ELE/m2mTEE1MbP7dj+e94dh2B0zc7E2ZIRd0wJNc7KeOOvk6q+UnlRSKdJ/3Q3n0+nw5/2XnJWkek9pgaZkfmTvSpsSB4Jop7pnJgkQwo2cAsqlC6zrtR67//9fre5aBZPuQSTst3lVWF6JVT479vTxXn4EBwEABGkSfAeFnfrNxcVZszEMKdKIxGpcIE8ReaePPLDjIhMru1s2Qm9bBUhoTHW4qPwoPNY6nTg0GpHtT8mrdT7i8sA14sW3vIGX9lEFxkTF3mUpLbcX9UZ9WaVIk5JW9+03VunL1yqPXt8HXgIWZvtZLRIJKYov5y/z9Wz+bTWZjGJNmqz7bEsmXsvrP4QcdxUAKzYcVh1aU9R5KJeuht1l8Xzzcmd0ECibcBng1fNOt9goS2Hvs8dBItP73nruhsVqp/Z9lZAmdK4NcD1nT1wOOCPg8zghUqTPm63rIlbPvz0thsZEiISW/gK/swTwB/DjJKG4pQqjFKwM8/2lEI05b5brAyzOHlbPA00Y3NL2cuFgD5n2APi2znHYRgc3tbJzTC57qDTdmlGz8tClzmY9Hcdam4CIEW6BO3iCzyq/DjtxtBySuJMcE4VCwqhXSutJMmpO6yODiDv7w7L8BvCenx9BPzbeJHCJDF431hSY3qS/Sujp9We9qxEVv4Z1Gdgz2Dt9HBlxLi0orjn5EUw7xIW9yc1TkjQml9MqIqqP75Ht5WRHM2/8l6fiZXla7cg2Add92pXqItMr9a+Lg/m0MY4RlVJWMmO9nxWR9ea2eYmTPQKAdcGzKYcK/h4H+teDbnO6OSdUb3BJk8KO9bs3tz0Bdtjh8/y26RvX0SPStVLauBu/vjwnqN7hciQDW1MPfMTlgRgae8se9n4OoVmu01LjtZBeh4H6ByGIefXFj+flg9T5durV8IYrIplxv1A5+9HqhB+sqU8spa0PvbTvSfuo7kKY3RNHVJEetQqtdmET3qo9WkTyD/LmtqdcHZB/37LMHqKhQam8uUnrVSM/IGWAJy43bDpYJ84NUAoV6nDRbpXTsY4P8U2SvwB+yuvLcP/n+dxXGhE1mvA+bf8uXRmNbA7TLQvrx/Py4cAVfHB4CSuMI23G/fK6MQrxdoeD/ZeDF2E7sXyeDficQENK11rpbNbT2iqscGcC2POY9EXmU6WVnDY5KBGRhqXKfN2NI2KXsFRUFJL1yckRcPrF8Uolj8y/a9+ol4v2r/tBrEmcYs+S8/FpX6vMB8nsxg4Ku+5lE4eEStHdolCZVYm01dAWn5n8DwEA/MzJ0SHH0kEuyQWSeyYRxlovvxcK38IIiZ+8gd9RzEv8zMnXwEeVQRwUYQ4u281GY2qrZrtymUS4V7ZSPml41YVjISotuJxQIdvBViYMu/NW66xfK4YmeAf3+ztgJ9xnlV+Gu6ohZ4iWzw7SG4r35cpZe/L4M3bXqqVk1ROXD5yaTKN721RlvGJEgQ7HN+2Li7Ty2NPcjsDmMOPB5PUq84Axxg7Rbnt+JDQ6Tq7qD42r6f2QeLAdIqvo2zq57du5A4SdIIoLAajCpFh8e5kty2LnXB4YA9+Py+VKDHZcCUctYIq9AKwrbhutZnObTBnbLzbmwGFuD9z2z22b5Obfr1n9Ye+MsRyIQRj69/6X3tKPkcCJPemkLjseF9ESgwHxOnUUTUIZ14hV2LNC9l6W1DOVROoFHj6f1lZCY4jtjJzODFlPM1vnHhpoTyUnjhT0Dzz24sktmWZ1zRvy3bekVXTJUsQ9VZpIed4bxFlStHOuH/Lt4gQTaSBnJjnjDvFxJdb04zf3wJUzscYQSaQeo4hjV2Cu9EG174TK0RXVu2zSrfOusJB/CF8sd8JdiAopsbhz4ujOJoQHr3ZvgVNQT/P+PfbzhPe+JLQGB/4DuTm5htiLNbV9lMCcymOmloQD117lniqUhZm/ws3yT9YKEsd9ja+jAQfWAxg6qnQgeMrzfj8/TrmYmUVKUEh24D1MRcvizCOEzRx7r4S0Wf3Q4rAMYS+yxP9EXusQvcpTTMeWGiPGaDrOQDarIFdet8xt6auB+eJ7deE485TpmjTCoyQc+Bri6rWgdH9PN5VzCUSck1fwzKXN+qCDcwkIW1ufJBMbj+G/WMqd15xoVQDCmuTw4pzcoTiKSzTbmdSnDqh+oLNhonNyiuc0zL3fznpjpnLcYT0l06wOsDMqRG6mXl+hrXOgrFL2KUOOIWLa7xbEyqMO1Lm3gNftQriPCvo5iklIHQ+LjtGX1zJatVEdup9unQtU2tqUGewkGUrCRhaO061yxt0m5BrBGrQNGPrOAGy4VxyhYmkh7giVFmVKy5y7OLzugqx2Nshf+uNu4oFK3CBW2bOIFDN3Ld9pJX4Ffe0BQ3wmL/ct/9R30f8MIgl13byPcjWA/bK+3DaJ1HvmPPBE4nk6TKQS4o4wqZAIrX2vDeyCO2wva7ID/+zdQXLDIBAEwMn/P50qXwgaIGVZyan7ZCW2D9rCIK3Y/Thu2zO/kJq+9jKuA3VsfFIFqMsl7OanJKfopvYbZxzFnZO/CVySXVI161iuNrLm9xqYkdb5j+cqeyXfmbWqRLQP5Eui8/77qlhJxkEuXVmyvgpPt8tJag6c83g67z904yTnssyLS+iKQTcPP1fCV/bwM/uHxkdIV7Nbjo+bp4KeZFWc6CvycXfVMBnn9nDxnRqM24xpznvDBe6WWv2Pw7kJUpJuTbYKXpb1TpLqAD66w/upfF8Nqo0c1oc9gFK737aVR6PTx02rpECmU1uPLF9nq2QOWMc1mT6Un3lzI+6OWhGe5HQjOp1S7T+lg2pV+cAk16vHLupbK9G9JOeSQ3m9ELg7Rpi6DtD459byPelXyTm1rlzGm3aFMXJZbCbrfuzpttOXzSTJGHyZvlSFWAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADguz04JAAAAAAQ9P+1NwwAAAAAAAAAAAAAAH8Bfp5z9BoGlOEAAAAASUVORK5CYII=" class="author_icon day1"></td>
<td style="text-align: left;"><a href="https://engineering.mercari.com/blog/entry/20231023-mmtf2023-day1-7/"><strong>フロントエンドチームのスキルテスト評価システム改善の取り組み</strong></a><br />tokuda109</td>
</tr>
<tr>
<td style="text-align: center;"><img src="https://events.merpay.com/techfest-2023/assets/Hal-c933b43d.png" class="author_icon day1"><br /><img src="https://events.merpay.com/techfest-2023/assets/arvinh-b771c2d9.png" class="author_icon day1"><br /><img src="https://events.merpay.com/techfest-2023/assets/benhsieh-d6add507.png" class="author_icon day1"><br /><img src="https://events.merpay.com/techfest-2023/assets/jas-3a345cc3.png" class="author_icon day1"></td>
<td style="text-align: left;"><a href="https://engineering.mercari.com/blog/entry/20231023-mmtf2023-day1-8/"><strong>WYSIWYGウェブページビルダーを支える技術的マジックの裏側</strong></a><br />Hal Amano , Arvin Huang , Ben Hsieh , Jas Chen</td>
</tr>
<tr>
<td style="text-align: center;"><img src="https://events.merpay.com/techfest-2023/assets/codechaitu-6de682ea.png" class="author_icon day1"></td>
<td style="text-align: left;"><a href="https://engineering.mercari.com/blog/entry/20231023-mmtf2023-day1-9/"><strong>メルカリのカスタマージャーニーにおける不正防止の取り組み</strong></a><br />codechaitu</td>
</tr>
<tr>
<td style="text-align: center;"><img src="https://events.merpay.com/techfest-2023/assets/tim-45249ecf.png" class="author_icon day1"><br /><img src="https://events.merpay.com/techfest-2023/assets/mann-1969fe75.png" class="author_icon day1"><br /><img src="https://events.merpay.com/techfest-2023/assets/chris-1b8829bc.png" class="author_icon day1"></td>
<td style="text-align: left;"><a href="https://engineering.mercari.com/blog/entry/20231023-mmtf2023-day1-10/"><strong>日本におけるお客さま本人確認と今後の技術的課題</strong></a><br />Tim Tosi , Manpreet Kaur , Christophe Labonne</td>
</tr>
<tr>
<td style="text-align: center;"><img src="https://events.merpay.com/techfest-2023/assets/koi-52ba609b.png" class="author_icon day1"><br /><img src="https://events.merpay.com/techfest-2023/assets/kokukuma-6c7fcb1f.png" class="author_icon day1"><br /><img src="https://events.merpay.com/techfest-2023/assets/daichiro-4d00b987.png" class="author_icon day1"><br /><img src="https://events.merpay.com/techfest-2023/assets/hidey-bb2890e3.png" class="author_icon day1"></td>
<td style="text-align: left;"><a href="https://engineering.mercari.com/blog/entry/20231023-mmtf2023-day1-11/"><strong>メルカリへのFIDO導入の経緯とこれからの展望、課題から得た学び</strong></a><br />koi , kokukuma , daichiro , hidey</td>
</tr>
<tr>
<td style="text-align: center;"><img src="https://events.merpay.com/techfest-2023/assets/pedro-a169c7ca.png" class="author_icon day2"></td>
<td style="text-align: left;"><a href="https://engineering.mercari.com/blog/entry/20231023-mmtf2023-day2-1/"><strong>メルペイのあと払いとスマートマネーを支える返済基盤マイクロサービスの進化</strong></a><br />Peichong Cui</td>
</tr>
<tr>
<td style="text-align: center;"><img src="https://events.merpay.com/techfest-2023/assets/Rupesh-37e39daf.png" class="author_icon day2"></td>
<td style="text-align: left;"><a href="https://engineering.mercari.com/blog/entry/20231023-mmtf2023-day2-2/"><strong>拡張性を備えたソフトウェア設計</strong></a><br />Rupesh Agrawal</td>
</tr>
<tr>
<td style="text-align: center;"><img src="https://events.merpay.com/techfest-2023/assets/kazuya-feae7291.png" class="author_icon day2"><br /><img src="https://events.merpay.com/techfest-2023/assets/ksoichiro-5cbeef61.png" class="author_icon day2"><br /><img src="https://events.merpay.com/techfest-2023/assets/mikael-51e1995d.png" class="author_icon day2"></td>
<td style="text-align: left;"><a href="https://engineering.mercari.com/blog/entry/20231023-mmtf2023-day2-3/"><strong>発行枚数100万枚を支えたメルカードGrowth施策の裏側</strong></a><br />Kazuya Kawashima , Soichiro Kashima , Mikael</td>
</tr>
<tr>
<td style="text-align: center;"><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAbgAAAG4CAMAAAAAFAKBAAAAhFBMVEWc2PoAAADr6+vl5eUiIiIWFhZcXFwbGxu8vLy3t7cLCwsVHiIICQpgYGAQEBAfHx9cf5Nubm7a2tqVzu5AWWe7u7tYWFh1dXWUlJSHutjLy8tObH18fHzS0tKHh4eenp5eXl4xRE/g4ODW1tadnZ0jMTg1NTW6urpKSkppkahbe41fhJh4sIetAAAFJUlEQVR42uzTsU3EQAAAwTvJ6AOQSZwgYhL6L5ACfMEHSO+1ZlpY7QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADg/2zv1/E7eNrHvI7vgXB3J1yUcFHCRQkXJVyUcFHCRQkXJVyUcFHCRQkXJVyUcFHCRQkXJVzAMc8eC9uL7PPsayBclXBRwkUJFyVclHBRwkUJFyVclHBRwkUJFyVclHBRwkUJFyVclHBRwgUcCz/72efC9iKPt7NjYdzZXNgvdNez5sK4M+GihIsSLkq4KOGihIsSLkq4KOGihIsSLkq4KOGihIsSLkq4qD/27iClYSgKoGgiCFqKKFIHHQoK4v735wLyMUU7yE3PHb/3MjjTwAcXDVy0a8Md5u10HPQ97aQ9w416nnYSuGjgooGLBi4auGjgooGLBi4auGjgooGLBi4auGjgooGLBi4auGjgov0Hrvij0dO87HUKBg4cuF8CBw4cOHCrgQMHDhy41cCBAwcO3GrgwIEDB241cODAgQO3Gjhw4HYHNx+XHQYNV0e7G2reNdyNBS4auGjgooGLBi4auGjgooGLBi4auGjgooGLBi4auGjgooGLBi4auGi7gXt5WHZ3YW/3y+ZBx8HcaXBvtHu46Bs3+CgSuEDgooGLBi4auGjgooGLBi4auGjgooGLBi4auGjgooGLBi4auGjgooGLBi4auGjgooGLBi4auGjgooGLBi4auGjgooGLBi4auGjgooGLBi4auGjgooGLdm2482nZ46jB3Pvg3qW7o7GPQV/TTroYLtg86HnaSeCigYsGLhq4aOCigYsGLhq4aOCigYsGLhq4aOCigYsGLhq4aOCigYu2JbjP+e+dF9fAgdti4KKBiwYuGrho4KKBiwYuGrho4KKBiwYuGrho4KKBiwbuh7172YkbCMIo7B5HSUhICGYUQxhIuChc3v8BkSyhWVSN1VIj4WPOt67d6eUvNZThoAwHZTiotw53P0abSudjVBL/x2gwXFu4xxJtGpRE+hIMZzgEw0EZDspwUIaDMhyU4aAMB2U4KMNBGQ7KcFCGgzIclOGgDAdlOCjDQV0ktl+i202duz/RSYOS+JncXX20cJltiWK4eqWWKy/DGc5wHIaDMhyU4aAMB2U4KMNBGQ7KcFCGgzIclOGgDAdlOCjDQRkOynAApcFmQU5K1K2Z4aAMB2U4KMNBGQ7KcFCGgzIclOGgDAdlOCjDQRkOynBQhoMyHJThoAwHZTiAy210mthd1/mxIM+/gtXsgrrvJTpKXPV1yoJcdCtmOCjDQRkOynBQhoMyHJThoAwHZTgow0EZDspwUIaDMhyU4aAMB2U4KMNBnSWOE98SQx8NlX4nrodoTO6GSp+Oo27NSqUYrt7nxHkf7ZK7vtKqV16GWxHDQRkOynBQhoMyHJThoAwHZTgow0EZDspwUIaDMhyU4aAMB2U4KMMBNI2FFh4us5pPkQxnuJzhJoYznOEMN8dwE8MZznCGm2O4ieEMZzjDzTHcxHCGM5zh5hhuYjjDGc5wcww3MZzhDIcJ93QTjbvoLnHbN2gI9/Uh6hvcJ05vorNuQS5L9Pco6hu0hXsf/0pkOMMZ7pXhDGc4w+0Z7gDDGc5whtsz3AGGM5zhDLdnuAMMZzjDGW7PcC/t0bEJAAAIwLD/v3Z0chUKyQs5iBMnTtwSdxAnTlyZuChxUeKixEWJixIXJS5KXJS4KHFR4qLERYmLEhclLkpclLgocVHioj7iBsxT94NB0l+SAAAAAElFTkSuQmCC" class="author_icon day2"></td>
<td style="text-align: left;"><a href="https://engineering.mercari.com/blog/entry/20231023-mmtf2023-day2-4/"><strong>メルカードの常時ポイント還元開発の裏側</strong></a><br />keitaj</td>
</tr>
<tr>
<td style="text-align: center;"><img src="https://events.merpay.com/techfest-2023/assets/shiv3-7533f78d.png" class="author_icon day2"></td>
<td style="text-align: left;"><a href="https://engineering.mercari.com/blog/entry/20231023-mmtf2023-day2-5/"><strong>メルペイ加盟店売上精算の仕組み</strong></a><br />Takumi Shibazaki</td>
</tr>
<tr>
<td style="text-align: center;"><img src="https://events.merpay.com/techfest-2023/assets/a-r-g-v-f54390f2.png" class="author_icon day2"></td>
<td style="text-align: left;"><a href="https://engineering.mercari.com/blog/entry/20231023-mmtf2023-day2-6/"><strong>GoによるSQLクエリテストの取り組み</strong></a><br />Yuki Mukasa</td>
</tr>
<tr>
<td style="text-align: center;"><img src="https://events.merpay.com/techfest-2023/assets/Liu-6bed0f27.png" class="author_icon day2"><br /><img src="https://events.merpay.com/techfest-2023/assets/Li-df6bd9b1.png" class="author_icon day2"></td>
<td style="text-align: left;"><a href="https://engineering.mercari.com/blog/entry/20231023-mmtf2023-day2-7/"><strong>発生可能な取引の属性データを用いた素早い不正検知</strong></a><br />Liu , Li</td>
</tr>
<tr>
<td style="text-align: center;"><img src="https://events.merpay.com/techfest-2023/assets/shuuk-e7d0ef9a.png" class="author_icon day2"><br /><img src="https://events.merpay.com/techfest-2023/assets/haruki-39805199.png" class="author_icon day2"><br /><img src="https://events.merpay.com/techfest-2023/assets/yukis-181d3984.png" class="author_icon day2"></td>
<td style="text-align: left;"><a href="https://engineering.mercari.com/blog/entry/20231023-mmtf2023-day2-8/"><strong>メルペイMLにおける機械学習の品質保証とリスク管理</strong></a><br />shuuk , Haruki Kaneko , Yuki Saito</td>
</tr>
<tr>
<td style="text-align: center;"><img src="https://events.merpay.com/techfest-2023/assets/maze-d75851b3.png" class="author_icon day2"><br /><img src="https://events.merpay.com/techfest-2023/assets/tori-8bbd243a.png" class="author_icon day2"><br /><img src="https://events.merpay.com/techfest-2023/assets/nu2-db55c3e4.png" class="author_icon day2"><br /><img src="https://events.merpay.com/techfest-2023/assets/hmj-0f8c3ee3.png" class="author_icon day2"></td>
<td style="text-align: left;"><a href="https://engineering.mercari.com/blog/entry/20231023-mmtf2023-day2-9/"><strong>Merpay & MercoinにおけるLLM活用の取り組み</strong></a><br />Yuki Ishikawa , Daisuke Torigoe , Noriaki Utsunomiya , hmj</td>
</tr>
<tr>
<td style="text-align: center;"><img src="https://events.merpay.com/techfest-2023/assets/hyrrot-47aa24c3.png" class="author_icon day2"></td>
<td style="text-align: left;"><a href="https://engineering.mercari.com/blog/entry/20231023-mmtf2023-day2-10/"><strong>BigQueryのデータ監視社内サービスを作った話</strong></a><br />Hirobumi Takahashi</td>
</tr>
<tr>
<td style="text-align: center;"><img src="https://events.merpay.com/techfest-2023/assets/goro-334c75dc.png" class="author_icon day2"></td>
<td style="text-align: left;"><a href="https://engineering.mercari.com/blog/entry/20231023-mmtf2023-day2-11/"><strong>社内用GitHub Actionsのセキュリティガイドラインを作成した話</strong></a><br />Toshiki Kawamura</td>
</tr>
<tr>
<td style="text-align: center;"><img src="https://events.merpay.com/techfest-2023/assets/gouki-115971b2.png" class="author_icon day2"></td>
<td style="text-align: left;"><a href="https://engineering.mercari.com/blog/entry/20231023-mmtf2023-day2-12/"><strong>BigQueryのコンピューティングリソース管理の取り組み</strong></a><br />Go Kojima</td>
</tr>
<tr>
<td style="text-align: center;"><img src="https://events.merpay.com/techfest-2023/assets/vvakame-2093e246.png" class="author_icon day3"><br /><img src="https://events.merpay.com/techfest-2023/assets/hiraku-031e0ab4.png" class="author_icon day3"><br /><img src="https://events.merpay.com/techfest-2023/assets/tanaka0325-96eb2c09.png" class="author_icon day3"></td>
<td style="text-align: left;"><a href="https://engineering.mercari.com/blog/entry/20231023-mmtf2023-day3-1/"><strong>fake clock microservice -時刻をハックしてテストする方法-</strong></a><br />vvakame , Hiraku Nakano , Hiroyuki Tanaka</td>
</tr>
<tr>
<td style="text-align: center;"><img src="https://events.merpay.com/techfest-2023/assets/m-iino-ad50baf8.png" class="author_icon day3"><br /><img src="https://events.merpay.com/techfest-2023/assets/yuhara-e13c6942.png" class="author_icon day3"></td>
<td style="text-align: left;"><a href="https://engineering.mercari.com/blog/entry/20231023-mmtf2023-day3-2/"><strong>メルコインのインフラ設計・構築と、信頼性のあるサービスをリリースするためのSREの取り組み</strong></a><br />Masaki Iino , Takaaki Yuhara</td>
</tr>
<tr>
<td style="text-align: center;"><img src="https://events.merpay.com/techfest-2023/assets/pobo380-a243fad6.png" class="author_icon day3"></td>
<td style="text-align: left;"><a href="https://engineering.mercari.com/blog/entry/20231023-mmtf2023-day3-3/"><strong>メルコインにおけるシステム間のデータ分離を実現するための通信アーキテクチャ</strong></a><br />Kohei Noda</td>
</tr>
<tr>
<td style="text-align: center;"><img src="https://events.merpay.com/techfest-2023/assets/robert-700db6f9.png" class="author_icon day3"><br /><img src="https://events.merpay.com/techfest-2023/assets/keigoand-a71a209e.png" class="author_icon day3"><br /><img src="https://events.merpay.com/techfest-2023/assets/Sumil-f8287e65.png" class="author_icon day3"></td>
<td style="text-align: left;"><a href="https://engineering.mercari.com/blog/entry/20231023-mmtf2023-day3-4/"><strong>Building a Global environment at Merpay: India & Japan</strong></a><br />Robert Jerovsek , Keigo Andrade , Sumil Panicker</td>
</tr>
<tr>
<td style="text-align: center;"><img src="https://events.merpay.com/techfest-2023/assets/y-sakamoto-5d570c2e.png" class="author_icon day3"><br /><img src="https://events.merpay.com/techfest-2023/assets/satomasa-06030aec.png" class="author_icon day3"></td>
<td style="text-align: left;"><a href="https://engineering.mercari.com/blog/entry/20231023-mmtf2023-day3-5/"><strong>なめらかなFintech QAを実現するためにテストケースフォーマットを標準化した話</strong></a><br />Yuki Sakamoto , Masatoshi Sato</td>
</tr>
<tr>
<td style="text-align: center;"><img src="https://events.merpay.com/techfest-2023/assets/foghost-70d814a3.png" class="author_icon day3"></td>
<td style="text-align: left;"><a href="https://engineering.mercari.com/blog/entry/20231023-mmtf2023-day3-6/"><strong>メルコイン決済基盤の実践話</strong></a><br />Junwei Liang</td>
</tr>
<tr>
<td style="text-align: center;"><img src="https://events.merpay.com/techfest-2023/assets/susho-916e21bd.png" class="author_icon day3"></td>
<td style="text-align: left;"><a href="https://engineering.mercari.com/blog/entry/20231023-mmtf2023-day3-7/"><strong>メルコイン決済マイクロサービスのトランザクション管理を支える技術</strong></a><br />Shota Suzuki</td>
</tr>
<tr>
<td style="text-align: center;"><img src="https://events.merpay.com/techfest-2023/assets/keigow-d98347ba.png" class="author_icon day3"><br /><img src="https://events.merpay.com/techfest-2023/assets/osamingo-e35a3f8d.png" class="author_icon day3"><br /><img src="https://events.merpay.com/techfest-2023/assets/fivestar-7e00c1a5.png" class="author_icon day3"></td>
<td style="text-align: left;"><a href="https://engineering.mercari.com/blog/entry/20231023-mmtf2023-day3-8/"><strong>Merpay Engineering Career Talk</strong></a><br />Keigo Watanabe , Osamu Tonomori , Katsuhiro Ogawa</td>
</tr>
<tr>
<td style="text-align: center;"><img src="https://events.merpay.com/techfest-2023/assets/goccy-70413798.png" class="author_icon day3"></td>
<td style="text-align: left;"><a href="https://engineering.mercari.com/blog/entry/20231023-mmtf2023-day3-9/"><strong>gRPC Federation を利用した巨大なBFFサービスに対するリアーキテクチャの試み</strong></a><br />goccy</td>
</tr>
<tr>
<td style="text-align: center;"><img src="https://events.merpay.com/techfest-2023/assets/kazegusuri-6b68305e.png" class="author_icon day3"></td>
<td style="text-align: left;"><a href="https://engineering.mercari.com/blog/entry/20231023-mmtf2023-day3-10/"><strong>Enabling ProgramのEngineering Headをちょっとやってみている</strong></a><br />Masahiro Sano</td>
</tr>
</tbody>
</table>
<h2>Merpay & Mercoin Tech Fest 2023 プレイリスト</h2>
<p>Merpay & Mercoin Tech Fest 2023 の各セッションごとの動画と、各DayごとのLiveアーカイブ動画をまとめたプレイリストです。気になる動画や、一気に視聴する場合などにお役立てください。</p>
<ul>
<li>再生リスト:<a href="https://www.youtube.com/playlist?list=PL5y9uEm8_ypVK1p1IMrxhHixa3K0KKsOc">Merpay & Mercoin Tech Fest 2023 – YouTube</a></li>
</ul>
- 【書き起こし】社内用GitHub Actionsのセキュリティガイドラインを作成した話 – Toshiki Kawamura【Merpay & Mercoin Tech Fest 2023】https://engineering.mercari.com/blog/entry/20231023-mmtf2023-day2-11/https://engineering.mercari.com/blog/entry/20231023-mmtf2023-day2-11/<p>Merpay & Mercoin Tech Fest 2023 は、事業との関わりから技術への興味を深め、プロダクトやサービスを支えるエンジニアリングを知ることができるお祭りで、2023年8月22日(火)からの3 […]</p>
Mon, 23 Oct 2023 10:00:56 GMT<p>Merpay & Mercoin Tech Fest 2023 は、事業との関わりから技術への興味を深め、プロダクトやサービスを支えるエンジニアリングを知ることができるお祭りで、2023年8月22日(火)からの3日間、開催しました。セッションでは、事業を支える組織・技術・課題などへの試行錯誤やアプローチを紹介していきました。<br />
この記事は、「<a href="https://events.merpay.com/techfest-2023/#day-2_session11" title="社内用GitHub Actionsのセキュリティガイドラインを作成した話">社内用GitHub Actionsのセキュリティガイドラインを作成した話</a>」の書き起こしです。</p>
<p><iframe loading="lazy" width="560" height="315" src="https://www.youtube.com/embed/mIuyfrONrjA?si=2OIaaOecxtPJ9sy-" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe></p>
<p>@goro:今回、「社内用GitHub Actionsのセキュリティガイドラインを作成した話」というタイトルで発表させていただきます。株式会社メルコインで、バックエンドエンジニアをしております。Toshiki Kawamuraと申します。よろしくお願いします。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/09/c00770f2--2023-09-27-16.54.04.png" alt="" /></p>
<p>私は株式会社メルコインに、2022年の6月に入社しました。メルコインでは主にビットコイン取引サービスの立ち上げに参画して、バックエンドエンジニアとして開発運用を担当しております。</p>
<p>今回発表の題材になった、GitHub Actionsのセキュリティガイドラインの作成などの取り組みも関わっております。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/09/67164d0d--2023-09-27-16.54.44.png" alt="" /></p>
<p>まず発表の最初として、今回テーマに置いているGitHub Actionsのセキュリティガイドラインについて説明させていただきます。</p>
<p>GitHub Actionsセキュリティガイドラインとは、社内でのGitHub Actionsの利用の広がりに合わせて、社内有志によって検討策定された。セキュリティのガイドラインになっております。</p>
<p>GitHub Actionsを使うにあたり、どういった点に留意すれば、最低限の安全性を確保できるか学習してもらいたい、定期的に本ドキュメントを見返してもらい、自分たちのリポジトリが安全な状態になっているかを点検する際に役立ててもらいたいという想いに基づいて作成されているガイドラインになります。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/09/fa2cf448--2023-09-27-16.55.23.png" alt="" /></p>
<p>ガイドラインは、合計4人のチームで作成しました。</p>
<p>まずは株式会社メルカリのSolutions Teamに所属している@vvakameさん、株式会社メルカリのバックエンドエンジニアをしている Motonori Iwataさん、株式会社メルコインのエンジニアリングマネージャーをしている sadahさん、僕の4人の有志メンバーで、このガイドラインを作成していきました。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/09/eb908be4--2023-09-27-16.55.57.png" alt="" /></p>
<p>それでは、本発表の流れを事前に紹介します。まず最初にガイドラインの中身を一部紹介いたします。その後に、ガイドラインの社内での活用状況について紹介した後に、最後にガイドライン策定の裏話ができればと思います。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/09/fec292d8--2023-09-27-16.56.32.png" alt="" /></p>
<p>それでは発表に入っていきます。まずはガイドラインの中身を一部紹介します。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/09/5d5301a7--2023-09-27-16.57.07.png" alt="" /></p>
<p>ガイドラインを策定する上で、まずはチーム目標を決めました。</p>
<p>まず一つ目に「常に達成したいこと」として決めたのは、「外部の攻撃者からの攻撃を防ぐこと」。二つ目に、「可能であれば考慮したいこと」として、「内部と同等の権限を持つ攻撃者からの攻撃を防ぐ」。この二つを大きな目標に置いて、ガイドラインを作成していきました。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/09/055b587d--2023-09-27-16.57.45.png" alt="" /></p>
<p>ガイドラインの構成は3部になっています。</p>
<p>まずはどのような脅威があるのかを知ってもらうという意図を込めて、第1部では、脅威を知るというテーマで、GitHub Actionsを利用するにあたって、起こりうるセキュリティ上の脅威を紹介しました。</p>
<p>2部では、1部で紹介した脅威に対して、どのような対策が取れるのかを紹介しました。</p>
<p>最後の3部では、主に2部の内容をベースに、どのような項目を満たすと、実際にセキュリティの対策ができるのかがわかりやすくなるように、チェックリスト形式で行って、ほしい対策について、具体的な設定方法を含め、記載しました。このような内容でガイドラインは構成されています。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/09/b291bf22--2023-09-27-16.58.21.png" alt="" /></p>
<p>それでは1部から紹介していきます。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/09/1515ce8e--2023-09-27-16.58.59.png" alt="" /></p>
<p>脅威として紹介したのは、「権限設定の不備を突く攻撃」になります。プルリクエストを契機に起動するトリガーは攻撃者が何かを仕掛ける余地が大きく、不注意にワークフローを構築すると、シークレットを外部に送信されて攻撃を受ける可能性があります。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/09/727ff2ba--2023-09-27-16.59.39.png" alt="" /></p>
<p>補足情報として、GitHub Actionsのトリガーにはどのようなものがあるかを紹介していきたいと思います。</p>
<p>例えば、ワークフローのリポジトリで発生したイベントです。例えば、リポジトリのDefault Branchにプッシュが行われたときや、リリースが作成されたとき、あるいはIssueがオープンされたとき。</p>
<p>プルリクエストが作成されたときなどに、ワークフローを実行するように設定することが可能です。</p>
<p>他にも、GitHubの外部で発生し、GitHubでリポジトリディスパッチイベントを発生させるイベントですとか、時間指定での自動実行など、さまざまなトリガーがあります。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/09/cfd42fb1--2023-09-27-17.00.17.png" alt="" /></p>
<p>そのようなワークフローの権限設定において不備があると、例えばシークレットが外部に送信されるなどの危険性があります。</p>
<p>ここで、外部に送信される方法として考えられるものをいくつか記載しました。例えば、ビルドスクリプトに細工をする、依存関係にあるライブラリを悪意のあるものに差し替える、自動実行の仕組みに相乗りされる(例えばnpmのpreinstallやpostinstall)。</p>
<p>また、過去にも実際に人気のライブラリでローカルファイルをスキャンする事例がありました。このような方法でシークレットが外部に送信される危険性があります。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/09/307baa9d--2023-09-27-17.00.51.png" alt="" /></p>
<p>それ以外にも、権限設定の不備があると、攻撃が行われるような影響が考えられます。</p>
<p>例えば、攻撃者に悪意のあるActionsや、侵害されたActionsによってGitHub Actionsの計算リソースを不正に利用される可能性が考えられます。</p>
<p>他にも、侵害された または 悪意のあるActionsによって、リポジトリの自動ワークフローが中断される可能性もあります。他にもDeployment Keyやアクセストークンなどの、シークレットへの読み取りアクセスは攻撃者が他のリソースを侵害するために利用される可能性があります。以上が権限設定不備があった場合に、起こりうる事象の紹介です。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/09/0e415baa--2023-09-27-17.01.35.png" alt="" /></p>
<p>次に紹介したいのは、インジェクションによる攻撃です。一見安全に見えるワークフローにおいても、コードやコマンドインジェクションを引き起こす可能性があります。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/09/7c4b9ecd--2023-09-27-17.02.10.png" alt="" /></p>
<p>事例を二つ紹介します。まず、事例の一つ目になります。スライドのコードを見てください。</p>
<p>このコードにはインジェクションの脆弱性があります。コメントを二重括弧で囲っている箇所がありますが、ここに1+1のようなものを入れると、Actionsは内部で二重括弧のあたりを補完するためにlodashを使っているため、Node.jsのコードが実行され、出力が2になります。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/09/08f494c2--2023-09-27-17.02.44.png" alt="" /></p>
<p>二つ目の事例です。ワークフローのインラインスクリプトに直接インジェクションを配置するシナリオも考えられます。また、ブランチ名やメールアドレスへのコマンドインジェクションも可能です。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/09/e9c88660--2023-09-27-17.03.18.png" alt="" /></p>
<p>ここで具体的に紹介するのは、こちらのコードです。</p>
<p>内部の式の二重括弧が評価され、結果の値に置き換えられるため、コマンドインジェクションに対して脆弱になる可能性があります。ここではプルリクエストのタイトルが二重括弧に囲まれています。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/09/2aae4918--2023-09-27-17.03.53.png" alt="" /></p>
<p>攻撃者が実際にどのようなことができるかというと、「a”;ls $ GITHUB_WORKSPACE*」というタイトルのPRを作成する可能性があります。これを利用して、ステートメントを中断し、ランナーでコマンドを実行できるようになっています。実行すると、lsコマンドが確認できます。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/09/18576e9e--2023-09-27-17.04.41.png" alt="" /></p>
<p>インジェクションが起こると、どのような影響があるかを紹介します。インジェクションをされると、攻撃者は任意のコマンドを実行できるため、外部のサーバーにシークレットを送信するHTTPリクエストを行うことが可能になります。</p>
<p>リポジトリへのアクセストークンを取得しても、ワークフローが完了すると失効するので、攻撃自体は簡単ではありません。しかし、攻撃者が自動化し、管理するサーバーにトークンを呼び出してコンマ数秒で攻撃を実行することが可能です。</p>
<p>その場合、GitHub APIを利用してリリースを含むリポジトリのコンテンツを変更するなどの影響も考えられます。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/09/a065cc20--2023-09-27-17.05.35.png" alt="" /></p>
<p>なので攻撃者は悪意のあるコンテンツをGitHub Context経由で追加できるので、潜在的に信頼できない入力として扱う必要があります。以上が第1部「GitHub Actionsでの脅威を知る」の紹介でした。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/09/873c8101--2023-09-27-17.06.09.png" alt="" /></p>
<p>第2部は「対策を考える」です。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/09/b5ef1ae4--2023-09-27-17.10.01.png" alt="" /></p>
<p>まず対策の一つとして紹介したいのは、「最小権限の原則に従う」ということです。最小権限の原則は、ソフトウェアがタスクを達成するために必要な最小限の権限セットで実行されるべきであるという原則です。</p>
<p>ワークフローで利用可能なシークレットの権限と、ワークフロートリガーの種類に基づいて自動的に提供される一時的なリポジトリトークンの両方に当てはまります。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/09/27516587--2023-09-27-17.10.43.png" alt="" /></p>
<p>この原則に従うと、GITHUB_TOKENの権限のデフォルト設定は、読み取りと書き込み権限から読み取り専用に変更した方がいいと思います。</p>
<p>実際にこの設定をやろうとすると、リポジトリのSettings > Actions > Generalから変更できるので、ぜひ変更してみてください。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/09/2bf69d71--2023-09-27-17.11.29.png" alt="" /></p>
<p>また、GitHub Actionsの権限はジョブ単位で設定を行うことで、権限を最小化できますのでなるべく権限は細分化して設定することが推奨されます。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/09/3acfc4bc--2023-09-27-17.12.04.png" alt="" /></p>
<p>シークレットの利用についても、いくつか対策があります。例えば、Long=lived tokenを使用しない、Workload identity federationを用いたSecret Managerの利用を検討する、JSONなどの構造化データをシークレットにしないことがあげられます。</p>
<p>3つ目については、なぜかというと、GitHub Actionsは、全文をマスクデータとして扱ってくれますが、部分マスクはされないためです。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/09/5999f767--2023-09-27-17.12.38.png" alt="" /></p>
<p>ワークフロー内で使用される全てのシークレットマスクをマスクするように登録することも、対策として考えられます。シークレットに保存されたアクセストークンの利用状況を観察することも必要です。</p>
<p>他にもスコープが最小限のクレデンシャルを使用する、登録されたシークレット監査およびローテーションする、シークレットへのアクセスについてレビューを要求する。こういった対策が考えられます。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/09/7612760c--2023-09-27-17.13.16.png" alt="" /></p>
<p>次は、イベントトリガーの対策です。</p>
<p>利用すべきイベントトリガーとして、リポジトリへのwriteはできないように制限されているので、プルリクエストの処理にはpull_requestイベントを使った方がいいです。</p>
<p>少し制限を緩めたものとして、pull_request_targetがあります。Github Actionsのワークフロー自体は、pull_request_targetだと、Default Branchのものが使われます。ワークフローのyamlに直接記載する場合は、攻撃者によって上書きされることはありません。チェックアウトしたコードに含まれるComposite Actionを使う場合は注意が必要となります。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/09/83a75d03--2023-09-27-17.13.52.png" alt="" /></p>
<p>ここで、Composite Actionについて補足させてください。Composite ActionはカスタムActionの一つであって、使用することで、ワークフローの複数Stepを組み合わせて一つのActionsにできます。</p>
<p>例えば、複数のrunコマンドを一つのActionにまとめて、そのActionsを一つのStepとしてワークフローから呼び出して実行することが可能になってきます。</p>
<p>なのでpull_request_targetをイベントトリガーとして使う場合、Composite Actionは、攻撃者によって上書きされる可能性があるので、注意が必要です。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/09/e4d91ee3--2023-09-27-17.15.10.png" alt="" /></p>
<p>シークレットの内容を露出する際、可能な限り単位を狭くする方がいいです。Job単位よりStep単位の方がより良いと考えられます。Step間のファイルによるデータのやり取りは、全ステップから可視であると考えてください。</p>
<p>Jobは処理によって分けることも一つの対策になります。例えば、テスト/ビルド/デプロイはそれぞれJobを分けた方がいいです。これはなぜかというと、必要なGithub ActionsのPermissionやクラウドプロバイダーの権限を制御できるためです。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/09/269e8087--2023-09-27-17.16.12.png" alt="" /></p>
<p>次に紹介したいのが、Dependabot / Renovateを利用したGithub Actionsでの更新になります。Actionsはバグの修正や新機能によって、頻繁に更新されます。Dependabot / RenovateでGitHub Actionsの依存関係を最新に保つことができるため、設定しておくとより良いと考えます。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/09/9935b7ae--2023-09-27-17.16.50.png" alt="" /></p>
<p>次はサードパーティのActionsを利用する際の注意点です。サードパーティのActionsを利用する場合、基本的にFull Changeset Hashに固定するのがいいと考えています。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/09/b809e426--2023-09-27-17.17.22.png" alt="" /></p>
<p>サードパーティのActionsの書き方は、四つあります。</p>
<p>まず一つ目がFull Changeset Hash。これは基本的には衝突が困難になっています。次にあるのがShort Changeset Hash。これも衝突がしにくいですが、脆弱となっています。次に、よく使われるTag / Releaseです。この場合、タグを後で変更されて意図しない変更が混入してしまう可能性があるので、注意が必要です。Branch Nameの場合意図しない変更が混入してしまう可能性もありますし、将来壊れる可能性があるので、なるべくFull Changeset Hashで指定するのがいいと考えています。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/09/a2518ce4--2023-09-27-17.17.59.png" alt="" /></p>
<p>Full Changeset Hashで記入すると、このバージョンを使っているのかがいまいちわからないなっていうのがあるので、その場合はバージョンコメントを記載するのがわかりやすくておすすめです。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/09/3151d0b9--2023-09-27-17.18.33.png" alt="" /></p>
<p>同じくサードパーティActionsを利用する際の注意点でもありますが、Actionsのソースコードをしっかり観察して、サードパーティのホストにシークレット送信するなどの疑わしいことがないか確認することが必要です。</p>
<p>ワークフロー内で利用しているサードパーティActionsのAction permissionsの設定をセキュリティ観点で見直すことも推奨されます。この設定に関しては同じくリポジトリのSettingsで可能になっています。不要なワークフローやJobは削除した方がいいです。不要なものは削除して、なるべく依存を減らすのが良いです。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/09/f16c7f8d--2023-09-27-17.19.08.png" alt="" /></p>
<p>先ほども触れたインジェクションについてですが、これを防ぐためには信頼されない式の入力値を中間環境変数に設定することが、対策として考えられます。</p>
<p>例ではこのようにenvに中間環境変数を入れていますが、この方式は、スクリプトの生成に影響するのではなく、メモリに保存されて変数として使用されます。このように、信頼されない式の入力値を中間環境変数に設定するのも有効です。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/09/386f9d80--2023-09-27-17.19.48.png" alt="" /></p>
<p>他にも、シェル変数をダブルクォートして単語の分割を避ける。これはシェルスクリプトの一般的な水槽事項でもあります。</p>
<p>GitHubのカスタムアクションやワークフローを書くときは、信頼できない入力に対して書き込み権限でコードを実行することがあることを考慮した方がいいです。外部Actionsとなりますが<a href="https://github.com/rhysd/actionlint" title="actionlist">actionlist</a>を使用することで対策できるので、導入を検討したり、GitHub Security Labの開発する<a href="https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning" title="CodeQL queries">CodeQL queries</a>を利用したりすることも対策として考えられます。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/09/6eca57d1--2023-09-27-17.21.11.png" alt="" /></p>
<p>それでも完全に攻撃を防ぐことは不可能と考えて、問題が発生したときに受ける影響を最小限に抑える必要があります。</p>
<p>例えば、プロダクション環境に影響を及ぼす(サービス停止など)ことが最悪のケースなので、対策が必要です。もう一つ、GitHub Action がPRを作成またはオーナーとして承認しないようにすることも対策の一つです。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/09/c2133496--2023-09-27-17.22.01.png" alt="" /></p>
<p>最後に、第3部の「セルフチェックリスト」です。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/09/a3e0eaf2--2023-09-27-17.22.41.png" alt="" /></p>
<p>セルフチェックリストは、定期的にチェックすることで、GitHub Actionsの安全な利用に繋げるという目的があり、ガイドラインで学習した内容が本チェックリストでカバーすることを目指して作成されています。</p>
<p>第2部の内容をベースに、講じてほしい具体的なセキュリティ対策を設定方法含め、チェックリスト形式で記載しています。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/09/a360b07d--2023-09-27-17.23.18.png" alt="" /></p>
<p>ここでは一例を紹介します。例えばCODEOWNERSの設定を見直すことが一つチェックリストにあります。CODEOWNERSというファイルが.githubディレクトリにあるのですが、そこで適切にコードのオーナーが設定されることが必要になってきます。</p>
<p>Protected Branchの設定で、Default BranchへのPull RequestがCODEOWNERSによる承認が必須になっていることも、チェックする必要があります。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/09/0f3d3d88--2023-09-27-17.23.56.png" alt="" /></p>
<p>続いて、ワークフロートリガーを見直すこと。コードプッシュをトリガーとする場合、pull_requestか、それが難しければ、pull_request_targetを使うことを考えた方がいいです。<br />
on: psuhをpull_request用に使っていたら見直す必要があります。<br />
このように、具体的な対策をチェックリスト形式で書いています。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/09/cbccc25f--2023-09-27-17.24.33.png" alt="" /></p>
<p>さらに詳しい内容はブログに公開しておりまして、そちらを見ていただきますと、今回発表したガイドラインの内容が更に詳しくなっておりますので、ぜひご覧いただければありがたいです。</p>
<p>参照<br />
<a href="https://engineering.mercari.com/blog/entry/20230609-github-actions-guideline/" title="社内用GitHub Actionsのセキュリティガイドラインを公開します">社内用GitHub Actionsのセキュリティガイドラインを公開します</a></p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/09/635c2182--2023-09-27-17.25.30.png" alt="" /></p>
<p>次は、ガイドラインが実際に社内でどのように活用できているかを紹介します。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/09/8e450ed2--2023-09-27-17.26.10.png" alt="" /></p>
<p>一つ目の活用状況として紹介したいのが、Developer Documentationの追加です。これは主に、メルカリ、メルペイ、メルコインのバックエンドエンジニアがよく参照する社内プラットフォームの使い方がまとまった社内ポータルです。そこにGitHub Actionsのセキュリティガイドラインを掲載していただきました。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/09/9f69597f--2023-09-27-17.26.50.png" alt="" /></p>
<p>次はSecure Coding Guidelinesの掲載です。このガイドラインはSecurity Teamがメンテナンスする社内基準のセキュリティルールを満たすためのガイドラインです。ここにもGitHub Actionsのガイドラインを掲載しました。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/09/d79fdc6b--2023-09-27-17.27.31.png" alt="" /></p>
<p>他にも各チームでのガイドライン提供やサポートなどを行っており、プロジェクトメンバーに自チームに関するリポジトリに対して、今回作成したガイドラインの内容を適用させたり、他のチームがガイドラインを適用する際のサポートや質問を受け付けるような体制となりました。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/09/0d0517a1--2023-09-27-17.28.53.png" alt="" /></p>
<p>最後に、「ガイドライン策定の裏話」をします。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/09/d5d3b978--2023-09-27-17.29.31.png" alt="" /></p>
<p>まず、このガイドラインをどのように作ったかについて紹介します。まず最初に、作成する上で行ったことは、GitHub Actionsのセキュリティに関する文献記事をチームメンバーで読んでいくことです。記事には複数の記事がリンクされているので、それらも読んでいきました。</p>
<p>そこで得たインプットをもとにガイドガイドラインのアウトラインをまとめて、3部構成を作りました。参考文献の設定を試したりしながら、ガイドラインを変えていくフェーズに入り、その後に自分たちでガイドラインをレビューして、あとSecurity Teamにもレビューしていただきました。レビューをいただいた内容を修正して、英訳して公開しました。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/09/a8978043--2023-09-27-17.30.05.png" alt="" /></p>
<p>ちょうど1年前ぐらいから始まったプロジェクトで、2022年の7月から9月の間にメンバーを招集して、アウトライン・執筆を開始していきました。その後、10月から12月の間にレビューを実施したり、リファクタリングをした後に、2023年に入ってから最終レビューが完了して、正式版を公開しました。4月から6月の間に、エンジニアリングブログでの社外公開なども行いました。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/09/14a5bf18--2023-09-27-17.30.39.png" alt="" /></p>
<p>ガイドラインを作成していく中で、気をつけた点についても、4点ほど紹介させてください。</p>
<p>まず一つ目は「小さく始める、無理をしない」ということ。これはボランティアメンバーで、それぞれのメンバーが別のプロジェクトを持ちながら進めていったので、なるべく無理をしない形で、進めていきました。</p>
<p>二つ目が、「絶対完成させるという強い意志を持つ」。こういう有志の取り組みを長期的に継続していくのは難しいかなと思うのですが、でも絶対完成させるという強い思いをみんなで持って、完成させました。</p>
<p>三つ目が、「適切な量のフィードバックをもらえるように意識する」。これはちゃんと外部の意見を取り入れながらリファクタリングできるようにという名目でもありますし、大量にフィードバックが降ってくると修正も大変なので、適切な量になるようにコントロールしてもらいました。</p>
<p>四つ目が、「正式版を公開してから育てていく」。GitHub Actionsやセキュリティなどに関連する技術は、今後も日々アップデートしていくので、正式版を公開したから終わりではなく、今後も育てていくようにしていきたいです。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/09/9e6998d7--2023-09-27-17.30.47.png" alt="" /></p>
<p>GitHub Actionsのセキュリティガイドラインは、今後も適切に更新していき、よりスムーズで安全な開発をサポートできるように努めていきたいと思っています。また更新した際には外部向けにも発信していこうと考えておりますので、ぜひご覧いただけるとありがたいです。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/09/a2857322--2023-09-22-9.18.13.png" alt="" /></p>
<p>以上です。ご清聴ありがとうございました。</p>
- 【書き起こし】Keynote – Shunya Kimura【Merpay & Mercoin Tech Fest 2023】https://engineering.mercari.com/blog/entry/20231023-mmtf2023-day1-1/https://engineering.mercari.com/blog/entry/20231023-mmtf2023-day1-1/<p>Merpay & Mercoin Tech Fest 2023 は、事業との関わりから技術への興味を深め、プロダクトやサービスを支えるエンジニアリングを知ることができるお祭りで、2023年8月22日(火)からの3 […]</p>
Mon, 23 Oct 2023 10:00:54 GMT<p>Merpay & Mercoin Tech Fest 2023 は、事業との関わりから技術への興味を深め、プロダクトやサービスを支えるエンジニアリングを知ることができるお祭りで、2023年8月22日(火)からの3日間、開催しました。セッションでは、事業を支える組織・技術・課題などへの試行錯誤やアプローチを紹介していきました。<br />
この記事は、「<a href="https://events.merpay.com/techfest-2023/#day-1_session1" title="Keynote">Keynote</a>」の書き起こしです。</p>
<p><iframe loading="lazy" width="560" height="315" src="https://www.youtube.com/embed/qOLQ6_s9FB0?si=oTDG9Qw8_6df4G8p" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe></p>
<p>@kimuras:メルカリFintech領域のCTOを担当しているKimuraです。本日はMerpay & Mercoin Tech Fest 2023をご視聴いただき、誠にありがとうございます。</p>
<p>メルペイが2017年に設立してから、2023年になって決済や与信、クレジットカード、暗号資産など、多くの金融サービスを提供してまいりました。今回は、多くの成功を支えてきた技術をふんだんにまとめ、Merpay & Mercoin Tech Fest 2023を実施することになりました。</p>
<p>また、これら金融サービスを伝える技術的な土台をいかして、これからもFintechサービスを成長させ、「世界をなめらかにする」というミッションを実現していくことを目指し、次世代のFintechサービスを作るためのアイディアやヒントになるような、気づきとなるものをご提供できたら幸いです。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/10/52c0f3a7--2023-10-19-14.15.15.png" alt="" /></p>
<p>私はKimuraと申します。メルペイ・メルコインのCTOを担当しています。</p>
<p>もともと機械学習領域を担当していましたが、2017年より株式会社メルカリに入社し、研究開発組織R4Dの立ち上げを行い、AIを中心とした幅広い研究領域のリサーチを担当しました。その後、AIと検索エンジン領域のエンジニア組織を設立してDirectorとしてメルカリのAI導入をリードしました。</p>
<p>2022年7月より、社内のプラットフォーム開発を統括するVP of Platform Engineeringを担当し、CTOとして4月から働いています。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/10/46491cf3--2023-10-19-14.17.08.png" alt="" /></p>
<p>まず、メルカリグループのFintech事業を振り返ります。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/10/2330047e--2023-10-19-14.17.36.png" alt="" /></p>
<p>2023年2月にメルカリグループは10周年を迎え、「あらゆる価値を循環させ、あらゆる人の可能性を広げる」というミッションを新しくしました。</p>
<p>メルペイはフリマアプリ「メルカリ」での売る・買うの取引を通じて信用情報を可視化し、新しい信用の形を生み出して、それに基づいてお金を自由に使える世界を作ることを目指しています。</p>
<p>メルコインはモノ・お金だけではなくて、暗号資産、NFTなど、あらゆる価値をめぐらせて新しい経済を作ることを目指しています。世界中のモノやコト、人には見出されていない価値がたくさんあり、その価値を必要としている人もまた世界中で数多く存在していると思っております。</p>
<p>メルカリグループはテクノロジーの力で世界中の人々をつないで、有形・無形に限らずあらゆる価値が循環するエコシステムを作ることを通じてその人の可能性を広げる存在でありたいと考えています。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/10/f3ccd429--2023-10-19-14.18.21.png" alt="" /></p>
<p>メルペイは2019年2月にサービスを開始しました。iD決済・コード決済といった決済領域から始まり、あと払いサービスであるメルペイスマート払いで、与信領域を本格的にスタートさせました。</p>
<p>その後、バーチャルカードやメルペイスマートマネー、メルカリの利用実績等で限度額が決まってアプリで利用と管理が完結できるメルカードをローンチするなど、サービスを拡充しました。</p>
<p>メルカードは提供開始から<a href="https://jp.merpay.com/news/2023/06/20230609_1million/" title="約半年で発行枚数100万枚を突破">約半年で発行枚数100万枚を突破</a>して、国内で今トップレベルとなっております。そして2023年3月からメルカリアプリ内でビットコインが売買できるようになりました。この開発を担うのがメルコインとなります。</p>
<p>金融機関からチャージした残高はもちろん、メルカリで不用品を売って得た売上金やポイントを活用して、1円という少額から安心して始めることができます。こちらも提供開始から<a href="https://about.mercoin.com/news/20230628_bitcoin500k/" title="3ヶ月強で利用者数50万人を突破">3ヶ月強で利用者数50万人を突破</a>しています。</p>
<p>これらのように2019年からサービスの提供を開始して、Fintech領域では1,571万人ものお客さまにご利用いただくまでに成長しました。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/10/97b05691--2023-10-19-14.20.29.png" alt="" /></p>
<p>メルカリグループで構築する「循環型金融」ということで、ここからこれらのサービスを支える技術について触れていきたいなと思います。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/10/69bcfc4d--2023-10-19-14.21.06.png" alt="" /></p>
<p>多くのサービスを開発し、約1,500万人ものお客さまに金融サービスを提供してきた成長の背景には どのようなものがあるのかをお話しします。</p>
<p>金融サービスはセキュリティ的な要件が非常に高く、技術的には妥協が許されない領域です。</p>
<p>そしてメルペイの特徴でもある「なめらかな社会を実現する」ため、簡単で便利なUIを提供するという攻めの姿勢と、セキュリティを高めるための守りの姿勢は、基本的に相反する関係にあります。</p>
<p>UIを簡易的にすればするほど、セキュリティは甘くなってしまいますし、セキュリティを厳しくすればするほど、簡易なUIを提供することは難しくなってしまいます。</p>
<p>我々はこの攻めと守りのバランスを保つことで、大きな障害を避けつつも便利なUIを提供することでお客さまの支持をいただいてきたと考えています。</p>
<p>攻めと守りを実現するために、人材投資にもバランスを保ってきているのが、我々を支えてきた源泉だと考えています。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/10/fc7b9135--2023-10-19-14.38.04.png" alt="" /></p>
<p>大きく組織を説明しますと、メルペイのエンジニア組織は大きく分けて、プロダクト開発をメインで行っているProduct Engineeringと、Foundationやプラットフォームの開発を行っているPlatform Engineeringの二つの組織があります。</p>
<p>メルペイは設立当初から現在までProduct EngineeringとPlatform Engineeringに対して、バランスよく人材投資を続けており、人数はおおむね同じ割合となっております。</p>
<p>なめらかなUI提供をするためにProduct Engineeringは重要ですし、堅牢なFoundation Platformを構築するPlatform Engineeringと同じ割合で投資することで、複雑なシステムであってもSecurityやAvailabilityを担保し続けているという背景があります。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/10/1fe55d50--2023-10-19-14.38.39.png" alt="" /></p>
<p>Fintechの成長を支えてきたテクノロジー投資についても説明します。多くのFoundationへの投資の中でも、今回は不正対策、セキュリティ、リアーキテクチャ、独自の与信モデルについてご紹介したいなと思っております。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/10/b9945cde--2023-10-19-14.39.16.png" alt="" /></p>
<p>基本的に不正対策はルールベースのものもありますが、機械学習をメインに活用し、Vertex AI Pipelinesを導入してモデルのトレーニングやデプロイを共通化しています。また特徴量を共有化することによるコスト削減はFeature StoreとしてFEASTを導入しています。これにより保守性担保やコスト削減だけじゃなくて結果としてエンジニアがモデル開発の改善に、より時間を費やすことができるようになり、生産性向上や品質改善にもつながりました。</p>
<p>メルペイの不正対策に活用しているのが、グラフ理論というものです。節点と呼ばれるノードの集合と、辺と呼ばれるリンクの集合で構成されるグラフに関する数学の理論を活用しています。メルカリ・メルペイには非常に多種多様なデータがあります。お客さまのアカウントの情報や出品、決済、購入といった情報をグラフとして表現し、類似度を計算するといったことができます。</p>
<p><a href="https://engineering.mercari.com/blog/entry/20221018-mtf2022-day1-3/" title="【書き起こし】グラフ理論と不正対策 つながりをデータから解き明かしたい – hmj 【Merpay Tech Fest 2022】">【書き起こし】グラフ理論と不正対策 つながりをデータから解き明かしたい – hmj 【Merpay Tech Fest 2022】</a></p>
<p>不正対策・不正検知については、今年もセッションを用意していますので別セッションで詳細をご覧ください。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/10/d1b2c53a--2023-10-19-14.43.25.png" alt="" /></p>
<p><a href="https://engineering.mercari.com/blog/entry/20231023-mmtf2023-day1-9/" title="【書き起こし】メルカリのカスタマージャーニーにおける不正防止の取り組み – codechaitu【Merpay & Mercoin Tech Fest 2023】">【書き起こし】メルカリのカスタマージャーニーにおける不正防止の取り組み – codechaitu【Merpay & Mercoin Tech Fest 2023】</a><br />
<a href="https://engineering.mercari.com/blog/entry/20231023-mmtf2023-day2-7/" title="【書き起こし】発生可能な取引の属性データを用いた素早い不正検知 – Liu / Li【Merpay & Mercoin Tech Fest 2023】">【書き起こし】発生可能な取引の属性データを用いた素早い不正検知 – Liu / Li【Merpay & Mercoin Tech Fest 2023】</a></p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/10/2a4ed18e--2023-10-19-14.44.36.png" alt="" /></p>
<p>続いて、セキュリティについてです。セキュリティでは3DセキュアやFIDO、パスキーの導入を行ってきました。</p>
<p>3Dセキュアの導入は不正利用を未然に防ぐための対策です。2021年12月に比べて、不正利用数は10分の1まで抑えることができました。図は1年前の状態を示しています。直近でもこの低水準の状態を継続できています。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/10/a320a485--2023-10-19-14.45.32.png" alt="" /></p>
<p>本件に関しても、去年のセッションやブログで紹介しています。今年も進化した部分であると、2022年11月に、<a href="https://about.mercari.com/security/news/articles/20221101_fidoalliance/" title="FIDOアライアンスに加盟">FIDOアライアンスに加盟</a>して、メルカリの各種サービスにFIDO認証の実装を進めています。2023年3月にリリースをしたメルコインにFIDOあるいはパスキーを導入しています。</p>
<p><a href="https://engineering.mercari.com/blog/entry/20221018-mtf2022-day2-2/" title="【書き起こし】Credit Card Payment Security: adding 3D Secure SDK for Merpay iOS – Mikael LE GOFF 【Merpay Tech Fest 2022】">【書き起こし】Credit Card Payment Security: adding 3D Secure SDK for Merpay iOS – Mikael LE GOFF 【Merpay Tech Fest 2022】</a><br />
<a href="https://engineering.mercari.com/blog/entry/20221018-mtf2022-day2-5/" title="【書き起こし】メルカリグループの認証基盤における理想と現状、今後の取り組み – kokukuma 【Merpay Tech Fest 2022】">【書き起こし】メルカリグループの認証基盤における理想と現状、今後の取り組み – kokukuma 【Merpay Tech Fest 2022】</a></p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/10/19ebaa3d--2023-10-19-14.46.56.png" alt="" /></p>
<p>約2年半かけて、iOS Androidアプリケーションでも同じく、スクラッチから作ったアプリケーションに移行するプロジェクトを進めました。</p>
<p>今はすでにこの新しいバージョンを全てお客さまに提供できておりまして、コードベースもモダンかつコンパクトな状態です。今後は生産性が改善によって上がり、よりよい機能をより早くお客さまに提供できるようになっています。</p>
<p>リアーキテクチャした背景として、メルカリのアプリリリースから約10年近くの月日が経っております。アプリのアーキテクチャの潮流やUI/UXのフレームワーク、OSが提供する機能など、何もかもが大きく変わっています。</p>
<p>またメルカリはこの間に大小さまざまな機能がリリースされており、コードベースも膨大になってビルドやメンテナンスに大きなコストがかかっていました。モダンなフレームワークの導入や新たなデザインシステムの採用などに取り組んできましたし、最終的には生産性の観点からAndroidでもコード量を約半分の程度に減らすことができたということで、大きなメリットを得ることができました。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/10/3476b279--2023-10-19-14.47.33.png" alt="" /></p>
<p>ほかにもさまざまなチャレンジをしていますので、詳細は他セッションをご覧ください。</p>
<p><a href="https://engineering.mercari.com/blog/entry/20231023-mmtf2023-day1-4/" title="【書き起こし】Merpay iOSのGroundUp Appへの移行 – kenmaz【Merpay & Mercoin Tech Fest 2023】">【書き起こし】Merpay iOSのGroundUp Appへの移行 – kenmaz【Merpay & Mercoin Tech Fest 2023】</a><br />
<a href="https://engineering.mercari.com/blog/entry/20231023-mmtf2023-day1-5/" title="【書き起こし】Merpay iOSにおけるSwift Concurrency対応の挫折と今後 – Takeshi Sato【Merpay & Mercoin Tech Fest 2023】">【書き起こし】Merpay iOSにおけるSwift Concurrency対応の挫折と今後 – Takeshi Sato【Merpay & Mercoin Tech Fest 2023】</a></p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/10/d98860e4--2023-10-19-14.48.41.png" alt="" /></p>
<p>技術的な観点では、独自の与信モデルも欠かせません。メルペイでは包括信用購入あっせん業者の認定を日本で初めて取得し、勤続年数や年収など、一般的な属性情報だけじゃなく、メルカリが持っているデータ、あるいはメルカリの行動実績に基づいて、信用を判断することができます。独自与信モデルはメルペイスマート払いやメルペイスマートマネー、メルカードなどのサービスで活用されています。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/10/e70e556f--2023-10-19-14.49.16.png" alt="" /></p>
<p>機械学習は不正対策だけではなく、このような独自与信モデルも使っています。詳細は、こちらのセッションをご覧ください。ITエンジニアとITリスクマネジメントのメンバーが密にコミュニケーションをとり、AIの危険性やリスク管理にどのように対応するかを解説します。</p>
<p><a href="https://engineering.mercari.com/blog/entry/20231023-mmtf2023-day2-8/" title="【書き起こし】メルペイMLにおける機械学習の品質保証とリスク管理 – shuuk / Haruki Kaneko / Yuki Saito【Merpay & Mercoin Tech Fest 2023】">【書き起こし】メルペイMLにおける機械学習の品質保証とリスク管理 – shuuk / Haruki Kaneko / Yuki Saito【Merpay & Mercoin Tech Fest 2023】</a></p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/10/bbfdefad--2023-10-19-14.50.05.png" alt="" /></p>
<p>ここからは、これからの技術的な挑戦について簡単に説明します。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/10/18738c14--2023-10-19-14.50.13.png" alt="" /></p>
<p>これまで説明したように、Fintech領域でおおむね土台となるサービスを実現してきました。「なめらかな社会を実現する」という意味では、大きな土台が出来上がりました。</p>
<p>大きく分けると、簡単で安全に利用できる決済システムや、メルカリでの行動に基づいたAI与信、簡単に本人確認ができるeKYCサービス、簡単に暗号資産を購入できるメルコイン、アプリで利用と管理が完結するクレジットカードサービスのメルカードなどがリリースされました。</p>
<p>しかし、これでFintechのテクノロジーが終わりではありません。今後もよりなめらかな世界を実現するためには、Fintechサービス外からでも活用できるようにプラットフォームの強化をしていく必要があります。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/10/75c36402--2023-10-19-14.51.17.png" alt="" /></p>
<p>今後Fintechを解き放ち、より世界をなめらかにしていくためには、三つのポイントが重要だと考えています。一つ目はこれまで培ってきたFintechサービスを生かして、プラットフォームとしてFintech外でも活用されること。</p>
<p>Fintechは単体でも価値のあるものですが、同時にメルカリグループ内でも重要なプラットフォームになっています。今後はよりグループシナジーの強化につながるプラットフォームとしての機能強化や、APIの整備に力を入れる予定です。</p>
<p>二つ目はデータ基盤整備です。Fintech領域では、日々、膨大な決済情報が蓄積され、よりメルカリグループでのサービスのデータ連携を強化することで、お客さまにより価値が提供できると考えています。</p>
<p>しかし、データ量は増えていくと同時に、データをうまく活用するためにはデータの基盤の改善・変革が必要となってきます。情報のAccessibilityとMaintainabilityをより改善していき、安全なサービス開発、そしてグループ間でのデータ連携を実現していきたいです。</p>
<p>最後に、LLMの活用強化も重要です。これまでメルペイでは、不正検知やAI与信でLLMの活用を本格的に行ってきましたが、LLMの実用化によって、Fintech領域でのAIの活用はさらに進化を遂げると考えています。より安全かつ便利で、金融領域に詳しくないお客さまへの洗練されたサポートを実現し、よりなめらかな社会構築に貢献できるのではないかなと思っております。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/10/fb4d15d1--2023-10-19-14.51.56.png" alt="" /></p>
<p>グループシナジーの強化について簡単にご説明します。メルペイやメルコインでは、決済、与信、信用情報、暗号資産など会社にとって重要な機能や情報を持っています。</p>
<p>これらの機能や情報は、すでにメルカリグループ内で活用は進んでいます。しかし、より今後お客さまに便利でお得にサービスをご利用いただくためにも、メルカードのポイント還元であるロイヤルティプログラムでデータ活用をより促進していくためのデータ連携やAPI強化は重要になってきます。また、メルカリ内で行っているサービスなどにあわせた決算基盤を提供するとか、そういったことがグループ会社として今後重要になってきます。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/10/b021f870--2023-10-19-14.52.29.png" alt="" /></p>
<p>メルカリではさまざまな新規事業も展開しておりまして、新規事業を実現する上でも、決済機能や与信機能、セキュリティ機能などを提供することによって、新規事業のサービス提供スピードがより向上して価値のあるサービス提供が可能になり、グループシナジーの強化につながると思っています。</p>
<p>また、具体的にはまだ進められていませんが、将来的にメルカリ外にも機能提供する基盤が整備されることによって、メルカリの金融サービスとしての価値向上や、世界の金融サービスの利便性向上にも貢献できるのではないかと考えています。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/10/a161a73a--2023-10-19-14.52.59.png" alt="" /></p>
<p>メルペイではメルカードをご利用いただいてるお客さまに、よりお得にサービスをご活用いただくためにポイント還元をするロイヤルティプログラムを提供しています。</p>
<p>これがデータ基盤にとても関わっており、現在もこのサービス自体は提供しています。提供を続けるには、日々お客さまの売買データを効率的に分析できるように基盤を整備していかなければいけません。</p>
<p>メルカリとのデータ連携を強化するためにも、中間データの整備やデータ構造自体の整備が今後重要になってきます。AIのためのトレーニングデータを作成し、包括的に管理する仕組みが重要になってきますし、特にLLMでは多くのテキスト情報とメタデータの付与が重要になるのでAIに特化したデータ構造や、データパイプラインの構築が今後重要になってきます。</p>
<p>同様にデータ利活用が進めば進むほど、データベースのインフラコストは増大します。</p>
<p>今後 AI活用が強化されている中で、世界中でもインフラコストの最適化は重要なトピックになっています。我々もサービス改善を目的として日々データの量は増加しているので、攻めと守りの姿勢を継続してインフラコスト最適化を進めていきたいです。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/10/506eb965--2023-10-19-14.53.35.png" alt="" /></p>
<p>最後にLLM活用なんですけども、こちらもセッションが別にございますので、ぜひこちらで議論を見ていただけたら幸いです。</p>
<p><a href="https://engineering.mercari.com/blog/entry/20231023-mmtf2023-day2-9/" title="【書き起こし】Merpay & MercoinにおけるLLM活用の取り組み – Yuki Ishikawa / Daisuke Torigoe / Noriaki Utsunomiya / hmj【Merpay & Mercoin Tech Fest 2023】">【書き起こし】Merpay & MercoinにおけるLLM活用の取り組み – Yuki Ishikawa / Daisuke Torigoe / Noriaki Utsunomiya / hmj【Merpay & Mercoin Tech Fest 2023】</a></p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/10/6e6d5f06--2023-10-19-14.54.20.png" alt="" /></p>
<p>今回のイベントは、「Unleash Fintech」というテーマで、これからも世の中を便利にしてなめらかな社会を作り、多様な価値をめぐる新しい経済を作り、そして人々の可能性をより解き放つために、テクノロジーの力でまだまだできることがあるという強い気持ちを持って開催させていただくことになりました。</p>
<p>我々がこれまで培ってきたテクノロジーを公開することで、少しでも世界のFintechの進化に貢献できたら、とても嬉しく思います。</p>
<p>そして、これからも技術に謙虚な反省を忘れず、時には大胆に、時には冷静に世の中をよくするために自信を持った技術を使っていきたいという気持ちがあります。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/09/a2857322--2023-09-22-9.18.13.png" alt="" /></p>
<p>以上です。ご清聴ありがとうございました。</p>
- 【書き起こし】Merpay iOSのGroundUP Appへの移行 – kenmaz【Merpay & Mercoin Tech Fest 2023】https://engineering.mercari.com/blog/entry/20231023-mmtf2023-day1-4/https://engineering.mercari.com/blog/entry/20231023-mmtf2023-day1-4/<p>Merpay & Mercoin Tech Fest 2023 は、事業との関わりから技術への興味を深め、プロダクトやサービスを支えるエンジニアリングを知ることができるお祭りで、2023年8月22日(火)からの3 […]</p>
Mon, 23 Oct 2023 10:00:54 GMT<p>Merpay & Mercoin Tech Fest 2023 は、事業との関わりから技術への興味を深め、プロダクトやサービスを支えるエンジニアリングを知ることができるお祭りで、2023年8月22日(火)からの3日間、開催しました。セッションでは、事業を支える組織・技術・課題などへの試行錯誤やアプローチを紹介していきました。<br />
この記事は、「<a href="https://events.merpay.com/techfest-2023/#day-1_session4" title="Merpay iOSのGroundUP Appへの移行">Merpay iOSのGroundUP Appへの移行</a>」の書き起こしです。</p>
<p><iframe loading="lazy" width="560" height="315" src="https://www.youtube.com/embed/BIUqsfJYFUY?si=ovlS2CjWiiWm1Mek" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe></p>
<p>@kenmaz:今回は、「Merpay iOSのGroundUP Appへの移行」というタイトルで、iOSチームの@kenmazが発表します。</p>
<p>こちらが私の自己紹介です。<br />
<img src="https://storage.googleapis.com/prd-engineering-asset/2023/09/72218a79--2023-09-21-15.56.14.png" alt="" /></p>
<p>今回のメインテーマであるMercari iOSのGroundUP App(GU App)とは何かについて説明します。<br />
<img src="https://storage.googleapis.com/prd-engineering-asset/2023/09/b814cdff--2023-09-21-15.57.52.png" alt="" /></p>
<p>GU Appは、メルカリ本体のコードをフルスクラッチで書き換え、さらにBazelによる高速・高信頼のビルド環境に置き換え、メルカリをイチから作り直すプロジェクトです。</p>
<p>メルカリの開発が始まってから10年以上経って、技術的負債がかなりたまってきている状態になったので、フルスクラッチで書き換えるのが本プロジェクトの目的です。</p>
<p>このプロジェクトではiOSではSwiftUI、AndoirdではJetpack Composeといった最新のUIフレームワーク、さらにDesign System v3.0という社内のUIライブラリを最新版に置き換えることが盛り込まれたプロジェクトです。</p>
<p>フルスクラッチで書き換えるプロジェクトなので、再構築期間中は新規機能開発を凍結し、フルスクラッチでのリライトに集中する流れで進みました。</p>
<p>参考記事<br />
<a href="https://engineering.mercari.com/blog/entry/20221213-ground-up-app/" title="メルカリアプリのコードベースを置き換える GroundUP App プロジェクトの話">メルカリアプリのコードベースを置き換える GroundUP App プロジェクトの話</a><br />
<a href="https://engineering.mercari.com/blog/entry/20221215-16cdd59909/" title="メルカリiOSアプリのBazelを使った高速・高信頼性ビルド">メルカリiOSアプリのBazelを使った高速・高信頼性ビルド</a></p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/09/fc5bfe54--2023-09-21-16.04.10.png" alt="" /></p>
<p>メルカリアプリの上にはメルペイの機能が載っています。メルペイにおいてのGU Appへの移行について説明します。</p>
<p>メルカリ本体のコードと比べるとメルペイのコードは、比較的技術的負債が少ない状況でした。開発が開始してから、当時で2〜3年ほどしか経っておらず、比較的クリーンなUIKit + Design System 1.5を使っていました。そのため、メルペイのコードについては、フルスクラッチで書き換えるよりも、最小限の変更でそのままGU Appに載せ替える方針を決めました。</p>
<p>さらにGU Appの裏で、メルペイでは新規機能開発の必要性があり、メルペイ自体の開発を止めるわけにはいきませんでした。移行期間中でもメルペイの新規開発は極力止めずにそのまま載せ替えることを目的としました。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/09/159d99cf--2023-09-21-16.05.17.png" alt="" /></p>
<p>こちらがプロジェクト全体の概要です。上の段の「Production」が実際にお客さまに届けるアプリの状況で、下の段の「Development」が社内で開発しているコード群です。</p>
<p>GU Appのプロジェクト自体は2020年4月頃に始まりました。最初はメルカリのコア部分のみをSwiftUIで書き換え、アーキテクチャや全体の構成などを固める作業からスタートしました。</p>
<p>その後2021年10月に全ての準備が完了して、本格的にリライトのプロジェクトが開始されました。この段階は、メルカリ側の機能群の開発を凍結し、全ての開発メンバーはメルカリのリライトに注力する期間でした。この期間が2022年8月まで続くことになります。</p>
<p>一方メルペイについては裏で重要なプロジェクトが動いていたので、機能群自体は変更せず、メルカリ側とメルペイ側のコードを Integrationするため、 IntegrationLayerと呼ばれる中間Layerのみを書き換えることで、メルペイの機能群のコードを変更せずに新しいアプリと古いアプリ両方で同じメルペイのコードが動作するように対応する必要がありました。</p>
<p>そこで IntegrationLayerの開発にメルペイチームが参加しました。このGroundUP Appのプロジェクトが始まった同時期に、社内では重要なプロジェクトがいくつか進みました。</p>
<p>メルペイのトップ画面をリニューアルするプロジェクトや、メルカードというメルカリのクレジットカードを開発するプロジェクト、メルコインというビットコイン関係のプロジェクトなど、いろいろなプロジェクトが走っている中で行われたのがGU Appです。</p>
<p>このメルカリと Integrationの実装が完了し、2022年8月に、Phase1としてGroundUP Appがリリースされました。この時点で、メルカリ側は全てSwiftUIにリライトされ、メルペイ側は今まで通りUIKitで動いています。</p>
<p>その後、2022年10月にメルペイ側でSwift UIで書き換えられたものがリリースされ、残りのメルペイの機能やメルカードの機能はまだUIKitで作られていました。2022年11月にメルカードの機能、翌年にメルコインの機能がリリースされました。</p>
<p>フェース3は進行中ですが、今残っている古いUIKitで作られてる部分を最終的には全部書き換え、最終的には100%Swift UIのアプリにする動きが続いています。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/09/5ae3ad2d--2023-09-21-16.06.24.png" alt="" /></p>
<p>それぞれのPhaseについて説明します。まずはPhase1のMerpay SDK GU App Integrationです。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/09/ff5f91bd--2023-09-21-16.07.18.png" alt="" /></p>
<p>Merpay自体はSDKという形で開発されていて、比較的メルカリと疎結合の状態で設計されていました。</p>
<p>これにはいろいろな背景がありますが、一つは、メルカリの機能はメルカリが担当し、メルペイの支払いや決済に関する機能はメルペイで開発を担当していて、会社自体がわかれていたのでリポジトリを分けて、設計も極力結合なものにした方がよいという考えで、このような構造になっています。</p>
<p>メルカリアプリとは別にメルペイアプリ単体のアプリを作る話もあがっていたので、かなり疎結合でポータビリティを持った設計になっていました。</p>
<p>参考記事:<a href="https://engineering.mercari.com/blog/entry/20210906-162294936e/" title="メルペイのスケーラビリティを支えるマルチモジュール開発">メルペイのスケーラビリティを支えるマルチモジュール開発</a></p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/09/9a7c1cdb--2023-09-21-16.08.40.png" alt="" /></p>
<p>GU App Integrationは4つのステップにわかれています。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/09/502a6622--2023-09-21-16.09.48.png" alt="" /></p>
<p>一つ目は、準備段階としてメルペイ関連機能のSDKへ集約することです。</p>
<p>Merpay SDKの中にはメルペイの必要な機能が入っていますが、いくつかの機能はメルカリ側に直接実装されているコードもあります。そこで、メルペイが管理すべきコードは一旦全部Merpay SDKに集約することにしました。例えば、本人確認機能や売上金の振り込み申請機能などはSDKに移しました。</p>
<p>これらはUIKit + ReactiveCocoaやObjective-Cで書かれていましたが、一旦メルペイのアーキテクチャに合わせてUIKit + MVVMに書き換えました。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/09/b75b48d3--2023-09-21-16.11.08.png" alt="" /></p>
<p>これで全てのコードがMerpay SDKに入ったので、次はビルドの環境を設定変更します。旧メルカリアプリはCocoaPods/Carthage/git-submoduleを使ってビルドを行っていましたが、新しいメルカリアプリであるGU Appでは、全てBazelを使ってビルドを行うように変更されました。</p>
<p>したがって、Merpay SDKを含む全ての社内外のコードをBazelでビルドできるように設定する必要がありました。</p>
<p>merpay-ios-sdkやDesign System、Google Maps SDK、Lottie-iOS、CryptSwiftなど社内外のコードをBazelで全てインポートし、一つのメルカリアプリとしてまとめてビルドできるようにする設定変更が行われました。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/09/4224a20d--2023-09-21-16.13.35.png" alt="" /></p>
<p>次はコーディングの段階です。メルペイにはMerpayDependencyRegistryというDIコンテナのようなものがあります。これを用いることにより、メルカリ側で実装されている機能をメルペイ側に注入できます。</p>
<p>全ての依存関係がここに集約されているので、メルカリ側の実装をメルペイに注入する作業をひたすら行います。実際に注入したコードとしては、Feature Flagやイベントログなど、図にある通りです。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/09/33841c0c--2023-09-21-16.16.08.png" alt="" /></p>
<p>これでメルペイのコードは全てMercari GU Appの上でビルドできるようになったので、最後に画面遷移の実装を行います。</p>
<p>GU Appでは、基本的に全ての画面はSwift UIで作られていますが、画面遷移周りはすべてUIKitで実装されています。Wireframe Layerというものがあり、そこでSwiftUIの画面は一旦UIHostingControllerでUIKitのViewControllerとして変換され、それをUINavigationControllerが画面遷移を制御します。</p>
<p>メルペイの画面はすべてUIViewControllerで作られているので、DependencyRegistryを経由して、UIViewControllerをWireframeにそのまま渡します。あとはWireframe内部で細かい画面遷移の実装を行います。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/09/eb292465--2023-09-21-16.17.47.png" alt="" /></p>
<p>以上で統合完了です。この段階で1年ぐらいかけて行われてきたGU Appのアプリがリリースされました。メルカリアプリは全てSwiftUIで書き換えられていますが、メルペイの機能が集約されている「支払い」タブに関してはまだUIKitのままの状態です。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/09/8c92a6a9--2023-09-21-16.18.32.png" alt="" /></p>
<p>しかし、更なる最適化の作業が残っています。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/09/c0e6e19e--2023-09-21-16.19.16.png" alt="" /></p>
<p>まずはGitリポジトリ統合です。GU Appが始まる前は、mercari-iosリポジトリでメルカリのコードが管理され、merpay-ios-sdk、mercari-jp-ios-coreは別リポジトリとして管理されていました。</p>
<p>GU Appでは、メルカリ側のコードはmercari-groundup-iosという新たなリポジトリで管理されています。メルコインのコードもすべて同じリポジトリに実装されています。しかし、メルペイの機能やメルカリのいくつか残りの機能、たとえばメルカリのToday Extensionの機能やメルペイでしか使用していないDesign Systemのコードは、依然として別のリポジトリで管理されており、Bazelによって都度インポートされる構成になっています。</p>
<p>しかし、この構成には開発効率の観点から二つの問題があります。</p>
<p>一つはリポジトリが分かれているのでメルペイの機能の開発を直接行えないという問題です。メルペイの機能を開発する際は、まずMerpay SDKのリポジトリをチェックアウトして、ソースコードを編集・プッシュして、GU Appに戻って、Bazelでリポジトリをインポートし直してビルド・動作確認、という非常に煩雑なデバッグプロセスが必要です。</p>
<p>また、GU Appのビルドインフラを活用できないという問題もありました。GU Appとメルペイのモジュールはそれぞれ別のビルドインフラ上でCIが実行されます。GU AppはBazelでビルドが行われているのでユニットテストも非常に高速にできます。一方、メルペイモジュール単体のビルドにはBazelは使用していないので、その恩恵を受けることができません。</p>
<p>Gitレポリポジトリ結合では、すべてのコードを単一のGU Appのリポジトリに移動することによって、上記の問題を解決します。</p>
<p>なおDesign System1.5はメルペイでしか使われてない古いUIライブラリなので、これは例外としてこのまま別リポジトリとして、コードフリーズした状態でインポートすることにします。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/09/8b0a4011--2023-09-21-16.20.07.png" alt="" /></p>
<p>リポジトリ統合にはいくつか方法がありますが、一番単純なのは、ファイルコピーです。一番簡単ですが、Gitの履歴が消えるという問題がありました。</p>
<p>なるべく履歴を壊さずにソースコードをGU Appのリポジトリに移動する手段として、Subtree Mergingというリポジトリの結合方法があります。これによって履歴は保持されますが、リポジトリ全体をマージしてしまうので、この2〜3年間で蓄積された不要なデータまでマージされてしまいます。リポジトリのサイズが増えることで、CIの時間に影響を与えてしまう問題があります。</p>
<p>そこで、もう一つの解決策としてgit-subtreeコマンドを使って、リポジトリを部分的に結合する方法をとりました。必要最小限のコードのみをピックアップして結合し、かつ履歴を保持することが可能になります。必要なコードをピックアップする必要があるので少々作業が煩雑になってしまいますが、これによってリポジトリの肥大化を抑えつつレポジトリ統合を行うことができます。</p>
<p>参照<br />
Subtree Merging:<a href="https://git-scm.com/book/en/v2/Git-Tools-Advanced-Merging#_subtree_merge" title="https://git-scm.com/book/en/v2/Git-Tools-Advanced-Merging#_subtree_merge">https://git-scm.com/book/en/v2/Git-Tools-Advanced-Merging#_subtree_merge</a><br />
git-subtree:<a href="https://git.kernel.org/pub/scm/git/git.git/plain/contrib/subtree/git-subtree.txt" title="https://git.kernel.org/pub/scm/git/git.git/plain/contrib/subtree/git-subtree.txt">https://git.kernel.org/pub/scm/git/git.git/plain/contrib/subtree/git-subtree.txt</a></p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/09/d8679284--2023-09-21-16.22.39.png" alt="" /></p>
<p>リポジトリに全てのファイルを移動できた後は、Bazelビルドに合わせた最適化を行います。ソースコードのレイアウトの変更や、画像アセットを最適にして無駄なデータを含めないようにすること、Bazelのビルドとビルドターゲットとするために、それぞれのモジュールをBazelのモジュールとして定義する作業などが行われました。</p>
<p>また移行期間中に古いコードに変更が入るとコンフリクトが発生するので、コードフリーズ宣言を行ってコードの変更を禁止することで、コンフリクトを防ぎました。</p>
<p>間違いで変更してしまうこともあり得るので、変更を検知するためのモニタリングの仕組みなども入れ、リポジトリ統合を進めました。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/09/7cfb1fe6--2023-09-21-16.23.27.png" alt="" /></p>
<p>このようにおよそ2ヶ月半ぐらいかけて、26モジュールを段階的に移行し、Gitレポジトリ統合が完了しました。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/09/d88d9daf--2023-09-21-16.26.51.png" alt="" /></p>
<p>もう一つの問題は、いくつかのメルカリの機能のモジュールがMerpay SDKに直接依存している点です。これによって、テストバンドルのキャッシュが肥大化してしまい、最終的にCIの実行時間やキャッシュストレージの使用量が増加してしまうことがわかりました。</p>
<p>この問題を解決するために、Merpay SDKのDIコンテナのインターフェースのみを抽出し、別のモジュールとして分離する設計の変更を行いました。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/09/21b7759a--2023-09-21-16.32.17.png" alt="" /></p>
<p>これはモジュールの相関関係を表す図です。「MK Feature modules」がメルカリの各機能を実装したモジュールです。いくつかのメルカリの機能はメルペイの機能に依存しているので、それらはMerpay SDKの中の「MerpayCoreKit」モジュールに依存する必要があります。</p>
<p>ただ、MerpayCoreKit自体はさらに、Protocol Buffersのモジュールや、Design System1.5のモジュールなどの、メルカリの機能にとっては不要なモジュールにも依存しています。それらのモジュールはサイズが大きいので、メルカリの機能モジュールをビルドしてテストしようとすると、依存関係にある全てのモジュールがビルドされ、Bazelのキャッシュとして残り続けて、最終的にCIインフラのストレージの使用量の増加を引き起こしてしまう問題がありました。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/09/540caf57--2023-09-21-16.33.02.png" alt="" /></p>
<p>そこで設計を変更して、MerpayCoreKitが提供していたコードのインターフェース部分だけをプロトコルとして切り出し、「MerpaySDKInterface」という別のモジュールとして切り出し、メルカリの機能モジュールは軽量なインターフェースモジュールのみに依存する形式に変更しました。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/09/d4190c7a--2023-09-21-16.33.55.png" alt="" /></p>
<p>これによってモジュールごとのテストバンドルのキャッシュサイズが300MBから150MBまで削減され、ストレージの使用量の問題も解決されました。</p>
<p>またMerpay SDKの最小限の機能のみをメルカリ側に整理・公開する設計にしたので、SDKの債務の明確化が行われ、SOLID原則に従ったSDKの設計にも貢献できたという副産物もありました。</p>
<p>参照:<a href="https://stackify.com/solid-design-principles/" title="Single Responsibility Principle in SOLID">Single Responsibility Principle in SOLID</a></p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/09/2e997df5--2023-09-22-9.04.14.png" alt="" /></p>
<p>以上で、Integrationのプロジェクト自体は完了しました。GU Appリリース後、Phase2として、次はメルペイの新規開発画面をShiftUI+GUアーキテクチャで開発するプロジェクトが始まりました。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/09/e6b851ac--2023-09-22-9.05.45.png" alt="" /></p>
<p>ここで、メルカリアプリ自体のアーキテクチャの変遷について振り返っていきたいと思います。</p>
<p>旧メルカリアプリでは、10年の間に様々な内部的なアーキテクチャの変遷がありました。最初は純粋なMVC、そこからMVVM+ReactiveCocoa、さらにMicro View Controller + Stateアーキテクチャに変遷しました。しかも全てが変遷していたわけではなく、部分的には古いMVCが残っている状況でした。</p>
<p>一方メルペイは独自のシンプルなMVVM Without 3rd party libsというシンプルなアーキテクチャを採用していました。このように、GU Appの前はいろいろなアーキテクチャが一つのアプリの中に共存している状態でした。</p>
<p>参考資料<br />
<a href="https://speakerdeck.com/kagemiku/mercari-iosniokeru-kirayabaarchitecturetoautomation?slide=22" title="Mercari iOSにおける きらやばArchitectureとAutomation">Mercari iOSにおける きらやばArchitectureとAutomation</a><br />
<a href="https://speakerdeck.com/buildersbox/after-re-architecture-of-mercari-ios-client" title="Mercari iOSクライアント Re-Architectureのその後 / After Re-Architecture of Mercari iOS client">Mercari iOSクライアント Re-Architectureのその後 / After Re-Architecture of Mercari iOS client</a><br />
<a href="https://engineering.mercari.com/en/blog/entry/2019-06-12-120000/" title="Introducing ViewModel Inputs/Outputs: a modern approach to MVVM architecture">Introducing ViewModel Inputs/Outputs: a modern approach to MVVM architecture</a></p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/09/1b008503--2023-09-22-9.09.12.png" alt="" /></p>
<p>GU Appでは、それが一新され共通アーキテクチャが策定されました。SwiftUIをベースとし、Reduxや TCA などからインスパイアされた単方向データフローの独自のアーキテクチャです。</p>
<p>メルカリおよびメルコインの機能は全てこのGUアーキテクチャに基づいて開発されています。メルペイもこの共通アーキテクチャに統合する方針を決定しました。</p>
<p>ちょうどメルペイのトップ画面のリニューアルする新規開発プロジェクトが別で計画されていたので、そこでGUアーキテクチャを試験的に先行導入することにしました。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/09/9ba7767d--2023-09-22-9.10.02.png" alt="" /></p>
<p>Phase2の開発が完了しました。この段階でメルペイのトップ画面に関しては100%SwiftUIが達成できました。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/09/4848dd29--2023-09-22-9.10.50.png" alt="" /></p>
<p>メルペイのトップ画面以外の既存画面に関しては、未だにUIKitのままです。Phase3では、既存画面をすべてSwiftUIに書き換えます。これは現在進行中です。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/09/6f6220d3--2023-09-22-9.11.30.png" alt="" /></p>
<p>ここで、なぜ最初に技術的負債が比較的少ないメルペイのコードを書き直す決断をしたのか、そのモチベーションについてお話します。</p>
<p>技術的負債負債が比較的少ないといえども、初期のコードは2018年に作られたものです。当然UIKitベースのコードなので、GUで刷新された基盤機能の恩恵は受けられせん。</p>
<p>SwiftUI自体ははもちろん導入できなくはないのですが、メルカリの機能で使用されているDesign System3.0を使用するには、GUアーキテクチャへの移行も必要になります。</p>
<p>アクセシビリティについてもGUアーキテクチャはかなり手厚くサポートされていて、新規イベントログ基盤もGUアーキテクチャにより最適化されたものになっていたなどの事情がありました。これらの事情によって、メルペイ側の既存コードも段階的にGUアーキテクチャに移植するのが良いという決断になりました。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/09/a0246dbe--2023-09-22-9.12.17.png" alt="" /></p>
<p>とはいえ、メルペイの既存機能がかなりの数があるので、まず現状の仕様整理から始めました。まず、移植作業の計画を立てます。大量のメルペイの既存画面の仕様を整理・リストアップし、優先度を決めて移植作業を少しずつ始める計画を立てようと考えました。</p>
<p>大量の既存コード・仕様書はありましたが、ここで既存コードと仕様書の対応関係が不明瞭であったり、対応する仕様書が見つからなかったりといった問題が発生しました。仕様書によっては同じ画面に対して別の呼び方がされていることもあり、特に非日本語話者にとってはその理解が非常に困難な状況でした。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/09/9622cc5a--2023-09-22-9.13.03.png" alt="" /></p>
<p>そこで既存の仕様を整理する前に、一旦全てのメルペイの画面を一意に特定する「Merpay Screen ID」を導入することにしました。</p>
<p>例えばここにあるように、”MP-BNK-001” はメルペイの銀行接続の1番目の画面を示します。このような採番作業を全ての画面に対して実施しました。</p>
<p>Screen IDをソースコード、仕様書、Figma上で横断的に記載することで、それぞれの関係を明確化しました。これによって、認識の齟齬を解消できる上、非日本語話者でも理解しやすくなりました。これは社内の開発ガイドラインにも組み込まれているので、この辺の整理が今後も進んでいくと思います。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/09/2e33153d--2023-09-22-9.13.55.png" alt="" /></p>
<p>これを導入したことによって、スクリーンに関して仕様書を探したいときにScreen IDで検索すればそれに関連する仕様書が全て出てきます。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/09/89c98063--2023-09-22-9.14.39.png" alt="" /></p>
<p>またFigmaでUIレイアウトを確認したいときも、Figmaの検索ボックスにScreen IDを入れれば、正式なレイアウトを確認できます。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/09/d8ff9f8c--2023-09-22-9.15.23.png" alt="" /></p>
<p>ソースコード中にこのようにScreen IDを埋め込んでおけば、ソースコードと仕様書の関係、その画面に関する実装についても発見できます。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/09/6aba7732--2023-09-22-9.16.06.png" alt="" /></p>
<p>このように、事前の準備をした上で計画を立て、現在絶賛移植作業中です。全体の77%は古いコードのままなので、今後数年かけて移植する予定です。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/09/7dd4e2a9--2023-09-22-9.16.44.png" alt="" /></p>
<p>こちらが現在移植中の銀行接続画面の例です。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/09/645a2009--2023-09-22-9.17.29.png" alt="" /></p>
<p>GU App Integrationの開発プロジェクトを進めることで、メルペイ自体の機能開発を止めることなく、GU AppのIntegrationが完了しました。メルペイのコードを変更することなくそのまま新しいGU Appのコードベースに移植することに成功しました。</p>
<p>またBazelのメリットを最大限に生かす構成に変更しました。さらにメルペイの既存画面をShiftUIで移行するにあたり、いろいろと基盤を整備しました。</p>
<p><img src="https://storage.googleapis.com/prd-engineering-asset/2023/09/a2857322--2023-09-22-9.18.13.png" alt="" /></p>
<p>以上です。ご清聴ありがとうございました。</p>