【インターンレポート】LINEポイントの大規模な処理改善を行いました

はじめに

はじめまして、Joeといいます。2月から2ヶ月間、LINEの開発3センターというところでインターンしておりました。 この記事ではインターン中に携わったプロジェクトを少し紹介しつつ、LINEの会社の雰囲気をお伝えできたらなと思います。

LINEについて

LINEのオフィスは新宿にあって、ミライナタワーの15階から23階を占有しています。皆さんのLINEのイメージはメッセージアプリだと思いますが、LINEは メッセージ機能の他にLINE Pay, LINEマンガ, LINE LIVEなどさまざまなアプリケーションを提供しています。 LINEのオフィスはミライナタワーの他にもいくつかあり、さまざまな部門で数千人のエンジニアが働いているようです。
今回、インターンシップでぼくが配属されたチームはLINEポイントのチームでした。 LINEポイントでは、ユーザは広告動画の視聴やアプリのインストールなどのミッションをこなすことでポイントを貯めることができ、 貯めたポイントはスタンプ購入に使ったり、LINE Payにチャージすることで1ポイント1円として使用できたりします。 LINE Payユーザなら買い物をするたびにポイントが還元されるのでよくお目にかかるのではないでしょうか。

LINEポイントの問題点とインターンの課題

LINEポイントのサーバは日々の膨大な件数のポイント処理を一定期間に渡ってすべて記録しています。これは例えば同じユーザが同じミッションを複数回こなしてポイントを何度も受け取ることを防ぐために行っています。
一度のポイント処理のログはコンバージョンログと呼ばれているのですが、現在LINEポイントのサーバにあるコンバージョンログは50億件を超えており、さらに毎日増え続けています。 これらのログはもともとMySQLに格納されていたのですが、あまりにデータが増えたせいで検索・書き込みにやや時間を要する状態になっていました。
そこで、コンバージョンログを保存するデータベースをMySQLから分散型データベースであるHBaseに移植するというプロジェクトが数年前から構想されていたのですが、あまりに大規模であったためなかなか時間をとって処理されず、まさかのインターンの課題として降ってきました(え?)

ポイント処理の複雑さ

ユーザがLINEアプリでミッションをクリアした後、実際にポイントが付与されるまでの数秒間に行われる処理はみなさんが想像されるよりずっと複雑になっているはずです(LINEポイントの諸々の実装の中でもトップクラスに複雑らしいです)。
まず1つ目に動画視聴やアプリインストールといったミッションは、ミッションごとに詳細なクリア条件が設定されており、クリア条件の違いによって実行される処理は異なります。たとえば地域や期間限定のものがあったり、さらには手動承認が必要な案件まであったりします。
2つ目として、一連の処理が複数のサーバやデータベースを経由するという特徴があります。 具体的には、あるコンバージョンのリクエストが不正でないと判断されると、その処理は同期的に行われるのではなくジョブとしてキューに詰められ、別プロセスがジョブを処理する仕組みになっています。これは、瞬間的に大量のリクエストが届いたときにシステム全体をスローダウンさせないための工夫で、利用者数が多いアプリケーションの設計ではよく見られると思います。
処理を非同期的に行う問題点として、重複リクエストを排除したり、処理を巻き戻すのが難しいという問題があります。次の図を見てみましょう。

上の図は、あるユーザがコンバージョンリクエストをサーバに送信し、ある程度時間が経ったタイミングで同じリクエストを再送信している様子を表しています。
ほとんどの処理に必要な情報はMySQLに入っているのですが、キャンペーンの定員の更新などリアルタイム性が重要なデータはRedisで処理が行われます。キャンペーンの定員に関しては、ジョブが処理されてから更新してはまずいです。なぜかというと、短時間に大量のリクエストが来た場合キューが詰まってしまい、その間に本来定員をオーバーしているにもかかわずユーザを受け付けてしまうからです。
たとえば図のような愚直な処理を書いてしまうと、2回めのリクエストの際に、同じユーザが定員の枠を2つ消費してしまう、といった問題が生じてしまいます(ゆっくり追ってみてください)。
既存のコードはこのような問題に対処するために複雑なフラグがたくさん設定されており、コード長が膨れ上がっておりました(数千行でした)。
最後にもうひとつ処理が複雑化する原因として、同じ端末でアカウントを作り直して不正に何度もポイントを取得しようとしたり、プログラムにより自動化してポイントを取得しようとするユーザを食い止めるために、通常ユーザにはなかなか気づかない点でたくさんの処理が実装されています。

インターンの課題(詳細)

ということがあり、インターンの課題としては以下の業務をすることになりました。

  • 既存の処理を把握し、コンバージョンログをHBaseに移植する
  • 移植後も重複コンバージョンを阻止できて、処理を巻き戻せるような新しい処理のフローを考案し、実装する
  • 既存の50億件のデータをマイグレーションする

苦労した点

まずコードを理解するのにかなりの時間がかかりました。すべてのコードに目を通すのは数万行レベルになるのでそれを諦め、重要な処理の部分に注目しましたが、逆にこうしてしまったせいでシステム全体の複雑さの見積もりを間違えてしまいました。
HBaseはJavaクライアントが使いやすいため、当初は、Perlで書かれたLINE Point APIのコンバージョンに関わるroutesをまるごとJavaに移植する予定でしたが、いざポイント処理のフローの考案が終了し、実装に移ったときに、明らかに2ヶ月で実装できる量ではないことを思い知らされました。
そこで、HBaseの検索・書き込みをAPIによって行うように設計を変更し、JavaでAPIを書きつつ、従来のコードを変更する形でPerl側からAPIを呼ぶように変えました。
データのマイグレーションでは、対象となるデータ数が50億件あるので、仮に100000個の書き込みを1秒でできたとしても、単純計算で14時間かかることになり、いかにスムーズにマイグレーションを行うかということについていろいろ調べました。

感想

これまで大量のユーザをかかえるアプリケーションの開発を経験したことがなかったので非常に貴重な経験になりました。
また、今回はプロジェクト全体をほぼまるごと任されたおかげで、仕事がコーディング以外にも多岐に渡っていて、特にLINEのインフラについてざっくりと仕組みを理解できたことや、問題が発生しないポイント処理の流れを考案するといった“考える仕事”に携われて、充実したインターンとなりました。
チームの方々、メンターの方、ありがとうございました。

Related Post