Sansanにおけるインフラから見たRDBMSの運用

岩下訓氏:「Sansanにおけるインフラから見たRDBMSの運用」ということで、インフラ枠の最後という感じで始めさせていただきたいと思います。

私は岩下と申しまして、法人向けサービスの「Sansan」というサービスでインフラを担当しています。

好きな言語はPythonで、好きなAWSサービスはRoute 53。趣味はDTMで、これはモジュラーシンセみたいなやつんですけど。それとロードバイクです。

アンケートというか挙手をお願いしたいんですけれども、RDBMSはなにを使っているかというのをお聞かせ願いたいと思っています。

MySQLをお使いという方、挙手いただけますか?

(会場挙手)

ありがとうございます。Oracleをお使いという方いらっしゃいますか?

(会場挙手)

SQL Serverをお使いという方はいらっしゃいますか?

(会場挙手)

PostgreSQLをお使いという方いらっしゃいますか?

(会場挙手)

ありがとうございます。こんな感じなんですね。それ以外とか「RDBMSをそもそも使っていないよ」って方っていらっしゃいますか?

(会場挙手)

あ、そうですね。ありがとうございます。

当然ですが、目的に応じてデータストアは選択する必要があると思うんですけれども、弊社でもいくつかデータストアがあります。

サービスのためのメインDBであったり、フィードのためのDBであったり、キャッシュであったり、画像ストレージであったり、ログ分析・KPI・監視などの運用目的で使っているデータストアがあったりします。

図にするとこのような感じですね。

目的に応じたデータストアがいくつか使われている感じになっています。本日は、そのなかでも、ここにあるMain DBのことをお話しさせていただきたいと思います。

ということで、本日のアジェンダなんですけれども、SansanのDBの特徴ということと、SansanではPostgreSQLを活用しておりますので、その活用の内容、そして運用周りで活用しているもろもろということでお話をさせていただきたいと思います。

SansanのDBの特徴

SansanのDBの特徴ということで、ご紹介させていただきます。

まず、.NETなのにPostgreSQLを使っています。もともとはOracleだったんですけれども、創業2年目ぐらいにPostgreSQLに移行したという経緯があります。

すべてのデータがオンメモリに乗っています。また、水平分散が実装されています。それとHINT句を駆使しているというのもあります。また、ブロックサイズをデフォルトの8KBから32KBに拡張しているのも、わりと特徴的かなというところですね。そして、ユーザー認証にLDAPを利用しています。

弊社に限らないと思いますが、RDBMSをそれなりの規模で扱うにはパフォーマンスが問題になることが多々あります。パフォーマンスに対する取り組みは継続して行っているんですけれども、弊社では極めてクリティカルな問題となっていた時期もありました。

SansanのDBの歴史はパフォーマンスとの戦いだったというのも過言ではないかなというところなので、その触りからお話をさせていただければと思います。

「なぜオンメモリなのか?」ということなんですけれども、オンプレ時代の2011年頃、パフォーマンス改善を目的にioDriveというのを導入していました。

ioDriveを導入した当時はやっぱりパフォーマンスは改善したと思うんですけれども、あんなクエリとかこんなクエリとか、DBがいじめられているような状態ですね。次第にioDriveの負荷が高まっていって温度が上がっていくということがありました。

ioDriveには安全機構がありまして、76度を超えるとスロットリングしてパフォーマンスが劣化する。85度を超えるとシャットダウンするという仕様があります。実際にオンタイムでシャットダウンが発生してサービスが停止してしまうなんてこともありました。

この頃には運用の負荷がかなり増大してきており、事業へのインパクトもかなり厳しいと言わざるを得ない状況でした。データを分割してパフォーマンス改善を図ろうという機運がここで立ち上がってくるといった感じになっていました。

そして始まったプロジェクトが水平分散というものだったんですけれども、事業の成長に応じて巨大化してきたデータを分割することで性能向上をさせようというのが目的で、このプロジェクトが発足しました。

AWSへの移行

水平分散に採用したアーキテクチャとしては、クライアント側がまずテナントIDを管理する共通のDBがありまして、そこに問い合わせを行います。共通DBは、そのテナントのデータが格納されているノードの場所を教えます。そして、クライアントがその該当のノードに接続してデータ取得するといったアーキテクチャになっています。

この水平分散により1ノードのデータ量が減るということで、データがシュリンクしていくかたちになっていくんですけれども、「この状態、ioDriveで運用してた状態でそもそもパフォーマンス的に立ち行かないんだったら、もうioDrive捨ててオンメモリしかなくない?」みたいな感じになっていったという経緯があります。

そして、この当時はAWS移行の真っ只中でして、すでにフロント側、Webとか画像ストレージへのプロキシとかはもうAWSで稼動していました。

データベースの接続はサイト間VPNというのも、AWSとデータセンターはVPNで接続していて、それでしのいでいたという状況だったんですけれども、VPNがなかなか安定しているとは言えず、細かいエラーが日常的に発生していておりまして、けっこうきつい状況でした。

そんな頃にr3インスタンス、メモリが大容量なAWSのEC2のインスタンスが登場します。「これに全部乗せたら意外といけるんじゃね?」みたいな感じになってきて、大容量メモリにすべてのデータを乗せることができそうだということで、検証が始まっていきました。

オンメモリ化の検証ということだったんですけれども、PostgreSQLはOSのファイルキャッシュを信頼するという特徴があります。

PostgreSQLは共有メモリをまず参照し、なければファイルキャッシュを参照し、キャッシュもなければディスクアクセスをするという挙動になると理解しています。

そして、メモリが潤沢なr3インスタンスですべてのデータをファイルキャッシュにするという方式を採用しました。具体的な方法としては、物理ファイルをfindして catするという原始的な方法と言えば原始的な方法なんですけれども、これで実際OSのファイルキャッシュに乗るので、これをPostgreSQLが参照するという構成になっていきます。

当社は99パーセントが参照系のクエリですが、このオンメモリ化で参照系のクエリが劇的に高速化したということがあります。

そして、r3インスタンスはとにかく高いんですね。なので、これは「金の弾丸」というような施策だったなと思っております。

スケールにおける課題

オンメモリ化が進んでいくと、これはこれでスケールに課題が出てきます。

当時のスケールアウトの方式はデータを分割するという感じだったんですけれども、データの削除を行うことで分割するという運用をしておりました。非常にリスキーですし、けっこうデータのdeleteにも時間かかりますので、運用負荷が非常に高いです。

また、そのスケールの際に長時間メンテナンスをしなきゃいけないので、お客様にはかなりご迷惑をおかけしたなという実感があります。

この頃には多ノード構成になっておりまして、r3.8xlargeのインスタンスでプライマリ側が11台あって、それに対応するスタンバイが各1台あるので計22台構成のデータベースインスタンスが存在していたと。けっこう運用がしんどいって感じでした。

このデータ総量が3TBぐらいを超えてきた頃に「X1インスタンス」が登場します。これで2TBまでメモリがスケールアップできるということで部内で歓喜しまして、運用負荷の低減を目的に、即座にスケーリングが判断されたという経緯があります。

実際にどんな感じでスケールインしていったかというとけっこう簡単で、各DBクラスタをX1インスタンスに単純にコピーするという感じですね。このときにpg_basebackupというユーティリティを使ったんですけれども、これが大活躍しました。

そして、そのpg_basebackupを取って作成されたレプリカのデータベースを、ポートを分けてそのまま起動するという構成ですね。そうすると、見たことのないDBインスタンスが誕生するわけなんですけれども。

ちょっと見づらいと思うんですけど、この上のほうがアクティブのノードで、こっちがスタンバイ側のノードって感じです。

それぞれ対応するVIPがあって、Pgpoolがあって、PostgreSQLがあってというのが1インスタンスに6つぐらいあるみたいな、こんな感じの構成になりました。

AWSのRDSの中の人とヒアリングがあったというのは聞いているんですけれども、その中の人に「こんな構成なんだよ」と言ったら、「こんなの見たことない」って言われたと聞きました。

X1インスタンスを運用してみて

「X1インスタンスを運用してみて」なんですけれども、実際にスケールアウトの頻度が下がりました。

3ヶ月に1回ぐらいスケールアウトをしていたんですけれども、これが2年に1回で済むようになったということで、やっぱりオンメモリというか、X1インスタンスの大容量メモリの恩恵にかなりあずかっているという感じですね。

ただ、Reserved Instanceを購入する手が震えるんですね。すっごい高いので、3人ぐらいで画面見ながら「押すよ? 押すよ?」みたいな感じでやるという、もうすっごい怖いやつです。「家買えんじゃん?」みたいな、そんな感じですね(笑)。

それ以外なんですけれども、構成変更などで新規X1を構築するときに、insufficient capacityということで、AWS側のリソースがないよという状況に陥ったことがありました。

実際にX1でもスケールアウトの運用をしたことはがあるんですけれども、その時にメンテナンスの日程を決めて立てますといった時に、その前々日ぐらいにX1を立てたら起動しなくて「うわ、やばい!」みたいな感じになったんです。なんだかんだで動いてよかったみたいな感じだったんですけれども、けっこうドキドキするので時間的余裕を持った構築をおすすめしたいなと。もし使われるのでしたら、というところですね。

PostgreSQLの運用

X1インスタンスに至る道のりについてお話しさせていただいたんですけれども、ここからはPostgreSQLの話をさせていただきたいと思います。PostgreSQLあんまり触らない方には退屈かもしれませんが、お付き合いいただければと思います。

「活用しているPostgreSQLの機能など」ということで、Streaming ReplicationとParallel Sequential Scan、pg_hint_plan、Block Sizeの拡張、LDAPといったものがあるんですけれども、1つずつ紹介させていただきたいと思います。

Streaming Replicationというのを採用しているんですけれども、かつてはslony-Iというミドルウェアでレプリケーションをしておりまして。このslony-Iは難解で、DDLの反映にも癖があって、とてもつらいものでした。

PostgreSQLの9系から、PostgreSQL本体の機能でレプリケーション可能になりまして、これを採用するといったかたちになっています。

方式としては非同期レプリケーションを利用しておりまして、とても設定が簡単ですべてのDDLがほぼリアルタイムで複製されるのは、かなり使いやすいレプリケーションの方式だと思っています。

また、先ほども申し上げたようなpg_basebackupというユーティリティですね。物理的に同じDBを複製できるんですけれども、これはとても強い安心感があるなという実感を得ています。

また、ただし物理ログシッピングという方式ですね、トランザクションログを直接転送して適用するみたいな方式のレプリケーションなので、ネットワーク転送が侮れないなという感じがしています。

活用しているPostgreSQLの機能たち

続いて、Parallel Sequential Scanなんですけれども、読んで字のごとく、Sequential Scanを並列化してくれる機能です。

PostgreSQLの9.6から機能なのですが、Sequential Scanをせざるえない一部機能に多大な恩恵がありました。実際に3.25倍ほど高速化した機能があったりもしました。このParallel Sequential ScanはSequential Scanのためのプロセスが都度生成されるものですので、CPUの性能やコア数を要相談かなという感じです。

続いて、pg_hint_planというエクステンションなんですけれども、HINT句によって実行計画を制御するものとなっています。

Sansanではこれを多くの機能で活用しておりまして。背景としては、テナントごとに異なるデータ量の増加に伴って、意図しない実行計画となることがあります。このHINT句の適切な使用によって、数百倍の速度改善など、目覚ましい効果を発揮することもありました。

ただ、やっぱり難解で「こんなのそもそも書かなくていいんだったら、書かなくていいよね?」というのもあったんですけど、とにかく速くなるので、「HINT句は麻薬なんじゃないか?」みたいな感じで部内で揶揄される感じもありました。なので、「用法・用量を守って正しくお使いください」というところなのかなと思っていまして。

それから、PostgreSQL10から拡張統計情報というのがまた追加されたようですので、こちらにも期待をしているという感じです。

麻薬と言ったのは、こんな感じでSlackでHINT句とやると、このHINT句イズムの悪循環みたいな感じに、「速くなるよ。一度だけなら」みたいなかたちで、こういうのがうちのSlackであるという感じです。

Block Sizeの拡張とLDAPんんしょう

次、Block Sizeの拡張ということですね。名刺情報の1レコードが大きいですけれども、わりと大きくて「これって意外と性能上の原因なんじゃないの?」というのがありました。

Toastという機構がありまして、PostgreSQLはデフォルト8KBを超えたページサイズを格納する場合には、データを分割して自動で格納してくれるって機能があるんですけれども、「これがオーバーヘッドなんじゃないの?」というところがありました。

PostgreSQL界で有名な永安さんという方の記事にもあるとおり、拡張をすることで速度改善がなされる可能性があるというのがありまして。これを検証して試してみたところ、実際に検索において、劇的ではないものの、性能の向上が見られたというのもありました。

ただ、RDSではBlock Sizeは変更不可なので、将来的な移行であったりという部分で不安があったりもします。不要カラムがあったら削除するっていうのも積極的に対応していきたいなというところではあります。

続いて、LDAP認証というところですね。

調査などで本番DBを見たいことはやっぱりあります。マイグレーションも開発チームでよしなに実施してほしいなという思いもあります。だからこそアカウンティングはしっかりしたいと。共有IDは全社的にもNGであるという方針があります。

そこで、本番環境に独立したActive Directoryを構築しまして利用するということにしまして、PostgreSQLの「pg_hba.conf」という設定ファイルがあるんですけれども、こちらでLDAPの設定して、ユーザー認証はADでやるという感じで、共有IDを廃止するというプロジェクトも行いました。

実はこれがRDSを使っていない最も大きい理由でして、RDSではこのLDAPの設定ができないんですね。なので、RDSは使えなかったというのがあったんですけれども、最近、AuroraのPostgresqlでIAM認証サポートされたということですので、移行する機運がアップしてきています。

DB運用において活用しているもの

もうだいぶ、もう時間があまりないんですけれども、DB周りで活用しているもろもろについてさらっと紹介させていただきたいんですけれども。冗長化についてはやっぱり避けては通れないかなというところで、お話しさせていただきたいと思います。

冗長化はPacemakerとCorosyncでやっておりまして、よくある構成っちゃよくある構成ですね。

Active/Standbyでやってまして、Pacemakerが各リソースを管理して、pingdでネットワーク監視でスプリットブレイン対策をしていると。フェイルオーバーの機構についてはpgpool-IIでやっていてみたいな感じになっています。

ざっくり構成こんな感じで、よくある構成かなという感じですね。

Corosyncでノードのクラスタの管理をして、Pacemakerで各ノード内のリソースを管理するみたいな感じですね。

続いてVIPなんですけれども、VIPはAWS VPCのRoute Tableの書き換え方式を採用しています。

サーバ側でIPエイリアスを作成して、仮想IPアドレスのようなものをまず設定します。実IPが「eth0」でこのIPだったら、仮想IPとして「eth0:0」でこのIPを設定するみたいな感じで、1インスタンスでこの2つのIPを設定するみたいな感じにします。

これをRoute Tableで仮想IPとEC2とかが、ENIをマッピングするという方式をとっていて、異常が発生したときに、Pacemakerでリソース移動が発生したときとかに、よしなに書き換えるみたいな感じになっています。よしなに書き換える君というか、これはBoto3を駆使したスクリプトで自動化している感じです。

スプリットブレインの対策なんですけれども。両系のDBがActiveとして起動しないようにする対策ですね。データ不整合を防ぐという目的です。

それぞれから自サブネット外の複数箇所にpingをして、すべての監視対象へpingが失敗したらスイッチオーバーを実施すると。スイッチオーバー後、一定期間プライマリのDBに接続できなくなったら、フェイスオーバーが実行されるみたいな感じになっています。

最悪、両系がActiveになったということを検知したら、両方ともにDownするという安全策をとっている感じになっています。

図的に言うと、pingが失敗したらこうなって、VIPが移動して、この時点でPgpoolがマスターのPostgreSQL見に行って、このとき生きてたらこの状態になるんですけど、さらに死んでたら、移動してこっちがpromoteしてマスターになるみたいな、こんな構成になっています。

次にバックアップですね。バックアップアップは、EBSのスナップショットとトランザクションログのアーカイブを両方ともにやっています。

WALと言われるトランザクションログがあって、これはPITRを目的にしてるんですけれども、実際に問題が発生した際にPITRは非常に強力なので、手順化して定期的にテストしておくとよいでしょう。

続いてリストアですね。

リストアは、なんか最近のSRE本で、「バックアップをしたい人はいない、本当に求められているのはリストアである」ということだそうです。当社では日次でリストアをテストしています。つらつら書いてますけど、要は自動でやってますよって感じですね。これを日次でやっています。

今後の展望

ログ管理みたいなところも最後にお話ししたいんですけれども、CSVにしたログをKibanaで見てますって感じですね。運用的に見たい指標をダッシュボード化して日々の運用に役立っている感じになります。

fluent-plugin-slackというので、アプリケーション利用以外のロールから実行されたクエリをリアルタイムで通知するといった感じで、fluentdのプラグインを活用したりもしています。

おしまいに今後の展望ですね。

まだしばらくPostgreSQLは使いそうだなというところですね。PostgreSQLの10以降の新しい機能をどんどん取り入れてやっていきたいなとは思っているものの、「AuroraのPostgreSQLはやっぱり魅力的に見えるなぁ」というふうにいま見えていて、どうしようか考えているところですね。

いろんなやり方があると思うんですけれども、なにか運用とかでよさげな案とかがあったら教えていただければ幸いでございます。

私からの発表は以上とさせていただきます。ご清聴ありがとうございました。

(会場拍手)