tag:blogger.com,1999:blog-45463653046912853082024-03-18T12:03:14.234+09:00PostgreSQL Deep DiveSatoshi Nagayasuhttp://www.blogger.com/profile/09706093898823214408noreply@blogger.comBlogger101125tag:blogger.com,1999:blog-4546365304691285308.post-67391480133300540002019-12-01T12:11:00.000+09:002019-12-02T00:21:31.069+09:00Jupyter+Pandasを使ったPostgreSQLパフォーマンス分析<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgbnEuTAkaGO276OHrvLkc4ftsTr22JQOhCmqQaOZCnadNWfbSooe00-n4nHITzo_0Ao3UTAPaKxBEtClMc8gYB5d4RU9AZYhkqTs85pdkmFEeadura2y8D_0T4dMdezE0CVXR4yI4eKFw/s1600/0.png" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgbnEuTAkaGO276OHrvLkc4ftsTr22JQOhCmqQaOZCnadNWfbSooe00-n4nHITzo_0Ao3UTAPaKxBEtClMc8gYB5d4RU9AZYhkqTs85pdkmFEeadura2y8D_0T4dMdezE0CVXR4yI4eKFw/s320/0.png" width="320" height="233" data-original-width="626" data-original-height="456" /></a></div>本記事は <a href="https://qiita.com/advent-calendar/2019/postgresql">PostgreSQL Advent Calendar 2019</a> の1日目の記事です。初日から遅れ気味ですすみません。。<br/>
<br/>
久しぶりの記事ですが、最近はPostgreSQLをゴリゴリと触る感じでもなくなってきているため、本記事もゆるめの感じでお送りしたいと思います。
<h4 class="post-body">■PostgreSQLの「パフォーマンス分析」とは</h4><br/>
PostgreSQLのパフォーマンス分析は、ざっくり言って、以下のようなステップで進められます。(PostgreSQLには限らないと思いますが)<br/>
<ul>
<li>パフォーマンスの状況から、課題について仮説を設定する。</li>
<li>パフォーマンスに関連する何の情報を収集するかを決める。</li>
<li>情報を収集する。</li>
<li>収集した情報を加工し、分析しやすい形式に整える。</li>
<li>分析し、仮説を検証、ないしは何かを発見する。</li>
<li>より深堀り、確証を高めるために、再度情報集をしたり、データを加工、分析したりする。</li>
<li>何か対策を打って、その結果を再度分析して、従前と比較する。</li>
</ul>
ある程度PostgreSQLに詳しい人は、「仮説の設定」や「どこから情報を取得するか」はさくっと決められると思いますが、情報を収集したり分析に適した形に加工したりするのはそれなりの手間と時間がかかるものです。<br/>
<br/>
しかも、ある程度探索的な分析を行わざるを得ないため、データやグラフの形式を最初から決めておいたとしても、パフォーマンス分析がディープになればなるほど、それが現実的に役に立つかどうかは微妙になっていきます。(なので、最終的には、あらゆるグラフを網羅的に出力するレポートを作成する羽目になります)<br/>
<br/>
そのため「Excel最強説」が流れるわけですが、再現可能性や繰り返しの作業についてはやはりいろいろと難がありますので、スクリプトで処理したくなってきます。
<h4 class="post-body">■「Jupyter + Pandas」を使ってパフォーマンス分析</h4><br/>
<a name='more'></a>
探索的かつスクリプトでのデータ処理、と言えばJupyterの出番でしょう。数値データをあれこれ捏ねたり捻ったりするのであればPandasも使うと便利そうです。<br/>
<br/>
というわけで、JupyterとPandasを使ってpg_stat_statementsのデータを分析してみようと思います。<br/>
<br/>
今回の目的としては、<br/>
<ul>
<li>複数時点のpg_stat_statementsのデータから、パフォーマンスが悪化しているクエリを抽出したい。</li>
<li>それ以外にもいろいろな分析ができるようにしておきたい。</li>
</ul>
あたりを設定したいと思います。<br/>
<br/>
実現するまでの全体の流れとしては、<br/>
<ul>
<li>pg_stat_statementsのデータを時間をおいて複数回取得する。</li>
<li>その時のタイムスタンプも取得する。</li>
<li>取得したデータをPandasのDataframeに読み込んで分析をする。</li>
</ul>
としたいと思います。<br/>
<br/>
今回使用したJupyter Notebookは以下にあります。<br/>
<ul>
<li><a href="https://gist.github.com/snaga/27f177ebf53087ae75e9a43b2ad328ec">pg_stat_statements_using_pandas.ipynb</a></li>
</ul>
<h4 class="post-body">■データ収集</h4><br/>
まず、データを収集します。<br/>
<br/>
収集するのは、pg_stat_statementsビューの内容と、タイムスタンプ(CURRENT_TIMESTAMP)の情報です。<br/>
<br/>
psqlコマンドを使って、<br/>
<pre style="overflow-x: scroll; white-space: pre">
select now();
select * from pg_stat_statements;
</pre>
<br/>
として取得します。<br/>
<br/>
取得したデータは、psqlで取得するテキスト形式のまま、以下のようにPythonの変数としてノートブック上に貼り付けます。ぺたり。<br/>
<br/>
<pre style="overflow-x: scroll; white-space: pre">
ts1 = '2019-11-30 18:41:46.290788+09'
stmt1 = """
userid | dbid | queryid | query | calls | total_time | min_time | max_time | mean_time | stddev_time | rows | shared_blks_hit | shared_blks_read | shared_blks_dirtied | shared_blks_written | local_blks_hit | local_blks_read | local_blks_dirtied | local_blks_written | temp_blks_read | temp_blks_written | blk_read_time | blk_write_time
--------+-------+------------+------------------------------------------------------------------------------------------------------------------------------------------------------------+-------+---------------------+----------------------+----------------------+----------------------+----------------------+--------+-----------------+------------------+---------------------+---------------------+----------------+-----------------+--------------------+--------------------+----------------+-------------------+---------------+----------------
10 | 12401 | 2111245140 | begin | 2 | 0.00213333060267016 | 0.000853332241068065 | 0.0012799983616021 | 0.00106666530133508 | 0.000213333060267016 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0
10 | 12401 | 3571892116 | BEGIN; | 8000 | 6.89236451110624 | 0.000426666120534032 | 0.0277332978347121 | 0.000861545563888342 | 0.000442352042060711 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0
(snip)
10 | 12401 | 927919161 | /*pga4dash*/ +| 228 | 54.0415308268406 | 0.139093155294095 | 0.753492368863101 | 0.237024258012459 | 0.0803793833218495 | 228 | 3034 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0
| | | SELECT +| | | | | | | | | | | | | | | | | | |
| | | (SELECT count(*) FROM pg_stat_activity WHERE datname = (SELECT datname FROM pg_database WHERE oid = ?)) AS "Total", +| | | | | | | | | | | | | | | | | | |
| | | (SELECT count(*) FROM pg_stat_activity WHERE state = ? AND datname = (SELECT datname FROM pg_database WHERE oid = ?)) AS "Active", +| | | | | | | | | | | | | | | | | | |
| | | (SELECT count(*) FROM pg_stat_activity WHERE state = ? AND datname = (SELECT datname FROM pg_database WHERE oid = ?)) AS "Idle" | | | | | | | | | | | | | | | | | | |
"""
</pre>
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEimmAuuBn1zuvLV4bwBwv-ENpVelhBZKBNBWths_vDX4Zj22pyJh9nZ-DUpncN-9HiByWLoVSTpGUmHc6W3cj_W5OvoUEuozUz8iWinf-7K-lcEDaQ1p45BIiAxh2b8vENmZ-tygZraoVs/s1600/1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEimmAuuBn1zuvLV4bwBwv-ENpVelhBZKBNBWths_vDX4Zj22pyJh9nZ-DUpncN-9HiByWLoVSTpGUmHc6W3cj_W5OvoUEuozUz8iWinf-7K-lcEDaQ1p45BIiAxh2b8vENmZ-tygZraoVs/s640/1.png" width="640" height="289" data-original-width="967" data-original-height="437" /></a></div>
<br/>
何度か取得し、変数名を変えて貼り付けます。
<h4 class="post-body">■PandasのDataframeに変換する</h4><br/>
次に、これらのテキストの出力をPandasのDataframeに変換します。(pgss_to_df関数の実装についてはノートブックを参照してください)<br/>
<br/>
今回は、pgbenchを何度か実行し、その間に3回ほどpg_stat_statementsビューの情報を取得して使っています。<br/>
<pre style="overflow-x: scroll; white-space: pre">
pgss1 = pgss_to_df(ts1, stmt1)
pgss2 = pgss_to_df(ts2, stmt2)
pgss3 = pgss_to_df(ts3, stmt3)
pgss = pd.concat([pgss1,pgss2,pgss3], ignore_index=True).sort_values(by=['queryid', 'datetime'])
</pre>
<br/>
すると以下のようなDataframeの形式になります。<br/>
<br/>
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi5hYrSNmoQUyA0mFVlsTnjHHN4lw4hsKvLQEJxEpkMceLGShHqFZWM15Sq6gu7CRTFb_29aPiGZYPtOTOLg8iRVWRzUQL8fecuLCvbeObVM8L_6_99lKUEVtjEAkUq9P4vmKGo0_UnBYE/s1600/2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi5hYrSNmoQUyA0mFVlsTnjHHN4lw4hsKvLQEJxEpkMceLGShHqFZWM15Sq6gu7CRTFb_29aPiGZYPtOTOLg8iRVWRzUQL8fecuLCvbeObVM8L_6_99lKUEVtjEAkUq9P4vmKGo0_UnBYE/s640/2.png" width="640" height="298" data-original-width="1231" data-original-height="574" /></a></div>
<h4 class="post-body">■特定のクエリの平均処理時間の推移を見る</h4><br/>
さて、Dataframeの準備ができたところで、実際にデータを見てみましょう。<br/>
<br/>
まず、特定のクエリの平均処理時間の推移を見てみます。(find_by_query関数の実装についてはノートブックを参照してください)<br/>
<br/>
<pre style="overflow-x: scroll; white-space: pre">
find_by_query(pgss, 'UPDATE pgbench_tellers')
</pre>
<br/>
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj6CW-9uC4QleK18vS9-M5lItFgIlMMvs_L2jOkLjN20j-wEv1TApTJFfKJm8VFvPEnNxu7-rjSFHMgcA2WZH4OHbImPsOUHdToZ5v3Xcawg7zpdCtwdMo9bW3wTSPYIxthhq43BUg2Ob4/s1600/3.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj6CW-9uC4QleK18vS9-M5lItFgIlMMvs_L2jOkLjN20j-wEv1TApTJFfKJm8VFvPEnNxu7-rjSFHMgcA2WZH4OHbImPsOUHdToZ5v3Xcawg7zpdCtwdMo9bW3wTSPYIxthhq43BUg2Ob4/s640/3.png" width="640" height="246" data-original-width="1166" data-original-height="449" /></a></div>
<br/>
mean_timeのカラムを見ると、pgbench_tellersテーブルを更新するクエリの平均処理時間が、少しずつ長くなっていることが分かるかと思います。(13.18ミリ秒→13.30ミリ秒→13.31ミリ秒)
<h4 class="post-body">■平均処理時間が10%以上悪化したクエリを抽出する</h4><br/>
次に、平均処理時間が10%以上悪化したクエリを抽出します。<br/>
<br/>
まず、最初と最後のタイムスタンプのレコードを、それぞれmin/maxとして、queryidカラムとmean_timeカラムを抽出します。<br/>
<pre style="overflow-x: scroll; white-space: pre">
# average query time at the start.
df_start = pgss[pgss.datetime == pgss['datetime'].min()].loc[:,['queryid', 'mean_time']]
# average query time at the end.
df_end = pgss[pgss.datetime == pgss['datetime'].max()].loc[:,['queryid', 'mean_time']]
</pre>
<br/>
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhmVvAFonwzxzNU1_R4rGMg2uO02urPQGjfd6C8Rnl4vLP59nTNK-GU2Wy-7V-s276MY9gWdynkKTRJq1dWcfLIg-H6rCsd1G9MD7pduemgIa4CMdr48EtHhJwp4eb6G1VEul3sDwTVz-w/s1600/4.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhmVvAFonwzxzNU1_R4rGMg2uO02urPQGjfd6C8Rnl4vLP59nTNK-GU2Wy-7V-s276MY9gWdynkKTRJq1dWcfLIg-H6rCsd1G9MD7pduemgIa4CMdr48EtHhJwp4eb6G1VEul3sDwTVz-w/s640/4.png" width="640" height="242" data-original-width="1010" data-original-height="382" /></a></div>
<br/>
次に、これらをqueryidで結合し、最後の処理時間が最初の処理時間に比べて10%以上長くなっているクエリを抽出します。<br/>
<br/>
<pre style="overflow-x: scroll; white-space: pre">
# join both using the queryid.
df = df_start.join(df_end.set_index('queryid'), on='queryid', lsuffix='_start', rsuffix='_end')
# select queries that slow down more than 10%
df = df[df.mean_time_end > df.mean_time_start * 1.1]
</pre>
<br/>
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjUHJlExWFhUjp1G-CbA6XOigPGG877OgVraH9y-SFaGnUtxmuKu06ADF1BAY-W1Qk0SI_WNIh4anehFVYRaEhmdTgzyez4u7ljpsC6TYza5a3qj_M6avWswFeCZXgT6PcBijdlP-4vp3U/s1600/5.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjUHJlExWFhUjp1G-CbA6XOigPGG877OgVraH9y-SFaGnUtxmuKu06ADF1BAY-W1Qk0SI_WNIh4anehFVYRaEhmdTgzyez4u7ljpsC6TYza5a3qj_M6avWswFeCZXgT6PcBijdlP-4vp3U/s640/5.png" width="640" height="249" data-original-width="1028" data-original-height="400" /></a></div>
<br/>
そして、最後にクエリ文字列と結合します。<br/>
<pre style="overflow-x: scroll; white-space: pre">
# join query strings using the queryid
df.set_index('queryid').join(pgss.loc[:,['queryid','query']].set_index('queryid')).drop_duplicates()
</pre>
<br/>
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg7K5O_xL_VJ_j_XCOqO4zqTODqncjRbf6HLfFBkyBYRxrEiRHjfIi0rjezVWr1eDSGTIyqX224Xgg7NNfFWTX8jkdcn4XqiAkqpckUU8V41dxjF7536Jn4cFCIYerCXGbwOc4eZCSkj98/s1600/6.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg7K5O_xL_VJ_j_XCOqO4zqTODqncjRbf6HLfFBkyBYRxrEiRHjfIi0rjezVWr1eDSGTIyqX224Xgg7NNfFWTX8jkdcn4XqiAkqpckUU8V41dxjF7536Jn4cFCIYerCXGbwOc4eZCSkj98/s640/6.png" width="640" height="300" data-original-width="1092" data-original-height="512" /></a></div>
<br/>
これで、処理時間が10%以上伸びているクエリを抽出することができるようになりました。
<h4 class="post-body">■まとめ</h4><br/>
以上、今回はJupyterとPandasを使って、PostgreSQLのパフォーマンス分析のためのデータ加工や簡単な分析を行ってみました。<br/>
<br/>
今回着手するに当たって、最初はmatplotlibを使った可視化なども考えていたのですが、実際にパフォーマンス分析の実務を考えると、安直な可視化よりは基本的なデータ操作、オペレーションを関数にまとめておくことの方が、生産性の観点ではより役に立つのではないかと思うようになりました。(もちろん、トレンド分析などにおける可視化の役割は否定するものではありませんが)<br/>
<br/>
実際のパフォーマンス分析の現場では、さまざまなデータの加工や集計作業が必要になります。一旦Pandasに取り込んでおけば、さまざまなデータ処理を簡単に行うことができ、いろいろ便利に使えるのではないかと思います。(Pandasを使いこなせる人であれば、という条件付きですが・・・私はPandasは初心者です・・・)<br/>
<br/>
今後は、今回のような処理をうまくwrapするモジュールやクラスに整理して、Jupyterからimport一発で簡単に使えるようなものを目指してみようと思います。<br/>
<br/>
では、また。<br/>
<br/>
PostgreSQL Advent Calendar 2019、明日の担当は <a href="https://twitter.com/yuina1056">@yuina1056</a> さんです。お楽しみに。Satoshi Nagayasuhttp://www.blogger.com/profile/09706093898823214408noreply@blogger.com0tag:blogger.com,1999:blog-4546365304691285308.post-66298858632256768062019-02-05T11:03:00.000+09:002019-02-05T11:06:04.633+09:00tablelog extension を使ってDB移行に必要なテーブルの更新差分のログを取得する<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhueNwx5ziXuHRZUJkbF0Mov2lLtIlmtqUakVBB_n0B13tUhtb4r1HNP0e1BPt0OQL7PlJ98FKc5wHRZnRiJDHnn4J-cjHMgKU9zZIAcWgSuucNHSP0uWl_bQlRTGr7k7GgrU-rr7RWgTM/s1600/fig.png" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhueNwx5ziXuHRZUJkbF0Mov2lLtIlmtqUakVBB_n0B13tUhtb4r1HNP0e1BPt0OQL7PlJ98FKc5wHRZnRiJDHnn4J-cjHMgKU9zZIAcWgSuucNHSP0uWl_bQlRTGr7k7GgrU-rr7RWgTM/s320/fig.png" width="320" height="159" data-original-width="1368" data-original-height="678" /></a></div>先日開催されたPostgreSQLアンカンファレンスで tablelog という extension の話をしたのですが、本エントリでは改めてその紹介をさせていただこうと思います。
<ul>
<li>第10回PostgreSQLアンカンファレンス - Togetter<br/><a href="https://togetter.com/li/1315752">https://togetter.com/li/1315752</a></li>
</ul>
<h4 class="post-body">■DB移行やメジャーバージョンアップの時、、、</h4><br/>
皆さんは、<br/>
<ul>
<li>システム更改によるDB移行</li>
<li>PostgreSQLのバージョンアップ</li>
<li>特定のテーブルだけ別インスタンスにコピーしたい</li>
</ul>
といったことをしたい場合に、どのように対処しているでしょうか?<br/>
<ul>
<li>その方式は?</li>
<li>ツールは何を使う?</li>
<li>ダウンタイムは?</li>
<li>DBaaSの場合はどうする?</li>
</ul>
場合によって変わってくるかと思いますが、皆さんはどのように対処しているでしょうか?<br/>
<br/>
もっともシンプルな方法は Dump & Restore だと思いますが、データベースの規模が大きくなってきた状態だと非常に時間がかかる場合があり、単純な Dump & Restore だと数日間データベースを停止しなければならない、といった見積もりになることもあります。
<h4 class="post-body">■更新差分だけを取得・適用して追い付きたい</h4><br/>
そういう状況で次に考えるのは、「データベースを一旦コピーしておいて、後から更新差分だけ適用して追い付きたい」という方式です。<br/>
<br/>
<a name='more'></a>
以下の図で言うと、<br/>
<ul>
<li>「インスタンスA」から「インスタンスB」に「テーブル1」を移行しようとする場合に、</li>
<li>① 稼働系のデータベースに通常の更新処理が行われている間、</li>
<li>② 更新処理をログとして取得しておき、</li>
<li>③ 更新が行われているかどうかに関わらずデータベースをコピーし、</li>
<li>④ 稼働系のデータベースへの更新を一時的に停止して、</li>
<li>⑤ 蓄積しておいた更新ログを新しいデータベースに適用して最新化し、</li>
<li>⑥ 新しいデータベースを稼働系にする切り替えを行う</li>
</ul>
という流れです。<br/>
<br/>
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgg7-xvAi9MX57XgpSiUxait07_uAwtTMShRpZqMK6zA49RBBjQHJu7H9ALTrY7i9O2XlKYwifgmmz1nvx4WKNL6mzrW307z1419ZcrARAcwF_2Mej-mOkm4n6GcQ5lnneKqiTNnofRkN0/s1600/fig.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgg7-xvAi9MX57XgpSiUxait07_uAwtTMShRpZqMK6zA49RBBjQHJu7H9ALTrY7i9O2XlKYwifgmmz1nvx4WKNL6mzrW307z1419ZcrARAcwF_2Mej-mOkm4n6GcQ5lnneKqiTNnofRkN0/s640/fig.png" width="640" height="317" data-original-width="1368" data-original-height="678" /></a></div>
<br/>
この時必要とされる機能は、②の「更新処理をログとして取得しておく」という機能になります。<br/>
<br/>
「更新処理のログ」というのは、要するにINSERT/UPDATE/DELETEに関する情報のことで、<br/>
<ul>
<li>どのようなレコードをINSERTしたのか?</li>
<li>どのレコードをどのようにUPDATEしたのか?</li>
<li>どのレコードをDELETEしたのか?</li>
</ul>
といった情報を蓄積できれば、後からいろいろ活用できるはず、と言うことになります。
<h4 class="post-body">■PostgreSQLで更新処理のログを取得するには</h4><br/>
PostgreSQLで更新処理のログを取得するには、現時点では大きく2つの方法があります。「Logical Decoding」を使う方法と「テーブルトリガ」を使う方法です。<br/>
<br/>
Logical Decodingを使う方法は新しくてPostgreSQLらしい洗練された方式ではあるのですが、バージョンや稼働環境を選ぶのと、PostgreSQLエンジニア以外に使ってもらうにはちょっとハードルが高くなるかもしれません。<br/>
<br/>
なお、Logical Decodingについては、以前書いた以下のエントリを参考にしてください。<br/>
<ul>
<li>PostgreSQL Deep Dive: Logical Decodingを使ったCDC(Change Data Capture)の実現方法を考えてみる <br/><a href="http://pgsqldeepdive.blogspot.com/2016/12/logical-decoding-change-data-capture.html">http://pgsqldeepdive.blogspot.com/2016/12/logical-decoding-change-data-capture.html</a></li>
</ul>
テーブルトリガを使う方法は、昔からある伝統的な方法で特に新しくも無いのですが、誰でも簡単に理解できて(ツールさえあれば)使える方法であると言えるでしょう。<br/>
<br/>
実際、以下のようなエントリを書いたことがありました。<br/>
<ul>
<li>PostgreSQL Deep Dive: tablelogでテーブルの更新差分を取得する <br/><a href="http://pgsqldeepdive.blogspot.com/2012/12/tablelog.html">http://pgsqldeepdive.blogspot.com/2012/12/tablelog.html</a></li>
</ul>
但し、上記のエントリで使った tablelog は C 言語で実装されていてビルドやサーバへのデプロイが必要なため、今日的な DBaaS 環境では使うことができません。<br/>
<br/>
そのため、改めて(DBaaS環境でも)テーブル更新差分を取得することができる extension を作ることにしました。
<h4 class="post-body">■tablelog pl/v8版</h4><br/>
さて、先に要件だけ書き出してしまうと、今回は以下のような要件で開発しました。<br/>
<ul>
<li>DBaaS環境で利用できること(PLなんちゃら、で実装されていること)</li>
<li>モダンな言語で実装されていること</li>
<li>extension としてパッケージングされていること</li>
</ul>
というわけで、とりあえずは RDS for PostgreSQL と Azure Database for PostgreSQL をターゲットとして pl/v8 で実装することにしました。<br/>
<br/>
実は、「extension としてパッケージング」と「DBaaS環境で利用できること」は背反する条件だったりするのですが(extensionはサーバにデプロイが必要なので)、この点については今回は別の方法で逃げることにしました。<br/>
<br/>
pl/v8 版の tablelog extension は以下のレポジトリから取得できます。<br/>
<ul>
<li>snaga/tablelog: A PostgreSQL extension for capturing table modifications with table trigger <br/><a href="https://github.com/snaga/tablelog">https://github.com/snaga/tablelog</a></li>
</ul>
なお、pl/pgsqlではなくpl/v8を使ったのは、トリガーの内部で受け取ったレコードのカラム名を取得する時に、連想配列のキーとして取得できるためです。<br/>
<pre style="overflow-x: scroll; white-space: pre">
new_cols = Object.keys(NEW);
old_cols = Object.keys(OLD);
</pre>
のようなカラム名の取り出し方は pl/pgsql では実現できないのですが、pl/v8(やpl/perl)だとこれが可能なので、トリガー関数を汎用的に作成することができるようになります。
<h4 class="post-body">■tablelogのインストール</h4><br/>
詳細は README に記載していますが、スタンドアロンのPostgreSQLの場合は create extension コマンドで、<br/>
<pre style="overflow-x: scroll; white-space: pre">
env USE_PGXS=1 make install
psql -c 'create extension plv8' dbname
psql -c 'create extension tablelog' dbname
</pre>
<br/>
DBaaS の場合はSQLファイルを編集して(\echo行をコメントアウトして) psql コマンドで流し込んでください。<br/>
<pre style="overflow-x: scroll; white-space: pre">
vi tablelog--X.X.sql
psql -c 'create extension plv8' dbname
psql -f tablelog--X.X.sql dbname
</pre>
<br/>
<h4 class="post-body">■tablelogの使い方</h4><br/>
まず、テーブルを作成して、tablelog_enable_logging() 関数でログ取得を有効化します。<br/>
<pre style="overflow-x: scroll; white-space: pre">
testdb=# create table t (uid integer primary key, uname text);
CREATE TABLE
testdb=# select tablelog_enable_logging('public', 't');
tablelog_enable_logging
-------------------------
t
(1 row)
testdb=#
</pre>
<br/>
するとテーブルにトリガが設定されます。<br/>
<pre style="overflow-x: scroll; white-space: pre">
testdb=# \d t
Table "public.t"
Column | Type | Collation | Nullable | Default
--------+---------+-----------+----------+---------
uid | integer | | not null |
uname | text | | |
Indexes:
"t_pkey" PRIMARY KEY, btree (uid)
Triggers:
public_t_trigger AFTER INSERT OR DELETE OR UPDATE ON t FOR EACH ROW EXECUTE PROCEDURE tablelog_logging_trigger('uid')
testdb=#
</pre>
<br/>
この状態でテーブルのレコードを更新(INSERT/UPDATE/DELETE)すると、 __table_logs__ テーブルにログが記録されます。<br/>
<pre style="overflow-x: scroll; white-space: pre">
testdb=# insert into t values ( 1, 'name 1');
INSERT 0 1
testdb=# select * from __table_logs__ ;
ts | txid | dbuser | schemaname | tablename | event | col_names | old_vals | new_vals | key_names | key_vals | status
----------------------------+--------+----------+------------+-----------+--------+-------------+-------------+--------------+-----------+----------+--------
2019-02-05 10:07:45.868346 | 244086 | postgres | public | t | INSERT | {uid,uname} | | {1,"name 1"} | {uid} | {1} | 0
(1 row)
testdb=#
</pre>
<br/>
UPDATE/DELETEでも同様に記録されます。<br/>
<pre style="overflow-x: scroll; white-space: pre">
testdb=# update t set uname = 'uname 11';
UPDATE 1
testdb=# delete from t;;
DELETE 1
testdb=# select * from __table_logs__ ;
ts | txid | dbuser | schemaname | tablename | event | col_names | old_vals | new_vals | key_names | key_vals | status
----------------------------+--------+----------+------------+-----------+--------+-------------+----------------+--------------+-----------+----------+--------
2019-02-05 10:07:45.868346 | 244086 | postgres | public | t | INSERT | {uid,uname} | | {1,"name 1"} | {uid} | {1} | 0
2019-02-05 10:28:54.124243 | 244088 | postgres | public | t | UPDATE | {uname} | {"name 1"} | {"uname 11"} | {uid} | {1} | 0
2019-02-05 10:29:13.115856 | 244090 | postgres | public | t | DELETE | {uid,uname} | {1,"uname 11"} | | {uid} | {1} | 0
(3 rows)
testdb=#
</pre>
<br/>
ログテーブルの構造の詳細については README を参照してください。<br/>
<ul>
<li>tablelog/README.md at master · snaga/tablelog <br/><a href="https://github.com/snaga/tablelog/blob/master/README.md">https://github.com/snaga/tablelog/blob/master/README.md</a></li>
</ul>
<h4 class="post-body">■まとめ</h4><br/>
というわけで、本エントリではテーブルの更新差分をトリガーで取得するための汎用的なextension「tablelog」を紹介しました。<br/>
<br/>
データベースで扱うデータ量が膨らむ一方の現在、アプリケーション更改におけるデータベース移行や、データベースのメジャーバージョンアップによるデータベース移行など、さまざまなところで「ダウンタイムを最小化してデータベースを移行したい」というニーズは高まる一方のように思います。<br/>
<br/>
一方で、DBaaS環境に代表されるように、さまざまな制約の中でこういった移行作業を実現しなければならない現実もあります。<br/>
<br/>
ぜひ、本エントリで紹介した tablelog のようなツールを使いこなして、DBaaS環境であっても、安全、確実、かつ最小のダウンタイムで実現していただければと思います。<br/>
<br/>
では。<br/>
Satoshi Nagayasuhttp://www.blogger.com/profile/09706093898823214408noreply@blogger.com0tag:blogger.com,1999:blog-4546365304691285308.post-21896527830364571052018-12-24T00:13:00.000+09:002018-12-25T08:04:37.596+09:00カラムナーDB拡張 cstore_fdw とその性能評価<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEizMp3Xypihn1-V0FlfxFVur3nLui5eLXoNGPSY3LCnVZ6QkTNrYvhMuwc3ePJfYz0yHlTojf8qpcCmj8V_bps7RRrSPHU6VADyixcOPEMDoaJBYZY-7uLWGSjI0Icg9G6XopVnnphW29Q/s1600/comparison2.png" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEizMp3Xypihn1-V0FlfxFVur3nLui5eLXoNGPSY3LCnVZ6QkTNrYvhMuwc3ePJfYz0yHlTojf8qpcCmj8V_bps7RRrSPHU6VADyixcOPEMDoaJBYZY-7uLWGSjI0Icg9G6XopVnnphW29Q/s320/comparison2.png" width="320" height="158" data-original-width="1320" data-original-height="651" /></a></div>本エントリは <a href="https://qiita.com/advent-calendar/2018/postgresql">PostgreSQL Advent Calendar 2018</a> の Day24 の記事です。<br/>
<br/>
昨日の記事は <a href="https://qiita.com/kabaome">@kabaome</a> さんによる <a href="http://kyabatalian.hatenablog.com/entry/2018/12/23/000000">拡張統計情報とテーブル結合</a> でした。<br/>
<br/>
本エントリでは、PostgreSQLのカラムナーDB拡張である cstore_fdw について、その基本的な使い方から、 DBT-3 のスキーマとクエリを使ってベンチマークをしてみた結果を解説してみます。<br/>
<br/>
とは言え、私自身、cstore_fdw をそれなりに使ったのはこれが初めてですので、あまり深く踏み込めていないところもあるかと思いますが、そういったところがありましたら、コメント欄や Twitter などで補足いただけると助かります。
<h4 class="post-body">■cstore_fdw とは</h4><br/>
cstore_fdw は Citus Data によって開発されているオープンソースの PostgreSQL 拡張で、PostgreSQL 本体に手を加えなくてもカラムストア型(カラムナー型)の備えたテーブルを利用することができるようになるものです。<br/>
<ul>
<li><a href="https://github.com/citusdata/cstore_fdw">https://github.com/citusdata/cstore_fdw</a></li>
</ul>
cstore_fdw では、テーブルを外部テーブルとして定義することによって、 PostgreSQL のオリジナルのストレージ構造をバイパスして、独自のストレージフォーマットを持つテーブルを保持することができるようになっています。<br/>
<a name='more'></a>
<h4 class="post-body">■cstore_fdw のセットアップ</h4><br/>
cstore_fdw のインストールの手順は、他のエクステンションや FDW と変わりません。Github から clone してきて、 make install します。
<pre style="overflow-x: scroll; white-space: pre">
# git clone https://github.com/citusdata/cstore_fdw.git
# env USE_PGXS=1 make install
</pre>
<br/>
実際にデータベースに組み込む前に postgresql.conf の shared_prealod_libraries に cstore_fdw を設定しておく必要があります。
<pre style="overflow-x: scroll; white-space: pre">
shared_preload_libraries = 'cstore_fdw' # (change requires restart)
</pre>
<br/>
<h4 class="post-body">■cstore_fdw の基本的な使い方</h4><br/>
それでは、 cstore_fdw を実際に使ってみましょう。ここでは cstore_fdw のドキュメントにあるサンプルをベースに進めます。<br/>
<br/>
まず、Citus で提供しているサンプルの CSV ファイルをダウンロードして準備します。
<pre style="overflow-x: scroll; white-space: pre">
$ wget http://examples.citusdata.com/customer_reviews_1998.csv.gz
$ gzip -d customer_reviews_1998.csv.gz
</pre>
<br/>
次に、データベースに cstore_fdw のエクステンションをインストールし、FDW 用のサーバの設定を行います。
<pre style="overflow-x: scroll; white-space: pre">
CREATE EXTENSION cstore_fdw;
CREATE SERVER cstore_server FOREIGN DATA WRAPPER cstore_fdw;
</pre>
<br/>
そして、通常の PostgreSQL のテーブルとカラムストアのテーブルをそれぞれ作成します。
<pre style="overflow-x: scroll; white-space: pre">
CREATE TABLE customer_reviews
(
customer_id TEXT,
review_date DATE,
review_rating INTEGER,
review_votes INTEGER,
review_helpful_votes INTEGER,
product_id CHAR(10),
product_title TEXT,
product_sales_rank BIGINT,
product_group TEXT,
product_category TEXT,
product_subcategory TEXT,
similar_product_ids CHAR(10)[]
);
CREATE FOREIGN TABLE customer_reviews_cstore
(
customer_id TEXT,
review_date DATE,
review_rating INTEGER,
review_votes INTEGER,
review_helpful_votes INTEGER,
product_id CHAR(10),
product_title TEXT,
product_sales_rank BIGINT,
product_group TEXT,
product_category TEXT,
product_subcategory TEXT,
similar_product_ids CHAR(10)[]
)
SERVER cstore_server
OPTIONS(filename '/tmp/customer_reviews.cstore',
compression 'pglz');
</pre>
<br/>
先に言及したように、 cstore_fdw では FDW の仕組みを使うことによって PostgreSQL のストレージをバイパスしてカラムストアのテーブルを実現していますので、外部テーブル(FOREIGN TABLE)として作成します。<br/>
<br/>
そして、そのカラムストアのテーブルの実体は、ここでは /tmp/customer_review.cstore となります。「compression 'pglz'」はカラムストアのテーブルを圧縮する、というオプションです。<br/>
<br/>
テーブルの作成が終わったら、通常のテーブルと同じように COPY コマンドを使って先ほどの CSV ファイルをそれぞれのテーブルにロードします。
<pre style="overflow-x: scroll; white-space: pre">
COPY customer_reviews FROM '/tmp/customer_reviews_1998.csv' WITH CSV;
COPY customer_reviews_cstore FROM '/tmp/customer_reviews_1998.csv' WITH CSV;
</pre>
<br/>
データのロードが終わったら、それぞれのテーブルに ANALYZE を実行します。
<pre style="overflow-x: scroll; white-space: pre">
ANALYZE customer_reviews;
ANALYZE customer_reviews_cstore;
</pre>
<br/>
さて、おもむろに count(*) を実行して、実行コストと実行時間を見てみましょう。<br/>
<br/>
通常のテーブルに対して SELECT COUNT(*) を EXPLAIN ANALYZE で実行したのが以下の結果です。
<pre style="overflow-x: scroll; white-space: pre">
postgres=# explain analyze select count(*) from customer_reviews;
QUERY PLAN
-----------------------------------------------------------------------------------------------------------------------------------------------------
Finalize Aggregate (cost=21832.40..21832.41 rows=1 width=8) (actual time=289.804..289.804 rows=1 loops=1)
-> Gather (cost=21832.18..21832.39 rows=2 width=8) (actual time=284.375..290.013 rows=3 loops=1)
Workers Planned: 2
Workers Launched: 2
-> Partial Aggregate (cost=20832.18..20832.19 rows=1 width=8) (actual time=275.572..275.573 rows=1 loops=3)
-> Parallel Seq Scan on customer_reviews (cost=0.00..20217.75 rows=245775 width=0) (actual time=0.040..194.432 rows=196620 loops=3)
Planning time: 0.060 ms
Execution time: 290.072 ms
(8 rows)
</pre>
<br/>
同様に、カラムストアのテーブルに対して同じクエリを実行したものが以下の結果になります。
<pre style="overflow-x: scroll; white-space: pre">
postgres=# explain analyze select count(*) from customer_reviews_cstore;
QUERY PLAN
-----------------------------------------------------------------------------------------------------------------------------------------
Aggregate (cost=7373.24..7373.25 rows=1 width=8) (actual time=185.477..185.477 rows=1 loops=1)
-> Foreign Scan on customer_reviews_cstore (cost=0.00..5898.59 rows=589859 width=0) (actual time=0.058..97.149 rows=589859 loops=1)
CStore File: /tmp/customer_reviews.cstore
CStore File Size: 35505490
Planning time: 0.074 ms
Execution time: 185.650 ms
(6 rows)
</pre>
<br/>
見ると分かる通り、カラムストアのテーブルでは実行コストが 1/3 程度になり、実行時間も半分強になっています。<br/>
<br/>
このように、カラムストアのテーブルでは(クエリの内容によっては)実行時間を短縮することができます。
<h4 class="post-body">■DBT-3 を使った性能評価</h4><br/>
さて、最後に DBT-3 のスキーマとクエリを使って性能を比較してみます。(DBT-3 はデータ分析系のワークロードの性能評価するためのオープンソース実装のツールです。)<br/>
<br/>
cstore_fdw のベンチマーク結果はネット上でもいくつか見つかるのですが、そういった情報は総じて「速くなったクエリだけを抜き出して性能をアピール」しているものが多く、そういった評価はユーザの立場から見て必ずしも十分な情報とは言えないように思います。<br/>
<br/>
DBT-3 ベンチマークには 1 ~ 22 までの 22 種類のクエリがありますので、それらをすべて実行して比較することで、さまざまなクエリにおける性能を確認するとともに、全体の傾向などを把握したいと思います。<br/>
<br/>
評価に使った環境は以下の通りです。<br/>
<ul>
<li>ThinkPad X Carbon</li>
<li>VirtualBox (VM 2GB RAM, 1CPU)</li>
<li>CentOS 7</li>
<li>PostgreSQL 10.6</li>
<li>DBT-3 (SF=1, DBサイズ 約1GB)</li>
<li>DBT-3 のクエリ 22 種 + データロード</li>
</ul>
なお、 DBT-3 のデータベースの作成は以下のレポジトリのコードを、<br/>
<ul>
<li><a href="https://github.com/snaga/dbt3-builddb">https://github.com/snaga/dbt3-builddb</a></li>
</ul>
実行するクエリは、以下のレポジトリのコードを使いました。<br/>
<ul>
<li><a href="https://github.com/snaga/db2-blu-config/tree/feature/pgsql/dbt3/q2">https://github.com/snaga/db2-blu-config/tree/feature/pgsql/dbt3/q2</a></li>
</ul>
また、測定のルールは以下の通りです。<br/>
<ul>
<li>データローディングについては、通常/カラムストア、それぞれ 1 回だけ計測。</li>
<li>その他のクエリについては、 6 回計測し、最初の 1 回を除いた 5 回分を平均。</li>
<li>(時間の都合上)クエリの実行時間の上限を 10 分に設定し、それを越えたクエリは無効とする。</li>
</ul>
上記の環境、条件で実行時間を計測した上で、通常のテーブルの処理性能を「1」とした時に、カラムストアのテーブルの処理性能を「倍率」で示したものが以下のグラフです。<br/>
<br/>
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhAvgChTohAEcRxLb9eNtvYrP-u5TJbGF-CmdlFannY5rC1-Sq2BdIkN61enekbBvj6J7zIRQya7BMmOM6mr9rMwJVJMZiGq-95kFNH2K5P9SY6XyLJ25lS-AED0Ek1DtZB0YfBtHlMF98/s1600/comparison.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhAvgChTohAEcRxLb9eNtvYrP-u5TJbGF-CmdlFannY5rC1-Sq2BdIkN61enekbBvj6J7zIRQya7BMmOM6mr9rMwJVJMZiGq-95kFNH2K5P9SY6XyLJ25lS-AED0Ek1DtZB0YfBtHlMF98/s640/comparison.png" width="640" height="316" data-original-width="1320" data-original-height="651" /></a></div>
<br/>
通常のテーブルに対するカラムストアのテーブルの処理性能の倍率ですので、グラフは高くなっている方が、よりカラムストアの方が高速であることを示しています。<br/>
<br/>
これを見ると、50 倍近く速くなっているクエリもある一方で、10 倍以下だったりするクエリもあります。<br/>
<br/>
なお、グレーアウトされているクエリの「17」と「20」は、通常テーブルおよびカラムストアの両方が 10 分のタイムアウトを越えたため、無効となっています。<br/>
<br/>
上記のグラフをもう少し細かく見てみたのが以下のグラフです。(縦軸の最大値を10、つまり 10 倍に設定)<br/>
<br/>
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi99m8Sgd7K4FakpXOHjZ5dMyZJ69FhlcWznO0hb1lg9CJDMtjstVbadb-aVTFbTp1bo1EcFEn70jVsshvfeZYP_9B0c9MY_7jFk0E2Rk0Ai7TgXlHmJRChzfesWMUtRnpj_Jd18JpHBy0/s1600/comparison2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi99m8Sgd7K4FakpXOHjZ5dMyZJ69FhlcWznO0hb1lg9CJDMtjstVbadb-aVTFbTp1bo1EcFEn70jVsshvfeZYP_9B0c9MY_7jFk0E2Rk0Ai7TgXlHmJRChzfesWMUtRnpj_Jd18JpHBy0/s640/comparison2.png" width="640" height="316" data-original-width="1320" data-original-height="651" /></a></div>
<br/>
これを見ると、通常のテーブルと比べてカラムストアの方が遅くなっている(1倍を割っている)のは、<br/>
<ul>
<li>ローディング</li>
<li>クエリ5、クエリ7、クエリ16、クエリ19</li>
</ul>
であり、これら以外は通常テーブルとほぼ同じくらいか、あるいは高速化していることが分かります。<br/>
<br/>
(データロードを除いた)クエリ全体を平均すると、カラムストアのテーブルの方が通常のテーブルよりも約 5.17 倍高速、という結果になりました。
<h4 class="post-body">■まとめ</h4><br/>
以上、駆け足になりましたが、 cstore_fdw の使い方、および DBT-3 を使ったカラムナー型テーブルの性能を見てきました。<br/>
<br/>
カラムナーテーブルは、あらゆるクエリが高速化されるものではありませんが、今回見てきたように、アナリティクス系のクエリでは高速化される可能性が高くなります。<br/>
<br/>
ぜひ、PostgreSQL でアナリティクスの世界に挑戦してみていただければと思います。<br/>
<br/>
では、また。<br/>
<br/>
PostgreSQL Advent Calendar 2018、明日の担当は <a href="https://qiita.com/kitayama_t">kitayama_t</a> さんです。<br/>
Satoshi Nagayasuhttp://www.blogger.com/profile/09706093898823214408noreply@blogger.com0tag:blogger.com,1999:blog-4546365304691285308.post-76749684896165496162018-12-20T00:00:00.000+09:002018-12-20T00:00:04.466+09:00機械学習ライブラリApache MADlibで決定木を使ってKaggleのTitanicを解く<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhrYVGAehL_txYEAg2VctreNgsC4PFfsaFRMvLbxFHgp83HwJdNDDuBmA-HFbuEHnM8T2ZPRhUSVqRC0nIwiBzLu-_yoBoEWJLWv04hh54l_UuTP9bgJViR6aviYpfKSPMbJ05NztF_-ik/s1600/cover.png" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhrYVGAehL_txYEAg2VctreNgsC4PFfsaFRMvLbxFHgp83HwJdNDDuBmA-HFbuEHnM8T2ZPRhUSVqRC0nIwiBzLu-_yoBoEWJLWv04hh54l_UuTP9bgJViR6aviYpfKSPMbJ05NztF_-ik/s320/cover.png" width="320" height="320" data-original-width="680" data-original-height="680" /></a></div>この記事は <a href="https://qiita.com/advent-calendar/2018/postgresql">PostgreSQL Advent Calendar 2018</a> のDay20の記事です。昨日19日は<a href="https://qiita.com/U_ikki/items/e05a063de6a991c84841"> U_ikki さんによるPostgreSQL 12の新機能の話</a>でした。<br/>
<br/>
以前から興味はあるのだけれどなかなか手を付けられなかったものの中に「Kaggleにチャレンジしてみる」というものがありました。<br/>
<br/>
「趣味はKaggleを少々嗜んでおりまして」とか言ってみたい。<br/>
<br/>
そんなことをずっと考えていたのですが、最近ようやくKaggleデビューしました。<br/>
<br/>
本エントリではPostgreSQLで使える機械学習ライブラリであるApache MADlibを使って、Kaggleの「チュートリアル」と言われているTitanicの問題を解いてみます。
<h4 class="post-body">■Kaggle Titanicとは</h4><br/>
Titanicは、Kaggle初心者のために準備されているチュートリアルの問題(Competition)のことで、以下のページから参照できます。<br/>
<ul>
<li><a href="https://www.kaggle.com/c/titanic">Titanic: Machine Learning from Disaster | Kaggle<br/>https://www.kaggle.com/c/titanic</a></li>
</ul>
簡単に言うと、<br/>
<br/>
「タイタニックで生き残った乗客と亡くなった乗客を記録した訓練用データ(トレーニングデータ)があり、その乗客の属性情報などを元に予測モデルを作成し、予測用データ(テストデータ)に掲載されている乗客が生き残るかどうかを予測し、その予測精度を競う」<br/>
<br/>
というものです。<br/>
<br/>
<a name='more'></a>
インターネット上には初心者向けに取り組み方を解説したページもいろいろとあり、私は以下のページを参考にまずは「Python + Jupyter」で実装してみました。<br/>
<ul>
<li><a href="https://www.codexa.net/kaggle-titanic-beginner/">【Kaggle初心者入門編】タイタニック号で生き残るのは誰?<br/>https://www.codexa.net/kaggle-titanic-beginner/</a></li>
</ul>
その結果は以下のJupyter Notebookにまとめてあります。<br/>
<ul>
<li><a href="https://github.com/snaga/kaggle/blob/master/titanic/Titanic.ipynb">kaggle/Titanic.ipynb at master · snaga/kaggle<br/>https://github.com/snaga/kaggle/blob/master/titanic/Titanic.ipynb</a></li>
</ul>
この内容を踏襲して、今回はPostgreSQLとApache MADlibでTitanicの予測を実装してみます。
<h4 class="post-body">■訓練用データと予測用データをPostgreSQLにロードする</h4><br/>
Titanicの訓練用データと予測用データがCSVファイルで提供されていますので、まずはこれをPostgreSQLにロードします。<br/>
<br/>
ほとんどのカラムが整数、文字列、浮動小数点のいずれかになるかと思います。<br/>
<br/>
最初に訓練用データをロードします。このテーブルのSurvivedカラムが、その乗客が生存したかどうかを示すフラグになります。<br/>
<pre style="overflow-x: scroll; white-space: pre">
titanic=# CREATE TABLE titanic_train (
titanic(# PassengerId INTEGER,
titanic(# Survived INTEGER,
titanic(# Pclass INTEGER,
titanic(# Name TEXT,
titanic(# Sex TEXT,
titanic(# Age DOUBLE PRECISION,
titanic(# SibSp INTEGER,
titanic(# Parch INTEGER,
titanic(# Ticket TEXT,
titanic(# Fare DOUBLE PRECISION,
titanic(# Cabin TEXT,
titanic(# Embarked TEXT
titanic(# );
CREATE TABLE
titanic=# \COPY titanic_train FROM 'train.csv' WITH (FORMAT CSV, DELIMITER ',', HEADER true);
COPY 891
titanic=# \d titanic_train
Table "public.titanic_train"
Column | Type | Collation | Nullable | Default
-------------+------------------+-----------+----------+---------
passengerid | integer | | |
survived | integer | | |
pclass | integer | | |
name | text | | |
sex | text | | |
age | double precision | | |
sibsp | integer | | |
parch | integer | | |
ticket | text | | |
fare | double precision | | |
cabin | text | | |
embarked | text | | |
titanic=# SELECT * FROM titanic_train LIMIT 5;
passengerid | survived | pclass | name | sex | age | sibsp | parch | ticket | fare | cabin | embarked
-------------+----------+--------+-----------------------------------------------------+--------+-----+-------+-------+------------------+---------+-------+----------
1 | 0 | 3 | Braund, Mr. Owen Harris | male | 22 | 1 | 0 | A/5 21171 | 7.25 | | S
2 | 1 | 1 | Cumings, Mrs. John Bradley (Florence Briggs Thayer) | female | 38 | 1 | 0 | PC 17599 | 71.2833 | C85 | C
3 | 1 | 3 | Heikkinen, Miss. Laina | female | 26 | 0 | 0 | STON/O2. 3101282 | 7.925 | | S
4 | 1 | 1 | Futrelle, Mrs. Jacques Heath (Lily May Peel) | female | 35 | 1 | 0 | 113803 | 53.1 | C123 | S
5 | 0 | 3 | Allen, Mr. William Henry | male | 35 | 0 | 0 | 373450 | 8.05 | | S
(5 rows)
titanic=#
</pre>
<br/>
次に予測用データをロードします。こちらは作成した予測モデルを使って予測をするためのデータですので、訓練用データにあったSurvivedカラムはありません。このSurvivedフラグを予測するのが今回の目的になります。<br/>
<pre style="overflow-x: scroll; white-space: pre">
titanic=# CREATE TABLE titanic_test (
titanic(# PassengerId INTEGER,
titanic(# Pclass INTEGER,
titanic(# Name TEXT,
titanic(# Sex TEXT,
titanic(# Age DOUBLE PRECISION,
titanic(# SibSp INTEGER,
titanic(# Parch INTEGER,
titanic(# Ticket TEXT,
titanic(# Fare DOUBLE PRECISION,
titanic(# Cabin TEXT,
titanic(# Embarked TEXT
titanic(# );
CREATE TABLE
titanic=# \COPY titanic_test FROM 'test.csv' WITH (FORMAT CSV, DELIMITER ',', HEADER true);
COPY 418
titanic=# \d titanic_test
Table "public.titanic_test"
Column | Type | Collation | Nullable | Default
-------------+------------------+-----------+----------+---------
passengerid | integer | | |
pclass | integer | | |
name | text | | |
sex | text | | |
age | double precision | | |
sibsp | integer | | |
parch | integer | | |
ticket | text | | |
fare | double precision | | |
cabin | text | | |
embarked | text | | |
titanic=# SELECT * FROM titanic_test LIMIT 5;
passengerid | pclass | name | sex | age | sibsp | parch | ticket | fare | cabin | embarked
-------------+--------+----------------------------------------------+--------+------+-------+-------+---------+---------+-------+----------
892 | 3 | Kelly, Mr. James | male | 34.5 | 0 | 0 | 330911 | 7.8292 | | Q
893 | 3 | Wilkes, Mrs. James (Ellen Needs) | female | 47 | 1 | 0 | 363272 | 7 | | S
894 | 2 | Myles, Mr. Thomas Francis | male | 62 | 0 | 0 | 240276 | 9.6875 | | Q
895 | 3 | Wirz, Mr. Albert | male | 27 | 0 | 0 | 315154 | 8.6625 | | S
896 | 3 | Hirvonen, Mrs. Alexander (Helga E Lindqvist) | female | 22 | 1 | 1 | 3101298 | 12.2875 | | S
(5 rows)
titanic=#
</pre>
<br/>
<h4 class="post-body">■訓練用データを使って予測モデルを作成する</h4><br/>
ここまで出来たら、MADlibの決定木を使って予測モデルを作成してみます。<br/>
<br/>
Pythonで実行する際に参考にした <a href="https://www.codexa.net/kaggle-titanic-beginner/">こちらの入門記事</a> ではデータの前処理を行っていますが、ここでは敢えて前処理無しでモデルを作成してみます。<br/>
<br/>
MADlibの決定木の使い方は以下から参照できます。<br/>
<ul>
<li><a href="http://madlib.apache.org/docs/latest/group__grp__decision__tree.html">MADlib: Decision Tree<br/>http://madlib.apache.org/docs/latest/group__grp__decision__tree.html</a></li>
</ul>
学習用データからモデルを作成するためには tree_train() 関数を使います。<br/>
<br/>
必須の引数は以下の通りです。<br/>
<ul>
<li>training_table_name 学習用テーブル名</li>
<li>output_table_name 作成したモデルの出力テーブル名</li>
<li>id_col_name 学習用データのレコードを特定する識別子のカラム名</li>
<li>dependent_variable 従属変数(結果)のカラム名</li>
<li>list_of_features 独立変数のカラム名のリスト</li>
</ul>
それでは、これらの引数を指定して予測モデルを作成します。作成したモデルは titanic_model というテーブルに保存されます。<br/>
<br/>
<pre style="overflow-x: scroll; white-space: pre">
titanic=# SELECT madlib.tree_train(
titanic(# 'titanic_train',
titanic(# 'titanic_model',
titanic(# 'passengerid',
titanic(# 'survived',
titanic(# 'pclass,sex,age,fare'
titanic(# );
tree_train
------------
(1 row)
titanic=# \d titanic_model
Table "public.titanic_model"
Column | Type | Collation | Nullable | Default
--------------------+---------------+-----------+----------+---------
pruning_cp | integer | | |
tree | madlib.bytea8 | | |
cat_levels_in_text | text[] | | |
cat_n_levels | integer[] | | |
tree_depth | integer | | |
titanic=#
</pre>
<br/>
<h4 class="post-body">■作成した予測モデルとテストデータを使って予測をする</h4><br/>
次に、今作成した予測モデルと、先ほどロードしておいたテストデータを使って予測を行ってみます。<br/>
<br/>
予測をするには tree_predict() 関数を使います。<br/>
<br/>
必須の引数は<br/>
<ul>
<li>tree_model 予測モデルを保存したテーブル名</li>
<li>new_data_table テストデータを保存してあるテーブル名</li>
<li>output_table 予測結果を出力するテーブル名</li>
</ul>
となります。<br/>
<br/>
<pre style="overflow-x: scroll; white-space: pre">
titanic=# SELECT madlib.tree_predict(
titanic(# 'titanic_model',
titanic(# 'titanic_test',
titanic(# 'titanic_predict');
tree_predict
--------------
(1 row)
titanic=#
</pre>
<br/>
予測結果のテーブルを見てみると、乗客IDと生存したかどうかを予測した出力(estimated_survived)がペアで出力されています。<br/>
<br/>
<pre style="overflow-x: scroll; white-space: pre">
titanic=# \d titanic_predict
Table "public.titanic_predict"
Column | Type | Collation | Nullable | Default
--------------------+---------+-----------+----------+---------
passengerid | integer | | |
estimated_survived | integer | | |
titanic=# select * from titanic_predict limit 5;
passengerid | estimated_survived
-------------+--------------------
892 | 0
893 | 1
894 | 0
895 | 0
896 | 0
(5 rows)
titanic=#
</pre>
<br/>
<h4 class="post-body">■予測結果をKaggleに投稿して予測精度を確認する</h4><br/>
それでは、最後に予測結果をCSVファイルにエクスポートして、Kaggleに投稿して予測精度を確認してみます。<br/>
<br/>
CSVファイルのカラムヘッダが passengerid と survived である必要があるため、カラム名にエイリアスを指定してCSVファイルにエクスポートします。<br/>
<br/>
<pre style="overflow-x: scroll; white-space: pre">
titanic=# \copy (select passengerid, estimated_survived survived from titanic_predict) to 'titanic_predict.csv' with (format csv, header true)
COPY 418
titanic=#
</pre>
<br/>
エクスポートしたCSVファイルをKaggleのTitanicのページの「Submit Predictions」から投稿すると、自分の予測精度と予測精度のランキングが表示されます。<br/>
<br/>
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgbAq6lkA0P9ciuLHKf6HSJ-XzvDwpqgRbrOEPR4K3ChscM8BMccwN5KCmp5oXK8cGET7WATeS37x0ITDGOQlWHGGtxgOdIZZUiTPKxS5d3PTqao2HkApuxLmSOkvS1SZ_tg_nHjrf6yG8/s1600/result1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgbAq6lkA0P9ciuLHKf6HSJ-XzvDwpqgRbrOEPR4K3ChscM8BMccwN5KCmp5oXK8cGET7WATeS37x0ITDGOQlWHGGtxgOdIZZUiTPKxS5d3PTqao2HkApuxLmSOkvS1SZ_tg_nHjrf6yG8/s640/result1.png" width="640" height="195" data-original-width="1233" data-original-height="375" /></a></div>
<br/>
今回の場合だと、予測精度は79%弱、ランキングは3571位(本エントリ執筆時点)だったようです。<br/>
<br/>
なお、今回Pythonで解くときに参考にした <a href="https://www.codexa.net/kaggle-titanic-beginner/">scikit-learn を使った入門記事</a> では、チューニング前の予測精度は71%程度だったようですので、それと比べてもなかなか良い結果が出ているのではないかと思います。<br/>
<br/>
今回は細かなパラメータのチューニングを行いませんでしたが、MADlibのマニュアルを見ていただくと、さまざまなパラメータを指定できることがお分かりいただけるかと思います。興味のある方は、ぜひモデル作成時のパラメータをチューニングして試してみていただければと思います。<br/>
<h4 class="post-body">■まとめ</h4><br/>
以上、簡単にではありましたが、Apache MADlibを使ってTitanicを解く手順をご紹介しました。<br/>
<br/>
データベースの中にあるデータに対して直接機械学習を活用できるというのは、さまざまな可能性があるように思います。<br/>
<br/>
機械学習についてはさまざまな参考書が出ていますし、Pythonで使えるscikit-learnの解説などもたくさんあります。MADlibに興味を持った方は、そういった参考資料をMADlibのマニュアルと読み比べながら、いろいろと試してみていただければと思います。<br/>
<br/>
では。<br/>
<br/>
PostgreSQL Advent Calendar 2018、明日21日の担当は <a href="https://qiita.com/tom-sato">tom-sato さん</a>です。contribモジュールについて書いていただけるようです。お楽しみに!
Satoshi Nagayasuhttp://www.blogger.com/profile/09706093898823214408noreply@blogger.com0tag:blogger.com,1999:blog-4546365304691285308.post-6262813108556468332018-12-01T00:00:00.000+09:002018-12-01T00:00:00.586+09:00Python版dblinkでデータベース連携をもっと「自由」に<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgXeMmfdgtnKVbvq_mPsPy4ggQJRVjm9Be_HXuWPFfForAarO9kVOUrx0ecIdq1K-ILZZeOiva0_PCXSZ69q69Vupk0j4aTecXLxpT3UncPnQDTEx6Xj83nmOOIhwGt1ySVAg19kElA7x4/s1600/dblink_py.png" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgXeMmfdgtnKVbvq_mPsPy4ggQJRVjm9Be_HXuWPFfForAarO9kVOUrx0ecIdq1K-ILZZeOiva0_PCXSZ69q69Vupk0j4aTecXLxpT3UncPnQDTEx6Xj83nmOOIhwGt1ySVAg19kElA7x4/s320/dblink_py.png" width="320" height="266" data-original-width="762" data-original-height="633" /></a></div>本エントリは、 <a href="https://qiita.com/advent-calendar/2018/postgresql">PostgreSQL Advent Calendar 2018</a> の Day1 のエントリです。<br/>
<br/>
エントリを書くのは実に半年以上ぶりなのですが、今回は以前から試してみたかったdblinkネタをお届けします。<br/>
<h4 class="post-body">■なぜ今さら「dblink」?</h4><br/>
PostgreSQLには、PostgreSQL、あるいは異種DBMSのデータベース連携を実現する手段として、dblinkとForeign Data Wrapper (FDW) が提供されています。<br/>
<ul>
<li><a href="https://www.postgresql.jp/document/10/html/dblink.html">https://www.postgresql.jp/document/10/html/dblink.html</a></li>
<li><a href="https://www.postgresql.jp/document/10/html/postgres-fdw.html">https://www.postgresql.jp/document/10/html/postgres-fdw.html</a></li>
</ul>
最近の方向性としては、FDWを充実させていくのが一般的な認識かと思います。<br/>
<br/>
しかし、実際にデータベース連携を実現していく中で、FDWでは対応が困難なシーンがあります。<br/>
<ul>
<li>FDWを使って外部テーブルを実装する際に設定すべき項目が多い。</li>
<li>FDWは、VIEWのようにクエリを定義(固定)して使うため、アドホックな(もしくは動的に変わる)クエリのリモート実行ができない。</li>
<li>FDWのAPIは年々複雑化しており、もはや普通の開発者が気軽に拡張できるレベルでない。</li>
<li>そもそも、Cで開発できる or したい人はもうそんなにいない。</li>
</ul>
というわけで、このエントリではdblink(のサブセット)をPL/Pythonで再実装し、それを他のDBMSに対応させるために拡張する、ということを試みます。<br/>
<br/>
PostgreSQLの強みのひとつは「拡張性の高さ」です。そのため、その「拡張性の高さ」を最大限に活かす実装を目指します。<br/>
<br/>
<a name='more'></a>
なお、このPL/Pythonによるdblinkの再実装を、本エントリでは便宜上「dblink/py」と表記します。
<h4 class="post-body">■dblink/pyの実装方針</h4><br/>
dblink/pyは以下の方針で実装します。<br/>
<ul>
<li>PostgreSQLのextensionとする。</li>
<li>APIはcontribのdblinkのサブセットとする。</li>
<li>各DBMSへの接続、操作はPythonのDatabase APIに対応したドライバを活用する。</li>
<ul>
<li><a href="https://www.python.org/dev/peps/pep-0249/">https://www.python.org/dev/peps/pep-0249/</a></li>
</ul>
<li>気軽に拡張を行えるようにシンプルな実装に留める。</li>
<li>まずはPostgreSQLへのリモート接続機能を実装し、その後SQLiteへ拡張する。</li>
</ul>
このようにすることで、PL/Pythonによるdblinkのリファレンス実装とすることにしました。
<h4 class="post-body">■実装するdblink/pyのAPI</h4><br/>
さて、dblink/pyで再実装するAPIを定義します。今回実装するAPIは以下の通りです。<br/>
<br/>
以下は、リモートデータベースに接続してカーソルを操作するためのAPIです。<br/>
<ul>
<li>dblink_connect</li>
<li>dblink_disconnect</li>
<li>dblink_get_connections</li>
<li>dblink_open</li>
<li>dblink_fetch</li>
<li>dblink_close</li>
</ul>
以下は、カーソル操作ではなく、リモートのデータベースに接続してひとつのSQLを実行して接続を閉じる、という処理を行うAPIです。<br/>
<ul>
<li>dblink</li>
<li>dblink_exec</li>
</ul>
まずは、これくらいを実装できれば、リモートデータベースへの最低限の操作はできるだろう、という目論見です。
<h4 class="post-body">■dblink/pyの初期実装と動作確認</h4><br/>
dblink/pyのコードは以下のレポジトリにあります。<br/>
<ul>
<li><a href="https://github.com/snaga/dblink_py">https://github.com/snaga/dblink_py</a></li>
</ul>
初期実装(PostgreSQL接続のみサポートしたもの)は「release-pg」というブランチになっていますので、まずはこれをチェックアウトしてインストールします。<br/>
<br/>
<pre style="overflow-x: scroll; white-space: pre">
[snaga@devvm06 temp]$ git clone https://github.com/snaga/dblink_py.git
Cloning into 'dblink_py'...
remote: Enumerating objects: 78, done.
remote: Counting objects: 100% (78/78), done.
remote: Compressing objects: 100% (35/35), done.
remote: Total 78 (delta 36), reused 64 (delta 25), pack-reused 0
Unpacking objects: 100% (78/78), done.
[snaga@devvm06 temp]$ cd dblink_py/
[snaga@devvm06 dblink_py]$ git checkout release-pg
Branch release-pg set up to track remote branch release-pg from origin.
Switched to a new branch 'release-pg'
[snaga@devvm06 dblink_py]$ sudo env USE_PGXS=1 PATH=/usr/pgsql-10/bin:$PATH make install
/usr/bin/mkdir -p '/usr/pgsql-10/share/extension'
/usr/bin/mkdir -p '/usr/pgsql-10/share/extension'
/usr/bin/install -c -m 644 .//dblink_py.control '/usr/pgsql-10/share/extension/'
/usr/bin/install -c -m 644 .//dblink_py--0.1.sql '/usr/pgsql-10/share/extension/'
[snaga@devvm06 dblink_py]$
</pre>
<br/>
インストールが完了したら、動作確認として2つのデータベース「testdb」と「testdb2」を作成して、dblinkを使って「testdb」を介して「testdb2」のデータを読み書きしてみます。<br/>
<br/>
まず、データベースを作成します。<br/>
<pre style="overflow-x: scroll; white-space: pre">
[snaga@devvm06 dblink_py]$ createdb -U postgres testdb
[snaga@devvm06 dblink_py]$ createdb -U postgres testdb2
</pre>
<br/>
次に「testdb」に接続して、dblink/pyをインストールします。<br/>
<br/>
<pre style="overflow-x: scroll; white-space: pre">
[snaga@devvm06 dblink_py]$ psql -U postgres testdb
psql (10.6)
Type "help" for help.
testdb=# create language plpython2u;
CREATE LANGUAGE
testdb=# create extension dblink_py;
CREATE EXTENSION
</pre>
<br/>
次に、「testdb」から「testdb2」に接続していることを確認します。<br/>
<pre style="overflow-x: scroll; white-space: pre">
testdb=# select * from dblink('postgresql://localhost/testdb2', 'select current_database()', true) as (dbname text);
dbname
---------
testdb2
(1 row)
</pre>
<br/>
次に、CREATE TABLE ASを使ってtestdb2上にテーブルを作成し、そのテーブルの内容を読んでみます。<br/>
<pre style="overflow-x: scroll; white-space: pre">
testdb=# select dblink_exec('postgresql://localhost/testdb2', 'create table temp as select current_database(),''ほげほげ''', true);
dblink_exec
-------------
SELECT 1
(1 row)
testdb=# select * from dblink('postgresql://localhost/testdb2', 'select * from temp', true) as (db text, foo text);
db | foo
---------+----------
testdb2 | ほげほげ
(1 row)
</pre>
<br/>
最後に、このテーブルがtestdb上には作成されておらず、testdb2上に作成されていることを確認します。<br/>
<pre style="overflow-x: scroll; white-space: pre">
testdb=# \d
Did not find any relations.
testdb=# \c testdb2
You are now connected to database "testdb2" as user "postgres".
testdb2=# \d
List of relations
Schema | Name | Type | Owner
--------+------+-------+----------
public | temp | table | postgres
(1 row)
testdb2=# select * from temp;
current_database | ?column?
------------------+----------
testdb2 | ほげほげ
(1 row)
</pre>
<br/>
以上で動作確認は完了です。(カーソルの動作確認はここではしてませんが、リグレッションテストには含まれていますので、 <a href="https://github.com/snaga/dblink_py/blob/release-pg/sql/dblink_py.sql">sql/dblink_py.sql</a> と <a href="https://github.com/snaga/dblink_py/blob/release-pg/expected/dblink_py.out">expected/dblink_py.out</a> を確認してみてください。)
<h4 class="post-body">■dblink/pyのSQLiteへの拡張</h4><br/>
さて、動作確認ができたら、次はSQLiteに対応するようにdblink/pyを拡張してみます。<br/>
<br/>
SQLite対応の拡張をしたコードは「release-sqlite」ブランチにあります。<br/>
<br/>
dblink/py本体のコード(つまりテストコードを除く)に関しては、PostgreSQLのみ対応のバージョン(release-pgブランチ)と比べると、17行追加、2行削除されていることが分かります。<br/>
<pre style="overflow-x: scroll; white-space: pre">
[snaga@devvm06 dblink_py]$ git checkout release-sqlite
Branch release-sqlite set up to track remote branch release-sqlite from origin.
Switched to a new branch 'release-sqlite'
[snaga@devvm06 dblink_py]$ git diff --stat release-pg dblink_py--0.1.sql
dblink_py--0.1.sql | 19 +++++++++++++++++--
1 file changed, 17 insertions(+), 2 deletions(-)
[snaga@devvm06 dblink_py]$
</pre>
<br/>
つまり、この「プラス17行、マイナス2行」だけでSQLite対応が完了したということです。詳細な差分は以下を参照してください。<br/>
<ul>
<li><a href="https://github.com/snaga/dblink_py/compare/release-pg...release-sqlite?diff=unified&expand=1">https://github.com/snaga/dblink_py/compare/release-pg...release-sqlite?diff=unified&expand=1</a></li>
</ul>
さて、それではSQLiteのデータベースに読み書きしてみます。<br/>
<br/>
まず、/tmp/testdb3.db というSQLiteデータベース(ファイル)を想定して、テーブルを作成します。<br/>
<br/>
<pre style="overflow-x: scroll; white-space: pre">
testdb=# select dblink_exec('sqlite:///tmp/testdb3.db', 'create table t ( uid integer, uname text)', true);
dblink_exec
-------------
OK
(1 row)
</pre>
<br/>
次にそのテーブルが空であることを確認して、1件INSERT、再度SELECTしてレコードが挿入されたことを確認します。<br/>
<pre style="overflow-x: scroll; white-space: pre">
testdb=# select * from dblink('sqlite:///tmp/testdb3.db', 'select * from t', true) as (uid int, uname text);
uid | uname
-----+-------
(0 rows)
testdb=# select dblink_exec('sqlite:///tmp/testdb3.db', 'insert into t values (1, ''aaa'')', true);
dblink_exec
-------------
OK
(1 row)
testdb=# select * from dblink('sqlite:///tmp/testdb3.db', 'select * from t', true) as (uid int, uname text);
uid | uname
-----+-------
1 | aaa
(1 row)
</pre>
<br/>
最後に、testdbデータベースにはテーブルが「作成されていないこと」を確認して、dblink/pyを通して読み書きしていたSQLiteデータベース /tmp/testdb3.db の内容を確認します。<br/>
<pre style="overflow-x: scroll; white-space: pre">
testdb=# \d
Did not find any relations.
testdb=# \q
[snaga@devvm06 dblink_py]$ sudo -u postgres sqlite3 /tmp/testdb3.db
SQLite version 3.7.17 2013-05-20 00:56:22
Enter ".help" for instructions
Enter SQL statements terminated with a ";"
sqlite> .tables
t
sqlite> select * from t;
1|aaa
sqlite>
</pre>
<br/>
以上で、dblink/pyのSQLite対応の拡張とその動作確認は完了です。
<h4 class="post-body">■まとめ</h4><br/>
というわけで、今回はdblinkの機能のサブセットをPL/Pythonで再実装し、それを他のDBMSに拡張する、ということを試してみました。<br/>
<br/>
本家のcontribのdblinkはコードが3,000行以上ある大規模なモジュールですが、今回作ったdblink/pyのコードは200行以下です。SQLite対応の拡張に至っては、わずか20行以下のコードの変更で実現できています。PostgreSQLの拡張性をうまく活かすことで、これくらいの労力でさまざまな拡張ができる、ということがお分かりいただけたかと思います。<br/>
<br/>
そんなこんなで今回いろいろやってきましたが、お伝えしたかったことは<br/>
<ul>
<li>「FDWが無くてもdblinkを使ってVIEWを定義してしまえば見た目はFDWとだいたい一緒や」</li>
<li>「シンプルに行こう。Less is more.」</li>
</ul>
ということです。<br/>
<br/>
<a href="https://qiita.com/advent-calendar/2018/postgresql">PostgreSQL Advent Calendar 2018</a>、明日の担当は・・・なんと、未定です(本エントリ執筆時点)。<br/>
<br/>
Advent Calendarが初日で終わってしまうのか。それとも奇跡が起きて継続されるのか。ドキドキハラハラが止まらない。刮目せよ。<br/>
<br/>
では。
Satoshi Nagayasuhttp://www.blogger.com/profile/09706093898823214408noreply@blogger.com0tag:blogger.com,1999:blog-4546365304691285308.post-58183109126082433382018-04-23T22:06:00.000+09:002018-04-24T09:46:18.119+09:00この連休の読書にオススメの一冊「SQLパフォーマンス詳解」(割引コードあり)<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhhC9PRSj7ef0cag6q8xwdh1G2xvP8An2pFBfBdKgKjAqxrZYlt2KUzC0Y-7OA2hukgR_xsSVqsAsDTy_3U9oi-Szvq92awJFpGB71oAtd_-Elry0w8hhe74-kyb6lh4PgeCtpTzcpBYRA/s1600/1.png" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhhC9PRSj7ef0cag6q8xwdh1G2xvP8An2pFBfBdKgKjAqxrZYlt2KUzC0Y-7OA2hukgR_xsSVqsAsDTy_3U9oi-Szvq92awJFpGB71oAtd_-Elry0w8hhe74-kyb6lh4PgeCtpTzcpBYRA/s400/1.png" width="261" height="400" data-original-width="703" data-original-height="1079" /></a></div>最近、久しぶりにPostgreSQLのクエリチューニングをしていたのですが、その過程で「この本はぜひもっと多くの人に読んでもらいたい」と改めて思い出した一冊がありました。<br/>
<br/>
それは、「SQLパフォーマンス詳解(原題:SQL Performance Explained)」という本です。<br/>
<ul>
<li>SQLパフォーマンス詳解 <br/><a href="http://sql-performance-explained.jp/">http://sql-performance-explained.jp/</a></li>
</ul>
パフォーマンスチューニング、特にクエリチューニングについて説明する場合、その前提となる知識は広範なものになります。<br/>
<br/>
そのため、自分が頑張って説明するよりも、優れたエキスパートのまとめたコンテンツを活用させてもらう方が、質・量ともに優れたインプットにしていただけるのではないか、と思うのです。<br/>
<br/>
また、この「SQLパフォーマンス詳解」は非常に良い本であるにも関わらず、一般の出版社から出ているわけではないため、それほど積極的にプロモーションされているわけではなく、日本語版についても、(残念ながら)一般的な書籍ほど話題になることが無いように思います。<br/>
<br/>
そういった理由により、本エントリではこの本について皆さんに知っていただくべくご紹介するとともに、著者のMarkus Winand氏から日本の読者の皆さんに「最大で半額」となる割引コードを提供いただけることになりましたので、その使い方についてご紹介したいと思います。<br/>
<br/>
ゴールデンウィーク直前ですが、ぜひ連休中に読む一冊に加えていただければと思います。データベースのパフォーマンスについて、網羅的かつ本質的な理解が深まること、間違いのない一冊です。
<h4 class="post-body">■著者のMarkus Winand氏について</h4><br/>
著者のMarkus Winand氏は、PostgreSQLを始めとするRDBMSのチューニングのエキスパート/コンサルタントとして有名な方で、「Use the Index, Luke!」というブログでお馴染みです。<br/>
<br/>
<a name='more'></a>
<ul>
<li>winand.at: SQL at the highest level<br/><a href="https://winand.at/">https://winand.at/</a></li>
<li>SQL Indexing and Tuning e-Book for developers: Use The Index, Luke covers Oracle, MySQL, PostgreSQL, SQL Server, ... <br/><a href="https://use-the-index-luke.com/">https://use-the-index-luke.com/</a></li>
<li>SQLのインデックスとそのチューニングについてのオンラインブック <br/><a href="https://use-the-index-luke.com/ja">https://use-the-index-luke.com/ja</a></li>
</ul>
データベースのパフォーマンスチューニングに興味のある方であれば、一度は目にしたことのあるブログではないかと思います。<br/>
<br/>
また、以前、UberがPostgreSQLからMySQLに移行したというニュースが流れた時に、「On Uber’s Choice of Databases」というブログを書かれたことでも有名です。<br/>
<ul>
<li>On Uber's Choice of Databases <br/><a href="https://use-the-index-luke.com/blog/2016-07-29/on-ubers-choice-of-databases">https://use-the-index-luke.com/blog/2016-07-29/on-ubers-choice-of-databases</a></li>
</ul>
このエントリは、当ブログでも翻訳して紹介させていただきました。<br/>
<br/>
<iframe marginwidth="0" marginheight="0" src="http://b.hatena.ne.jp/entry.parts?url=http%3A%2F%2Fpgsqldeepdive.blogspot.com%2F2016%2F08%2Fon-ubers-choice-of-databases-uber.html" scrolling="no" frameborder="0" height="230" width="500"><div class="hatena-bookmark-detail-info"><a href="http://pgsqldeepdive.blogspot.com/2016/08/on-ubers-choice-of-databases-uber.html">PostgreSQL Deep Dive: 【翻訳】 On Uber’s Choice of Databases (データベースにおけるUberの選択について)</a><a href="/entry/pgsqldeepdive.blogspot.com/2016/08/on-ubers-choice-of-databases-uber.html">はてなブックマーク - PostgreSQL Deep Dive: 【翻訳】 On Uber’s Choice of Databases (データベースにおけるUberの選択について)</a></div></iframe><br/>
<br/>
また、PostgreSQLのグローバルカンファレンスである「<a href="https://www.pgcon.org/">pgcon</a>」にも度々登壇しており、パフォーマンスチューニングのエキスパートの観点から、さまざまな知見をコミュニティで共有しています。私も一度、pgconで氏の講演を直接聞いたことがあります。<br/>
<br/>
このように、著者のMarkus Winand氏は、データベースのパフォーマンスについて、理論と実践のいずれの面から見ても第一人者の一人であると言えます。<br/>
<h4 class="post-body">■「SQLパフォーマンス詳解」について</h4><br/>
「SQLパフォーマンス詳解」は、データベースのパフォーマンスについて、主にインデックスの観点から解説した本です。(目次については後述します)<br/>
<br/>
もともとは英語の書籍だったのですが、少し前に日本語に翻訳されて、現在は日本語版を読むことができます。<br/>
<br/>
対応するRDBMSとしては、主にOracle、MySQL、PostgreSQL、SQL Serverについて記載されています。<br/>
<br/>
個人的には、本書は特に<br/>
<ul>
<li>自分のWebアプリケーションのデータベースアクセスのパフォーマンスについて詳しく理解したい開発者の方</li>
<li>RDBMSのパフォーマンスやインデックスについて、網羅的かつ実戦的な知識を得たいDBエンジニア</li>
<li>システムの性能分析やデータベースのパフォーマンスチューニングを担う基盤系エンジニアやDBA</li>
<li>今まで使ってきたRDBMSと違うRDBMSを使うことになった(なりそうな)DBエンジニア</li>
</ul>
といった方々にオススメの一冊だと思います。<br/>
<br/>
というか、データベースのパフォーマンスやインデックスの設計方法について考える時、個々のRDBMS製品に特化した書籍は多少はあるかもしれませんが、ここまで網羅的かつ横断的に解説されている本を、私は他に見たことがありません。<br/>
<br/>
前述した通り、著者のMarkus Winand氏はRDBMSのパフォーマンスおよびチューニングのエキスパートです。そんなエキスパートの知見と経験が詰まった一冊になっていますので、ぜひ読んでみていただければと思います。<br/>
<br/>
なお、私は最近、本書に書かれていた知見を活用してSQLチューニングをした結果、とあるクエリのパフォーマンスが10万倍に改善しました。
<h4 class="post-body">■「SQLパフォーマンス詳解」目次</h4><br/>
以下が「SQLパフォーマンス詳解」の目次になります。<br/>
<br/>
如何に網羅的かつ実戦的な情報がまとまっている一冊であるか、目次を見るだけでも理解していただけるのではないかと思います。<br/>
<ul>
<li>前書き ............................................................................................ vi</li>
<li>1章 インデックスの内部構造 ............................................................... 1</li>
<ul>
<li>インデックスリーフノード</li>
<li>検索ツリー (Bツリー)</li>
<li>遅いインデックス パートI </li>
</ul>
<li>2章 where句 ...................................................................................... 9</li>
<ul>
<li>等価演算子</li>
<li>関数</li>
<li>パラメータ化クエリ</li>
<li>範囲検索</li>
<li>部分インデックス</li>
<li>OracleにおけるNULL</li>
<li>処理しにくい条件</li>
</ul>
<li>3章 パフォーマンスとスケーラビリティ ............................................ 79</li>
<ul>
<li>データ量がパフォーマンスに与える影響</li>
<li>システム負荷がパフォーマンスに与える影響</li>
<li>応答時間とスループット</li>
</ul>
<li>4章 結合処理 .................................................................................... 91</li>
<ul>
<li>入れ子ループ</li>
<li>ハッシュ結合</li>
<li>ソートマージ</li>
</ul>
<li>5章 データのクラスタリング ........................................................... 111</li>
<ul>
<li>フィルタ述語の意図的な使用</li>
<li>インデックスのみのスキャン</li>
<li>索引構成表</li>
</ul>
<li>6章 ソートとグルーピング .............................................................. 129</li>
<ul>
<li>インデックスを使ったorder by</li>
<li>ASC、DESCとNULLS FIRST/LAST</li>
<li>インデックスを使ったgroup by</li>
</ul>
<li>7章 部分結果 .................................................................................. 143</li>
<ul>
<li>最初のN行のみの選択</li>
<li>次ページの取得</li>
<li>ページネーションのための窓関数の使用</li>
</ul>
<li>8章 データの変更 ........................................................................... 159</li>
<ul>
<li>挿入</li>
<li>削除</li>
<li>更新</li>
</ul>
<li>A. 実行計画 ................................................................................. 165</li>
<ul>
<li>Oracle</li>
<li>PostgreSQL</li>
<li>SQL Server </li>
<li>MySQL</li>
</ul>
<li>索引 ............................................................................................ 193</li>
</ul>
<h4 class="post-body">■割引コードの使い方</h4><br/>
著者のMarkus Winand氏に「かくかくしかじかにより、SQLパフォーマンス詳解を紹介したい(ダジャレではありません)」と相談したところ、日本語版の読者に割引コードを発行していただけることになりました。<br/>
<br/>
割引コードは「GW2018INDEX」です。<br/>
<br/>
この割引コードは、<br/>
<ul>
<li>PDF版は 50% OFF (9.95ユーロ → 5.00 ユーロ)</li>
<li>印刷版は 5% OFF (29.95ユーロ → 26.95ユーロ)</li>
<li>PDF+印刷版は 14% OFF (34.95ユーロ → 30.00 ユーロ)</li>
<li>有効なのは <a href="http://sql-performance-explained.jp/">直販サイト</a> のみ。</li>
<li>有効期限は 5/10 まで</li>
</ul>
となっています。<br/>
<br/>
使い方は、書籍の直販サイトに行き、<br/>
<ul>
<li>SQLパフォーマンス詳解 <br/><a href="http://sql-performance-explained.jp/">http://sql-performance-explained.jp/</a></li>
</ul>
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjp9iXZotkkaJcNXRZUdTEeBmJ1y9rVdtxo6w5M_LWyfqsz5Q18bm4mIQXau_ub-L3TFUsg7gELJ36XwJ8z8AcbDzcMVK1cwDdr7dKMKJh3h_5T4xerASQWi_urcJBghC1ZCaAWrTNVE3E/s1600/2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjp9iXZotkkaJcNXRZUdTEeBmJ1y9rVdtxo6w5M_LWyfqsz5Q18bm4mIQXau_ub-L3TFUsg7gELJ36XwJ8z8AcbDzcMVK1cwDdr7dKMKJh3h_5T4xerASQWi_urcJBghC1ZCaAWrTNVE3E/s400/2.png" width="400" height="296" data-original-width="1239" data-original-height="916" /></a></div>
<br/>
「割引コードを入力」をクリックして、割引コードを入力するだけです。<br/>
<br/>
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiS6mnqyx_Qjv8U_D4Otjs-Qoj9z9TA319igdmxW8nqkoNEzPp_BPC9AvLgluDPLpuLZLn8Yx9nt0IiDjlaFxAGz1ZRLG45W3Nl5i6LrwhdZAdT8u-Nx3_tBL5n7vIdeptC9BTR4frQHi4/s1600/3.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiS6mnqyx_Qjv8U_D4Otjs-Qoj9z9TA319igdmxW8nqkoNEzPp_BPC9AvLgluDPLpuLZLn8Yx9nt0IiDjlaFxAGz1ZRLG45W3Nl5i6LrwhdZAdT8u-Nx3_tBL5n7vIdeptC9BTR4frQHi4/s320/3.png" width="290" height="320" data-original-width="376" data-original-height="415" /></a></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhYNUsqukHT-ilEY9cRhtVaTfsPTYSREBpXwPvXQisKT2J0AhIDONvZ2rR9mCAq731QWBTF7RVCPaE8YWJNhSBL0ToeGtHvT2hWpngaruS_5FZzDaWTPCOgFuZR7qJfdcE-84EhOt8r_fA/s1600/4.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhYNUsqukHT-ilEY9cRhtVaTfsPTYSREBpXwPvXQisKT2J0AhIDONvZ2rR9mCAq731QWBTF7RVCPaE8YWJNhSBL0ToeGtHvT2hWpngaruS_5FZzDaWTPCOgFuZR7qJfdcE-84EhOt8r_fA/s320/4.png" width="291" height="320" data-original-width="376" data-original-height="413" /></a></div>
<br/>
割引コードを入力すると、上記の割引価格が表示されますので、そのまま購入することができます。<br/>
<br/>
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh-OjykA4_xnjMTgttCbkoHpWEsIl9XyBgMrA5UWDIES2K3-z04pbivWIjpv_qFy1zN8nDX1tVaz54M4cWHb8e3l02Roz8SeLiYMV3q_2sb5nTTrRsLaWJW_V7tP0iuE_q9oyeeNsQZ7nk/s1600/5.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh-OjykA4_xnjMTgttCbkoHpWEsIl9XyBgMrA5UWDIES2K3-z04pbivWIjpv_qFy1zN8nDX1tVaz54M4cWHb8e3l02Roz8SeLiYMV3q_2sb5nTTrRsLaWJW_V7tP0iuE_q9oyeeNsQZ7nk/s320/5.png" width="292" height="320" data-original-width="376" data-original-height="412" /></a></div>
<h4 class="post-body">■まとめ</h4><br/>
本エントリでは、PostgreSQLのパフォーマンスチューニングにおいて参考にしていただきたい書籍、「SQLパフォーマンス詳解」についてご紹介してきました。<br/>
<br/>
パフォーマンスチューニングに必要なのは、「RDBMSの仕組みについての知識」、「問題の合理的な切り分けのスキル」、「解決策の引き出しの多さ」なのではないかと思っています。そういった観点で見ると、本書はそれらを網羅的かつ実践的に得られる、他に類書が見当たらない非常に稀な一冊であると思います。<br/>
<br/>
割引コードを使うと、特にPDF版は50% OFFという破格な値段になりますので、データベースのパフォーマンスチューニングに興味のある方には、これを機会にぜひ一度読んでみていただければと思います。<br/>
<br/>
連休中に読み切れる分量だと思いますし、読んだ後、長期に渡って活用できる知見を得られる、絶対に後悔しない一冊になると思います。連休中の読書&スキルアップに、ぜひ活用いただければと思います。<br/>
<br/>
では、また。<br/>
Satoshi Nagayasuhttp://www.blogger.com/profile/09706093898823214408noreply@blogger.com0tag:blogger.com,1999:blog-4546365304691285308.post-42260807593667156982018-03-17T21:20:00.000+09:002018-03-17T21:27:08.428+09:00PostgreSQLのデータをPandasのデータフレームとして読み書きする<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiMt0QAhSVb0cp5BQkny3oBagwTkMLqgiVw3aORlNCNkCt0AyQ7PTc4dextp1n1rvfYioldfVFjPUPhZKsompKmw3FEEzzuN5EaW0A6P6-sPZsyPWFMulzij6LzTgBERnn0A6gJG-k2pcM/s1600/0.png" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiMt0QAhSVb0cp5BQkny3oBagwTkMLqgiVw3aORlNCNkCt0AyQ7PTc4dextp1n1rvfYioldfVFjPUPhZKsompKmw3FEEzzuN5EaW0A6P6-sPZsyPWFMulzij6LzTgBERnn0A6gJG-k2pcM/s320/0.png" width="320" height="206" data-original-width="1437" data-original-height="923" /></a></div>
最近、JupyterやPandasを使ってデータを処理する機会が増えてきました。<br/>
<br/>
とは言え、手元のデータはPostgreSQLに溜まっていたり、あるいはSQLで処理したい、ということがよくあります。<br/>
<br/>
というわけで、Jupyterを使っている時に、「PostgreSQLからデータを取り出して、Pandasやら何やらでいろいろ処理した後、結果をPostgreSQLを書き出す」というユースケースを想定して、その方法を調べてみました。
<h4 class="post-body">■やりたいこと</h4><br/>
やりたいことは、PostgreSQLのデータをJupyter上でPandasのデータフレームとして読み込み、集計やデータ分析をした結果をPostgreSQLに書き戻す、ということです。<br/>
<br/>
データの加工や整形など(データ前処理)はPostgreSQLの方が高速に行えるのでSQLで、複雑なアルゴリズムの適用はPythonで行いたい、そしてその結果をPostgreSQLに書き戻して利用したい、というケースを想定しています。<br/>
<br/>
あるいはPostgreSQLのデータをmatplotlibを使って可視化したい、といった場合にも使えるでしょう。(この場合は書き戻しは必要ありませんが)
<h4 class="post-body">■必要なもの</h4><br/>
必要なものは以下の通りです。<br/>
<br/>
<a name='more'></a>
<ul>
<li>PostgreSQL</li>
<li>Jupyter</li>
<li>pscycopg2</li>
<li>SQLAlchemy</li>
</ul>
PostgreSQLからDataFrameにデータを読み込むだけならpsycopg2だけで実現可能なのですが、DataFrameのデータをPostgreSQLに書き戻すにはSQLAlchemyが必要になります。<br/>
<br/>
つまり、最初からSQLAlchemyを用意しておいた方がいいでしょう。
<h4 class="post-body">■PostgreSQLに接続する</h4><br/>
まず、PostgreSQLに接続する必要があります。<br/>
<br/>
Python(つまりJupyter上で)必要なモジュールのインポートは以下の通りです。<br/>
<pre style="overflow-x: scroll; white-space: pre">
from sqlalchemy import create_engine
import pandas as pd
</pre>
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhGf1wuarcU0Qm9KkhilviQq8WyS3IgQrDARPV-Sdsy6gBqu8cHNigB5QgdX6HvLUOjjTn2mSoH_NSOdPnz5wCHRHD5MTkZnl8SU9xT5u9ov-AvRQiKnmJboWxRlv0HDdF9FB_O_N8cK7c/s1600/1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhGf1wuarcU0Qm9KkhilviQq8WyS3IgQrDARPV-Sdsy6gBqu8cHNigB5QgdX6HvLUOjjTn2mSoH_NSOdPnz5wCHRHD5MTkZnl8SU9xT5u9ov-AvRQiKnmJboWxRlv0HDdF9FB_O_N8cK7c/s640/1.png" width="640" height="49" data-original-width="1421" data-original-height="108" /></a></div>
<br/>
そして、SQLAlchemyのcreate_engine()関数を使ってPostgreSQLのエンジンオブジェクトを作成します。<br/>
<pre style="overflow-x: scroll; white-space: pre">
engine = create_engine('postgresql://postgres:postgres@localhost:5432/postgres')
</pre>
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgLwBKLFjpqiG-9D8-aos55l54GgwwkMuhXOrflT6rXCxzfl2K9cifUKztVk2mP7rL4pl_7Vp92MiFBk08Dg1ggkVt89YZqAfnKAxHGq6JncFBUHk7K1YhBsKEsh_LPgXePbgumIA-33mU/s1600/2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgLwBKLFjpqiG-9D8-aos55l54GgwwkMuhXOrflT6rXCxzfl2K9cifUKztVk2mP7rL4pl_7Vp92MiFBk08Dg1ggkVt89YZqAfnKAxHGq6JncFBUHk7K1YhBsKEsh_LPgXePbgumIA-33mU/s640/2.png" width="640" height="28" data-original-width="1421" data-original-height="63" /></a></div>
<br/>
<h4 class="post-body">■PostgreSQLのデータをPandasに読み込む</h4><br/>
PostgreSQLのデータをPandasのDataFrameに読み込むには、read_sql()メソッドを使います。<br/>
<br/>
以下の例では、pg_databaseテーブルの内容をDataFrame dfに読み込んでいます。<br/>
<pre style="overflow-x: scroll; white-space: pre">
df = pd.read_sql("SELECT * FROM pg_database", engine)
</pre>
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhINyHk95j_EBufPxriFEQBKi88pw-glAKlb12O6xg2xfpZ9yfh9ku5-A3z7AsTVXKHW3XHYa1qFTfFgS-6OwciSy4JNkbC5kZVsexcoa21w-MLe7anOf9zwmY00Qx8WIofo_tN_xkStbI/s1600/3.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhINyHk95j_EBufPxriFEQBKi88pw-glAKlb12O6xg2xfpZ9yfh9ku5-A3z7AsTVXKHW3XHYa1qFTfFgS-6OwciSy4JNkbC5kZVsexcoa21w-MLe7anOf9zwmY00Qx8WIofo_tN_xkStbI/s640/3.png" width="640" height="160" data-original-width="1421" data-original-height="356" /></a></div>
<br/>
このメソッドを一回呼び出すだけで、PostgreSQLのデータをDataFrameに取り込んで扱うことができます。<br/>
<h4 class="post-body">■PandasのデータをPostgreSQLに書き出す</h4><br/>
DataFrameを使っていろいろ処理した結果をPostgreSQLに書き出すには、DataFrameのto_sql()メソッドを使います。<br/>
<ul>
<li>pandas.DataFrame.to_sql — pandas 0.22.0 documentation <br/><a href="https://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.to_sql.html">https://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.to_sql.html</a></li>
</ul>
Pandasのドキュメントにも書かれていますが、SQLAlchemyを使う場合には、SQLAlchemyによってサポートされているDBMSで書き出しを行うことができます。そうでない場合には、SQLiteのみがサポートされます。よって、PostgreSQLに書き出したい場合には、SQLAlchemyを使う必要があります。<br/>
<br/>
以下の例では、DataFrame dfの内容をt1テーブルに書き出しています。その際、既にt1テーブルが存在した場合には、そのテーブルを置き換えるように指定しています。(if_exists引数)<br/>
<pre style="overflow-x: scroll; white-space: pre">
df.to_sql('t1', engine, if_exists='replace')
</pre>
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi_gmGsE2FhNAliZ8RadJL-qaH59zAFHYbP-nKh3l9zmQqGT3F5YVjePlnu2M1cmjx0H10K0dRiVD__KJ7wjsTunv8y7SbykQPWqMbe6BCDy6YidwbrI3KIKq0S1iLEe1w8MqVGRiVgTKI/s1600/4.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi_gmGsE2FhNAliZ8RadJL-qaH59zAFHYbP-nKh3l9zmQqGT3F5YVjePlnu2M1cmjx0H10K0dRiVD__KJ7wjsTunv8y7SbykQPWqMbe6BCDy6YidwbrI3KIKq0S1iLEe1w8MqVGRiVgTKI/s640/4.png" width="640" height="38" data-original-width="1419" data-original-height="85" /></a></div>
その結果は以下の通りです。<br/>
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhtGPw62kGuVPYvTEnXBKRDk9kmbm9NjlKS9EZxZSdaha1K13QwN4LcNl7yzdxwqoK-hAY-DPrairt2kEbrTvBJWPemRlDwnm1vgJpJYOh1JYz6ZGnzlQotQRk9dVLTTDSo_fcOIDaikpo/s1600/5.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhtGPw62kGuVPYvTEnXBKRDk9kmbm9NjlKS9EZxZSdaha1K13QwN4LcNl7yzdxwqoK-hAY-DPrairt2kEbrTvBJWPemRlDwnm1vgJpJYOh1JYz6ZGnzlQotQRk9dVLTTDSo_fcOIDaikpo/s640/5.png" width="640" height="160" data-original-width="1420" data-original-height="355" /></a></div>
<br/>
to_sql()メソッドにはif_exists以外にもいろいろなオプションがありますので、ぜひ確認してみていただければと思います。
<h4 class="post-body">■まとめ</h4><br/>
以上、非常に簡単ではありましたが、Python(というかJupyter)からPandas(DataFrame)を介してPostgreSQLのデータを読み書きする方法をご紹介しました。<br/>
<br/>
データベースエンジニアの目の前には、さまざまなデータ、多くのデータが横たわっています。<br/>
<br/>
私自身は、PL/PythonやMADlibを始めとするIn-Database処理が好みではありますが、一方でPandasで扱うデータの前処理をPostgreSQLで実現できたら便利だなぁ、と感じることも多々あるのが現実だったりします。<br/>
<br/>
ぜひ、さまざまなツールを知ることで、それらのデータを活用していただければと思います。<br/>
<br/>
では、また。<br/>
Satoshi Nagayasuhttp://www.blogger.com/profile/09706093898823214408noreply@blogger.com0tag:blogger.com,1999:blog-4546365304691285308.post-42205854603348166642017-12-06T00:00:00.000+09:002017-12-06T08:20:15.442+09:00Oracle対応アプリケーションのDockernize事始め本エントリは<a href="https://jpoug.doorkeeper.jp/events/67051">JPOUG Advent Calendar 2017</a> Day6の記事です。<br/>
<br/>
普段はPostgreSQLのブログなのですが、今回はスピンオフ企画(番外編)として、先日のJPOUGのイベント「<a href="http://www.jpoug.org/2017/09/20/in15m6">JPOUG in 15 minutes #6</a>」で発表した「Oracle対応アプリケーションのDockernize事始め」の内容をブログエントリとしてお送りします。(Oracleネタを書くブログが無いので・・・)<br/>
<br/>
なお、資料は公開していますので、興味のある方はそちらも併せてどうぞ。<br/>
<br/>
<iframe src="//www.slideshare.net/slideshow/embed_code/key/4Z1gEX9NhOz5Bh" width="595" height="485" frameborder="0" marginwidth="0" marginheight="0" scrolling="no" style="border:1px solid #CCC; border-width:1px; margin-bottom:5px; max-width: 100%;" allowfullscreen> </iframe> <div style="margin-bottom:5px"> <strong> <a href="//www.slideshare.net/snaga/oracledockerize" title="Oracle対応アプリケーションのDockerize事始め" target="_blank">Oracle対応アプリケーションのDockerize事始め</a> </strong> from <strong><a href="https://www.slideshare.net/snaga" target="_blank">Satoshi Nagayasu</a></strong> </div>
<h4 class="post-body">■なぜ今さら「Docker」か、という前口上</h4><br/>
既にDockerに十分触れている方、慣れている方には釈迦に説法になるかと思いますが、なぜ最近になってDockerに着目して使うようになったのか、ということからお話しようと思います。<br/>
<br/>
私がDockerを使い始めたのは、実は先日のJPOUGのイベントでのセッションが決まってからです。なので、片手間に触り始めてからまだ数ヶ月と言ったところです。<br/>
<br/>
<a name='more'></a>
ずっと興味は持っていたのですが、自分が普段使っている環境が長いことRHEL6/CentOS6系だったこと、Dockerでどうしても使ってみたいことが無かったので、なかなか手を動かして試してみるところまで到達しませんでした。<br/>
<br/>
そういう状況ではあったのですが、春先に自分が開発しているツールをオープンソースとして公開したあたりから、少し状況が変わってきました。<br/>
<ul>
<li>Hecatoncheir: The Data Stewardship Studio 0.8を公開しました <br/><a href="http://pgsqldeepdive.blogspot.jp/2017/05/hecatoncheir-data-stewardship-studio.html">http://pgsqldeepdive.blogspot.jp/2017/05/hecatoncheir-data-stewardship-studio.html</a></li>
</ul>
オープンソースとして開発する際、さまざまなフレームワークやライブラリに依存した開発をすることがあります。そうなると、動作させるためにはそれらを整えた環境を構築する必要があります。<br/>
<br/>
しかし、環境構築も、自分が慣れている技術スタックならまだしも、不慣れなスタックを整備するのは技術的のみならず心理的障壁も高くなります。また、特にデータベースエンジニアや基盤系エンジニアの方だと、「不要なものは自分の環境に入れたくない(環境を汚したくない)」という心理も働くと思われ、そうなると公開したツールを試してもらうのもなかなか難しくなります。(簡単に試せないものを紹介する側も気が引けますし)<br/>
<br/>
というわけで、環境をカプセル化して簡単に試してもらうにはDocker化してみるのがいいのでは、と考えたのが、今回Dockerに触ってみる動機になったのでした。
<h4 class="post-body">■HecatoncheirのDocker化</h4><br/>
というわけで、HecatoncheirのDocker化を試みます。<br/>
<br/>
Hecatoncheirはデータベースから情報を収集してレポジトリに保存するクライアントツールと、レポジトリの情報を公開するサーバから構成されています。<br/>
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh-TmkjiFBzuJvUBPKH7RHFcLh24OJlSYfd0LOueyrbIWwA52iysf2AbC6IeppGaWtBsaopE-mcqH1A5zW3MYsuqB64qbbI84La-GDFjKjehlyn1bPyTXuHG8YKvduDBBCOUt0cm43Zudk/s1600/%25E3%2582%25B9%25E3%2583%25A9%25E3%2582%25A4%25E3%2583%258915.PNG" imageanchor="1" ><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh-TmkjiFBzuJvUBPKH7RHFcLh24OJlSYfd0LOueyrbIWwA52iysf2AbC6IeppGaWtBsaopE-mcqH1A5zW3MYsuqB64qbbI84La-GDFjKjehlyn1bPyTXuHG8YKvduDBBCOUt0cm43Zudk/s640/%25E3%2582%25B9%25E3%2583%25A9%25E3%2582%25A4%25E3%2583%258915.PNG" width="640" height="451" data-original-width="1126" data-original-height="793" /></a>
<br/>
そして、それらのツールはさまざまなPythonライブラリと、Oracle DatabaseのクライアントライブラリであるOracle Instant Clientに依存しています。<br/>
<br/>
これらをDocker化し、コマンド一発で数分以内に利用できるようにするところまでを目指します。<br/>
<br/>
目標は以下の通りです。<br/>
<ul>
<li>コマンド一発で数分で利用可能に</li>
<li>収集したレポジトリのデータは残せるようにする(永続化可能にする)</li>
</ul>
<h4 class="post-body">■Dockerイメージ化する</h4><br/>
イベントの時とは説明の順序が違いますが、Dockerイメージ化してみます。イメージ化するためにはDockerfileと呼ばれる定義ファイルを作成します。このファイルで、Dockerイメージに何を含めるかを決めます。<br/>
<br/>
プレゼンの際には抜粋したDockerfileでしたが、全体は以下のようになっています。
<pre style="overflow-x: scroll; white-space: pre">
FROM centos:7
MAINTAINER Satoshi Nagayasu <snaga@uptime.jp>
#ADD oracle-instantclient12.2-basic-12.2.0.1.0-1.x86_64.rpm /tmp
#ADD oracle-instantclient12.2-devel-12.2.0.1.0-1.x86_64.rpm /tmp
ENV ORACLE_HOME=/usr/lib/oracle/12.2/client64
ENV LD_LIBRARY_PATH=$ORACLE_HOME/lib
ENV LANG=C
# pip
RUN yum install -y gcc python-devel
RUN curl -o get-pip.py https://bootstrap.pypa.io/get-pip.py
RUN python get-pip.py; rm get-pip.py
# Oracle support
RUN yum install -y libaio unzip
RUN rpm -ivh https://s3-ap-northeast-1.amazonaws.com/uptime-dev01/oracle/oracle-instantclient12.2-basic-12.2.0.1.0-1.x86_64.rpm
RUN rpm -ivh https://s3-ap-northeast-1.amazonaws.com/uptime-dev01/oracle/oracle-instantclient12.2-devel-12.2.0.1.0-1.x86_64.rpm
#RUN rpm -ivh /tmp/oracle-instantclient12.2-basic-12.2.0.1.0-1.x86_64.rpm
#RUN rpm -ivh /tmp/oracle-instantclient12.2-devel-12.2.0.1.0-1.x86_64.rpm
# PostgreSQL support
RUN rpm -ivh https://download.postgresql.org/pub/repos/yum/10/redhat/rhel-7-x86_64/pgdg-centos10-10-2.noarch.rpm
RUN yum install -y postgresql10-devel
# MySQL support
RUN rpm -ivh https://dev.mysql.com/get/mysql57-community-release-el7-11.noarch.rpm
RUN yum install -y mysql-community-devel
# Hecatoncheir
RUN curl -O https://codeload.github.com/snaga/Hecatoncheir/zip/develop; unzip develop
RUN cd Hecatoncheir-develop; pip install -r requirements.txt; pip install .
</pre>
また、Dockerfileの作成・更新は、以下のような流れで行っていました。<br/>
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjKIcsTghM5Ze6lLsLKtZE-FP-mGl5fxH2hwni01K6zWxqS0ZRhWcXU5ED3gBjMvlhBR2CWIFfbbviQgR9pS4u-7LGG7jxuQ6k1_5SmtAJPmHfoJfuhQQuwqMRIs-z136Qk2Y5ABSpMSCY/s1600/%25E3%2582%25B9%25E3%2583%25A9%25E3%2582%25A4%25E3%2583%258929.PNG" imageanchor="1" ><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjKIcsTghM5Ze6lLsLKtZE-FP-mGl5fxH2hwni01K6zWxqS0ZRhWcXU5ED3gBjMvlhBR2CWIFfbbviQgR9pS4u-7LGG7jxuQ6k1_5SmtAJPmHfoJfuhQQuwqMRIs-z136Qk2Y5ABSpMSCY/s640/%25E3%2582%25B9%25E3%2583%25A9%25E3%2582%25A4%25E3%2583%258929.PNG" width="640" height="451" data-original-width="1126" data-original-height="793" /></a>
<br/>
今回はベースイメージをCentOSにしましたので、Dockerfileは最低限以下の内容から始まります。
<pre style="overflow-x: scroll; white-space: pre">
FROM centos:7
MAINTAINER Satoshi Nagayasu <snaga@uptime.jp>
</pre>
<br/>
docker buildでイメージが作成できて、docker run /bin/bash で起動や動作に問題がないことが確認できたら、少しずつコンテンツを追加していきます。<br/>
<br/>
今回イメージを作成する際、最初はOracle Instant ClientのRPMファイルをローカルに置いてrpm -iしていたのですが、Githubで管理する場合には50MB以上のファイルは置けないため、s3にファイルを置いてHTTP経由で直接rpm -iするように切り替えました。
<pre style="overflow-x: scroll; white-space: pre">
#ADD oracle-instantclient12.2-basic-12.2.0.1.0-1.x86_64.rpm /tmp
#ADD oracle-instantclient12.2-devel-12.2.0.1.0-1.x86_64.rpm /tmp
# Oracle support
RUN rpm -ivh https://s3-ap-northeast-1.amazonaws.com/uptime-dev01/oracle/oracle-instantclient12.2-basic-12.2.0.1.0-1.x86_64.rpm
RUN rpm -ivh https://s3-ap-northeast-1.amazonaws.com/uptime-dev01/oracle/oracle-instantclient12.2-devel-12.2.0.1.0-1.x86_64.rpm
#RUN rpm -ivh /tmp/oracle-instantclient12.2-basic-12.2.0.1.0-1.x86_64.rpm
#RUN rpm -ivh /tmp/oracle-instantclient12.2-devel-12.2.0.1.0-1.x86_64.rpm
</pre>
の部分になります。
<h4 class="post-body">■Dockerイメージを使ってデータベースプロファイリングをする</h4><br/>
というわけで、作成したDockerイメージを使ってみます。<br/>
<br/>
今回、DockerイメージはDocker Hubに登録してありますので、まずはそちらをdocker pullで取得します。<br/>
<br/>
<iframe src="https://player.vimeo.com/video/243096186" width="640" height="480" frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe><br/>
<p><a href="https://vimeo.com/243096186">Pulling a docker image</a> from <a href="https://vimeo.com/user19493714">Satoshi Nagayasu</a> on <a href="https://vimeo.com">Vimeo</a>.</p>
次に、クライアントを動かして、Oracle Databaseに接続してデータベースのプロファイリングを行います。<br/>
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgnzQSPhO_NMR8rSoMy6tmK95Z72GPlA8QHNB-T252KLEb5YuWG5cymtPc8767l3o-xOSvI4s1q1VL6fhaxK9dzPH7F543wxT0TSBGfonRfYaiiV4fQw2APt2vaqJS-EdCajdPWsTiUh-c/s1600/%25E3%2582%25B9%25E3%2583%25A9%25E3%2582%25A4%25E3%2583%258923.PNG" imageanchor="1" ><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgnzQSPhO_NMR8rSoMy6tmK95Z72GPlA8QHNB-T252KLEb5YuWG5cymtPc8767l3o-xOSvI4s1q1VL6fhaxK9dzPH7F543wxT0TSBGfonRfYaiiV4fQw2APt2vaqJS-EdCajdPWsTiUh-c/s640/%25E3%2582%25B9%25E3%2583%25A9%25E3%2582%25A4%25E3%2583%258923.PNG" width="640" height="451" data-original-width="1126" data-original-height="793" /></a><br/>
<br/>
背景が水色の部分はDockerのコマンドとオプション、白い部分はHecatoncheirのコマンドとオプションです。<br/>
<br/>
最終的には、ホストの /tmp/docker というディレクトリ(Dockerコンテナ内では /docker ディレクトリ)に repo.db というファイルが作成され、プロファイリングした結果がそこに保存されます。<br/>
<br/>
<iframe src="https://player.vimeo.com/video/243096558" width="640" height="480" frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe><br/>
<p><a href="https://vimeo.com/243096558">Running data profiling</a> from <a href="https://vimeo.com/user19493714">Satoshi Nagayasu</a> on <a href="https://vimeo.com">Vimeo</a>.</p>
<h4 class="post-body">■プロファイリングしたデータを閲覧する</h4><br/>
最後にサーバを起動して、レポジトリの情報を閲覧可能にします。<br/>
<br/>
ブラウザ経由で、データの状態を確認することができます。<br/>
<br/>
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjVcr4lNid3IRc6cSpA_kmayQEBoTaM_MOgKmqLRmNRyDgxAVpjhXf2Rz4dZParHOUU-ZH22wohesOEL9zqofq0HQ4FOevcy5Pr50yC5EmKJxrAFqAUjHX7ccqBbDJuGpXeHJyGDzGurSo/s1600/%25E3%2582%25B9%25E3%2583%25A9%25E3%2582%25A4%25E3%2583%258924.PNG" imageanchor="1" ><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjVcr4lNid3IRc6cSpA_kmayQEBoTaM_MOgKmqLRmNRyDgxAVpjhXf2Rz4dZParHOUU-ZH22wohesOEL9zqofq0HQ4FOevcy5Pr50yC5EmKJxrAFqAUjHX7ccqBbDJuGpXeHJyGDzGurSo/s640/%25E3%2582%25B9%25E3%2583%25A9%25E3%2582%25A4%25E3%2583%258924.PNG" width="640" height="451" data-original-width="1126" data-original-height="793" /></a>
<br/>
<iframe src="https://player.vimeo.com/video/243096673" width="640" height="480" frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe><br/>
<p><a href="https://vimeo.com/243096673">Running the repository server</a> from <a href="https://vimeo.com/user19493714">Satoshi Nagayasu</a> on <a href="https://vimeo.com">Vimeo</a>.</p>
<h4 class="post-body">■まとめ</h4><br/>
以上、今回はOracle Databaseに対応したツールをDocker化する方法についてご紹介しました。<br/>
<br/>
冒頭でも紹介しましたが、オープンソースのツールを使う/使ってもらう場合、依存関係が多くなり、環境構築がハードルになるケースが多々あります(慣れている人であっても)。<br/>
<br/>
特に、HecatoncheirのようにさまざまなDBMSに対応したツールを開発する場合、クライアントライブラリなども含めると依存関係が多岐に渡るため、Dockerは非常に便利であると感じました。<br/>
<br/>
ぜひ、データベースエンジニアにも新しいツールやソフトウェアを積極的に試してみていただければと思います。もちろん、Hecatoncheirも使ってみていただけると嬉しいです。<br/>
<br/>
では、また。<br/>
Satoshi Nagayasuhttp://www.blogger.com/profile/09706093898823214408noreply@blogger.com0tag:blogger.com,1999:blog-4546365304691285308.post-90838992270437195082017-12-03T08:27:00.001+09:002017-12-03T08:42:21.748+09:00Dockerを使ってデータ分析用にPostgreSQLを使ってみるこれは <a href="https://qiita.com/advent-calendar/2017/postgresql">PostgreSQL Advent Calendar 2017</a> の Day3 の記事です。昨日はMorihayaさんの「<a href="https://qiita.com/Morihaya/items/a7f49b4bd2d69cdec307">DB Management tool新時代の幕開けか!? OmniDBを評価させていただく!</a>」でした。<br/>
<br/>
さて、最近ようやくDockerに触り始めたのですが、使い方が少しずつ分かってきたのでいろいろと遊んでいます。<br/>
<br/>
今回は、In-Database AnalyticsとDockerです。
<h4 class="post-body">■全部入りのDockerイメージを作ってみた</h4><br/>
最近、In-Database Analyticsがマイブームになっていますので、ボチボチと遊んでいます。<br/>
<br/>
いろいろ遊んではいるのですが、いろいろセットアップしたり変更したり、アレが足りない、コレが動かない、みたいなことをやっているのが面倒になってきていました。面倒さが原因で手が動かないことも。。<br/>
<br/>
これはいかん。<br/>
<br/>
というわけで、データ分析に使えそうなExtensionをいろいろと入れ込んだ(自分的な)全部入りのPostgreSQLのDockerイメージを作ってみました。<br/>
<ul>
<li>CentOS 7</li>
<ul>
<li>Python 2.7</li>
</ul>
<li>PostgreSQL 10.1</li>
<ul>
<li>PL/Python</li>
<li>postgres_fdw</li>
</ul>
<li>PL/R 8.3.0.17</li>
<ul>
<li>R 3.4.2</li>
</ul>
<li>Apache MADlib 1.13-dev</li>
<li>pg_bigm 1.2</li>
<li>mecab 0.996</li>
<ul>
<li>mecab-ipadic 2.7.0</li>
<li>mecab-python 0.993</li>
</ul>
<li>numpy / scipy / scikit-learn / pandas / matplotlib</li>
</ul>
おかげで1.7GBもあるイメージになってしまいましたが、まぁそこはご愛敬、ということで。<br/>
<br/>
<a name='more'></a>
とりあえず、Rのグラフィック関連のパッケージはゴリゴリと削ってたのですが、numpyとかscipyとかが案外大きいんですね。。無理して削った関係上、動かない機能があったらすみません。連絡いただければ修正する所存です。。
<h4 class="post-body">■Dockerコンテナを起動してPostgreSQLに接続する</h4><br/>
まぁ何はともあれ、まずは使ってみましょう。起動するコマンドは以下です。
<pre style="overflow-x: scroll; white-space: pre">
docker pull uptimejp/postgres4analytics
</pre>
docker imagesを叩くとこんな感じになります。
<pre style="overflow-x: scroll; white-space: pre">
# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
docker.io/uptimejp/postgres4analytics latest 3421a544c9a5 7 hours ago 1.722 GB
docker.io/centos 7 196e0ce0c9fb 11 weeks ago 196.6 MB
#
</pre>
<pre style="overflow-x: scroll; white-space: pre">
docker run -p 5432:5432 -ti uptimejp/postgres4analytics
</pre>
<br/>
簡単ですねー。イメージが大きい関係上、pullに少し時間がかかります。<br/>
<br/>
Dockerコンテナを起動するとPostgreSQLのバックエンドが起動している様子が分かります。
<pre style="overflow-x: scroll; white-space: pre">
# docker run -p 5432:5432 -ti uptimejp/postgres4analytics
2017-12-02 00:02:07.922 UTC [5] LOG: listening on IPv4 address "0.0.0.0", port 5432
2017-12-02 00:02:07.922 UTC [5] LOG: listening on IPv6 address "::", port 5432
2017-12-02 00:02:07.939 UTC [5] LOG: listening on Unix socket "/var/run/postgresql/.s.PGSQL.5432"
2017-12-02 00:02:07.949 UTC [5] LOG: listening on Unix socket "/tmp/.s.PGSQL.5432"
2017-12-02 00:02:07.961 UTC [5] LOG: redirecting log output to logging collector process
2017-12-02 00:02:07.961 UTC [5] HINT: Future log output will appear in directory "log".
</pre>
<br/>
ここで別のターミナルからPostgreSQLに接続します。<br/>
<br/>
ユーザ postgres で、データベース template2 に接続します。<br/>
<br/>
この template2 データベースが全部入りをセットアップしたデータベースになります。(template1 には MADlib をうまくセットアップできなかったためです・・・)
もちろん postgres はスーパーユーザーですが、その辺は気にしない方向で。<br/>
<pre style="overflow-x: scroll; white-space: pre">
$ psql -h localhost -U postgres template2
psql (10.1)
Type "help" for help.
template2=# select version();
version
---------------------------------------------------------------------------------------------------------
PostgreSQL 10.1 on x86_64-pc-linux-gnu, compiled by gcc (GCC) 4.8.5 20150623 (Red Hat 4.8.5-16), 64-bit
(1 row)
template2=#
</pre>
<br/>
<h4 class="post-body">■各種Extensionを見てみる</h4><br/>
というわけで、セットアップされたExtension諸々を見てみます。
<pre style="overflow-x: scroll; white-space: pre">
template2=# \dx
List of installed extensions
Name | Version | Schema | Description
--------------+----------+------------+------------------------------------------------------------------
pg_bigm | 1.2 | public | text similarity measurement and index searching based on bigrams
plpgsql | 1.0 | pg_catalog | PL/pgSQL procedural language
plpythonu | 1.0 | pg_catalog | PL/PythonU untrusted procedural language
plr | 8.3.0.17 | public | load R interpreter and execute R script from within a database
postgres_fdw | 1.0 | public | foreign-data wrapper for remote PostgreSQL servers
(5 rows)
template2=#
</pre>
<br/>
こんな感じで、いくつかのExtensionがあらかじめセットアップされています。<br/>
<br/>
まず PL/R。
<pre style="overflow-x: scroll; white-space: pre">
template2=# select r_version();
r_version
-------------------------------------------------
(platform,x86_64-redhat-linux-gnu)
(arch,x86_64)
(os,linux-gnu)
(system,"x86_64, linux-gnu")
(status,"")
(major,3)
(minor,4.2)
(year,2017)
(month,09)
(day,28)
("svn rev",73368)
(language,R)
(version.string,"R version 3.4.2 (2017-09-28)")
(nickname,"Short Summer")
(14 rows)
template2=#
</pre>
おぉ、動いてるっぽい。<br/>
<br/>
続いてApache MADlib。
<pre style="overflow-x: scroll; white-space: pre">
template2=# select madlib.version();
version
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
---------------------------------------
MADlib version: 1.13-dev, git revision: unknown, cmake configuration time: Sat Nov 25 07:43:17 UTC 2017, build type: RelWithDebInfo, build system: Linux-3.10.0-693.2.1.el7.x86_64, C compil
er: gcc 4.8.5, C++ compiler: g++ 4.8.5
(1 row)
template2=#
</pre>
1.13-devが入ってますね。<br/>
<br/>
次。なぜか勢いで入れてしまったpg_bigm。
<pre style="overflow-x: scroll; white-space: pre">
template2=# select show_bigm('進捗は如何ですか。');
show_bigm
-------------------------------------------------------
{"。 ",か。,すか,です,は如,何で,如何,捗は,進捗," 進"}
(1 row)
template2=#
</pre>
きちんと動いてます。ところで進捗は如何ですか。<br/>
<br/>
次、みんな大好きPythonもPostgreSQLで動きます。ここでは以下のようにMecabを使って形態素解析をするUDFを作って動作確認して見ます。
<pre style="overflow-x: scroll; white-space: pre">
CREATE OR REPLACE FUNCTION mecab_tokenize(string text)
RETURNS text[]
AS $$
import MeCab
import plpy
a = []
m = MeCab.Tagger("-Ochasen")
"""
Mecabに渡すためにはunicodeではなくutf-8である必要がある。
Mecabから戻ってきたらunicodeに戻す。
また、Mecabはエンコード済みのutf-8文字列へのポインタを返すので、
on-the-flyでutf-8に変換するのではなく、変数として保持しておく
必要がある。(でないとメモリ領域がGCで回収されてデータが壊れる)
参照:
http://shogo82148.github.io/blog/2012/12/15/mecab-python/
"""
enc_string = string
node = m.parseToNode(enc_string)
while node:
n = node.surface.decode('utf-8')
if n:
a.append(n)
node = node.next
return a
$$ LANGUAGE plpythonu;
</pre>
<br/>
動かしてみます。
<pre style="overflow-x: scroll; white-space: pre">
template2=# select mecab_tokenize('進捗は如何ですか。');
mecab_tokenize
---------------------------
{進捗,は,如何,です,か,。}
(1 row)
template2=#
</pre>
動いてます。進捗は如何ですか。<br/>
<br/>
こんな感じで、このDockerイメージを使うと、「聞いたことはあるけれど使ったことは無い」ものが一瞬(?)で使えるようになります。<br/>
<br/>
あと、postgres_fdwなどもセットアップされていますので、リモートにあるPostgreSQLのテーブルをそのまま取ってくる、みたいなことも簡単に(?)できます。できるはずです。
<h4 class="post-body">■Dockerfile</h4><br/>
ちなみに、Dockerfileは以下から入手できます。<br/>
<ul>
<li>postgres4analytics <br/><a href="https://github.com/snaga/postgres4analytics">https://github.com/snaga/postgres4analytics</a></li>
</ul>
こういうの追加したい、みたいなのがあれば、forkしていじってみるなり、プルリクしてみるなりして頂ければと思います。
<h4 class="post-body">■まとめ</h4><br/>
そんなわけで、全部入りPostgreSQL(しかも最新版)を作ってみた、というお話でした。自分も、今後はこれを使っていこうと思っています。<br/>
<br/>
Happy In-Database Analytics Lifeを!<br/>
<br/>
では。<br/>
<br/>
PostgreSQL Advent Calendar 2017、明日は<a href="https://twitter.com/vidaisuki">vidaisuki</a>さんです。<br/>
Satoshi Nagayasuhttp://www.blogger.com/profile/09706093898823214408noreply@blogger.com0tag:blogger.com,1999:blog-4546365304691285308.post-19780476873236344662017-11-28T18:28:00.001+09:002017-11-29T22:19:58.485+09:00[翻訳] たった一つの設定変更が如何にしてクエリのパフォーマンスを50倍も改善したか (How a single PostgreSQL config change improved slow query performance by 50x)先日、「How a single PostgreSQL config change improved slow query performance by 50x」というPostgreSQLのSSD環境でのチューニングの記事を見つけたのですが、これをTweetしたらRTやLikeを比較的たくさん頂きました。
<ul>
<li><a href="https://amplitude.engineering/how-a-single-postgresql-config-change-improved-slow-query-performance-by-50x-85593b8991b0">How a single PostgreSQL config change improved slow query performance by 50x<br/>https://amplitude.engineering/how-a-single-postgresql-config-change-improved-slow-query-performance-by-50x-85593b8991b0</a></li>
</ul>
<blockquote class="twitter-tweet" data-cards="hidden" data-lang="ja"><p lang="en" dir="ltr">How a single PostgreSQL config change improved slow query performance by 50x <a href="https://t.co/PdzosNoeYN">https://t.co/PdzosNoeYN</a></p>— Satoshi Nagayasu 🦃 (@snaga) <a href="https://twitter.com/snaga/status/934946553432645632?ref_src=twsrc%5Etfw">2017年11月27日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
日本でも興味を持つ方がいるかもと思い、オリジナルの著者の方に許可をもらったので翻訳したものを対訳形式で掲載します。<br/>
<br/>
オリジナル版と併せて、よろしければご覧ください。<br/>
<hr/>
<h4 class="post-body">■How a single PostgreSQL config change improved slow query performance by 50x<br/>■たった一つの設定変更が如何にしてクエリのパフォーマンスを50倍も改善したか</h4><br/>
Pavan Patibandla<br/>
<br/>
At Amplitude, our goal is to provide easy-to-use interactive product analytics, so everyone can find answers to their product questions. In order to provide a great user experience, Amplitude needs to provide these answers quickly. So when one of our customers complained about how long it took to load the event properties dropdown in the Amplitude UI, we started digging into it.<br/>
<br/>
Amplitudeでの我々のゴールは、簡単に使えるインタラクティブなプロダクト分析を提供することです。それによって、すべての人が自分たちのプロダクトについての疑問の答えを得ることができるようになります。素晴らしいユーザエクスペリエンスを提供するために、Amplitudeは答えを迅速に提供する必要があります。ある顧客から Amplitude UI でイベントプロパティのドロップダウンリストのロードに時間がかかると苦情が来たため、その調査を開始しました。<br/>
<br/>
<a name='more'></a>
By tracking latency at different levels we figured one particular PostgreSQL query was taking 20 sec to finish. This was a surprise for us, as both tables have indexes on the joined column.<br/>
<br/>
さまざまなレベルで遅延を追跡した結果、ある特定のPostgreSQLのクエリが完了するのに20秒もかかっていることを突き止めました。これは我々にとって驚くべきことでした。というのも、JOINしている両方のテーブルとも、結合しているカラムにインデックスを作成してあったからです。<br/>
<br/>
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiWdiTXZ8q40uLL-Br1GsJNXDQVo-NS2RDkygUdmES9Keg2kB5y_S_pMQ6hemeLep4Yetg2sBMAJtnNst5aZJELSxPX7uPrsR6dnkgeSAseLn4r81Xn5oTkeXmch24NqJwnOTK0HWM6lSA/s1600/1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiWdiTXZ8q40uLL-Br1GsJNXDQVo-NS2RDkygUdmES9Keg2kB5y_S_pMQ6hemeLep4Yetg2sBMAJtnNst5aZJELSxPX7uPrsR6dnkgeSAseLn4r81Xn5oTkeXmch24NqJwnOTK0HWM6lSA/s1600/1.png" data-original-width="800" data-original-height="65" /></a>Slow Query</div>
<br/>
The PostgreSQL execution plan for this query was unexpected. Even though both tables have Indexes, PostgreSQL decided to do a Hash Join with a sequential scan on the large table. The sequential scan on a large table contributed to most of the query time.<br/>
<br/>
このクエリの実行プランは想定外のものでした。両方のテーブルにインデックスがあるにも関わらず、PostgreSQLは大きいテーブルに対してシーケンシャルスキャンとハッシュ結合を選択していました。大きなテーブルへのシーケンシャルスキャンは、クエリの実行時間の大部分を占めていました。<br/>
<br/>
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg5piL6pzRel2QYTBlq8thU0uBzS6vTlNcLRQdXn5GC18t43pB61o_hegB7gAt7zrIe9pc_5QFgt9xpsSijE0k7RvpItqSZ0xIBWw8TH_vJ9jGmqD_8lLdV3gFUXflVW_rkOhF3_PYjDWE/s1600/2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg5piL6pzRel2QYTBlq8thU0uBzS6vTlNcLRQdXn5GC18t43pB61o_hegB7gAt7zrIe9pc_5QFgt9xpsSijE0k7RvpItqSZ0xIBWw8TH_vJ9jGmqD_8lLdV3gFUXflVW_rkOhF3_PYjDWE/s1600/2.png" data-original-width="800" data-original-height="415" /></a>Slow Query Execution Plan</div>
<br/>
I initially suspected it could be due to fragmentation. But after inspecting the data, I realized this table was append only and there weren’t many deletions happening on this table. Since reclaiming space using vacuum is not going help much here, I started exploring more. Next, I tried the same query on another customer with good response times. To my surprise the query execution plan looked completely different!<br/>
<br/>
最初に私はフラグメンテーションを疑いました。しかし、データを調査した結果、当該テーブルは追記のみであり、削除はあまり発生していませんでした。領域を解放するVACUUMは助けにならなかったため、さらに深い調査を始めました。次に、同じクエリをレスポンスタイムの良い他の顧客のデータで試してみました。すると驚いたことに、クエリの実行プランは全く違ったものになったのです!<br/>
<br/>
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjOW5lWNpbi4oj4taJJ-x7-V_ra3S5EBlP-JbEtsNDXRn4vr0x5Fng-6qpkjfhtQuJGeVBYyj7SYtbaKlGMXdETOvAedUyEDYfcumALMUGhU8Km4JY3aM8f5eiKVWPpULIGnY2kwSVcLxA/s1600/3.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjOW5lWNpbi4oj4taJJ-x7-V_ra3S5EBlP-JbEtsNDXRn4vr0x5Fng-6qpkjfhtQuJGeVBYyj7SYtbaKlGMXdETOvAedUyEDYfcumALMUGhU8Km4JY3aM8f5eiKVWPpULIGnY2kwSVcLxA/s1600/3.png" data-original-width="800" data-original-height="329" /></a>Execution plan of similar query on another App</div>
<br/>
Interestingly, app A only accessed 10x more data than app B, but the response time was 3000x longer.<br/>
<br/>
興味深いことに、アプリAはアプリBと比べて10倍のデータにアクセスしているだけなのに、レスポンスタイムは3,000倍も長くかかっていました。<br/>
<br/>
To see the alternative query plans PostgreSQL considered before picking Hash Join, I disabled hash join and reran the query.<br/>
<br/>
PostgreSQLがハッシュ結合を選択する前に作成する異なるクエリプランを見てみるために、ハッシュ結合を無効にしてクエリを再度実行してみました。<br/>
<br/>
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEinBjYhzcWKYYe7Hx4ioBVnHM_l4ZEBg_Q78Px-agbf_jVYMGBAcsrgXhArflPV1PKImuZ0kTkJ5Qw5YAygERUdx-GJD6_Shhph9FnSTbZ9fc7PfjroOwn0pC8SE-cWtPmsAw73PCLJHR0/s1600/4.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEinBjYhzcWKYYe7Hx4ioBVnHM_l4ZEBg_Q78Px-agbf_jVYMGBAcsrgXhArflPV1PKImuZ0kTkJ5Qw5YAygERUdx-GJD6_Shhph9FnSTbZ9fc7PfjroOwn0pC8SE-cWtPmsAw73PCLJHR0/s1600/4.png" data-original-width="800" data-original-height="360" /></a>Alternative execution plan for Slow Query</div>
<br/>
There you go! The same query finished 50x faster when using a Nested Loop instead of a Hash Join. So why did PostgreSQL choose a worse plan for app A?<br/>
<br/>
やりました! ハッシュ結合ではなくネステッドループ結合を使った場合に、同じクエリが50倍も速くなりました。なぜ、PostgreSQLはアプリAで性能の悪い実行プランを選択したのでしょうか?<br/>
<br/>
Looking more closely at the estimated cost and actual run time for both plans, estimated cost to actual runtime ratios were very different. The main culprit for this discrepancy was the sequential scan cost estimation. PostgreSQL estimated that a sequential scan would be better than 4000+ index scans, but in reality index scans were 50x faster.<br/>
<br/>
両方の推定コスト(estimated cost)と実行時間(actual run time)を詳細に見てみると、推定コストと実行時間の比率が大きく異なっていることが分かります。この食い違いの犯人は、シーケンシャルスキャンのコスト推定の部分です。PostgreSQLは、インデックススキャンを4,000回するよりもシーケンシャルスキャンを1回する方がコストが低いと推定しましたが、実際にはインデックススキャンの方が50倍も速かったのです。<br/>
<br/>
That led me to the ‘random_page_cost’ and ‘seq_page_cost’ configuration options. The default PostgreSQL values of 4 and 1 for ‘random_page_cost’, ‘seq_page_cost’ respectively are tuned for HDD, where random access to disk is more expensive than sequential access. However these costs were inaccurate for our deployment using gp2 EBS volume, which are solid state drives. For our deployment random and sequential access is almost the same.<br/>
<br/>
このことから、私は random_page_cost と seq_page_cost の設定について考え始めました。PostgreSQLのデフォルトでは random_page_cost には 4 、seq_page_cost には 1 が設定されています。これはランダムアクセスの方がシーケンシャルアクセスよりもコストが高いハードディスクに合わせてチューニングされています。しかし、これらのコストは、我々がデプロイしている(AWSの)gp2 EBS ボリューム(SSDです)においては正確ではありません。我々のデプロイメント環境では、ランダムアクセスとシーケンシャルアクセスの性能はほぼ同じです。<br/>
<br/>
I changed ‘random_page_cost’ to 1 and retried the query. This time, PostgreSQL used a Nested Loop and the query finished 50x faster. After the change we also noticed a significant drop in max response times from PostgreSQL.<br/>
<br/>
私は random_page_cost を 1 に設定してクエリを再度実行してみました。この時には、PostgreSQLはネステッドループ結合を採用し、クエリは50倍速く実行されました。この設定を変更した後、我々のPostgreSQLの最大のレスポンスタイムは著しく短縮されました。<br/>
<br/>
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhejTj_hR3jq4tYNhRiodeqPBNjQnJhHE7jZwW24pdTdjvxC5oa2P_O5-s9n5tGcwLK7tDYjefCDYeldtBnH2NnxSbj8APGc4pImvrci26O6aAPM2wEl_a0tOk1w0BVRyz-0uq09ebuyo8/s1600/5.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhejTj_hR3jq4tYNhRiodeqPBNjQnJhHE7jZwW24pdTdjvxC5oa2P_O5-s9n5tGcwLK7tDYjefCDYeldtBnH2NnxSbj8APGc4pImvrci26O6aAPM2wEl_a0tOk1w0BVRyz-0uq09ebuyo8/s1600/5.png" data-original-width="800" data-original-height="382" /></a>Overall Slow Query performance improved significantly</div>
<br/>
If you are using SSDs and running PostgreSQL with default configuration, I encourage you to try tuning random_page_cost & seq_page_cost. You might be surprised by some huge performance improvements.<br/>
<br/>
もし、あなたがSSDを使っていて、PostgreSQLをデフォルトの設定で使っているのなら、 random_page_cost と seq_page_cost をチューニングしてみることをお勧めします。大きなパフォーマンス改善に驚くかもしれません。<br/>
<br/>
Has any other parameter tuning given you huge gains across the board? Let us know about it in the comments.<br/>
<br/>
他に何か全体的なパフォーマンスの大きな改善につながったパラメータチューニングをご存知でしょうか? コメント欄で教えていただければと思います。<br/>
<hr/>
以上です。<br/>
<br/>
最後で触れられているコメントについては、(英語で)オリジナル版の方にお願いいたします。<br/>
<br/>
Hacker Newsのコメントも面白いと思いますので、併せてどうぞ。<br/>
<br/>
<blockquote class="twitter-tweet" data-lang="ja"><p lang="en" dir="ltr">How a single PostgreSQL config change improved slow query performance by 50x | Hacker News <a href="https://t.co/QlpjDcWr89">https://t.co/QlpjDcWr89</a></p>— Satoshi Nagayasu 🦃 (@snaga) <a href="https://twitter.com/snaga/status/935483274964504576?ref_src=twsrc%5Etfw">2017年11月28日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<br/>
では、また。Satoshi Nagayasuhttp://www.blogger.com/profile/09706093898823214408noreply@blogger.com2tag:blogger.com,1999:blog-4546365304691285308.post-14855985340963926942017-08-25T12:55:00.000+09:002017-08-25T12:59:59.361+09:00【告知】9月9日(土)に関西DB勉強会で講演します<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgwoT6o5nCi0udDzwn7l7rPCyOT_l1RCaFahshW9NkgH8gqnddZzaFYjO4CUZYvKKMfuzZ-IUonmltotEtQCHzMmoYn3CSj7vMl8SpSZBYJh6XvpmGciKvMh35W51pRPLPytmo4QnH6-bI/s1600/%25E7%2584%25A1%25E9%25A1%258C.png" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgwoT6o5nCi0udDzwn7l7rPCyOT_l1RCaFahshW9NkgH8gqnddZzaFYjO4CUZYvKKMfuzZ-IUonmltotEtQCHzMmoYn3CSj7vMl8SpSZBYJh6XvpmGciKvMh35W51pRPLPytmo4QnH6-bI/s320/%25E7%2584%25A1%25E9%25A1%258C.png" width="320" height="271" data-original-width="1081" data-original-height="916" /></a></div>9月9日(土)に関西DB勉強会で講演します。
<ul>
<li><a href="https://kansaidbstudy.connpass.com/event/62974/">第6回 関西DB勉強会 - connpass<br/>
https://kansaidbstudy.connpass.com/event/62974/</a></li>
</ul>
<p>
「PostgreSQLエンジニアにとってのデータ分析プロジェクト:テクノロジーとその実践(仮)」というタイトルで、ここ3~4年のテクノロジーやスキル、経験、そこからの学びなどをごった煮でお送りする予定です。他にも面白そうなセッションが目白押しとなっております。
</p>
<p>
既にキャンセル待ちとなっておりますが、都合の付く方はぜひご参加ください。私からのトークだけではなく、いろいろな方と意見交換をできればと思っています。
</p>
<p>
よろしくお願いします。
</p>
Satoshi Nagayasuhttp://www.blogger.com/profile/09706093898823214408noreply@blogger.com0tag:blogger.com,1999:blog-4546365304691285308.post-31733307777279813602017-06-08T12:21:00.001+09:002017-06-08T12:39:10.834+09:00技術文書「PostgreSQL 10 Beta1 新機能検証結果」が公開されました<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi-E8MlHbPnhxafq-L3rv2Q331g6HlU2Mz6DDZqGnQu9W2ILSA5lWpulOwSAaBc5ahlFirYf-6yqWAEKpgAu8hUGPp-V_BamEa4zEMe6qfZEVnl6eoegKGnBqUdQKKtxL7Zn01PEQfIlxE/s1600/pg10.png" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi-E8MlHbPnhxafq-L3rv2Q331g6HlU2Mz6DDZqGnQu9W2ILSA5lWpulOwSAaBc5ahlFirYf-6yqWAEKpgAu8hUGPp-V_BamEa4zEMe6qfZEVnl6eoegKGnBqUdQKKtxL7Zn01PEQfIlxE/s320/pg10.png" width="226" height="320" data-original-width="676" data-original-height="956" /></a></div>
少し前の話になりますが、みなさんお馴染みとなりつつある日本HP篠田さんから PostgreSQL 10 beta1 の資料が公開されました。<br/>
<ul>
<li><a href="https://community.hpe.com/t5/%E6%97%A5%E6%9C%AC%E3%81%AE%E3%81%8A%E5%AE%A2%E6%A7%98%E5%90%91%E3%81%91-%E3%82%A8%E3%83%B3%E3%82%BF%E3%83%BC%E3%83%97%E3%83%A9%E3%82%A4%E3%82%BA-%E3%83%88%E3%83%94%E3%83%83%E3%82%AF%E3%82%B9/%E7%AF%A0%E7%94%B0%E3%81%AE%E8%99%8E%E3%81%AE%E5%B7%BB-%E7%AC%AC7%E5%BC%BE%E5%85%AC%E9%96%8B-PostgreSQL-10-beta-1%E3%81%AB%E5%AF%BE%E5%BF%9C/ba-p/6965982?profile.language=ja#.WTi2vm6kJhF">HP コミュニティ - 『篠田の虎の巻』 第7弾公開!PostgreSQL 10 beta 1に対応! - エンタープライズ・ビジネス・コミュニティ</a></li>
</ul>
<blockquote class="twitter-tweet" data-partner="tweetdeck"><p lang="ja" dir="ltr">PostgreSQL 10 Beta 1 の新機能を検証した資料を公開しました。<a href="https://t.co/uFi49d095c">https://t.co/uFi49d095c</a><a href="https://twitter.com/hashtag/PostgreSQL10?src=hash">#PostgreSQL10</a> <a href="https://twitter.com/hashtag/PostgreSQL?src=hash">#PostgreSQL</a></p>— Noriyoshi Shinoda (@nori_shinoda) <a href="https://twitter.com/nori_shinoda/status/868108814469406720">May 26, 2017</a></blockquote>
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>
今回、私も事前レビューに参加させてもらいました。<br/>
<br/>
本ドキュメントは全体で100ページ以上あり、以下のような構成になっています。<br/>
<br/>
1. 本文書について<br/>
2. バージョン表記<br/>
3. 新機能解説<br/>
3.1 PostgreSQL 10における変更点概要<br/>
3.2 パーティション・テーブル<br/>
3.3 Logical Replication<br/>
3.4 パラレル・クエリーの拡張<br/>
3.5 アーキテクチャの変更<br/>
3.6 モニタリング<br/>
3.7 Quorum-based同期レプリケーション<br/>
3.8 Row Level Securityの拡張<br/>
3.9 SQL文の拡張<br/>
3.10 パラメーターの変更<br/>
3.11 ユーティリティの変更<br/>
3.12 Contribモジュール<br/>
参考にしたURL<br/>
<br/>
<a name='more'></a>
正直、私も PostgreSQL 10 の機能はほとんど追えていないのと、このドキュメントも100ページ以上あってきちんと理解するのはそれなりに大変なわけですが、そんな人のために「3.1 PostgreSQL 10における変更点概要」という数ページのセクションを用意していただきました。<br/>
<br/>
まだPostgreSQLにそれほど詳しくない方、理解に時間をかけられない方でも、この数ページをざっと読んでおくだけでも、それなりに理解できるのではないかと思います。<br/>
<br/>
最近、篠田さんは本家のGitのコミットログまでほぼリアルタイムに読むようになっているようで、開発内容のキャッチアップの速さと正確さは「梵天丸もかくありたいが多分無理」というレベルに達しつつある気がします。<br/>
<br/>
そんな著者による解説ですので、ぜひこの機会に目を通しておくことをお勧めします。<br/>
<br/>
では、また。<br/>
Satoshi Nagayasuhttp://www.blogger.com/profile/09706093898823214408noreply@blogger.com0tag:blogger.com,1999:blog-4546365304691285308.post-3011183422260857972017-05-14T16:15:00.005+09:002017-05-14T16:15:46.509+09:00Azure Database for PostgreSQLにアクセスしてみた<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEizk5QXMg48FFF3b7va-dBXcgsgoy7dH6O4_rXZGf831ibUtZPvARaLOckb72yReBs0-7DtVVl3iLi3UWP-UwVab6xnGw4busCSCW0625L-SF8-skECZB03zD-jbzYCZ4bJZrf5UvK4Qvs/s1600/000.png" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEizk5QXMg48FFF3b7va-dBXcgsgoy7dH6O4_rXZGf831ibUtZPvARaLOckb72yReBs0-7DtVVl3iLi3UWP-UwVab6xnGw4busCSCW0625L-SF8-skECZB03zD-jbzYCZ4bJZrf5UvK4Qvs/s400/000.png" width="400" height="314" /></a></div>5/11のMicrosoft Build 2017で、PostgreSQLのDBaaSがAzureで提供されることが発表されました。<br/>
<ul>
<li>[速報]マイクロソフト、Azureで「MySQL」「PostgreSQL」のデータベースサービス提供を発表、運用の手間は不要。Build 2017 - Publickey <br/><a href="http://www.publickey1.jp/blog/17/azuremysqlpostgresqlbuild_2017.html">http://www.publickey1.jp/blog/17/azuremysqlpostgresqlbuild_2017.html</a></li>
</ul>
現時点ではプレビューのようですが、ちょっと興味があったので軽く触ってみました。<br/>
<br/>
ちなみに、Azureは普段使っていないのでそんなに詳しくありません。
<h4 class="post-body">■PostgreSQLのリソースを作成する</h4><br/>
まず、Azureのダッシュボードで「PostgreSQL」と検索すると、PostgreSQLのリソースが出てきます。<br/>
<br/>
<a name='more'></a>
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjEevC-rbID-DbOef1-ZMcL5aJ5OUqWf4_vMf4VTXylh-UFJ2oSmYRIXsA_FJDVAiNngsEEJLhtLPFxwlsc3cEqKs6SsgYWgeuyhQfYHW93j7OxNkm9JYmXGMNZlCcd_sWP4l49sg8vdno/s1600/001.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjEevC-rbID-DbOef1-ZMcL5aJ5OUqWf4_vMf4VTXylh-UFJ2oSmYRIXsA_FJDVAiNngsEEJLhtLPFxwlsc3cEqKs6SsgYWgeuyhQfYHW93j7OxNkm9JYmXGMNZlCcd_sWP4l49sg8vdno/s400/001.png" width="400" height="233" /></a></div>
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEio3W1Idn-xC78k7TYJ5V0RjvIJmLj3s7MhIvUEiTeGYRBvy8TkxTmWtl7C8qnMEFUA_my5AsYiw5EPZ5BrhqsIVc0HHLfJeOfImXLcbjjoqyIwQTAeooAc9Xfnf433xTmeusZkwRZLt-o/s1600/002.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEio3W1Idn-xC78k7TYJ5V0RjvIJmLj3s7MhIvUEiTeGYRBvy8TkxTmWtl7C8qnMEFUA_my5AsYiw5EPZ5BrhqsIVc0HHLfJeOfImXLcbjjoqyIwQTAeooAc9Xfnf433xTmeusZkwRZLt-o/s400/002.png" width="346" height="400" /></a></div>
<br/>
そこで「追加」を選び、入力項目を適当に埋めて「作成」を実行すると、数分経ってPostgreSQLのリソースが作成されます。<br/>
<br/>
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg6M9l2epqHUtDDEUxozbFP4hLNxqru9UuZwt-bcMiasVQJHbGAz3XBahiq1XJOHnu9l5l-s4dN49iMma5TRTODoX2pyO9WLe4uMjSez6haPp7Xx6K5aFOuu_IRwjcK1OPvFYU-3Lod_3I/s1600/003.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg6M9l2epqHUtDDEUxozbFP4hLNxqru9UuZwt-bcMiasVQJHbGAz3XBahiq1XJOHnu9l5l-s4dN49iMma5TRTODoX2pyO9WLe4uMjSez6haPp7Xx6K5aFOuu_IRwjcK1OPvFYU-3Lod_3I/s400/003.png" width="400" height="234" /></a></div>
<h4 class="post-body">■PostgreSQLに接続する</h4><br/>
リソースが作成されただけだと外部から接続ができないので、「設定」→「接続のセキュリティ」から接続を許可するネットワークを指定します。<br/>
<br/>
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiwbe3lqjk8yzohY4t1mqwVaGrLpdJZQVHxY_J9-2KmXH5vzerCgFZZJX_BPWZ9vuFtygRlXRjuYQ-d_pOHpH_ra4dCrKRMui08sP9IUaLbgOMwo8XjYEDIKRLMW1FSukGRdbjiU9M-U30/s1600/004.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiwbe3lqjk8yzohY4t1mqwVaGrLpdJZQVHxY_J9-2KmXH5vzerCgFZZJX_BPWZ9vuFtygRlXRjuYQ-d_pOHpH_ra4dCrKRMui08sP9IUaLbgOMwo8XjYEDIKRLMW1FSukGRdbjiU9M-U30/s400/004.png" width="400" height="234" /></a></div>
<br/>
すると、外部からでもpsqlで普通に接続できるようになりました。<br/>
<pre style="overflow-x: scroll; white-space: pre">
[snaga@localhost ~]$ psql -h hostname.postgres.database.azure.com -U snaga@hostname postgres
Password for user snaga@hostname:
psql (9.6.0, server 9.6.2)
SSL connection (protocol: TLSv1.2, cipher: ECDHE-RSA-AES256-SHA384, bits: 256, compression: off)
Type "help" for help.
postgres=> select version();
version
-------------------------------------------------------------
PostgreSQL 9.6.2, compiled by Visual C++ build 1800, 64-bit
(1 row)
</pre>
<br/>
ちなみに、Azureで「サーバー管理者ログイン名」として作成しているユーザは、PostgreSQLで言うところのスーパーユーザーではありませんので注意が必要でしょう。まぁ、PostgreSQLのDBaaSはどこも同じだと思いますが。<br/>
<pre style="overflow-x: scroll; white-space: pre">
postgres=> select * from pg_user;
usename | usesysid | usecreatedb | usesuper | userepl | usebypassrls | passwd | valuntil | useconfig
-----------------+----------+-------------+----------+---------+--------------+----------+----------+-----------
azure_superuser | 10 | t | t | t | t | ******** | |
snaga | 12900 | t | f | f | f | ******** | |
(2 rows)
</pre>
Collationは「C」ではなかったので、この辺は注意が必要かもしれません。
<pre style="overflow-x: scroll; white-space: pre">
postgres=> \l
List of databases
Name | Owner | Encoding | Collate | Ctype | Access privileges
-----------+-----------------+----------+----------------------------+----------------------------+-------------------------------------
postgres | azure_superuser | UTF8 | English_United States.1252 | English_United States.1252 |
template0 | azure_superuser | UTF8 | English_United States.1252 | English_United States.1252 | =c/azure_superuser +
| | | | | azure_superuser=CTc/azure_superuser
template1 | azure_superuser | UTF8 | English_United States.1252 | English_United States.1252 | =c/azure_superuser +
| | | | | azure_superuser=CTc/azure_superuser
(3 rows)
</pre>
<br/>
<h4 class="post-body">■EXTENSIONを見てみる</h4><br/>
さて、PostgreSQL と言えば EXTENSION です。<br/>
<br/>
というわけで、最初からセットアップされている EXTENSION と、追加で使える EXTENSION を確認してみました。<br/>
<br/>
以下は最初からインストールされている EXTENSION です。<br/>
<pre style="overflow-x: scroll; white-space: pre">
postgres=> select * from pg_extension;
extname | extowner | extnamespace | extrelocatable | extversion | extconfig | extcondition
--------------------+----------+--------------+----------------+------------+-----------+--------------
plpgsql | 10 | 11 | f | 1.0 | |
pg_stat_statements | 10 | 2200 | t | 1.4 | |
pg_buffercache | 10 | 2200 | t | 1.2 | |
(3 rows)
</pre>
<br/>
以下は CREATE EXTENSION を実行すれば使える EXTENSION です。プレビューだからか、ちょっと少な目な感じでしょうか。個人的には PL/Python などが使えるといいのになぁと思います。<br/>
<pre style="overflow-x: scroll; white-space: pre">
postgres=> select * from pg_available_extensions;
name | default_version | installed_version | comment
------------------------------+-----------------+-------------------+---------------------------------------------------------------------------------------------------------------------
address_standardizer | 2.3.2 | | Used to parse an address into constituent elements. Generally used to support geocoding address normalization step.
address_standardizer_data_us | 2.3.2 | | Address Standardizer US dataset example
btree_gin | 1.0 | | support for indexing common datatypes in GIN
btree_gist | 1.2 | | support for indexing common datatypes in GiST
citext | 1.3 | | data type for case-insensitive character strings
fuzzystrmatch | 1.1 | | determine similarities and distance between strings
hstore | 1.4 | | data type for storing sets of (key, value) pairs
intarray | 1.2 | | functions, operators, and index support for 1-D arrays of integers
pgcrypto | 1.3 | | cryptographic functions
pgrouting | 2.3.2 | | pgRouting Extension
pg_buffercache | 1.2 | 1.2 | examine the shared buffer cache
pg_partman | 2.6.3 | | Extension to manage partitioned tables by time or ID
pg_prewarm | 1.1 | | prewarm relation data
pg_stat_statements | 1.4 | 1.4 | track execution statistics of all SQL statements executed
pg_trgm | 1.3 | | text similarity measurement and index searching based on trigrams
plpgsql | 1.0 | 1.0 | PL/pgSQL procedural language
postgis | 2.3.2 | | PostGIS geometry, geography, and raster spatial types and functions
postgis_sfcgal | 2.3.2 | | PostGIS SFCGAL functions
postgis_tiger_geocoder | 2.3.2 | | PostGIS tiger geocoder and reverse geocoder
postgis_topology | 2.3.2 | | PostGIS topology spatial types and functions
postgres_fdw | 1.0 | | foreign-data wrapper for remote PostgreSQL servers
unaccent | 1.1 | | text search dictionary that removes accents
uuid-ossp | 1.1 | | generate universally unique identifiers (UUIDs)
(23 rows)
</pre>
<br/>
ちなみに、スーパーユーザー権限ではないユーザでCREATE EXTENSIONできるんだっけ?というのを確認してみたのですが、一応、CREATE EXTENSIONできるようです。<br/>
<pre style="overflow-x: scroll; white-space: pre">
postgres=> create extension hstore;
CREATE EXTENSION
postgres=> create extension postgis;
CREATE EXTENSION
postgres=> create extension pg_trgm;
CREATE EXTENSION
postgres=>
</pre>
<br/>
<h4 class="post-body">■AzureのPostgreSQLリソースの設定項目</h4><br/>
ざっと見た感じ、以下のような設定ができるようです。<br/>
<br/>
サーバパラメータ<br/>
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhX5CSxrsRjd7YUZ0t3qRtPUacNG227iN_MA_AzdMc1Wr8D8kF0XLOojiHYTL4XscOI8iklrXIyDuA6fRUif9cgDoMu6L9fwAD2vG0Ar8qdFStjVL1LRUh7EeiBQHW2qRSHs117btkF0vU/s1600/005.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhX5CSxrsRjd7YUZ0t3qRtPUacNG227iN_MA_AzdMc1Wr8D8kF0XLOojiHYTL4XscOI8iklrXIyDuA6fRUif9cgDoMu6L9fwAD2vG0Ar8qdFStjVL1LRUh7EeiBQHW2qRSHs117btkF0vU/s400/005.png" width="400" height="234" /></a></div>
<br/>
監視できるメトリックス<br/>
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhJRkFB6iZuekFjWbBip6eYRP3VvJRHQ-JSITXzJgtqLThcDMNKtYUaLDBwFn9BvnlvU0VMAHta9NmMAFa3pqwp0sNCfKjHGvZWsGrRU6Bd_-oc5CaVbjguS-o1TdrpLTO7eIGbG3RKLeA/s1600/006.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhJRkFB6iZuekFjWbBip6eYRP3VvJRHQ-JSITXzJgtqLThcDMNKtYUaLDBwFn9BvnlvU0VMAHta9NmMAFa3pqwp0sNCfKjHGvZWsGrRU6Bd_-oc5CaVbjguS-o1TdrpLTO7eIGbG3RKLeA/s400/006.png" width="400" height="234" /></a></div>
<br/>
サーバログ<br/>
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjV3XzO9FsgUmFyxmt5_VGFPsF913_2zfwn835GctVryPMUyBDlkBipqTwpTVDGR-iKktYppVeNsvDmQGR30ScI5IswisfeM7xUmuhYoecw0Y9iAZFPrt2hB45_lfu9FCsy65llIVryk2Q/s1600/007.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjV3XzO9FsgUmFyxmt5_VGFPsF913_2zfwn835GctVryPMUyBDlkBipqTwpTVDGR-iKktYppVeNsvDmQGR30ScI5IswisfeM7xUmuhYoecw0Y9iAZFPrt2hB45_lfu9FCsy65llIVryk2Q/s400/007.png" width="400" height="234" /></a></div>
<h4 class="post-body">■まとめ</h4><br/>
というわけで、ざっと Azure Database for PostgreSQL を見てみました。<br/>
<br/>
このエントリは、Azure Database for PostgreSQL を使い始めるところから1時間弱くらいで書き上げているわけですが、DBaaSは楽だなぁと改めて感じました。<br/>
<br/>
一方で、PostgreSQLの良さの一部には、アプリケーション開発における拡張性や柔軟性の高さがあったりするわけですが、この辺りをDBaaSでどこまで実現できるのかというのが(サービス提供側の)難しさなのかなぁ、という気もします。(まぁ、使える EXTENSION とかは決めの問題のような気もしますが。)<br/>
<br/>
Azure Database for PostgreSQL はDBaaSとしては後発だと思うので、後発ならではの工夫や特徴があるともっと面白くなりそうだなぁと感じました(MADlibが使える、とか)。<br/>
<br/>
では、また。<br/>
Satoshi Nagayasuhttp://www.blogger.com/profile/09706093898823214408noreply@blogger.com0tag:blogger.com,1999:blog-4546365304691285308.post-47879299505811137112017-05-09T17:22:00.000+09:002017-05-09T22:19:16.079+09:00Hecatoncheir: The Data Stewardship Studio 0.8を公開しました<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjx9vJgCBwNKNZ3B8k3Fe2TvMmtQ32D9_BZvNc4m5Ogrb1hDZICeErvWVlmVCpQTAGHemHLJlds2eZ9YHRSO8u_635XahZ4wnFwC1AADn_iZPeIXzWXivuSSr705mfF1ZCSWAWlX0wD_tI/s1600/ss1.png" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjx9vJgCBwNKNZ3B8k3Fe2TvMmtQ32D9_BZvNc4m5Ogrb1hDZICeErvWVlmVCpQTAGHemHLJlds2eZ9YHRSO8u_635XahZ4wnFwC1AADn_iZPeIXzWXivuSSr705mfF1ZCSWAWlX0wD_tI/s400/ss1.png" width="400" height="318" /></a></div>
本日、「Hecatoncheir: The Data Stewardship Studio」という最近開発していた新しいツールをOSSとして公開しました。<br/>
<ul>
<li>Hecatoncheir: The Data Stewardship Studio <br/><a href="https://github.com/snaga/Hecatoncheir">https://github.com/snaga/Hecatoncheir</a></li>
</ul>
本ツールは、データベースのメタデータおよび実データの統計情報やプロファイルを用いることで、データ品質マネジメントおよびデータガバナンスを実施するデータスチュアードを支援することを目的としたソフトウェアです。<br/>
<br/>
既に某所のデータウェアハウスのシステムの周辺で稼働しています。<br/>
<br/>
本エントリでは、このツールの紹介をさせていただきます。(本ツールはPostgreSQLにも対応しております)<br/>
<h4 class="post-body">■本ツールを開発した背景</h4><br/>
最近は、データウェアハウスの設計から構築、データマネジメント(ガバナンスやスチュアードシップと呼ばれることもありますが)を手掛けることが多くなってきました。<br/>
<br/>
データベースエンジニアですから、新しい情報系システムの領域であってもテクニカルな作業もそれなりにこなせるのですが、いくつかの案件を手掛ける中で気付いたことがありました。<br/>
<br/>
それは、どのような局面であっても、データの調査や確認のために同じような作業(クエリの実行など)を何度も何度も繰り返して行わなければならない、ということです。そして、データは日々変化していくため、そのタスクを毎日のように繰り返さなければなりません。<br/>
<br/>
<a name='more'></a>
また、データについて、他人に説明する時にもこのような調査や確認の作業が常に付きまとってきます。<br/>
<br/>
それに加えて、データに何か問題が発生する可能性があるのであれば、それをできるだけ速やかに検出したくなります。<br/>
<br/>
しかし、そのような調査や確認作業に日常的に忙殺されていると、もっと本質的な作業に手を回すことができなくなってしまいます。<br/>
<br/>
そのため、このような「ルーティンで何度も行わなければならないタスク」を自動的に実行することでデータの状態の把握・可視化を容易にし、またデータの品質問題の早期検知を実現するためのツールを開発することにしました。<br/>
<br/>
このツールを使うことによって、データの面倒を見ている人たち(データスチュアード)が「当たり前のことを手間をかけずに行い、もっと新しいこと、本質的なことに安心して時間をかけられる」ようになります。<br/>
<h4 class="post-body">■本ツールでできること</h4><br/>
本ツールでできることは以下の通りです。<br/>
<ul>
<li>データベースディクショナリ(カタログ)からメタデータを取得する</li>
<li>テーブルやカラムのプロファイリングを行う</li>
<li>あらかじめ定義したビジネスルールに則ってデータ検証を行う</li>
<li>タグ付けおよび補足的なメタデータを取り込むことでデータセットをカタログ化する</li>
<li>ビジネス用語集を構築する</li>
<li>データの開発者と利用者が必要とする情報をキュレーション・共有する</li>
</ul>
いくつかスクリーンショットを貼っておきます。<br/>
<br/>
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgJZ0Uib_E1H00MT3a-MHiCc7QSMPePtruBNpcKR54YS7rA-eBXMe-CONxRfr6yRZWQypbn-40G_NG1Xd9nXmyTm7ovQuOZ7DRe3PNXemmFOZkwPAKUzBZty0QlfnLQGhPiM847UxifFek/s1600/ss2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgJZ0Uib_E1H00MT3a-MHiCc7QSMPePtruBNpcKR54YS7rA-eBXMe-CONxRfr6yRZWQypbn-40G_NG1Xd9nXmyTm7ovQuOZ7DRe3PNXemmFOZkwPAKUzBZty0QlfnLQGhPiM847UxifFek/s400/ss2.png" width="400" height="308" /></a></div>
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg79vy8sQh7PwSytWfVJm9Rgu5F5RIuMFz0h8WgknrfITTFU4mA3QsSu0NceQkQUq8pNeH_pA1kp7M2hQBmI1gAd6mrc0RF5U1VVMyH61QUJw3UD8w8YA0LRqQZn5iCHYOKt44DFuQcPDQ/s1600/ss4.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg79vy8sQh7PwSytWfVJm9Rgu5F5RIuMFz0h8WgknrfITTFU4mA3QsSu0NceQkQUq8pNeH_pA1kp7M2hQBmI1gAd6mrc0RF5U1VVMyH61QUJw3UD8w8YA0LRqQZn5iCHYOKt44DFuQcPDQ/s400/ss4.png" width="400" height="148" /></a></div>
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgYCaOLppfJJeyblfpOJZR-F_JQHWDC8xgS0YQGFwY0C26geuLpRiDmIKcVbRWAOqgWdoq2Bt82uRayw7rNEzr1TDJ296WT3mHNJd2iX8KOGIf0TZd9Y_teEY8545g08O37BNLwxPuDhCg/s1600/ss5.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgYCaOLppfJJeyblfpOJZR-F_JQHWDC8xgS0YQGFwY0C26geuLpRiDmIKcVbRWAOqgWdoq2Bt82uRayw7rNEzr1TDJ296WT3mHNJd2iX8KOGIf0TZd9Y_teEY8545g08O37BNLwxPuDhCg/s400/ss5.png" width="400" height="195" /></a></div>
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgpW2rR1S_KcYChwXoD_hDcshQ5_Rw39AL_8K-ZrqJQ7yMc4P5j21-eY2UeK72oZ7fQuizBCVbGfsXuNErF-fPzzSFEq9aqsgoyL7zj6nb0hkLtOmbVwJkVTfnt8ek9aQVe3zoq2aihEog/s1600/ss6.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgpW2rR1S_KcYChwXoD_hDcshQ5_Rw39AL_8K-ZrqJQ7yMc4P5j21-eY2UeK72oZ7fQuizBCVbGfsXuNErF-fPzzSFEq9aqsgoyL7zj6nb0hkLtOmbVwJkVTfnt8ek9aQVe3zoq2aihEog/s400/ss6.png" width="400" height="218" /></a></div>
<h4 class="post-body">■本ツールの仕組み</h4><br/>
本ツールは、コマンドラインで動作するツール類と、ツールからの入力を保存・統合するレポジトリ(SQLiteファイル)から構成されています。<br/>
<ul>
<li>①まず、データディクショナリからメタデータを取得してレポジトリに保存する</li>
<li>②補足的なメタデータをCSVファイルからレポジトリに取り込む</li>
<li>③実データに対するプロファイリングやデータ検証を行い、結果をレポジトリに取り込む</li>
<li>④レポジトリにあるデータをHTMLとしてエクスポートする</li>
</ul>
というのが基本的な仕組み、動作の流れになります。<br/>
<h4 class="post-body">■動作環境・対応プラットフォーム</h4><br/>
動作環境・対応プラットフォームについては、READMEを参照してください。<br/>
<ul>
<li>Hecatoncheir/README.md at develop · snaga/Hecatoncheir <br/><a href="https://github.com/snaga/Hecatoncheir/blob/develop/README.md">https://github.com/snaga/Hecatoncheir/blob/develop/README.md</a></li>
</ul>
<h4 class="post-body">■本ツールの使い方</h4><br/>
ソースコードはGithubレポジトリから入手できます。<br/>
<ul>
<li>Hecatoncheir: The Data Stewardship Studio <br/><a href="https://github.com/snaga/Hecatoncheir">https://github.com/snaga/Hecatoncheir</a></li>
</ul>
クイックスタートガイドを用意していますので、興味のある方はこちらをご覧ください。
<ul>
<li>クイックスタート — Hecatoncheir 0.8 ドキュメント <br/><a href="http://hecatoncheir-ja.readthedocs.io/ja/latest/quick-start.html">http://hecatoncheir-ja.readthedocs.io/ja/latest/quick-start.html</a></li>
</ul>
リファレンス的な情報もまとめてあります。<br/>
<ul>
<li>Hecatoncheir: The Data Stewardship Studio — Hecatoncheir 0.8 ドキュメント <br/><a href="http://hecatoncheir-ja.readthedocs.io/ja/latest/">http://hecatoncheir-ja.readthedocs.io/ja/latest/</a></li>
</ul>
<h4 class="post-body">■名前(Hecatoncheir)の由来</h4><br/>
ご存じの方も多いと思いますが、Hecatoncheir(ヘカトンケイル)というのは、ギリシャ神話に出てくる神様の名前です。<br/>
<ul>
<li>ヘカトンケイル - Wikipedia <br/><a href="https://ja.wikipedia.org/wiki/%E3%83%98%E3%82%AB%E3%83%88%E3%83%B3%E3%82%B1%E3%82%A4%E3%83%AB">https://ja.wikipedia.org/wiki/%E3%83%98%E3%82%AB%E3%83%88%E3%83%B3%E3%82%B1%E3%82%A4%E3%83%AB</a></li>
<li>ヘカトンケイルたち <br/><a href="http://www.h6.dion.ne.jp/~em-em/page263.html">http://www.h6.dion.ne.jp/~em-em/page263.html</a></li>
</ul>
50の頭と100の手を持ち、戦いに当たってはその怪力と多くの腕で次々と大きな岩を投げつけて勝利に導き、戦いが終わると今度は100の眼で奈落の出入口で監視をするという役回りが、このツールのイメージとピッタリのように感じたため、この名前を選びました。<br/>
<h4 class="post-body">■まとめ</h4><br/>
本エントリでは、今回公開した Hecatoncheir の紹介をしました。<br/>
<br/>
まだまだ対応DBMSも増やしていきたいですし、いろいろと機能開発や改良もしていきたいと思っています。<br/>
<br/>
興味のある方は、ぜひGithubのIssueなどでご意見、コメントを頂ければと思います。<br/>
<br/>
では。<br/>
Satoshi Nagayasuhttp://www.blogger.com/profile/09706093898823214408noreply@blogger.com0tag:blogger.com,1999:blog-4546365304691285308.post-24518728594280644072017-02-21T13:36:00.000+09:002017-02-21T13:36:28.185+09:00In-database Analyticsの集い #1を開催します<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjINuFC0OP-hNP7db_aEFGTHN2Pf7B7A2-P0yO7iMBz0MnADT3F5KjzoiIYaegoV1QDEBp9q5AaXUZJi4rjgGmbggZNIbpXkbGrildwyct8XfvXfuQwAlKjrmeGr4KVXdGF1enCMkGQsio/s1600/indatabase.png" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjINuFC0OP-hNP7db_aEFGTHN2Pf7B7A2-P0yO7iMBz0MnADT3F5KjzoiIYaegoV1QDEBp9q5AaXUZJi4rjgGmbggZNIbpXkbGrildwyct8XfvXfuQwAlKjrmeGr4KVXdGF1enCMkGQsio/s320/indatabase.png" width="320" height="238" /></a></div><p>
3月10日(金)に「In-database Analyticsの集い #1」というMeetupを開催することになりました。
</p>
<ul>
<li><a href="https://connpass.com/event/50950/">In-database Analyticsの集い #1 - connpass</a></li>
</ul>
<p>
「In-Database Analytics」というのは、データベースに蓄積されたデータに対して、「データを取り出さずに」データベース内部で分析処理をする技術の総称だと思っていただければいいかと思います。
</p>
<p>
データベースに蓄積されるデータはどんどん大きくなっている昨今ですが、それに伴ってデータベースからデータを取り出してから分析処理をする、というのが難しくなりつつあります。そのため、データベースからデータを取り出さずに分析処理をする「In-Database Analytics」の重要性がより高まってくると感じています。
</p>
<p>
今回のMeetupでは、ソフトウェアによるIn-Database Analyticsの話から始めて、昨今注目されているハードウェアアクセラレーションの活用(GPGPUやFPGA)などについて情報交換する場にできればと思っています。
</p>
<p>
というわけで、この辺の話に興味がある方はぜひご参加いただければと思います。お待ちしています。
</p>
<p>
では。
</p>
Satoshi Nagayasuhttp://www.blogger.com/profile/09706093898823214408noreply@blogger.com0tag:blogger.com,1999:blog-4546365304691285308.post-86459748427032182242017-01-24T13:03:00.003+09:002017-01-24T14:37:45.788+09:00コサイン類似度に基づくソート処理の実装方法とその性能比較<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg4PvMhoPajGUX4-l33Yp-6RoWCXxSq5qqR-EL7IiznKyXbqJex26DrdC97kf5N0M8ULbRHavUX3_KXUKJAZDspHGn-6LkJa0YgeisXiywdCzQOG3tMyZLk-AQpO60SGPrn__tFpRD7F0k/s1600/0.png" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg4PvMhoPajGUX4-l33Yp-6RoWCXxSq5qqR-EL7IiznKyXbqJex26DrdC97kf5N0M8ULbRHavUX3_KXUKJAZDspHGn-6LkJa0YgeisXiywdCzQOG3tMyZLk-AQpO60SGPrn__tFpRD7F0k/s400/0.png" width="400" height="91" /></a></div>
文書の類似度を計算する方法に「コサイン類似度」を用いる方法があります。<br/>
<br/>
これは、出現する単語を出現回数などで数値化して、空間ベクトルに変換した上でベクトル同士の類似度を計算する、という手法です。<br/>
<ul>
<li>コサイン類似度 <br/><a href="http://www.cse.kyoto-su.ac.jp/~g0846020/keywords/cosinSimilarity.html">http://www.cse.kyoto-su.ac.jp/~g0846020/keywords/cosinSimilarity.html</a></li>
</ul>
最近、このコサイン類似度を使って、似ているデータを検索するWebアプリを試しに作っていたのですが、ふと、<br/>
<br/>
「このコサイン類似度を使ったソート処理をPostgreSQLでどのように実装するともっとも高速な実装になるのだろうか。また、現実的なパフォーマンスを考えた時にデータ量や次元のサイズはどこまで増やせるのだろうか」<br/>
<br/>
ということが気になりました。<br/>
<br/>
PostgreSQLは、その拡張性の高さがウリの一つですが、そのため「UDFを作る」ということを考えても、実装方法にもいろいろあります。<br/>
<br/>
今回は、PostgreSQL内部でデータ処理を実装するに当たって、どのような実装方法があるのか、それぞれどのように性能が違うのか、そしてその時にデータサイズがどのように影響するのかを見てみます。
<h4 class="post-body">■前提条件</h4><br/>
今回は以下の前提条件で実装と性能比較を行います。<br/>
<ul>
<li>ソート処理するデータはPostgreSQLに蓄積されているものを対象とする</li>
<li>空間ベクトルを表すデータは、PostgreSQL の float8 の配列で1カラムに保持する</li>
<li>コサイン類似度による類似度を計算し、もっとも類似度の高いレコードをN件取得する</li>
</ul>
<a name='more'></a>
これらを前提条件として、コサイン類似度の計算を<br/>
<ul>
<li>(1) CのUDFで実装した場合</li>
<li>(2) PythonのUDFで実装した場合(PL/Python)</li>
<li>(3) scikit-learnの<a href="http://scikit-learn.org/stable/modules/generated/sklearn.metrics.pairwise.cosine_similarity.html">cosine_similarity関数</a>を使ってPythonのUDFで実装した場合(PL/Python + scikit-learn)</li>
<li>(4) <a href="http://pgsqldeepdive.blogspot.jp/search/label/MADlib">MADlib</a>の<a href="https://madlib.incubator.apache.org/docs/latest/linalg_8sql__in.html#a1782f2ba00d9f9fab20894a576079f87">cosine_similarity関数</a>を使った場合</li>
<li>(5) scikit-learnを使ってクライアント側に取得してクライアント側で計算する場合</li>
</ul>
に、パフォーマンスがそれぞれどう異なるかを確認してみます。<br/>
<br/>
また、レコード数および空間ベクトルの次元数によって実行時間がどのように変わるかも確認してみます。
<h4 class="post-body">■処理対象のデータ</h4><br/>
処理対象のデータは、 data_vec テーブルに主キーとして整数型のカラム id を持ち、空間ベクトルのデータとして float8[] 型の カラム vec を持つテーブルです。<br/>
<pre style="overflow-x: scroll; white-space: pre">
CREATE TABLE data_vec (
id INTEGER PRIMARY KEY,
vec FLOAT8[] NOT NULL
);
</pre>
<br/>
このテーブルの中に、以下のようにレコードが入っています。(以下は100次元の空間ベクトルのデータが5件入っている状態)<br/>
<pre style="overflow-x: scroll; white-space: pre">
test_cos_sim=> select * from data_vec ;
id | vec
----+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
0 | {1,1,0,0,1,0,1,0,0,1,1,1,0,1,1,0,1,1,1,1,0,1,1,1,0,0,0,1,1,1,0,1,0,1,1,0,1,0,1,1,0,0,1,0,0,1,0,1,0,1,1,0,0,0,1,1,0,1,1,1,1,1,1,1,1,0,1,0,1,0,0,0,1,1,0,0,1,1,1,1,1,1,1,0,1,0,1,1,1,1,1,1,0,1,1,1,1,0,1,0}
1 | {1,1,0,1,0,0,1,0,1,0,0,1,0,1,1,1,1,0,1,1,1,0,0,1,0,0,1,0,0,0,1,1,0,0,0,0,0,1,0,0,1,0,0,1,0,0,1,1,1,0,1,1,0,1,1,0,0,1,0,0,0,0,1,1,0,0,1,0,1,0,1,0,0,0,0,1,0,0,0,1,1,1,1,0,0,1,0,0,0,0,1,0,1,1,0,0,1,0,1,0}
2 | {0,0,0,1,0,1,1,1,0,0,0,1,1,1,0,1,1,0,1,1,0,1,0,0,1,0,1,1,0,0,1,1,0,1,0,1,0,0,0,0,1,1,0,1,1,0,1,0,0,0,1,1,1,1,1,0,1,1,1,1,1,1,0,1,0,1,0,0,0,1,1,0,1,0,0,1,0,0,1,0,0,0,0,0,1,0,0,0,0,0,1,0,1,1,0,0,1,0,0,1}
3 | {1,1,1,0,1,1,0,0,1,0,0,0,1,0,0,1,1,0,0,1,0,1,0,0,0,1,1,1,0,1,0,0,0,0,1,0,0,0,0,1,0,1,1,0,0,1,1,0,1,0,0,0,1,0,1,0,0,1,1,1,0,0,0,1,1,1,1,1,0,0,1,0,0,0,0,1,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,0,1,0,1,1}
4 | {1,1,0,0,1,0,0,1,1,0,0,1,0,1,0,0,1,0,0,0,0,1,1,1,0,1,0,1,0,0,0,0,0,1,1,0,1,0,0,1,0,1,1,0,1,0,0,1,1,1,0,1,1,1,1,1,1,1,1,0,1,0,1,0,1,0,0,1,0,1,1,1,1,0,0,1,1,1,1,0,0,0,0,1,1,1,0,0,1,1,0,0,1,0,1,1,1,0,0,0}
(5 rows)
test_cos_sim=>
</pre>
<br/>
<h4 class="post-body">■コサイン類似度計算UDFの実装</h4><br/>
コサイン類似度の計算をするUDFのそれぞれの実装は以下のようになります。<br/>
<br/>
CによるUDFの実装 (1) は以下のようになります。<br/>
<pre style="overflow-x: scroll; white-space: pre">
Datum
cosine_similarity(PG_FUNCTION_ARGS)
{
ArrayType *vec_a = PG_GETARG_ARRAYTYPE_P(0);
ArrayType *vec_b = PG_GETARG_ARRAYTYPE_P(1);
float8 ab = 0;
float8 aa = 0;
float8 bb = 0;
int i;
int len_a = (ARR_SIZE(vec_a) - ARR_DATA_OFFSET(vec_a)) / sizeof(float8);
int len_b = (ARR_SIZE(vec_b) - ARR_DATA_OFFSET(vec_b)) / sizeof(float8);
float8 *a = (float8 *)ARR_DATA_PTR(vec_a);
float8 *b = (float8 *)ARR_DATA_PTR(vec_b);
for (i = 0; i < len_a; i++)
{
aa += a[i] * a[i];
bb += b[i] * b[i];
ab += a[i] * b[i];
}
PG_RETURN_FLOAT8(ab/(sqrt(aa)*sqrt(bb)));
}
</pre>
<br/>
PL/PythonのUDFとして実装 (2) すると以下のようになります。<br/>
<pre style="overflow-x: scroll; white-space: pre">
CREATE OR REPLACE FUNCTION cosine_similarity_plpy(vec_a float8[], vec_b float8[])
RETURNS float8
AS $$
from math import sqrt
aa = 0
bb = 0
ab = 0
for a,b in zip(vec_a, vec_b):
aa += a * a
bb += b * b
ab += a * b
return ab/(sqrt(aa)*sqrt(bb))
$$
LANGUAGE 'plpython2u';
</pre>
<br/>
PL/Pythonでも scikit-learn を使った実装 (3) は以下のようになります。<br/>
<pre style="overflow-x: scroll; white-space: pre">
CREATE OR REPLACE FUNCTION cosine_similarity_sk(vec_a float8[], vec_b float8[])
RETURNS float8
AS $$
from sklearn.metrics.pairwise import cosine_similarity
return cosine_similarity([vec_a], [vec_b])[0][0]
$$
LANGUAGE 'plpython2u';
</pre>
<br/>
<h4 class="post-body">■検証用クエリ</h4><br/>
今回の評価では、上記のデータとUDFを使って、「id=0」の空間ベクトルを問い合わせクエリとして、「その他(id=0以外)」のレコードの中から最も似ているレコードを10件取得してみます。クエリは、それぞれ以下のようになります。<br/>
<br/>
CによるUDFを使う場合 (1):<br/>
<pre style="overflow-x: scroll; white-space: pre">
SELECT
a.id as "id1",
b.id as "id2",
cosine_similarity(a.vec, b.vec)
FROM
data_vec a,
data_vec b
WHERE
a.id = 0
AND
b.id <> 0
ORDER BY
3 DESC
LIMIT 10;
</pre>
<br/>
PL/PythonによるUDFを使う場合 (2):<br/>
<pre style="overflow-x: scroll; white-space: pre">
SELECT
a.id as "id1",
b.id as "id2",
cosine_similarity_plpy(a.vec, b.vec)
FROM
data_vec a,
data_vec b
WHERE
a.id = 0
AND
b.id <> 0
ORDER BY
3 DESC
LIMIT 10;
</pre>
<br/>
PL/Python + scikit-learn によるUDFを使う場合 (3):<br/>
<pre style="overflow-x: scroll; white-space: pre">
SELECT
a.id as "id1",
b.id as "id2",
cosine_similarity_sk(a.vec, b.vec)
FROM
data_vec a,
data_vec b
WHERE
a.id = 0
AND
b.id <> 0
ORDER BY
3 DESC
LIMIT 10;
</pre>
<br/>
MADlib の cosine_similarity 関数を使う場合 (4):<br/>
<pre style="overflow-x: scroll; white-space: pre">
SELECT
a.id as "id1",
b.id as "id2",
madlib.cosine_similarity(a.vec, b.vec)
FROM
data_vec a,
data_vec b
WHERE
a.id = 0
AND
b.id <> 0
ORDER BY
3 DESC
LIMIT 10;
</pre>
<br/>
上記は関数名が違うだけで、クエリは基本的には同じです。<br/>
<br/>
なお、クライアント側に取得して scikit-learn で計算する実装 (5) は以下のようになります。<br/>
<pre style="overflow-x: scroll; white-space: pre">
import psycopg2
from sklearn.metrics.pairwise import cosine_similarity
conn = psycopg2.connect("dbname=test_cos_sim")
cur = conn.cursor()
q = """
SELECT
a.id as "id1",
a.vec as "vec1",
b.id as "id2",
b.vec as "vec2"
FROM
data_vec a,
data_vec b
WHERE
a.id = 0
AND
b.id <> 0
"""
cur.execute(q)
d = []
for r in cur.fetchall():
d.append((r[2], cosine_similarity([r[1]], [r[3]])[0][0]))
print "sk_cli"
for r in sorted(d, key=lambda s: s[1], reverse=True)[:10]:
print(r)
conn.close()
</pre>
<br/>
<h4 class="post-body">■検証環境</h4><br/>
今回検証に用いた環境は以下の通りです。Windows上でのVirtualBoxのVM環境です。<br/>
<ul>
<li>Core i7-4785T 2.20GHz (Quad core)</li>
<li>CentOS 6.6 (x86_64)</li>
<li>Python 2.7.9</li>
<li>PostgreSQL 9.5.2</li>
<li>VM(VirtualBox)に4コアを100%割り当て</li>
<li>VM上で4GB RAM</li>
</ul>
<h4 class="post-body">■実装方式による性能比較</h4><br/>
以下は、実装方式による実行時間の比較です。<br/>
<br/>
ここでは、まずはベースラインとして500次元の空間ベクトルを10,000レコード作成して、クエリの実行時間(5回実行した平均値)を取得しています。<br/>
<br/>
縦軸は実行時間(短い方が高速)、横軸はそれぞれ<br/>
<ul>
<li>(1) CによるUDF (func_native)</li>
<li>(2) PL/PythonによるUDF (func_plpy)</li>
<li>(3) PL/Python+scikit-learnによるUDF (func_plpy_sk)</li>
<li>(4) MADlibのcosine_similarity関数 (func_madlib)</li>
<li>(5) クライアント側に取得してscikit-learnで処理 (cli_sk)</li>
</ul>
を示しています。<br/>
<br/>
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgnMNT3uSB8d9GXrFBDEzzg23MZKDfCbO4n5o2FeB1WLxhyGLrJtS7inuqiE93ZYUips0o7n-UeRqjbWbnK8nKYDDa9MNknXDW1dejgBZ5zqXMu6cChrSUJg93fg74dA4pdYIwLkZGhNCM/s1600/1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgnMNT3uSB8d9GXrFBDEzzg23MZKDfCbO4n5o2FeB1WLxhyGLrJtS7inuqiE93ZYUips0o7n-UeRqjbWbnK8nKYDDa9MNknXDW1dejgBZ5zqXMu6cChrSUJg93fg74dA4pdYIwLkZGhNCM/s640/1.png" width="640" height="277" /></a></div>
<br/>
この結果を見ると、
<ul>
<li>CによるUDFとMADlibの関数が圧倒的に高速(それぞれ102msと129ms)</li>
<li>PL/Python系の実装が一桁遅い(1906msと2854ms)</li>
<li>クライアント側にデータを持ってきてソート処理をするのがもっとも遅い(7381ms)</li>
</ul>
という結果になっています。
<h4 class="post-body">■次元数の違いによる性能比較</h4><br/>
次に、空間ベクトルの次元数を500から1,000および2,000に増やして実行時間がどのように変化するかを確認します。レコード数はすべて10,000件としています。<br/>
<br/>
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjx50dH_OE3InrUJvhBEzhiJBpfocEwLkhGMUvN4DzCsINWvLcXbwDK6rKKp19t4NiettJh_HFVNzj7ZBZdWWB7XRebnNzVALXrrEqABUiLh3S0xnJJE2YUOSkecMMVTWa3Za1Daq-S3Ks/s1600/2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjx50dH_OE3InrUJvhBEzhiJBpfocEwLkhGMUvN4DzCsINWvLcXbwDK6rKKp19t4NiettJh_HFVNzj7ZBZdWWB7XRebnNzVALXrrEqABUiLh3S0xnJJE2YUOSkecMMVTWa3Za1Daq-S3Ks/s640/2.png" width="640" height="277" /></a></div>
<br/>
結果を見ると、次元数に応じて実行時間は長くなっています。<br/>
<br/>
CによるUDFとMADlibが圧倒的に高速なのは変わらないのですが、PL/Python系の実装について見てみると、500次元の時には(scikit-learnを使わない)素のPL/Pythonの実装(func_plpy)の方が高速であったのが、次元数が2,000になると、scikit-learnを使った実装(func_plpy_sk)の方が高速になっています。<br/>
<br/>
これは、おそらく素の Python で大きな配列を扱うよりは scikit-learn の方が大量の数値データの扱いに長けているためでしょう。(今回は実施しませんでしたが、numpyを使うと素のPythonで実装するよりは高速になるかもしれません)
<h4 class="post-body">■レコード数の違いによる性能比較</h4><br/>
最後に、空間ベクトルの次元数は2,000のままにして、レコード数を10,000件から20,000件、40,000件と増やしてみて、実行時間がどのように変化するかを確認します。<br/>
<br/>
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjmd6vdCMeY3hHsIzlcDC3jRTlcPCNs0lFVAZmbw4MCMMzKI_DSb7kQtMt_H4nEGudyZ8Xfb-_Ayw9icFsqLSOeNSfzVXvRj_1SmKOSGCbSVa9SUWnTDtaWtAFoJI7YOtkkly0AVzPwHOc/s1600/3.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjmd6vdCMeY3hHsIzlcDC3jRTlcPCNs0lFVAZmbw4MCMMzKI_DSb7kQtMt_H4nEGudyZ8Xfb-_Ayw9icFsqLSOeNSfzVXvRj_1SmKOSGCbSVa9SUWnTDtaWtAFoJI7YOtkkly0AVzPwHOc/s640/3.png" width="640" height="277" /></a></div>
<br/>
レコードの増加に伴って、レコード数と同じ程度に実行時間が延びていることが分かります。<br/>
<br/>
ここでも、レコード数が増えると素の PL/Python による実装よりも、PL/Python の UDF 内で scikit-learn を使った方が高速になる傾向が出ています。<br/>
<br/>
なお、クライアント側に取ってきて scikit-learn で処理する方式(cli_sk)は、40,000レコードの時に Out of Memory エラーで実行できなかったので、結果がありません。<br/>
<br/>
以下に、計測した数値を一覧で示します。(数値はミリ秒)<br/>
<br/>
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg1a1X4NUTJAcbuP_IZmjm4JkRP45NcwxX0YDSC7aMHHjEWQgpQa1bFgA8WbKN2QH6CMSypKmTNrWPREisJN27DJ6AxrTamBEqavPLrNcgK3YA1nzK8vNllI9PDFMdbHs_X1n-oswOGv8w/s1600/4.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg1a1X4NUTJAcbuP_IZmjm4JkRP45NcwxX0YDSC7aMHHjEWQgpQa1bFgA8WbKN2QH6CMSypKmTNrWPREisJN27DJ6AxrTamBEqavPLrNcgK3YA1nzK8vNllI9PDFMdbHs_X1n-oswOGv8w/s1600/4.png" /></a></div>
<br/>
なお、今回利用したコード類は以下に置いておきましたので、興味のある方は合わせてご利用ください。
<ul>
<li>snaga/eval_cosine_similarity <br/><a href="https://github.com/snaga/eval_cosine_similarity">https://github.com/snaga/eval_cosine_similarity</a></li>
</ul>
<h4 class="post-body">■まとめ</h4><br/>
今回は、「データベース内のデータに対してコサイン類似度を計算して、より似ているレコードを取得する」というケースを想定して、どのような実装がより高速なのかを検証してみました。<br/>
<br/>
その結果として、今回の前提条件であれば、<br/>
<ul>
<li>Cで作成したUDFとMADlibはパフォーマンス的にほとんど変わらない。</li>
<li>それに比べると、PL/Pythonでの実装は(scikit-learnを使うかどうかに関わらず)1ケタ以上遅い。</li>
<li>処理するデータ量(次元またはレコード数)が多くなると、scikit-learnを使う実装のメリットが出てくる。</li>
<li>オンラインシステムでの利用を想定すると、数百ミリ秒で返ってきたMADlibはそのまま利用できる可能性がある。</li>
<li>データをクライアントに転送して処理するのは時間がかかる。</li>
</ul>
ということが分かりました。<br/>
<br/>
興味のある方は、自分のデータを使って、あるいは別のアルゴリズムについてもデータベース内での処理を試してみていただければと思います。<br/>
<br/>
では。<br/>
Satoshi Nagayasuhttp://www.blogger.com/profile/09706093898823214408noreply@blogger.com0tag:blogger.com,1999:blog-4546365304691285308.post-82012531346987816162016-12-24T00:00:00.000+09:002016-12-24T11:59:09.213+09:00オープンデータ+PostGIS+Google Maps で観光マップを作ってみた<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjIVIhMhtzJFtOBgyWnVX8UWLKmzqtxSou5CUkpiA9Cpni_gxpUvcnStDfn1ZvOKVOSFtdiM2vzASphFxpWJqDGfr6KpI7CNU5vaNwHyd_zw75NhI1S8f3amIvWXQucpOloINNfafiDfnA/s1600/top.png" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjIVIhMhtzJFtOBgyWnVX8UWLKmzqtxSou5CUkpiA9Cpni_gxpUvcnStDfn1ZvOKVOSFtdiM2vzASphFxpWJqDGfr6KpI7CNU5vaNwHyd_zw75NhI1S8f3amIvWXQucpOloINNfafiDfnA/s320/top.png" width="320" height="244" /></a></div>
本エントリは <a href="http://qiita.com/advent-calendar/2016/postgresql">PostgreSQL Advent Calendar 2016</a> の Day24 のエントリです。昨日は @mazudakz さんの「<a href="http://qiita.com/masudakz/items/8e5643d5e3fcbf245fb5">pg_stats_reporter をしくじった話</a>」でした。読み応えあって面白かった。<br/>
<br/>
さて、先日(と言っても結構前)、地理情報をPostgreSQLで扱う例として、巡回セールスマン問題をPostgreSQLで解きつつGoogle Mapsで可視化するエントリを書きました。<br/>
<ul>
<li>巡回セールスマン問題における最短経路をpgRoutingで探索する <br/><a href="http://pgsqldeepdive.blogspot.jp/2016/09/pgrouting.html">http://pgsqldeepdive.blogspot.jp/2016/09/pgrouting.html</a></li>
</ul>
今回は、もう少し進んでPostgreSQLにおける地理情報の検索とGoogle Mapsの動的な可視化を連動させてみましたので、その内容を紹介します。<br/>
<br/>
実現したいことは、
<ul>
<li>観光に関連する情報をPostgreSQLに取り込んで、</li>
<li>Google Mapsで地図上にマッピングして可視化しつつ、</li>
<li>地図上をブラウジングしながら、</li>
<li>興味のある場所があったらそのままGoogle検索に飛ぶ</li>
</ul>
という仕組みです。<br/>
<br/>
年末年始のお出かけの検討に、または雑談のお供にご活用いただければと思います。
<h4 class="post-body">■オープンデータ「国土数値情報 観光資源データ」とは</h4><br/>
まず、今回使うデータですが、国土交通省が公開している「国土数値情報」の中から「観光資源」のデータを使います。<br/>
<ul>
<li>国土数値情報ダウンロードサービス <br/><a href="http://nlftp.mlit.go.jp/ksj/index.html">http://nlftp.mlit.go.jp/ksj/index.html</a></li>
<li>国土数値情報 観光資源データの詳細 <br/><a href="http://nlftp.mlit.go.jp/ksj/gml/datalist/KsjTmplt-P12-v2_2.html">http://nlftp.mlit.go.jp/ksj/gml/datalist/KsjTmplt-P12-v2_2.html</a></li>
</ul>
このデータは各都道府県が「観光資源」として登録しているデータで、以下のような項目が含まれています。<br/>
<ul>
<li>観光資源_ID</li>
<li>観光資源名</li>
<li>都道府県コード</li>
<li>行政コード</li>
<li>種別名称</li>
<li>所在地住所</li>
<li>観光資源分類コード</li>
<li>観光資源(地理情報)</li>
</ul>
そのため、これらをうまくPostgreSQLに取り込んでやる必要があります。<br/>
<br/>
このデータは地理情報のデータフォーマットとして広く使われている「シェープファイル(Shape File)」と呼ばれる形式で配布されています。<br/>
<br/>
<a name='more'></a>
PostGISには、このシェープファイルをPostgreSQLに取り込むためのコマンドラインツールが含まれており、比較的容易にPostgreSQLのデータベースに取り込むことができます。<br/>
<ul>
<li>シェープファイルのデータをインポートしてみよう コマンドライン編 - Qiita <br/><a href="http://qiita.com/yellow_73/items/f2d8388e88d534066bc0">http://qiita.com/yellow_73/items/f2d8388e88d534066bc0</a></li>
</ul>
今回はこの方法を使って、観光資源データのシェープファイルをPostgreSQLに取り込みます。
<h4 class="post-body">■データを準備する</h4><br/>
データセットをダウンロードすると分かりますが、47都道府県×3種類で、100以上のシェープファイルが含まれています。<br/>
<br/>
シェープファイルを取り込む shp2pgsql コマンドは1シェープファイル1テーブルとして作成しますので、普通に取り込むと100個以上のテーブルができることになります。前処理としてそれらのテーブルを統合しておいた方が良いでしょう。<br/>
<br/>
というわけで、取り込むスクリプトは以下です。<br/>
<ul>
<li><a href="https://github.com/snaga/postgis-googlemaps/blob/master/db/004_P12-14_GML.sh">https://github.com/snaga/postgis-googlemaps/blob/master/db/004_P12-14_GML.sh</a></li>
</ul>
各シェープファイルの取り込みと、それらを統合して「p12_14」というテーブルを作成してくれます。<br/>
<br/>
実行すると以下のようになります。<br/>
<pre style="overflow-x: scroll; white-space: pre">
[snaga@localhost postgis_test]$ ./004_P12-14_GML.sh
Archive: ./data/P12-14_GML.zip
creating: P12-14_GML/
inflating: P12-14_GML/KS-META-P12_14-01.xml
inflating: P12-14_GML/KS-META-P12_14-02.xml
inflating: P12-14_GML/KS-META-P12_14-03.xml
...
DROP TABLE
DROP TABLE
DROP TABLE
COMMIT
都道府県コード | count
----------------+-------
01 | 53
02 | 524
03 | 513
04 | 8
05 | 679
...
45 | 265
46 | 13
47 | 198
(47 rows)
[snaga@localhost postgis_test]$ psql -c 'select count(*) from p12_14' gistest
count
-------
19140
(1 row)
[snaga@localhost postgis_test]$
</pre>
<br/>
ついでに都道府県コードの変換テーブルも作成しておきます。<br/>
<ul>
<li><a href="https://github.com/snaga/postgis-googlemaps/blob/master/db/201_PREFCODE.SQL">https://github.com/snaga/postgis-googlemaps/blob/master/db/201_PREFCODE.SQL</a></li>
</ul>
<pre style="overflow-x: scroll; white-space: pre">
[snaga@localhost postgis_test]$ psql -f 201_PREFCODE.SQL gistest
psql:201_PREFCODE.SQL:1: ERROR: table "prefcode" does not exist
CREATE TABLE
COPY 47
[snaga@localhost postgis_test]$ psql -c 'select count(*) from prefcode' gistest
count
-------
47
(1 row)
[snaga@localhost postgis_test]$
</pre>
<br/>
データをロードし終わると、以下のような状態になります。<br/>
<br/>
<pre style="overflow-x: scroll; white-space: pre">
gistest=> \d
List of relations
Schema | Name | Type | Owner
--------+-------------------+-------+----------
public | geography_columns | view | postgres
public | geometry_columns | view | postgres
public | p12_14 | table | snaga
public | prefcode | table | snaga
public | raster_columns | view | postgres
public | raster_overviews | view | postgres
public | spatial_ref_sys | table | postgres
(7 rows)
gistest=> \d p12_14
Table "public.p12_14"
Column | Type | Modifiers
--------------------+----------------+-----------
観光資源ID | integer |
観光資源名 | text |
都道府県コード | character(2) |
行政コード | character(5)[] |
種別名称 | text |
所在地住所 | text |
観光資源分類コード | numeric(2,0) |
geom | geometry |
Indexes:
"p12_14_idx" btree ("都道府県コード", "観光資源ID")
gistest=> \d prefcode
Table "public.prefcode"
Column | Type | Modifiers
----------------+--------------+-----------
都道府県コード | character(2) | not null
都道府県名 | text | not null
Indexes:
"prefcode_pkey" PRIMARY KEY, btree ("都道府県コード")
gistest=>
</pre>
<br/>
<h4 class="post-body">■ポリゴンデータを点データに変換する</h4><br/>
次に、geomカラムに含まれているポリゴンデータを点のデータに変換します。<br/>
<br/>
ポリゴンデータのままだと、地理情報を使った演算に時間がかかってしまいます。今回はそこまで厳密な距離の計算などは必要ないため、計算量を減らすためにポリゴンから「点」、具体的には「ポリゴンの重心」に変換します。<br/>
<br/>
重心を求めるには PostGIS の ST_Centroid() 関数を使います。<br/>
<br/>
<pre style="overflow-x: scroll; white-space: pre">
gistest=> select pg_total_relation_size('p12_14');
pg_total_relation_size
------------------------
10821632
(1 row)
gistest=> update p12_14 set geom = ST_Centroid(geom);
UPDATE 19140
gistest=> vacuum full p12_14 ;
VACUUM
gistest=> select pg_total_relation_size('p12_14');
pg_total_relation_size
------------------------
3571712
(1 row)
gistest=>
</pre>
<br/>
ポリゴンから点に変換したことによって、テーブルサイズが1/3程度になりました。<br/>
<h4 class="post-body">■観光資源データを検索する</h4><br/>
ここまでできたら観光資源データを検索してみます。<br/>
<br/>
今回の検索は基本的に「緯度経度で矩形を指定し、その中に存在している観光資源の情報を取得する」というものです。というのは、最終的にはGoogle Mapsで表示、検索できるようにしますので、Google Mapsの表示領域である矩形に切り取れる必要があるのです。<br/>
<br/>
クエリとしては、まず検索したい対象の区域の緯度経度からポリゴンを作成し、そのポリゴンに含まれる観光資源を取得します。<br/>
<br/>
ポリゴンを作成するには ST_MakePolygon() 関数を、作成したポリゴンに座標系を設定するには ST_SetSRID() 関数を使います。<br/>
<br/>
そして、ST_Contains() 関数を使って、作成したポリゴンに観光資源が含まれるかどうかを判定します。<br/>
<br/>
例えば、都庁から浜離宮恩賜庭園にかけての一帯を検索したいのであれば、それぞれの座標は<br/>
<ul>
<li>都庁: 緯度 35.689634 経度 139.692101</li>
<li>浜離宮恩賜庭園: 緯度 35.6597374 経度 139.7634925</li>
</ul>
となりますので、この座標を対角線に持つ矩形を「139.692101 35.689634, 139.692101 35.6597374, 139.7634925 35.6597374, 139.7634925 35.689634, 139.692101 35.689634」と定義して、ポリゴンを作成します。<br/>
<br/>
この辺。
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh49Gre3RMtNZ1ga37vlrhwZ-1315p9vjr1Caw2_ueqNPDYiZwm76tIsat0gtgIMe_jfpXJrq2IF9LhNNTvJOCRJLX-8pkcTCpsOuwYmF_MN9LUqjQ4PWlDhBLDI4XBwwH-pFM_7DSksUw/s1600/map.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh49Gre3RMtNZ1ga37vlrhwZ-1315p9vjr1Caw2_ueqNPDYiZwm76tIsat0gtgIMe_jfpXJrq2IF9LhNNTvJOCRJLX-8pkcTCpsOuwYmF_MN9LUqjQ4PWlDhBLDI4XBwwH-pFM_7DSksUw/s400/map.png" width="400" height="239" /></a></div>
<br/>
矩形を指定する方法ですが、「4つの頂点を指定する」のではなく、「頂点を結ぶ線を定義する」必要があります。つまり、最後には「開始点に戻ってくる」必要がある、ということです。開始点と終了点が一致して閉じられていないとエラーが出ますので注意してください。<br/>
<br/>
具体的なクエリとしては以下のようになります。<br/>
<pre style="overflow-x: scroll; white-space: pre">
gistest=> SELECT
観光資源名,
ST_Y(geom) lat,
ST_X(geom) lon,
所在地住所
FROM
p12_14
WHERE
ST_Contains(
ST_SetSRID(ST_MakePolygon('LINESTRING(139.692101 35.689634, 139.692101 35.6597374,
139.7634925 35.6597374, 139.7634925 35.689634,
139.692101 35.689634)'::geometry), 4612),
geom
);
観光資源名 | lat | lon | 所在地住所
----------------------------------+------------------+------------------+-----------------------
江戸城跡 | 35.6848450646745 | 139.753409059839 | 千代田区千代田
江戸城跡 | 35.6848545616798 | 139.753620448968 | 千代田区千代田
原宿 | 35.6677931758621 | 139.706963201124 | 渋谷区神宮前
明治神宮 | 35.6759794856927 | 139.699423688458 | 渋谷区代々木神園町1-1
新宿御苑 | 35.6852930986455 | 139.71005110951 | 新宿区
国会議事堂 | 35.6758880055466 | 139.744858001005 | 千代田区永田町1-7-1
根津美術館 | 35.6622430012588 | 139.717093001577 | 港区南青山6-6-5-1
江戸前の寿司 | 35.6896339956514 | 139.692101001444 | 新宿区
国立能楽堂で上演される能・狂言 | 35.6804039978788 | 139.708209998586 | 渋谷区千駄ヶ谷4-18-1
国立劇場で上演される歌舞伎・文楽 | 35.6815600003142 | 139.74327700092 | 千代田区隼町4-1
明治神宮 | 35.6757916030472 | 139.699511838812 | 渋谷区代々木神園町1-1
新宿御苑 | 35.6850607466651 | 139.709971187167 | 新宿区
(12 rows)
gistest=>
</pre>
<br/>
東京にこれしか観光資源ないのかよというツッコミはあろうかと思いますが、PostgreSQLのせいではないのでここでは不問とします。
<h4 class="post-body">■検索をREST API化する</h4><br/>
矩形で検索できるようになったら、これをブラウザのJavaScriptから呼び出せるようにREST API化します。<br/>
<br/>
Flaskを使ってさくっとREST API化して手元なりどこかのPaaSなりで動作させておきます。<br/>
<br/>
ソースコードは以下です。<br/>
<ul>
<li><a href="https://github.com/snaga/postgis-googlemaps">https://github.com/snaga/postgis-googlemaps</a></li>
</ul>
<h4 class="post-body">■Google Mapsと連携する</h4><br/>
最後にGoogle Mapsと連携させます。<br/>
<br/>
(力尽きたので詳細は割愛。ソース見てちょ)
<ul>
<li><a href="https://github.com/snaga/postgis-googlemaps/blob/master/static/maps/index.html">https://github.com/snaga/postgis-googlemaps/blob/master/static/maps/index.html</a></li>
</ul>
特長、というか、こんな感じで動いてます。<br/>
<ul>
<li>Google Maps JavaScript API使ってます。</li>
<li>表示している領域の四隅の緯度経度を取得して、その領域内にあるアイテムを検索・表示しています。</li>
<li>アイテムをクリックして情報ウィンドウを表示させ、そこからGoogle検索することができます。</li>
<li>表示領域が変わると、新たな座標をパラメータにしてREST APIを呼び、表示すべき項目を取得します。</li>
<li>広域表示をすると表示する項目が多くなりすぎるので、最大100件に間引いてます。</li>
<li>雑に間引いているので、ズームアウトすると見えなくなったり見えたりします。</li>
</ul>
URLはこちら。<br/>
<ul>
<li>観光マップ feat. オープンデータ <br/><a href="http://snaga-lab.mybluemix.net/static/maps/index.html">http://snaga-lab.mybluemix.net/static/maps/index.html</a></li>
</ul>
ぐりぐりブラウズしながら、興味のありそうなところをさくっとGoogle検索できます。<br/>
<br/>
日本全域表示。最大100件に間引いて表示してます。(北海道に何も表示されていませんが、これは雑に間引いて表示している偶然の産物であり他意はございません。ズームしていくと見えてきます)<br/>
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi8Q6N31w7ZREWRbcKwKLWG0hVHM6hVzIe-43fFUmvFO9r4iKEa9yPD9Y18SsJxQZVU9zelBaLRYkXLPNOf47DkRgZF33Do7_AiGXShCL4aHHtiLBXJPCSoakgGDIY71PtGksPff664ruM/s1600/001.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi8Q6N31w7ZREWRbcKwKLWG0hVHM6hVzIe-43fFUmvFO9r4iKEa9yPD9Y18SsJxQZVU9zelBaLRYkXLPNOf47DkRgZF33Do7_AiGXShCL4aHHtiLBXJPCSoakgGDIY71PtGksPff664ruM/s400/001.png" width="400" height="225" /></a></div>
たくさん表示されるとちょっとキモい。<br/>
<br/>
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg_nwNv8rcKQrSSKN1Ae8a0nq3z8p9i0QES4kPCiq4AiJ10gptc8aw9mWdn-BmgT6_V49kPq44c-t-jlEP8ijsMbMcrm3jkvHRauCtvBOmf0HzV2PXsMSpRI2UkFR_5MdtxRw2lwf0izFE/s1600/004.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg_nwNv8rcKQrSSKN1Ae8a0nq3z8p9i0QES4kPCiq4AiJ10gptc8aw9mWdn-BmgT6_V49kPq44c-t-jlEP8ijsMbMcrm3jkvHRauCtvBOmf0HzV2PXsMSpRI2UkFR_5MdtxRw2lwf0izFE/s400/004.png" width="400" height="225" /></a></div>
おきなわー。クリックすると情報ウィンドウが表示される。<br/>
<br/>
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEimt9my-1iF7iVLu8qLseP7hMp6-nOpRaGoySeYiMgEGbkJLjnrSc6libmvKi5RNFBws2KQiO8d5mxB-dB0e8FAzKpZDXkW80VQeTS1i_fuHmXb2gZg0EsQrFI0Oar-NUxgcCuMeMTwFzs/s1600/005.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEimt9my-1iF7iVLu8qLseP7hMp6-nOpRaGoySeYiMgEGbkJLjnrSc6libmvKi5RNFBws2KQiO8d5mxB-dB0e8FAzKpZDXkW80VQeTS1i_fuHmXb2gZg0EsQrFI0Oar-NUxgcCuMeMTwFzs/s400/005.png" width="400" height="225" /></a></div>
情報ウィンドウから検索に飛ぶことができる。べんりー。<br/>
<h4 class="post-body">■まとめ</h4><br/>
というわけで、今回はオープンデータをPostgreSQL/PostGISに投入して、クエリをREST API化することによってGoogle Mapsと連携できることを示しました。<br/>
<br/>
先日の<a href="http://pgsqldeepdive.blogspot.jp/2016/09/pgrouting.html">巡回セールスマン問題のエントリ</a>ではMy MapsにKMLファイルをインポートする方式を取りましたので、扱っているデータはあくまでもstaticなデータでしたが、今回はREST APIを使って位置情報を動的に取得して可視化することができることを示しました。<br/>
<br/>
フロントエンドと動的に連携できるようになりましたので、キーワード検索や条件検索などなど、「データベースとつながっていることの価値」が出せるようになるのではないかと思います。<br/>
<br/>
また、今回は時間の都合上確認できませんでしたが、近隣にあるデータをクラスタリングして代表点を求める、みたいなこともできるのではないかと考えています(AVG() GROUP BYのようなノリで)。それができると、広域表示の時にもう少し分かりやすい表示になるように思います。<br/>
<br/>
PostgreSQLの強みの一つにはGISデータの扱いであり、かつ、GISデータを扱えるというだけではなく、先のエントリで紹介したような巡回セールスマン問題のソルバーのようなライブラリが存在している、ということも非常にユニークなところだと思います。<br/>
<br/>
地理情報は、まだまだいろんな使い方ができるのではないかなーと感じています。<br/>
<br/>
興味のある方は、ぜひこれを機会に何かチャレンジしてみていただければと思います。<br/>
<br/>
では、また。<br/>
Satoshi Nagayasuhttp://www.blogger.com/profile/09706093898823214408noreply@blogger.com0tag:blogger.com,1999:blog-4546365304691285308.post-75744784687442495572016-12-01T00:15:00.001+09:002017-01-27T21:10:34.691+09:00Logical Decodingを使ったCDC(Change Data Capture)の実現方法を考えてみる今年も風物詩である PostgreSQL Advent Calendar の時期がやって参りました。Day1担当のデータマエショリスト <a href="https://twitter.com/snaga">@snaga</a> です。<br/>
<ul>
<li>PostgreSQL Advent Calendar 2016 - Qiita <br/><a href="http://qiita.com/advent-calendar/2016/postgresql">http://qiita.com/advent-calendar/2016/postgresql</a></li>
</ul>
去年もDay1を担当した気がしますが、それはさておき。<br/>
<br/>
余談ですが、今年のAdvent Calendarは<br/>
<ul>
<li>[学生さん・初心者さん大歓迎!]Xamarin Advent Calendar 2016 - Qiita <br/><a href="http://qiita.com/advent-calendar/2016/xamarin-welcome">http://qiita.com/advent-calendar/2016/xamarin-welcome</a></li>
</ul>
にも参加しております。また、<br/>
<ul>
<li>C# チュートリアル 全部俺 Advent Calendar 2016 - Qiita <br/><a href="http://qiita.com/advent-calendar/2016/c_sharp_tutorial">http://qiita.com/advent-calendar/2016/c_sharp_tutorial</a></li>
</ul>
というのにも(個人的に)チャレンジしていますので、この辺に興味のある方はよろしければどうぞ。
<h4 class="post-body">■Logical Decoding?</h4><br/>
閑話休題。<br/>
<br/>
皆さんご存知の通り、「Logical Decoding」と呼ばれる機能がPostgreSQL 9.4で導入されました。<br/>
<br/>
PostgreSQLでは「新しい機能入ったらしいが一体何にどう使えばいいんだ?」というような機能が稀によくあります。そのため、2年前にリリースされた機能にも関わらず誰かが使っているという話を聞いたことがない、といったことが起こります。<br/>
<br/>
Logical Decodingにもその空気を感じます。<br/>
<br/>
<a name='more'></a>
個人的には Logical Decoding みたいなものはインフラストラクチャーであって機能ではないと思うのですが、リリースノートにガッツリ「新機能!」とか書かれたりする関係上、いろいろなところでプレゼンなどを通して話に聞くようになるものの、具体的に何に使えるかサッパリ分からんというような事態に遭遇します。分かります。私もです。<br/>
<br/>
不憫な機能はそのままそっとしておいてもいいのですが、今回は歳末助け合いの精神を発揮して使いどころを考えてみたいと思います。Advent Calendarですし。<br/>
<br/>
なお、以降のLogical Decodingの話に特に興味のないという方には、とりあえず以下の動画をお楽しみいただければと思います。<br/>
<br/>
<iframe width="560" height="315" src="https://www.youtube.com/embed/LjrJ_MXN4no" frameborder="0" allowfullscreen></iframe><br/>
<br/>
ありがとうございました。
<h4 class="post-body">■CDC(Change Data Capture)とは何か</h4><br/>
CDC(Change Data Capture)とは、その名の通り「変更を検知・検出する処理」です。データウェアハウスなどの情報系(分析系)システムで出てくる用語で、具体的にはETLの一貫として行われることの多い処理になります。<br/>
<br/>
なぜ情報系のシステムで出てくるのかというと、その瞬間の取引の記録を残すのが要件のオンライン系のシステムと異なり、情報系では「一定の期間に渡るデータの変化」を時系列で分析したいという局面が多々あるからです。<br/>
<br/>
例えば、以下のように会員情報のマスタがあった場合、オンライン系のシステムでは「その取引が発生した瞬間に会員が(住所とか各種ステータスとかが)どういう状態だったのか」が分かっていれば用は足ります。<br/>
<br/>
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgIsHNqbscE35DE93-wkxC60rcCePw_kNS8E2rKQqkIp3H4SxLhP5iUDC9QL7vjQ9MOw3PmNV0zhazKN2lEKwj3HH8zGz623Tw16ky1vkpMBEptwQSp79l2HGpvnmvjVuAjLojZv_QxnYA/s1600/3-1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgIsHNqbscE35DE93-wkxC60rcCePw_kNS8E2rKQqkIp3H4SxLhP5iUDC9QL7vjQ9MOw3PmNV0zhazKN2lEKwj3HH8zGz623Tw16ky1vkpMBEptwQSp79l2HGpvnmvjVuAjLojZv_QxnYA/s400/3-1.png" width="400" height="75" /></a></div>
<br/>
一方で、情報系のシステムで分析をする場合には「どこに住んでいる会員がどれくらいいるのか、それはどれくらい変化しているのか」といった「変化」を把握する必要が出てきます(もちろん分析内容次第ですが)。<br/>
<br/>
そのため、会員情報などのマスタについてもその変化を追える必要がある、具体的に言うと以下のような形式でデータが欲しくなるわけです。<br/>
<br/>
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjKx5czjAANZ5cgZaX4A8DIike4J_p1jFTz-7QvFPJf-dIHx5uYOXjRs17g81qA8blcQ_GvRJXX_1PFxm2OV46l3HR1e71WF9nxEWW3E2h0VkaSm17WUFouICNMqjFkWnkQ9VXzNL5C5W8/s1600/3-2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjKx5czjAANZ5cgZaX4A8DIike4J_p1jFTz-7QvFPJf-dIHx5uYOXjRs17g81qA8blcQ_GvRJXX_1PFxm2OV46l3HR1e71WF9nxEWW3E2h0VkaSm17WUFouICNMqjFkWnkQ9VXzNL5C5W8/s400/3-2.png" width="400" height="95" /></a></div>
<br/>
というわけで、データ分析に適した形に変換するためにオンライン系のテーブルからその変更を検出する処理をCDC(Change Data Capture)と言います。<br/>
<br/>
ETL処理についてガッツリ知りたい方は以下の書籍などを参照してください。<br/>
<ul>
<li>Amazon.co.jp: The Data Warehouse ETL Toolkit: Practical Techniques for Extracting, Cleaning, Conforming, and Delivering Data 電子書籍: Ralph Kimball, Joe Caserta: Kindleストア <br/><a href="https://www.amazon.co.jp/dp/B006JAWFE4/ref=dp-kindle-redirect?_encoding=UTF8">https://www.amazon.co.jp/dp/B006JAWFE4/ref=dp-kindle-redirect?_encoding=UTF8</a></li>
</ul>
<h4 class="post-body">■Logical Decodingとは何か</h4><br/>
次にLogical Decodingの復習です。<br/>
<br/>
Logical DecodingはPostgreSQL 9.4で実装された機能で、トランザクションログの内容を論理的なレコードとして取り出せる、という機能です。<br/>
<br/>
PostgreSQLはRDBMSですので、もともとのトランザクションログは物理的なログ、つまり「どのブロックのどのオフセットにどのようなバイト列を書き込む」というような形式でした。<br/>
<br/>
が、物理的なログでは活用(再利用)できる範囲が限られるため、論理的なログ(いわゆるレコード)として取得できるようになった、というのがLogical Decodingの意味するところです。<br/>
<br/>
Logical Decodingの基礎は以下の記事を読んでいただければと思いますが、基本的には「テーブルへの変更が論理的なレコードの形式で取得できる」、というものです。<br/>
<ul>
<li>PostgreSQLのLogical Decoding機能についての紹介 - Qiita <br/><a href="http://qiita.com/bwtakacy/items/d8461518a1770524e0d6">http://qiita.com/bwtakacy/items/d8461518a1770524e0d6</a></li>
</ul>
<h4 class="post-body">■CDCに必要な要件</h4><br/>
というわけで、本エントリでは「Logical DecodingをCDCに使えるのか」という点について、もう少し詳細に見ていこうと思います。<br/>
<br/>
それトリガでやればいいじゃん、とか、夜間バッチで、といった方式もありますが(実際私もやっておりますが)、「ですよねー」と言った瞬間に話が終わるので、今回はLogical Decodingで検討してみることにします。(某社さんのGoldenGateとかってのもこの方式らしいですしおすし)<br/>
<br/>
CDCを実現するに当たって本質的に必要な要件が3つほどあります。(snaga調べ)<br/>
<br/>
まずは「レコードを特定することができ、新規作成か更新かを判別することができる」ということです。つまり、「このレコードは新規レコードなのか、それとも既存のレコードの変更なのか」を判別する必要があるのです。<br/>
<br/>
これは一般的には主キーまたはユニークインデックスの作成されたカラムを使って実現されます。<br/>
<br/>
主キーが「A」というレコードがテーブルに存在している場合、新たに入ってきたログが「A」という主キーを持っていれば既存レコードの更新(UPDATE)、「B」という主キーを持っていたら新規レコード(INSERT)と判別します。<br/>
<br/>
2つ目の要件は「変更を検出したい対象のカラムを絞ることができる」ということです。<br/>
<br/>
つまり、「変更されたら検出したいカラム」と「変更されても検出したくないカラム」を設定できることが重要です。<br/>
<br/>
例えば、会員のマスターとなるテーブルに「住所」と「最終ログイン時刻」のような情報を保持している場合もあるかと思いますが、住所の変更は検出したくても、最終ログイン時刻は検出したくない、といったケースが考えられます。<br/>
<br/>
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhLiCyAXyKLPMkNE8CZQNBUJcshCV7m3bhehX-2We5NVyKFPyUkQzSU77OH2EzAhf3ZqCsCsObbbARBc2bZ4JopKRee6zuHA4nZ3wBZ4S8sw8WbH73PdENVZJXcsbtg_WpzG1lum5WzlNY/s1600/3-3.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhLiCyAXyKLPMkNE8CZQNBUJcshCV7m3bhehX-2We5NVyKFPyUkQzSU77OH2EzAhf3ZqCsCsObbbARBc2bZ4JopKRee6zuHA4nZ3wBZ4S8sw8WbH73PdENVZJXcsbtg_WpzG1lum5WzlNY/s400/3-3.png" width="400" height="283" /></a></div>
<br/>
このような時に「住所」や「ステータス」の変更だけを検出できるというのもCDCに必要な要件になります。<br/>
<br/>
そして、最後は「データが(論理的に)変更されていない時にはログを吐かない」ということです。<br/>
<br/>
これが物理ログと論理ログの違いであり、「論理ログの量」を必要最小限に抑えるために重要な要件となります。
<h4 class="post-body">■Logical Decodingのセットアップ</h4><br/>
さて、というわけで、そろそろLogical Decodingの出力を詳細に見てみます。<br/>
<br/>
Logical Decodingでは出力プラグインを自由に設定できますが、今回は test_decoding を使います。<br/>
<ul>
<li>PostgreSQL: Documentation: 9.6: test_decoding <br/><a href="https://www.postgresql.org/docs/9.6/static/test-decoding.html">https://www.postgresql.org/docs/9.6/static/test-decoding.html</a></li>
</ul>
test_decodingの詳細はマニュアルを参照してください、と言いたいところなのですが、マニュアルにはまったく詳細が書かれていないので、それは言うだけ野暮というものです。<br/>
<br/>
まず、以下の設定をpostgresql.confにします。<br/>
<pre style="overflow-x: scroll; white-space: pre">
wal_level = logical
max_replication_slot = 3
</pre>
<br/>
次に、レプリケーションスロットを作成します。<br/>
<br/>
レプリケーションスロットは、Logical Decodingでデータを取得する対象のデータベース上で作成する必要があるので注意してください。(今回はtestdb)<br/>
<pre style="overflow-x: scroll; white-space: pre">
testdb=# SELECT * FROM pg_replication_slots;
slot_name | plugin | slot_type | datoid | database | active | active_pid | xmin | catalog_xmin | restart_lsn | confirmed_flush_lsn
-----------+--------+-----------+--------+----------+--------+------------+------+--------------+-------------+---------------------
(0 行)
testdb=# SELECT * FROM pg_create_logical_replication_slot('testslot0', 'test_decoding');
slot_name | xlog_position
-----------+---------------
testslot0 | 2/CFE57030
(1 row)
testdb=# SELECT * FROM pg_replication_slots;
slot_name | plugin | slot_type | datoid | database | active | active_pid | xmin | catalog_xmin | restart_lsn | confirmed_flush_lsn
-----------+---------------+-----------+--------+----------+--------+------------+------+--------------+-------------+---------------------
testslot0 | test_decoding | logical | 83604 | testdb | f | | | 4283 | 1/FF467510 | 1/FF467548
(1 行)
testdb=#
</pre>
<br/>
まず、テーブルを作成します。<br/>
<pre style="overflow-x: scroll; white-space: pre">
testdb=# create table k1 (
testdb(# uid integer primary key,
testdb(# uname text not null,
testdb(# gname text not null
testdb(# );
CREATE TABLE
testdb=#
</pre>
<br/>
この時、レプリケーションスロットからは以下のデータを取得できます。<br/>
<pre style="overflow-x: scroll; white-space: pre">
testdb=# SELECT * FROM pg_logical_slot_get_changes('testslot0', NULL, NULL, 'include-xids', '0');
location | xid | data
------------+------+--------
1/FF4675B0 | 4283 | BEGIN
1/FF4861C0 | 4283 | COMMIT
(2 行)
testdb=#
</pre>
<br/>
どうやらDDLのデータは取得できないようです。
<h4 class="post-body">■Logical Decodingでは何が出力され、何を取得できるのか</h4><br/>
それでは、まずレコードをINSERTしてみます。<br/>
<br/>
<pre style="overflow-x: scroll; white-space: pre">
testdb=# insert into k1 values (1, 'Park Gyu-ri', 'KARA');
INSERT 0 1
testdb=#
</pre>
<br/>
この時、以下のようなログを取得することができます。<br/>
<pre style="overflow-x: scroll; white-space: pre">
testdb=# SELECT * FROM pg_logical_slot_get_changes('testslot0', NULL, NULL, 'include-xids', '0');
location | xid | data
------------+------+--------------------------------------------------------------------------------------
1/FF4861F8 | 4284 | BEGIN
1/FF4861F8 | 4284 | table public.k1: INSERT: uid[integer]:1 uname[text]:'Park Gyu-ri' gname[text]:'KARA'
1/FF486308 | 4284 | COMMIT
(3 行)
testdb=#
</pre>
主キーの値と、名前が出力されています。<br/>
<br/>
複数レコードを一括してINSERTすると、<br/>
<pre style="overflow-x: scroll; white-space: pre">
testdb=# insert into k1 values (2, 'Nicole Jung', 'KARA'),
testdb-# (3, 'Goo Ha-ra', 'KARA'),
testdb-# (4, 'Han Seung-yeon', 'KARA'),
testdb-# (5, 'Kang Ji-young', 'KARA');
INSERT 0 4
testdb=#
</pre>
<br/>
以下のようなログになります。<br/>
<pre style="overflow-x: scroll; white-space: pre">
testdb=# SELECT * FROM pg_logical_slot_get_changes('testslot0', NULL, NULL, 'include-xids', '0');
location | xid | data
------------+------+-----------------------------------------------------------------------------------------
1/FF486340 | 4285 | BEGIN
1/FF486340 | 4285 | table public.k1: INSERT: uid[integer]:2 uname[text]:'Nicole Jung' gname[text]:'KARA'
1/FF4863D0 | 4285 | table public.k1: INSERT: uid[integer]:3 uname[text]:'Goo Ha-ra' gname[text]:'KARA'
1/FF486460 | 4285 | table public.k1: INSERT: uid[integer]:4 uname[text]:'Han Seung-yeon' gname[text]:'KARA'
1/FF4864F0 | 4285 | table public.k1: INSERT: uid[integer]:5 uname[text]:'Kang Ji-young' gname[text]:'KARA'
1/FF4865B0 | 4285 | COMMIT
(6 行)
testdb=#
</pre>
<br/>
次に、主キーを指定してレコードを更新してみます。<br/>
<pre style="overflow-x: scroll; white-space: pre">
testdb=# update k1 set uname = 'Nicole' where uid = 2;
UPDATE 1
testdb=#
</pre>
<br/>
この時のログは以下のようになります。主キーおよび更新された属性(今回は名前)が出力されています。<br/>
<pre style="overflow-x: scroll; white-space: pre">
testdb=# SELECT * FROM pg_logical_slot_get_changes('testslot0', NULL, NULL, 'include-xids', '0');
location | xid | data
------------+------+---------------------------------------------------------------------------------
1/FF4865E8 | 4286 | BEGIN
1/FF4865E8 | 4286 | table public.k1: UPDATE: uid[integer]:2 uname[text]:'Nicole' gname[text]:'KARA'
1/FF486670 | 4286 | COMMIT
(3 行)
testdb=#
</pre>
<br/>
では次に、主キーを指定しないで更新してみます。<br/>
<pre style="overflow-x: scroll; white-space: pre">
testdb=# update k1 set uname = 'Nicole Jung' where uname = 'Nicole';
UPDATE 1
testdb=#
</pre>
<br/>
この時、以下のログを取得できます。主キーを指定しない更新でしたが、ログには主キーの情報も出力されています。<br/>
<pre style="overflow-x: scroll; white-space: pre">
testdb=# SELECT * FROM pg_logical_slot_get_changes('testslot0', NULL, NULL, 'include-xids', '0');
location | xid | data
------------+------+--------------------------------------------------------------------------------------
1/FF4866A8 | 4287 | BEGIN
1/FF4866A8 | 4287 | table public.k1: UPDATE: uid[integer]:2 uname[text]:'Nicole Jung' gname[text]:'KARA'
1/FF486730 | 4287 | COMMIT
(3 行)
testdb=#
</pre>
<br/>
なお、値が変わらない更新をしてみると、<br/>
<pre style="overflow-x: scroll; white-space: pre">
testdb=# update k1 set uname = uname;
UPDATE 5
testdb=#
</pre>
<br/>
律儀に全レコードの更新ログが出力されます。<br/>
<pre style="overflow-x: scroll; white-space: pre">
testdb=# SELECT * FROM pg_logical_slot_get_changes('testslot0', NULL, NULL, 'include-xids', '0');
location | xid | data
------------+------+-----------------------------------------------------------------------------------------
1/FF4869C0 | 4288 | BEGIN
1/FF4869C0 | 4288 | table public.k1: UPDATE: uid[integer]:1 uname[text]:'Park Gyu-ri' gname[text]:'KARA'
1/FF486A18 | 4288 | table public.k1: UPDATE: uid[integer]:3 uname[text]:'Goo Ha-ra' gname[text]:'KARA'
1/FF486A70 | 4288 | table public.k1: UPDATE: uid[integer]:4 uname[text]:'Han Seung-yeon' gname[text]:'KARA'
1/FF486AD0 | 4288 | table public.k1: UPDATE: uid[integer]:5 uname[text]:'Kang Ji-young' gname[text]:'KARA'
1/FF486B30 | 4288 | table public.k1: UPDATE: uid[integer]:2 uname[text]:'Nicole Jung' gname[text]:'KARA'
1/FF486BB8 | 4288 | COMMIT
(7 行)
testdb=#
</pre>
<br/>
最後に主キーを指定せずに削除すると<br/>
<pre style="overflow-x: scroll; white-space: pre">
testdb=# delete from k1;
DELETE 5
testdb=#
</pre>
<br/>
主キーのみを出力として含むログを取得できます。<br/>
<pre style="overflow-x: scroll; white-space: pre">
testdb=# SELECT * FROM pg_logical_slot_get_changes('testslot0', NULL, NULL, 'include-xids', '0');
location | xid | data
------------+------+-----------------------------------------
1/FF486BF0 | 4289 | BEGIN
1/FF486BF0 | 4289 | table public.k1: DELETE: uid[integer]:1
1/FF486C30 | 4289 | table public.k1: DELETE: uid[integer]:3
1/FF486C70 | 4289 | table public.k1: DELETE: uid[integer]:4
1/FF486CB0 | 4289 | table public.k1: DELETE: uid[integer]:5
1/FF486CF0 | 4289 | table public.k1: DELETE: uid[integer]:2
1/FF486D60 | 4289 | COMMIT
(7 行)
testdb=#
</pre>
<br/>
<h4 class="post-body">■要するに、Logical DecodingはCDCに使えるのか?</h4><br/>
ここまで見てきたように、Logical Decodingではテーブルに主キーが存在していれば、主キーを指定しない更新であってもログに主キーが出力されることが分かりました。そのため、「レコードを特定して新規か更新かを判別する」ということが可能になります。<br/>
<br/>
一方で、「変更を検知したいカラムだけを対象にする」という要件については、現在のLogical Decoding(というか test_decoding プラグイン)の仕様としては、(変更されていないカラムも含めて)すべてのカラムの変更の際にログが出力されることになります。よって、変更を検知する対象としてカラムを絞りたいといった場合には別のしくみが必要になります。<br/>
<br/>
通信の負荷などを考えると、Loigcal Decodingのログを受け取るアプリ側ではなく、ログを出力する Output プラグイン側でフィルターできるようにするべきでしょう。<br/>
<br/>
また、「論理的に値が変わっていない時にはログを吐かない」という点についても、もう一工夫が必要なように感じます。<br/>
<br/>
test_decoding のソースを見ると、テーブルの各カラムの情報である tupledesc と、更新前および更新後のタプルのデータ oldtuple と newtuple を扱えるようですので、この辺りを使えばCDCに必要な要件を実現できるように思います。(汎用的に実現するにはそれなりに手間がかかりそうですが・・・)<br/>
<ul>
<li>postgres/test_decoding.c at master · postgres/postgres <br/><a href="https://github.com/postgres/postgres/blob/master/contrib/test_decoding/test_decoding.c">https://github.com/postgres/postgres/blob/master/contrib/test_decoding/test_decoding.c</a></li>
</ul>
<pre style="overflow-x: scroll; white-space: pre">
/*
* callback for individual changed tuples
*/
static void
pg_decode_change(LogicalDecodingContext *ctx, ReorderBufferTXN *txn,
Relation relation, ReorderBufferChange *change)
{
...
TupleDesc tupdesc;
...
tupdesc = RelationGetDescr(relation);
...
case REORDER_BUFFER_CHANGE_UPDATE:
appendStringInfoString(ctx->out, " UPDATE:");
if (change->data.tp.oldtuple != NULL)
{
appendStringInfoString(ctx->out, " old-key:");
tuple_to_stringinfo(ctx->out, tupdesc,
&change->data.tp.oldtuple->tuple,
true);
appendStringInfoString(ctx->out, " new-tuple:");
}
if (change->data.tp.newtuple == NULL)
appendStringInfoString(ctx->out, " (no-tuple-data)");
else
tuple_to_stringinfo(ctx->out, tupdesc,
&change->data.tp.newtuple->tuple,
false);
break;
</pre>
<br/>
<h4 class="post-body">■まとめ</h4><br/>
まとめます。<br/>
<ul>
<li>誰か汎用CDC用プラグイン作ってください</li>
<li>「トリガーとかバッチでいいじゃん」って言わない</li>
</ul>
PostgreSQL Advent Calendar 2016、Day2の明日の担当は <a href="https://twitter.com/seikoudoku2000">@seikoudoku2000</a> さんです。<br/>
<br/>
では、また。<br/>
<br/>
みなさま、良いお年を。<br/>
Satoshi Nagayasuhttp://www.blogger.com/profile/09706093898823214408noreply@blogger.com0tag:blogger.com,1999:blog-4546365304691285308.post-16286974056231669472016-11-06T16:51:00.000+09:002016-11-06T22:26:36.264+09:00Jupyter NotebookからPostgreSQLに接続してデータを可視化する<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgsrKGiwktDq5mNS0FITIYvbdK2UUxiFDRPbOpC_Oa6tGAgmFIw-2qEUM2BcKljNgUaczNAt0seu2S4gleit3AjlJVVhk6lmtVGaqXVHTgIrTdS_vNXPePprcQybd3H1xCsxPioqUaHO3c/s1600/title.png" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgsrKGiwktDq5mNS0FITIYvbdK2UUxiFDRPbOpC_Oa6tGAgmFIw-2qEUM2BcKljNgUaczNAt0seu2S4gleit3AjlJVVhk6lmtVGaqXVHTgIrTdS_vNXPePprcQybd3H1xCsxPioqUaHO3c/s320/title.png" width="320" height="242" /></a></div>
最近、なんだかんだとデータに触る機会が増えてきております。<br/>
<br/>
Unix系エンジニア兼DBAとしては、CLI(コマンドラインインターフェース)が生産性が高くて好きだけど、一方で可視化もお手軽にやりたい、というケースが多々あります。<br/>
<br/>
Jupyter Notebookでデータベースに接続して可視化できる、という話は以前から聞いたことがあったのですが、実際に試してみたことがありませんでした。<br/>
<br/>
今回、軽くPostgreSQLで試してみたのでその手順を簡単にご紹介します。
<h4 class="post-body">■セットアップ</h4><br/>
以下の3つのモジュールをpipでインストールします。<br/>
<ul>
<li>jupyter</li>
<li>psycopg2</li>
<li>ipython-sql</li>
</ul>
<a name='more'></a>
<pre style="overflow-x: scroll; white-space: pre">
[snaga@localhost]$ ipython notebook --ip=\* --port=8080
[W 16:01:11.273 NotebookApp] WARNING: The notebook server is listening on all IP addresses and not using encryption. This is not recommended.
[W 16:01:11.273 NotebookApp] WARNING: The notebook server is listening on all IP addresses and not using authentication. This is highly insecure and not recommended.
[I 16:01:11.276 NotebookApp] Serving notebooks from local directory: /disk/disk1/snaga
[I 16:01:11.276 NotebookApp] 0 active kernels
[I 16:01:11.276 NotebookApp] The IPython Notebook is running at: http://[all ip addresses on your system]:8080/
[I 16:01:11.276 NotebookApp] Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).
</pre>
<br/>
と起動してブラウザから接続できるようにします。
<h4 class="post-body">■PostgreSQLデータベースへの接続</h4><br/>
まず、sql拡張をロードして、PostgreSQLへ接続します。<br/>
<br/>
<pre style="overflow-x: scroll; white-space: pre">
%load_ext sql
%sql postgresql://snaga@localhost/testdb
</pre>
<br/>
「Connected」と表示されたら成功です。<br/>
<br/>
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhqiGpvtXAPm7KR2E7G5qdvCJC8zEzuJ1ImotL3u9ci_yFuApBfxUgwew4z-pv_omK0-2KWesjGz8u9qYHFcLQzzi6ZArleIRcZlPdEdDYQsb81eyPb9IFmDOsVm179mBAKe_vMSpRqOhw/s1600/connected.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhqiGpvtXAPm7KR2E7G5qdvCJC8zEzuJ1ImotL3u9ci_yFuApBfxUgwew4z-pv_omK0-2KWesjGz8u9qYHFcLQzzi6ZArleIRcZlPdEdDYQsb81eyPb9IFmDOsVm179mBAKe_vMSpRqOhw/s640/connected.png" width="640" height="68" /></a></div>
<h4 class="post-body">■クエリの実行</h4><br/>
クエリを実行するには、「%sql」に続けてクエリを入力します。<br/>
<br/>
<pre style="overflow-x: scroll; white-space: pre">
%sql select table_catalog,table_schema,table_name,table_type from information_schema.tables where table_schema = 'public'
%sql select count(*) from orders
</pre>
<br/>
SQLの実行だけを行うと、その結果が表形式で表示されます。<br/>
<br/>
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhPywmDmAicLM9dGEDXfe_DYDqBgVSqhFtZjqJ8cBibadep8dhCEXRifpANbRrla2VyzdXU3Fmf2EcoYnrv0FDWpV5yiixEL4PcjFFh2PLxcbKRlpcKErNbiDGOR8gB6bsWBcclSvCtNIA/s1600/query.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhPywmDmAicLM9dGEDXfe_DYDqBgVSqhFtZjqJ8cBibadep8dhCEXRifpANbRrla2VyzdXU3Fmf2EcoYnrv0FDWpV5yiixEL4PcjFFh2PLxcbKRlpcKErNbiDGOR8gB6bsWBcclSvCtNIA/s640/query.png" width="640" height="310" /></a></div>
<h4 class="post-body">■問い合わせ結果の利用</h4><br/>
問い合わせ結果は「_」という変数に格納されていますので、これを取り出します。<br/>
<br/>
ここでは、顧客ごとの売り上げを集計するクエリを実行し、その後で変数 res に結果を取り出します。<br/>
<br/>
<pre style="overflow-x: scroll; white-space: pre">
# https://pypi.python.org/pypi/ipython-sql
%config SqlMagic.displaylimit=5
%sql select c_name,sum(o_totalprice) from customer left outer join orders on c_custkey = o_custkey group by c_name order by 2 desc
res = _
</pre>
<br/>
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgR3Xc-yvD-tyA20WVTO0fRTHWxzh9o2B5IkmNY9SxrJ0URqQGK8OcJE0Ua-QhU5Lo-Czqs1HaUkLmz5NxFeK2DrTtLVKA8-l9VuUrPFPkCmG-mSGc8NerXl2sMicR2anvoJT3itbYx_34/s1600/resultset.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgR3Xc-yvD-tyA20WVTO0fRTHWxzh9o2B5IkmNY9SxrJ0URqQGK8OcJE0Ua-QhU5Lo-Czqs1HaUkLmz5NxFeK2DrTtLVKA8-l9VuUrPFPkCmG-mSGc8NerXl2sMicR2anvoJT3itbYx_34/s640/resultset.png" width="640" height="216" /></a></div>
<h4 class="post-body">■問い合わせ結果の可視化</h4><br/>
最後にmatplotlibを使って可視化します。<br/>
<br/>
取り出した問い合わせ結果は普通にfor文で1レコードずつ取り出すことができますし、そのレコードはカラムのリストになっていますので、通常のSQLアクセスと同じようにデータを取り出します。<br/>
<br/>
そして matplotlib に渡してグラフを描画します。<br/>
<pre style="overflow-x: scroll; white-space: pre">
import matplotlib.pyplot as plt
%matplotlib inline
x = [rr[0] for rr in res]
y = [rr[1] for rr in res]
plt.bar(range(len(x)), y)
</pre>
<br/>
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjvoy6dmNYgYz-NCxUM4hFjOzRYWizUC-EknuhLWDkNzz8OPeHDxxTm09H6GdX1PwdD3E8vMxG9O3jeBVU9N5KZI1g592zPzz4fdHXvxYjqPflLdLfv5ADE-dfdaDTcv9n__HB4Hdz9P7E/s1600/matplotlib.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjvoy6dmNYgYz-NCxUM4hFjOzRYWizUC-EknuhLWDkNzz8OPeHDxxTm09H6GdX1PwdD3E8vMxG9O3jeBVU9N5KZI1g592zPzz4fdHXvxYjqPflLdLfv5ADE-dfdaDTcv9n__HB4Hdz9P7E/s640/matplotlib.png" width="640" height="260" /></a></div>
<br/>
matplotlibの詳細については、長くなるのでここでは割愛します。(というか、私もまだ詳しくないので・・・)
<h4 class="post-body">■まとめ</h4><br/>
今回は、Jupyter Notebookから直接SQLを発行して取得したデータを可視化する方法を試してみました。<br/>
<br/>
Jupyter Notebookは探索的にデータを分析したり、作業の過程を記録に残したりするのに非常に便利です。また、matplotlibはさまざまなチャートを描くことができます。<br/>
<br/>
ぜひ、このようなツールを活用しつつ、データでいろいろ遊んでみていただければと思います。<br/>
<br/>
私ももう少しmatplotlibを活用できるように勉強をしてみようと思います。<br/>
<br/>
では。
<h4 class="post-body">■参考文献</h4><br/>
<ul>
<li>現代のエンジニアのための強力なメモ帳 Jupyter notebookのすゝめ - クックパッド開発者ブログ <br/><a href="http://techlife.cookpad.com/entry/write-once-share-anywhare">http://techlife.cookpad.com/entry/write-once-share-anywhare</a></li>
<li>Jupyter notebookでMySQLに接続する - Qiita <br/><a href="http://qiita.com/shrkw/items/c38def7d60b0099b0c55">http://qiita.com/shrkw/items/c38def7d60b0099b0c55</a></li>
<li>Jupyter から PostgreSQL に接続 - Qiita <br/><a href="http://qiita.com/kshigeru/items/d5554c6d41cedf471a91">http://qiita.com/kshigeru/items/d5554c6d41cedf471a91</a></li>
<li>ipython-sql 0.3.8 : Python Package Index <br/><a href="https://pypi.python.org/pypi/ipython-sql">https://pypi.python.org/pypi/ipython-sql</a></li>
</ul>
Satoshi Nagayasuhttp://www.blogger.com/profile/09706093898823214408noreply@blogger.com0tag:blogger.com,1999:blog-4546365304691285308.post-12380988307166109592016-10-01T17:07:00.001+09:002016-10-02T14:24:56.516+09:00PostgreSQL 9.5日本語マニュアルの検索システムをリリースしました(追記あり)<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgwS1pd19o02H5qe43iCADVkyViIiNW28LHNa7cAeVlQ7uIalj6klILmVpVd5pbga28QVudrnl3mZbdOfUKG3_3CRDNGkqxgqJ36FPqLF8ZdRFgPIcStiwUdGbViLpQWNOX2RtGab1h5c8/s1600/ss.png" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgwS1pd19o02H5qe43iCADVkyViIiNW28LHNa7cAeVlQ7uIalj6klILmVpVd5pbga28QVudrnl3mZbdOfUKG3_3CRDNGkqxgqJ36FPqLF8ZdRFgPIcStiwUdGbViLpQWNOX2RtGab1h5c8/s320/ss.png" width="320" height="232" /></a></div>PostgreSQL 9.5の日本語マニュアルの検索システムをリリースしたので、ご紹介します。<br/>
<ul>
<li>PostgreSQL 9.5 マニュアル検索 <br/><a href="http://snaga-lab.mybluemix.net/static/pgdoc/index.html">http://snaga-lab.mybluemix.net/static/pgdoc/index.html</a></li>
</ul>
少し前からPostgreSQLのマニュアルを細かく調べる必要性が出てきたのですが、ご存じの通り、PostgreSQLのオンラインのマニュアルはGoogleと相性が良くありません。<br/>
<br/>
本当はgrep -cでもいいくらいの機能なのですが、公開されているフォーマットがHTML、マニュアルのソースファイルはSGMLファイルなので、実際にそのままgrepしても、見栄え的にあまり嬉しくありません。<br/>
<br/>
そのため、自分の開発の練習もかねてWebアプリとして作ってみました。
<h4 class="post-body">■マニュアル検索システムの機能</h4><br/>
検索システムのURLは以下です。<br/>
<ul>
<li>PostgreSQL 9.5 マニュアル検索 <br/><a href="http://snaga-lab.mybluemix.net/static/pgdoc/index.html">http://snaga-lab.mybluemix.net/static/pgdoc/index.html</a></li>
</ul>
検索対象となるのは、日本PostgreSQLユーザ会が翻訳して以下で公開しているPostgreSQL 9.5の日本語マニュアルで、リリースノートと索引を除いたページです。<br/>
<ul>
<li>PostgreSQL 9.5.4文書 <br/><a href="http://www.postgresql.jp/document/9.5/html/index.html">http://www.postgresql.jp/document/9.5/html/index.html</a></li>
</ul>
<a name='more'></a>
マニュアルのページ自体は、日本PostgreSQLユーザ会のマニュアルをオンラインで参照する形になっています。<br/>
<br/>
検索キーワードを入力すると、キーワードが合致した回数が多いページから順に表示します。検索結果のページタイトルの右側に表示されている数字は、キーワードが合致した回数です。1回でも合致したページはすべて表示します。<br/>
<br/>
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhsTsiUOh0FgXVWyiQMfUCBqyOk6vZluv_roNw_72Z66flElZVhUyaJeKzVDqyHJVO27yYNUxxnAzxTUpC2XWIakD2RqVqFXBEGlYMtlY0c8lkN5-tRk74JewL4ZiHCNEeH_0J1CNY9sVU/s1600/fig2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhsTsiUOh0FgXVWyiQMfUCBqyOk6vZluv_roNw_72Z66flElZVhUyaJeKzVDqyHJVO27yYNUxxnAzxTUpC2XWIakD2RqVqFXBEGlYMtlY0c8lkN5-tRk74JewL4ZiHCNEeH_0J1CNY9sVU/s400/fig2.png" width="284" height="400" /></a></div>
<br/>
また、複数のキーワードをスペースで区切って入力することで、OR検索またはAND検索ができます。OR検索では、「いずれかのキーワードを含むページ」を表示します。AND検索では「すべてのキーワードを含むページ」を表示します。<br/>
<br/>
OR検索の場合は「単語 単語」のように単にスペースで区切って入力してください。AND検索の場合には「単語 +単語」のように、いずれかの単語の最初にプラス記号を付加してください。プラス記号を付加するとAND検索に切り替わります。<br/>
<br/>
「性能」や「パフォーマンス」などのように言い換えた言葉で記載されている個所をすべて確認したい場合にはOR検索を使うといいでしょう。一方で、「ログ」、「アーカイブ」のように異なる単語で絞り込む場合には、AND検索を使うといいでしょう。<br/>
<br/>
単語の出現回数をもとにランキングする仕組みであるため、ページの長さでnormalize(単語の出現回数を文書の長さで割る)しようかとも思ったのですが、「長いページである」というのもひとつの情報であり、そのままにした方がより価値があると考えたためnormalizeはしていません。
<h4 class="post-body">■検索の仕組み</h4><br/>
この検索システムは、Python用のWebフレームワークであるFlaskを使ったWebアプリケーションとして実装されています。<br/>
<br/>
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgKPN8TU0C3lFu9iesMe5J6SeHMi1xocCFn6y6wQleSfizVC1ulCWdXBDzKtDvnLWom7oBd_NlautrhtEVltjMnOC42BeDftoKhIu25NXAL6I1e3mga1Q_tksUmOafCXah-cPy4UhfDQc0/s1600/fig.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgKPN8TU0C3lFu9iesMe5J6SeHMi1xocCFn6y6wQleSfizVC1ulCWdXBDzKtDvnLWom7oBd_NlautrhtEVltjMnOC42BeDftoKhIu25NXAL6I1e3mga1Q_tksUmOafCXah-cPy4UhfDQc0/s400/fig.png" width="400" height="242" /></a></div>
<br/>
以前のエントリで紹介したように、PostgreSQLのマニュアルをwgetで取得、データベースに格納して、HTMLからプレーンテキストに変換し、それを検索対象としています。<br/>
<ul>
<li>TF-IDFでデータベース内の類似テキストを検索する Part 2 (実践編) <br/><a href="http://pgsqldeepdive.blogspot.jp/2016/07/tf-idf-part-2.html">http://pgsqldeepdive.blogspot.jp/2016/07/tf-idf-part-2.html</a></li>
</ul>
今回のテーブルは以下の通りです。<br/>
<pre style="overflow-x: scroll; white-space: pre">
testdb=> \d pgdoc
Table "public.pgdoc"
Column | Type | Modifiers
----------+---------+-------------------------------------------------------
docid | integer | not null default nextval('pgdoc_docid_seq'::regclass)
filename | text | not null
html | text | not null
plain | text |
title | text |
Indexes:
"pgdoc_pkey" PRIMARY KEY, btree (docid)
</pre>
<br/>
ページごとのスコア(キーワード出現回数)を取得するために、ドキュメントとキーワードのtext配列を渡すと、合致した回数をスコアとして返却するPL/PythonのSQL関数を作成します。<br/>
<br/>
<pre style="overflow-x: scroll; white-space: pre">
CREATE OR REPLACE FUNCTION pgdoc_score(doc text, q text[])
RETURNS float8
AS $$
import re
score = 0
for t in q:
f = re.findall(t, doc, flags=re.IGNORECASE)
score += len(f)
return score
$$
LANGUAGE 'plpython2u';
</pre>
<br/>
この関数は、以下のように大文字小文字を違いを無視して、キーワードが合致した回数を返却します。<br/>
<pre style="overflow-x: scroll; white-space: pre">
testdb=> SELECT pgdoc_score('foo foo bar bar bar', '{"foo", "BAR"}');
pgdoc_score
-------------
5
(1 row)
testdb=>
</pre>
<br/>
このSQL関数を使って、指定したキーワードに対するスコアを取得し、それをスコア順に並べ替えて表示しています。<br/>
<pre style="overflow-x: scroll; white-space: pre">
testdb=> SELECT docid,filename,title,pgdoc_score(plain, '{WAL}') FROM pgdoc ORDER BY 4 DESC LIMIT 5;
docid | filename | title | pgdoc_score
-------+---------------------------------+------------------------------------------------------------+-------------
1181 | continuous-archiving.html | 24.3. 継続的アーカイブとポイントインタイムリカバリ(PITR) | 85
178 | runtime-config-wal.html | 18.5. ログ先行書き込み(WAL) | 77
69 | warm-standby.html | 25.2. ログシッピングスタンバイサーバ | 70
907 | wal-configuration.html | 29.4. WALの設定 | 56
1011 | runtime-config-replication.html | 18.6. レプリケーション | 40
(5 rows)
</pre>
<br/>
<h4 class="post-body">■pg_bigmを試してみる</h4><br/>
なお、全文検索ということでpg_bigmを使ってみたのですが、これくらいの文章量だと少なすぎてインデックスをうまく使ってくれませんでした。実行プランを見ても、シーケンシャルスキャンの方がコストが低いと判断されているようです。<br/>
<br/>
<pre style="overflow-x: scroll; white-space: pre">
testdb=> CREATE INDEX pgdoc_plain_idx ON pgdoc USING GIN (plain gin_bigm_ops);
CREATE INDEX
testdb=> EXPLAIN ANALYZE SELECT
testdb-> docid,
testdb-> title,
testdb-> filename,
testdb-> pgdoc_score(plain,'{ログ,アーカイブ}')
testdb-> FROM
testdb-> pgdoc
testdb-> WHERE
testdb-> docid in (SELECT docid FROM pgdoc WHERE (plain ILIKE '%ログ%' AND plain ILIKE '%アーカイブ%') AND filename NOT LIKE 'release-%' AND filename <> 'bookindex.html')
testdb-> ORDER BY
testdb-> 4 DESC;
QUERY PLAN
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------
Sort (cost=170.75..170.75 rows=1 width=755) (actual time=201.327..201.362 rows=38 loops=1)
Sort Key: (pgdoc_score(pgdoc.plain, '{ログ,アーカイブ}'::text[]))
Sort Method: quicksort Memory: 30kB
-> Nested Loop (cost=0.28..170.74 rows=1 width=755) (actual time=2.166..201.201 rows=38 loops=1)
-> Seq Scan on pgdoc pgdoc_1 (cost=0.00..162.18 rows=1 width=4) (actual time=1.387..169.877 rows=38 loops=1)
Filter: ((plain ~~* '%ログ%'::text) AND (plain ~~* '%アーカイブ%'::text) AND (filename !~~ 'release-%'::text) AND (filename <> 'bookindex.html'::text))
Rows Removed by Filter: 1271
-> Index Scan using pgdoc_pkey on pgdoc (cost=0.28..8.30 rows=1 width=755) (actual time=0.004..0.006 rows=1 loops=38)
Index Cond: (docid = pgdoc_1.docid)
Planning time: 1.654 ms
Execution time: 201.443 ms
(11 行)
</pre>
<br/>
enable_seqscanパラメータをoffにして強制的にインデックスを使うようにしてみましたが、それでもpg_bigmのインデックスではなく主キーのインデックスが使われてしまいました。しかも推定コストはこちらの方が高く、実際の実行時間もほとんど変わりませんでした。<br/>
<pre style="overflow-x: scroll; white-space: pre">
testdb=> set enable_seqscan TO off;
SET
testdb=> EXPLAIN ANALYZE SELECT
testdb-> docid,
testdb-> title,
testdb-> filename,
testdb-> pgdoc_score(plain,'{ログ,アーカイブ}')
testdb-> FROM
testdb-> pgdoc
testdb-> WHERE
testdb-> docid in (SELECT docid FROM pgdoc WHERE (plain ILIKE '%ログ%' AND plain ILIKE '%アーカイブ%') AND filename NOT LIKE 'release-%' AND filename <> 'bookindex.html')
testdb-> ORDER BY
testdb-> 4 DESC;
QUERY PLAN
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------
Sort (cost=381.05..381.05 rows=1 width=755) (actual time=200.820..200.854 rows=38 loops=1)
Sort Key: (pgdoc_score(pgdoc.plain, '{ログ,アーカイブ}'::text[]))
Sort Method: quicksort Memory: 30kB
-> Nested Loop (cost=0.56..381.04 rows=1 width=755) (actual time=3.918..200.702 rows=38 loops=1)
-> Index Scan using pgdoc_pkey on pgdoc pgdoc_1 (cost=0.28..372.48 rows=1 width=4) (actual time=3.136..169.352 rows=38 loops=1)
Filter: ((plain ~~* '%ログ%'::text) AND (plain ~~* '%アーカイブ%'::text) AND (filename !~~ 'release-%'::text) AND (filename <> 'bookindex.html'::text))
Rows Removed by Filter: 1271
-> Index Scan using pgdoc_pkey on pgdoc (cost=0.28..8.30 rows=1 width=755) (actual time=0.004..0.005 rows=1 loops=38)
Index Cond: (docid = pgdoc_1.docid)
Planning time: 1.672 ms
Execution time: 200.934 ms
(11 行)
</pre>
<br/>
(10/02 14:25追記)<br/>
<br/>
と思っていたのですが、よくよく調べてみたら、pg_bigmは「LIKE」には対応しているけれども、「ILIKE」には対応していないようです。
<ul>
<li>pg_bigm 1.1 ドキュメント<br/><a href="http://pgbigm.osdn.jp/pg_bigm-1-1.html">http://pgbigm.osdn.jp/pg_bigm-1-1.html</a></li>
</ul>
それが原因で、pg_bigmのインデックスが使われなかったようです。<br/>
<br/>
そのため、検索対象となるカラム plain のアルファベットをすべて小文字に変換して、クエリで検索する文字列もすべて小文字に揃えた上で、「ILIKE」から「LIKE」に変更したら、無事にpg_bigmのインデックスを使ってくれるようになりました。
<pre style="overflow-x: scroll; white-space: pre">
testdb=> EXPLAIN ANALYZE SELECT
testdb-> docid,
testdb-> title,
testdb-> filename,
testdb-> pgdoc_score(plain,'{ログ,アーカイブ}')
testdb-> FROM
testdb-> pgdoc
testdb-> WHERE
testdb-> docid in (SELECT docid FROM pgdoc WHERE (plain LIKE '%ログ%' AND plain LIKE '%アーカイブ%') AND filename NOT LIKE 'release-%' AND filename <> 'bookindex.html')
testdb-> ORDER BY
testdb-> 4 DESC;
QUERY PLAN
--------------------------------------------------------------------------------------------------------------------------------------
Sort (cost=76.59..76.59 rows=1 width=625) (actual time=53.287..53.321 rows=38 loops=1)
Sort Key: (pgdoc_score(pgdoc.plain, '{ログ,アーカイブ}'::text[]))
Sort Method: quicksort Memory: 30kB
-> Nested Loop (cost=64.28..76.58 rows=1 width=625) (actual time=1.553..53.171 rows=38 loops=1)
-> Bitmap Heap Scan on pgdoc pgdoc_1 (cost=64.00..68.02 rows=1 width=4) (actual time=0.427..21.555 rows=38 loops=1)
Recheck Cond: ((plain ~~ '%ログ%'::text) AND (plain ~~ '%アーカイブ%'::text))
Rows Removed by Index Recheck: 2
Filter: ((filename !~~ 'release-%'::text) AND (filename <> 'bookindex.html'::text))
Rows Removed by Filter: 45
Heap Blocks: exact=38
-> Bitmap Index Scan on pgdoc_plain_idx (cost=0.00..64.00 rows=1 width=0) (actual time=0.124..0.124 rows=85 loops=1)
Index Cond: ((plain ~~ '%ログ%'::text) AND (plain ~~ '%アーカイブ%'::text))
-> Index Scan using pgdoc_pkey on pgdoc (cost=0.28..8.30 rows=1 width=625) (actual time=0.004..0.006 rows=1 loops=38)
Index Cond: (docid = pgdoc_1.docid)
Planning time: 1.264 ms
Execution time: 53.417 ms
(16 rows)
</pre>
<br/>
というわけで、検索も高速化(200ms→50ms)したことなので、pg_bigmを使うように修正しました。
<h4 class="post-body">■まとめ</h4><br/>
今回は、PostgreSQLで構築したPostgreSQLの日本語マニュアルの検索システムをご紹介しました。<br/>
<br/>
PostgreSQLを使いこんでくると、パラメータ名などでマニュアルを隅々まで検索したい、といったニーズが出てくることがあります。<br/>
<br/>
Googleなどの検索エンジンでざっくりと検索するだけでは検索の精度が足りないケースが出てくると思いますので、そのような場合に活用していただければと思います。<br/>
<br/>
では、また。<br/>
Satoshi Nagayasuhttp://www.blogger.com/profile/09706093898823214408noreply@blogger.com0tag:blogger.com,1999:blog-4546365304691285308.post-52517159910049564472016-09-22T18:23:00.000+09:002016-09-22T18:23:16.509+09:00MADlib 1.9.1 Release (GA)がリリースされました<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiShOMQJIFqDBDrT6bfnk2AZPRye396i2j0OMSQlsvmdq3edp1IqjqQHwQ6Wx49kUXctGR5LRvtfydKLNxjtYmi1atVGkyM7sm3tn7czWNmHC3w6ufUnFW4uaPnDCpC5QIW0f6KJAHGOvU/s1600/tem.png" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiShOMQJIFqDBDrT6bfnk2AZPRye396i2j0OMSQlsvmdq3edp1IqjqQHwQ6Wx49kUXctGR5LRvtfydKLNxjtYmi1atVGkyM7sm3tn7czWNmHC3w6ufUnFW4uaPnDCpC5QIW0f6KJAHGOvU/s320/tem.png" width="320" height="275" /></a></div>このブログでも何度か紹介しているPostgreSQLのデータベース内で機械学習の処理を行えるApache MADlibですが、1.9.1 GAがリリースされました。<br/>
<ul>
<li>Apache MADlib (incubating): Big Data Machine Learning in SQL <br/><a href="http://madlib.incubator.apache.org/">http://madlib.incubator.apache.org/</a></li>
</ul>
前のリリース1.9からの変更点は、以下のようになっています。<br/>
<ul>
<li>New function: One class SVM</li>
<li>SVM: Added functionality to assign weights to each class, simplying classification of unbalanced data.</li>
<li>New function: Prediction metrics</li>
<li>New function: Sessionization</li>
<li>New function: Pivot</li>
<li>Path: Major performance improvement</li>
<li>Path: Add support for overlapping patterns</li>
<li>Build: Add support for PG 9.5 and 9.6</li>
<li>PGXN: Update PostgreSQL Extension Network to latest release</li>
</ul>
私の送ったパッチも取り込まれて、無事に最新のPostgreSQL 9.5と、パラレルクエリを実装した次期バージョンである9.6でも動作するようになりました。<br/>
<br/>
興味のある方は、ぜひ試してみていただければと思います。<br/>
<br/>
<a name='more'></a>
以下のエントリあたりが参考になればと思います。<br/>
<ul>
<li>データ分析用ライブラリ MADlib を使って PostgreSQL で機械学習する <br/><a href="http://pgsqldeepdive.blogspot.jp/2016/04/madlib-postgresql.html">http://pgsqldeepdive.blogspot.jp/2016/04/madlib-postgresql.html</a></li>
<li>TF-IDFでデータベース内の類似テキストを検索する Part 4 (MADlib svec編) <br/><a href="http://pgsqldeepdive.blogspot.jp/2016/07/tf-idf-part-4-madlib-svec.html">http://pgsqldeepdive.blogspot.jp/2016/07/tf-idf-part-4-madlib-svec.html</a></li>
</ul>
では、また。<br/>
Satoshi Nagayasuhttp://www.blogger.com/profile/09706093898823214408noreply@blogger.com0tag:blogger.com,1999:blog-4546365304691285308.post-19319617636439010262016-09-17T18:11:00.000+09:002017-03-12T16:04:41.223+09:00巡回セールスマン問題における最短経路をpgRoutingで探索する<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhuHv1gvWXaHlkTYo40tij1jZHCSf0KQ2STrionXkI9Aaqoni8SjF12ild4FjRdHs6iCwyJCxfCB7gLYtZn_Luo72sUFEDS9wlU02PLdycLzwMtPSpe8cPaIQx8kfCb25wkPYc6O8ElEKs/s1600/00.jpg" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhuHv1gvWXaHlkTYo40tij1jZHCSf0KQ2STrionXkI9Aaqoni8SjF12ild4FjRdHs6iCwyJCxfCB7gLYtZn_Luo72sUFEDS9wlU02PLdycLzwMtPSpe8cPaIQx8kfCb25wkPYc6O8ElEKs/s320/00.jpg" width="281" height="320" /></a></div>
先日、PostgreSQLアンカンファレンスを開催した際、「pgRoutingを使って巡回セールスマン問題を解く」という発表を<a href="https://kenpg.bitbucket.io/">国府田さん</a>がされていました。<br/>
<ul>
<li>第8回 PostgreSQLアンカンファレンス@東京(2016/9/10) - connpass <br/><a href="http://pgunconf.connpass.com/event/37285/">http://pgunconf.connpass.com/event/37285/</a></li>
<li>第8回 PostgreSQLアンカンファレンス ツイートまとめ - Togetterまとめ <br/><a href="http://togetter.com/li/1023030">http://togetter.com/li/1023030</a></li>
</ul>
非常に面白そうな機能で、私も少し使ってみましたので、今回はその使い方や使用例などを含めてご紹介します。
<h4 class="post-body">■「巡回セールスマン問題」とは何か</h4><br/>
「巡回セールスマン問題」というのは、以下のようなものです。
<blockquote>
巡回セールスマン問題(じゅんかいセールスマンもんだい、英: traveling salesman problem、TSP)は、都市の集合と各2都市間の移動コスト(たとえば距離)が与えられたとき、全ての都市をちょうど一度ずつ巡り出発地に戻る巡回路の総移動コストが最小のものを求める(セールスマンが所定の複数の都市を1回だけ巡回する場合の最短経路を求める)組合せ最適化問題である。<br/>
<a href="https://ja.wikipedia.org/wiki/%E5%B7%A1%E5%9B%9E%E3%82%BB%E3%83%BC%E3%83%AB%E3%82%B9%E3%83%9E%E3%83%B3%E5%95%8F%E9%A1%8C">巡回セールスマン問題 - Wikipedia</a>
</blockquote>
簡単に言うと、「セールスマンが何か所か回る時、回る場所が増えれば増えるほど、可能性のある経路の候補が爆発的に増えていくので、最短経路を導き出すのが困難になる」ということです。回る場所の数「n」に対して、計算量はその階乗「n!」のオーダーとなります。<br/>
<br/>
<a name='more'></a>
そのため、データ量が増えると総当たりで解くことが計算量的に困難になる問題のひとつとして知られています。
<h4 class="post-body">■「pgRouting」とは何か</h4><br/>
pgRoutingは、PostgreSQLおよびPostGISの拡張で、PostgreSQL/PostGISの地理空間データベースの機能に経路探索(routing)の機能を追加するライブラリです。<br/>
<ul>
<li>pgRouting Project — Open Source Routing Library <br/><a href="http://pgrouting.org/">http://pgrouting.org/</a></li>
</ul>
プロジェクトの紹介文によると、<br/>
<ul>
<li>さまざまなクライアント(ライブラリ)を通してデータや属性を加工可能。</li>
<li>データの変更はすぐに経路探索に反映される。事前の計算などは不要。</li>
<li>「コスト」のパラメータは動的にSQLで計算され、テーブルのフィールドやレコードから取得可能。</li>
</ul>
となっています。<br/>
<br/>
経路探索のアルゴリズムはいろいろあるようで、コアの機能としては以下のようなアルゴリズムを使うことができます。<br/>
<ul>
<li>All Pairs Shortest Path, Johnson’s Algorithm</li>
<li>All Pairs Shortest Path, Floyd-Warshall Algorithm</li>
<li>Shortest Path A*</li>
<li>Bi-directional Dijkstra Shortest Path</li>
<li>Bi-directional A* Shortest Path</li>
<li>Shortest Path Dijkstra</li>
<li>Driving Distance</li>
<li>K-Shortest Path, Multiple Alternative Paths</li>
<li>K-Dijkstra, One to Many Shortest Path</li>
<li>Traveling Sales Person</li>
<li>Turn Restriction Shortest Path (TRSP)</li>
</ul>
今回は、この中から巡回セールスマン問題(Traveling Sales Person)を解くための関数を使ってみます。
<h4 class="post-body">■pgRoutingのpgr_tsp関数</h4><br/>
今回使用するpgRoutingの関数はpgr_tspです。<br/>
<ul>
<li>pgr_tsp - Traveling Sales Person — pgRouting Manual (2.2) <br/><a href="http://docs.pgrouting.org/2.2/en/src/tsp/doc/pgr_tsp.html">http://docs.pgrouting.org/2.2/en/src/tsp/doc/pgr_tsp.html</a></li>
</ul>
この関数は Simulated Annealing(焼きなまし法)という方法で、最短経路を探索します。<br/>
<ul>
<li>焼きなまし法 - Wikipedia<br/><a href="https://ja.wikipedia.org/wiki/%E7%84%BC%E3%81%8D%E3%81%AA%E3%81%BE%E3%81%97%E6%B3%95">https://ja.wikipedia.org/wiki/%E7%84%BC%E3%81%8D%E3%81%AA%E3%81%BE%E3%81%97%E6%B3%95</a></li>
</ul>
この関数は2つの使い方があります。ユークリッド距離を使って2点間の距離を計算して最短経路を探す方法と、各点間の距離をあらかじめ行列として定義したものを与えて最短経路を探す方法です。<br/>
<br/>
まず、ユークリッド距離を使う方法ですが、引数にSQL文を渡す必要があり、このSQL文は「id, x, y」というカラムを返却するSQL文である必要があります。<br/>
<pre style="overflow-x: scroll; white-space: pre">
pgr_costResult[] pgr_tsp(sql text, start_id integer);
pgr_costResult[] pgr_tsp(sql text, start_id integer, end_id integer);
</pre>
<br/>
もう一つの各点間の距離を行列で与える場合には、float型の二次元配列として与えます。<br/>
<pre style="overflow-x: scroll; white-space: pre">
record[] pgr_tsp(matrix float[][], start integer)
record[] pgr_tsp(matrix float[][], start integer, end integer)
</pre>
<br/>
今回は、前者のユークリッド距離を使うバージョンを使います。<br/>
<br/>
まず、以下のように位置情報をx,yとして持つテーブルを作成しておきます。<br/>
<pre style="overflow-x: scroll; white-space: pre">
pgr=> CREATE TABLE vertex_table (
pgr(> id serial,
pgr(> x double precision,
pgr(> y double precision
pgr(> );
CREATE TABLE
pgr=>
pgr=> INSERT INTO vertex_table VALUES
pgr-> (1,2,0), (2,2,1), (3,3,1), (4,4,1), (5,0,2), (6,1,2), (7,2,2),
pgr-> (8,3,2), (9,4,2), (10,2,3), (11,3,3), (12,4,3), (13,2,4);
INSERT 0 13
pgr=> SELECT * FROM vertex_table;
id | x | y
----+---+---
1 | 2 | 0
2 | 2 | 1
3 | 3 | 1
4 | 4 | 1
5 | 0 | 2
6 | 1 | 2
7 | 2 | 2
8 | 3 | 2
9 | 4 | 2
10 | 2 | 3
11 | 3 | 3
12 | 4 | 3
13 | 2 | 4
(13 rows)
pgr=>
</pre>
<br/>
この位置情報のテーブルから「id,x,y」を取得するSQL文を与えて、pgr_tspを実行すると、以下のように最短経路を表示してくれます。<br/>
<pre style="overflow-x: scroll; white-space: pre">
pgr=> SELECT seq, id1, id2, cost FROM pgr_tsp('SELECT id, x, y FROM vertex_table ORDER BY id', 6, 5);
seq | id1 | id2 | cost
-----+-----+-----+------------------
0 | 5 | 6 | 1
1 | 6 | 7 | 1
2 | 7 | 8 | 1.4142135623731
3 | 9 | 10 | 1
4 | 12 | 13 | 1.4142135623731
5 | 10 | 11 | 1
6 | 11 | 12 | 1
7 | 8 | 9 | 1
8 | 3 | 4 | 1
9 | 2 | 3 | 1.4142135623731
10 | 0 | 1 | 1
11 | 1 | 2 | 2.23606797749979
12 | 4 | 5 | 1
(13 rows)
pgr=>
</pre>
<br/>
出力で重要なのは「seq、id2、cost」です。seqは経路の順番、id2はノードのid(vertex_tableのidカラム)、costは次のノードに移動するためのコスト(距離)です。<br/>
<h4 class="post-body">■聖地巡礼の最短経路探索問題</h4><br/>
世間では今、映画「<a href="http://www.kiminona.com/index.html">君の名は。</a>」が大ヒットしています。(唐突感)<br/>
<br/>
<iframe width="560" height="315" src="https://www.youtube.com/embed/k4xGqY5IDBE" frameborder="0" allowfullscreen></iframe><br/>
<br/>
アニメのヒット作には、聖地の存在が欠かせません。そしてそのファンは聖地巡礼をすることになっています(?)。<br/>
<br/>
しかし、聖地といってもたくさんありますし、時間は限られていますので、効率よく回ることが求められてきます。巡るべき聖地が増えれば増えるほど計算量が爆発的に増大し、最短経路を求めることが困難になってきます。多分。<br/>
<br/>
というわけで、「君の名は。」の聖地を巡礼するための最短経路を巡回セールスマン問題としてpgRoutingを使って探索してみます。(手段の目的化)<br/>
<br/>
なお、本エントリはここからが本題です。
<h4 class="post-body">■PostGIS/pgRoutingとpykmlのインストール</h4><br/>
まず、PostGIS/pgRoutingをインストールします。<br/>
<br/>
PostgreSQLコミュニティのyumレポジトリを使っている場合には以下のコマンドでインストールできます。今回はPostgreSQL 9.5と一緒に使っています。<br/>
<pre style="overflow-x: scroll; white-space: pre">
$ sudo yum install -y postgis2_95 pgrouting_95
(...)
$ rpm -qa | grep pgrouting
pgrouting_95-2.0.1-1.rhel6.x86_64
$ rpm -qa | grep postgis
postgis2_95-2.2.2-1.rhel6.x86_64
$
</pre>
<br/>
また、三次元地理空間情報のデータ形式であるKMLファイルを扱うPythonのライブラリpykmlもインストールします。<br/>
<pre style="overflow-x: scroll; white-space: pre">
$ sudo pip install pykml
(...)
$ pip list | grep pykml
pykml (0.1.0)
$
</pre>
<br/>
インストールが終わったら、今回使うデータベースにPostGISとpgRoutingのEXTENSIONをインストールします。<br/>
<pre style="overflow-x: scroll; white-space: pre">
pgr=# create extension postgis;
CREATE EXTENSION
pgr=# create extension pgrouting;
CREATE EXTENSION
pgr=# \d
List of relations
Schema | Name | Type | Owner
--------+-------------------+-------+----------
public | geography_columns | view | postgres
public | geometry_columns | view | postgres
public | raster_columns | view | postgres
public | raster_overviews | view | postgres
public | spatial_ref_sys | table | postgres
(5 rows)
pgr=#
</pre>
<br/>
<h4 class="post-body">■データの準備をする</h4><br/>
最初、自分で聖地の位置情報のデータを作成しようかとも思っていたのですが、いろいろ探していたらGoogle Mapsに「君の名は。」の聖地マップが公開されていましたので、今回はこれを使います。<br/>
<ul>
<li>【君の名は。】聖地巡礼マップ【8/31現在】 <br/><a href="https://www.google.com/maps/d/u/0/viewer?mid=1Y_ioR8c1iCSR-t7qTN3Yl1qhAUw&hl=en_US">https://www.google.com/maps/d/u/0/viewer?mid=1Y_ioR8c1iCSR-t7qTN3Yl1qhAUw&hl=en_US</a></li>
</ul>
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgvp-d7fnzy653vMHdG9K1a5G4RMOj_zve7FgYJjH-4ByIl4R7WaydOYHyB5qIByMu0nTfZy3w2GSukKVq_M0Ow2QpjGbxNecpdxnfkeogcP0dytUKSbLYaHWTRlUyjHXvoeFzRIH1tvEs/s1600/0.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgvp-d7fnzy653vMHdG9K1a5G4RMOj_zve7FgYJjH-4ByIl4R7WaydOYHyB5qIByMu0nTfZy3w2GSukKVq_M0Ow2QpjGbxNecpdxnfkeogcP0dytUKSbLYaHWTRlUyjHXvoeFzRIH1tvEs/s400/0.png" width="400" height="270" /></a></div>
まず、この聖地マップを自分のアカウントにコピーしてきます。<br/>
<br/>
コピーの方法は、マップの右上にあるプルダウンメニューから「Copy map」を選ぶだけです。<br/>
<br/>
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhV6rrvGynwC7efNIy5cKfZxNTSENhz3l38lmb1ciAgalrHTbj6YJqWko9GX-gFf5I_8ImaQ68VzaxCgUNL1A8ar-0KbS6B5m5PBWJTy5KhEdK0O_-TyQzmIEasdeNShWg7uCsC_whESww/s1600/1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhV6rrvGynwC7efNIy5cKfZxNTSENhz3l38lmb1ciAgalrHTbj6YJqWko9GX-gFf5I_8ImaQ68VzaxCgUNL1A8ar-0KbS6B5m5PBWJTy5KhEdK0O_-TyQzmIEasdeNShWg7uCsC_whESww/s400/1.png" width="400" height="232" /></a></div>
<br/>
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgkE8TAFMpKR_m1HPEIGF1Y9k5CTG80lQJA_nlpOT3UeVSEQhBMmIkgOPPGBzXyBk_fHwGaZQSXDiwzIzU1arbIaqYCDrd9fC8B5iuKJ36n9LMpgp2wAx_VWpLGngI93Xe_LdWr4_Q1jSM/s1600/2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgkE8TAFMpKR_m1HPEIGF1Y9k5CTG80lQJA_nlpOT3UeVSEQhBMmIkgOPPGBzXyBk_fHwGaZQSXDiwzIzU1arbIaqYCDrd9fC8B5iuKJ36n9LMpgp2wAx_VWpLGngI93Xe_LdWr4_Q1jSM/s400/2.png" width="400" height="234" /></a></div>
<br/>
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiluJW29Q7L_10YUzidgG94kXNsIqFqI1FCi2HDYZ8WC98cI3YM4WlpsJNdtkS0ukXoXNS2bWFQDKQx8hW_8vbztSN90l8Mx7ea4n4i1Z5AIv07XQth-mrbKqPAHKZDMRDwQLF1Ng8Dlzo/s1600/3.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiluJW29Q7L_10YUzidgG94kXNsIqFqI1FCi2HDYZ8WC98cI3YM4WlpsJNdtkS0ukXoXNS2bWFQDKQx8hW_8vbztSN90l8Mx7ea4n4i1Z5AIv07XQth-mrbKqPAHKZDMRDwQLF1Ng8Dlzo/s400/3.png" width="400" height="286" /></a></div>
<br/>
次に、聖地マップのデータをKML形式でエクスポートします。<br/>
<br/>
コピーの時と同じく、プルダウンメニューから「Download KML」を選んで、マップ全体のデータをKMLとしてダウンロードして保存します。<br/>
<br/>
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiteUAE3R3WrQtN3lt189bY1hgXKifzoNY23K6Vuw-ZvROTXjgD0IwdrZOwrDxo0tV_Ce-C-wk_tUqQFF7gKTraB3pMcAPB_ZgdJXvE_tzKYthW-4yeYxa16RekxJ2zrMNP6hjD1h7MqGA/s1600/4.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiteUAE3R3WrQtN3lt189bY1hgXKifzoNY23K6Vuw-ZvROTXjgD0IwdrZOwrDxo0tV_Ce-C-wk_tUqQFF7gKTraB3pMcAPB_ZgdJXvE_tzKYthW-4yeYxa16RekxJ2zrMNP6hjD1h7MqGA/s400/4.png" width="400" height="188" /></a></div>
<br/>
エクスポートしたKMLファイルは以下のようなXMLファイルになっているはずです。<br/>
<pre style="overflow-x: scroll; white-space: pre">
$ head Copy\ of\ 【君の名は。】聖地巡礼マップ【831現在】.kml
<?xml version='1.0' encoding='UTF-8'?>
<kml xmlns='http://www.opengis.net/kml/2.2'>
<Document>
<name>Copy of 【君の名は。】聖地巡礼マップ【8/31現在】</name>
<description><![CDATA[]]></description>
<Folder>
<name>無題のレイヤ</name>
<Placemark>
<name>予告映像カット
</name>
$
</pre>
<br/>
次に、KMLファイルの中に含まれている位置情報をPostgreSQLに投入するためのINSERT文に変換します。<br/>
<br/>
今回テーブルは以下の構造にします。<br/>
<pre style="overflow-x: scroll; white-space: pre">
CREATE TABLE seichi (
sid serial primary key,
name text not null,
descr text,
img text,
lat float8 not null,
lon float8 not null
);
</pre>
<br/>
sidはシーケンス番号、nameはGoogle Mapsに登録されていた名前、descrは説明文です。imgは位置の画像が設定されたいた場合のURL、lat/lonは緯度経度をfloat8の精度で持ちます。<br/>
<br/>
場所の情報はKMLファイル内の「Placemark」というタグに囲まれていますので、この情報を取得します。<br/>
<br/>
KMLファイルの解析はpykmlモジュールを使って行います。<br/>
<ul>
<li>Welcome to pyKML — pyKML v0.1.0 documentation <br/><a href="https://pythonhosted.org/pykml/">https://pythonhosted.org/pykml/</a></li>
</ul>
<a href="https://gist.github.com/snaga/477100b0f7415f660fa0f45df7eee1bd">kml_to_sql.pyスクリプト</a>にKMLファイルを引数として渡すと、KMLファイル内のPlacemarkのデータをINSERT文に変換して以下のように出力します。<br/>
<pre style="overflow-x: scroll; white-space: pre">
$ python kml_to_sql.py Copy\ of\ 【君の名は。】聖地巡礼マップ【831現在】.kml > ins.sql
$ head ins.sql
INSERT INTO seichi (name,descr,img,lat,lon) VALUES
('予告映像カット','新宿警察署裏交差点','',139.6944129,35.6925938)
, ('君の名は。第1弾キービジュアル','瀧が立っている横断歩道の背景建物','https://blogger.googleusercontent.com/img/proxy/AVvXsEgLXxyhLQGYjVApXbrHHFw6IQRERj_ElP9jq75ll8YSfZgTToH4FBtM0n5tX5T2hSzDf0E3SZoZAl2mghpnwqvhM-h5XfPChG5Nyj1FrE5G06KFP4ddvnajkQDWHXgRU59VRY8RvW9_rupQI1sR_aylQ8M_w6-TfJMk=',139.723177,35.6607657)
, ('三葉が座っていた総武線ホームのベンチ','','https://blogger.googleusercontent.com/img/proxy/AVvXsEjs-Qf1ula5UcaavKPNgW5l1RrPOblkLW8M_nRWTB2PeNVwCnlApcOEpyeCbWHKU2k3fCFFZGChkPbFPYK152VboPYJRZ478d09RMisUsKnzh7QxyQ7dATT7oGQvuQrS0qqZWHmau5rluf3SaBk6-RaAIwhdPpOBBbp=',139.7020626,35.6840715)
, ('瀧と奥寺先輩がお茶していたスタバ','看板TSUTAYAのTSUの部分にあたるカウンター席','https://blogger.googleusercontent.com/img/proxy/AVvXsEjJovc3KVdT6rnstEyq4eZOWc_r_WCuZNXm57S6nuA24SKQ-C-GZChGQnTsqGUJOj-F-qkYvmcEFEytmTLdLYJ6bEQ_2ErDThFYRZFv_HNZho-BQOhRkzUDUpDjiU-es86G0H86fSYcadNB6ro8q7bZbRF-25PU2vWG=',139.7003701,35.6598526)
, ('君の名は。第1弾キービジュアル','背景の六本木ヒルズ','',139.7293139,35.6603647)
, ('君の名は。第1弾キービジュアル','背景の東京ミッドタウンビル','',139.7312933,35.6662877)
, ('君の名は。第1弾キービジュアル','瀧が立っている横断道','',139.7228122,35.6613584)
, ('道路標識案内板','瀧と奥寺先輩が別れる歩道橋についている道路標識案内板','https://blogger.googleusercontent.com/img/proxy/AVvXsEig76fE_x43p9U6vuYakLKM1xZkH-TgIosQ9dl7hAAb0NxvaCys-MuIIzeby4ElYN0xCMyGSRkKZ8KTDCyaXT3acNn8D5iFdR8FKLecoXTTrepfY86ITUhHA_NFPTa8vtoN2iIAhZ_dpNd-7_FnFhs-4t_yXvqyKTd9=',139.7233808,35.6743456)
, ('歩道橋背景1','瀧と奥寺先輩が別れる歩道橋の背景','https://blogger.googleusercontent.com/img/proxy/AVvXsEg0KNNAo6DX5jUntHLJw-Cen_ySf035voZcrPRCPAQbIldwNNFERUcZ7ixRdaw9PiLcaCY2NP6aoekUvtMCeTqM6M0CEBMKl5fDDdP_NI91NlQgjrEVmGCZWB2mPxiMaZhRvet_vfklN9T6GTXuRP86mg_E26luvq2fyus=',139.7238582,35.6723802)
$
</pre>
<br/>
そして、作成したテーブルにINSERT文でデータを流し込みます。<br/>
<pre style="overflow-x: scroll; white-space: pre">
$ psql -f ins.sql pgr
INSERT 0 38
$
</pre>
<br/>
データができたことを確認したら、準備は完了です。<br/>
<pre style="overflow-x: scroll; white-space: pre">
pgr=> \x
Expanded display is on.
pgr=> select * from seichi limit 3;
-[ RECORD 1 ]-----------------------------------------------------------------------------------------------------------------------------------------
sid | 1
name | 予告映像カット
descr | 新宿警察署裏交差点
img |
lat | 139.6944129
lon | 35.6925938
-[ RECORD 2 ]-----------------------------------------------------------------------------------------------------------------------------------------
sid | 2
name | 君の名は。第1弾キービジュアル
descr | 瀧が立っている横断歩道の背景建物
img | https://blogger.googleusercontent.com/img/proxy/AVvXsEgLXxyhLQGYjVApXbrHHFw6IQRERj_ElP9jq75ll8YSfZgTToH4FBtM0n5tX5T2hSzDf0E3SZoZAl2mghpnwqvhM-h5XfPChG5Nyj1FrE5G06KFP4ddvnajkQDWHXgRU59VRY8RvW9_rupQI1sR_aylQ8M_w6-TfJMk=
lat | 139.723177
lon | 35.6607657
-[ RECORD 3 ]-----------------------------------------------------------------------------------------------------------------------------------------
sid | 3
name | 三葉が座っていた総武線ホームのベンチ
descr |
img | https://blogger.googleusercontent.com/img/proxy/AVvXsEjs-Qf1ula5UcaavKPNgW5l1RrPOblkLW8M_nRWTB2PeNVwCnlApcOEpyeCbWHKU2k3fCFFZGChkPbFPYK152VboPYJRZ478d09RMisUsKnzh7QxyQ7dATT7oGQvuQrS0qqZWHmau5rluf3SaBk6-RaAIwhdPpOBBbp=
lat | 139.7020626
lon | 35.6840715
pgr=>
</pre>
<br/>
<h4 class="post-body">■聖地巡礼の最短経路を求める</h4><br/>
それでは、聖地巡礼の最短経路を巡回セールスマン問題として探索してみます。<br/>
<br/>
前述したように、pgr_tsp関数にユークリッド距離を使って計算させます。<br/>
<br/>
pgr_tspに与えるクエリは<br/>
<pre style="overflow-x: scroll; white-space: pre">
SELECT sid id, lat x, lon y FROM seichi ORDER BY sid
</pre>
<br/>
となります(エイリアスでカラム名を指定)。<br/>
<br/>
pgr_tsp関数に与える2つ目の引数は、スタートの点のidを示しています。(今回は1番目から出発)<br/>
<br/>
さて、クエリを実行してみましょう。<br/>
<pre style="overflow-x: scroll; white-space: pre">
pgr=> SELECT * FROM pgr_tsp('SELECT sid id, lat x, lon y FROM seichi ORDER BY sid', 1);
seq | id1 | id2 | cost
-----+-----+-----+----------------------
0 | 0 | 1 | 0.00276786017347617
1 | 16 | 17 | 0.00336861318052751
2 | 21 | 22 | 0.00418150821235386
3 | 26 | 27 | 0.00070285384682649
4 | 18 | 19 | 0.00103679575616323
5 | 17 | 18 | 0.000724005262411046
6 | 14 | 15 | 0.00040594088239891
7 | 25 | 26 | 0.00420010183685812
8 | 2 | 3 | 0.00965949435530557
9 | 19 | 20 | 0.00786443285746516
10 | 24 | 25 | 0.00638462619110202
11 | 30 | 31 | 0.000613460422526542
12 | 31 | 32 | 0.0020755731593899
13 | 36 | 37 | 0.00521034970899676
14 | 34 | 35 | 0.00453278438158901
15 | 23 | 24 | 0.00214997988828621
16 | 22 | 23 | 0.00338756302081414
17 | 20 | 21 | 0.00901689128802769
18 | 13 | 14 | 4.29288947063113e-05
19 | 33 | 34 | 0.00758742562995817
20 | 35 | 36 | 0.00655833544277088
21 | 37 | 38 | 0.00662867080567097
22 | 32 | 33 | 0.0076538252201047
23 | 29 | 30 | 0.000494603275347511
24 | 28 | 29 | 0.00116522954392147
25 | 11 | 12 | 0.00340343115400377
26 | 12 | 13 | 0.00387475855377472
27 | 7 | 8 | 0.00202254985600024
28 | 8 | 9 | 0.000924678733398354
29 | 9 | 10 | 0.00108906486492197
30 | 10 | 11 | 0.00636705982066888
31 | 15 | 16 | 0.00573513230013025
32 | 4 | 5 | 0.00657719868789178
33 | 6 | 7 | 0.0222159042861008
34 | 27 | 28 | 0.00284098090455204
35 | 3 | 4 | 0.0228251711761441
36 | 1 | 2 | 0.00981665980311944
37 | 5 | 6 | 0.0453009359877922
(38 rows)
pgr=>
</pre>
<br/>
最短経路が出ました。id2カラムの順番に巡礼していけば、最短経路で聖地巡礼ができることになります。<br/>
<br/>
なお、これだけではちょっと分かりづらいので、元の地理のデータをJOINしてnameとdescrを表示してみましょう。(不要なid1とcostカラムも省きます)<br/>
<pre style="overflow-x: scroll; white-space: pre">
pgr=> SELECT
pgr-> t.seq,
pgr-> t.id2,
pgr-> s.name,
pgr-> s.descr
pgr-> FROM
pgr-> pgr_tsp('SELECT sid id, lat x, lon y FROM seichi ORDER BY sid', 1) t
pgr-> LEFT OUTER JOIN seichi s ON t.id2 = s.sid;
seq | id2 | name | descr
-----+-----+--------------------------------------------------+--------------------------------------------------------------------------------------------------------------------------
0 | 1 | 予告映像カット | 新宿警察署裏交差点
1 | 17 | オープニング | ・作中のオープニングの背景・大人になった瀧と三葉がすれ違う歩道橋
2 | 22 | ユニカビジョン |
3 | 27 | 大人になった瀧が走り出てくる改札 | 三葉を探しに飛び出す南口改札
4 | 19 | 瀧の通学路&大人になった瀧が走っていく場所 | ・はじめて瀧と入れ替わった三葉が学校へ向かう通ったルミネのショップウィンドウ沿い・大人になった瀧が三葉を探しに走っていく
5 | 18 | 作中背景 | 連絡橋からバルト9方面
6 | 15 | 風景カット | サザンテラス口店 スタバの入口階段と手前の花壇
7 | 26 | 大人になった瀧が来ていたスタバ | ここで式の話をしている大人になったテッシーとさやちんが登場
8 | 3 | 三葉が座っていた総武線ホームのベンチ |
9 | 20 | 大人の三葉 | ラスト、 瀧を見つけた大人の三葉が駆け出してくる千駄ヶ谷駅の改札
10 | 25 | 走る三葉 | 大人になった三葉が瀧を見つけに走って通る場所
11 | 31 | 君の名は。 第2弾キービジュアル&作中ラストシーン | 瀧と三葉がすれ違う階段・物語の最後のシーン
12 | 32 | 十字路 | みつはが走ってくるシーン
13 | 37 | 風景カット | 道路標識
14 | 35 | 瀧が息を整える場所 |
15 | 24 | 瀧と奥寺先輩が四ッ谷駅方面を眺める場所 | 就活中の瀧と奥寺先輩
16 | 23 | 瀧(大人)と奥寺先輩が話しながら歩いている道 |
17 | 21 | 瀧と奥寺先輩のデート | 物語終盤、就活中の瀧と奥寺先輩のデートで渡っていた弁慶橋
18 | 14 | 瀧と奥寺先輩の待ち合わせ1 | 四ッ谷駅赤坂方面改札
19 | 34 | 瀧と奥寺先輩の待ち合わせ2 | アトレの方角
20 | 36 | 風景カット | 郵便ポスト
21 | 38 | 風景カット | 四谷三丁目の交差点
22 | 33 | 分かれ道 | 瀧が走っていくシーン
23 | 30 | 太陽の光とドコモタワー |
24 | 29 | 瀧が立っていた歩道橋 | JR総武線信濃町駅前の歩道橋
25 | 12 | 歩道橋背景4 | 瀧と奥寺先輩が別れる歩道橋の下に見切れている道路標識板
26 | 13 | 瀧の通学路 | 瀧と司と真太の下校シーン
27 | 8 | 道路標識案内板 | 瀧と奥寺先輩が別れる歩道橋についている道路標識案内板
28 | 9 | 歩道橋背景1 | 瀧と奥寺先輩が別れる歩道橋の背景
29 | 10 | 歩道橋背景2 | 瀧と奥寺先輩が別れる歩道橋
30 | 11 | 歩道橋背景3 | 瀧と奥寺先輩が別れる歩道橋
31 | 16 | 国立新美術館 |
32 | 5 | 君の名は。第1弾キービジュアル | 背景の六本木ヒルズ
33 | 7 | 君の名は。第1弾キービジュアル | 瀧が立っている横断道
34 | 28 | あおい書店前歩道橋 | 作中、瀧が数回通っていた歩道橋
35 | 4 | 瀧と奥寺先輩がお茶していたスタバ | 看板TSUTAYAのTSUの部分にあたるカウンター席
36 | 2 | 君の名は。第1弾キービジュアル | 瀧が立っている横断歩道の背景建物
37 | 6 | 君の名は。第1弾キービジュアル | 背景の東京ミッドタウンビル
(38 rows)
pgr=>
</pre>
<br/>
「新宿警察署裏交差点」を出発点として、聖地巡礼のルートがより具体的に見えてきました。
<h4 class="post-body">■求めた最短経路を可視化する</h4><br/>
最後に、この巡礼の経路をGoogle Mapsに取り込んで可視化してみます。<br/>
<br/>
経路を可視化するには、2点間に線を引かなければなりません。Google Maps上で線を引かせるには、出発点の緯度経度と、到着点の緯度経度情報が必要です。ウィンドウ関数の匂いがします。<br/>
<br/>
まず、先ほどのクエリを少し修正して、場所の名前ではなく緯度経度を表示させます。<br/>
<pre style="overflow-x: scroll; white-space: pre">
pgr=> SELECT
pgr-> t.seq,
pgr-> t.id2,
pgr-> s.lat,
pgr-> s.lon
pgr-> FROM
pgr-> pgr_tsp('SELECT sid id, lat x, lon y FROM seichi ORDER BY sid', 1) t
pgr-> LEFT OUTER JOIN seichi s ON t.id2 = s.sid;
seq | id2 | lat | lon
-----+-----+-------------+------------
0 | 1 | 139.6944129 | 35.6925938
1 | 17 | 139.6971166 | 35.6931863
2 | 22 | 139.7004586 | 35.6936089
3 | 27 | 139.7002923 | 35.6894307
4 | 19 | 139.7009951 | 35.689422
(...)
33 | 7 | 139.7228122 | 35.6613584
34 | 28 | 139.7010112 | 35.6570849
35 | 4 | 139.7003701 | 35.6598526
36 | 2 | 139.723177 | 35.6607657
37 | 6 | 139.7312933 | 35.6662877
(38 rows)
pgr=>
</pre>
<br/>
次に、ウィンドウ関数を使って「前の地点の緯度経度」を表示させます。<br/>
<pre style="overflow-x: scroll; white-space: pre">
pgr=> SELECT
pgr-> t.seq,
pgr-> t.id2,
pgr-> s.lat,
pgr-> s.lon,
pgr-> lag(s.lat) OVER (ORDER BY seq) prev_lat,
pgr-> lag(s.lon) OVER (ORDER BY seq) prev_lon
pgr-> FROM
pgr-> pgr_tsp('SELECT sid id, lat x, lon y FROM seichi ORDER BY sid', 1) t
pgr-> LEFT OUTER JOIN seichi s ON t.id2 = s.sid;
seq | id2 | lat | lon | prev_lat | prev_lon
-----+-----+-------------+------------+-------------+------------
0 | 1 | 139.6944129 | 35.6925938 | |
1 | 17 | 139.6971166 | 35.6931863 | 139.6944129 | 35.6925938
2 | 22 | 139.7004586 | 35.6936089 | 139.6971166 | 35.6931863
3 | 27 | 139.7002923 | 35.6894307 | 139.7004586 | 35.6936089
4 | 19 | 139.7009951 | 35.689422 | 139.7002923 | 35.6894307
(...)
33 | 7 | 139.7228122 | 35.6613584 | 139.7293139 | 35.6603647
34 | 28 | 139.7010112 | 35.6570849 | 139.7228122 | 35.6613584
35 | 4 | 139.7003701 | 35.6598526 | 139.7010112 | 35.6570849
36 | 2 | 139.723177 | 35.6607657 | 139.7003701 | 35.6598526
37 | 6 | 139.7312933 | 35.6662877 | 139.723177 | 35.6607657
(38 rows)
pgr=>
</pre>
<br/>
このデータをPythonスクリプトでKMLファイルに変換します。<br/>
<br/>
<a href="https://gist.github.com/snaga/fc5d1629149389a17bd6c8b36d50b0d9">tsp_to_kml.pyスクリプト</a>では、psycopg2を使ってPostgreSQLに接続してデータを取り出し、XMLファイルをベタに書き出します。<br/>
<br/>
このスクリプトの出力を保存すると、以下のようなKMLファイルを得られます。<br/>
<pre style="overflow-x: scroll; white-space: pre">
$ python tsp_to_kml.py > route.kml
$ cat route.kml
<?xml version='1.0' encoding='UTF-8'?>
<kml xmlns='http://www.opengis.net/kml/2.2'>
<Document>
<Style id="line-DB4436-5-nodesc">
<LineStyle>
<color>ff0000ff</color>
<width>5</width>
</LineStyle>
</Style>
<name>Copy of 【君の名は。】聖地巡礼マップ【8/31現在】</name>
<description><![CDATA[]]></description>
<Folder>
<name>巡礼最短経路</name>
<Placemark>
<name>Path 1</name>
<styleUrl>#line-DB4436-5-nodesc</styleUrl>
<LineString>
<coordinates>139.6971166,35.6931863,0 139.6944129,35.6925938,0</coordinates>
</LineString>
</Placemark>
<Placemark>
<name>Path 2</name>
<styleUrl>#line-DB4436-5-nodesc</styleUrl>
<LineString>
<coordinates>139.7004586,35.6936089,0 139.6971166,35.6931863,0</coordinates>
</LineString>
</Placemark>
(...)
</Folder>
</Document>
</kml>
$
</pre>
<br/>
なお、KMLファイルのFolder要素がGoogle Mapsで言うところのレイヤーに当たります。今回は、巡礼の経路情報はすべて「巡礼最短経路」という一つのレイヤーにまとめてあります。<br/>
<br/>
最後に、作成したroute.kmlというKMLファイルを、Google Mapsにインポートします。<br/>
<br/>
まず、地図に「Add layer」でレイヤーを追加します。<br/>
<br/>
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgORj0ard65W7WfJzXfwDXgypjM15g2vbPyXZBUOj_atUtHY8zQdNpc2Jt51xYVKJH18oGraj8ohem8OI518Nu1GIIUv5rkCzIocAPYpl1rWj8eeZhcUGEyGBnhFfHFut87YDz05uIb4YE/s1600/5.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgORj0ard65W7WfJzXfwDXgypjM15g2vbPyXZBUOj_atUtHY8zQdNpc2Jt51xYVKJH18oGraj8ohem8OI518Nu1GIIUv5rkCzIocAPYpl1rWj8eeZhcUGEyGBnhFfHFut87YDz05uIb4YE/s400/5.png" width="400" height="314" /></a></div>
<br/>
次に、作成した新しいレイヤーに「Import」というメニューがありますので、そこをクリックしてKMLファイルをインポートします。<br/>
<br/>
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiYzhXCf5QpuzQE-tffHO-9x4mkmr3Pb_vka7K3E14EyjojbM27O9T_P1pGjchouNL1Vn4A_lYavkATwooFpxRem2m5utVS9t0V7OCOU3yyRFqP8yeIw686d7dKCbVK24chyXHN4ClFqLE/s1600/6.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiYzhXCf5QpuzQE-tffHO-9x4mkmr3Pb_vka7K3E14EyjojbM27O9T_P1pGjchouNL1Vn4A_lYavkATwooFpxRem2m5utVS9t0V7OCOU3yyRFqP8yeIw686d7dKCbVK24chyXHN4ClFqLE/s400/6.png" width="400" height="291" /></a></div>
<br/>
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhl3QizgZ2p30CjQO0hY9zXyllhp9iOvWTG6XoFilHSvNeNoxGepxXJBEKsfkrItXRkIYAnVgU9IUidV7TL58X2E3QI-qI3Ai3scTcJy-cXk2LWYhunkGCuYTUXgVSz8YxCMnGM2bS2fow/s1600/7.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhl3QizgZ2p30CjQO0hY9zXyllhp9iOvWTG6XoFilHSvNeNoxGepxXJBEKsfkrItXRkIYAnVgU9IUidV7TL58X2E3QI-qI3Ai3scTcJy-cXk2LWYhunkGCuYTUXgVSz8YxCMnGM2bS2fow/s400/7.png" width="400" height="252" /></a></div>
<br/>
インポートが無事に完了すれば、レイヤーの名前が「巡礼最短経路」となり、経路が地図上に表示されます。<br/>
<br/>
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi8h8P4u-bvmsR1RR0v40dIKPYPg_My6o4HPQvd5zXW0xtbQ9NngUKbxWZnkh5NgLJHCHeyoasyozdcgOlEH8kpbCx__Gjtwm5Ol8HcOSOI2DjObLzPOr7aZh4AMeFNehHBdTTwT4VjW0M/s1600/8.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi8h8P4u-bvmsR1RR0v40dIKPYPg_My6o4HPQvd5zXW0xtbQ9NngUKbxWZnkh5NgLJHCHeyoasyozdcgOlEH8kpbCx__Gjtwm5Ol8HcOSOI2DjObLzPOr7aZh4AMeFNehHBdTTwT4VjW0M/s400/8.png" width="400" height="270" /></a></div>
<br/>
やったぜ可視化!<br/>
<ul>
<li>Copy of 【君の名は。】聖地巡礼マップ【8/31現在】<br/><a href="https://drive.google.com/open?id=1-Q4Bmes5ZAkdKofBral_hGEHxu4&usp=sharing">https://drive.google.com/open?id=1-Q4Bmes5ZAkdKofBral_hGEHxu4&usp=sharing</a></li>
</ul>
<h4 class="post-body">■まとめ</h4><br/>
今回は、複数の緯度経度の情報から、それらを結ぶ最短経路を求める演算をpgRoutingを使って実現しました。<br/>
<br/>
また、KMLファイルをエクスポート・インポートすることで、その演算結果をGoogle Mapsのデータを活用して、可視化できることを示しました。<br/>
<br/>
なお、今回は実現できなかったこととして、以下のようなことがあります。<br/>
<ul>
<li>KMLのエクスポート、インポートをWebAPIでやりたい。(誰か<a href="https://twitter.com/snaga">教えて</a>ください)</li>
<li>地理的な距離ではなく、所用時間などでコスト計算やってみたい。</li>
<li>総距離や総時間に制限を設けた上で、「制限時間内にできるだけ多く回る」みたいな探索をしてみたい。</li>
<li>など。</li>
</ul>
演算の部分については、もしかしたらpgRoutingの他の関数などで実現できるかもしれないので、少しずつ調べてみたいと思います。<br/>
<br/>
ぜひ、みなさんも地理情報とPostGISやpgRoutingを使って、何か面白いことにチャレンジしてみていただければと思います。<br/>
<br/>
Enjoy, 聖地巡礼ライフ!!<br/>
<br/>
では、また。<br/><br/>
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEikpUDPSRd1GUO3IbXVFj5ulwCci2z8xo5UUhkTKHHR_5LjcwdVLx9pUU4QfrZDM_0xzPFHG6Psrd7cv1IXOMgk0wGMG6OLgU-Gcr1jvlOGy_8pAL9RDb6kuMH9RZd-KkuPLcn_8qFpbVg/s1600/99.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEikpUDPSRd1GUO3IbXVFj5ulwCci2z8xo5UUhkTKHHR_5LjcwdVLx9pUU4QfrZDM_0xzPFHG6Psrd7cv1IXOMgk0wGMG6OLgU-Gcr1jvlOGy_8pAL9RDb6kuMH9RZd-KkuPLcn_8qFpbVg/s640/99.jpg" width="640" height="353" /></a></div>Satoshi Nagayasuhttp://www.blogger.com/profile/09706093898823214408noreply@blogger.com0tag:blogger.com,1999:blog-4546365304691285308.post-10141174588161291112016-09-04T13:51:00.000+09:002016-09-04T19:32:44.524+09:009月10日(土)に第8回PostgreSQLアンカンファレンスを開催します<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj0Kq3-OMfDfMrpPv-aHJL_CQXjaRXK23FyHTTSLHjOTRFnV9__3fH_zea-cg81z1hm9ydMF4VasjSI0XOBMewx_FQmxi3UiqWuUqUP9y__M_HcxpbBb90TVv742dQT3_U4deJqWcciaS0/s1600/compass.png" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj0Kq3-OMfDfMrpPv-aHJL_CQXjaRXK23FyHTTSLHjOTRFnV9__3fH_zea-cg81z1hm9ydMF4VasjSI0XOBMewx_FQmxi3UiqWuUqUP9y__M_HcxpbBb90TVv742dQT3_U4deJqWcciaS0/s320/compass.png" width="320" height="298" /></a></div>
開催まであと1週間を切りましたが、9/10にPostgreSQLアンカンファレンスを開催します。多分、8回目くらいだと思います。<br/>
<ul>
<li>第8回 PostgreSQLアンカンファレンス@東京(2016/9/10) - connpass<br/>
<a href="http://pgunconf.connpass.com/event/37285/">http://pgunconf.connpass.com/event/37285/</a></li>
</ul>
いつもの通り、プログラムやタイムテーブルは当日集まってから募集して調整します。<br/>
<br/>
PostgreSQLに興味があって、いろんな技術レベルの人が集まっていますので、初めての方もお気軽にご参加ください。<br/>
<br/>
いつもオープニングの時に聞いているのですが、参加者のうち、だいたい2/3くらいは初めて参加の方っぽいですので。<br/>
<br/>
では、来週末お会いしましょう。
Satoshi Nagayasuhttp://www.blogger.com/profile/09706093898823214408noreply@blogger.com0tag:blogger.com,1999:blog-4546365304691285308.post-79915845582162061312016-08-04T22:53:00.000+09:002016-10-01T18:36:56.338+09:00【翻訳】 On Uber’s Choice of Databases (データベースにおけるUberの選択について)<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhzjbKMos9r9Gsy7xVXWZR_tBrnzFNtCDbNb9wgpiwDIo-yIbbcMJ3Y54_G6YfTbzTQnSVRKzFjzK2-BeFYVBsZI9SyA585C-hS4JV9zSBxizvMRCu1DTXm0hLbBtoApGbDj7xAtMY4xls/s1600/luk.png" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhzjbKMos9r9Gsy7xVXWZR_tBrnzFNtCDbNb9wgpiwDIo-yIbbcMJ3Y54_G6YfTbzTQnSVRKzFjzK2-BeFYVBsZI9SyA585C-hS4JV9zSBxizvMRCu1DTXm0hLbBtoApGbDj7xAtMY4xls/s320/luk.png" width="320" height="241" /></a></div>数日前、Uberのブログで「Why Uber Engineering Switched from Postgres to MySQL」というエントリが公開されました。<br/>
<ul>
<li>Why Uber Engineering Switched from Postgres to MySQL - Uber Engineering Blog <br/><a href="https://eng.uber.com/mysql-migration/">https://eng.uber.com/mysql-migration/</a></li>
</ul>
それに対して、PostgreSQLコミュニティ界隈でもいろいろなブログエントリが公開されました。<br/>
<ul>
<li>Robert Haas: Uber's move away from PostgreSQL <br/><a href="http://rhaas.blogspot.jp/2016/08/ubers-move-away-from-postgresql.html">http://rhaas.blogspot.jp/2016/08/ubers-move-away-from-postgresql.html</a></li>
<li>On Uber’s Choice of Databases <br/><a href="http://use-the-index-luke.com/blog/2016-07-29/on-ubers-choice-of-databases">http://use-the-index-luke.com/blog/2016-07-29/on-ubers-choice-of-databases</a></li>
<li>Thoughts on Uber’s List of Postgres Limitations | <br/><a href="http://blog.2ndquadrant.com/thoughts-on-ubers-list-of-postgres-limitations/">http://blog.2ndquadrant.com/thoughts-on-ubers-list-of-postgres-limitations/</a></li>
<li>re: Why Uber Engineering Switched From Postgres to MySQL - DZone Database<br/><a href="https://dzone.com/articles/re-why-uber-engineering-switched-from-postgres-to">https://dzone.com/articles/re-why-uber-engineering-switched-from-postgres-to
</a></li>
</ul>
今回は、そのエントリの中でも、「<a href="http://use-the-index-luke.com/">Use The Index, Luke!</a>」でおなじみのMarkus Winand氏のエントリ「On Uber’s Choice of Databases」が個人的に興味深かったので、同氏の翻訳許可をいただきまして、ここに対訳形式で公開します。
<blockquote class="twitter-tweet" data-lang="en"><p lang="en" dir="ltr"><a href="https://twitter.com/MarkusWinand">@MarkusWinand</a> Great article. If you give me a permission, I would like to tranalate it for the Japanese PG community. Is it possible?</p>— Satoshi Nagayasu (@snaga) <a href="https://twitter.com/snaga/status/760789506278633472">August 3, 2016</a></blockquote><br/>
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>
<blockquote class="twitter-tweet" data-lang="en" ><p lang="en" dir="ltr"><a href="https://twitter.com/MarkusWinand">@MarkusWinand</a> Thanks!</p>— Satoshi Nagayasu (@snaga) <a href="https://twitter.com/snaga/status/760804562139656192">August 3, 2016</a></blockquote><br/>
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>
なお、当然ですが翻訳に際しての文責は翻訳者である永安にありますので、問題を見つけた場合にはコメント欄またはTwitter (<a href="https://twitter.com/snaga/">@snaga</a>)などで連絡いただけますと幸いです。<br/>
<br/>
では、どうぞ。<br/>
<br/>
<hr/>
<h4 class="post-body">■On Uber’s Choice of Databases (データベースにおけるUberの選択について)</h4><br/>
On 7-29-2016<br/>
By <a href="http://use-the-index-luke.com/blog/2016-07-29/on-ubers-choice-of-databases">Markus Winand</a><br/>
<br/>
<a name='more'></a>
A few days ago Uber published the article “<a href="https://eng.uber.com/mysql-migration/">Why Uber Engineering Switched from Postgres to MySQL</a>”. I didn’t read the article right away because my inner nerd told me to do some home improvements instead. While doing so my mailbox was filling up with questions like “Is PostgreSQL really that lousy?”. Knowing that PostgreSQL is not generally lousy, these messages made me wonder what the heck is written in this article. This post is an attempt to make sense out of Uber’s article.<br/>
<br/>
数日前、Uberが「<a href="https://eng.uber.com/mysql-migration/">なぜUberエンジニアリングはPostgresからMySQLに切り替えたのか(Why Uber Engineering Switched from Postgres to MySQL)</a>」という記事を公開しました。私は、この記事をすぐには読んでいませんでした。というのは、私の中のナード魂が、記事を読むのではなく自宅の改修を行うように私に促してきたからです。それをしている間、私のメールボックスは「PostgreSQLはそんなにひどいのか?(Is PostgreSQL really that lousy?)」というような質問でいっぱいになりました。一般的に言って、PostgreSQLはひどくはありません。それらのメッセージは、そもそも元記事でどれだけ大げさなことが書かれているのだろうか、という疑問を私に植えつけました。この記事は、Uberの記事にどのような理屈・道理を見出すか、というひとつ試みになります。<br/>
<br/>
In my opinion Uber’s article basically says that they found MySQL to be a better fit for their environment as PostgreSQL. However, the article does a lousy job to transport this message. Instead of writing “PostgreSQL has some limitations for update-heavy use-cases” the article just says “Inefficient architecture for writes,” for example. In case you don’t have an update-heavy use-case, don’t worry about the problems described in Uber’s article.<br/>
<br/>
私の見解では、Uberの記事は彼らの環境においてPostgreSQLよりMySQLの方がよくフィットしていることに気付いた、ということを基本的には述べています。が、そのメッセージを伝えるに当たって、この記事はひどいものとなっています。例えば、「PostgreSQLは更新処理の多いユースケースではいくつかの制約がある」と書くのではなく、この記事は単に「更新には非効率なアーキテクチャ」と書いています。もし、あなたが更新処理の多いユースケースでないのであれば、Uberの記事で説明されている問題を心配する必要はありません。<br/>
<br/>
In this post I’ll explain why I think Uber’s article must not be taken as general advice about the choice of databases, why MySQL might still be a good fit for Uber, and why success might cause more problems than just scaling the data store.<br/>
<br/>
本ポストでは、なぜUberの記事を一般的なデータベース選択のアドバイスとして受け取ってはならないのか、なぜMySQLがUberによって良い選択肢なのか、そして、成功することがなぜ単なるデータストアのスケーリング以上の問題を引き起こすのかを解説します。
<h4 class="post-body">■On UPDATE (UPDATEについて)</h4><br/>
The first problem Uber’s article describes in great, yet incomplete detail is that PostgreSQL always needs to update all indexes on a table when updating rows in the table. MySQL with InnoDB, on the other hand, needs to update only those indexes that contain updated columns. The PostgreSQL approach causes more disk IOs for updates that change non-indexed columns (“Write Amplification” in the article). If this is such a big problem to Uber, these updates might be a big part of their overall workload.<br/>
<br/>
Uberの記事が解説しているけれども厳密には不完全な最初の問題は、PostgreSQLはテーブル内の行を更新する時に常にすべてのインデックスを更新する必要がある、という部分です。一方で、InnoDBを使うMySQLは更新されたカラムを使っているインデックスだけを更新する必要がある。PostgreSQLのアプローチはインデックスのないカラムの更新時により多くのディスクI/Oを引き起こす(元記事では "Write Amplification" とされています)。もし、これがUberにとって大きな問題なのであれば、これらの更新が彼らのワークロードの多くを占めているはずです。<br/>
<br/>
However, there is a little bit more speculation possible based upon something that is not written in Uber’s article: The article doesn’t mention PostgreSQL Heap-Only-Tuples (HOT). From the <a href="https://github.com/postgres/postgres/blob/master/src/backend/access/heap/README.HOT">PostgreSQL source</a>, HOT is useful for the special case “where a tuple is repeatedly updated in ways that do not change its indexed columns.” In that case, PostgreSQL is able to do the update without touching any index if the new row-version can be stored in the same page as the previous version. The latter condition can be tuned using the <a href="https://www.postgresql.org/docs/9.2/static/sql-createtable.html#SQL-CREATETABLE-STORAGE-PARAMETERS">fillfactor</a> setting. Assuming Uber’s Engineering is aware of this means that HOT is no solution to their problem because the updates they run at high frequency affect at least one indexed column.<br/>
<br/>
しかし、Uberの記事に書かれていない事柄について考慮すると、少し思惑があるのかもしれません: 記事では PostgreSQL の Heap-Only-Tuples (HOT) について言及していないのです。<a href="https://github.com/postgres/postgres/blob/master/src/backend/access/heap/README.HOT">PostgreSQLのソースコード</a>には、HOTは特殊なケース、「インデックスが作成されているカラムを変更しない更新が繰り返される」時に有用である、とあります。この場合に、PostgreSQLは新しいバージョンの行が以前のバージョンの行と同じページに格納できる時には、インデックスに一切触らずに更新できるのです。後者の条件は <a href="https://www.postgresql.org/docs/9.2/static/sql-createtable.html#SQL-CREATETABLE-STORAGE-PARAMETERS">fillfactor</a> の設定を使うことで調整できます。Uber's Engineering の記事がこれに気付いていると仮定すると、HOTが彼らの問題のソリューションにならない理由は、つまりは彼らが高頻度で実行している更新処理が、インデックスの貼られたカラムを少なくとも一つは対象としているからなのでしょう。<br/>
<br/>
This assumption is also backed by the following sentence in the article: “if we have a table with a dozen indexes defined on it, an update to a field that is only covered by a single index must be propagated into all 12 indexes to reflect the ctid for the new row”. It explicitly says “only covered by a single index” which is the edge case—just one index—otherwise PostgreSQL’s HOT would solve the problem.<br/>
<br/>
この仮定は、記事中の次の文章によって裏付けられています: 「もし、1ダースのインデックスの貼られたテーブルがあったとすると、たった一つのインデックスが作成されているフィールドへの更新は、新しい行の ctid を反映させるために12個すべてのインデックスへと伝播されなければなりません」。ここでは明確に、「たった一つのインデックスが作成されている」と書かれており、ひとつのインデックスというのは境界条件(edge case)になりますが、そうでなければ(※訳注:インデックスが無いカラムの場合には)PostgreSQLのHOTがこの問題を解決します。<br/>
<br/>
[Side note: I’m genuinely curious whether the number of indexes they have could be reduced—<a href="http://winand.at/index-design&utm_medium=UTILt&utm_compaign=201607-uber">index redesign</a> in my challenge. However, it is perfectly possible that those indexes are used sparingly, yet important when they are used.]<br/>
<br/>
[備考: 私は心底、彼らの作成しているインデックスの数をどれだけ減らせるかに興味があります、<a href="http://winand.at/index-design&utm_medium=UTILt&utm_compaign=201607-uber">インデックス再設計</a>の挑戦として。しかし、それらのインデックスがあまり使われていないという可能性は十分にあるものの、それでもそれらが使われた時には重要ではあります。]<br/>
<br/>
It seems that they are running many updates that change at least one indexed column, but still relatively few indexed columns compared to the “dozen” indexes the table has. If this is a predominate use-case, the article’s argument to use MySQL over PostgreSQL makes sense.<br/>
<br/>
彼らは、多くの更新処理を、インデックスの貼られた少なくとも一つ以上のカラムを更新するものを実行しているようですが、それでも「1ダースの」インデックスと比べると、相対的に少ないです。これがユースケースの大部分なのであれば、記事の主張であるPostgreSQLの代わりにMySQLを使うという主張は納得がいくものです。
<h4 class="post-body">■On SELECT (SELECTについて)</h4><br/>
There is one more statement about their use-case that caught my attention: the article explains that MySQL/InnoDB uses clustered indexes and also admits that “This design means that InnoDB is at a slight disadvantage to Postgres when doing a secondary key lookup, since two indexes must be searched with InnoDB compared to just one for Postgres.” I’ve previously written about this problem <a href="http://use-the-index-luke.com/blog/2014-01/unreasonable-defaults-primary-key-clustering-key">(“the clustered index penalty”) in context of SQL Server</a>.<br/>
<br/>
彼らのユースケースについて、私が注目した文章がもう一つあります: 記事では、MySQL/InnoDBはクラスター化インデックスを使っており、「この設計は、InnoDBがセカンダリインデックスを参照する時に、PostgreSQLに対してわずかな不利益があることを意味している。なぜならば、Postgresがただ一つのインデックスを使うのに対して、InnoDBでは2つのインデックスを検索しなければならないからだ」ということを認めています。私は、<a href="http://use-the-index-luke.com/blog/2014-01/unreasonable-defaults-primary-key-clustering-key">SQL Serverにおけるこの問題(クラスタ化インデックスのペナルティ)</a>について以前書いたことがあります。<br/>
<br/>
What caught my attention is that they describe the clustered index penalty as a “slight disadvantage”. In my opinion, it is a pretty big disadvantage if you run many queries that use secondary indexes. If it is only a slight disadvantage to them, it might suggest that those indexes are used rather seldom. That would mean, they are mostly searching by primary key (then there is no clustered index penalty to pay). Note that I wrote “searching” rather than “selecting”. The reason is that the clustered index penalty affects any statement that has a where clause—not just select. That also implies that the high frequency updates are mostly based on the primary key.<br/>
<br/>
私の注意を引いたのは、彼らがクラスター化インデックスのペナルティを「わずかな不利益」としていたことです。私の見解では、もしあなたがセカンダリインデックスを使う多くのクエリを実行しているのであれば、これは非常に大きな不利益なのです。もし、これが彼らにとってわずかな不利益なのであれば、それらのインデックスがめったに使われていないことを示唆しています。つまり、ほとんどの場合には主キーによる検索(searching)である(よってクラスター化インデックスのペナルティは発生しない)ことを意味しています。私が選択(selecting)ではなく検索(searching)と書いたことに注意してください。その理由は、クラスター化インデックスのペナルティは、SELECTのみならず、すべてのWHERE句を持つクエリに影響するからです。またこのことにより、高頻度の更新処理の大部分は主キーを使っていると想定できます。<br/>
<br/>
Finally there is another omission that tells me something about their queries: they don’t mention PostgreSQL’s limited ability to do <a href="https://wiki.postgresql.org/wiki/What's_new_in_PostgreSQL_9.2#Index-only_scans">index-only scans</a>. Especially in an update-heavy database, the PostgreSQL implementation of index-only scans is pretty much useless. I’d even say this is the single issue that affects most of my clients. I’ve already <a href="http://use-the-index-luke.com/de/blog/2011-06-24/dear-database-vendors">blogged about this in 2011</a>. In 2012, <a href="https://wiki.postgresql.org/wiki/What%27s_new_in_PostgreSQL_9.2#Index-only_scans">PostgreSQL 9.2 got limited support of index-only scans</a> (works only for mostly static data). <a href="https://wiki.postgresql.org/wiki/Pgcon2014unconferenceBitmapIndexOnlyScan">In 2014 I even raised one aspect of my concern at PgCon</a>. However, Uber doesn’t complain about that. Select speed is not their problem. I guess query speed is generally solved by running the selects on the replicas (see below) and possibly limited by mostly doing primary key side.<br/>
<br/>
最後に、もう一つの書かれていない点、彼らのクエリについて私に何かを教えてくれている部分があります: 彼らはPostgreSQLの<a href="https://wiki.postgresql.org/wiki/What's_new_in_PostgreSQL_9.2#Index-only_scans">Index-Onlyスキャン</a>の実行における制約については触れていません。特に更新の多いデータベースにおいては、PostgreSQLのIndex-Onlyスキャンはまったくと言っていいほど役に立ちません。これは、私の顧客の多くに影響を与える唯一の問題です。このことについて、<a href="http://use-the-index-luke.com/de/blog/2011-06-24/dear-database-vendors">2011年にはブログを書きました</a>。<a href="https://wiki.postgresql.org/wiki/What%27s_new_in_PostgreSQL_9.2#Index-only_scans">2012年には、PostgreSQL 9.2がIndex-Onlyスキャンの限定されたサポート</a>(大部分が静的なデータに対してのみ機能する)を実現しました。<a href="https://wiki.postgresql.org/wiki/Pgcon2014unconferenceBitmapIndexOnlyScan">2014年には、私の懸念のひとつの側面について PgCon で問題提起をしました</a>。しかし、Uberはそれについて問題視していません。SELECTの速さは彼らにとって問題ではないのです。想像するに、クエリの速さは一般的に、レプリカでSELECTすることによって解決され、かつ、主キーを使って操作するということによって(※訳注:実行時間は)限定されるのです。<br/>
<br/>
By now, their use-case seems to be a better fit for a key/value store. And guess what: InnoDB is a pretty solid and popular key/value store. There are even packages that bundle InnoDB with some (<a href="http://de.slideshare.net/MarkusWinand/modern-sql">very limited</a>) SQL front-ends: MySQL and MariaDB are the most popular ones, I think. Excuse the sarcasm. But seriously: if you basically need a key/value store and occasionally want to run a simple SQL query, MySQL (or MariaDB) is a reasonable choice. I guess it is at least a better choice than any random NoSQL key/value store that just started offering an even more limited SQL-ish query language. Uber, on the other hand just builds their own thing (“<a href="https://eng.uber.com/schemaless-part-one/">Schemaless</a>”) on top of InnoDB and MySQL.<br/>
<br/>
この時点で、彼らのユースケースは Key/Valueストアの方がよりフィットするように見えます。そして、想像してください: InnoDBは非常に堅牢で人気のあるKey/Valueストアなのです。InnoDBに(<a href="http://de.slideshare.net/MarkusWinand/modern-sql">非常に限定された</a>)SQLフロントエンドをバンドルしたパッケージがあります: 私が思うに、MySQLとMariaDBは非常に人気のあるものです。皮肉を許してください。でもマジメに: もしあなたが求めているものがKey/Valueストアであり、シンプルなクエリを時々実行するものであれば、MySQL(またはMariaDB)は合理的な選択肢です。少なくとも、最近になって限定されたSQLっぽいクエリ言語を提供し始めたばかりのどこかのNoSQL Key/Valueストアよりは良い選択肢だと思います。Uberは、逆に彼ら自身のもの(<a href="https://eng.uber.com/schemaless-part-one/">Schemaless</a>)をInnoDBとMySQLの上に構築しました。
<h4 class="post-body">■On Index Rebalancing (インデックスの再バランスについて)</h4><br/>
One last note about how the article describes indexing: it uses the word “rebalancing” in context of B-tree indexes. It even links to a Wikipedia article on “<a href="https://en.wikipedia.org/wiki/B-tree#Rebalancing_after_deletion">Rebalancing after deletion</a>.” Unfortunately, the Wikipedia article doesn’t generally apply to database indexes because the algorithm described on Wikipedia maintains the requirement that each node has to be at least half-full. To improve concurrency, PostgreSQL uses the <a href="http://www.csd.uoc.gr/~hy460/pdf/p650-lehman.pdf">Lehman, Yao variation of B-trees</a>, which lifts this requirement and thus allows sparse indexes. As a side note, PostgreSQL still removes empty pages from the index (see <a href="https://www.pgcon.org/2016/schedule/attachments/434_Index-internals-PGCon2016.pdf">slide 15 of “Indexing Internals”</a>). However, this is really just a side issue.<br/>
<br/>
その記事が、インデックスについてどのように説明しているかについての最後の一点です: 記事中でB-Treeインデックスの文脈で「再バランス(rebalancing)」という言葉を使っています。また、Wikipediaの「<a href="https://en.wikipedia.org/wiki/B-tree#Rebalancing_after_deletion">削除後の再バランス</a>」の記事へのリンクも貼っています。残念なことに、Wikipediaの記事の内容はデータベースのインデックスに対して適用させることはできません。なぜなら、Wikipediaで説明されているアルゴリズムでは、各ノードが少なくとも半分埋まっている状態であることを要求しているからです。PostgreSQLでは、並行性を向上させるためにこの前提を除去して疎(sparse)なインデックスを可能にする<a href="http://www.csd.uoc.gr/~hy460/pdf/p650-lehman.pdf">LhemanとYaoのB-treeの派生版</a>を使っています。追記しておくと、PostgreSQLはインデックス内で空になったページを削除しますが(<a href="https://www.pgcon.org/2016/schedule/attachments/434_Index-internals-PGCon2016.pdf">"Indexing Internals"のスライド15枚目</a>を見てください)、これは枝葉の問題です。<br/>
<br/>
What really worries me is this sentence: “An essential aspect of B-trees are that they must be periodically rebalanced, …” Here I’d like to clarify that this is not a periodic process one that runs every day. The index balance is maintained with every single index change (even worse, hmm?). But the article continues “…and these rebalancing operations can completely change the structure of the tree as sub-trees are moved to new on-disk locations.” If you now think that the “rebalancing” involves a lot of data moving, you misunderstood it.<br/>
<br/>
私が本当に心配しているのは次の文章です: 「B-treeの本質的な側面は、それらが定期的に再バランスを必要とすることです」。ここで明確にしておきたいのは、これは毎日実行されるような定期的なプロセスではない、ということです。インデックスのバランスは、いかなる小さな変更であれ、インデックスが変更される際に常にメンテナンスされるのです(もっと悪いですか? ふーむ)。しかし、この記事は続けます。「これらの再バランスの処理は、ツリーの一部(sub-trees)を新しいディスク上の位置に移動させることによって、ツリーの構造を完全に変えてしまうことになります」。ここで、もしあなたが「再バランス」が大量のデータ移動を引き起こしてしまうと考えているならば、それは間違いです。<br/>
<br/>
The important operation in a B-tree is the node split. As you might guess, a node split takes place when a node cannot host a new entry that belongs into this node. To give you a ballpark figure, this might happen once for about 100 inserts. The node split allocates a new node, moves half of the entries to the new node and connects the new node to the previous, next and parent nodes. This is where Lehman, Yao save a lot of locking. In some cases, the new node cannot be added to the parent node straight away because the parent node doesn’t have enough space for the new child entry. In this case, the parent node is split and everything repeats.<br/>
<br/>
B-treeにおいて重要な操作はノードの分割です。あなたが想像した通り、ノードの分割は、そのノードに保持すべき新しいエントリが入り切らなくなった時に発生します。ざっくり理解するために、100回INSERTする度に発生すると仮定しましょう。ノード分割は、新しいノードを割り当て、インデックスエントリの半分を新しいノードに移動させ、新しいノードを以前のノード、隣のノード、および親ノードと連結します。これこそが、LehmanとYaoが多くのロックを削減した部分です。場合によっては、親ノードがいっぱいで新しいノードをすぐに親ノードに追加できない場合があります。その場合、親ノードが分割され、同じようにすべてが繰り返されることになります。<br/>
<br/>
In the worst case, the splitting bubbles up to the root node, which will then be split as well and a new root node will be put above it. Only in this case, a B-tree ever becomes deeper. Note that a root node split effectively shifts the whole tree down and therefore keeps the balance. However, this doesn’t involve a lot of data moving. In the worst case, it might touch three nodes on each level and the new root node. To be explicit: most real world indexes have no more than 5 levels. To be even more explicit: the worst case—root node split—might happen about five times for a billion inserts. On the other cases it will not need to go the whole tree up. After all, index maintenance is not “periodic”, not even very frequent, and is never completely changing the structure of the tree. At least not physically on disk.<br/>
<br/>
最悪なケースでは、ノード分割はルートノードまで伝播していき、同じようにルートノードが分割され、新しいルートノードがその上に配置されます。この場合のみ、B-treeはその階層が深くなるのです。ルートノードの分割は実際、ツリーを下方にシフトさせることでそのバランスを保ちます。しかし、この処理は多くのデータ移動を伴うものではありません。最悪の場合には、各階層で3ノード、および新しいルートノードにアクセスします。明確にしておきたい点: 実際の世の中のほとんどのインデックスは、5階層以上にはならないということです。さらに明確にしておきたい点: 最悪なケース ―ルートノードの分割― は、10億回のINSERTに対して5回くらい起こる、ということです。その他の場合には、ツリー全体を上がっていく必要はありません。このように、インデックスのメンテナンスというのは「定期的」なのではなく、非常に頻繁に発生しているものであり、ツリー全体の構造を完全に変えてしまうようなものでもありません。少なくとも、ディスク上の物理配置については。
<h4 class="post-body">■On Physical Replication (物理ログを使ったレプリケーションについて)</h4><br/>
That brings me to the next major concern the article raises about PostgreSQL: physical replication. The reason the article even touches the index “rebalancing” topic is that Uber once hit a PostgreSQL replication bug that caused data corruption on the downstream servers (the bug “only affected certain releases of Postgres 9.2 and has been fixed for a long time now”).<br/>
<br/>
この記事が提起したPostgreSQLへの次の大きな懸念: 物理ログを使ったレプリケーションです。元の記事がインデックスの「再バランス」の件に触れたのは、UberがPostgreSQLのレプリケーションで、下流のレプリカにおけるデータ破壊を引き起こすバグを踏んだからです。(このバグは「Postgres 9.2の特定のバージョンだけに発生し、かなり以前に修正されているものです」)<br/>
<br/>
Because PostgreSQL 9.2 only offers physical replication in core, a replication bug “can cause large parts of the tree to become completely invalid.” To elaborate: if a node split is replicated incorrectly so that it doesn’t point to the right child nodes anymore, this sub-tree is invalid. This is absolutely true—like any other “if there is a bug, bad things happen” statement. You don’t need to change a lot of data to break a tree structure: a single bad pointer is enough.<br/>
<br/>
PostgreSQL 9.2では、コア(本体)では物理ログを使ったレプリケーションだけを提供していますので、レプリケーションのバグは「インデックスツリーの大部分が完全に壊れているという状況を引き起こしうる」ものです。詳細に言うと、ノード分割が間違ってレプリケーションされると、二度と正しい子ノードを指し示さなくなってしまい、ツリーの一部が壊れた状態になります。これは、その他の「もしバグがあったなら、悪いことが起こるでしょう」という話と同じで真実のように聞こえるものです。ツリー構造を壊すには、多くのデータを書き換える必要はあなく、ただひとつの間違ったポインタだけで十分なのです。<br/>
<br/>
The Uber article mentions other issues with physical replication: huge replication traffic—partly due to the write amplification caused by updates—and the downtime required to update to new PostgreSQL versions. While the first one makes sense to me, I really cannot comment on the second one (but there were some <a href="https://www.postgresql.org/message-id/20160727002711.GI4028%40tamriel.snowman.net">statements on the PostgreSQL-hackers mailing list</a>).<br/>
<br/>
Uberの元記事は、物理ログベースのレプリケーションのその他の問題を指摘しています: 大量のレプリケーション通信―UPDATEによる書き込みの増幅(write amplification)によるものを含む―、および新しいPostgreSQLバージョンへのアップデートの際にダウンタイムを必要としていること、などです。前者については納得がいくものですが、後者については私にはコメントできません。(が、<a href="https://www.postgresql.org/message-id/20160727002711.GI4028%40tamriel.snowman.net">PostgreSQL-hackersメーリングリストでいくつか言及</a>がありました)<br/>
<br/>
Finally, the article also claims that “Postgres does not have true replica MVCC support.” Luckily the article links to the PostgreSQL documentation where this problem (and remediations) are explained. The problem is basically that the master doesn’t know what the replicas are doing and might thus delete data that is still required on a replica to complete a query.<br/>
<br/>
最後に、元記事は「PostgreSQLはレプリカで真のMVCCをサポートしていない」と苦情を述べています。ラッキーなことに、元記事はその問題を解説した(そして改善した)PostgreSQLのドキュメントにリンクを張っています。この問題は、基本的にはマスターというのはレプリカが何をやっているか知る由もない、つまり、レプリカがクエリを完了するために必要としているデータを、(マスタが)削除するようなことがあり得る、ということです。<br/>
<br/>
According to the <a href="https://www.postgresql.org/docs/9.2/static/hot-standby.html#HOT-STANDBY-CONFLICT">PostgreSQL documentation</a>, there are two ways to cope with this issue: (1) delaying the application of the replication stream for a configurable timeout so the read transaction gets a chance to complete. If a query doesn’t finish in time, kill the query and continue applying the replication stream. (2) configure the replicas to send feedback to the master about the queries they are running so that the master does not vacuum row versions still needed by any slave. Uber’s article rules the first option out and doesn’t mention the second one at all. Instead the article blames the Uber developers.<br/>
<br/>
<a href="https://www.postgresql.org/docs/9.2/static/hot-standby.html#HOT-STANDBY-CONFLICT">PostgreSQLのドキュメント</a>によると、この問題に対処するためには2つの方法があります: (1) 設定したタイムアウトに達するまで、レプリケーションストリーム(ログ転送)を生成しているアプリケーションを遅延させることで、(※訳注:レプリカで)実行されている読み取りトランザクションが完了できるようにする。(※訳注:レプリカ上の読み取り)クエリが指定した時間内に完了しなければ、そのクエリをキャンセルしてレプリケーションストリームの適用を継続する。 (2) レプリカが実行しているクエリについて、マスター側にフィードバックを送るように設定する。それによって、どこかのスレーブで必要としている行のバージョンに対してマスター側でVACUUMしないようにする。Uberの元記事は1つ目の選択肢については規定していますが、2つ目の選択肢についてはまったく言及していません。Uberの開発者のせいにする代わりに。
<h4 class="post-body">■On Developers (開発者について)</h4><br/>
To quote it in all its glory: “For instance, say a developer has some code that has to email a receipt to a user. Depending on how it’s written, the code may implicitly have a database transaction that’s held open until after the email finishes sending. While it’s always bad form to let your code hold open database transactions while performing unrelated blocking I/O, the reality is that most engineers are not database experts and may not always understand this problem, especially when using an ORM that obscures low-level details like open transactions.”<br/>
<br/>
これを引用できることを嬉しく思います: 「例えば、開発者が領収書をユーザにメールするコードを書いたとします。どのように書かれたのか、その実装にもよりますが、そのコードでは暗黙的に、メールの送信を完了するまでデータベーストランザクションがオープンされたままになるかもしれません。データベースに関連性のないブロッキングI/Oが実行される間、データベーストランザクションをオープンしたままにするというのは、どんな場合でも悪い状況となるわけけですが、現実的にはほとんどのエンジニアはデータベースのエキスパートではなく、特にオープントランザクションのような低いレベルの問題を引き起こすORMを使うような場合には、これらの問題を常には認識していないでしょう。」<br/>
<br/>
Unfortunately, I understand and even agree with this argument. Instead of “most engineers are not database experts” I’d even say that most developers have very little understanding of databases because every developer that touches SQL needs know about transactions—not just database experts.<br/>
<br/>
残念なことに、私は認識しており、この主張に賛同しています。「ほとんどのエンジニアはデータベースのエキスパートではない」と言う代わりに、データベースエキスパートだけではなくSQLに触るすべての開発者がトランザクションについて知っていなければならないにも関わらず、ほとんどの開発者はデータベースについてわずかしか理解していない、と言うことができます。<br/>
<br/>
Giving SQL training to developers is my main business. I do it at companies of all sizes. If there is one thing I can say for sure is that the knowledge about SQL is ridiculously low. In context of the “open transaction” problem just mentioned I can conform that hardly any developer even knows that read only transactions are a real thing. Most developers just know that transactions can be used to back out writes. I’ve encountered this misunderstanding often enough that I’ve prepared slides to explain it and I just <a href="http://www.slideshare.net/MarkusWinand/sql-transactions-what-they-are-good-for-and-how-they-work">uploaded these slides for the curious reade</a>r.<br/>
<br/>
SQLのトレーニングを開発者に提供することは私のメインのビジネスです。あらゆる規模の企業においてこれを行っています。私が言えるひとつ確実なことは、SQLについての知識が尋常でなく低いということです。たった今言った「オープントランザクション」の文脈においては、開発者たちは、参照のみのトランザクションが実際重要なものである、と多少は知っていると思います。ほとんどの開発者はトランザクションは書いたものを戻すために使うことができる、ということを知っているくらいでしょう。私は、これを説明するスライドを準備している際、度々この誤解に遭遇しますし、その度にこの問題に<a href="http://www.slideshare.net/MarkusWinand/sql-transactions-what-they-are-good-for-and-how-they-work">興味のある読者に向けてスライドをアップロードしている</a>のです。
<h4 class="post-body">■On Success (成功について)</h4><br/>
This leads me to the last problem I’d like to write about: the more people a company hires, the closer their qualification will be to the average. To exaggerate, if you hire the whole planet, you’ll have the exact average. Hiring more people really just increases the sample size.<br/>
<br/>
このことは、私が書きたい最後の問題に私を導きます: 企業がより多くの人を雇うようになると、彼ら彼女らの能力は平均値に近づきます。大げさに言うと、もし地球上のすべての人を雇えば、完全に平均値になります。より多くの人を雇うということは、実際にサンプルサイズを大きくする、ということなのです。<br/>
<br/>
The two ways to beat the odds are: (1) Only hire the best. The difficult part with this approach is to wait if no above-average candidates are available; (2) Hire the average and train them on the job. This needs a pretty long warm-up period for the new staff and might also bind existing staff for the training. The problem with both approaches is that they take time. If you don’t have time—because your business is rapidly growing—you have to take the average, which doesn’t know a lot about databases (empirical data from 2014). In other words: for a rapidly growing company, technology is easier to change than people.<br/>
<br/>
この可能性を避ける2つの方法は: (1) ベストな人材のみを雇うこと。このアプローチの難しいところは、平均値以上の候補者が見つからない時に待つ必要があることです。 (2) 平均値の人を雇って仕事をしながらトレーニングすること。この方法は新しいスタッフに対して本当に長いウォームアップ期間を必要としますし、既存のスタッフについてもトレーニングに拘束することになるでしょう。両者に共通する問題は、時間がかかる、ということです。もし、あなたに時間がない ―ビジネスが急成長しているなどの理由で― のであれば、データベースについてさほど分かっていない平均値の人を雇うしかありません(2014年に実証されています)。言い換えれば: 急激に成長している企業にとっては、人材よりもテクノロジーの方が取り換えは容易なのです。<br/>
<br/>
The success factor also affects the technology stack as requirements change over time. At an early stage, start-ups need out-of-the-box technology that is immediately available and flexible enough to be used for their business. SQL is a good choice here because it is actually flexible (you can query your data in any way) and it is easy to find people knowing SQL at least a little bit. Great, let’s get started! And for many—probably most—companies, the story ends here. Even if they become moderately successful and their business grows, they might still stay well within the limits of SQL databases forever. Not so for Uber.<br/>
<br/>
また成功要因は、時間の経過に伴って要求が変化するため、テクノロジースタックに影響を与えます。アーリーステージにおいては、スタートアップはすぐに利用可能なテクノロジーであり、彼らのビジネスにおいて十分に柔軟であるものを必要とします。SQLは、本当に柔軟であるからこそ(どんなやり方であれ、データに対して問い合わせすることはできます)、この局面で良い選択肢であり、SQLを多少なりとも知っている人を見つけることはたやすいことです。素晴らしい、さぁ始めましょう! そして、ほとんどの会社にとって、話はここで終わるのです。仮に緩やかな成功と、ビジネスの成長が実現できたとしても、SQLデータベースの制約の中に健やかに留まっていることになるでしょう。Uberはそうではありませんでしたが。<br/>
<br/>
A few lucky start-ups eventually outgrow SQL. By the time that happens, they have access to way more (virtually unlimited?) resources and then…something wonderful happens: They realize that they can solve many problems if they replace their general purpose database by a system they develop just for their very own use-case. This is the moment a new NoSQL database is born. At Uber, they call it <a href="https://eng.uber.com/schemaless-part-one/">Schemaless</a>.<br/>
<br/>
まれにラッキーなスタートアップは最終的にSQLの限界を超えます。それが起こる時、彼らはさらなる(仮想的には無限の?)リソースを利用することができ、そして。。。何か素敵なことが起こります: 彼らは、その一般的な用途のデータベースを彼ら独自のユースケースだけのために開発したシステムでリプレースすることで、多くの問題を解決できることを認識するのです。これこそが、新しいNoSQLデータベースが生まれる瞬間です。Uberでは、それは<a href="https://eng.uber.com/schemaless-part-one/">Schemaless</a>と呼ばれています。
<h4 class="post-body">■On Uber’s Choice of Databases (データベースにおけるUberの選択について)</h4><br/>
By now, I believe Uber did not replace PostgreSQL by MySQL as their article suggests. It seems that they actually replaced PostgreSQL by their tailor-made solution, which happens to be backed by MySQL/InnoDB (at the moment).<br/>
<br/>
現時点では、私はUberは彼らの記事が言っているようにPostgreSQLをMySQLでリプレースしたとは思っていません。彼らは実際には、(現時点では)MySQL/InnoDBで支えられた彼ら独自のソリューションでPostgreSQLを置き換えたように見えます。<br/>
<br/>
It seems that the article just explains why MySQL/InnoDB is a better backend for Schemaless than PostgreSQL. For those of you using Schemaless, take their advice! Unfortunately, the article doesn’t make this very clear because it doesn’t mention how their requirements changed with the introduction of Schemaless compared to <a href="https://www.yumpu.com/en/document/view/53683323/migrating-uber-from-mysql-to-postgresql">2013, when they migrated from MySQL to PostgreSQL</a>.<br/>
<br/>
元記事は、MySQL/InnoDBがなぜ彼らのSchemalessのバックエンドとして、PostgreSQLよりも良かったのか、ということを説明しているように見えます。Schemalessを使っている人たちは、そのアドバイスを聞くべきでしょう! 残念ながら、元記事はそのことについて明確にしていません。というのは、Schemalessを紹介するに当たって、彼らの要件が<a href="https://www.yumpu.com/en/document/view/53683323/migrating-uber-from-mysql-to-postgresql">2013年、彼らがMySQLからPostgreSQLに移行した時</a>と比べてどのように変化してきたのかについて言及していないからです。<br/>
<br/>
Sadly, the only thing that sticks in the reader’s mind is that PostgreSQL is lousy.<br/>
<br/>
悲しいことに、読者の頭の中に残るのは、PostgreSQLがひどい、ということだけです。<br/>
<br/>
If you like my way of explaining things, you’ll love <a href="http://sql-performance-explained.com/?utm_source=UTIL&utm_medium=BOTTOM&utm_campaign=1">my book</a>.<br/>
<br/>
もし、私が説明する諸々を気に入っていただけるのであれば、おそらく<a href="http://sql-performance-explained.jp/?utm_source=UTIL&utm_medium=BOTTOM&utm_campaign=1">私の本</a>(※訳注:日本語版)も気に入ってもらえるのではないかと思います。<br/>
<br/>
Satoshi Nagayasuhttp://www.blogger.com/profile/09706093898823214408noreply@blogger.com2tag:blogger.com,1999:blog-4546365304691285308.post-88622628835044737902016-07-19T16:45:00.001+09:002016-07-19T17:48:48.905+09:00TF-IDFでデータベース内の類似テキストを検索する Part 4 (MADlib svec編)<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh7c4LTLE3elEMG6oFAQtc7Ce222O-d8WEqRCEgG0mtLKgF3JWDjPmoOENJB222sWUbCgdmlGbD3sLLdMZ48blcvFE_zGzpqC1I1OtKRulmkUm4hRyoGqnvSIBwAaRGQeI7CT6eTJeoWmE/s1600/svec.png" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh7c4LTLE3elEMG6oFAQtc7Ce222O-d8WEqRCEgG0mtLKgF3JWDjPmoOENJB222sWUbCgdmlGbD3sLLdMZ48blcvFE_zGzpqC1I1OtKRulmkUm4hRyoGqnvSIBwAaRGQeI7CT6eTJeoWmE/s320/svec.png" width="320" height="230" /></a></div>TF-IDF 感動巨編3部作は前回のエントリで完結したわけですが、今回はその番外編、スピンオフとして「MADlib svec編」をお送りします。<br/>
<br/>
MADlib には、sparse(疎)な配列、つまり多くの要素がゼロであるような配列を扱うデータ型として svec というデータ型があります。<br/>
<ul>
<li>MADlib: Sparse Vectors <br/><a href="https://madlib.incubator.apache.org/docs/latest/group__grp__svec.html">https://madlib.incubator.apache.org/docs/latest/group__grp__svec.html</a></li>
</ul>
本エントリでは、TF-IDF のベクトルに MADlib の svec を使って、通常の float8[] などとどのように違うのかを見てみます。
<h4 class="post-body">■「MADlib」とは何か</h4><br/>
MADlib については、ガッツリと割愛します。以前のエントリで詳しくご紹介しましたので、そちらを参照してください。<br/>
<ul>
<li>データ分析用ライブラリ MADlib を使って PostgreSQL で機械学習する <br/><a href="http://pgsqldeepdive.blogspot.jp/2016/04/madlib-postgresql.html">http://pgsqldeepdive.blogspot.jp/2016/04/madlib-postgresql.html</a></li>
</ul>
<h4 class="post-body">■「svec」 とは何か</h4><br/>
svec は、ゼロの多い sparse な配列を圧縮して保持するデータ型です。データ分析をしていると、頻繁に遭遇するデータの構造になります。<br/>
<br/>
例えば、float8 の配列で以下のようにゼロが並ぶデータがあったとします。
<pre style="overflow-x: scroll; white-space: pre">
'{0, 33,...40,000個のゼロ..., 12, 22 }'::float8[]
</pre>
<a name='more'></a>
すると、この配列は 320kB 以上のディスク容量またはメモリを消費することになります。ほとんど意味のないゼロを保持するだけのために、これだけのリソースを食ってしまいます。<br/>
<br/>
svec は、この配列を以下のようにランレングス圧縮(RLE圧縮)することでデータサイズを縮小します。
<pre style="overflow-x: scroll; white-space: pre">
'{1,1,40000,1,1}:{0,33,0,12,22}'::madlib.svec
</pre>
このように圧縮することによって、5つの整数型と5つの浮動小数点型に集約され、データサイズが劇的に小さくなります。<br/>
<br/>
このようなデータ型を用意することで、ディスク容量とメモリの消費を抑え、大量のデータの処理を可能にします。(もちろん、演算処理時には圧縮されたデータを展開しながら行いますので、そのCPUコストは発生します)<br/>
<br/>
さらなる詳細はマニュアルを参照してください。<br/>
<ul>
<li>MADlib: Sparse Vectors <br/><a href="https://madlib.incubator.apache.org/docs/latest/group__grp__svec.html">https://madlib.incubator.apache.org/docs/latest/group__grp__svec.html</a></li>
</ul>
<h4 class="post-body">■float8[] を svec に変換する</h4><br/>
まず、TF-IDF 感動巨編3部作が完結した状態のテーブルから始めます。<br/>
<ul>
<li>TF-IDFでデータベース内の類似テキストを検索する Part 3 (性能改善編) <br/><a href="http://pgsqldeepdive.blogspot.jp/2016/07/tf-idf-part-3.html">http://pgsqldeepdive.blogspot.jp/2016/07/tf-idf-part-3.html</a></li>
</ul>
この時、以下のようなテーブル定義になっているはずです。
<pre style="overflow-x: scroll; white-space: pre">
snaga=> \d pgsql_doc
Table "public.pgsql_doc"
Column | Type | Modifiers
-----------+--------------------+-----------------------------------------------------------
docid | integer | not null default nextval('pgsql_doc_docid_seq'::regclass)
filename | text | not null
html | text | not null
plain | text |
tf | jsonb |
tfidf | jsonb |
tfidf_vec | double precision[] |
Indexes:
"pgsql_doc_pkey" PRIMARY KEY, btree (docid)
snaga=>
</pre>
この時、tfidf_vec のカラムは、以下のようにゼロの多い float8 の配列になっています。
<pre style="overflow-x: scroll; white-space: pre">
snaga=> SELECT substring(tfidf_vec::text from 0 for 100) FROM pgsql_doc LIMIT 1;
substring
-----------------------------------------------------------------------------------------------------
{0,0,0,0,0,0,0,0.0328645679655572,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.0
(1 row)
snaga=>
</pre>
この float8[] を svec に変換するには、単に madlib.svec 型へキャストすれば完了です。
<pre style="overflow-x: scroll; white-space: pre">
snaga=> SELECT substring(tfidf_vec::madlib.svec::text from 0 for 100) FROM pgsql_doc LIMIT 1;
substring
-----------------------------------------------------------------------------------------------------
{7,1,31,1,4,1,18,1,70,1,88,1,4,1,51,1,11,1,45,1,1,1,52,1,2,1,4,1,1,7,1,1,1,36,1,1,1,36,1,106,1,14,1
(1 row)
snaga=>
</pre>
<h4 class="post-body">■float8[] と svec のテーブルを用意する</h4><br/>
まず、比較のために、docid, filename カラムと、float8[] の tfidf_vec カラム、もしくは svec の tfidf_svec カラムだけを保持するテーブル pgsql_doc_vec と pgsql_doc_svec を作成します。<br/>
<br/>
tfidf_svec カラムは madlib.svec 型になっていることを確認します。
<pre style="overflow-x: scroll; white-space: pre">
snaga=> CREATE TABLE pgsql_doc_vec AS SELECT docid,filename,tfidf_vec FROM pgsql_doc;
SELECT 1304
snaga=> \d pgsql_doc_vec
Table "public.pgsql_doc_vec"
Column | Type | Modifiers
-----------+--------------------+-----------
docid | integer |
filename | text |
tfidf_vec | double precision[] |
snaga=> CREATE TABLE pgsql_doc_svec AS SELECT docid,filename,tfidf_vec::madlib.svec as tfidf_svec FROM pgsql_doc;
SELECT 1304
snaga=> \d pgsql_doc_svec
Table "public.pgsql_doc_svec"
Column | Type | Modifiers
------------+-------------+-----------
docid | integer |
filename | text |
tfidf_svec | madlib.svec |
snaga=>
</pre>
<h4 class="post-body">■float8[] と svec のデータサイズを比較する</h4><br/>
まず、2つのテーブルのデータサイズを比較してみます。
<pre style="overflow-x: scroll; white-space: pre">
snaga=> \d+
List of relations
Schema | Name | Type | Owner | Size | Description
--------+---------------------+----------+-------+------------+-------------
public | pgsql_doc | table | snaga | 407 MB |
public | pgsql_doc_docid_seq | sequence | snaga | 8192 bytes |
public | pgsql_doc_svec | table | snaga | 3872 kB |
public | pgsql_doc_vec | table | snaga | 6752 kB |
(4 rows)
snaga=>
</pre>
上記を見て分かる通り、MADlib の svec 型を使ったテーブルの方が 3MB ほど小さくなっています。<br/>
<br/>
このようにデータサイズを小さくすることによって、ディスクサイズの節約、I/O読み込みの抑制、バッファキャッシュの消費抑制などが実現され、ひいてはパフォーマンスの向上につながります。
<h4 class="post-body">■float8[] と svec のパフォーマンス比較</h4><br/>
それでは、上記で作ったテーブルとカラムを使って、前回用いた「wal.html と類似のドキュメントを検索する」クエリでパフォーマンスを比較してみます。<br/>
<br/>
float8[] 型を使う場合のクエリは以下の通りです。
<pre style="overflow-x: scroll; white-space: pre">
EXPLAIN ANALYZE SELECT
filename,
euclidean_distance(tfidf_vec, (SELECT tfidf_vec FROM pgsql_doc_vec WHERE filename = 'wal.html') )
FROM
pgsql_doc_vec
ORDER BY
2;
</pre>
svec 型を使う場合のクエリは以下の通りです。
<pre style="overflow-x: scroll; white-space: pre">
EXPLAIN ANALYZE SELECT
filename,
euclidean_distance(tfidf_svec::float8[], (SELECT tfidf_svec::float8[] FROM pgsql_doc_svec WHERE filename = 'wal.html') )
FROM
pgsql_doc_svec
ORDER BY
2;
</pre>
上記のクエリを5回ずつ実行した結果、<br/>
<ul>
<li>float8[] 使用時:平均 4757.6 ミリ秒</li>
<ul>
<li>4738.021, 4739.953, 4734.725, 4793.513, 4781.721</li>
</ul>
<li>svec 使用時:平均 4379.5 ミリ秒</li>
<ul>
<li>4395.491, 4315.103, 4408.664, 4342.631, 4435.416</li>
</ul>
</ul>
となり、svec を使用した方が1割ほど速い、という結果になりました。<br/>
<br/>
データが圧縮されたことによって、パフォーマンス改善が実現されているようです。
<h4 class="post-body">■まとめ</h4><br/>
今回は、MADlib で提供されている svec 型を使ってみました。<br/>
<br/>
今回のケースでは1割程度のパフォーマンス改善になりましたが、当然ながらデータ圧縮の程度によってパフォーマンス向上の度合いは変わってくるでしょうし、データ量によっても変わってくると思われます。<br/>
<br/>
興味のある方は、ぜひトライしてみていただければと思います。<br/>
<br/>
では、また。<br/>
<br/>
Satoshi Nagayasuhttp://www.blogger.com/profile/09706093898823214408noreply@blogger.com0