Shopee - アプリケーションに適したデータベースを選択する

Shopeeは、東南アジアや台湾における有力なeコマースプラットフォームです。Shopeeは、地域に合わせてカスタマイズされており、決済と物流を強力にサポートしながら、簡単かつ安全でスピーディーなオンラインショップエクスペリエンスをお客様に提供しています。

私たちのビジネスが急成長し、需要に対応しようとバックエンドシステムをスケーリングする際、チームは厳しい課題に直面しました。前回の記事では、データベース容量を心配することなくユーザーに優れたサービスをできるように、TiDBを活用してシステムをスケールアウトする方法をご紹介しました。TiDBは、MySQLとの互換性を持った、オープンソースのハイブリッドトランザクション分析処理(HTAP)データベースです。

市場には非常に多くのデータベースがあります。最適なものを選ぶにはどうすればいいのでしょうか?  今回の記事では、このことに関する当社の考え方をご紹介します。いくつかのデータベースを比較し、用途にぴったりと合致するものを見つける際、この記事がお役に立てれば幸いです。

以下の項目を検討していきます。

 

 

Shopeeで使用しているデータベース

Shopeeで使用しているデータベースを説明します。

 

  • リレーショナルデータベースとしてMySQLとTiDBを使用。MySQLに大部分のデータを保存しています。また、TiDBクラスタを継続的に追加しています。
  • さまざまなShopeeアプリケーションでRedisを幅広く使用。

 

  • 一部のアプリケーションとチームは、HBaseやPikaなどの他のデータベースも使用。ただし、これらのデータベースについては今回の記事では取り上げません。

 

データベース選択の重要性

当社ビジネスは急成長しています。今年、1週間に作成しているデータベースの数は、1年前や2年前と比べて10倍にもなっています。実稼働環境の論理データベースの数は、毎年約3倍から5倍に増加しています。2019年、この数は5倍以上に増加しました。

データベースが実稼働環境に入る前に、データベース管理者(DBA)とR&Dチームは、データベースの物理設計と論理設計のおおまかな評価を行います。

当社の経験によると、設計段階中に間違った決定を下してしまうと、後ほどそれを修正するのに多くの時間と労力を費やすことになります。したがって、簡潔かつ効率的なデータベース選択戦略を策定し、いつでも正しい決定を下せるようにします。

 

当社のデータベース選択戦略

Shopeeでのデータベース選択戦略は通常以下のとおりです。

 

  • デフォルトでMySQLを使用するが、テラバイト規模のMySQLインスタンスはなるべく減らす。
  • 可能な範囲内でTiDBの使用を試みる。2018年以降、TiDBを使用しています。また、シャーディングに適していない大量のデータを取り扱うシナリオでは、特にTiDBをお勧めします。
  • 必要に応じてRedisを導入し、一部のリレーショナルデータベースのピークトラフィックを緩和する。これに関しては後ほど詳しく説明します。

 

データベースを選択するときに考慮すべき要素

さまざまな製品の中からデータベースを選択するとき、以下を自問します。

 

  • 新しいデータベースのデータサイズは、今後12~18か月の間に1 TBを超えるか? 「イエス」の場合は、MySQLシャーディングまたはTiDBを検討すべきです。

 

  • 単一のMySQLテーブルが、1000万行以上または10 GB以上のデータを持つことになるか? 「イエス」の場合は、MySQLシャーディングを利用するか、TiDBを導入して水平方向の拡張性を獲得し、労力を要するシャーディングを回避することができます。これは、単一のMySQLテーブルに含まれるデータが1000万行を超えた場合、または単一のテーブルが10 GB以上のディスクスペースを占有した場合、そのデータベースの速度が低下し、メンテナンスが困難になる可能性があるからです。例えば一部のSQLクエリは最適化が難しく、オンラインデータ定義言語(DDL)が大きな課題になります。

 

  • 単一のMySQLノードの1秒あたりの書き込みクエリ(QPS)が1,000回の書き込みを超えるか? 「イエス」の場合は、以下のアプローチを試します。

 

    • MySQLシャーディングを適用し、書き込みトラフィックを複数のMySQLプライマリに分散させる。
    • TiDBを使用して書き込みをより多くのノードに分散させる。
    • 非同期書き込みを実装するため、書き込みキャッシュとしてRedisまたはメッセージキューを導入することを検討する。 1秒あたり1,000回の書き込みは、しきい値としては低いと思われるかもしれません。当社は以下の理由から、しきい値を1,000回に設定しています。

 

    • アプリケーションを実稼働環境に移行させる前は、推定値が不正確である可能性があります。通常の条件下では、1秒あたりの書き込みは1,000回です。しかし、大々的な販売促進キャンペーンを実行している時は、書き込みQPSは10,000回まで跳ね上がる可能性があります。したがって、保守的な参照値を設定するのが賢明です。
    • 当社のR&Dチームは大きなテキストフィールドを利用できます。単一行の長さが一定のポイントに達すると、プライマリデータベースの書き込みパフォーマンスとセカンダリデータベースのレプリケーションパフォーマンスが大幅に低下する可能性があります。したがって、単一ノードに対して高い書き込みレートを想定することはできません。
  • 該当する用途において、99パーセンタイルのレスポンス時間が1ミリ秒(ms)以内である必要があるか? 「イエス」の場合は、データベースの直接読み取りや直接書き込みを行わないようにします。Redisをバッファレイヤーとして利用できます。フロントエンドが直接Redisに読み取りと書き込みを行い、高速なI/Oを確保します。 MySQLを使用してきた経験から、ほとんどのシナリオにおいて、MySQLサーバーオプション、テーブルスキーマ設計、SQL文、アプリケーションコードを最適化した後は、99パーセンタイルのレスポンス時間を10 ms未満にすることができます。 TiDBのコンピューティングレイヤーとストレージレイヤーは分離されており、複数のコンポーネントが連携してSQL文を実行します。したがって、当社はTiDB内での99パーセンタイルのレスポンス時間を100 ms前後に想定しています。

 

シャーディングすべきか否か

Shopeeでは、新しいデータベース設計を評価するときに役立つ、数十のチェックポイントのリストを用意していますが、そのうちの重要なポイントの1つが「シャーディングすべきか否か」です。当社にとってMySQLシャーディングは長い間、データベースを水平方向にスケーリングするための唯一の方法でした。ShopeeにTiDBを導入した後は、「シャーディング不要」という新しい選択肢を得ました。

当社の経験によると、シナリオによっては、MySQLシャーディングは長所よりも短所の方が多い場合があります。日々の開発、オペレーション、メンテナンスのために余分なコストを負担する必要があります。データベースを選択する段階では、DBAとR&Dは以下のシナリオを特定し、諸問題を解決する具体的方法を見つける必要があります。

 

  • データベース容量を正確に見積もれない場合 例えば直近の3か月間で、オンラインのログデータベースの増分データのサイズが、過去3年間のサイズを超えていたとします。シャーディングを実施する場合、この種のデータベースは再シャーディングを繰り返し行う必要があります。各シャーディングプロセスは複雑であり、大きな労力を必要とします。 当社の経験によると、TiDBはログストレージに対する理想的なソリューションと言えます。現在のShopeeでは、ログは通常TiDBに保存します。

 

  • データベースを使用して複雑な多次元クエリを実行する場合 例えばeコマースの注文データベースについて考えてみましょう。各サブシステムでは、買主、売主、注文ステータス、支払方法別にデータをフィルタリングする必要があります。買主別にシャーディングした場合、売主の情報を照会することが難しくなります。買主と売主を逆にした場合も同様のことが言えます。 一方では、当社は最も重要な次元のクエリ向けに、ヘテロジニアスな別個のインデックスデータベースを作成しました。他方では、TiDB上に注文集約テーブルを作成し、複数のシャード間に分散していた注文データを単一のTiDBテーブルに集約しました。これにより、フルテーブルスキャンを必要とする複雑なSQLクエリを、直接TiDBの集約テーブル上で実行することができます。

 

  • 1つのデータベース内にデータが偏在している場合 「いいね!」や「フォローする」などのソーシャルアプリケーションのデータの場合は、データベースをユーザー別にシャーディングすると、データが偏在する可能性があります。少数のシャードのデータ量が他の多数のシャードのデータ量に比べて格段に大きくなっている場合があります。こうした大量のデータを持つシャードも、読み取りや書き込みのホットスポットです。そのため、パフォーマンスのボトルネックが発生しやすくなります。 一般的に行われる手法は再シャーディングです。つまり、データをより多くのパーティションごとにスライスすることによって、各シャードのデータサイズを縮小し、読み書きトラフィックを削減します。 当社は近年、一部のデータをTiDBに移行し始めました。理論上、TiDBテーブルのプライマリキーがまばらに分散している場合、ホットデータをTiKVリージョン(TiKV内のデータストレージの基本単位)間で均等に分散することができます。 全体として、MySQLシャーディングはデータベースの水平方向の拡張性に関する問題を解決できますが、開発、オペレーション、メンテナンスに関するいくつかの問題を引き起こします。当社はMySQLシャーディングフレームワークの下で、問題を緩和および解決しようとしています。今のところ、データベースをシャーディングする必要のない、TiDBベースのソリューションを構築しよう試みています。この取り組みは成果を上げつつあります。

 

データベース選択から得た教訓

ここまでのところ、Shopeeのデータベース選択の基本戦略と関連する重要測定項目について説明してきました。このセクションでは、当社が得た教訓を説明します。

 

インメモリデータベースとしてMySQLを使用する

Shopeeでは、インメモリデータベースとしてMySQLを使用することがあります。アプリケーションが実稼働環境に移行した後、R&Dチームは最初にアプリケーションロジックに注力する一方、データベースアクセスレイヤーのコードが最適でない可能性があります。

したがって、プロジェクトの初期段階では、スロークエリやバースト的な高頻度の読み書きの問題がよく発生します。当社はこれらの問題に対処するため、すべてのホットデータをMySQLバッファプールにロードできるよう、十分な大きさのメモリスペースを確保しています。こうすることにより、アプリケーションパフォーマンスに関する一部の問題を緩和することができます。

当社の統計によると、Shopeeの実稼働データベースのうちデータ量が50 GB未満のものは、全体の80%を占めています。当社のデータベースサーバーのメモリサイズは50 GBを超えているため、アプリケーションの試行錯誤の期間が不要になります。データアウトバーストフェーズに入ると、R&Dチームにデータベースを最適化するよう依頼できます。

 

テラバイト規模のデータベースを削減する

当社の実稼働データベースのうち、1 TB以上のデータを持つものは全体の2.5%です。これらのテラバイト規模データベースのデータサイズの平均は、2 TBです。最大のもので4 TBのデータを持っています。DBAの最優先事項は、テラバイト規模データベースのデータ量を継続的に削減することです。

データサイズの急増に対処するため、MySQLシャーディングとTiDBを試すことができます。また、旧データをアーカイブし、ハードウェアリソースをアップグレードすることもできます。

 

旧データのアーカイブ

旧データはディスクスペースを多くとっていますが、頻繁に読み取りや書き込みが行われるわけではありません。つまり、これらのデータはおそらく「ホットデータ」に該当しないと思われます。アプリケーションオーナーが許容すれば、当社は通常、旧データを別のMySQLインスタンスにアーカイブします。このアプリケーションでは、読み取りと書き込みを新しいインスタンスに移行する必要があります。新しいインスタンスでは、旧データを年ごとまたは月ごとに別々のテーブルに保存することによって、単一のテーブルが大きくなり過ぎないようにします。新しいインスタンスでは、InnoDBの透過的ページ圧縮を有効化してディスク使用量を削減することもできます。

TiDBはデータのアーカイブに適しています。理論上、TiDBクラスタは無制限にスケールアウトすることができるので、ユーザーはディスク容量の制限について心配する必要がありません。TiDBの特徴は、コンピューティングレイヤーとストレージレイヤーの両方で柔軟な水平方向の拡張性を備えていることです。したがって、データの増加やアプリケーションの読み書きトラフィックに合わせ、徐々にサーバーを追加することができます。これにより、ハードウェアを効率的に利用できるほか、アーカイブデータベースの実稼働環境の初期段階で、多くのリソースがアイドル状態になることを防ぐことができます。

 

スケールアップ

MySQLのデータサイズが1 TBに達し、ディスクスペースが不足してきたらどうすればいいのでしょうか。ディスクスペースを2倍にし、メモリ容量を増やすことによって、エンジニアリングがデータベースをシャーディングするための時間をかせぐことは可能でしょうか。

実際のところ、データベースがテラバイト規模のデータを持つようになったとき、長い間利用してきた既存アプリケーション向けにデータベースシャーディングを実装するのが困難な場合があります。旧データをアーカイブしてデータ量を安定的なレベルに維持することが可能ならば(ただし、データベースには依然としてテラバイト単位のデータが保存されている)、ハードウェアをアップグレードしてデータベースパフォーマンスを改善することもできます。

 

Redisを使用してピークトラフィックを緩和する

Redisを利用して同時実行性の高い読み取りと書き込みを処理する方法には2つあります。

 

  • キャッシュに書き込みを行ってから、データベースに書き込みを行う
  • データベースに書き込みを行ってから、キャッシュに書き込みを行う

 

キャッシュに書き込みを行ってから、データベースに書き込みを行う

アプリケーションのフロントエンドは、直接Redisに読み取りと書き込みを行います。アプリケーションのバックエンドは、MySQLまたはTiDBにデータをスムーズかつ非同期的に永続化します。ここでは、MySQLやTiDBはRedisデータの永続化レイヤーとして機能します。システムを設計するとき、実稼働環境において同時実行性の高い読み取りと書き込みが想定される場合は、バッファレイヤーとしてRedisを利用するとシステムがうまく機能します。

Shopeeのソーシャル関連アプリケーションの場合、大々的な販売促進キャンペーン中、これらのピークトラフィックは通常の何十倍さらには何百倍にもなります。これらのアプリケーションは、典型的なパフォーマンスクリティカルなアプリケーションです。R&Dチームがこうしたトラフィックの増加を想定せず、アプリケーションが依然としてデータベースに直接読み取りと書き込みを行えば、販売促進キャンペーン中にトラフィックが急増したときにデータベースはクラッシュします。このシナリオにおいて、バックエンドデータベースのピークトラフィックを緩和するための理想的なソリューションとなるのはRedisです。

Redisクラスタ全体に障害が生じた場合はどうでしょうか。これには2つのソリューションがあります。

 

  • アプリケーションが直接データベースに読み取りと書き込みを行うようにする。このソリューションでは、パフォーマンスが低下する可能性がありますが、大部分のデータの整合性が保持されます。一部のデータクリティカルなアプリケーションでは、このソリューションを採用する傾向があります。
  • トラフィックを新しいRedisクラスタに切り替えてサービスを再開し、その後データの蓄積を開始する。また、別のアプリケーションを実行して旧データの一部をデータベースからRedisにロードすることも可能。アプリケーションの中には、多数のオペレーションを同時に実行していても、データ喪失に対して耐性を持つものもあります。そうしたアプリケーションでは、このソリューションを採用できます。

 

データベースに書き込みを行ってから、キャッシュに書き込みを行う

当社は今なおデータベースに読み取りと書き込みを行うアプリケーションを使用しています。当社はShopeeのData Event Center(DEC)を利用することができます。DECは、MySQLのバイナリログを継続的にパースし、結果を再編成し、その後Redisに書き込みを行うミドルウェアです。この方法で、集中的な読み取り専用トラフィックをRedisに移すことができます。その結果、データベースへの負荷が大幅に軽減されます。

データをRedisに再構築するとき、データ構造を特定のクエリパターンに合わせてカスタマイズすることができます。クエリの中には、SQLでの実装に適していないものもあります。Redisを使用すると、こうしたクエリをより効率的に実行できる場合もあります。

また、データベースとRedisに同時にデータを書き込むアプリケーションに比べ、MySQLのバイナリログをパースしてデータをRedisに再構築するアプリケーションの方が有益です。そのアプリケーションの実装は簡単です。なぜならば、開発者はデータベースからRedisにデータをレプリケートするためのロジックを考慮する必要がないからです。

しかし、この方法の弱点は書き込みレイテンシーです。データはMySQLプライマリに書き込まれ、次いでRedisに送られます。このプロセス中、数十ミリ秒のレイテンシーが発生する場合があります。この方法でRedisを利用する場合は、アプリケーションがこのレベルのレイテンシーを許容できるかどうかを確認しておく必要があります。

当社が新しい注文の照会をリアルタイムで行う場合は、通常、次に説明する方法によって、MySQLプライマリでの高頻度の読み取り専用クエリを回避します。セカンダリレプリケーションの遅延による影響を回避するため、当社はShopee注文テーブルのクリティカルカラムのクエリの一部をMySQLプライマリにルーティングする必要がありました。大々的な販売促進キャンペーン中、プライマリに過剰な負荷がかかる可能性がありました。したがって、アプローチを変更し、最初に新しい注文データをMySQLに書き込み、次いでバイナリログのパース結果をRedisに変換しました。このようにして、MySQLプライマリに対するプレッシャーを効果的に軽減しました。

 

データ構造とコードを直接移行するのではなく、リファクタリングする

開発者がデータをMySQLからTiDBに移行するとき、DBAはしばしば開発者にデータ構造とコードをTiDBに適合させるよう注意喚起します。

一例をご紹介します。MySQLシャーディングを利用しているシステムがすでに導入されているとします。このソリューションでは、データは1,000のテーブルに均等に分割されています。TiDBに移行した後、シャーディングを停止し、これらのテーブルを単一テーブルにマージしました。

移行を完了し、アプリケーションがサービスを再開した後、あるSQLクエリのパフォーマンスが大幅に低下していることがわかりました。同時実行性の高いトラフィックの場合、このクエリはTiDBクラスタ全体のハングアップさえも引き起こしました。

クエリを分析した結果、以下の点が判明しました。

 

  • このクエリは、かなり頻繁に実行されている。ピークタイムでは、読み取り専用クエリ全体の90%を占めていました。
  • このクエリは、複雑なSQLクエリであり、フルテーブルスキャンを必要とする。インデックスを追加しても、クエリを最適化するのは困難でした。TiDBに移行する前、MySQLには1,000のテーブルがありました。このクエリを実行したとき、スキャンされたのは小さなテーブル1つのみであり、クエリ処理のためのMySQLセカンダリは20以上ありました。こうした状態であっても、データサイズが増加し、ホットデータの量がメモリサイズを超えると、すべてのMySQLセカンダリに過剰な負荷がかかりました。 データがTiDBに移行され、1,000のテーブルが1つのテーブルにマージされた後、このクエリでは以前よりもはるかに大きなテーブルをスキャンすることが必要になりました。大量の中間結果セットはTiKVノードからSQLノードに渡されていました。そのため、期待していたパフォーマンスが出ませんでした。

上記の分析に基づき、R&DチームはRedisを導入しました。R&Dチームは、バイナリログのパース結果をRedisに変換する際、クエリのデータ構造をカスタマイズしました。読み取り専用クエリの90%がRedisに切り替えられました。その結果、このクエリは以前よりも高速かつ安定的になり、TiDBのストレージノードとコンピューティングノードが大幅に削減されました。

TiDBは、MySQL構文との高い互換性があります。そのため、MySQLからTiDBへの移行を容易に行うことができます。しかし、TiDBは新しいデータベースなので、その実装はMySQLの実装とはまったく異なります。したがって、TiDBの特徴や具体的なアプリケーションシナリオに合わせてカスタマイズされたソリューションを構築する必要があります。

ShopeeでのTiDBの活用方法

TiDBはオープンソースのNewSQLデータベースであり、HTAPワークロードをサポートしています。MySQLとの互換性を持ち、水平方向の拡張性、厳密な整合性、優れた可用性を備えています。こちらのリンクからTiDBのアーキテクチャをご覧ください。 このセクションでは、ShopeeでのTiDBクラスタのステータスと、当社がTiDBを使用しているいくつかのシナリオを簡単に説明します。

クラスタのステータス

  • 2019年末までに、実稼働環境に20以上のTiDBクラスタをデプロイしました。現在のノード数は400以上です。
  • データサイズは200 TB以上です。
  • 実稼働環境では、主にTiDB 2.1を使用しており、一部のアプリケーションではTiDB 3.0を試用し始めています。
  • 最大のクラスタには40以上のノードがあり、データサイズは約30 TBになっています。
  • これらのTiDBクラスタは、ユーザー、商品、注文、リスク管理のような複数のシステムで実行されています。

アプリケーションシナリオ

Shopeeでは、3つのシナリオでTiDBを使用しています。

  • ログストレージ
    • ユースケース:監査ログとリスク管理システム
    • フロントエンドアプリケーションは、ログデータをKafkaに書き込みます。バックエンドアプリケーションは、非同期的にKafkaのメッセージを消費し、これらをTiDBに変換します。バックエンドの管理者ウェブサイトのようなアプリケーションは、直接TiDBからログを読み取ることができます。
    • ストレージノードやコンピューティングノードを必要に応じて追加することができます。各オペレーションやメンテナンスはMySQLシャーディングの場合よりも簡単です。
  • MySQLシャーディングのためのデータ集約
    • ユースケース:注文データの集約と商品データの集約
    • アプリケーションデータはMySQLのシャードに書き込まれます。MySQLのバイナリログをパースしてデータを非同期的にTiDBにレプリケートするため、Data Event Center(DEC)というミドルウェアを開発しました。ビジネスインテリジェンス(BI)やバックエンドの管理者システムなどのアプリケーションは、TiDB上で複雑なクエリを実行します。これらのアプリケーションについては、シャーディングルールを考慮したり、TiDBの集約テーブルを直接読み込んだりする必要はありません。
  • TiDBに直接読み込みと書き込みを行うアプリケーション
    • ユースケース:Shopeeチャットシステム
    • このアプリケーションは直接TiDBに読み込みと書き込みを行います。シャーディングを行う必要がありませんので、アプリケーションを簡単に実装することができます。一部のクラスタのTiDB Binlogコンポーネントをセットアップし、バイナリログをKafkaにパースしました。これで、他のアプリケーションがデータ変更をサブスクライブできます。

結論

今回の記事では、リレーショナルデータベースの選択方法に対するShopeeの考え方と、MySQL、TiDB、Redisの使用経験ついて説明しました。 簡潔に言えば、扱っているデータのサイズが小さく、アプリケーションがまだ初期段階ならば、MySQLを選択するのがよいでしょう。また、シャーディングのためにアプリケーション設計で妥協する必要もありません。なぜならばビジネスが成長し、データ量が増加したとき、MySQLからTiDBにスムーズに移行できるからです。アプリケーションは水平方向の拡張性を持つようになり、しかもアプリケーション開発における柔軟性を引き続き確保できます。一方、Redisを活用することによって、クエリのスピードを上げ、データベースへのプレッシャーを緩和することができます。これにより、これまで以上にスループットや整合性の向上に注力することができます。 オープンソースのデータベースであること、水平方向の拡張性を備えていること、MySQLとの互換性を持っていることから、当社はTiDBを選択しました。この2年間、TiDBは急速に発展し、目覚ましい進歩を遂げています。ShopeeにとってTiDBは最も重要なデータベースインフラストラクチャの1つであり、ますます多くのシナリオで活用されています。TiDBは将来、さらに多くのShopeeのトラフィックを処理してくれるでしょう。 TiDBを開発し、有益なサポートを提供してくれるPingCAPのメンバーとTiDBコミュニティに感謝しています。

 

Shopee社について

Shopee社は東南アジアや台湾で業界をリードしているeコマースプラットフォームです。2015年に7つの市場でローンチされ、地域の消費者、販売者、企業を結びつけています。

日々、何百万もの人々が当社のサービスを通じ、簡単かつ安全で魅力的なエクスペリエンスを享受しています。統合された決済機能とロジスティクスに支えられた幅広い品揃えに加え、各市場に合った人気のエンターテイメント機能を利用できます。また、当社は、地域のデジタル経済にとっての主要なコントリビューターでもあり、各ブランドや企業家がeコマース分野で成功できるよう支援を行うことに固くコミットしています。

当社は、世界をリードする消費者向けインターネット企業であるSea Limited(NYSE:SE)のグループ企業です。Sea社はShopeeに加え、デジタルエンターテイメント部門のGarena、デジタル金融サービス部門のSeaMoneyなどの他のコアビジネスも展開しています。Sea社のミッションは、テクノロジーによって消費者の生活とスモールビジネスを改善することです。