ラベル Oracle の投稿を表示しています。 すべての投稿を表示
ラベル Oracle の投稿を表示しています。 すべての投稿を表示

2018年9月30日日曜日

RDBMS(Oracle、SQL Server、MySQL)でJSONを扱ってみた

1. はじめに

複雑な構造のデータやデータ定義が頻繁に変わるデータを容易に格納できるという点で、NoSQLの中でもMongoDBやCouchbase、MarklogicなどのJSON形式でデータを保持できるドキュメント指向DBの利用は広がりつつあります。

ただし、JSON形式のデータを保持できるというだけであれば、昨今のメジャーなRDBMSにおいても機能的には可能になってきています。また、SQLにおけるJSONの扱いはSQL:2016という標準で規定されているため、各製品で利用できる関数や記法などはある程度似ています。ただ、実際には標準への準拠状況や製品独自の表記/オプションにおいて少なくない差異がある状況になっています。

そこで今回(?)は、メジャーなRDBMSの内でもOracle、SQL Server、MySQLの3DBにおける基本的なJSONの取り扱い方を簡単に見ていきたいと思います。

# PostgreSQLも見ようと思ったのですが、力尽きた…

マニュアルなどは当たっていますが、隅々まで読み込んだわけではないので、間違いやより良い方法があるかもしれません。その点はご容赦ください。また、最初に調べ始めたのがOracleのため、若干Oracle基準の説明になっている部分がありますが、ご承知おきください。

2. 動作確認環境

  • Oracle Database 18c (18.3.0) for Linux x86-64 (Enterprise Edition)
  • SQL Server 2017 for Linux (Evaluation Edition)
  • MySQL 8.0.12 for Linux x86-64 (Community Edition)

3. 使用データ

今回の動作確認では以下のデータを使っていきます。
 {
    "book_id" : 1,
    "title" : "キャズム",
    "publisher" : "翔泳社",
    "pages" : 360,
    "publish_dt" : "2014-10-04",
    "author" : [{"name" : "ジェフリー・ムーア", "role" : "著"},
                {"name" : "川又 政治", "role" : "翻訳"}]
}
{
    "book_id" : 2,
    "title" : "ビジョナリー・カンパニー",
    "publisher" : "日経BP社",
    "pages" : 475,
    "publish_dt" : "1995-09-26",
    "author" : [{"name" : "ジム・コリンズ", "role" : "著"},
                {"name" : "山岡 洋一", "role" : "翻訳"}]
}
{
    "book_id" : 3,
    "title" : "世界の経営学者はいま何を考えているのか",
    "publisher" : "英治出版",
    "pages" : 352,
    "publish_dt" : "2012-11-13",
    "author" : [{"name" : "入山 章栄", "role" : "著"}]
}
{
    "book_id" : 4,
    "title" : "イノベーションへの解",
    "publisher" : "翔泳社",
    "pages" : 373,
    "publish_dt" : "2003-12-13",
    "author" : [{"name" : "クレイトン・クリステンセン", "role" : "著"},
                {"name" : "マイケル・レイナー", "role" : "著"},
                {"name" : "玉田 俊平太", "role" : "監修"},
                {"name" : "山岡 洋一", "role" : "翻訳"}]
}
{
    "book_id" : 5,
    "title" : "機械との競争",
    "publisher" : "日経BP社",
    "pages" : 176,
    "publish_dt" : "2013-02-07",
    "author" : [{"name" : "エリック・ブリニョルフソン", "role" : "著"},
                {"name" : "アンドリュー・マカフィー", "role" : "著"},
                {"name" : "村井 章子", "role" : "翻訳"}]
} 

4. 動作確認

4.1. データ挿入

JSONデータを格納するテーブルを作成し、データを挿入します。

* Oracle

CREATE TABLE book (
    book_id   NUMBER(3),
    book_json VARCHAR2(4000),
    CONSTRAINT pk_book PRIMARY KEY (book_id),
    CONSTRAINT c_book_01 CHECK (book_json IS JSON)
);
INSERT INTO book VALUES (
1,
'{
    "book_id" : 1,
    "title" : "キャズム",
    "publisher" : "翔泳社",
    "pages" : 360,
    "publish_dt" : "2014-10-04",
    "author" : [{"name" : "ジェフリー・ムーア", "role" : "著"},
                {"name" : "川又 政治", "role" : "翻訳"}]
}');
OracleではVARCHAR2、CLOB、またはBLOBのデータ型でJSONを保持します。対象の列に正しいJSONデータが入っていることを確認するために、「IS JSON」制約を利用します。

* SQL Server

CREATE TABLE [book] (
    [book_id] INT,
    [book_json] nvarchar(4000),
    CONSTRAINT [pk_book] PRIMARY KEY ([book_id]),
    CONSTRAINT [c_book_01] CHECK (ISJSON([book_json]) = 1)
);
INSERT INTO [book] VALUES (
1,
'{
    "book_id" : 1,
    "title" : "キャズム",
    "publisher" : "翔泳社",
    "pages" : 360,
    "publish_dt" : "2014-10-04",
    "author" : [{"name" : "ジェフリー・ムーア", "role" : "著"},
                {"name" : "川又 政治", "role" : "翻訳"}]
}');
SQL Serverではnvarcahrまたはnvarchar(max)のデータ型でJSONを保持します。対象の列に正しいJSONデータが入っていることを確認するために、正しいJSONデータを受け取ったときに1を返す「ISJSON」関数を利用します。

* MySQL

 CREATE TABLE book (
    book_id int,
    book_json json,
    CONSTRAINT pk_book PRIMARY KEY (book_id)
);
 INSERT INTO book VALUES (
1,
'{
    "book_id" : 1,
    "title" : "キャズム",
    "publisher" : "翔泳社",
    "pages" : 360,
    "publish_dt" : "2014-10-04",
    "author" : [{"name" : "ジェフリー・ムーア", "role" : "著"},
                {"name" : "川又 政治", "role" : "翻訳"}]
}');
MySQLではJSONデータ型が準備されているので、JSONデータの格納にはそれを用います。個人的には一番分かり易いかなと思います。

4.2. フィールドの値(スカラー値)の取得

各JSONデータからbook_idとtitleというフィールドの値を取得します。

* Oracle 

// ドット表記法 (Oracle)
SELECT b.book_json.book_id, b.book_json.title FROM book b;
Oracleにはドット表記法という記法があり、(JSONデータを含む列名).(JSONフィールド名)で該当の値を取得することができます。とても簡単ですね。ただ、取得できる値は一律に文字列型になってしまいます。上の結果でBOOK_IDの出力結果が左寄せになっていることからも分かると思います。
ちなみに、テーブル別名を付けないとORA-00904エラーになります。
// JSON_VALUE関数の利用 (Oracle)
SELECT
    json_value(book_json, '$.book_id' RETURNING NUMBER) book_id,
    json_value(book_json, '$.title') title
FROM
    book
SQL:2016ではJSONデータからスカラー値を取得するJSON_VALUEという関数があります。Oracleの場合はRETURNING句を付けることで返るデータ型も指定することができます。出力結果でBOOK_IDが右寄せになっていることが確認できます。
標準とはいえ、正直面倒くさい表記法ですね。
// JSON_TABLE関数の利用 (Oracle)
SELECT
    x.*
FROM
    book b,
    json_table(b.book_json, '$'
        COLUMNS (book_id NUMBER PATH '$.book_id',
                 title   VARCHAR2(80 CHAR) PATH '$.title')) x
先のJSON_VALUEのSQL文では、JSON_VALUE関数が2回呼ばれていますが、実際にJSONデータ1つに対して解析も2回行われるそうです。無駄ですね。この非効率を回避するために、JSON_TABLEという関数を使うことができます。これもSQL:2016の一部です。上のSQL文の結果は先のクエリと同じです。
ただ、ぶっちゃけ分かり易いSQL文かというと…

* SQL Server

// JSON_VALUE関数の利用 (SQL Server)
SELECT
    json_value([book_json], '$.book_id') [book_id],
    json_value([book_json], '$.title') [title]
FROM
    [book];
SQL Serverでは、Oracleのドット表記法のような記法はなく、基本SQL:2016で定義された関数を用いてJSONデータから値を取得します。
ちなみに、SQL ServerのJSON_VALUE関数には、OracleのRETURNINGのようなデータ型を指定するオプションはありません。
// 結果をJSON形式で返す (SQL Server)
SELECT
    json_value([book_json], '$.book_id') [book_id],
    json_value([book_json], '$.title') [title]
FROM
    [book]
FOR JSON PATH, WITHOUT_ARRAY_WRAPPER;
SQL Serverでは「FOR JSON」オプションを指定することで、結果をJSON形式で返すことができます。
ただし、アプリ側がJSON形式でデータを欲しいことは多々ありますが、複数のJSONデータをカンマでつなぎ1行という形式が使いやすいかというと、どうなんでしょうね。
// OPENJSON関数の利用 (SQL Server)
SELECT
    [b].[book_id],
    [x].[key],
    [x].[key],
    [x].[value]
FROM
    [book] [b]
CROSS APPLY
    openjson([b].[book_json]) [x]
WHERE
    [x].[key] IN ('title', 'pages');
SQL ServerにはJSON_TABLE関数はありません。JSONデータを表データに変換するには代わりにOPENJSON関数を利用します。ただ、結果はいわゆる縦持ちモデルで返ってくるので注意が必要です。

* MySQL

 // -> 記法 (MySQL)
SELECT
    b.book_json->'$.book_id',
    b.book_json->'$.title'
FROM
    book b
MySQLでは(JSONデータが格納された列)->(JSONパス式)という形でデータを取得することができます。Oracleのドット表記法と似ていますが、「->」の後ろがJSONパス式であることと、出力結果にダブルクォートが付くことに違いがいあります。ダブルクォートを外す関数UNQUOTEも準備されていますが少し試してみて想定通り動かなかったです(調査が不足しているのでしょう)。
// JSON_EXTRACT関数の利用 (MySQL)
SELECT
    json_extract(b.book_json, '$.book_id') book_id,
    json_extract(b.book_json, '$.title') title
FROM
    book b
MySQLにはJSON_VALUE関数はありませんが、代わりにJSON_EXTRACTという関数が準備されています(SQL:2016標準には含まれていません)。使い方はほぼ一緒ですね。
// JSON_EXTRACTは複数項目を一度に処理でき (MySQL)
SELECT
    json_extract(b.book_json, '$.book_id', '$.title')
FROM
    book b
MySQLではJSON_EXTRACT関数の1回の呼び出しで、複数のフィールドの値を取得することができます。ただし、本当に処理が効率的に行われているかは分かりません。また、結果が配列で返ってくるのは仕方ないとは言え、あまり便利ではないですね。
今回は触れませんが、MySQLにもJSON_TABLE関数は準備されています。

4.3.  フィールドの値(オブジェクト)の取得

各JSONデータからauthor(複数の著者に関しての情報を配列で保持)の1番目の値を取得します。

* Oracle

// JSON_QUERY関数の利用 (Oracle)
SELECT json_query(book_json, '$.author[0]') from book
スカラー値(数字や文字列、日付など)ではなく、JSONオブジェクトを取得する場合は、JSON_QUERY関数を使います。

* SQL Server

// JSON_QUERY関数の利用 (SQL Server)
SELECT
    json_query([book_json], '$.author[0]')
FROM
    [book];
JSON_QUERYの使い方は、Oracleとほぼ一緒です。

* MySQL

 // JSON_EXTRACTによるJSONオブジェクトの取得(MySQL)
SELECT
    b.book_json->'$.author[0]'
FROM
    book b
Oracle/SQLServerの場合、取得対象がスカラー値かJSONオブジェクト/配列かでJSON_VALUEとJSON_QUERYを使い分ける必要がありますが、MySQLの場合はどちらでもJSON_EXTRACTもしくは「->」表記を使えます。メリデメあって良し悪しは一概には言えませんが。 

4.4. 配列からのデータ取得

著者名のリストを取得します。

* Oracle

 // 配列の取得 (Oracle)
SELECT json_query(book_json, '$.author.name' WITH WRAPPER) from book
JSON_QUERYはJSONオブジェクトだけではなく、配列も返すことができます。

* SQL Server

// 配列の取得(SQL Server) - うまくいかない
SELECT
    json_query([book_json], '$.author.name')
FROM
    [book];
これをSQL Serverでやる方法は分かりませんでした。JSONデータは所詮文字列なので頑張ればできるのですが、JSON関数をうまく使ってできないのでしょうか。

* MySQL

// 配列の取得 (MySQL)
SELECT
    b.book_json->'$.author[*].name'
FROM
    book b
MySQLでは[*]という表記をパス式で配列のすべての要素にアクセスできます。

4.5. 絞り込み

出版社(publisher)が「翔泳社」である書籍のタイトル(title)を取得します。

* Oracle

// JSON_EXISTS関数の利用 (Oracle)
SELECT b.book_json.title FROM book b
WHERE json_exists(b.book_json, '$?(@.publisher == "翔泳社")');
単純な絞り込みであればWHERE句でJSON_VALUEを使えばいい気もしますが(もちろん使えます)、OracleにはJSON_EXISTSという関数も用意されています。

* SQL Server  

// JSON_VALUE関数による絞り込み (SQL Server)
SELECT
    json_value([book_json], '$.book_id') [book_id],
    json_value([book_json], '$.title') [title]
FROM
    [book]
WHERE
    json_value([book_json], '$.publisher') = '翔泳社';
SQL ServerにはJSON_EXISTS関数はないので、JSON_VALUE関数を使って絞り込みを実装します。
単純なケースであれば、個人的にはこっちの方が分かり易い気もします。

* MySQL

// JSON_CONTAINSによる絞り込み (MySQL)
SELECT
    b.book_json->'$.book_id',
    b.book_json->'$.title'
FROM
    book b
WHERE
    json_contains(b.book_json, '"翔泳社"', '$.publisher')
MySQLでもJSON_EXTRACTもしくは「->」表記による絞り込みはできますが、JSON_CONTAINSという関数を使った絞り込みも可能です。ちなみにこの「CONTAINS」の意味ですが、「翔泳社という文字をpublisherに含む」という意味ではなく、「(複数存在するかもしれない)puslisherの中に、翔泳社に一致するフィールドが最低1つ含まれる」という意味です(今回は各JSONデータの中に1回しかpublisherが出現しないので違いが分かり辛いですが)。

4.5. 集約(配列の作成)

出版社(publisher)ごとに書籍のタイトルを集約し、配列で返します。

* Oracle

 // JSON_OJECT関数とJSON_ARRAYAGG関数の利用 (Oracle)
SELECT
    json_object(
        'publisher'  VALUE x.publisher,
        'titles'     VALUE json_arrayagg(x.title))
FROM
    book b,
    json_table(b.book_json, '$'
        COLUMNS (publisher VARCHAR2(80 CHAR) PATH '$.publisher',
                 title   VARCHAR2(80 CHAR) PATH '$.title')) x
GROUP BY
    x.publisher;
このSQL文では、データを集約し配列を生成するためのJSON_ARRAYAGG関数と、結果をJSON形式で返すために表データ⇒JSONデータの変換を行うJSON_OBJECT関数を利用しています。

*SQL Server

SQL Serverでこれを実現する方法は分かりませんでした。もちろん文字列処理をすれば可能なのは分かりますが。

* MySQL

// JSON_OBJECTとJSON_ARRAYAGGを利用 (MySQL)
SELECT
    json_object('publisher', x.publisher, 'titles', json_arrayagg(x.title))
FROM
    (
    SELECT
        b.book_json->'$.publisher' publisher,
        b.book_json->'$.title' title
    FROM
        book b
    ) x
GROUP BY
    x.publisher
MySQLには若干表記が異なりますが、Oracleと同じJSON_OBJECTとJSON_ARRAYAGG関数が用意されています。
本当は副問い合わせなしで書きたかったのですが、どうもGROUP BYの中では「->」表記はGROUP BYの中では使えないようですね(JSON_EXTRACTも)。なぜだろう…

5. まとめ

今回、Oracle、SQL Server、MySQLのJSONの扱いを比較してみましたが、扱った範囲だと、以下のような感じでしょうか。
  • OracleとMySQLは大体やりたいことができそう。あとは好みかな。
  • SQL Serverは対応しているJSON関数やJSONパス式の範囲が狭く、まだこれから。
あと、今回は索引が利用できるかなどの性能面は全く見ていないので、実際に利用するにはもう少し調査が必要でしょう。
あと、今回は基本、JSONデータから表形式データを取得するケースがメインでしたが、それは準備されている関数がJSONデータ⇒SQL値の変換関数が多いことから来ています。JSONデータをそのまま処理してJSONデータとして返すにはSQL:2016の関数群(JSON_*)を使うのはちょっと違うのでしょうね。

そういうことをするには、MySQLなどではMySQL Shell for Javascript/Pythonなど使えるのでしょうか。

2013年1月26日土曜日

KVMでRAC+DataGuadの環境を作ってみよう Part3

まだまだ終わりません。あと、タイトルにRACというキーワード加えました。

(7)共有ディスクの設定

RACはシェアードディスクタイプのクラスタなので、 dg01とdg02で共有するディスクイメージをホストOS上で作成します。サイズは50GBとします。
# dd if=/dev/zero of=/vmdata/ss01.img \
  bs=`expr 1024 \* 1024` count=`expr 50 \* 1024`
仮想OSのローカルディスク用イメージとは異なり、QCOW2形式ではなくRAW形式を用います。よく考えれば当たり前ですが、複数の仮想ゲストで動的割当てファイルを共有することはできないようです(最初は気付かずにGrid Infrastructureのroot.shで失敗しまくりました)。

次に仮想ゲストの起動オプションに共有ディスク設定を追加します。
# /usr/libexec/qemu-kvm -k ja \
  -hda /vmdata/dg01root.img \
  -hdb /vmdata/dg01swap.img \
  -drive file=/vmdata/ss01.img,cache=none \
  -boot c -m 4096 -vnc :1 \
  -net nic,macaddr:=54:52:00:12:01:01,vlan=0 \
  -net tap,ifname=tap0,script=addif.sh,downscript=no,vlan=0 \
  -net nic,macaddr:=54:52:00:12:01:02,vlan=1 \
  -net tap,ifname=tap6,script=addif.sh,downscript=no,vlan=1 \
  -net nic,macaddr:=54:52:00:12:01:03,vlan=2 \
  -net tap,ifname=tap11,script=addif.sh,downscript=no,vlan=2
 今まではディスク指定に-hdXというオプションを使ってきましたが、cache=noneを指定するために-driveオプションを利用しています。この指定がないとやはりroot.shが失敗します。

以上の設定を行い仮想ゲストdg01を起動すると、共有ディスクが/dev/sdcとして認識されます。なので同様にss02.img、ss03.imgを作成し、dg03からdg06に追加してきます。

2度書きますが、共有ディスク用のイメージでは以下の2点が重要です。これを守らなくともOracle Universal Installerによる事前チェックは通ることがあるので要注意です。
  • 動的割当を利用しない
  • cache=noneオプションでキャッシュを無効にする 
 (8)Oracleインストール前のゲストOSの設定

インストール要件をクリアしていくために設定を入れていきますが、ここは簡単に。
  •  必要なRPMパッケージのインストールに関してはOracle Linux 6 Update3のDVDの中にある「oracle-rdbms-server-11gR2-preinstall-1.0-6.el6.x86_64.rpm」をyum経由でインストールすれば自動でインストールされるっぽい。マニュアルに比べるとbcコマンドやXクライアントも追加で入ります。ちなみに、oracleユーザも勝手に作ってくれるけどuid=54321になるのが気に入らないので、後で削除。
  • NTPで時刻合わせ。/etc/sysconfig/ntpdで-xオプションでslewモードにすることを忘れずに(インストーラがチェックしてくれますが)。
  • ユーザ作成とディレクトリ設定は以下を参考に。あとは、パスワード設定とSSH透過ログインの設定を。
groupadd -g 1000 oinstall
groupadd -g 1031 dba
groupadd -g 1020 asmadmin
groupadd -g 1021 asmdba
useradd -u 1100 -g oinstall -G asmadmin,asmdba grid
useradd -u 1101-g oinstall  -G dba,asmdba oracle
mkdir -p /u01/app/grid
mkdir -p /u01/app/11.2.0/grid
chown -R grid:oinstall /u01
mkdir -p /u01/app/oracle
chown oracle:oinstall /u01/app/oracle
chmod -R 775 /u01
  • 共有ディスクのオーナー、パーミッション設定は【こちら】を参考に/etc/udev/rules.d/99-oracle-asmdevicesruleに以下の内容を記述。ちなみにこの設定方法はRHEL6系に固有らしく、後のインストーラによる要件チェックでは正しく設定してもWaringを喰らうようです。
KERNEL=="sdc"、SUBSYSTEM=="block"、ENV{DEVTYPE}=="disk", NAME+="oracleasm/disk1", OWNER="grid", GROUP="asmadmin", MODE="0660"
  •  ネットワークでは/etc/hostsとデフォルトゲートウェイの設定を。今回はSCAN用にDNSは利用しないのでSCAN用IPアドレスは1つのみ指定します。
  • /etc/sysctl.confと/etc/security/limits.confの設定は、今回はインストーラが作成する修正スクリプトに任せます(lmits.confの設定はログインし直さないと有効にならないので、本来は修正スクリプトに頼らず、事前設定が良いみたいですが)。
以上の設定を6台全部に行います。実際は、1台で設定した後に前回記述した方法で仮想ゲストをクローニングしています。

ASMLibやhanguptimer、MTUなど設定すべき項目はまだまだありますが、今回は無視します。

(9) Oracleのインストール・DBの作成

環境が正しく整えば、インストール&DB作成は楽勝ですね。dg01&dg02にRAC環境を作ります。
  • Grid Infrastructureの拡張インストール(クラスタ名:cluster01)
  • Databaseのソフトウェアのみのインストール
  • Examplesのインストール(自分の趣味です)
  • RACデータベースの作成(DB名:ora11ga、インスタンス名:ora11ga1/ora11ga2)

今回はここまでです。次回はフィジカルスタンバイデータベースを作成していきます。もうKVMの話は出てこないかもです。

2012年8月4日土曜日

ZFS Storage Appliance SimulatorでOracle11gのハイブリッド列圧縮は使えるか?

【前回】は下調べせずに撃沈したが、今回は【こちら】で事前に可能だという情報を得た上でのトライ。
結果としては、以下の通りOK。

SQL> CREATE TABLE sales_qh COMPRESS FOR QUERY HIGH AS SELECT * FROM sh.sales WHERE 1 = 0;

表が作成されました。

経過: 00:00:00.82
SQL> INSERT /* APPEND */ INTO sales_qh SELECT * FROM sh.sales;

918843行が作成されました。

経過: 00:00:12.90
SQL> SELECT compression, compress_for FROM user_tables WHERE table_name = 'SALES_QH';

COMPRESS COMPRESS_FOR
-------- ------------
ENABLED  QUERY HIGH

経過: 00:00:00.05
SQL> SELECT segment_name, sum(bytes) / 1024 / 1024 mb FROM dba_extents
2 WHERE segment_name IN ('SALES', 'SALES_QH')
3 GROUP BY segment_name;

SEGMENT_NAME         MB
--------------- -------
SALES               128
SALES_QH             32

経過: 00:00:46.97
SQL>
圧縮率に関しては、データが少なすぎてあまりHCCの効果が。。。
構築の注意点・問題点をいくつか。
  • Solaris x86-64 Oracle11.2.0.3、ZSA Simulator共にメモリ1GBで動作可能。ただし、DBインストール&作成時や後に述べるパッチ適用時は一時的に2GBに増強することが好ましい。(今回は1GBでパッチ適用してしまいスワップストリームに嵌ることにorz)
  •  ZSA Simulator上のフォルダにno_root_squash設定したかったんだけど、設定箇所が見つからずchownできなかった(単に調査不足)。そのためフォルダ作成時にユーザ、グループにoracleユーザのuid、dbaグループのgidを指定して回避。
  • DBサーバ上のmountオプションは以下。このストレージはnointrなのね。あと、なぜ未だにバージョン3しばり?
rw,bg,hard,nointr,rsize=32768,wsize=32768,proto=tcp,vers=3,actimeo=0
  •  11.2.0.3.0でZSAをNFSマウントしてHCC使う場合は、DBソフトにパッチが必要(サポート情報なので詳細はMOSを検索してください)。おそらくPillarをASMグリッドディスクに使う場合は同じパッチをGrid Infrastructureに適用することになると思う。11.2.0.3.1ならパッチは要らないと思う。
  • DirectNFSの設定必要。とはいっても、Oracle停止&NFSアンマウント状態で以下を実行するだけですが。
$ cd $ORACLE_HOME/lib
$ mv libodm11.so libodm11.so_orig
$ ln -s libnfsodm11.so libodm11.so
HCCには関係ないけど、ZSAの管理画面はWebブラウザでリアルタイムでCPU使用率やネットワーク利用状況が表示されて面白い。あのアプリがどんなライブラリを使っているか興味あるなぁ。