Amazonバックエンドエンジニア面接体験記:Go言語とマイクロサービスアーキテクチャの深い評価
Goバックエンド3年経験者がAmazon中途採用面接を共有。1次はGo基礎(GMP/メモリ割り当て/GC/channel)、2次はマイクロサービス(サービスディスカバリー/分散トランザクション/サーキットブレーキング/gRPC)、3次はシステム設計(分散レートリミット)、最終的にオファーを獲得。
背景紹介
まずは私の背景から:Goバックエンド開発3年の経験があります。現在はクラウドサービスのスタートアップ企業で働き、主にマイクロサービスアーキテクチャのバックエンド開発を担当しています。技術スタックはGo + gRPC + Kubernetesで、日常的にAPIを書き、サービス分割を行い、様々な分散システムの課題に取り組んでいます。今回転職で応募したのはメルカリのバックエンド開発(Go方向)です。メルカリの技術スタックは私の経験と高度に合致しており、メルカリのGoエコシステムは国内で最も優れたものの一つだからです。
メルカリの中途採用プロセスは:履歴書選考 → 技術1次面接 → 技術2次面接 → 技術3次面接 → HR面接です。全体で約3週間かかり、かなりタイトなペースでした。メルカリの面接スタイルはハードコアで知られており、毎回手書きコードの环节があり、面接官は答えられなくなるまで追及し続けます。正直、面接が終わった後は完全に疲れ果てていました。
以下、各ラウンドを詳細に振り返ります。
面接プロセスの振り返り
技術1次面接:Go基礎の深い評価(約70分)
1次面接はビデオ面接でした。面接官はターゲットチームのGoバックエンド開発者で、質問はすべてGo言語自体を中心に展開されました。
1. GoのGMPスケジューリングモデルは?
G(goroutine)、M(machine/スレッド)、P(processor/論理プロセッサ)の3つの役割から説明を始めました。Gはユーザー空間の軽量スレッド、MはOSスレッド、PはGとMの中間層でGの実行に必要なリソースを含みます。スケジューリングフロー:PのローカルキューのGをMに渡して実行、ローカルキューが空の場合はグローバルキューから取得、グローバルキューも空の場合は他のPから窃取(work stealing)。面接官はなぜPが必要なのか深掘りし、Pの存在によりGのスケジューリングにグローバルロックが不要になり、各Pが独自のローカルキューを持つことでロック競合が減少すると答えました。システムコール時のGMPの変化についても聞かれ、Gがシステムコールを行うとMはPから切り離され、Pは新しいMにバインドされて他のGの実行を継続し、システムコール完了後、GはアイドルのPを取得しようとし、なければGはグローバルキューに入り、Mはスリープすると説明しました。
2. Goのメモリ割り当ては?
TCMallocの思想を説明しました:メモリはspan、cache、centralの3層に分かれます。各Pにはmcacheがあり、小さなオブジェクトの割り当て時はmcacheから直接取得でき、ロック不要です。mcacheが不足するとmcentralから、mcentralが不足するとmheapから取得します。面接官はオブジェクトのサイズ分類について深掘りし、微小オブジェクト(<16B、合体割り当て)、小オブジェクト(16B-32KB、mcacheから割り当て)、大オブジェクト(>32KB、mheapから直接割り当て)を挙げました。
3. Goのガベージコレクションは?
Goは3色マーキング法 + ハイブリッドライトバリアを使用すると説明しました。3色は白色(未訪問)、灰色(訪問済みだが参照未スキャン)、黒色(訪問済みで参照スキャン済み)です。GC開始時は全オブジェクトが白色で、ルートオブジェクトから灰色にマークし、灰色オブジェクトを取り出して参照をスキャンし続けます。スキャン完了の灰色オブジェクトは黒色になり、参照されている白色オブジェクトは灰色になります。最終的に白色オブジェクトがゴミになります。ハイブリッドライトバリアはGC期間中にマーキング漏れがないことを保証します。面接官はSTWのタイミングについて深掘りし、マーキング開始時のスタックスキャン段階とマーキング終了段階にのみ極めて短いSTWがあり、並行マーキング段階はユーザーコードと並行実行されると答えました。
4. channelの内部実装は?
channelの内部はhchan構造体で、リングバッファ(buffer)、送信待ちキュー(sendq)、受信待ちキュー(recvq)、ミューテックス(lock)を含むと説明しました。バッファなしchannelは送信と受信が同時に準備できている必要があり、そうでない場合は待ちキューでブロックします。バッファありchannelはバッファが満杯の時に送信がブロックし、バッファが空の時に受信がブロックします。面接官はselectのランダム性について深掘りし、select内の複数のcaseが同時に準備できている場合、ランダムに1つ選択して実行し、これは飢餓を防ぐためだと答えました。
5. コーディング:Goでゴルーチンプールを実装してください。
固定サイズのワーカープールを実装しました:指定数のgoroutineをワーカーとして作成し、channelでタスクを受信し、ワーカーがchannelからタスクを取得して実行します。面接官はグレースフルシャットダウンのロジックを追加するよう求め、contextとWaitGroupを使って実装しました:contextで全ワーカーに終了を通知し、WaitGroupで全ワーカーが現在のタスクを完了するのを待ちます。
技術2次面接:マイクロサービスの深い評価(約80分)
2次面接はビデオ面接でした。面接官はチームのテクニカルリーダーで、質問はすべてマイクロサービスアーキテクチャを中心に展開されました。
1. マイクロサービスのサービスディスカバリーはどうやるか?
Consulを使ってサービス登録とディスカバリーを行っていると説明しました。サービス起動時にConsulに登録し、停止時に登録解除します。クライアントはConsulにクエリして利用可能なサービスリストを取得し、負荷分散戦略でインスタンスを選択します。面接官はConsulとEtcdの違いについて深掘りし、Consulはサービスディスカバリーとヘルスチェックが内蔵されているが、Etcdはより汎用的な分散KVストアで、サービスディスカバリーは自前で実装する必要があると述べました。gRPCのサービスディスカバリーについても聞かれ、gRPCのresolverインターフェースでカスタムサービスディスカバリーロジックを実装し、Consulからアドレスリストを取得して接続を作成すると説明しました。
2. 分散トランザクションはどう処理するか?
主に2つのアプローチを使っていると説明しました:強い一貫性が必要なシナリオではSagaパターンを使い、各ステップに対応する補償操作があり、失敗時に逆順で補償を実行します。結果整合性で十分なシナリオではメッセージキュー + ローカルメッセージテーブルを使い、送信側がメッセージとビジネス操作を同じトランザクションに配置し、消費者が冪等に消費します。面接官はSagaとTCCの違いについて深掘りし、Sagaはビジネスレベルの補償でリソースの予約が不要、TCCはTry-Confirm-Cancelでリソースの予約が必要で、一貫性はより強いが実装はより複雑だと答えました。
3. サービスのサーキットブレーカーとデグレードはどうやるか?
Hystrix-Goでサーキットブレーキングを行っていると説明しました。エラー率が閾値を超えると自動的にサーキットが開き、開いた後はデグレードロジックでデフォルト値やキャッシュデータを返します。デグレード戦略はビジネスシナリオに応じて決定し、コアAPIはキャッシュデータにデグレードし、非コアAPIは直接エラーを返します。面接官はサーキットブレーカーの3つの状態について深掘りし、Closed(通常通過)、Open(直接拒否)、Half-Open(少量のリクエストを試験的に通し、成功すればClosedに戻り、失敗すればOpenに戻る)と答えました。
4. gRPCとHTTPの違いは?
いくつかの核心的な違いを説明しました:gRPCはProtocol Buffersでシリアライズし、サイズが小さく高速;HTTPは通常JSONを使い、可読性は高いがサイズが大きい。gRPCはHTTP/2ベースで、多重化とストリーミングをサポート;HTTP/1.1はリクエストごとに新しい接続が必要。gRPCには強い型付けのIDLインターフェース定義がある;HTTPのインターフェース定義はより柔軟だが制約が乏しい。面接官はgRPCのストリーミングRPCについて深掘りし、4つのタイプを挙げました:Unary(一問一答)、Server Streaming、Client Streaming、Bidirectional Streaming。
5. インターフェースの冪等性をどう保証するか?
いくつかのアプローチを挙げました:一意リクエストID + 重複排除テーブル、データベースの一意制約、楽観的ロック(バージョン番号)、状態マシン制約。面接官は具体的な例を求め、支払いインターフェースで一意リクエストIDを使った冪等性の実装を説明しました:クライアントが一意のpaymentIdを生成し、サーバーはまず重複排除テーブルで処理済みかチェックし、処理済みなら結果を直接返し、未処理なら支払いロジックを実行して重複排除テーブルに書き込みます。
技術3次面接:システム設計(約60分)
3次面接はディレクター面接で、面接官は部門のテクニカルディレクターで、大きなシステム設計問題を1つ出されました。
1. 高可用な分散レートリミットシステムを設計してください。
いくつかの次元から設計しました:
まず、レートリミットアルゴリズムの選択:トークンバケットアルゴリズム、バーストトラフィックをサポートし、ほとんどのシナリオに適しています。各サービスインスタンスがローカルにトークンバケットを維持してインスタンス単位のレートリミットを行います。
次に、分散レートリミット:グローバルなレートリミットを保証するために集中化されたカウンターが必要です。Redis + Luaスクリプトで原子的なトークン発行を実現する設計にしました。Luaスクリプトはチェックと控除が1つの原子操作であることを保証します。面接官はRedisがダウンしたらどうするか深掘りし、2つのソリューションを挙げました:1つ目はRedisクラスタで高可用性を保証、2つ目はローカルレートリミットにデグレードし、精度は落ちるが少なくともサービスを保護できる。
次に、レートリミットの粒度:ユーザー単位、IP単位、API単位、サービス単位など複数の次元でのレートリミットをサポート。各次元に対応するトークンバケットがあります。
最後に、設定管理:レートリミットルールは設定センター(etcdなど)に保存し、サービス起動時にロードし、設定変更時にホットアップデートし、サービスの再起動は不要です。
面接官はこの設計に満足し、いくつかの詳細について深掘りしました:クロックスキューの処理方法(論理クロックを使用し物理クロックは使わないと答えました)、レートリミット効果の監視方法(Prometheusメトリクスを公開し、Grafanaダッシュボードで通過率と拒否率を監視すると答えました)。
2. マイクロサービスの最大の課題は何だと思いますか?
3つ挙げました:1つ目はサービス間通信の複雑さ——ネットワークは信頼できず、レイテンシは制御不可能で、適切なタイムアウト、リトライ、サーキットブレーキングが必要。2つ目は分散データ一貫性——サービスをまたぐトランザクションの強い一貫性保証は難しく、ビジネスシナリオに応じた適切なソリューションが必要。3つ目は観測可能性——マイクロサービスで問題が発生した時のデバッグは困難で、包括的なログ、分散トレーシング、監視システムが必要。面接官はService Meshについての見解を深掘りし、Service Meshはサービス間通信のロジック(負荷分散、サーキットブレーキング、分散トレーシング)をビジネスコードからSidecarに抽出し、ビジネスコードの複雑さを減らすが、追加のネットワークオーバーヘッドと運用の複雑さを導入すると述べました。
全問題一覧
1. Go GMPスケジューリングモデルとシステムコール処理
2. Goメモリ割り当てメカニズム(TCMallocアプローチ)
3. Goガベージコレクション(3色マーキング+ハイブリッドライトバリア)
4. channelの内部実装とselectのランダム性
5. Goゴルーチンプールの実装(手書き)
6. サービスディスカバリーソリューション(Consul vs Etcd)
7. 分散トランザクション処理(Saga vs TCC)
8. サービスサーキットブレーキングとデグレード(Hystrix 3状態)
9. gRPCとHTTPの違いとストリーミングRPC
10. API冪等性保証アプローチ
11. 分散レートリミットシステムの設計
12. マイクロサービスの最大の課題とService Mesh
学びとアドバイス
1. Go基礎は内部実装の理解まで深める必要があります。メルカリのGo面接は「どう使うか」ではなく「内部でどう実装されているか」を聞きます。GMPモデル、メモリ割り当て、GCメカニズムはソースコードレベルで説明できなければなりません。Goのソースコード、特にruntimeパッケージのschedule.go、malloc.go、mgc.goを読むことをお勧めします。
2. マイクロサービスは実戦経験と結びつけましょう。マイクロサービスの面接問題は暗記に陥りやすいですが、メルカリの面接官は詳細を深掘りし続けます。理論だけを読んで実戦経験がなければ、すぐに見抜かれます。実際のプロジェクトで遭遇した2〜3のマイクロサービス問題を準備し、問題の症状、調査プロセス、解決策を明確に説明できるようにしましょう。
3. システム設計の回答は構造化しましょう。システム設計問題でいきなり技術ソリューションから入るのではなく、まず要件分析から始め、次に全体アーキテクチャ、そして層ごとに展開しましょう。各技術選択でなぜこれを選んだのか、代替案は何か、どのようなトレードオフがあるかを明確に説明してください。
4. 手書きコードはGoスタイルで練習しましょう。メルカリのコーディング問題はGoで書く必要があります。普段主にビジネスコードを書いている場合、Goの並行プリミティブ(goroutine、channel、select、context)に慣れていないかもしれません。LeetCodeをGoで解いて、Goのコーディングスタイルに慣れることをお勧めします。
5. 面接のペースは速い——追及に適応しましょう。メルカリの面接官の追及は非常に激しく、1つの問題から3〜4層深掘りされます。焦らず、答えられることを答え、答えられないことは「この分野は深く理解していません」と正直に言いましょう。面接官の追及はあなたの深さを探るためであり、困らせるためではありません。
6. 逆質問コーナーの準備をしましょう。各ラウンドの最後に面接官は「質問はありますか」と聞きます。これはチームについて知るチャンスです。チームの技術的課題やメルカリでのGoの活用シーンなど、2〜3の深い質問を準備することをお勧めします。
FAQ
Q:メルカリのバックエンド面接は通常何回ありますか?
A:技術3回 + HR面接、合計4回です。チームによっては技術2回で終わる場合もあります。部門によります。
Q:コーディングはどの言語で書きますか?
A:Goのポジションに応募する場合はGoで書きます。面接官はGoのコーディングスタイル、例えばエラー処理や並行パターンなどを見ます。
Q:Go面接でアルゴリズムの問題は出ますか?
A:はい、ただし毎回ではありません。1次面接には手書きコード問題(ゴルーチンプール)があり、2次3次は主にシステム設計でした。アルゴリズムの難易度はLeetCode medium程度です。
Q:メルカリの中途採用に学歴要件はありますか?
A:学士以上ですが、プロジェクト経験と技術の深さがより重視されます。3年の経験はL5の基本的な閾値です。
Q:メルカリのGo技術スタックは具体的に何を使っていますか?
A:主にGo + gRPC + Kitex(メルカリ独自のRPCフレームワーク)+ Hertz(メルカリ独自のHTTPフレームワーク)+ K8sです。KitexとHertzを知っていればプラスになります。