さいきんのMySQLのJSONまわり

こんにちわ。せじまです。
さいきん、しばしば庭園や日帰り登山に行って風景写真を撮っているのですが、カメラで写真を撮るという行為は(中略)実行計画を考えながらSQLを書く行為に近しいことだと思いますので、エンジニアの方にはけっこうオススメです。

今日は軽めの話をさっくりさせていただこうかと思います。

はじめに

皆さんは最近のMySQLがJSON型をサポートしているのをご存知でしょうか。「なぜ正規化されていないJSONをRDBMSに格納するのですか!正規化しましょう正規化」という至極ごもっともなご意見もあるでしょうが、 MySQLは5.7からJSON型のサポートをはじめ、8.0でかなり開発が加速している印象を受けます。JSON型がネイティブでサポートされるようになったのは、MySQL5.7のRelease Candidate以降です。5.7 RCがリリースされた2015年あたりから、MySQL関連のイベントに行くたびにJSON型の話を聞く機会が増えていきました。ただ、日本のMySQLコミュニティ(少なくともMySQLのイベントに来るユーザの間)では、「RDBなんだから正規化すればいいのでは・・・」「それSQLでもできるのでは・・・」という声が多かったような印象を受けます。ただ、MySQL関連のイベントに来られる方の多くはDBAで、正規化されたデータを扱うことを良しとされる、SQLが共通言語な方々が多いでしょうから、JSON型にあまり魅力を感じない方が多かったのかもしれません。
直近わたしもJSON型使う予定はないですけど、MySQLのJSON型を取り巻く環境がちょっとアツイ感じになってきているように感じるので、備忘録程度に書いてみようと思います。

MySQL5.7.12から見えてきたJSON推し

MySQL5.7のGAがリリースされてから半年後、GAがリリースされてから半年後、mysqld - クライアント間にX Protocolという新しいプロトコルが追加され、SQLだけではなくCRUDメソッドでデータの読み書きができるようになりました。MySQLをJSON Documentの保存先として使う、いわゆる MySQL Document Store と呼ばれる大きな機能追加であったのですが、CRUDメソッドを使ったときデータを保存する際の型が、5.7で追加されたJSON型でした。この話を初めて聞いたとき、私は「SQLを積極的には書きたくない開発者を、MySQLの新規ユーザとして取り込もうとしているのかな?」くらいにしか認識してなかったのですが、JSON型に対する開発はここで停滞せず、8.0になってからも様々な改善が継続されることとなりました。

MySQL8.0での改善

MySQL 8.0 でJSON型に対して入った改善の一部に、次のようなものがあります。

  • TABLEのColumnをJSON Documentに変換するための関数の追加。
  • JSON Documentから teomporary table を生成するための関数、JSON_TABLE()の追加。
  • JSON型のColumnを partial update 対応(InnoDB的に、Columnを全部書き換えるのではなく、JSON型のColumnの一部だけ更新することができる)
  • JSON型のColumnを partial update 対応(--binlog-row-value-options=PARTIAL_JSON を指定することで、 binlog_format=row でも差分情報を保存できるようになり、row-based replication のときに使う binlog や relay-log の量を減らせる)

(私の知る限り)MySQLは機能ベースで開発チームが分かれているのですが、少なくともこれらは Optimizer, InnoDB, Replication と三つ以上のチームによって開発されています。そして、これらのJSON型に対する改善は、 MySQL Server Blog や MySQL High Availability という blog に、中の人による解説記事が書かれています。むかしから MySQL を見ている人間としては、「新機能追加されたけど、ドキュメント追いついてないしよくわからないからソースコード読んでどんな実装か確認するか」ということをやってきた人間としては、「けっこうな力の入れようだなぁ」と感じてしまうわけです。このへんが、個人的にはアツイなぁと感じているところです。

そもそも、MySQLにスキーマレスなデータを保存したいというニーズはあるのか?

振り返ってみると、これがけっこうあると思います。
例えば、Uberさんは

  • オペレーションデータをスキーマレスで保存したい
  • スケーラビリティ欲しい
  • 高い更新性能欲しい
  • Kafkaにstreamingしたい(Change Data Capture したい)
  • セカンダリインデックス使いたい
  • 高い信頼性が欲しい

ということで、JSON objectを圧縮してMySQLにBLOBで保存されているそうです。
大変ざっくりいいますと、Uberさんのニーズを満たすには、InnoDBと(MySQLの)Replicationが最適解であり、Kafkaにデータ流し込むならbinary logをパースすればいい、といったところだったわけですね。binary log読んでKafkaなどで Real-time change data streaming するためのソフトウェアは、 StorageTapper という名称で github.com で公開されてます。また、Uberの方は Oracle Open World 2017 で登壇され、 StorageTapper について解説されておりました。

私見ですが、JSONであろうとJSONでなかろうと、何らかのかたちでserializeされたデータ、あるいは文字列型のログデータなどをMySQLに格納するというのは、あまり珍しいことではないと思います。MySQLにJSON型がなかった時代は、TEXT型あるいはBLOB型などで各自お好みのフォーマットで保存していたわけですが、 現代のMySQLにおいては、 OptimizerのチームもInnoDBのチームもReplicationのチームも、JSON型への強力なサポート体制を示してくれています。有力な選択肢が一つできたのではないか、と考えられます。

これまた極めて個人的な見解ですが、MySQLで秀でている要素はInnoDBとReplicationであると私は捉えており、かつて私は、MySQLはOLAPよりOLTPで使うのに適したRDBMSだと認識していました。ただ、最近のMySQLはWindow関数やCTE(Common Table Expression)への対応など、Optimizerの開発がかなり進んでおり、「OLAPもMySQLで行けるようにしようぜ」という、中の人の強い意志を感じています。そう考えると、「とにかく正規化しにくいデータでもJSONで保存しよう。集計するときにJSON_TABLE()で表形式にして、JOINするなりなんなりしよう。CTEなどへのサポートも進んできたし」といった捉え方ができるわけで、MySQLがOLAP市場に食い込んでいくための第一歩として、JSONサポートの充実はありなのかもしれないなぁ、と感じるなどしています。
InnoDBとMySQLのReplicationはかなりよくできていて実績ある仕組みなので、その枠組みを活かしつつ新しいことができるようになるというのは、なかなか良いことなのではないかと思います。

かつて StorageTapper のようなモノはあまりなかった。しかし、これからは

UberさんはスキーマレスなデータをMySQLに保存し、binary logを活用してChange Data Captureするスケーラビリティの高い StorageTapperというOSSを開発されました。あと何年かすると、Uberさんほど大規模でなければ、似たようなことをMySQLでもっとお手軽にできるようになってくるかもしれません。
一部繰り返しになりますが、最近のMySQLには、JSON型に対して次のような改善点が挙げられます。

  • MySQL5.7以降はネイティブのJSON型が存在しているので、MySQL5.6の頃よりもJSONとの親和性が上がっている(2017年のころ、StorageTapper はMySQL5.6をサポート対象としていた)
  • MySQL8.0以降、JSON型のColumnからJSON_TABLE()で temporary table を生成できるようになったので、5.7以前のころと比べ、JSONを保存しているtableから集計するのが容易になっている。
  • MySQL8.0以降、JSON型のColumnの差分更新が、InnoDB&Replicaitonの各要素でサポートされている。

また、binary logのパースに関しては、次のような改善もあります。

  • MySQL8.0以降は「binary logをパースして別のシステムに流し込む、 Change Data Capture のために binary log を使う」といった用途を想定し、よりパースしやすくするために binary log にメタデータを多数追加している。

さらに、5月に開催されたMySQL Innovation Day Tokyoというイベントで、 MySQL Optimizer Team Director の Manyi さんが言ってたのですが、

  • MySQL5.7でもGenerated Columnという機能を使ってJSON型のColumnにindexを張ることができたが、今後のリリースで、直接JSON型にindexを張れるSyntaxが追加される。

とのことです。
最近のMySQLは「正規化しにくいデータ(長期に渡って拡張され続けるログデータなど)をとりあえずJSON型で保存しておけば、あとからindex張ってSQLで集計することが容易にできる」といった使い方を想定しつつ、各開発チームも開発しているのでは?といった雰囲気が伝わってくるわけです。
あと何年かすると「正規化されてないログデータなどは、とりあえずMySQLにJSON型で保存しておこう。あとで集計するのも難しくないし、 binary logよんでChange Data Captureするという使い方もできる」といった運用が、珍しいことではなくなるのかもしれません。少なくとも、MySQLの中の人たちは、そういった未来を視野に入れて開発していることでしょう。

MySQLにログを保存する、MySQLならではのメリット

現代においては、MySQL以外でもログの保存や集計は可能でしょう。むしろMySQLよりSaaSなど使う方がメジャーでしょう。しかし、MySQLでログの保存や集計を行う場合、次のようなメリットが挙げられます。

  • 小さくサービスをはじめるとき、他のデータと一緒にMySQLをデータの保存先として使うことで、システムの構成がシンプルになる。
  • MySQLは可搬性に優れる。オンプレミス環境に閉じて構築することも、パブリッククラウド間での移行も、比較的容易である。
  • オンプレミス環境に閉じて構築する場合、インターネット経由でSaaSにログを転送しない分、回線の細い地域でも構築しやすい。
  • MySQLのメジャーバージョンは、GAが出てから5年間Premier Support、6-8年目はExtended Supportとなっており、少なくとも8年間はセキュリティアップデートなどが継続して提供される。
  • 最近のMySQLはデータの暗号化などにも力を入れているので、要件的にオンプレミス環境に閉じてセキュアなシステムを設計したいとき、候補となる。
  • (マネージドサービスでなければ)マイナーバージョンアップしたいときは、自分たちで自由にスケジューリングできる。
  • マネージドサービスと異なり、自分でソースコードを読んで調査できる。
  • 必要であれば自分たちでパッチも書ける。

今後MySQLに保存するデータすべてがJSONである必要はなく、それが正しいというわけでもありません。ただ、次のような性質を持つログデータなども、これからはMySQLに保存しやすくなるわけです。

  • 将来の拡張も考えると正規化しにくい
  • とりあえずスキーマレスに保存しておいて、必要に応じてJOINしつつ集計したい
  • 集計するとき、slave側でさっくりインデックス追加して集計したい

MySQLにスキーマレスなデータを保存して、あとからいろいろ加工して使うという使い方は、Uberさんをはじめ様々な会社が何年も前から実践されていたことであり、そういった運用に対するハードルが、いま下がってきているわけであります。

例えば私がいますぐJSON型を使うとしたら

例えば弊社は、cron で

叩いた結果を daily で MySQL に格納し、SSDの書き込み寿命どれくらい減ったか確認できるようにしてあるのですが、いま新規にそういうものを作るんだったら、そのTABLEはJSON型のColumnにしてもいいかなぁと思ったりします。SSDのSMARTは、SSDのベンダーごとに細かく違ったりするので、「とりあえずSMARTの情報で欲しいものは一通り保存しておいて、集計時にベンダーごとの差分を埋めよう」といった使い方が、MySQLのJSON型に好適な使い方ではないかなぁと思うなどします。

次回は

具体的にJSON型がInnoDBにpartial updateされるあたり、実際にパラメータ等いじりながらその検証結果を書こうと思ったのですが、記事が長くなりそうだしちょっと時間もかかるかなと思ったので、次回以降に書きたいなと思うなどしました。ただ、InnoDBがいかに堅牢強固であるか示すために innodb_thread_concurrency についてもちょっと書きたいことがあるので、そっちが先になるかもしれません。
いずれにせよ、そのうちまたInnoDB関連で何か書こうかと思います。

References