個人的に気になってるNewSQLな一覧

OceanBaseとかNEDOに採択されたProject Tsurugiとか気になるニューフェイスが登場したので、トラッキング用にメモしておく。 NewSQLの定義が謎だけど、たぶん脱レガシーを標榜したSQLは皆NewSQLに分類されてるんじゃ無いかなぁ?

OceanBase

  • Alibabaによって開発/実用されているRDB。暫定世界最強。
  • SSDなどの最新のHWを前提にした構造。書き込みに強い
  • MySQL互換
  • 詳しくは下記 koduki.hatenablog.com

Project Tsurugi

PG-Strom

  • GPUを活用してスケールアップを目指したPostgreSQLベースのDWH。OLAP向け。
  • JOINやSCANをGPUにオフロードすることで大幅な性能を向上
  • SSDからRDMAで直接GPUに転送してるので圧倒的なI/Oを誇る。
  • 以下参照

VoltDB

Peloton

CockroachDB

 まとめ

自分で書いたことはもちろん利用者としてもDBに詳しいとは言えないけど、DB系の説明は読んでてワクワクしちゃうのでつい追っちゃうんですよね。

もちろん、HANAとかAWS AuroraとかCloud SpannerとかExadataやNonStop SQLとか商用にも興味深いのはいっぱいあるのでだけど、とりあえずマイナーそうなのを忘れないように上げてみた。気になったのが増えたら追加するかも。

しかし、SpannerのクローンはあるのにAuroraのクローンは無いのかな?S3にログ書くデザインとかCloud前提であれも面白そうなんだが。

それではHappy Hacking!

世界1位になったアリババの独自開発DB OceanBaseとは何者か?

さて、1週間くらい前のニュースですがAlibabaがOracleのデータベースを抜いて世界1位の座を獲得しました。 itnews.org

世界一位って何よ? って所なのですが TPC-C - All Results - Sorted by Performance の結果みたいです。 f:id:pascal256:20191021055634p:plain

まあ「Oracleのデータが10年前の11gなので今のExadata X8Mとは比べ物にならない程性能が低いであろうこと」「HPEとかも虎の子のNonStop SQL出してない」「レギュレーションの問題なのかやる気の問題なのかAWS AuroraもGCP Spannerも居ない」という事もあって、最速のRDBランキングとしての妥当性に疑問はありますが、これはランキングが悪いのであってAlibabaを貶めるものでは無いでしょう。誇大広告感は狙ったと思いますがw

ベンチマーク結果としては以下のように、2位のOracleと比較して性能がザックリと2倍。ただしコスパは10%ダウンです。とはいえ当時円高だった事もあって同等といっても差し支えは無いでしょう。

DB System Performance (tpmC) Price/tpmC
OceanBaseAlibaba Cloud Elastic Compute Service Cluster60,880,80095.6 円
OracleSPARC SuperCluster with T3-4 Servers30,249,68884.2 円(当時)

どんなデータベース?

Database of Databases - OceanBaseによると2013年から開発されていて以外と実績がある事がわかります。GithubソースコードGPLで公開されていますがほとんど更新されていません。現状の最新版の2.0とはかなり変わってるかもです。

我々が普通に使おうとするとAlibaba Cloudを利用するしか無いっぽいですね。

公式ページや中国語の解説サイトをGoogle翻訳しながら調べた感じだと以下のような特徴があります。

  • 伝統的なRDBのACIDやSQLとNoSQLの可用性やスケーラビリティを備えたデーターベース
  • SSDなどの最新のハードウェアを前提として設計
  • Shared Nothingな分散DB
  • マルチリージョンなどクラウド環境を想定したアーキテクチャ
  • MySQL互換
  • LSM treeのような階層的なストレージ構造により高速なDMLを実現

この点としては割と現代的な分散DBあるあるな特徴ですね。Auroraとか。

ストレージエンジンなんかは結構面白そうな特徴を持っていました。 f:id:pascal256:20191021064254p:plain ref: OceanBaseの概要 - ストレージエンジン

データをまずIncremental Data(増分データ)とHistorical data(ベースライン)に分けます。その上で、増分データはインメモリ(MemTable)、ベースラインはSSD(SSTable)に分けて保存します。

RDBで良く使われるB-Tree Indexは原理的にReadには強いけどWriteには弱いという特徴を持っています。これはリアルタイムにインデックス全体を再構築するためです。増分データとベースラインを分けることでWriteが発生した時の作業範囲を小さく出来ます。で、実際にデータを見るときにはSSTableにMemTableの差分を適用して見ることで対応してるかと思います。

また、SSTableは常にRead Onlyなのでロックは不要。増分が書き込まれるMemTableもRead時はロックフリーとの事なのでイベントログ自体を書き込んでると思われます。

LSM treeそのものを使ってるかはちょっと分からなかったですが、マージコストをLevelDBやRocksDBと比較してるので似たような考え方などだと予想。

結果として、DMLは全てメモリ操作になるのでインメモリDB並みに高速なようです。

Ali Payとかにも使ってるなら書き込み性能が参照性能以上に重要になって来るので、その辺りを重視した設計になってるのでしょうね。

まとめ

分散トランザクションとか色々書いてあって面白そうなのですが、良く分からないのでデーターベースに詳しい人が誰が解説してくれるのを期待!

あと、日本語はもちろん英語のドキュメントすらほぼ無いのが最大の欠点ですね。その気があれば上手く英語化したりグローバルにもオープンにしていく事で、ひとかどのポジションを気づくかもです。

しかしRDB自作できる会社は強いですよねぇ。日本だと今や日立くらい? KVSは結構各種Web企業も一時作ってましたが。

TPC-Cに関しては恐らくAmazonAWSに全面移行された記事とか合わせてイラっとしてるOracleさんが、大人気なくベンチマークを更新し他社もそれに乗ってきてそれなりに意味のあるランキングになるとちょっと面白いなぁ、と個人的には思います。

参考

JDK Flight Recorderのアーキテクチャ概要

はじめに

JDK Flight Recorder(JFR)はJavaで利用できる常時本番適用可能な超低オーバーヘッドのプロファイラです。性能分析とか障害対応の強い味方ですね!

今まで商用ライセンスのみ使用可能だったのですが、JDK11よりOpenJDKに取り込まれ自由に使えるようになりました。オープンになったので色々自分でも使いたいですし、色んな人にも使って欲しいなと思っています。

ただ、Oracleの公式ドキュメントが余りにもサラリとし過ぎていて良くわかんないので、いくつかのOracleや有志の発表資料や自分の知識を元にアーキテクチャ概要を解説して見ました。

この辺をしっかり分かりやすく書いた公式ドキュメントが無い(=あればそれが欲しい!)のと、ソースコードまで読んだ訳では無いので普通に間違ってる箇所がある可能性があります。詳しい方いましたらぜひ指摘して頂けると嬉しいです。

JFRにおける「イベント」

JFRはプロファイリング/診断のためにイベントを集めるメカニズムです。

イベントとはJVMまたはJava アプリケーションから取得するデータで、「名前」「タイムスタンプ」「カスタムペイロード」を持ちます。こちらのペイロードが実際の性能情報等が書き込まれる部分で、CPUの情報とかヒープだとかスレッドだとか具体的な値が格納されています。

JFR Eventsの概要

主として下記のような情報が取得できます。

取得対象 主な取得内容
オペレーティングシステム メモリ、CPU Load、CPU情報、ネイティブライブラリ、プロセス情報
JVM メモリの割当て、クラスのロード, JIT, GC, メソッドプロファイルイベント
Java API ソケット I/O, ファイル I/O, 例外、スレッド、モジュール情報
カスタムイベント アプリケーションコンテナなどのミドルウェア、その他ユーザ定義イベント

アプリケーション、JVM、OSと広範囲に情報が記録される事がわかるかと思います。また、カスタムに関しては下記のように作成/利用が可能です。

public class HelloJFR {
    @Label("Hello World!")
    static class HelloWorldEvent extends Event {
        @Label("Message")
        String message;
    }
    public static void main(String[] args) {
        HelloWorldEvent event = new HelloWorldEvent();
        event.message = "Hello World!";
        event.commit();
    }
}

Weblogicのようにすでにカスタムイベントに対応したミドルウェアを使えばサーブレットJDBCの情報も取得できますし、自分で拡張することも可能です。 こちらに関しては4章と6章で詳しく説明します。

イベントタイプとサンプリング

JFRでは以下の3つのタイプのイベント分類して情報を収集します。

  1. 期間イベント(duration event)

    • 開始時間と終了時間を持つイベントです。GCイベントやカスタムイベントで性能を取りたい時に使います。
    • 閾値を儲ける事が出来るので、例えば10ms以下のSQL実行イベントは記録しない、などの設定も可能です
  2. インスタントイベント(instant event)

    • 期間を持たない発生すると直ちに記録されるイベントです。Exceptionなどがこれに当たります。
  3. サンプリングイベント(sample event/requestable event)

    • 定期的に記録されるイベントです。CPU使用率などがこれに当たります。こちらの頻度は変更可能です。

JFRは大量のメトリクスを格納しつつも低遅延を保つために閾値を指定したりサンプリング頻度を過度に上げない事が重要です。

多くの場合、極端に実行時間が短いイベントは問題の焦点ではない事が多いので閾値を付けるのは妥当なトレードオフだと思います。もちろん、これはアプリケーションの性質で大きく異なり、例えばバッチとリアルタイムシステムでは「遅い」という時間の基準が違うので、必要に応じてカスタマイズをします。

データフォーマット

イベントはJSONやMessagePackのような自己記述的なデータ構造です。IDLやヘッダーなどが不要になるのでイベント単位で色々加工したいログには向いたデータ構造と言えます。データもバイナリフォーマットなため非常に効率的です。

例えば、クラスロードイベントの場合、タイムスタンプ、タイムスパン、スレッドID、スタックトレースペイロードを含み、下記のように格納されます。

<memory address>: 98 80 80 00 87 02 95 ae e4 b2 92 03 a2 f7 ae 9a 94 02 02 01 8d 11 00 00

  • Event size [98 80 80 00]
  • Event ID [87 02]
  • Timestamp [95 ae e4 b2 92 03]
  • Duration [a2 f7 ae 9a 94 02]
  • Thread ID [02]
  • Stack trace ID [01]
  • Payload [fields]
    • Loaded Class: [0x8d11]
    • Defining ClassLoader: [0]
    • Initiating ClassLoader: [0]

ref: https://openjdk.java.net/jeps/328

循環バッファとデータフロー

JFRではイベントは循環バッファに記録されます。これは、メモリとディスクを組み合わせた概念で階層を持たせる事で低遅延かつ大量のデータ格納を実現しています。

循環バッファの各領域 説明
Thread-local Buffers 各スレッドが持つローカルバッファ領域。5KB/Thread
Global in-memory Buffer Javaアプリケーションで持つインメモリ領域。452KB (default)
ディスク領域(リポジトリ) オプション指定をして置けば溢れたデータをChunk単位に分割してダンプする。 Max Chunk Sizeは12MB (default)

まず、JFRがJVMおよびJavaアプリケーションから集めたデータを 「Thread-local Buffers」 に格納します。ここは比較的小さめのサイズとなっています。

次に、 「Thread-local Buffer」 のデータが溢れたら、「Global in-memory Buffer」 にデータがコピーされ古いデータは破棄されます。

最後に、 「Global in-memory Buffer」 から溢れたデータは リポジトリ と呼ばれるディスク領域に 「Chunk」 の単位で格納されます。こちらはJVMオプションでdisk=true,repository={一時ファイル保存先}を指定した場合のみに格納されます。また、ファイルに格納されるイベントは複数スレッドから集められているので必ずしも時系列順を保証はしません。

JFRのデータフローを図示すると以下のようになります。また、MaxSizeもしくはMaxAgeで循環バッファ全体の閾値を決めます。この閾値を越すとリポジトリのデータも古いものから順に消去されることになります。 JFR Eventsのデータフロー 図. JFR イベントのデータフロー

また、異常/正常問わずJavaのプロセスが終了すればJFRファイルとして結果を格納する事が可能です。

リポジトリとJFRファイル名

JFRではfilenameというJVMオプションでJFRファイルの格納先のファイル名を指定できます。

ただし、注意が必要なのはこれは循環バッファの内容が 順次書き込まれる場所ではない という事です。

ここは少しややこしくて、この関係性を理解していないとJFRファイルの取り扱いで混乱してしまいます。

前述の通り、循環バッファの内容はrepositoryオプションで指定されたディレクトリにファイルダンプとして出力されます。もし「実行中のJavaアプリケーションのJFRファイルを取得したい」と思うなら、リポジトリのファイルを取得してください。

filenameに出力されるのは最終アウトプットになるJFRファイルです。このファイルはプロセスの完了時かJMXまたはJMCやJCMDでJFRのダンプ命令を出した時に出力されます。そして 「プロセス完了時点またはダンプ命令時点の循環バッファ」 が格納されます。そのためプロセスが動いている間は基本的にファイルは書き込まれません。

なので「Webアプリケーションのような長期に動くJavaアプリケーションのJFRファイルをバックアップしたい」等といった用途にはリポジトリからファイルを取得してやる必要があります。おそらくこちらのリポジトリに格納される一時ファイルの方が 「ログ」としてみた時の運用には近いと思います。

また、maxsizemaxageを指定した場合は循環バッファ全体の閾値となるので結果としてfilenameに含まれる内容になります。裏を返せばリポジトリに格納される一つ一つのJFRファイルのサイズはmaxsizeで指定できません。こちらに関してはmaxchunksizeを変更してやる必要があります。

両方指定することも可能ですが、個人的にはリポジトリだけ指定してれば実運用上は十分な気がします。この辺りに関しては5章で解説します。

JFRの開始とJFRファイルダンプ

JFRはMBean(FlightRecorderMXBean)またはJDK JFR Managemnet APIを使うことで操作(開始/終了)が可能です。また、JDK Mission ControlやJCMDおよびJVMオプションでも可能です。

JFR Eventsのデータフロー

デモなどではJMCやJCMDを使ったものを良く見ますが、実運用上はJVMオプションで指定して常に記録を行いリポジトリに格納される一時ファイルかプロセス終了時に作成されるJFRファイルを確認する事がほとんどだと思います。

まとめ

さて、ちょっと長くなりましたがこんな感じでしょうか。特に、循環バッファやリポジトリとJFRファイル名の事は把握しておいた方が実運用でも便利です。

コメントまたは指摘等を頂けるととても嬉しいです。

それではHappy Hacking!

参考

Oracle Code One 2019に参加してきました

ブログに書くの忘れてましたが、去年に引き続き今年もOracle Code One/Oracle Open Worldに参加してきました。

f:id:pascal256:20191001135557p:plain

すでにキーノートとかの詳細は素晴らしいレポート出てるのでそちらをみてください。

あと、去年とは違いありがたいことに今年は採択されたので発表することができました!

Performance Monitoring with Java Flight Recorder on OpenJDK [DEV2406]

話したいところはある程度気持ちを乗せてちゃんと話せたかなと思う反面、質疑応答はもう少しスムーズに出来たらとも思ったのでやはり英語の練習をもっとしないとですね。

で、ここからはかなり主観というか個人的な想像を入れた気になるトピックのまとめです。

JDKの新機能と進捗

ここに関しては良い意味で新しい情報は無かったです。OpenJDKがOSSとして運用されJSRやJEPでオープンに仕様が管理されているので、Oracle隠し球で持ってた新機能の発表とかは無かったです。

カンファレンスに行って目新しい情報が無いというのもちょっと寂しいですが、AppleオープンソースのはずのSwiftでそれをやらかして炎上しましたし、OSSとしては健全なのでこのほうが良いですね。

もちろん、各機能のJEPの現状や詳細機能の説明はとても参考になりますし、クォータリリースなので1年間を振り返ったサマリーは役に立ちます。

GraalVM

反面、ロードマップを含めて色々発表があり注目の的だったのがGraalVMです。

GraalVM自体は去年も発表ありましたし、結構前から存在していますが1.0になったということもあって今年はかなりアピールしていました。

native-image(AOT)を中心にコミュニティからの発表も多かったですが「OpenJDKと互換性を保ったまま高速なJVM(JIT)」としてOracleがかなり強く推してたので、おそらくGraalVM EEを結構推していきそうな感じです。OCIなら無料というのも売りにできるでしょうし。

ちなみに価格表を見るとOracle Java SE($25/processor/month)よりGraalVM EE($18/processor/month)のが安いので、OracleJDK使うならGraalVM使えよ、というオーラが出まくっていますw

あと、ロードマップ的には11月にはちゃんとOpenJDK11に対応するので、少なくともLTS版には随時追従していく形になるのだと思われます。 思ったよりOpenJDK11対応が早くて安心。

AOTやマルチ言語対応もかなり進んでてLLVM組み込みはJNI代用として面白そう。あと、Rubyもほぼレディな状態になっていて後はバージョンを追いつかせるだけ、とのことなのでここも個人的にはかなり気になります。爆速だし、JFR使えるかもだし。

なおAOTとJITは現時点では以下のような性能のトレードオフがあるけど、将来的には全部AOTで上回りたいという野望があるそうですw f:id:pascal256:20191001140618p:plain

Jakarata EEとEclipse MicroProfile

大変悲しいことですが今回のタイミングでJakarata EEにMicroProfileは統合されませんでした。

名前空間の差分とかの話もあるのですが、そもそもマイクロサービスの早い進化に対応させたいMicroProfileと標準化を主眼としてるJakarata EEではライフサイクルが異なるという問題も大きいようです。

個人的にはLTS版的な感じて定期的にJakarataEEにMPの仕様が取り込まれれば良いと思うのですが、今のままだとSpring的なポジションになる可能性もあってちょっと心配。 たぶんJakarataEEで行われるであろう名前空間の変更と合わせてMPの取り込みも来年か再来年くらいにされるんでは? 的な雰囲気なので当面はMPが実質的な仕様を牽引してく形になりそうなので、こちらを追う感じですね。

MPに関してはOracleWeblogicではなくHelidonで対応していくとJakarataEEのセッション中に言ってましたし、IBM RedhatのQuarkusと合わせてエンタープライズのサポートも問題なさそうです。

MPとしてはやはり話題の中心はnative-image + QuarkusでWebSphere LibertyやPayara Microはあまりトピックにはなってなかった印象。もちろん、ユーザ事例として出てくるときはそれら既存の奴を使ってたでしょうが、あまりその手のセッションは参加しなかったのでちょっとバイアスあり。

ただ、native-imageとの連携もあるので既存のアプリケーションコンテナとしての実装ではなく、Helidon/QuarkusのようなMP向けライブラリとしての開発が主流になってく気は改めてしました。

ExadataとPersistence Memory

以下の記事でも書いたのですが、Exadataについに不揮発性メモリが乗ってきました。

koduki.hatenablog.com

Intelの3D XPointが発表されてここまでくるのに長かったなぁ。まあ、今回はインメモリDBに使えるというよりは超高速なストレージとしての利用になりますが、こうした事例が増えてDBがアーキテクチャ的に進化したり、HPEのThe Machineのような近未来的なアーキテクチャが実際に出てくるようになると興味深いですよね。

これで量産化されて自宅マシンでも使えるようにならないかなー。まあ、Itaniumが自宅で使える価格/流通ではなかったので、期待しすぎると悲しくなるかもですが><

f:id:pascal256:20191001151956p:plain

ref: Introducing Exadata X8M: In-Memory Performance with All the Benefits of Shared Storage for both OLTP and Analytics | Oracle Exadata Database Machine Blog

お得意のInfiniBandを捨てて100GbE + RoCE (RDMA over Converged Ethernet)を採用しています。40Gbpsという帯域から100Gbpsに上げつつ、RDMAをRoCEで処理することでレイテンシも確保しています。

「最速のコモディティサーバ」として設計されるExadataとしてパフォーマンスを上げつつフィットさせてきた感じですね。

今回のバージョンアップで帯域的/レイテンシー的なボトルネックにさらに改善ができたと思うので、やはり「僕の考えた最強のDBサーバ」としてのExadataは面白いですね。スペック厨的にワクワクするw

Oracle Cloudの戦略

さて、最後はOCIの話です。ここ最近Oraclegが力を入れてる気がするけどちょっとパッとしない、後発だからものは良さそうだけどなぁってポジションだったOCIですが今回は結構戦略を明確にしてきたと思います。

毎度のごとくAWSをディスりつつOracle DBをageてましたが、やはりOracleはデータベースの会社です。AzureにはWindowsやOffice365/ADとの親和性、GCPにはBigQueryとAIというキラーコンテンツがあるようにOCIのキラーコンテンツはDBだということを明確に出してきています。

なので、k8sやその他マネージドサービスなど他者との差分は追いつかせるけど、推しはやはりDB。単にRDBというだけじゃなくてAWSに比べて単一のアーキテクチャでDWHもトランザクションもグラフDBもドキュメントDBもやるよ、という点を強く出してました。

むしろ、Microsoftとの提携でイントラ系も基幹/業務系もエンプラ系は取り込みたいって感じですね。Web系に関してはスモールスタートができる料金体系になるかが鍵かなぁ。

特にShared環境Autonomous DatabaseはAWS Aurora Serverlessと同様に利用した分だけ課金するモデルのようなのでコストしだいですが最良のサーバレスRDBになり得るんじゃ無いかと。コストシミュレータで計算しようとするとバグってて常にゼロ円になる(そういうところだぞ、Oracle!)のですが、購入しようとすると最低1000ドルって書いてあるので多分そういう価格感ですね。ちょっと個人だと厳しそうかなぁ。

バックエンドが何しろExadataなので前述の通りのインフラスペックは言うに及ばずOracle Enterprise Editionをベースにしてるので暗号化やパラレルクエリやパーティションなどなど盛りだくさんでサポートしてるはず。この多機能性はMySQLPostgreSQLベースでは実現できないところなので、どこまで使えるかは触ってみないとですが、私とても気になります!

そして、開発者フレンドリーなAlways Free!

www.oracle.com

検証目的という形ではありますが、個人でも企業でも2データベース、 1 CPU、 20 GBストレージが利用できます。無料で! とりあえず私も作ってみましたが、数分ポチポチすれば作れました。

今後、本格運用する前のシステムなら個人でも取りあえずOCIでAutonomous DBで作るってのはありかもしれないです。 当然、WordpressとかOracle DBに現状対応してないのですが今後そういうOSSも対応してくるかもですね。その布石なのか先日ついにOracle JDBCMaven リポジトリに登録されました!

mvnrepository.com

個人向けにはちょっぴり高そうな匂いがしますが、企業ユースだと十分良い選択肢かもですね。低スペックで良いので最低料金を下げるか、無料枠の規定に個人ユースも可とか明記してくれるとさらに最高ですがw

まとめ

キーノートがちょっとジェネリックすぎたり、ランチが微妙になってたりと少し肩透かし感もあったOCO2019ですが、発表できたしそれなりにたくさんの情報もキャッチできたと思うので良かったです。 来年も行けると良いな。

それでは、Happy Hacking!

カタログスペックで見るExadata X8M

Oracle OpenWorld 2019でExadata X8Mが登場しましたね! 基本的にExadataはOracleが毎年リリースする「僕の考えた最強のデータベース」です。 で、今回はついに夢の不揮発性メモリIntel Optena DCを本格搭載して登場しました!

blogs.oracle.com

不揮発性メモリってどのくらい速いんだろう? って気になったのでとりあえず過去にリリースされたExadataのカタログスペックと比較してみました。

https://docs.google.com/spreadsheets/d/1dC0wqfD-Dpe2VNObOqr2xs1dBnt15oVsMP8zU_06Pnw/edit?usp=sharing

まずはdatabase read I/O operations. なんと軽く2倍以上速いw f:id:pascal256:20190919161518p:plain

I/O letencyは過去機種のほぼ10倍ですね。 f:id:pascal256:20190919161750p:plain

てか、この2つの値は少なくともX5から対して変わってないですね。そしてなぜかX6の数値が少し良い。 なので、システム全体の性能としてどのくらい影響があるかはちょっと謎だったりします。QPSと言うわけでも無いですし。 もしかしたら意外とかわらないかもですし、あるいは過去に類を見ないレベルで性能向上する可能性も無きにしも非ず。

この辺は門外漢なので教えて偉い人!

なんにしてもIntel Optena DCを本格利用ということで、これから生産体制が整って普通に買えるようになると嬉しいなぁ。

それではHappy Hacking!

参考:

サーバレスなバッチを管理するためのKudaを作成しました

概要

こちらの記事でも書いたのですが、小さなバッチを運用するにはCloud Runのようなコンテナベースのサーバレス環境はとても向いています。

バッチサーバとしてインスタンスを常駐しておく必要が無いのでコスト面でも有利ですし、横にスケールさせやすいと言うのもあります。

ただ、バッチとして考えた場合にはジョブ管理ツールというかワークフローエンジンが無い。1ジョブで完結するものは良いですが、典型的なジョブは以下のように後続処理を持っています。

f:id:pascal256:20190708234925p:plain
Job Flow

後続処理が並列で動いて集約の処理がそれを待つとか典型的な実装ですよね?

ただ、これをマイクロサービスでやるのはちょっと面倒。おそらくMSA的に正しく実装しようとすると全てをイベントドリブンで記述して「JOBaの後にJOBbが動く」と言ったことを連鎖的に書くのだと思います。

これは大規模な処理で並列度を高めるには良いと思うのですが、既存のマイグレーションや小規模な運用では少し面倒です。

なので、シンプルにそれを実現するためのツールとして「Kuda」を作りました。

github.com

Kudaを使うことで各マイクロサービスは後続のことを気にする事無く記述でき、ジョブのワークフローを外出しできます。

使い方

現状、アルファ版でとりあえず作ったレベルでバギー&GCPのみ対応ですが、一応動かす事は可能です。

まず、下記のようなjobs.yamlというYAMLファイルでワークフローを記述します。

name: exec-batch
tasks:
  - name: step-load
    url: https://xxx-app1.run.app
  - name: step-parallel1
    dependencies: [step-load]
    url: https://xxx-app2.run.app
  - name: step-parallel2
    dependencies: [step-load]
    url: https://xxx-app3.run.app
  - name: step-join
    dependencies: [step-parallel1, step-parallel2]
    url: https://xxx-app4.run.app

tasks配下に対象となるジョブの一覧を書いていきます。 nameはそのまま名前で、urlがジョブとして実行したいマイクロサービスのURLとなります。dependenciesには依存(先行ジョブ)を書きます。

同じ依存を書けばそれらは並列で実行されますし、カンマ区切りで複数個書けば全てのジョブの完了を待ってから処理に入ります。

このファイルをGCS上に置いてkuda自体もCloud Runなどにデプロイすれば準備完了です。

ジョブのステータスを確認

JOBの実行状態を以下のようにURLを叩く事で確認できます。「READY -> RUNNING -> DONE」という状態遷移をします。サンプルは未稼働状態なので全部READY。

$ curl http://localhost:8080/jobq 
[(step-load, READY), (step-parallel1, READY), (step-parallel2, READY), (step-join, READY)]

実行

以下のようにexecで実行を開始します。

$ curl http://localhost:8080/exec 
done

このURLは非同期なのでジョブの完了を待つ事なくすぐにレスポンスを返します。 ジョブの進捗を見る場合は先ほどのjobqを実行するか、kuda側の標準出力を見る事で確認できます。 標準出力の結果は下記の通り。

07:09:29 INFO  [cn.or.pa.ku.WorkflowManager]] (ForkJoinPool.commonPool-worker-1) START:JOB step-btc-collector
07:09:35 INFO  [cn.or.pa.ku.WorkflowManager]] (ForkJoinPool.commonPool-worker-1) END:JOB step-btc-collector
07:09:35 INFO  [cn.or.pa.ku.WorkflowManager]] (ForkJoinPool.commonPool-worker-3) START:JOB step-btc-predictor-dtree
07:09:35 INFO  [cn.or.pa.ku.WorkflowManager]] (ForkJoinPool.commonPool-worker-5) START:JOB step-btc-predictor-knn
07:09:39 INFO  [cn.or.pa.ku.WorkflowManager]] (ForkJoinPool.commonPool-worker-5) END:JOB step-btc-predictor-knn
07:09:39 INFO  [cn.or.pa.ku.WorkflowManager]] (ForkJoinPool.commonPool-worker-3) END:JOB step-btc-predictor-dtree
07:09:39 INFO  [cn.or.pa.ku.WorkflowManager]] (ForkJoinPool.commonPool-worker-1) START:JOB step-btc-scoring
07:09:45 INFO  [cn.or.pa.ku.WorkflowManager]] (ForkJoinPool.commonPool-worker-1) END:JOB step-btc-scoring
07:09:45 INFO  [cn.or.pa.ku.rs.WorkflowResource]] (executor-thread-3) END:WORKFLOW
07:09:45 INFO  [cn.or.pa.ku.rs.WorkflowResource]] (executor-thread-3) CLEAR:JOBQ

仕組み

基本的な挙動としては

  1. execが実行されるとjobs.yamlを解析してDAGを作成
  2. DAGを元に実行状態を表すキューを作ってファイルに保存
  3. キューを読み込んで依存ジョブが全てDONEになってるREADY のジョブがあれば全て実行
  4. 実行時にジョブのステータスをRUNNNINGに変更してファイルに保存
  5. 実行時に実行完了時に自分自身(kuda)を呼ぶようにするために非同期処理で対象ジョブをキック
  6. 全てのジョブがDONEになるまで3-5を繰り返し

となります。

ポイントとしては一つ一つのリクエストは非同期処理で終わらせてるのでjobs.yamlに書いたジョブ全てが終わるまでkudaのリクエストが継続する訳ではない、ということです。

なので、小粒なジョブをたくさん書いてトータル実行時間が伸びたから親のリクエストであるKudaがタイムアウト、とかにはなら無いはず。まだ、ちゃんとテストして無いから肝心の部分がバグってるかもですがw

Kuda自体もCloud Runで動くサーバレス環境で状態はGCSなど外部に保存する想定なので並列度が上がれば勝手にスケールするはず。ただ、並列度が上がれば誤って同時実行しそうな気がするので排他ロックをDB使うなりで実現が必要な気はしてます。

今後の想定

とりあえず今後の想定としては以下の通り

  • ジョブの実行状態とかが見えるWeb UIの作成
  • Cloud Scheduler とかと連携してスケジューラーもWeb UIに統合
  • quarkusで作ってあるのでnative-buildして高速化 # 現状実行時エラーになるのでjavaで稼働
  • 排他処理周りの検討
  • テストとリファクタリングをいっぱい

まとめ

hwrapと合わせて使うことで、Cloud Runなどを利用して簡単にサバーレスバッチを作るためのツールを作りました。

巨大なバッチならSparkとか使えばスケールアウト構成に出来るのですが、そうじゃない場合はk8sとかを使って上手くできないかなー、と思ってたのでようやく開発着手、というところですね。

Dockerというかk8sをベースにバッチ管理だとargoがありますが、あれよりも機能を減らしてでも手軽に運用出来るものに出来たら良いな、と思っています。HTTP対応してれば良いのでk8s/Cloud Runに本来的には縛られないし。

ちなみに名前の由来は「パイプライン」からの連想で「管」と、なんとなくリクエストを連鎖的に発生させて動かすイメージから「管狐」を連想したのが理由です。

それでは、Happy Hacking!

「二段階認証…?」と言わないためのMFA入門 --- あるいはIDシステム地獄への案内

はじめに

さて、7 Payの社長が2段階認証知らなかった問題が良い感じに炎上していますね。 個人的には常に知っておく必要が絶対あるかというと専門分野の問題があるから議論の余地あるとして、セキュリティの問題で会見するなら部下にちゃんとレクさせろよ、とは思う。

7 PayとMFAの話は下記の動画でも話したんですが、7 Pay云々は忘れて「そもそもMFAってなに?」ってところをもう少し整理してまとめていきたいと思います。

www.youtube.com

認証と認可

まずはおきまりの「認証(Authentication)」と「認可(Authorization)」の違いです。

日本語で書いても英語で書いてもややこしいのですが、「認証」とは「Identify」すなわち誰であるかの確認です。対して「認可」は「Access Control」すなわち誰に何をさせる事を許可するか、という事です。

基本的にはMFAは「認証」の技術ですがアクセスコントロールとも実際には絡みます。

二段階認証? 二要素認証? 多要素認証? 2FA? MFA?

二段階認証(2-Step Verification)と呼ばれることが多いですが、2FAあるいは多要素認証(Multi-factor authentication/MFA)と呼ぶこともありますよね?

そもそもこの違いが分かりますか? 基本的には「結果として」同じことを指しますが、1つ以上の認証要素(つまり1種類でも良い)を2回重ねるのが2段階認証で必ず2つ以上の要素を使うのが多要素認証---すなわちMFAです。

定義的に二要素認証は必ず二段階認証ですがその逆は真では無いので要注意です。

では、「認証要素」とは何でしょう? セキュリティ情報の定義を調べるのに便利なNISTの「電子的認証に関するガイドライン(EN, JP)」には以下のように定義されています。

3 つの認証の基本要素

  • Something you(they) know/本人だけが知っていること
  • Something you(they) have/本人だけが持っているもの
  • Something you(they) are/本人だけが持っている特徴

Something you knowは「知識要素」とも言います。本人だけが知ってることつまりパスワードや秘密の質問が典型的な知識要素です。

生年月日や電話番号、親の氏名などもここに含まれはしますがこれらは友人知人はもちろんSNSを介して多くの人が知っているほぼ公知の情報です。

そのため、単独で認証に使うのは弱いでしょう。今回、7 Payで問題になってるのはこれを単独で使ってるからですね。

Something you haveは「所有要素」とも言います。分かりやすいのは昔銀行とかで配ってたセキュリティカード(乱数表カード)ですね。

他にもスマートフォンを用いたSMS認証やメールアドレスによる認証もこれに入ります。二段階認証と聞いて多くの人がイメージするのは今は多分これかな?

他にも端末認証やGoogleのTitanみたいなセキュリティキーやRFID/磁気カードによるスマートカードスマホや専用デバイスを使ったOTPもここに含まれます。あと、自宅とか。

Something you areは「生体要素」とも言います。いわゆる生体認証ですね。スマホ指紋認証に対応したのを皮切りに今では顔認証とかも結構一般的ですよね。他にも静脈認証とか虹彩認証もあります。

これを組み合わせて認証を行うのがMFAです。つまり、パスワードの後に第二パスワードや秘密の質問を聞くようなパターンは「二段階認証」ではあっても、「二要素認証」ではありません。

なんで、多要素が必要なの?

さて、なぜ多要素認証が必要でしょうか? それは単純に複数の要素を同時に揃えるのは難しいからです。

7 Payを例にとってみましょう。7 Payではパスワードやメールアドレスの変更に必要な認証として「ID(現在のメールアドレス)」「電話番号」「生年月日」というとても弱い知識要素を使って本人認証をしています。

なので、容易に攻撃されるわけですがパスワードなどが使えないかつ重要度の高い操作を行うときの鉄板は「変更用URLを登録済みのメールアドレスまたは電話番号のSMSに送信する」ですよね? これは基本情報処理試験にも載っているごく初歩的なテクニックです。

MFAという言葉が無い時代から使われていますが、これは「メールアドレスという所有要素」を足しているMFAなんですね。このように本人確認に複数要素を使うのは一般的で金融機関だと「家に郵送する」という手段によって「自宅」という所有要素を確認するケースも多いです。

同じ要素による二段階認証がなぜセキュリティ的に微妙かというと「同じ手段で抜かれてしまう可能性が高い」からです。例えば、現代は正しいIDとパスワードの組み合わせは普通に流出しているので攻撃者は把握していますが、同じ知識要素である秘密の質問や第二パスワードも同じ手段で抜かれる可能性が高いです。

そのため、違う系統の要素を足すことで効果的にセキュリティを高めようというのが基本的な発想です。

認証要素の組み合わせに関しては、こうしす!さんの「イマドキの小学生は「多要素認証」を正しく使いこなすらしい 」が分かりやすかったです。

www.atmarkit.co.jp

MFAとリスクベース認証とCAAC

MFAそのものでは無いのですが、MFAと合わせて使われる技術としてMFAとCAACがあります。 ついでなのでこれも簡単に説明します。

リスクベース認証

リスクベース認証とは、怪しい振る舞いをしたユーザ認証に対して追加の認証用を加える仕組みです。

例えば、5分前までに日本でログインしたのに次のアクセスがいきなりアメリカだとおかしいですよね? このように強い認証基盤には不正検知(Fraud detection)が必須です。 不正検知は時間やGeo Locationを利用した仕組みが基本だとは思いますが最近はAIなども活用されてかなり高度な分野です。 加えて、現代のセキュリティの常識として「攻撃者は正しいIDとパスワードを知っている」という前提があります。なので、単にブルートフォースを単純に弾けば良いという物でもなくなってきておりいかに「怪しいログインであるか?」を特定するのが鍵となってきています。

でも、怪しいけど正しい動作のこともありますよね? 5分でワープは出来なくても次の日にアメリカに行くこともあるでしょう。普段と違ってカフェの公衆WiFiから繋ぐかもしれません。いつもとは違うPCを使うことだって当然ありますよね?

なので、不正検知により「リスクのある認証」と判定した物は追加の認証を行うのがリスクベース認証です。この時に別な要素、例えばSMS認証とかOTPを使う事が一般的だと思うのでMFAとも大きく関連する技術要素です。

Context-Aware Access Control(CAAC)

アクセスコントロールというと昔からロールベースアクセス制御 (RBAC)とかあるわけですが、ロールだけではなくコンテキストをより適切に見ていこうという認証方式です。

コンテキストとはアクセス時にダイナミックに決まる情報なのですが、まあザックリ書くと

  • 誰が
  • どのデバイス
  • どこから
  • どのシステムの
  • どの操作をするか

といった複数の要素を加味してアクセス権を付与する方式です。

この辺、実装が先行してる気がしていて学術的な定義があるんだか無いんだが分からん感じです。論文だとこの辺りだと思いますが、普通にGoogleのCloud IAPやAzureADの「条件付きアクセス」ですね。

この仕組みの良いところは、人が同じでもアクセスしてる場所や対象機能が異なれば権限を変えれる事です。なので、例えば同じ人でも会社からアクセスする時と自宅からアクセスする時で権限を変更できるんですね。 しかも、通常はリスクベースの認証と組み合わせて自宅などから作業するときは認証を追加することも可能です。

ロケーションとかを加味せずに例えば同じアカウントでも明細を見るときはパスワードだけで良いけど決済機能を使うときはOTPを経由する、みたいなのも一般的な実装になってきたと思いますがこれがRBACの範疇なのかCAACに含まれるのか勉強不足で判断が出来ないです。。。詳しい人の解説を求む。

IDシステムを自作するのは止めよう!

読んで分かる通りMFAというか認証システムは奥が深いです。とりあえず関連することを図にしてみましたが特に認証がAIとか必要だしやること多い。アクセスコントロールも実装は状態遷移複雑でめんどそう。 f:id:pascal256:20190706151859p:plain

MFA周りは実はライブラリも充実してるしどうとでもなる気はしますが、真の闇は不正検知とリスクベース認証です。 この辺りを真っ当に運用するためには相当の技術と監視体制が必要です。国内外を問わず昔からtoCのサービスを出してる所は内部にそれなりのコストをかけて仕組みを作っています。戦いの歴史なので。

でも、スタートアップはもちろん大手企業ですらいきなりそのレベルの仕組みを作るのは困難だと思います。何しろ「認証システムなんてビジネス的にはどうでも良い」事がほとんどでしょうから。。。

そんなところに予算を積むほうがおかしいです。なので、特別な理由が無い限りは自作はやめましょう。

エンタープライズシステムならAzureADなどのIDaaSに乗っかるのが一番です。何しろMSは「世界で2番目にアタックされてる組織」なので投資規模が計り知れないです。とりあえずSAML2対応しとけばなんとかなる。

コンシューマーはベストプラクティスがまだなくて、CIAM(Customer Identity Access Management)という言葉もで始めてるようですが代名詞的な製品はまだ無い気がします。 とりあえず、Google/Twitter/Github/FacebookあたりのSNS認証を使って自前では認証を作らないのが基本でしょうか? 独自ID作りたいのは分かるけど「認証の不正検知を作り込む予算と時間を得るまで」我慢しましょう。

まとめ

さて、MFA周りについてざっと書きましたが如何でしたでしょうか。今回はFIDOがどうとか規格/実装側には触れず概念的なことを中心に書きました。 とりあえず大事なこととしては「MFAの要素として如何ありそれを複数個組み合わせるのが重要!」ですね。

  • Something you(they) know/本人だけが知っていること
  • Something you(they) have/本人だけが持っているもの
  • Something you(they) are/本人だけが持っている特徴

あと、不用意に自前で認証システム作るのは脆弱性の元だからやめましょう!

それではHappy Hacking!

参考