Jubatusでラーメンレビューの点数推測

我が家に新しい猫が来ました。にゃー。

ということで、うちのPCにJubatusを入れてみました。
機械学習フレームワークということで、インストールから含めてなんかやたら難しげな印象がありましたが、
インストール自体はyumって終わりだった。意外と簡単。

使う場合は、各種言語のクライアントライブラリを別にインストールして、それを使ったプログラムを組まなきゃいけないけど、最近はjubash(うちの会社のJubatusエキスパートが開発したものらしい)という対話型クライアントがあるので、まずこれを使ってみることに。
ただ、Pythonに慣れていないので、Jubashを使えるようになるまで、ちょっと躓いたけど。

やってみること

手元にある、ラーメンデータベース内の結構な量のラーメンのレビューを使ってみよう。
まず、レビューの点数、レビューの本文を抜き出してみる。
レビューの点数は0点から100点までの1点刻みになっているが、今回は5点単位に集約して、0, 5, 10, ..., 100 に分類する。

で、レビューの点数と、レビュー本文(Mecab形態素解析して特徴抽出)を組として事前に学習させ、その後で別のレビュー本文のみを与えて、スコアを推測(正確にはclassifyで分類)させる。
といっても、今回は最初だから、細かいチューニングはなし。

やってみる

Jubatusサーバを分類機能(classify)で起動する。
起動時の設定ファイルは以下のようなJSONファイルを指定する。
(このファイルは、QiitaにあったJubatusでテキストに含まれる特徴語の傾向を学習し、入力テキストをカテゴライズするを参考にした。

{
  "method": "AROW",
  "parameter": {
    "regularization_weight": 0.001
  },
  "converter": {
    "num_filter_types": {
    },
    "num_filter_rules": [
    ],
    "string_filter_types": {
    },
    "string_filter_rules": [
    ],
    "num_types": {
    },
    "num_rules": [
    ],
    "string_types": {
        "bigram":  { "method": "ngram", "char_num": "2" },
        "mecab": {
          "method": "dynamic",
          "path": "libmecab_splitter.so",
          "function": "create"
        }
    },
    "string_rules": [
        { "key": "*", "type": "mecab", "sample_weight": "bin", "global_weight": "idf" }
    ]
  }
}
  • Jubatusサーバを起動する。
[nuko@localhost jubatus]$ jubaclassifier -f rdb.json 
2016-05-22 07:15:46,379 28285 INFO  [server_util.cpp:376] starting jubaclassifier 0.9.0 RPC server at 192.168.9.143:9199
    pid                  : 28285
    user                 : nuko
    mode                 : standalone mode
    timeout              : 10
    thread               : 2
    datadir              : /tmp
    logdir               : 
    log config           : 
    zookeeper            : 
    name                 : 
    interval sec         : 16
    interval count       : 512
    zookeeper timeout    : 10
    interconnect timeout : 10
(中略)
2016-05-22 07:15:46,395 28285 INFO  [server_helper.hpp:226] start listening at port 9199
2016-05-22 07:15:46,396 28285 INFO  [server_helper.hpp:233] jubaclassifier RPC server startup

サーバが起動したので、さっそく学習データを jubash に食わせてみる。

train 点数 "レビュー本文"

こんな感じのフォーマットで10000件ほどのラーメンレビューの点数と、レビュー本文を組にして学習させる。
例えば、数年前に俺がレビューしたもの(http://ramendb.supleks.jp/review/300073.html)だと、

train 70 "【入店状況】1954入店。先客4〜50名程、後続も数名あり。【注文】胡麻味噌ラーメンセット(1080円)。セットには半ライス、焼売3つ、ザーサイが付く。【待ち時間】7分。【麺】中程度の太さ、軽い縮れを残した麺。茹で加減は普通。麺の量も普通というところ。【スープ】少し甘みのある味噌スープ。といっても味噌の風味は控えめで、代わりに胡麻風味が強く感じられる。これはこれで悪くない。ご飯にも良く合う味のスープになっている。【具】チャーシュー、もやし&ニラ炒め、コーン、海苔。チャーシューは煮豚タイプで脂少なめ・固めの食感。あまり好みのチャーシューではない。もやしとニラは軽く炒めてある。結構、具の量も多い。【感想】胡麻味噌風味で少し変わった味わいの一杯だったが、なかなか悪くなかった。ご飯&焼売のセットをつけて、おかずラーメンとして食べたのも良い選択だったかもしれない。ご飯も一緒に食べたこともありボリュームも十分。食堂のラーメンとしては頑張っているのではないかと思えた。【備考】肉味噌ラーメンはメニューからなくなり、この胡麻味噌ラーメンに変更されたようだ。"

こんなふうにtrainコマンドを使う。
これをざっと10000件ほど登録しておく。

やってみた

登録させた状態で、登録したものと別のレビュー本文を与えて、学習結果からどの点数に分類されるか試してみる。
例えば、別レビュー(http://ramendb.supleks.jp/review/290716.html)のレビュー本文から、点数の分類をやってみる。

classify review "【入店状況】1345入店。先客なし(客だと思ったのは店員だった)。 【注文】ジャージャー麺(750円) 【待ち時間】6分。 【麺】細目の麺。茹で加減は少し柔目。麺の量は普通。もう少し固めに仕上げてあ るとなお良かったかもしれない。 【具】肉味噌、胡瓜、葱。肉味噌はやや緩めに仕上げ てあり、普通のジャージャー麺とジャージャースープ麺との中間形態になっている。肉味噌の量は十分に多く、丼になみなみと盛られている。肉味噌内の具は挽肉と刻み玉葱。味付けの強さは中庸。胡瓜と刻み葱は食感に適度な変化をあたえてくれた。 【感想】なか なか悪くない一杯。ジャージャー麺にも色々な形態があるものだ。この一杯はまさに肉味噌が主役。この肉味噌の味付けなら、もう少し太目の麺でも十分受けきれるだろうと思えた。"
70: 0.142693147063
60: 0.116121843457
55: 0.109682038426
50: 0.0359025709331
40: 0.01305016689
25: 0.0105530405417
20: 0.00953553896397
30: 0.00441248342395
5: 0.00240551866591
0: 0.0016572214663
45: -0.00519017456099
35: -0.01147844363
75: -0.013936586678
85: -0.0146971540526
10: -0.0233904644847
95: -0.0392095260322
65: -0.0501153841615
90: -0.0509259775281
100: -0.0881003141403
80: -0.120291739702

すると、点数と類似度(-1.0から1.0の値域)が出力される。
この結果から、類似度の値が最も高いものの分類(この場合だと70)だと判断できる。
おお、実際のレビューの点数合ってるな!

ということで、他の自分の過去レビューを何パターンかやってみた。

レビュー対象 実際の点数 推測された分類(上位3件)
ジャージャー麺 70 70, 60, 55
ラーメン 70 70, 60, 0
皆楽みそラーメン 80 70, 65, 60
五目そば 65 75. 70, 65
まぜそば 75 80, 60, 70

うーん、思ったより正解の分類にはならないものだな。
日本語の表現で、「不満はない」などの書き方だと、自分ではポジティブに書いたつもりでも、ネガティブな結果になりやすいのかもしれない。日本語は難しい。ある程度はフレーズを意識しないと精度の高い学習自体難しいのか。

自分のレビューはほぼ定型化されているのだけど、他人のレビューからの推測はどうだろう・・・?
他の人のレビュー結果を適当にサンプリングして再試行してみる。

レビュー対象 実際の点数 推測された分類(上位3件) 備考
ウルトラタンメン 79 60, 85, 80  
ラーメン(ヤサイ) 80 80, 85, 60 非常に文章が短いケース
ラーメン海苔増し味濃いめ半ライス 70 70, 80, 55  
豚骨らーめん(ネギ多め) 90 70, 60, 55  
燈郎+無料トッピング(ヤサイマシ) 81 80, 85, 90  

うーむ、こちらも意外と推測結果がブレるな・・・。4番目のケースなどはかなり高いスコアではあるのだが。
やはりもうちょっと、Mecab methodを使う場合のチューニングが必要なのかなあ。
もう一つのチューニングとしては、ユーザIDを学習データに組み込むことか。
結構、ユーザによってスコアの付けかた、平均点って異なるので、その傾向を加味しないといけないかも。

大訂正

  • classifyの見方を間違っていた!
    • 絶対値云々は間違いかも。
    • 正値の上位3件で集計しなおした。

Neo4jユーザーグループ勉強会 #7 に行ってきたよ

ということでNeo4jユーザーグループ勉強会 #7 に行ってきましたよ。

今回は恵比寿ガーデンプレイスタワーにあるクラウドワークスさんで開催。
久々の恵比寿です。
前回、恵比寿に行ったのって、岩合光昭氏の写真展「ネコライオン」を観に行った時以来かなあ。

最初に注意が

  • データの注意点・・・?
  • かなり個人情報に触れるデータを使っているので配布資料はなし。
  • デモ用の?データはCSVだけでなく、Neo4jフォーマットでの配布もある!

パナマ文書の概要

  • パナマ文書、わくわくw
  • PanamaPaper特集・・・と言いながら、Graph Connect Europeイベントには実は誰も出席していないとw
    • その代わりビデオは一通り見たと。
  • 15分、20枚のスライド。
  • Mar Cabraさん@ICIJがデータ分析PJのトップ。
  • 以前はオフショアリークス、という分析を以前行った。
  • パナマ文書のサイズ:2.6TB 21万社の企業。
  • 3人でパナマ文書のデータ解析をやった。
  • パナマ文書からわかったことの一部
    • ターゲット。政治家、政治家の親族(ここがグラフデータベースの強み)
    • 世界中の大きな銀行が関わっている。英国の不動産業者も。
  • ICIJでこの情報をどう扱っているのか。
    • Oxwall(OSSのジャーナリスト間のデータ共有ソフトウェア)
  • Neo4jで扱っているのは、Database format、300万ファイル。
    • これだけ多いのでまだ未分析のものも多い。
  • 前処理
  • オフショアリークの時にはSQL ServerJavascriptで可視化していた。
  • パナマ文書のときには、Neo4j+LINKURIOUSを採用
  • 前処理としてETLを実施。talendを使用。
  • 出来上がったデータ
    • 950000 node, 1.2million edge (4GBデータ)

v3.0発表

  • Creationlineの李さんから説明。
V2までのおさらい。
  • RDBMSとグラフデータベース
    • 定義はエンティティ。しかし現実のデータ処理はレコードレベルの処理、このため大変。
  • Neo4jの歴史
    • CypherQueryが入ったことがやっぱり大きいと思うな。
  • バルクデータ処理もできるよー!
  • SQLとCpypherとの対応の話。Cypher全文検索とトリガ、プロシージャが未実装なのが課題なのだよなー。
コミュニティ版とエンタープライズ
3.0の話
  • Scale and Performance
    • データ数制限が実質上なし?(340億ノード)
    • Dynamic poinnter compression.
  • インデックスフリーの隣接性改善
    • pointer hop という技法でエッジなしでもトラバース可能にする?これでウルトラ高速化!とのこと。
  • オプティマイザ改善
    • 2.0まではWriteはRuleベースだった。
    • 3.0からはRead/WriteもCostベース
  • 開発者向け機能
    • 公式SDK。3.0 Bolt+Langage Driver. JavaだけでなくPython等も。
    • Bolt + Java Stored Procedure
      • 例えばMySQLに接続してJDBC経由で情報を取得してNeo4jにデータが投入できる。
    • Neo4j Browser Sync.
  • Docker上でもクラウド上でも使えるように。
  • 座標情報関数のサポート。つまりGis対応はじまた?
  • 最短経路(shortestPath)改善。
  • 3.0への移行
    • 3.0はJDK8対応。
    • 旧バージョンからの移行はそんなに大変ではない。
    • configure fileが1つに統合された。このへんは要注意。

パナマ文書:データ分析作業の話。

  • 4種類のCSVになっていて、それをNeo4jにロード可能。
  • なお、Neo4jフォーマットでも入手できる。
  • パナマの法律事務所(Mossack Fonseca)がデータ元。
    • そこがハッキングされて外部流出している(と事務所側は主張している)。
    • ドイツの新聞社がまずそれを受け取った。
      • 新聞社で独自に解析。Nuixでメタデータ抽出を試みたが失敗。
  • そこでICIJと協業することに。
  • ETL/Neo4jでデータベースか、Linkriousで可視化。5/9にデータ公開。211MB、840Kノード、1.27Mエッジ。
  • これは2ndPlatform側
  • ノードの抽出
    • クライアント、企業、住所、役員(企業、個人)
    • 属性情報(ドキュメント番号、日付等)
  • 関係柄の抽出
    • 直接的なものは、データベースそのもの見れば分かる。
    • 間接的なもの:同じ住所、メール、似たような名前。
  • 今後、ICIJで拡張を考えていること。
    • イメージ、PDF、Eメール
      • 例えばEメールからの関係の分析
    • 外部情報源の参照(国際調査情報やSNS情報)
    • 企業活動・キャッシュフロー分析
  • 1st Platform(イメージ、PDF等)側の分析。
    • 個人企業と関連ドキュメントの関係、人間関係の関係、企業登記情報
    • ここが機械化できない。自動化できている2nd Platformと連携。
パナマ文書の話
  • 今はパナマからバハマなど別の国にオフショアする動きになっている。
  • MF社使ったTax Havenの国はパナマだけじゃない。実は米国内の州も可能。
  • 仲介している銀行。香港、スイス、イギリスなど。
  • パナマ文書に載ってる会社=Tax Havenを使っているわけではないよ!そこは留意してねとのこと。

ETL(Talend)の話

  • Talendはオープン版、商用版の2つがある。
  • 日本の顧客、いろいろ使っている。DATAやCOMでも使っている。
  • ICIJでも使っていた。
  • 某キャリアでのNW機器情一元管理・・・どこなんだろう・・・。
  • システム移行
  • Talendは単なるETLではないよ。データの統合連携基盤だよ。
  • 無償版と有償版の違い
    • 無償版はデスクトップ向け、有償版はサーバ上、運用監視など。
  • Neo4jとあんまり関係なかったけど、Talendは調査しておかないといけないかな(社畜的に)
Neo4jを使用したパナマ文書データ検索デモ
  • クリエーションラインの木内さん。
  • デモに使ったデータは加工・匿名化してる。
  • 4種類のノード
    • Ofiicer(役員), Address(住所), Entity(オフショア会社・個人), Intermediary(仲介業者)
Officer ----> Addeess
  |  |
  |  +------------+
  |               |
  o               o
Entity <---- Intermediary
  • ETL済み、配布ファイルは210MB、展開後470MB
  • プロパティは実はディスク上に置かれている。ノードとエッジをオンメモリに。
    • インデックスもオンメモリに。
    • これ、知らんかった。基本、ノードとエッジがオンメモリだから探索が高速なのか。
  • CSVロードしちゃうと時間めっさかかるよー。なぜならクエリ内で変換しちゃうから。
  • リレーションはMATCH/MERGE文を使って手動でつけないといけない。エッジが多いととてつもなく時間がかかる。
  • LOAD CSVの説明
  • neo4j-importはLinuxコマンドとして実行。データベースと、CSV群を指定。
  • クエリ例
    • 日本人は何人いる? (2317人)
      • もっとも日本人ではない名前もあるし、逆もしかり。
  • パナマ文書で明らかになった人数は。
  • ある人間からリレーションをたどるデモ。
  • Officerが一杯・・・しかも同一住所。
  • 香港にEntityが何社あるか。
    • ただし、struck_off_date (活動停止)の会社も含む。
    • じゃあ、アクティブな会社は?
  • 香港のとある地区の活動中の会社役員の日本人の数は?
  • あるノードからあるノードを辿りつつ条件を指定できるのがCypherQueryの特徴。
    • 何らかの理由で繋がっている日本人の組

恒例:Neo4jじゃんけん大会

  • Neo4jTシャツと、Neo4j水ボトル、Neo4jガラス、Cpypher本、シール10枚
    • 残念、今回は勝てなかった。

懇親会

PostgreSQL 9.6 - 手元の環境でpgbenchった

手元の貧弱な環境でPostgreSQL 9.6 beta1とPostgreSQL 9.5のpgbench結果を比較してみた。
ついでに、今リリースされている、9.6 beta1 RPMに --enable-cassert ついてんじゃね?みたいな話を某所で聞いたので、--enable-cassert の有無によって、どのくらいpgbench結果に影響が出るのかも見てみることにした。

環境

PostgreSQL

  • 以下、全てソースビルド版

pgbench設定

  • 初期化時
    • -s 10 --unlogged-table -F 90
  • 実行時
    • -c 2 -T 30
    • これを3回連続測定して平均値をとる

結果

バージョン 平均tps
9.6 beta1 2757.322
9.6 beta1 cassertあり 1845.260
9.5.2 3331.687


  • おやあ・・・この環境だと明らかにPostgreSQL 9.6 beta1のほうがtpsが落ちている
    • beta1なので、まだ性能改善の余地がたっぷりあるのか。
    • コア数が少ない貧弱な環境だと、9.6のほうが不利になるのか。
  • 当たり前だけど、 --enable-cassert を付与するとめっさ性能に影響でるのね。
  • 一応、9.6のpgbenchのクエリの実行計画も見てみたけど、基本、IndexScanしかないので max_worker_degree がデフォルト値(2)であっても、別に実行計画が変わるわけじゃない。
    • ただ、Planing Timeが9.5よりも、微妙に大きくなっているような・・・max_worker_degree = 0 にするとPlaning Timeが多少改善されたりするのかな?

PostgreSQL 9.6 - みんな大好きpsql

ということで、今日はみんな大好き psql の9.6改造項目について、実際に動かして試してみましたよ。
psqlがあればGUIクライアント要らないよねw
なお、今回のエントリ、ちょい長いです。

複数のコマンド指定、複数のコマンドファイル指定

リリースノートの記述。

Support multiple -c and -f command-line options (Pavel Stehule, Catalin Iacob)
To allow this with sane behavior, one backwards incompatibility had to be introduced: -c no longer implies --no-psqlrc.

要するに、-c コマンド文字列 や -f コマンドファイル名 が複数記述可能になったっぽい。
試してみよう。

[nuko@localhost ~]$ psql postgres -c "SELECT pg_backend_pid()" -c "SELECT pg_backend_pid()"
 pg_backend_pid 
----------------
          12699
(1 row)

 pg_backend_pid 
----------------
          12699
(1 row)

どうやら、この挙動を見る限りでは、複数回 -c オプションで指定したときには、同じセッション内で動作するっぽいですね。

じゃあ、次は複数の-fオプション指定。
こんな2つのファイルを用意する。

[nuko@localhost ~]$ cat /tmp/file1.txt 
BEGIN;
SELECT pg_backend_pid();
[nuko@localhost ~]$ cat /tmp/file2.txt 
SELECT pg_backend_pid();
COMMIT;

トランザクション途中でファイルを分割していることに注目。

で、これを複数同時に指定してみる。

[nuko@localhost ~]$ psql postgres -f /tmp/file1.txt -f /tmp/file2.txt 
BEGIN
 pg_backend_pid 
----------------
          12416
(1 row)

 pg_backend_pid 
----------------
          12416
(1 row)

COMMIT

複数のファイルにまたがってトランザクションが継続しているのがわかりますね。
(そうでなければ、最後のCOMMITで"WARNING: there is no transaction in progress"と怒られるはず。

まあ、これはあると嬉しい機能ですよね。

クロスタブ集計ビュー

Add a \crosstabview command that prints the results of a query in a cross-tabulated display

面白いけど、謎機能その1。

たとえば、こんなクエリを発行して、以下の様な結果を取得する。

tmp=# TABLE sales ;
    date    | item  | sales 
------------+-------+-------
 2016-05-16 | Ramen |  1650
 2016-05-16 | Curry |  2500
 2016-05-16 | Udon  |   500
 2016-05-16 | Soba  |   600
 2016-05-17 | Ramen |  2400
 2016-05-17 | Curry |   800
 2016-05-18 | Ramen |  1400
 2016-05-18 | Curry |  1600
 2016-05-18 | Udon  |  1000
(9 rows)

この状態で、以下の様な9.6新規のpsqlのメタコマンドを発行する。

tmp=# \crosstabview date item sales
    date    | Ramen | Curry | Udon | Soba 
------------+-------+-------+------+------
 2016-05-16 |  1650 |  2500 |  500 |  600
 2016-05-17 |  2400 |   800 |      |     
 2016-05-18 |  1400 |  1600 | 1000 |     
(3 rows)

なんかクロスタブ集計っぽい結果が出てきたw

\crosstab メタコマンドは3つないし4つの引数をとる。
1つ目は縦側の列、2つ目は横側の列、3つ目はセルに相当する列を指定。
4つ目の引数にはソートしたい列名を指定する。

面白い機能なんだけど、
なぜpsql上でクロスタブ集計ぽいことしたかったのか、という疑問はある。いいぞ、もっとやれw

\errverbose

Add an \errverbose command that shows the last server error at full verbosity

直前のエラー情報詳細を表示してくれるメタコマンド。

tmp=# SELECT hoge;
ERROR:  column "hoge" does not exist
LINE 1: SELECT hoge;
               ^
tmp=# \errverbose 
ERROR:  42703: column "hoge" does not exist
LINE 1: SELECT hoge;
               ^
LOCATION:  errorMissingColumn, parse_relation.c:3090
tmp=# SELECT 1;
 ?column? 
----------
        1
(1 row)

tmp=# \errverbose 
ERROR:  42703: column "hoge" does not exist
LINE 1: SELECT hoge;
               ^
LOCATION:  errorMissingColumn, parse_relation.c:3090

現実社会でもいるよね。
いつまでも失敗を覚えてぐちぐち蒸し返す奴ってwww

\evコマンドと\svコマンド

Add \ev and \sv commands for editing and showing view definitions

\ev コマンドは、クエリエディタコマンド \e をちょい拡張したもので、ビュー編集テンプレートをエディタに表示してくれる。

CREATE VIEW  AS
 SELECT
  -- something...

似たような機能として、 \ef (関数のテンプレート表示)があったけど、それのView版ね。

\sv はビュー生成時のクエリを表示してくれるメタコマンド。こんな感じ。

tmp=# \sv sales_date 
CREATE OR REPLACE VIEW public.sales_date AS
 SELECT sales.date,
    sum(sales.sales) AS sum
   FROM sales
  GROUP BY sales.date

\gexec

Add a \gexec command that executes a query and re-submits the result(s) as new queries

最初はよくわかんなかったけど、最後に実行した実行結果を、SQLとして実行するというメタコマンド。
つまり、\gexec の直前に実行するクエリは
SQLを生成するクエリ
である必要がある。変態ですね。
実例を示したほうが早いかな。

まず、こんなクエリを実行する。

tmp=# SELECT 'SELECT now()' FROM (SELECT generate_series(1,2)) AS t;
   ?column?   
--------------
 SELECT now()
 SELECT now()
(2 rows)

2行の SELECT now() という文字列が結果として返却される。

この状態で、 \gexecを実行すると・・・

tmp=# \gexec
              now              
-------------------------------
 2016-05-18 20:31:06.846529+09
(1 row)

              now              
-------------------------------
 2016-05-18 20:31:06.846616+09
(1 row)

さっきの結果文字列 SELECT now() が2回実行される。
さて、この結果を見るとわかると思うけど、個々のクエリは別トランザクションとして実行されている。
が、これは \gexec の問題ではなく、psqlの自動コミットモードがデフォルトで on になっているため。
なので、自動コミットモードを off にすると、2回とも now() は同じ時刻を返却する(同一トランザクションになるので)。

tmp=# \set AUTOCOMMIT off
tmp=# ;
tmp=# SELECT 'SELECT now()' FROM (SELECT generate_series(1,2)) AS t;
   ?column?   
--------------
 SELECT now()
 SELECT now()
(2 rows)

tmp=# \gexec
              now              
-------------------------------
 2016-05-18 22:26:15.190622+09
(1 row)

              now              
-------------------------------
 2016-05-18 22:26:15.190622+09
(1 row)

おわりに

今日はネムイ(´・ωゞ)のでここまで。
psql は他にも9.6での改善項目、特にTAB補完などは次のエントリで紹介しようと思います。

それにしても、psqlって単なるSQLフロントエンドとは思えないほど、
変態的に高度な機能を実装しちゃってるよなー。
これだからpgAdminが使われなく(ry

PostgreSQL 9.6 - システムカタログの差分

今度は、9.5と9.6のシステムカタログの差分を見てみる。
PostgreSQLの場合、特にDDLの追加や変更があった場合、必ずと言っていいほどシステムカタログに変更がある。裏を返せば、シスタムカタログの変更から、どんな機能追加があったのかを推測することもできる。
(まあ、リリースノート見たほうが早いのだけど)

調べ方

PostgrerSQL 9.5とPostgreSQL 9.6 beta1を起動して、PostgreSQL 9.6 beta1 のpsqlから

psql postgres -q -c "\d pg_*" > ファイル名

でファイルを生成。そのファイルのdiffを取る。シンプル。

システムカタログの差分

テーブル/ビュー名 変更種別 変更内容 ぬこメモ
pg_aggregate 列の追加 aggcombinefn, aggserialfn, aggdeserialfn, aggserialtype の追加 パラレル集約の関係?要調査
pg_am 全面変更 個々のアクセスメソッド用フラグが消えて、ハンドラ関数ポインタに置き換わったみたい。 インデックスのサポート能力ってSQLからどうやって見ればいいのだろう。
pg_config カタログの追加 pg_config相当の情報 リモートからconfigが参照可能になったってことだね。
pg_init_privs カタログの追加 システム内オブジェクトの初期権限管理用のカタログ? 普段使うものではない?要調査
pg_proc 列の追加 proparallelの追加 名前からすると、関数が並列処理に対応するかどうかのフラグ?CREATE FUNCTIONに影響あるのかな。
pg_replication_slots 列の追加 confirmed_flush_lsn の追加。 ロジカルレプリケーション利用者が受信した場所を示す列かな。
pg_stat_activity 列の変更 waitingがwait_event_typeとwait_eventに分離したみたい。 監視方式にも影響でるのかな?
pg_stat_progress_vacuum カタログの追加 VACUUM処理進捗表示のためのカタログ PostgreSQL: Documentation: 9.6: Progress Reportingを見ればいいのだな。
pg_stat_wal_receiver カタログの追加 名前のとおり、wal recieverの挙動に関する稼働統計情報かな。 これを使うと、どういう監視が出来るようになるのかなあ。

システムカタログも結構変更が入っているな。

  • pg_am(インデックスアクセスメソッド)の詳細が見れなくなってる・・・どうやって見ればいいんだろうな。
  • pg_configの追加なんかは、ベンチマーク測定時の環境情報取得時に、ちょい楽になりそう。

PostgreSQL 9.6 - GUCの変更

ということでPostgreSQL 9.6 beta1が予定どおり、5/12にリリースされましたよ。
それにしても今回はalphaバージョンはなしなのか。

GUCの差分を見てみた

とりあえず、動かしてみる前に9.5と9.6のGUCの差分を確認してみた。
結構いろんなパラメータの追加や、値域の変更があるなあ。

パラメータ名 変更の種別 内容 ぬこメモ
autovacuum_max_workers 最大値の変更 8388607から262143になった。 10より大きくしたことない気がするのでわりとどうでもいい。
backend_flush_after 新規追加 ディスクフラッシュに関するパラメータ? 通常はデフォルト値(16ページ)のままでいいのかなあ?
bgwriter_flush_after 新規追加 上のパラメータのbgwriter版? これも通常はデフォルト(64ページ)のままでいいのかなあ?
checkpoint_flush_after 新規追加 上のパラメータのcheckpointer版? これも(略)(32ページ)の(略)
enable_fkey_estimates 新規追加 プランナで結合時の推定に外部キーの情報を使うか否か? 該当クエリのEXPLAINをとって確認するのが手っ取り早いかな
force_parallel_mode 新規追加 パラレルスキャンを強制? enable_* パラメータみたいなものかな?
idle_in_transaction_session_timeout 新規追加 一定時間のidle in transaction sessionを切断 運用ミスによるロングトランザクション防止に使えそう?
max_connections 最大値の変更 8388607から262143になった。 これもせいぜい1000くらいまでしか上げないからなあ・・・
max_parallel_degree 新規追加 ノード実行時に同期実行するプロセス数上限値 パラレルスキャンに関する重要なパラメータっぽい
max_prepared_transactions 最大値の変更 8388607から262143になった。 このパラメータ自体チューニングで使ったこと、あったけなあ・・・
max_replication_slots 最大値の変更 8388607から262143になった。 レプリケーションスロット数をそんな大きな値に設定したことがそもそもない。
max_wal_senders 最大値の変更 8388607から262143になった。 wal senderプロセス数って、もうちょっと上限小さくてもいいんじゃないかなあという気も。
max_worker_processes 最大値の変更 8388607から262143になった。 ワーカプロセス数って、もうちょっと上限(略)
old_snapshot_threshold 新規追加 非常に古いスナップショット読み込み挙動のフラグ? きちんと調べて、どういうときにデフォルト(-1)以外之設定が必要か理解しないと・・・
parallel_setup_cost 新規追加 パラレルスキャンのコスト推定パラメータ パラレルスキャンのチューニング時に重要なパラメータになるのか、だいたいの場合デフォルトでいいのかくらいは調べておかないと。
parallel_tuple_cost 新規追加 パラレルスキャンのコスト推定パラメータ パラレルスキャンのチューニング時に重要なパラメータになるのか(略)
replacement_sort_tuples 新規追加 ソート方式(クイックソート/外部ソート)の切り替え閾値(タプル数) 既存のwork_men/maintenance_work_memとの関係も調べるのかな。
server_version 設定値変更 バージョン番号の変更 そういえば10.0beta2になるかも話もあったが・・・
server_version_num 設定値変更 バージョン番号値の変更 そういえば10.0beta2になるかも話もあったが・・・
superuser_reserved_connections 最大値の変更 8388607から262143になった。 max_connectionと同じ話か。
synchronous_commit 値域の追加 remote_applyの追加 WAL反映完了まで待つ、完全な同期レプリケーションが可能になるのかな。
synchronous_standby_names 説明の変更 複数同期スタンバイに対応した説明変更 これも早く自分で動かしてみないとなあ。
syslog_sequence_numbers 新規追加 syslogでメッセージ分割したときの通番付与フラグ? あんまりsyslog出力って使ってないんだよなー。
syslog_split_messages 新規追加 syslogでメッセージ分割するかどうかのフラグ? あんまりsyslog(略)
wal_level 値域の変更 archive,hot_standbyがreplicaに統一された。 過去にPITRやレプリケーション設定方法を書いているドキュメントへの影響がw
wal_writer_delay 説明の変更 説明文の変更のみ。 フラッシュ関係のパラメータ追加とかが関係しているのかな。
wal_writer_flush_after 新規追加 wal_writerの制御パラメータ またフラッシュ関係のパラメータか!

既存の運用に影響があるのは、wal_levelの値域変更、あとは、基本的には新規機能関連かな。
最大値上限が小さくなることで影響を受けるシステムは多分ないと思う・・・。

補足

wal_level については、実は9.5までの設定値であるarchive, hot_standbyを指定しても、きちんと起動する。
また、そのときの設定値は、replica になる。

[nuko@localhost ~]$ grep wal_level /tmp/pg96/postgresql.conf 
#wal_level = minimal			# minimal, replica, or logical
wal_level = archive			# minimal, replica, or logical
#wal_level = hot_standby			# minimal, replica, or logical
[nuko@localhost ~]$ pg_ctl start -D /tmp/pg96/ -l /tmp/pglog
server starting
[nuko@localhost ~]$ psql postgres -c "SELECT name, setting FROM pg_settings WHERE name = 'wal_level'"
   name    | setting 
-----------+---------
 wal_level | replica
(1 row)

[nuko@localhost ~]$ vi /tmp/pg96/postgresql.conf 
[nuko@localhost ~]$ grep wal_level /tmp/pg96/postgresql.conf 
#wal_level = minimal			# minimal, replica, or logical
#wal_level = archive			# minimal, replica, or logical
wal_level = hot_standby			# minimal, replica, or logical
[nuko@localhost ~]$ pg_ctl restart -D /tmp/pg96/ -l /tmp/pglog
waiting for server to shut down.... done
server stopped
server starting
[nuko@localhost ~]$ psql postgres -c "SELECT name, setting FROM pg_settings WHERE name = 'wal_level'"
   name    | setting 
-----------+---------
 wal_level | replica
(1 row)

ただ、ソースコードを見ると、archiveもhot_standbyも設定自体は残っているけど、hidden扱い、かつコメントには deprecated になっているので、9.6にアップデートした契機でなるべく wal_level = replica と記述するようにすべきなんでしょうね。

backend/access/rmgrdesc/xlogdesc.c の抜粋。

const struct config_enum_entry wal_level_options[] = {
        {"minimal", WAL_LEVEL_MINIMAL, false},
        {"replica", WAL_LEVEL_REPLICA, false},
        {"archive", WAL_LEVEL_REPLICA, true},  /* deprecated */
        {"hot_standby", WAL_LEVEL_REPLICA, true},  /* deprecated */
        {"logical", WAL_LEVEL_LOGICAL, false},
        {NULL, 0, false}
};

履歴情報つきinteger型をpgbenchモデルで動かしてみた


ということで、先週作成した履歴情報付きinteger(疑似)型を、pgbenchモデルに組み込んで、どのくらい性能が劣化するものか確認してみた。

測定モデル

基本的にはpgbanechをベースにした。オリジナルのpgbenchとは以下のような違いがある。

  • historyテーブルを削除。
    • 更新記録をhistoryテーブルではなく、履歴情報付きinteger型自体に持たせるため。
  • accountsのabalance, branchesのbbalance, tellersのtbalanceをintegerではなく、履歴情報つきinteger型(jsonb)で定義する。
テーブル定義

実際の定義はこんな感じ。

CREATE UNLOGGED TABLE accounts (aid int primary key, bid int, abalance jsonb, filler char(84)) WITH (fillfactor = 90);
CREATE UNLOGGED TABLE branches (bid int primary key, bbalance jsonb, filler char(88)) WITH (fillfactor = 90);
CREATE UNLOGGED TABLE tellers (tid int primary key, bid int, tbalance jsonb, filler char(84)) WITH (fillfactor = 90);

例によって効果を見えやすくするために、テーブルはUNLOGGED TABLEで作成してWAL出力をスキップする。
また、fillfactorを90にしてフツーならHOT更新が効く状態にしておく。

初期データ

初期データは、以下の手順で生成する。

  • まず、フツーにpgbenchでデータを初期生成モード(-i -s スケール数)で生成する。
  • 以下のSELECT+COPY文でダンプする。
    • abalance, bbalance, tbalanceをそれぞれ create_tt_int()関数経由でJSONBデータ化した値で初期化する。
COPY (SELECT aid, bid, create_tt_int(abalance), filler FROM pgbench_accounts) TO '/tmp/accounts.txt';
COPY (SELECT bid, create_tt_int(bbalance), filler FROM pgbench_branches) TO '/tmp/branches.txt';
COPY (SELECT tid, bid, create_tt_int(tbalance), filler FROM pgbench_tellers) TO '/tmp/tellers.txt';

これをさっき作成したテーブルにCOPY FROMで叩き込む。

COPY accounts FROM '/tmp/accounts.txt';
COPY branches FROM '/tmp/branches.txt';
COPY tellers  FROM '/tmp/tellers.txt';

なお、各テーブルの初期レコード数は

テーブル名 レコード数
accounts 1000000
branches 10
tellers 100

今回のベンチマークモデルでは、この件数は最後まで変動しない。

実行トランザクション
  • pgbenchのカスタムクエリモードで実行する。
  • カスタムクエリのファイル内容は以下。
\set scale 10
\set nbranches :scale
\set ntellers 10 * :scale
\set naccounts 100000 * :scale
\setrandom aid 1 :naccounts
\setrandom bid 1 :nbranches
\setrandom tid 1 :ntellers
\setrandom delta -5000 5000
BEGIN;
UPDATE accounts SET abalance = add_tt_int(abalance, value(abalance) + :delta) WHERE aid = :aid;
SELECT value(abalance) FROM accounts WHERE aid = :aid;
UPDATE tellers SET tbalance = add_tt_int(tbalance, value(tbalance) + :delta) WHERE tid = :tid;
UPDATE branches SET bbalance = add_tt_int(bbalance, value(bbalance) + :delta) WHERE bid = :bid;
END;

オリジナルのpgebnchとの違いは以下。

  • accountsのabalance, branchesのbbalance, tellersのtbalanceの更新方法のみ。
    • オリジナルは元の値に :delta を加算した値でUPDATE
    • 履歴情報つきintの場合は、value()で取り出した値を :delta で加算し、その値を、add_tt_int()で更新(履歴追加)した結果をUPDATE
  • WHERE句評価用の各テーブルidはそのままなので、基本的にはUPDATE/SELECT対象のレコードを選択するまでの処理はオリジナルとは変わらないはず。
測定環境
  • 今回の測定環境も例によって、Let's note SX4上のVMWare+CentOS7。
  • PostgreSQL 9.5.1(ビルドバージョン)
  • postgresql.confの設定は基本デフォルトで。
    • (悲観的ではあるが)たぶん、性能向上につながるようなパラメータはないような気がする。
    • UNLOGGED TABLEなのでWALまわりのパラメータも関係なし。
測定方式
  • 以下の測定を10回連続で行う。
    • pgbenchのカスタムモードで実行
    • 事前バキュームはなし
    • 同時実行数は1
    • トランザクション数は1000
  • 最終的には10000トランザクションが実行される。
  • 実行後に、tps、クエリのレイテンシ、テーブルサイズを測定する。

予測

  • branchesテーブルへの更新劣化が原因で、徐々に性能が劣化していくはず。
  • branchesテーブルが非常に巨大になるはず。

測定結果

tps

レイテンシ

  • やはりbranchesテーブルのレイテンシの劣化が際立っている。線形に劣化しているなあ。ここのレイテンシの悪化がtps低下の主要因ですね。
    • 更新結果の生成処理(tt_int_add())の時間と、UPDATE処理時間の合わせ技か。
    • 最終的に平均世代数は1000になる。
  • tellersもbranchesほどではないが、線形に劣化している。
    • 最終的に平均世代数は100になる。
  • accountsは1件あたりの世代数がほとんど増えないので、更新性能の劣化はほとんどない。
  • またaccountsへの参照も同様にほぼ劣化が見られない。
データサイズ
  • 今回データサイズとしてはpg_relation_size(), pg_indexes_size(), pg_total_relation_size()の3つを取得した。
  • accountsについては、10000トランザクションでは全くサイズの変動はなかったので、プロット対象外としている。
  • また、pg_indexes_size()についても(一応測定はしたけど)サイズの変動はなし。



  • branchesのpg_relation_size()の結果を見ると、早々に増加傾向が停まっている。
    • が、pg_total_reration_size()の結果を見ると順調に肥大化してますなw
    • つまり、branchesのレコードについては、早々に全レコードともTOAST対象になってしまったということかと。
  • tellersの場合は、branchesの1/10の速度で世代数が増えていくためか、途中までは段階的にpg_relation_sizeも増加していくが、8000トランザクション以降は(おそらくは)その値のままになりそう=全レコード(100行)が全て、この時点でTOAST対象になったのかな。
    • なので、以降はpg_total_reration_size()が線形に増加していくのだろう。

結論

結論も何もないような気がするが

  • pgbenchの bbalance, tbalnceのような非常に更新が多いカラムに、履歴情報付き整数型を使うとカジュアルに性能劣化して死ぬ
  • 記録する世代数としてはやはり数十程度の用途にしないと、ちょっと厳しそう・・・
  • 今回の試作では対象のデータ型をinteger型にしたけど、それより数回〜数十回程度の更新があると思われるTEXT型を履歴情報付きデータ型のターゲットにしたほうがいいのかもなあ。