Ninja Van - K8s上でデータベースをスケールアウト - VitessやCRDBではないMySQLの代替製品を選択

事例公開日:2021年3月18日

 

Ninja Van社は、東南アジアで急成長しているラストワンマイルロジスティクス企業です。現在、東南アジア6か国で事業を展開しています。取引先には、Amazon、Shopee、Lazada、Lineman、GradExpress、Zilingo、Tokopedia、Sendoなどがあります。

配達する小包の数は1日あたり150万個以上に上ります。データサイズが急増大するにつれて、当社のデータベースに大きなプレッシャーがかかりました。また、ProxySQL、シャーディング、Galeraに関して大きな問題が発生しました。Vitess、CockroachDB(CRDB)、TiDBを比較した結果、オープンソースでMySQL互換の分散型SQLデータベースであるTiDBが最適なソリューションであることがわかりました。現在、Kubernetes(K8s)上でTiDBを運用することによって、データベースの拡張性を実現しています。

この記事では、当社のアプリケーションの問題点、VitessやCockroachDBではなくTiDBを選択した理由、K8s上でTiDBを使用している方法、計画している今後のTiDBの活用方法について説明します。

 

問題点

当社は日々何百万個もの小包を配達しています。ほぼすべてのシステムアプリケーションがK8s上にデプロイされています。オンライントランザクション処理(OLTP)データベースは、今でも仮想マシン(VM)上で実行されており、大部分のデータベースがMySQL上にあります。100種類以上のマイクロサービスがあり、OLTPストレージとして主にMySQLを利用しています。通常、各アプリケーションの読み取りと書き込みの回数は非常に高くなっています。

データベースには以下が含まれています。

 

  • 約70台のVM
  • 100以上のスキーマ
  • 3.6 TBに及ぶテーブル

当社はGaleraプラグインを使用してMySQLを実行していました。これにより、高可用性(HA)マルチプライマリソリューションが実現されました。このソリューションでは、クラスタ内のすべてのノードをプライマリデータベースとして機能させることができるため、任意のノードに読み取りと書き込みを行うことができます。下図に示すように、書き込みを単一のノードに割り当て、読み取り/書き込みをノード間で分割しました。

 

 

旧データベースアーキテクチャ

以下はアーキテクチャ図の解説です。

 

  • 左側にあるのが各マイクロサービス
  • 右側にあるのがGaleraクラスタ
  • これらはProxySQLによって接続されている。ProxySQLはオープンソースプロジェクトであり、SQLを認識するレイヤーである。ProxySQLによって、クエリルールを定義することができるほか、高可用性を維持することもできる

クラスタ図を見ると、1つのノードが「仮想プライマリデータベース」であることがわかります。非同期レプリケーションを採用している場合は、他のノードへの書き込みに遅延が生じることがあります。アカウントからの控除といった、厳密な整合性が必要なタスクの場合は、仮想プライマリに書き込みます。読み取りに整合性を求める場合は、やはり仮想プライマリから読み込みます。なぜならば他のノードから読み込んだ場合、古くなったデータを受け取るおそれがあるからです。

 

直面した問題

ProxySQLを使用すれば、整合性のある読み込みを実現することができますが、書き込みの拡張性はあまり高くありません。また、シャーディングやGaleraに関する問題にも直面しました。

 

シャーディングの欠点

ProxySQLのユースケースの1つは、シャーディングを行うことです。しかし、シャーディングには以下の欠点があります。

 

  • スキーマベースのシャーディングよりも粒度を細かくするには、アプリケーションの変更が必要。
  • いったんシャーディングを使用すると、元に戻すのが非常に困難。データがノード間に分散された後は、もうJOINオペレーションを実行することはできません。いわゆる「クロスシャード結合」をアプリケーションレイヤーに移行する必要があります。
  • 読み取り/書き込みの分割ルールを各シャードに追加しなければならない。

これらの理由から、当社はアプリケーションレベルのシャーディングを行っていません。

 

Galeraの欠点

Galeraには以下の問題があります。

 

  • 多数のクラスタを管理するのに非常に時間がかかる — 多くのタスクを自動化したとしても。効率を上げるため、複数のスキーマをGaleraクラスタにグループ分けし、クラスタ数を減らすことにしました。
  • Galeraは書き込みの拡張性が低いため、クラスタごとの複数のスキーマがうまく機能しない。
  • レプリケーションでのクラスタ間通信で、貴重なIOPSが使用される。
  • クラスタがキャッチアップできるように書き込みがブロックされたとき、フロー制御問題が発生する可能性がある。

もう1つの課題は、OLTP量と書き込み量が急増しており、さらにスキーマを追加する必要があったことです。当社には使いやすい新しいデータベースが必要でした。しかし、どのような種類のものでしょうか?

 

データベースの要件

データベースの主な要件は以下のとおりです。

 

  • MySQLとの可能な限り高い互換性を持っている。コアへの変更を最小限に抑えたいと考えています。
  • 水平方向の拡張性。データニーズの変化に合わせ、ノードを簡単に追加または削除できる必要があります。
  • 高可用性。常時稼働することができる、高可用性のデータベースソリューションが必要です。
  • オペレーションが容易。例えばノードのスケールアップが可能な限り簡単であることが必要です。ノンロッキングDDLを、設定なしにすぐに実行できることが必要です。
  • 変更データキャプチャ(CDC)。CDCは当社のユースケースのコアなので、CDCに対応していなければなりません。CDCを使用し、Parquet形式のデータレイクにデータを取り込みます。CDCを使用し、こうしたコアデータをElasticsearchにインデックスします。また、キャッシュのアップデートにもCDCを使用します。したがって、CDCは不可欠です。
  • モニタリング。当社は高度にデータドリブンな企業です。異常の発生を認識し、それに対処できる必要があります。可用性とパフォーマンスがきわめて重要です。
  • クラウドネイティブ。昨年、すべてのものをK8sにプッシュすることを試みました。したがってK8sのサポートが不可欠です。

 

VitessやCockroachDBではなくTiDBを選択した理由

Vitess、CockroachDB、TiDBを含むいくつかの選択肢を詳しく調べました。TiDBが要件をすべて満たしていることがわかりました。TiDBをテストした後、そのOLTPシナリオとオンライン分析処理(OLAP)シナリオのパフォーマンスが満足のいくものであることがわかりました。

 

Vitessを選択しなかった理由

Vitessについて、徹底的な調査と審査を行いました。Vitessの問題点と思われるものをいくつか挙げます。

 

  • Vitessを選択した場合、現行のデータベーススキーマを大幅に変更することになる。Vitessはシャーディングを自動的に処理しますが、外部キーのクリーンアップのような大幅なスキーマ変更が必要になります。一方、TiDBは外部キー制約をサポートしていませんが、当社の現行のスキーマとクエリを解釈することができます。
  • Vitessはシャード間クエリを実行するときに、READ COMMITTEDセマンティックを提供する。この点が、REPEATABLE READをデフォルトとしているMySQLやTiDBと異なっています。
  • Vitessにはサポートされていないクエリの長いリストがあり、そのうちの一部は当社のサービスで使用されている。総じて、TiDBの方がMySQLとの互換性が高いと言えます。
  • Vitessはデフォルトではシャード間をまたがるトランザクションをサポートしていない。Vitessには2PCメカニズムが実装されているものの、Vitessの開発者自身でさえ、パフォーマンスの大幅な低下につながるという理由でこの機能の有効化を推奨していません。
  • Vitessでは各オペレーションが比較的重く、複雑である。
  • VitessはKubernetesに対応していない。2020年の7月に、Vitessの調査を開始した時、Kubernetes上でVitessを実行および管理するためにVitess Operatorが使用されていました。ところが、Vitess Operatorは不安定で、まだ実稼働環境に対応していなかったため、Vitess Operator抜きで、手動によってKubernetesにVitessクラスタをデプロイしました。

 

CockroachDBではなくTiDBを選択した理由

以下の理由からCockroachDBではなくTiDBを選択しました。

 

  • CockroachDBがサポートしているのは、PostgreSQLワイヤプロトコルとその構文の大部分である。これはPostgreSQLを利用している会社にとってはメリットになりますが、当社には当てはまりません。CockroachDBへの移行は多くの時間とコストがかかる可能性があります。
  • CockroachDBと比較した場合、TiDBにはより優れたオープンソースエコシステムとツールがある。例えばTiDB Data MigrationTiDB Lightningはデータベースの移行や同期のために使用され、TiCDCは多くのオープンソースプロトコルやシンクをサポートしています。これらすべてのツールによって、業務をより簡単に遂行できるとともに、TiDBへの移行を非常に円滑に行うことができます。対照的に、CockroachDBのツールは比較的制限されており、一部の機能はエンタープライズ向け専用です。

 

TiDBのテスト結果

テストクラスタの仕様は以下のとおりです。

 

インスタンス Google Cloud Platformのマシンタイプ インスタンス数 ストレージ
Placement Driver n1-standard-4 3 ネットワークSSD
TiKV n1-standard-16 3 ローカルSSD×5(1875 GB)
TiDB n1-standard-16 2

当社のデータベースサイズの上限は6 TB。

 

OLTPシナリオ

OLTPシナリオでは、SysbenchとカスタマイズしたLuaスクリプトを使用してベンチマーキングを行いました。OLTPシナリオ全般でのTiDBのパフォーマンスは非常に満足のいくものでした。

 

OLAPシナリオ

Ninja Van社では、多くのオペレーションで、リアルタイムデータに対する、地域の倉庫、トラック、運用、ビジネスインテリジェンスのクエリを使用しています。現在これらのオペレーションでは、MySQLセカンダリ上で、Redashを介してクエリを実行しています。以下のクエリについて、MySQLとTiDBのパフォーマンスを比較しました。業務の機密性から、SQLステートメントは省いてあります。この図から、一部のクエリに関しては、TiDBによってパフォーマンスが100倍まで改善されていることがわかります。

 

クエリ名 平均MySQL実行時間(秒) 平均TiDB実行時間(秒) パフォーマンスの改善率 コメント
Complete address For AV Unverified Addresses 207.92 33.4 6.22倍 インデックス付きカラムでの複数の結合
Order Tags Partnerships SME Prio 281.94 4.67 60.37倍 ネストされた結合
RTS For AV 193.17 14 13.79倍  
Rider Route Audit – Individual 207.48 16 12.96倍 複雑なWHERE条件を使用した複数の結合
Damage Ticket Creator 223.85 2 111.92倍 GROUP BY条件を使用した複数の結合

 

TiDBを使ってデータベースをスケールアウトする方法

TiDBについて

TiDBとは、MySQLとの互換性を持つオープンソースのNewSQLデータベースであり、PingCAP社とそのオープンソースコミュニティによって開発されました。

水平方向の拡張性、厳密な整合性、高い可用性を備えています。OLTPワークロードとOLAPワークロード両方のためのワンストップソリューションです。TiDBのアーキテクチャの詳細については、こちらをご覧ください。

 

TiDB Operatorによって、K8s上でのTiDBのオペレーションを自動化

TiDB Operatorは、K8s内のTiDBクラスタ用のオペレーション自動化システムです。デプロイメント、アップグレード、スケーリング、バックアップ、フェールオーバー、構成変更を含む、TiDBのすべての管理ライフサイクルに対応しています。

下図に示すとおり、主要部分が2つあります。

 

  • TiDB Operatorと調整ループ
  • カスタムリソース。このシナリオではTiDBクラスタです

 

 

TiDB Operatorの機能

クラスタがデプロイされた後、TiDB Operatorはなされた変更を記録します。次いで現在の状態を目的の状態に一致させるため、必要な調整を行います。

では、例をいくつか見ていきましょう。

 

クラスタのスケールアップ

TiKVノード(TiDBのストレージエンジン)が1つだけあり、これを3つのノードにスケールアップするとします。この場合は、replicas数を変更するだけです。

replicas: 3

Pods(デフォルト)画面が即座に更新され、3つのTiKVノードが実行されている様子が表示されます。

 

 

クラスタのスケールアップ

 

Google Cloud Storageのバックアップ

クラスタをGoogle Cloud Storage(GCS)にバックアップするには、YAMLファイルでバックアップパラメータを定義してから、それをK8sに適用します。TiDB Operatorがバックアップを実行します。

 

 

Google Cloud Storageのバックアップ

これらのオペレーションは非常にスムーズに実行されます。ただし、犯しやすいいくつかの間違いを知っておく必要があります。

 

潜在的な問題の回避

 ローカルSSDを使用する場合の注意点

 

ローカルSSDとリージョナルSSD間でのパフォーマンスが大幅に上昇します。そのため、ローカルSSDでTiDBクラスタを実行したいという衝動にかられるかもしれません。

しかし、これを試みる前に以下の点に留意してください。

 

  • クラウド上でK8sを実行している場合は、自動修復または自動アップグレードを無効化する。ノードを自動修復または自動アップグレードした場合、そのノードは別のマシンと交換される可能性があります。SSDはそのマシンに関連付けられているため、マシン交換があるとデータが失われます。
  • ディスクの再マウントは注意深く行う。当社はローカルボリュームプロビジョナーを使って、K8s内にローカルSSDを公開しています。しかし、ローカルSSDは実際のところ、K8s上で実行されているデーモンセットです。これらのデーモンが両方とも設定されており、マシンはいつ再起動してもおかしくない状態にあります。偶発的にディスクが再フォーマットされてデータが失われないようにする必要があります。
  • 定期的にクラスタのバックアップをとる、できればスタンバイクラスタを用意する。最悪のケースが発生したとしても、リカバリする道が残されています。

 

MySQLとの互換性の限界

開発者は、TiDBのMySQLとの互換性を可能な限り高めるため、これまで尽力してきました。とはいえ、認識しておくべき相違点がいくつかあります。

 

  • ENUM要素を変更することはできない(追加することはできる)。この問題の回避策は以下のとおりです。
    • 各要素を追加する。ただし、これらをシフト、削除、アップデートしない。
    • ENUMタイプの使用を避ける。ビジネスロジックをデータベースレイヤーに結びつけると、アプリケーションの拡張性が低下する場合があります。
  • カラムタイプを変更することはできない。例えば日付フォーマットをタイムスタンプに変更したり、カラムの小数点以下ケタ数を変更したりすることはできません。この問題の回避策は以下のとおりです。
    • 希望するタイプのカラムを再作成する。現在のカラムを変更することはできません。
    • インフラストラクチャチームやDBAと協力してスキーマレビューワークフローを確立する。これにより、スキーマが作成された後、多くの不要なタイプ変更が排除されます。
  • MySQLとTiDBとでは大文字と小文字の区別と照合順序が異なる。
    • MySQLのデフォルトの照合順序は一般的な照合順序であり、末尾が_general_ciで、大文字と小文字を区別しません。TiDBのデフォルトの照合順序はバイナリの照合順序であり、末尾が_binで、大文字と小文字を区別します
    • ユーザーがデータベースの大文字と小文字の区別を意識しなかった場合、クエリは予想外の結果を返す可能性があります。
    • 一般的な照合順序を有効化したい場合は、クラスタを初期化する時に行う必要があります。パラメータ「new_collations_enabled_on_first_bootstrap」を有効化してください。

注記

本手順は後で行うことはできません。このタイミングを逃すと後ほど問題が発生します。(この問題は今後PingCAPチームが解決するはずです。)

 

メモリ不足エラーの防止

メモリ不足(OOM)エラーを避けることは不可能です。しかし、以下のパラメータを注意深く設定することによって、メモリ不足に対して3段階の防衛策を講じることができます。

 

タスク パラメータ
マシンの仕様に基づき、クエリに適切なメモリクォータを設定する mem-quota-queryを指定
クエリがメモリクォータを超過した場合に、クエリをキャンセルするかどうかを指定する oom-actionを「cancel」に設定
単一のSQLステートメントがメモリクォータを超過した場合に、一時的なストレージを使用する use-tmp-storageを有効化

これらの防衛策にもかかわらず、OOMが生じる場合があります。例えばクエリ数が多く、それぞれのクエリがメモリを多く使っている場合は、OOMが生じる可能性があります。したがって、クエリをよく監視する必要があります。

 

OLTPシナリオとOLAPシナリオでのTiDBの利用計画

OLTPシナリオでは、最近、すべてのマイクロサービスをTiDBに移行し始めました。ロジスティクス企業である当社は、11月11日の「独身の日」のセールのようなピーク時に、eコマース業界と同様の課題に直面しています。すべてのマイクロサービスがKubernetesと自動スケーリングによって支えられています。しかし、データベースでボトルネックが発生すると、十分な速さのレスポンスを維持できなくなります。TiDBの水平方向の拡張性と高可用性は、こうしたシナリオで大きな助けになります。

OLAPシナリオでは、一部のクエリの効率が悪くなりますが、その理由にはこれらが最適化されていないクエリであるという点だけでなく、当社のデータベーススキーマがOLAP向けではなくOLTP向けに設計されているという点もあります。データベースの正規化がいつも功を奏するとは限りません。これを解決するため、Apache Flinkと、TiDBとTiFlashを利用したリアルタイムデータウェアハウスソリューションによって、ワイドテーブルをリアルタイムで構築することを模索しています。

 

Ninja Van社とTiDBのこれから

私たちは皆、TiDBが当社の次世代HTAPデータベースソリューションにとって最適な選択肢だと確信しています。しかし、「ローマは一日にして成らず」というように、TiDBに移行する過程の中で、達成すべきマイルストーンがいくつかあります。

 

意識変革

TiDBはMySQLとの互換性が高いため、日常業務がこれまでよりもずっと容易になります。しかし、TiDBは分散システムであり、MySQLとは根本的に異なる特徴がいくつかあります。例えば(InnoDBストレージエンジンを備えた)MySQLでは、自動インクリメントプライマリキーが広く利用されており、これがベストプラクティスだとみなされています。これは主に、MySQL InnoDBがデータをインデックスし保存する方法に起因しています。自動インクリメントプライマリキーを持っていれば、不要なデータ移動、ページ分割、メモリ断片化を回避することができます。

TiDBでは、データはTiKVにリージョンとして保存されます。自動インクリメントプライマリキーがあると、1つのリージョンが満杯になる前に、すべての書き込みトラフィックがTiKVインスタンスの1つに流れます。その結果、書き込みトラフィックがクラスタ間で不均衡になります。

MySQLに関する知識の大部分がまだTiDBに適用できますが、そのすべてが適用できるというわけではありません。私たちは意識を改革し、TiDBのような分散システムの本来の力をすべて引き出す必要があります。

 

CDCシナリオのサポート

当社は現在、Maxwellを使用してDB変更をキャプチャし、これらをデータレイクに同期させています。Maxwellは実行されたSQLステートメントをキャプチャできます。そのため、Maxwellを使用して消費者サイドのロジックの一部をカスタマイズしていました。しかし、TiCDCはTiKVから変更を受け取るため、SQLステートメントをキャプチャできないほか、SQLレイヤーからの情報を持っていません。当社はSQLステートメントを転送するためのリレーとしてMySQLインスタンスを導入せざるを得ません。これはあまり洗練されたアプローチとは言えず、構造全体の複雑性がさらに高まりますが、とりあえず機能はします。この状態をどうすれば改善できるか、これから検討していきたいと思います。

 

TiDBへの移行

K8s上でMySQLからTiDBに最小限のダウンタイムで移行したいと考えており、そのためのプランも策定しました。これには、MySQLインスタンスと移行先のTiDBクラスタ間での双方向同期が必要です。まず、MySQLからTiDBに移行してデータを移動させてから、プランBとしてMySQLとTiDBとの同期をとり、MySQLインスタンスの同期を維持します。何か問題が生じた場合は、最短時間でMySQLにロールバックすることができます。当社がTiDBをどのように活用しているのかもっと詳しくお知りになりたい場合、または何か質問がある場合は、SlackのTiDBコミュニティにご参加ください。