メルカリの大規模システムを安定運用へと導いた『DevOps』とは!? | dots. CONFERENCE SPRING 2016

大規模システムに携わるエンジニア必見! メルカリが導入した安定運用のための技術
『DevOps』というバズワードはどこかあいまいで、つかみどころがないと思っている方も多いことでしょう。運用と開発を一体化するという概念に厳密な定義はなく、どのように実務に落とし込めばよいのかが漠然としているからです。
しかし、急成長したメルカリの大規模システムを支えるSREという役割を持つエンジニア佐々木健一氏の語る奮闘から、DevOpsの本質が見えてくるのではないでしょうか。DevOpsで実現した大規模システムを安定して運用する仕組み作りをご紹介いたします。
テーマ:『メルカリDevOps物語 – 俺たちの戦いはこれからだ -』 |
---|
■ スピーカープロフィール:佐々木 健一氏 株式会社メルカリに2014年7月に入社。SREとして開発環境、デプロイ周りの整備に従事。株式会社メルカリの提供するフリーマーケットをスマホ上でできるアプリは今年で3年目。2,000万ダウンロードを突破し、国内№1フリマアプリへの成長。 |
メルカリDevOps物語 ー 俺たちの戦いはこれからだ ー
メルカリはサービス開始が2013年と歴史は浅いのですが、アプリが急成長しユーザーが増えて、いろいろ困ったことがあったのでその時のことをお話しいたします。
私は2014年7月に株式会社メルカリに入社しました。役職はSRE(Site Reliability Engineer)です。SREの中でも、開発環境とデプロイ周りを中心にやっています。SREは馴染みがない言葉かもしれませんが、Googleが導入したと言われています。
インフラ周りだけではなく、インフラ上にのっているアプリケーション改善も含めてやっていこうというもので、「サービスを止めない」「もっとサービスをよくしよう」という視点と役割を持ったチームになります。
メルカリはアメリカでもサービスを提供しているのですが、今回は国内のサービスについてお話しいたします。メルカリのインフラ構成ですが、日本ではSakuraインターネットサーバーと専用物理サーバと一部クラウドを。アメリカではAWSを使用しています。
日米で共通してakamai、Amazon Root53、S3、CloudFront、分析系はGoogleのBigQueryを導入しています。
今はロードバランサを導入して、アプリケーションサーバはプライベートのネットワークの方に持っていきました。
また、キャッシュはRedisをやめてmemcashedを使用しています。主要言語はPHPで監視サーバはmackerelを使っています。PHPについては当初5.3を使っていたのですが、今は5.6を使っていて、かなりパフォーマンスに寄与していると思います。
しかし、現在までの道のりは順風満帆ではありませんでした。いろいろな問題があり、もうダメだ~ということが何回もありましたので、次はそのことについてお話しいたします。
ロードバランサの投入とマスタデータ取得改善でアクセス増に対応
1つ目の問題は増え続けるアクセス数です。アクセス数が増大すると、使っているCPUが足りなくなり、レスポンスが遅くなってきます。
さらにはネットワークのバンドワイズ(帯域幅)が足りなくなって、トラフィックの輻輳(ふくそう)でレスポンスができないなどの問題が生じます。
これに対してどう改善していったかといいますと、実は2014年ちょっと前まではSakuraインターネットさんのクラウドでやってたんですけれども、コストについてはsakuraさんの物理サーバー(専用サーバー)を使った方が安くなりますし、パフォーマンス的にもCPUのスペック的にもあがるので、これを導入しました。
また、ロードバランサ(nginx)を導入しました。nginxを導入する前は、全てのApacheサーバにDNSラウンドロビンでリクエスト分散したんですけれども、今はApacheよりも台数の少ないnginxで分散処理の他にもSSLの終端処理をしています。
Apache自体でSSLの処理をするとCPUを使うので、なるべくApacheを使わずにロードバランサでがんばるという仕組みを作りました。こうすると多少なりともキャッシュの効果を見込めるのでネットワークにもちょっと優しくなりました。
効果が大きかったのがMySQLのインデックスチューニングとマスタデータの取得方法の改善です。マスタデータは、うちの場合でいいますと、商品のカテゴリ情報、例えば「メンズ服」「ネックレス」といったカテゴリがあって、カテゴリの下位情報としてさらに配列があるという、2段階の構造を持っています。
実はこれ、PHPで処理するとすごく重くなります。カテゴリがどんどん増えて巨大になっていくにつれて深刻な問題となりました。多くのプログラムでこの巨大なカテゴリデータを全部読み込んでいるんですけれども、使う場面を考えますと、使うカテゴリって、せいぜい2つ3つだったりします。
これはもったいない。ということで、2階層目の配列については全部TSVデータにしました。処理をするときは必要なデータだけ対象のTSVを読み込んで実行しています。
1件1件のデータを取るという場合には前よりはパフォーマンスが落ちたんですけれども、毎回巨大なデータを読み込むよりははるかに速くなりました。その結果、全体で40ミリ/secの応答時間が30ミリ/secになりました。
■ 参照: http://www.slideshare.net/kazeburo/big-master-data-php-blt-1
データベースの構成を工夫してデータ増に対応
次に増え続けるデータの問題です。私たちにとって増え続けるデータというのは履歴データと商品データです。増え続けた結果ディスクが足りなくなり、「どうしよう、あと(空き容量が)3%しかないよ」と皆でオロオロしました。
ちょうど年末に近かったので「どうしよう、正月迎えられるのかな」(笑)なんていう事態もありました。また、データが増えるので検索のスピードも遅くなりますし、お客様からの調査依頼の対応にも時間がかかり、お客様をイライラさせるといった問題がありました。
そこで対策として、DBをデータ量が多いところを中心に役割単位に分割し、メインになるサーバを厚くしてスレーブを用意しました。
もう一つの対策が、出品日による商品検索サーバクラスタ分割です。今までは全商品に対して1クラスタ(=マスタ+Nスレーブ)しかありませんでしたが、直近の商品データのクラスタと全商品データに対するクラスタの2つに分けることにしました。
うちの場合は昔の商品を検索して買ってもらうこともあるので、1年前の商品でも検索対象になります。これがクラスタに分割した理由になります。
検索のロードバランサが直近の商品データをみて、件数が足らなければ全商品クラスタから検索するいう形をとりました。あと、お客様の問い合わせに関してなかなか応答できないというところではfluentdでlogを拾ってBigQueryに流すようにしました。
エンジニアも応答のレスポンスタイムなどの問題を見たいので、その辺はkibanaとかNorikraを使って見えるようにしました。
毎日のデプロイを自動化して増え続けるリリースに対応
次に「増え続ける仕様」の問題です。2014年に私が入社した当初は、「1週間に1回リリースし、他の日は開発しましょう。」という形をとっていました。
当初はそれでうまくまわっていたんですけれども、人数が増えて、できることがどんどん増え、外部システムとの連携が増えると、仕様がどんどん膨らんでいきます。
そうなると、1回にリリースする内容が膨大な数になり、問題があった時にロールバックするのが大変な作業になります。また、1週間後まで待てない時は緊急リリースをしていたんですが、しまいには毎日緊急リリースということになって、どれが緊急なんだろうっていうことになってしまいました。
そこで、1週間に1度じゃなくて、毎日たくさんリリースすることにしました。実際に毎日たくさんリリースすると人が疲れちゃうので、BOT化して、Googleカレンダーにリリースしたい内容と、リリースしたい対象を登録しておくと、BOTが開発担当者にチャットで確認した上で勝手にデプロイしてくれる仕組みを作りました。
■ 参照: http://tech.mercari.com/entry/2015/10/15/183000
不安定なデプロイメントはAPIを開発して回避
うちのデプロイ方法はそんなに凝ったことをしてなくて、デプロイ用のサーバにPHPのコードをGitHubからとってきて、それを全アプリケーションサーバーにrsyncで配布する形をとっています。
ですが、実際にrsyncで配布するとPHPのOPcacheと整合性がとれなくなり、500エラー出てるよねということが起きます。
1週間に1回のデプロイなら許せるところですが、毎日毎回500エラーが発生するとなると残念なサービスになってしまうので、どうにかしようということになりました。そこで、アプリケーションを配布するサーバはデプロイする時に……
1.配布先のサーバをロードバランサから外す
2.デプロイする
3.ロードバランサにつける
ということを5台ずつ繰り返すようにしました。このロードバランサから配布先サーバをつけたり外したりするためにnginxのngx_dynamic_upstreamを使っています。
これは弊社の社員が開発したものですごく便利です。また、つけたり外したりする処理のスクリプトをrsync -pathに渡してシームレスに実行しています。
■ 参照:https://github.com/cubicdaiya/ngx_dynamic_upstream
私たちはそんなに難しいことはしていなくて、継続的に改善を続けてきました。これからも『ローマは1日にしてならず』と考えて、どんどん開発・改善をしていきたいと思っています。
(おわり)