Quantcast
Channel: PostgreSQL Deep Dive
Viewing all 94 articles
Browse latest View live

Hecatoncheir: The Data Stewardship Studio 0.8を公開しました

$
0
0
本日、「Hecatoncheir: The Data Stewardship Studio」という最近開発していた新しいツールをOSSとして公開しました。
本ツールは、データベースのメタデータおよび実データの統計情報やプロファイルを用いることで、データ品質マネジメントおよびデータガバナンスを実施するデータスチュアードを支援することを目的としたソフトウェアです。

既に某所のデータウェアハウスのシステムの周辺で稼働しています。

本エントリでは、このツールの紹介をさせていただきます。(本ツールはPostgreSQLにも対応しております)

■本ツールを開発した背景


最近は、データウェアハウスの設計から構築、データマネジメント(ガバナンスやスチュアードシップと呼ばれることもありますが)を手掛けることが多くなってきました。

データベースエンジニアですから、新しい情報系システムの領域であってもテクニカルな作業もそれなりにこなせるのですが、いくつかの案件を手掛ける中で気付いたことがありました。

それは、どのような局面であっても、データの調査や確認のために同じような作業(クエリの実行など)を何度も何度も繰り返して行わなければならない、ということです。そして、データは日々変化していくため、そのタスクを毎日のように繰り返さなければなりません。

また、データについて、他人に説明する時にもこのような調査や確認の作業が常に付きまとってきます。

それに加えて、データに何か問題が発生する可能性があるのであれば、それを自動的に検出したくなります。

しかし、そのような調査や確認作業に日常的に忙殺されていると、もっと本質的な作業に手を回すことができなくなってしまいます。

そのため、このような「ルーティンで何度も行わなければならないタスク」を自動的に実行することでデータの状態の把握・可視化を容易にし、またデータの品質問題の早期検知を実現するためのツールを開発することにしました。

このツールを使うことによって、データの面倒を見ている人たち(データスチュアード)が「当たり前のことを手間をかけずに行い、もっと新しいこと、本質的なことに安心して時間をかけられる」ようになります。

■本ツールでできること


本ツールでできることは以下の通りです。
  • データベースディクショナリ(カタログ)からメタデータを取得する
  • テーブルやカラムのプロファイリングを行う
  • あらかじめ定義したビジネスルールに則ってデータ検証を行う
  • タグ付けおよび補足的なメタデータを取り込むことでデータセットをカタログ化する
  • ビジネス用語集を構築する
  • データの開発者と利用者が必要とする情報をキュレーション・共有する
いくつかスクリーンショットを貼っておきます。

■本ツールの仕組み


本ツールは、コマンドラインで動作するツール類と、ツールからの入力を保存・統合するレポジトリ(SQLiteファイル)から構成されています。
  • ①まず、データディクショナリからメタデータを取得してレポジトリに保存する
  • ②補足的なメタデータをCSVファイルからレポジトリに取り込む
  • ③レポジトリにあるデータをHTMLとしてエクスポートする
というのが基本的な仕組み、動作の流れになります。

■動作環境・対応プラットフォーム


動作環境・対応プラットフォームについては、READMEを参照してください。

■本ツールの使い方


ソースコードはGithubレポジトリから入手できます。
クイックスタートガイドを用意していますので、興味のある方はこちらをご覧ください。 リファレンス的な情報もまとめてあります。

■名前(Hecatoncheir)の由来


ご存じの方も多いと思いますが、Hecatoncheir(ヘカトンケイル)というのは、ギリシャ神話に出てくる神様の名前です。
50の頭と100の手を持ち、戦いに当たっては次々と大きな岩を投げつける、戦いが終わると、今度は100の眼で奈落の出入口で監視をするという役回りが、このツールのイメージとピッタリのように感じたため、この名前を選びました。

■まとめ


本エントリでは、今回公開した Hecatoncheir の紹介をしました。

まだまだ対応DBMSも増やしていきたいですし、いろいろと機能開発や改良もしていきたいと思っています。

興味のある方は、ぜひGithubのIssueなどでご意見、コメントを頂ければと思います。

では。

Azure Database for PostgreSQLにアクセスしてみた

$
0
0
5/11のMicrosoft Build 2017で、PostgreSQLのDBaaSがAzureで提供されることが発表されました。
現時点ではプレビューのようですが、ちょっと興味があったので軽く触ってみました。

ちなみに、Azureは普段使っていないのでそんなに詳しくありません。

■PostgreSQLのリソースを作成する


まず、Azureのダッシュボードで「PostgreSQL」と検索すると、PostgreSQLのリソースが出てきます。


そこで「追加」を選び、入力項目を適当に埋めて「作成」を実行すると、数分経ってPostgreSQLのリソースが作成されます。

■PostgreSQLに接続する


リソースが作成されただけだと外部から接続ができないので、「設定」→「接続のセキュリティ」から接続を許可するネットワークを指定します。


すると、外部からでもpsqlで普通に接続できるようになりました。

[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)

ちなみに、Azureで「サーバー管理者ログイン名」として作成しているユーザは、PostgreSQLで言うところのスーパーユーザーではありませんので注意が必要でしょう。まぁ、PostgreSQLのDBaaSはどこも同じだと思いますが。

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)
Collationは「C」ではなかったので、この辺は注意が必要かもしれません。

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)

■EXTENSIONを見てみる


さて、PostgreSQL と言えば EXTENSION です。

というわけで、最初からセットアップされている EXTENSION と、追加で使える EXTENSION を確認してみました。

以下は最初からインストールされている EXTENSION です。

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)

以下は CREATE EXTENSION を実行すれば使える EXTENSION です。プレビューだからか、ちょっと少な目な感じでしょうか。個人的には PL/Python などが使えるといいのになぁと思います。

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)

ちなみに、スーパーユーザー権限ではないユーザでCREATE EXTENSIONできるんだっけ?というのを確認してみたのですが、一応、CREATE EXTENSIONできるようです。

postgres=> create extension hstore;
CREATE EXTENSION
postgres=> create extension postgis;
CREATE EXTENSION
postgres=> create extension pg_trgm;
CREATE EXTENSION
postgres=>

■AzureのPostgreSQLリソースの設定項目


ざっと見た感じ、以下のような設定ができるようです。

サーバパラメータ

監視できるメトリックス

サーバログ

■まとめ


というわけで、ざっと Azure Database for PostgreSQL を見てみました。

このエントリは、Azure Database for PostgreSQL を使い始めるところから1時間弱くらいで書き上げているわけですが、DBaaSは楽だなぁと改めて感じました。

一方で、PostgreSQLの良さの一部には、アプリケーション開発における拡張性や柔軟性の高さがあったりするわけですが、この辺りをDBaaSでどこまで実現できるのかというのが(サービス提供側の)難しさなのかなぁ、という気もします。(まぁ、使える EXTENSION とかは決めの問題のような気もしますが。)

Azure Database for PostgreSQL はDBaaSとしては後発だと思うので、後発ならではの工夫や特徴があるともっと面白くなりそうだなぁと感じました(MADlibが使える、とか)。

では、また。

技術文書「PostgreSQL 10 Beta1 新機能検証結果」が公開されました

$
0
0
少し前の話になりますが、みなさんお馴染みとなりつつある日本HP篠田さんから PostgreSQL 10 beta1 の資料が公開されました。
今回、私も事前レビューに参加させてもらいました。

本ドキュメントは全体で100ページ以上あり、以下のような構成になっています。

1. 本文書について
2. バージョン表記
3. 新機能解説
3.1 PostgreSQL 10における変更点概要
3.2 パーティション・テーブル
3.3 Logical Replication
3.4 パラレル・クエリーの拡張
3.5 アーキテクチャの変更
3.6 モニタリング
3.7 Quorum-based同期レプリケーション
3.8 Row Level Securityの拡張
3.9 SQL文の拡張
3.10 パラメーターの変更
3.11 ユーティリティの変更
3.12 Contribモジュール
参考にしたURL

正直、私も PostgreSQL 10 の機能はほとんど追えていないのと、このドキュメントも100ページ以上あってきちんと理解するのはそれなりに大変なわけですが、そんな人のために「3.1 PostgreSQL 10における変更点概要」という数ページのセクションを用意していただきました。

まだPostgreSQLにそれほど詳しくない方、理解に時間をかけられない方でも、この数ページをざっと読んでおくだけでも、それなりに理解できるのではないかと思います。

最近、篠田さんは本家のGitのコミットログまでほぼリアルタイムに読むようになっているようで、開発内容のキャッチアップの速さと正確さは「梵天丸もかくありたいが多分無理」というレベルに達しつつある気がします。

そんな著者による解説ですので、ぜひこの機会に目を通しておくことをお勧めします。

では、また。

【告知】9月9日(土)に関西DB勉強会で講演します

$
0
0
9月9日(土)に関西DB勉強会で講演します。

「PostgreSQLエンジニアにとってのデータ分析プロジェクト:テクノロジーとその実践(仮)」というタイトルで、ここ3~4年のテクノロジーやスキル、経験、そこからの学びなどをごった煮でお送りする予定です。他にも面白そうなセッションが目白押しとなっております。

既にキャンセル待ちとなっておりますが、都合の付く方はぜひご参加ください。私からのトークだけではなく、いろいろな方と意見交換をできればと思っています。

よろしくお願いします。

[翻訳] たった一つの設定変更が如何にしてクエリのパフォーマンスを50倍も改善したか (How a single PostgreSQL config change improved slow query performance by 50x)

$
0
0
先日、「How a single PostgreSQL config change improved slow query performance by 50x」というPostgreSQLのSSD環境でのチューニングの記事を見つけたのですが、これをTweetしたらRTやLikeを比較的たくさん頂きました。 日本でも興味を持つ方がいるかもと思い、オリジナルの著者の方に許可をもらったので翻訳したものを対訳形式で掲載します。

オリジナル版と併せて、よろしければご覧ください。

■How a single PostgreSQL config change improved slow query performance by 50x
■たった一つの設定変更が如何にしてクエリのパフォーマンスを50倍も改善したか


Pavan Patibandla

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.

Amplitudeでの我々のゴールは、簡単に使えるインタラクティブなプロダクト分析を提供することです。それによって、すべての人が自分たちのプロダクトについての疑問の答えを得ることができるようになります。素晴らしいユーザエクスペリエンスを提供するために、Amplitudeは答えを迅速に提供する必要があります。顧客から Amplitude UI でイベントプロパティのドロップダウンリストのロードに時間がかかると苦情が来たため、その調査を開始しました。

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.

さまざまなレベルで遅延を追跡した結果、ある特定のPostgreSQLのクエリが完了するのに20秒もかかっていることを突き止めました。これは我々にとって驚くべきことでした。というのも、JOINしている両方のテーブルとも、結合しているカラムにインデックスを作成してあったからです。

Slow Query

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.

このクエリの実行プランは想定外のものでした。両方のテーブルにインデックスがあるにも関わらず、PostgreSQLは大きいテーブルに対してシーケンシャルスキャンとハッシュ結合を選択していました。大きなテーブルへのシーケンシャルスキャンは、クエリの実行時間の大部分を占めていました。

Slow Query Execution Plan

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!

最初に私はフラグメンテーションを疑いました。しかし、データを調査した結果、当該テーブルは追記のみであり、削除はあまり発生していませんでした。領域を解放するVACUUMは助けにならなかったため、さらに深い調査を始めました。次に、同じクエリをレスポンスタイムの良い他の顧客のデータで試してみました。すると驚いたことに、クエリの実行プランは全く違ったものになったのです!

Execution plan of similar query on another App

Interestingly, app A only accessed 10x more data than app B, but the response time was 3000x longer.

興味深いことに、アプリAはアプリBと比べて10倍のデータにアクセスしているだけなのに、レスポンスタイムは3,000倍も長くかかっていました。

To see the alternative query plans PostgreSQL considered before picking Hash Join, I disabled hash join and reran the query.

PostgreSQLがハッシュ結合を選択する前に作成する異なるクエリプランを見てみるために、ハッシュ結合を無効にしてクエリを再度実行してみました。

Alternative execution plan for Slow Query

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?

やりました! ハッシュ結合ではなくネステッドループ結合を使った場合に、同じクエリが50倍も速くなりました。なぜ、PostgreSQLはアプリAで性能の悪い実行プランを選択したのでしょうか?

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.

両方の推定コスト(estimated cost)と実行時間(actual run time)を詳細に見てみると、推定コストと実行時間の比率が大きく異なっていることが分かります。この食い違いの犯人は、シーケンシャルスキャンのコスト推定の部分です。PostgreSQLは、シーケンシャルスキャンの方がインデックススキャンよりも4000倍以上もコストが低いと推定しましたが、実際にはインデックススキャンの方が50倍も速かったのです。

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.

このことから、私は random_page_cost と seq_page_cost の設定について考え始めました。PostgreSQLのデフォルトでは random_page_cost には 4 、seq_page_cost には 1 が設定されています。これはランダムアクセスの方がシーケンシャルアクセスよりもコストが高いハードディスクに合わせてチューニングされています。しかし、これらのコストは、我々がデプロイしている(AWSの)gp2 EBS ボリューム(SSDです)においては正確ではありません。我々のデプロイメント環境では、ランダムアクセスとシーケンシャルアクセスの性能はほぼ同じです。

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.

私は random_page_cost を 1 に設定してクエリを再度実行してみました。この時には、PostgreSQLはネステッドループ結合を採用し、クエリは50倍速く実行されました。この設定を変更した後、我々のPostgreSQLの最大のレスポンスタイムは著しく短縮されました。

Overall Slow Query performance improved significantly

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.

もし、あなたがSSDを使っていて、PostgreSQLをデフォルトの設定で使っているのなら、 random_page_cost と seq_page_cost をチューニングしてみることをお勧めします。大きなパフォーマンス改善に驚くかもしれません。

Has any other parameter tuning given you huge gains across the board? Let us know about it in the comments.

他に何か全体的なパフォーマンスの大きな改善につながったパラメータチューニングをご存知でしょうか? コメント欄で教えていただければと思います。

以上です。

最後で触れられているコメントについては、(英語で)オリジナル版の方にお願いいたします。

Hacker Newsのコメントも面白いと思いますので、併せてどうぞ。


では、また。

Dockerを使ってデータ分析用にPostgreSQLを使ってみる

$
0
0
これは PostgreSQL Advent Calendar 2017の Day3 の記事です。昨日はMorihayaさんの「DB Management tool新時代の幕開けか!? OmniDBを評価させていただく!」でした。

さて、最近ようやくDockerに触り始めたのですが、使い方が少しずつ分かってきたのでいろいろと遊んでいます。

今回は、In-Database AnalyticsとDockerです。

■全部入りのDockerイメージを作ってみた


最近、In-Database Analyticsがマイブームになっていますので、ボチボチと遊んでいます。

いろいろ遊んではいるのですが、いろいろセットアップしたり変更したり、アレが足りない、コレが動かない、みたいなことをやっているのが面倒になってきていました。面倒さが原因で手が動かないことも。。

これはいかん。

というわけで、データ分析に使えそうなExtensionをいろいろと入れ込んだ(自分的な)全部入りのPostgreSQLのDockerイメージを作ってみました。
  • CentOS 7
    • Python 2.7
  • PostgreSQL 10.1
    • PL/Python
    • postgres_fdw
  • PL/R 8.3.0.17
    • R 3.4.2
  • Apache MADlib 1.13-dev
  • pg_bigm 1.2
  • mecab 0.996
    • mecab-ipadic 2.7.0
    • mecab-python 0.993
  • numpy / scipy / scikit-learn / pandas / matplotlib
おかげで1.7GBもあるイメージになってしまいましたが、まぁそこはご愛敬、ということで。

とりあえず、Rのグラフィック関連のパッケージはゴリゴリと削ってたのですが、numpyとかscipyとかが案外大きいんですね。。無理して削った関係上、動かない機能があったらすみません。連絡いただければ修正する所存です。。

■Dockerコンテナを起動してPostgreSQLに接続する


まぁ何はともあれ、まずは使ってみましょう。起動するコマンドは以下です。

docker pull uptimejp/postgres4analytics
docker imagesを叩くとこんな感じになります。

# 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
#

docker run -p 5432:5432 -ti uptimejp/postgres4analytics

簡単ですねー。イメージが大きい関係上、pullに少し時間がかかります。

Dockerコンテナを起動するとPostgreSQLのバックエンドが起動している様子が分かります。

# 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".

ここで別のターミナルからPostgreSQLに接続します。

ユーザ postgres で、データベース template2 に接続します。

この template2 データベースが全部入りをセットアップしたデータベースになります。(template1 には MADlib をうまくセットアップできなかったためです・・・) もちろん postgres はスーパーユーザーですが、その辺は気にしない方向で。

$ 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=#

■各種Extensionを見てみる


というわけで、セットアップされたExtension諸々を見てみます。

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=#

こんな感じで、いくつかのExtensionがあらかじめセットアップされています。

まず PL/R。

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=#
おぉ、動いてるっぽい。

続いてApache MADlib。

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=#
1.13-devが入ってますね。

次。なぜか勢いで入れてしまったpg_bigm。

template2=# select show_bigm('進捗は如何ですか。');
show_bigm
-------------------------------------------------------
{"。 ",か。,すか,です,は如,何で,如何,捗は,進捗,"進"}
(1 row)

template2=#
きちんと動いてます。ところで進捗は如何ですか。

次、みんな大好きPythonもPostgreSQLで動きます。ここでは以下のようにMecabを使って形態素解析をするUDFを作って動作確認して見ます。

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;

動かしてみます。

template2=# select mecab_tokenize('進捗は如何ですか。');
mecab_tokenize
---------------------------
{進捗,は,如何,です,か,。}
(1 row)

template2=#
動いてます。進捗は如何ですか。

こんな感じで、このDockerイメージを使うと、「聞いたことはあるけれど使ったことは無い」ものが一瞬(?)で使えるようになります。

あと、postgres_fdwなどもセットアップされていますので、リモートにあるPostgreSQLのテーブルをそのまま取ってくる、みたいなことも簡単に(?)できます。できるはずです。

■Dockerfile


ちなみに、Dockerfileは以下から入手できます。
こういうの追加したい、みたいなのがあれば、forkしていじってみるなり、プルリクしてみるなりして頂ければと思います。

■まとめ


そんなわけで、全部入りPostgreSQL(しかも最新版)を作ってみた、というお話でした。自分も、今後はこれを使っていこうと思っています。

Happy In-Database Analytics Lifeを!

では。

PostgreSQL Advent Calendar 2017、明日はvidaisukiさんです。

Oracle対応アプリケーションのDockernize事始め

$
0
0
本エントリはJPOUG Advent Calendar 2017 Day6の記事です。

普段はPostgreSQLのブログなのですが、今回はスピンオフ企画(番外編)として、先日のJPOUGのイベント「JPOUG in 15 minutes #6」で発表した「Oracle対応アプリケーションのDockernize事始め」の内容をブログエントリとしてお送りします。(Oracleネタを書くブログが無いので・・・)

なお、資料は公開していますので、興味のある方はそちらも併せてどうぞ。

■なぜ今さら「Docker」か、という前口上


既にDockerに十分触れている方、慣れている方には釈迦に説法になるかと思いますが、なぜ最近になってDockerに着目して使うようになったのか、ということからお話しようと思います。

私がDockerを使い始めたのは、実は先日のJPOUGのイベントでのセッションが決まってからです。なので、片手間に触り始めてからまだ数ヶ月と言ったところです。

ずっと興味は持っていたのですが、自分が普段使っている環境が長いことRHEL6/CentOS6系だったこと、Dockerでどうしても使ってみたいことが無かったので、なかなか手を動かして試してみるところまで到達しませんでした。

そういう状況ではあったのですが、春先に自分が開発しているツールをオープンソースとして公開したあたりから、少し状況が変わってきました。
オープンソースとして開発する際、さまざまなフレームワークやライブラリに依存した開発をすることがあります。そうなると、動作させるためにはそれらを整えた環境を構築する必要があります。

しかし、環境構築も、自分が慣れている技術スタックならまだしも、不慣れなスタックを整備するのは技術的のみならず心理的障壁も高くなります。また、特にデータベースエンジニアや基盤系エンジニアの方だと、「不要なものは自分の環境に入れたくない(環境を汚したくない)」という心理も働くと思われ、そうなると公開したツールを試してもらうのもなかなか難しくなります。(簡単に試せないものを紹介する側も気が引けますし)

というわけで、環境をカプセル化して簡単に試してもらうにはDocker化してみるのがいいのでは、と考えたのが、今回Dockerに触ってみる動機になったのでした。

■HecatoncheirのDocker化


というわけで、HecatoncheirのDocker化を試みます。

Hecatoncheirはデータベースから情報を収集してレポジトリに保存するクライアントツールと、レポジトリの情報を公開するサーバから構成されています。

そして、それらのツールはさまざまなPythonライブラリと、Oracle DatabaseのクライアントライブラリであるOracle Instant Clientに依存しています。

これらをDocker化し、コマンド一発で数分以内に利用できるようにするところまでを目指します。

目標は以下の通りです。
  • コマンド一発で数分で利用可能に
  • 収集したレポジトリのデータは残せるようにする(永続化可能にする)

■Dockerイメージ化する


イベントの時とは説明の順序が違いますが、Dockerイメージ化してみます。イメージ化するためにはDockerfileと呼ばれる定義ファイルを作成します。このファイルで、Dockerイメージに何を含めるかを決めます。

プレゼンの際には抜粋したDockerfileでしたが、全体は以下のようになっています。

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 .
また、Dockerfileの作成・更新は、以下のような流れで行っていました。

今回はベースイメージをCentOSにしましたので、Dockerfileは最低限以下の内容から始まります。

FROM centos:7
MAINTAINER Satoshi Nagayasu <snaga@uptime.jp>

docker buildでイメージが作成できて、docker run /bin/bash で起動や動作に問題がないことが確認できたら、少しずつコンテンツを追加していきます。

今回イメージを作成する際、最初はOracle Instant ClientのRPMファイルをローカルに置いてrpm -iしていたのですが、Githubで管理する場合には50MB以上のファイルは置けないため、s3にファイルを置いてHTTP経由で直接rpm -iするように切り替えました。

#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
の部分になります。

■Dockerイメージを使ってデータベースプロファイリングをする


というわけで、作成したDockerイメージを使ってみます。

今回、DockerイメージはDocker Hubに登録してありますので、まずはそちらをdocker pullで取得します。


Pulling a docker image from Satoshi Nagayasu on Vimeo.

次に、クライアントを動かして、Oracle Databaseに接続してデータベースのプロファイリングを行います。


背景が水色の部分はDockerのコマンドとオプション、白い部分はHecatoncheirのコマンドとオプションです。

最終的には、ホストの /tmp/docker というディレクトリ(Dockerコンテナ内では /docker ディレクトリ)に repo.db というファイルが作成され、プロファイリングした結果がそこに保存されます。


Running data profiling from Satoshi Nagayasu on Vimeo.

■プロファイリングしたデータを閲覧する


最後にサーバを起動して、レポジトリの情報を閲覧可能にします。

ブラウザ経由で、データの状態を確認することができます。



Running the repository server from Satoshi Nagayasu on Vimeo.

■まとめ


以上、今回はOracle Databaseに対応したツールをDocker化する方法についてご紹介しました。

冒頭でも紹介しましたが、オープンソースのツールを使う/使ってもらう場合、依存関係が多くなり、環境構築がハードルになるケースが多々あります(慣れている人であっても)。

特に、HecatoncheirのようにさまざまなDBMSに対応したツールを開発する場合、クライアントライブラリなども含めると依存関係が多岐に渡るため、Dockerは非常に便利であると感じました。

ぜひ、データベースエンジニアにも新しいツールやソフトウェアを積極的に試してみていただければと思います。もちろん、Hecatoncheirも使ってみていただけると嬉しいです。

では、また。

PostgreSQLのデータをPandasのデータフレームとして読み書きする

$
0
0
最近、JupyterやPandasを使ってデータを処理する機会が増えてきました。

とは言え、手元のデータはPostgreSQLに溜まっていたり、あるいはSQLで処理したい、ということがよくあります。

というわけで、Jupyterを使っている時に、「PostgreSQLからデータを取り出して、Pandasやら何やらでいろいろ処理した後、結果をPostgreSQLを書き出す」というユースケースを想定して、その方法を調べてみました。

■やりたいこと


やりたいことは、PostgreSQLのデータをJupyter上でPandasのデータフレームとして読み込み、集計やデータ分析をした結果をPostgreSQLに書き戻す、ということです。

データの加工や整形など(データ前処理)はPostgreSQLの方が高速に行えるのでSQLで、複雑なアルゴリズムの適用はPythonで行いたい、そしてその結果をPostgreSQLに書き戻して利用したい、というケースを想定しています。

あるいはPostgreSQLのデータをmatplotlibを使って可視化したい、といった場合にも使えるでしょう。(この場合は書き戻しは必要ありませんが)

■必要なもの


必要なものは以下の通りです。

  • PostgreSQL
  • Jupyter
  • pscycopg2
  • SQLAlchemy
PostgreSQLからDataFrameにデータを読み込むだけならpsycopg2だけで実現可能なのですが、DataFrameのデータをPostgreSQLに書き戻すにはSQLAlchemyが必要になります。

つまり、最初からSQLAlchemyを用意しておいた方がいいでしょう。

■PostgreSQLに接続する


まず、PostgreSQLに接続する必要があります。

Python(つまりJupyter上で)必要なモジュールのインポートは以下の通りです。

from sqlalchemy import create_engine
import pandas as pd

そして、SQLAlchemyのcreate_engine()関数を使ってPostgreSQLのエンジンオブジェクトを作成します。

engine = create_engine('postgresql://postgres:postgres@localhost:5432/postgres')

■PostgreSQLのデータをPandasに読み込む


PostgreSQLのデータをPandasのDataFrameに読み込むには、read_sql()メソッドを使います。

以下の例では、pg_databaseテーブルの内容をDataFrame dfに読み込んでいます。

df = pd.read_sql("SELECT * FROM pg_database", engine)

このメソッドを一回呼び出すだけで、PostgreSQLのデータをDataFrameに取り込んで扱うことができます。

■PandasのデータをPostgreSQLに書き出す


DataFrameを使っていろいろ処理した結果をPostgreSQLに書き出すには、DataFrameのto_sql()メソッドを使います。
Pandasのドキュメントにも書かれていますが、SQLAlchemyを使う場合には、SQLAlchemyによってサポートされているDBMSで書き出しを行うことができます。そうでない場合には、SQLiteのみがサポートされます。よって、PostgreSQLに書き出したい場合には、SQLAlchemyを使う必要があります。

以下の例では、DataFrame dfの内容をt1テーブルに書き出しています。その際、既にt1テーブルが存在した場合には、そのテーブルを置き換えるように指定しています。(if_exists引数)

df.to_sql('t1', engine, if_exists='replace')
その結果は以下の通りです。

to_sql()メソッドにはif_exists以外にもいろいろなオプションがありますので、ぜひ確認してみていただければと思います。

■まとめ


以上、非常に簡単ではありましたが、Python(というかJupyter)からPandas(DataFrame)を介してPostgreSQLのデータを読み書きする方法をご紹介しました。

データベースエンジニアの目の前には、さまざまなデータ、多くのデータが横たわっています。

私自身は、PL/PythonやMADlibを始めとするIn-Database処理が好みではありますが、一方でPandasで扱うデータの前処理をPostgreSQLで実現できたら便利だなぁ、と感じることも多々あるのが現実だったりします。

ぜひ、さまざまなツールを知ることで、それらのデータを活用していただければと思います。

では、また。

この連休の読書にオススメの一冊「SQLパフォーマンス詳解」(割引コードあり)

$
0
0
最近、久しぶりにPostgreSQLのクエリチューニングをしていたのですが、その過程で「この本はぜひもっと多くの人に読んでもらいたい」と改めて思い出した一冊がありました。

それは、「SQLパフォーマンス詳解(原題:SQL Performance Explained)」という本です。
パフォーマンスチューニング、特にクエリチューニングについて説明する場合、その前提となる知識は広範なものになります。

そのため、自分が頑張って説明するよりも、優れたエキスパートのまとめたコンテンツを活用させてもらう方が、質・量ともに優れたインプットにしていただけるのではないか、と思うのです。

また、この「SQLパフォーマンス詳解」は非常に良い本であるにも関わらず、一般の出版社から出ているわけではないため、それほど積極的にプロモーションされているわけではなく、日本語版についても、(残念ながら)一般的な書籍ほど話題になることが無いように思います。

そういった理由により、本エントリではこの本について皆さんに知っていただくべくご紹介するとともに、著者のMarkus Winand氏から日本の読者の皆さんに「最大で半額」となる割引コードを提供いただけることになりましたので、その使い方についてご紹介したいと思います。

ゴールデンウィーク直前ですが、ぜひ連休中に読む一冊に加えていただければと思います。データベースのパフォーマンスについて、網羅的かつ本質的な理解が深まること、間違いのない一冊です。

■著者のMarkus Winand氏について


著者のMarkus Winand氏は、PostgreSQLを始めとするRDBMSのチューニングのエキスパート/コンサルタントとして有名な方で、「Use the Index, Luke!」というブログでお馴染みです。

データベースのパフォーマンスチューニングに興味のある方であれば、一度は目にしたことのあるブログではないかと思います。

また、以前、UberがPostgreSQLからMySQLに移行したというニュースが流れた時に、「On Uber’s Choice of Databases」というブログを書かれたことでも有名です。
このエントリは、当ブログでも翻訳して紹介させていただきました。



また、PostgreSQLのグローバルカンファレンスである「pgcon」にも度々登壇しており、パフォーマンスチューニングのエキスパートの観点から、さまざまな知見をコミュニティで共有しています。私も一度、pgconで氏の講演を直接聞いたことがあります。

このように、著者のMarkus Winand氏は、データベースのパフォーマンスについて、理論と実践のいずれの面から見ても第一人者の一人であると言えます。

■「SQLパフォーマンス詳解」について


「SQLパフォーマンス詳解」は、データベースのパフォーマンスについて、主にインデックスの観点から解説した本です。(目次については後述します)

もともとは英語の書籍だったのですが、少し前に日本語に翻訳されて、現在は日本語版を読むことができます。

対応するRDBMSとしては、主にOracle、MySQL、PostgreSQL、SQL Serverについて記載されています。

個人的には、本書は特に
  • 自分のWebアプリケーションのデータベースアクセスのパフォーマンスについて詳しく理解したい開発者の方
  • RDBMSのパフォーマンスやインデックスについて、網羅的かつ実戦的な知識を得たいDBエンジニア
  • システムの性能分析やデータベースのパフォーマンスチューニングを担う基盤系エンジニアやDBA
  • 今まで使ってきたRDBMSと違うRDBMSを使うことになった(なりそうな)DBエンジニア
といった方々にオススメの一冊だと思います。

というか、データベースのパフォーマンスやインデックスの設計方法について考える時、個々のRDBMS製品に特化した書籍は多少はあるかもしれませんが、ここまで網羅的かつ横断的に解説されている本を、私は他に見たことがありません。

前述した通り、著者のMarkus Winand氏はRDBMSのパフォーマンスおよびチューニングのエキスパートです。そんなエキスパートの知見と経験が詰まった一冊になっていますので、ぜひ読んでみていただければと思います。

なお、私は最近、本書に書かれていた知見を活用してSQLチューニングをした結果、とあるクエリのパフォーマンスが10万倍に改善しました。

■「SQLパフォーマンス詳解」目次


以下が「SQLパフォーマンス詳解」の目次になります。

如何に網羅的かつ実戦的な情報がまとまっている一冊であるか、目次を見るだけでも理解していただけるのではないかと思います。
  • 前書き ............................................................................................ vi
  • 1章 インデックスの内部構造 ............................................................... 1
    • インデックスリーフノード
    • 検索ツリー (Bツリー)
    • 遅いインデックス パートI
  • 2章 where句 ...................................................................................... 9
    • 等価演算子
    • 関数
    • パラメータ化クエリ
    • 範囲検索
    • 部分インデックス
    • OracleにおけるNULL
    • 処理しにくい条件
  • 3章 パフォーマンスとスケーラビリティ ............................................ 79
    • データ量がパフォーマンスに与える影響
    • システム負荷がパフォーマンスに与える影響
    • 応答時間とスループット
  • 4章 結合処理 .................................................................................... 91
    • 入れ子ループ
    • ハッシュ結合
    • ソートマージ
  • 5章 データのクラスタリング ........................................................... 111
    • フィルタ述語の意図的な使用
    • インデックスのみのスキャン
    • 索引構成表
  • 6章 ソートとグルーピング .............................................................. 129
    • インデックスを使ったorder by
    • ASC、DESCとNULLS FIRST/LAST
    • インデックスを使ったgroup by
  • 7章 部分結果 .................................................................................. 143
    • 最初のN行のみの選択
    • 次ページの取得
    • ページネーションのための窓関数の使用
  • 8章 データの変更 ........................................................................... 159
    • 挿入
    • 削除
    • 更新
  • A. 実行計画 ................................................................................. 165
    • Oracle
    • PostgreSQL
    • SQL Server
    • MySQL
  • 索引 ............................................................................................ 193

■割引コードの使い方


著者のMarkus Winand氏に「かくかくしかじかにより、SQLパフォーマンス詳解を紹介したい(ダジャレではありません)」と相談したところ、日本語版の読者に割引コードを発行していただけることになりました。

割引コードは「GW2018INDEX」です。

この割引コードは、
  • PDF版は 50% OFF (9.95ユーロ → 5.00 ユーロ)
  • 印刷版は 5% OFF (29.95ユーロ → 26.95ユーロ)
  • PDF+印刷版は 14% OFF (34.95ユーロ → 30.00 ユーロ)
  • 有効なのは 直販サイトのみ。
  • 有効期限は 5/10 まで
となっています。

使い方は、書籍の直販サイトに行き、

「割引コードを入力」をクリックして、割引コードを入力するだけです。


割引コードを入力すると、上記の割引価格が表示されますので、そのまま購入することができます。

■まとめ


本エントリでは、PostgreSQLのパフォーマンスチューニングにおいて参考にしていただきたい書籍、「SQLパフォーマンス詳解」についてご紹介してきました。

パフォーマンスチューニングに必要なのは、「RDBMSの仕組みについての知識」、「問題の合理的な切り分けのスキル」、「解決策の引き出しの多さ」なのではないかと思っています。そういった観点で見ると、本書はそれらを網羅的かつ実践的に得られる、他に類書が見当たらない非常に稀な一冊であると思います。

割引コードを使うと、特にPDF版は50% OFFという破格な値段になりますので、データベースのパフォーマンスチューニングに興味のある方には、これを機会にぜひ一度読んでみていただければと思います。

連休中に読み切れる分量だと思いますし、読んだ後、長期に渡って活用できる知見を得られる、絶対に後悔しない一冊になると思います。連休中の読書&スキルアップに、ぜひ活用いただければと思います。

では、また。

Python版dblinkでデータベース連携をもっと「自由」に

$
0
0
本エントリは、 PostgreSQL Advent Calendar 2018の Day1 のエントリです。

エントリを書くのは実に半年以上ぶりなのですが、今回は以前から試してみたかったdblinkネタをお届けします。

■なぜ今さら「dblink」?


PostgreSQLには、PostgreSQL、あるいは異種DBMSのデータベース連携を実現する手段として、dblinkとForeign Data Wrapper (FDW) が提供されています。
最近の方向性としては、FDWを充実させていくのが一般的な認識かと思います。

しかし、実際にデータベース連携を実現していく中で、FDWでは対応が困難なシーンがあります。
  • FDWを使って外部テーブルを実装する際に設定すべき項目が多い。
  • FDWは、VIEWのようにクエリを定義(固定)して使うため、アドホックな(もしくは動的に変わる)クエリのリモート実行ができない。
  • FDWのAPIは年々複雑化しており、もはや普通の開発者が気軽に拡張できるレベルでない。
  • そもそも、Cで開発できる or したい人はもうそんなにいない。
というわけで、このエントリではdblink(のサブセット)をPL/Pythonで再実装し、それを他のDBMSに対応させるために拡張する、ということを試みます。

PostgreSQLの強みのひとつは「拡張性の高さ」です。そのため、その「拡張性の高さ」を最大限に活かす実装を目指します。

なお、このPL/Pythonによるdblinkの再実装を、本エントリでは便宜上「dblink/py」と表記します。

■dblink/pyの実装方針


dblink/pyは以下の方針で実装します。
  • PostgreSQLのextensionとする。
  • APIはcontribのdblinkのサブセットとする。
  • 各DBMSへの接続、操作はPythonのDatabase APIに対応したドライバを活用する。
  • 気軽に拡張を行えるようにシンプルな実装に留める。
  • まずはPostgreSQLへのリモート接続機能を実装し、その後SQLiteへ拡張する。
このようにすることで、PL/Pythonによるdblinkのリファレンス実装とすることにしました。

■実装するdblink/pyのAPI


さて、dblink/pyで再実装するAPIを定義します。今回実装するAPIは以下の通りです。

以下は、リモートデータベースに接続してカーソルを操作するためのAPIです。
  • dblink_connect
  • dblink_disconnect
  • dblink_get_connections
  • dblink_open
  • dblink_fetch
  • dblink_close
以下は、カーソル操作ではなく、リモートのデータベースに接続してひとつのSQLを実行して接続を閉じる、という処理を行うAPIです。
  • dblink
  • dblink_exec
まずは、これくらいを実装できれば、リモートデータベースへの最低限の操作はできるだろう、という目論見です。

■dblink/pyの初期実装と動作確認


dblink/pyのコードは以下のレポジトリにあります。
初期実装(PostgreSQL接続のみサポートしたもの)は「release-pg」というブランチになっていますので、まずはこれをチェックアウトしてインストールします。


[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]$

インストールが完了したら、動作確認として2つのデータベース「testdb」と「testdb2」を作成して、dblinkを使って「testdb」を介して「testdb2」のデータを読み書きしてみます。

まず、データベースを作成します。

[snaga@devvm06 dblink_py]$ createdb -U postgres testdb
[snaga@devvm06 dblink_py]$ createdb -U postgres testdb2

次に「testdb」に接続して、dblink/pyをインストールします。


[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

次に、「testdb」から「testdb2」に接続していることを確認します。

testdb=# select * from dblink('postgresql://localhost/testdb2', 'select current_database()', true) as (dbname text);
dbname
---------
testdb2
(1 row)

次に、CREATE TABLE ASを使ってtestdb2上にテーブルを作成し、そのテーブルの内容を読んでみます。

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)

最後に、このテーブルがtestdb上には作成されておらず、testdb2上に作成されていることを確認します。

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)

以上で動作確認は完了です。(カーソルの動作確認はここではしてませんが、リグレッションテストには含まれていますので、 sql/dblink_py.sqlexpected/dblink_py.outを確認してみてください。)

■dblink/pyのSQLiteへの拡張


さて、動作確認ができたら、次はSQLiteに対応するようにdblink/pyを拡張してみます。

SQLite対応の拡張をしたコードは「release-sqlite」ブランチにあります。

dblink/py本体のコード(つまりテストコードを除く)に関しては、PostgreSQLのみ対応のバージョン(release-pgブランチ)と比べると、17行追加、2行削除されていることが分かります。

[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]$

つまり、この「プラス17行、マイナス2行」だけでSQLite対応が完了したということです。詳細な差分は以下を参照してください。
さて、それではSQLiteのデータベースに読み書きしてみます。

まず、/tmp/testdb3.db というSQLiteデータベース(ファイル)を想定して、テーブルを作成します。


testdb=# select dblink_exec('sqlite:///tmp/testdb3.db', 'create table t ( uid integer, uname text)', true);
dblink_exec
-------------
OK
(1 row)

次にそのテーブルが空であることを確認して、1件INSERT、再度SELECTしてレコードが挿入されたことを確認します。

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)

最後に、testdbデータベースにはテーブルが「作成されていないこと」を確認して、dblink/pyを通して読み書きしていたSQLiteデータベース /tmp/testdb3.db の内容を確認します。

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>

以上で、dblink/pyのSQLite対応の拡張とその動作確認は完了です。

■まとめ


というわけで、今回はdblinkの機能のサブセットをPL/Pythonで再実装し、それを他のDBMSに拡張する、ということを試してみました。

本家のcontribのdblinkはコードが3,000行以上ある大規模なモジュールですが、今回作ったdblink/pyのコードは200行以下です。SQLite対応の拡張に至っては、わずか20行以下のコードの変更で実現できています。PostgreSQLの拡張性をうまく活かすことで、これくらいの労力でさまざまな拡張ができる、ということがお分かりいただけたかと思います。

そんなこんなで今回いろいろやってきましたが、お伝えしたかったことは
  • 「FDWが無くてもdblinkを使ってVIEWを定義してしまえば見た目はFDWとだいたい一緒や」
  • 「シンプルに行こう。Less is more.」
ということです。

PostgreSQL Advent Calendar 2018、明日の担当は・・・なんと、未定です(本エントリ執筆時点)。

Advent Calendarが初日で終わってしまうのか。それとも奇跡が起きて継続されるのか。ドキドキハラハラが止まらない。刮目せよ。

では。

機械学習ライブラリApache MADlibで決定木を使ってKaggleのTitanicを解く

$
0
0
この記事は PostgreSQL Advent Calendar 2018のDay20の記事です。昨日19日は U_ikki さんによるPostgreSQL 12の新機能の話でした。

以前から興味はあるのだけれどなかなか手を付けられなかったものの中に「Kaggleにチャレンジしてみる」というものがありました。

「趣味はKaggleを少々嗜んでおりまして」とか言ってみたい。

そんなことをずっと考えていたのですが、最近ようやくKaggleデビューしました。

本エントリではPostgreSQLで使える機械学習ライブラリであるApache MADlibを使って、Kaggleの「チュートリアル」と言われているTitanicの問題を解いてみます。

■Kaggle Titanicとは


Titanicは、Kaggle初心者のために準備されているチュートリアルの問題(Competition)のことで、以下のページから参照できます。
簡単に言うと、

「タイタニックで生き残った乗客と亡くなった乗客を記録した訓練用データ(トレーニングデータ)があり、その乗客の属性情報などを元に予測モデルを作成し、予測用データ(テストデータ)に掲載されている乗客が生き残るかどうかを予測し、その予測精度を競う」

というものです。

インターネット上には初心者向けに取り組み方を解説したページもいろいろとあり、私は以下のページを参考にまずは「Python + Jupyter」で実装してみました。
その結果は以下のJupyter Notebookにまとめてあります。
この内容を踏襲して、今回はPostgreSQLとApache MADlibでTitanicの予測を実装してみます。

■訓練用データと予測用データをPostgreSQLにロードする


Titanicの訓練用データと予測用データがCSVファイルで提供されていますので、まずはこれをPostgreSQLにロードします。

ほとんどのカラムが整数、文字列、浮動小数点のいずれかになるかと思います。

最初に訓練用データをロードします。このテーブルのSurvivedカラムが、その乗客が生存したかどうかを示すフラグになります。

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=#

次に予測用データをロードします。こちらは作成した予測モデルを使って予測をするためのデータですので、訓練用データにあったSurvivedカラムはありません。このSurvivedフラグを予測するのが今回の目的になります。

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=#

■訓練用データを使って予測モデルを作成する


ここまで出来たら、MADlibの決定木を使って予測モデルを作成してみます。

Pythonで実行する際に参考にした こちらの入門記事ではデータの前処理を行っていますが、ここでは敢えて前処理無しでモデルを作成してみます。

MADlibの決定木の使い方は以下から参照できます。
学習用データからモデルを作成するためには tree_train() 関数を使います。

必須の引数は以下の通りです。
  • training_table_name 学習用テーブル名
  • output_table_name 作成したモデルの出力テーブル名
  • id_col_name 学習用データのレコードを特定する識別子のカラム名
  • dependent_variable 従属変数(結果)のカラム名
  • list_of_features 独立変数のカラム名のリスト
それでは、これらの引数を指定して予測モデルを作成します。作成したモデルは titanic_model というテーブルに保存されます。


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=#

■作成した予測モデルとテストデータを使って予測をする


次に、今作成した予測モデルと、先ほどロードしておいたテストデータを使って予測を行ってみます。

予測をするには tree_predict() 関数を使います。

必須の引数は
  • tree_model 予測モデルを保存したテーブル名
  • new_data_table テストデータを保存してあるテーブル名
  • output_table 予測結果を出力するテーブル名
となります。


titanic=# SELECT madlib.tree_predict(
titanic(# 'titanic_model',
titanic(# 'titanic_test',
titanic(# 'titanic_predict');
tree_predict
--------------

(1 row)

titanic=#

予測結果のテーブルを見てみると、乗客IDと生存したかどうかを予測した出力(estimated_survived)がペアで出力されています。


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=#

■予測結果をKaggleに投稿して予測精度を確認する


それでは、最後に予測結果をCSVファイルにエクスポートして、Kaggleに投稿して予測精度を確認してみます。

CSVファイルのカラムヘッダが passengerid と survived である必要があるため、カラム名にエイリアスを指定してCSVファイルにエクスポートします。


titanic=# \copy (select passengerid, estimated_survived survived from titanic_predict) to 'titanic_predict.csv' with (format csv, header true)
COPY 418
titanic=#

エクスポートしたCSVファイルをKaggleのTitanicのページの「Submit Predictions」から投稿すると、自分の予測精度と予測精度のランキングが表示されます。


今回の場合だと、予測精度は79%弱、ランキングは3571位(本エントリ執筆時点)だったようです。

なお、今回Pythonで解くときに参考にした scikit-learn を使った入門記事では、チューニング前の予測精度は71%程度だったようですので、それと比べてもなかなか良い結果が出ているのではないかと思います。

今回は細かなパラメータのチューニングを行いませんでしたが、MADlibのマニュアルを見ていただくと、さまざまなパラメータを指定できることがお分かりいただけるかと思います。興味のある方は、ぜひモデル作成時のパラメータをチューニングして試してみていただければと思います。

■まとめ


以上、簡単にではありましたが、Apache MADlibを使ってTitanicを解く手順をご紹介しました。

データベースの中にあるデータに対して直接機械学習を活用できるというのは、さまざまな可能性があるように思います。

機械学習についてはさまざまな参考書が出ていますし、Pythonで使えるscikit-learnの解説などもたくさんあります。MADlibに興味を持った方は、そういった参考資料をMADlibのマニュアルと読み比べながら、いろいろと試してみていただければと思います。

では。

PostgreSQL Advent Calendar 2018、明日21日の担当は tom-sato さんです。contribモジュールについて書いていただけるようです。お楽しみに!

カラムナーDB拡張 cstore_fdw とその性能評価

$
0
0
本エントリは PostgreSQL Advent Calendar 2018の Day24 の記事です。

昨日の記事は @kabaomeさんによる 拡張統計情報とテーブル結合でした。

本エントリでは、PostgreSQLのカラムナーDB拡張である cstore_fdw について、その基本的な使い方から、 DBT-3 のスキーマとクエリを使ってベンチマークをしてみた結果を解説してみます。

とは言え、私自身、cstore_fdw をそれなりに使ったのはこれが初めてですので、あまり深く踏み込めていないところもあるかと思いますが、そういったところがありましたら、コメント欄や Twitter などで補足いただけると助かります。

■cstore_fdw とは


cstore_fdw は Citus Data によって開発されているオープンソースの PostgreSQL 拡張で、PostgreSQL 本体に手を加えなくてもカラムストア型(カラムナー型)の備えたテーブルを利用することができるようになるものです。
cstore_fdw では、テーブルを外部テーブルとして定義することによって、 PostgreSQL のオリジナルのストレージ構造をバイパスして、独自のストレージフォーマットを持つテーブルを保持することができるようになっています。

■cstore_fdw のセットアップ


cstore_fdw のインストールの手順は、他のエクステンションや FDW と変わりません。Github から clone してきて、 make install します。

# git clone https://github.com/citusdata/cstore_fdw.git
# env USE_PGXS=1 make install

実際にデータベースに組み込む前に postgresql.conf の shared_prealod_libraries に cstore_fdw を設定しておく必要があります。

shared_preload_libraries = 'cstore_fdw' # (change requires restart)

■cstore_fdw の基本的な使い方


それでは、 cstore_fdw を実際に使ってみましょう。ここでは cstore_fdw のドキュメントにあるサンプルをベースに進めます。

まず、Citus で提供しているサンプルの CSV ファイルをダウンロードして準備します。

$ wget http://examples.citusdata.com/customer_reviews_1998.csv.gz
$ gzip -d customer_reviews_1998.csv.gz

次に、データベースに cstore_fdw のエクステンションをインストールし、FDW 用のサーバの設定を行います。

CREATE EXTENSION cstore_fdw;
CREATE SERVER cstore_server FOREIGN DATA WRAPPER cstore_fdw;

そして、通常の PostgreSQL のテーブルとカラムストアのテーブルをそれぞれ作成します。

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');

先に言及したように、 cstore_fdw では FDW の仕組みを使うことによって PostgreSQL のストレージをバイパスしてカラムストアのテーブルを実現していますので、外部テーブル(FOREIGN TABLE)として作成します。

そして、そのカラムストアのテーブルの実体は、ここでは /tmp/customer_review.cstore となります。「compression 'pglz'」はカラムストアのテーブルを圧縮する、というオプションです。

テーブルの作成が終わったら、通常のテーブルと同じように COPY コマンドを使って先ほどの CSV ファイルをそれぞれのテーブルにロードします。

COPY customer_reviews FROM '/tmp/customer_reviews_1998.csv' WITH CSV;
COPY customer_reviews_cstore FROM '/tmp/customer_reviews_1998.csv' WITH CSV;

データのロードが終わったら、それぞれのテーブルに ANALYZE を実行します。

ANALYZE customer_reviews;
ANALYZE customer_reviews_cstore;

さて、おもむろに count(*) を実行して、実行コストと実行時間を見てみましょう。

通常のテーブルに対して SELECT COUNT(*) を EXPLAIN ANALYZE で実行したのが以下の結果です。

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)

同様に、カラムストアのテーブルに対して同じクエリを実行したものが以下の結果になります。

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)

見ると分かる通り、カラムストアのテーブルでは実行コストが 1/3 程度になり、実行時間も半分強になっています。

このように、カラムストアのテーブルでは(クエリの内容によっては)実行時間を短縮することができます。

■DBT-3 を使った性能評価


さて、最後に DBT-3 のスキーマとクエリを使って性能を比較してみます。(DBT-3 はデータ分析系のワークロードの性能評価するためのオープンソース実装のツールです。)

cstore_fdw のベンチマーク結果はネット上でもいくつか見つかるのですが、そういった情報は総じて「速くなったクエリだけを抜き出して性能をアピール」しているものが多く、そういった評価はユーザの立場から見て必ずしも十分な情報とは言えないように思います。

DBT-3 ベンチマークには 1 ~ 22 までの 22 種類のクエリがありますので、それらをすべて実行して比較することで、さまざまなクエリにおける性能を確認するとともに、全体の傾向などを把握したいと思います。

評価に使った環境は以下の通りです。
  • ThinkPad X Carbon
  • VirtualBox (VM 2GB RAM, 1CPU)
  • CentOS 7
  • PostgreSQL 10.6
  • DBT-3 (SF=1, DBサイズ 約1GB)
  • DBT-3 のクエリ 22 種 + データロード
なお、 DBT-3 のデータベースの作成は以下のレポジトリのコードを、
実行するクエリは、以下のレポジトリのコードを使いました。
また、測定のルールは以下の通りです。
  • データローディングについては、通常/カラムストア、それぞれ 1 回だけ計測。
  • その他のクエリについては、 6 回計測し、最初の 1 回を除いた 5 回分を平均。
  • (時間の都合上)クエリの実行時間の上限を 10 分に設定し、それを越えたクエリは無効とする。
上記の環境、条件で実行時間を計測した上で、通常のテーブルの処理性能を「1」とした時に、カラムストアのテーブルの処理性能を「倍率」で示したものが以下のグラフです。


通常のテーブルに対するカラムストアのテーブルの処理性能の倍率ですので、グラフは高くなっている方が、よりカラムストアの方が高速であることを示しています。

これを見ると、50 倍近く速くなっているクエリもある一方で、10 倍以下だったりするクエリもあります。

なお、グレーアウトされているクエリの「17」と「20」は、通常テーブルおよびカラムストアの両方が 10 分のタイムアウトを越えたため、無効となっています。

上記のグラフをもう少し細かく見てみたのが以下のグラフです。(縦軸の最大値を10、つまり 10 倍に設定)


これを見ると、通常のテーブルと比べてカラムストアの方が遅くなっている(1倍を割っている)のは、
  • ローディング
  • クエリ5、クエリ7、クエリ16、クエリ19
であり、これら以外は通常テーブルとほぼ同じくらいか、あるいは高速化していることが分かります。

(データロードを除いた)クエリ全体を平均すると、カラムストアのテーブルの方が通常のテーブルよりも約 5.17 倍高速、という結果になりました。

■まとめ


以上、駆け足になりましたが、 cstore_fdw の使い方、および DBT-3 を使ったカラムナー型テーブルの性能を見てきました。

カラムナーテーブルは、あらゆるクエリが高速化されるものではありませんが、今回見てきたように、アナリティクス系のクエリでは高速化される可能性が高くなります。

ぜひ、PostgreSQL でアナリティクスの世界に挑戦してみていただければと思います。

では、また。

PostgreSQL Advent Calendar 2018、明日の担当は kitayama_tさんです。

tablelog extension を使ってDB移行に必要なテーブルの更新差分のログを取得する

$
0
0
先日開催されたPostgreSQLアンカンファレンスで tablelog という extension の話をしたのですが、本エントリでは改めてその紹介をさせていただこうと思います。

■DB移行やメジャーバージョンアップの時、、、


皆さんは、
  • システム更改によるDB移行
  • PostgreSQLのバージョンアップ
  • 特定のテーブルだけ別インスタンスにコピーしたい
といったことをしたい場合に、どのように対処しているでしょうか?
  • その方式は?
  • ツールは何を使う?
  • ダウンタイムは?
  • DBaaSの場合はどうする?
場合によって変わってくるかと思いますが、皆さんはどのように対処しているでしょうか?

もっともシンプルな方法は Dump & Restore だと思いますが、データベースの規模が大きくなってきた状態だと非常に時間がかかる場合があり、単純な Dump & Restore だと数日間データベースを停止しなければならない、といった見積もりになることもあります。

■更新差分だけを取得・適用して追い付きたい


そういう状況で次に考えるのは、「データベースを一旦コピーしておいて、後から更新差分だけ適用して追い付きたい」という方式です。

以下の図で言うと、
  • 「インスタンスA」から「インスタンスB」に「テーブル1」を移行しようとする場合に、
  • ① 稼働系のデータベースに通常の更新処理が行われている間、
  • ② 更新処理をログとして取得しておき、
  • ③ 更新が行われているかどうかに関わらずデータベースをコピーし、
  • ④ 稼働系のデータベースへの更新を一時的に停止して、
  • ⑤ 蓄積しておいた更新ログを新しいデータベースに適用して最新化し、
  • ⑥ 新しいデータベースを稼働系にする切り替えを行う
という流れです。


この時必要とされる機能は、②の「更新処理をログとして取得しておく」という機能になります。

「更新処理のログ」というのは、要するにINSERT/UPDATE/DELETEに関する情報のことで、
  • どのようなレコードをINSERTしたのか?
  • どのレコードをどのようにUPDATEしたのか?
  • どのレコードをDELETEしたのか?
といった情報を蓄積できれば、後からいろいろ活用できるはず、と言うことになります。

■PostgreSQLで更新処理のログを取得するには


PostgreSQLで更新処理のログを取得するには、現時点では大きく2つの方法があります。「Logical Decoding」を使う方法と「テーブルトリガ」を使う方法です。

Logical Decodingを使う方法は新しくてPostgreSQLらしい洗練された方式ではあるのですが、バージョンや稼働環境を選ぶのと、PostgreSQLエンジニア以外に使ってもらうにはちょっとハードルが高くなるかもしれません。

なお、Logical Decodingについては、以前書いた以下のエントリを参考にしてください。
テーブルトリガを使う方法は、昔からある伝統的な方法で特に新しくも無いのですが、誰でも簡単に理解できて(ツールさえあれば)使える方法であると言えるでしょう。

実際、以下のようなエントリを書いたことがありました。
但し、上記のエントリで使った tablelog は C 言語で実装されていてビルドやサーバへのデプロイが必要なため、今日的な DBaaS 環境では使うことができません。

そのため、改めて(DBaaS環境でも)テーブル更新差分を取得することができる extension を作ることにしました。

■tablelog pl/v8版


さて、先に要件だけ書き出してしまうと、今回は以下のような要件で開発しました。
  • DBaaS環境で利用できること(PLなんちゃら、で実装されていること)
  • モダンな言語で実装されていること
  • extension としてパッケージングされていること
というわけで、とりあえずは RDS for PostgreSQL と Azure Database for PostgreSQL をターゲットとして pl/v8 で実装することにしました。

実は、「extension としてパッケージング」と「DBaaS環境で利用できること」は背反する条件だったりするのですが(extensionはサーバにデプロイが必要なので)、この点については今回は別の方法で逃げることにしました。

pl/v8 版の tablelog extension は以下のレポジトリから取得できます。
なお、pl/pgsqlではなくpl/v8を使ったのは、トリガーの内部で受け取ったレコードのカラム名を取得する時に、連想配列のキーとして取得できるためです。

new_cols = Object.keys(NEW);
old_cols = Object.keys(OLD);
のようなカラム名の取り出し方は pl/pgsql では実現できないのですが、pl/v8(やpl/perl)だとこれが可能なので、トリガー関数を汎用的に作成することができるようになります。

■tablelogのインストール


詳細は README に記載していますが、スタンドアロンのPostgreSQLの場合は create extension コマンドで、

env USE_PGXS=1 make install
psql -c 'create extension plv8' dbname
psql -c 'create extension tablelog' dbname

DBaaS の場合はSQLファイルを編集して(\echo行をコメントアウトして) psql コマンドで流し込んでください。

vi tablelog--X.X.sql
psql -c 'create extension plv8' dbname
psql -f tablelog--X.X.sql dbname

■tablelogの使い方


まず、テーブルを作成して、tablelog_enable_logging() 関数でログ取得を有効化します。

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=#

するとテーブルにトリガが設定されます。

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=#

この状態でテーブルのレコードを更新(INSERT/UPDATE/DELETE)すると、 __table_logs__ テーブルにログが記録されます。

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=#

UPDATE/DELETEでも同様に記録されます。

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=#

ログテーブルの構造の詳細については README を参照してください。

■まとめ


というわけで、本エントリではテーブルの更新差分をトリガーで取得するための汎用的なextension「tablelog」を紹介しました。

データベースで扱うデータ量が膨らむ一方の現在、アプリケーション更改におけるデータベース移行や、データベースのメジャーバージョンアップによるデータベース移行など、さまざまなところで「ダウンタイムを最小化してデータベースを移行したい」というニーズは高まる一方のように思います。

一方で、DBaaS環境に代表されるように、さまざまな制約の中でこういった移行作業を実現しなければならない現実もあります。

ぜひ、本エントリで紹介した tablelog のようなツールを使いこなして、DBaaS環境であっても、安全、確実、かつ最小のダウンタイムで実現していただければと思います。

では。

Jupyter+Pandasを使ったPostgreSQLパフォーマンス分析

$
0
0
本記事は PostgreSQL Advent Calendar 2019の1日目の記事です。初日から遅れ気味ですすみません。。

久しぶりの記事ですが、最近はPostgreSQLをゴリゴリと触る感じでもなくなってきているため、本記事もゆるめの感じでお送りしたいと思います。

■PostgreSQLの「パフォーマンス分析」とは


PostgreSQLのパフォーマンス分析は、ざっくり言って、以下のようなステップで進められます。(PostgreSQLには限らないと思いますが)
  • パフォーマンスの状況から、課題について仮説を設定する。
  • パフォーマンスに関連する何の情報を収集するかを決める。
  • 情報を収集する。
  • 収集した情報を加工し、分析しやすい形式に整える。
  • 分析し、仮説を検証、ないしは何かを発見する。
  • より深堀り、確証を高めるために、再度情報集をしたり、データを加工、分析したりする。
  • 何か対策を打って、その結果を再度分析して、従前と比較する。
ある程度PostgreSQLに詳しい人は、「仮説の設定」や「どこから情報を取得するか」はさくっと決められると思いますが、情報を収集したり分析に適した形に加工したりするのはそれなりの手間と時間がかかるものです。

しかも、ある程度探索的な分析を行わざるを得ないため、データやグラフの形式を最初から決めておいたとしても、パフォーマンス分析がディープになればなるほど、それが現実的に役に立つかどうかは微妙になっていきます。(なので、最終的には、あらゆるグラフを網羅的に出力するレポートを作成する羽目になります)

そのため「Excel最強説」が流れるわけですが、再現可能性や繰り返しの作業についてはやはりいろいろと難がありますので、スクリプトで処理したくなってきます。

■「Jupyter + Pandas」を使ってパフォーマンス分析


探索的かつスクリプトでのデータ処理、と言えばJupyterの出番でしょう。数値データをあれこれ捏ねたり捻ったりするのであればPandasも使うと便利そうです。

というわけで、JupyterとPandasを使ってpg_stat_statementsのデータを分析してみようと思います。

今回の目的としては、
  • 複数時点のpg_stat_statementsのデータから、パフォーマンスが悪化しているクエリを抽出したい。
  • それ以外にもいろいろな分析ができるようにしておきたい。
あたりを設定したいと思います。

実現するまでの全体の流れとしては、
  • pg_stat_statementsのデータを時間をおいて複数回取得する。
  • その時のタイムスタンプも取得する。
  • 取得したデータをPandasのDataframeに読み込んで分析をする。
としたいと思います。

今回使用したJupyter Notebookは以下にあります。

■データ収集


まず、データを収集します。

収集するのは、pg_stat_statementsビューの内容と、タイムスタンプ(CURRENT_TIMESTAMP)の情報です。

psqlコマンドを使って、

select now();
select * from pg_stat_statements;

として取得します。

取得したデータは、psqlで取得するテキスト形式のまま、以下のようにPythonの変数としてノートブック上に貼り付けます。ぺたり。


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" | | | | | | | | | | | | | | | | | | |
"""

何度か取得し、変数名を変えて貼り付けます。

■PandasのDataframeに変換する


次に、これらのテキストの出力をPandasのDataframeに変換します。(pgss_to_df関数の実装についてはノートブックを参照してください)

今回は、pgbenchを何度か実行し、その間に3回ほどpg_stat_statementsビューの情報を取得して使っています。

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'])

すると以下のようなDataframeの形式になります。

■特定のクエリの平均処理時間の推移を見る


さて、Dataframeの準備ができたところで、実際にデータを見てみましょう。

まず、特定のクエリの平均処理時間の推移を見てみます。(find_by_query関数の実装についてはノートブックを参照してください)


find_by_query(pgss, 'UPDATE pgbench_tellers')


mean_timeのカラムを見ると、pgbench_tellersテーブルを更新するクエリの平均処理時間が、少しずつ長くなっていることが分かるかと思います。(13.18ミリ秒→13.30ミリ秒→13.31ミリ秒)

■平均処理時間が10%以上悪化したクエリを抽出する


次に、平均処理時間が10%以上悪化したクエリを抽出します。

まず、最初と最後のタイムスタンプのレコードを、それぞれmin/maxとして、queryidカラムとmean_timeカラムを抽出します。

# 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']]


次に、これらをqueryidで結合し、最後の処理時間が最初の処理時間に比べて10%以上長くなっているクエリを抽出します。


# 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]


そして、最後にクエリ文字列と結合します。

# join query strings using the queryid
df.set_index('queryid').join(pgss.loc[:,['queryid','query']].set_index('queryid')).drop_duplicates()


これで、処理時間が10%以上伸びているクエリを抽出することができるようになりました。

■まとめ


以上、今回はJupyterとPandasを使って、PostgreSQLのパフォーマンス分析のためのデータ加工や簡単な分析を行ってみました。

今回着手するに当たって、最初はmatplotlibを使った可視化なども考えていたのですが、実際にパフォーマンス分析の実務を考えると、安直な可視化よりは基本的なデータ操作、オペレーションを関数にまとめておくことの方が、生産性の観点ではより役に立つのではないかと思うようになりました。(もちろん、トレンド分析などにおける可視化の役割は否定するものではありませんが)

実際のパフォーマンス分析の現場では、さまざまなデータの加工や集計作業が必要になります。一旦Pandasに取り込んでおけば、さまざまなデータ処理を簡単に行うことができ、いろいろ便利に使えるのではないかと思います。(Pandasを使いこなせる人であれば、という条件付きですが・・・私はPandasは初心者です・・・)

今後は、今回のような処理をうまくwrapするモジュールやクラスに整理して、Jupyterからimport一発で簡単に使えるようなものを目指してみようと思います。

では、また。

PostgreSQL Advent Calendar 2019、明日の担当は @yuina1056さんです。お楽しみに。
Viewing all 94 articles
Browse latest View live