Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.
PL/Pythonで2層アプリを作ってみた
PostgreSQLカンファレンス2015/05
アシストセミナールーム
who am i
名前:山田 聡
会社:アシスト
仕事:ポスグレとオラクルのサポートしてます
年齢:社会人5年目(ポスグレ4年生)
注意
初心者がやってみた系です
若干ギャグなので生暖かい目で見てください
9.5 について知りたい方は
今直ぐ
←
Long Time Ago..
クラサバ
クライアントとデータベースが直
ビジネスロジックはクライアント側中心
graph LR id1(クライアント
ロジック)-->id2(データベース) style id1 fill:#955,stroke:#111,stroke-
wi...
3層アプリケーション
間にWEB/APサーバ増えた
クライアント側はブラウザ
ビジネスロジックはWEB/APサーバ
graph LR id1(クライアント:
ブラウザ)-->id2(WEBサーバ:
ロジック) id2-->id3(DB) sty...
でも...
管理とか
負荷とか
速度とか
そうだ2層に戻ろう
できあがったもの
PG2LAYER
PG2LAYER
PostgreSQL Two Layer Management System
管理用のダッシュボード
クエリ結果をJSONで戻すAPIを提供
普通のWEBアプリに見える何か
構造
PostgreSQLに子プロセスで直接リスニングさせる
PL/Pythonを使用
Pythonの を利用bottoleフレームワーク
インデント原理主義
2系と3系があって過渡期
機械学習とか統計学で最近よく見る
def say_hello():
    for i in range(1,100):
        print "HELLO No %s!" % i
PL/Python
create extensionで追加
PythonをPostgreSQLで使える
plpyモジュールでSQLもあつかえる
プレースホルダーを使って実行す
る場合
CREATE FUNCTION pystrip(x text)
  RETURNS text
AS $$
target_query = "select empno from emp where ename...
PythonのWEBフレームワーク
1ファイルに全てが含まれる
最軽量
PG2LAYER 構成
でも2層構造
graph LR id1(クライアント:
ブラウザ)-->id2(DBサーバ:
ロジック) style id1 fill:#955,stroke:#111,stroke-width:4px; style i...
低依存性 (bottole.py /DBLINK / ドライバ不要)
低レイヤー (ブラウザ <-> データベース)
低管理コスト (DBのバックアップ = APのバックアップ, htmlファイル
レス)
起動
使用ポートを引数にしてファンクションを起動するだけ
pg2layer_db=# select start_bottole_httpd(1192);
ソース
CREATE OR REPLACE FUNCTION start_bottole_httpd(v_port integer) RETURNS text AS
$$
"""
PG2LAYER ­­ PostgreSQL Two Layer...
api(select_list,table_name,where_col=None,col=None,col_val=None):
URLからクエリを生成しJSONのレスポンスを戻す
static(content_type,file_name)...
構想1時間
作成1日(のはずが1週間)
はまったところ
(きっと自分だけ)
問題その1
ImportError: No module named bottole
カレントに配置したbottole.pyが読み込めない
普通はカレントディレクトリは指定しなくてもいい
PostgreSQLの子プロセスは$PGDATAがカレン...
原因
PYTHONPATHに$PGDATAが含まれていない
javaのCLASSPATHとかLD_LIBRARY_PATH的なもの
ライブラリを読み込むディレクトリ
対処
AP内で動的に$PGDATA配下をPYTHONPATH
に追加
import sys
# $PGDATA配下に配置したbottole.pyをPYTHONPATHに追加
sys.path.append(os.getcwd()+"/PL_Py...
問題その2
トランザクションがCommitできない
HTMLソース編集機能を追加
フォームからPOSTしたら反映はされる
PG2LAYERが異常終了したら変更データが戻る(!?)
PL/Pythonでトランザクション管理
をしてもだめだった(というかcommitないし)
43.8. 明示的サブトランザクション
原因
呼び出し元のstart_bottole_httpdファンクションが終了しないから
行った変更はstart_bottole_httpdを正常停止させない限りrollback
対処
DBLINKで自律型トランザクション
DBLINKでループバックして自律型トランザクションで対応
    base_query = "SELECT * FROM dblink('host=%s port=%s dbname=%s user...
問題3
稼動統計が意図せず読み取り一貫性を発揮
api()関数で発生
pg_stat_activity等がstart_bottole_httpd起動時点の結果しかとれない
repetable read 的な挙動
普通の表はちゃんとread co...
原因
不明(多分問題2と同じ?)
対処
DBLINK
稼動統計系だけ分けるもの面倒なのでAPIは全部DBLINK経由に変更
問題4
DBLINK経由のクエリで列リストが不定
APIでは列リストをURLで指定する実装
url query
/api/*/hoge -> select * from hoge
/api/col1,col2/hoge -> select co...
対処
そうだJSONにしよう
元クエリをjson_aggでラップ
戻り値はかならずJSON
dblink('dbname=pg_2_layer', 'select json_agg(t) from (元クエリ) t')
AS t(result ...
元のクエリを
select * from emp
json_aggでラップして
select json_agg(t) from (
    select * from emp
) t
dblinkでラップする
select * from dbl...
こんだけラップで何がパフォーマ
ンスか
問題5
停止できない
start_bottole_httpdが止められない
ctrl+c/pg_terminate_backendできず
ctrl+c
pg2layer_db=# select start_bottole_httpd(1192);
^CCancel request sent
ctrl+c
pg2layer_db=# select start_bottole_httpd(1192);
^CCancel request sent
^CCancel request sent
ctrl+c
pg2layer_db=# select start_bottole_httpd(1192);
^CCancel request sent
^CCancel request sent
^CCancel request sent
ctrl+c
pg2layer_db=# select start_bottole_httpd(1192);
^CCancel request sent
^CCancel request sent
^CCancel request sent
^...
落ちないorz
原因
実行中はpostgresqlのコンテキストではなくPythonのコンテキスト
シグナルハンドラがPythonコンテキストで動作してない?
対処
自分でシグナルハンドラ書く
PythonコンテキストでSIGINTを処理するように
受け取ったら落ちる
def signal_handler(num, frame):
    plpy.log("SIGINT_restart")
    ...
いないと思いますが
PL/Pythonを使ったWEBアプリを作成されようと思っている方の
一助になれば幸いです。
終わり
201505 PostgreSQLアンカンファレンス(PL/Pythonで作るWEBアプリ)
Upcoming SlideShare
Loading in …5
×

201505 PostgreSQLアンカンファレンス(PL/Pythonで作るWEBアプリ)

777 views

Published on

2015/05/30に行われたPostgreSQLアンカンファレンスでの
資料です。

PL/Python + bottle.py でWEBアプリを作る話です

Published in: Technology
  • Be the first to comment

  • Be the first to like this

201505 PostgreSQLアンカンファレンス(PL/Pythonで作るWEBアプリ)

  1. 1. PL/Pythonで2層アプリを作ってみた PostgreSQLカンファレンス2015/05 アシストセミナールーム
  2. 2. who am i 名前:山田 聡 会社:アシスト 仕事:ポスグレとオラクルのサポートしてます 年齢:社会人5年目(ポスグレ4年生)
  3. 3. 注意 初心者がやってみた系です 若干ギャグなので生暖かい目で見てください
  4. 4. 9.5 について知りたい方は 今直ぐ ←
  5. 5. Long Time Ago..
  6. 6. クラサバ クライアントとデータベースが直 ビジネスロジックはクライアント側中心 graph LR id1(クライアント ロジック)-->id2(データベース) style id1 fill:#955,stroke:#111,stroke- width:4px; style id2 fill:#559,stroke:#f66,stroke-width:2px;
  7. 7. 3層アプリケーション 間にWEB/APサーバ増えた クライアント側はブラウザ ビジネスロジックはWEB/APサーバ graph LR id1(クライアント: ブラウザ)-->id2(WEBサーバ: ロジック) id2-->id3(DB) style id1 fill:#955,stroke:#111,stroke- width:4px; style id2 fill:#555,stroke:#111,stroke-width:4px; style id3 fill:#559,stroke:#f66,stroke-width:2px;
  8. 8. でも... 管理とか 負荷とか 速度とか
  9. 9. そうだ2層に戻ろう
  10. 10. できあがったもの
  11. 11. PG2LAYER
  12. 12. PG2LAYER PostgreSQL Two Layer Management System 管理用のダッシュボード クエリ結果をJSONで戻すAPIを提供 普通のWEBアプリに見える何か
  13. 13. 構造 PostgreSQLに子プロセスで直接リスニングさせる PL/Pythonを使用 Pythonの を利用bottoleフレームワーク
  14. 14. インデント原理主義 2系と3系があって過渡期 機械学習とか統計学で最近よく見る
  15. 15. def say_hello():     for i in range(1,100):         print "HELLO No %s!" % i
  16. 16. PL/Python create extensionで追加 PythonをPostgreSQLで使える plpyモジュールでSQLもあつかえる
  17. 17. プレースホルダーを使って実行す る場合 CREATE FUNCTION pystrip(x text)   RETURNS text AS $$ target_query = "select empno from emp where ename=$1" plan = plpy.prepare(target_query,["text"]) r_set = plpy.execute(plan, [ "SMITH" ]) return r_set[0]["empno"] $$ LANGUAGE plpythonu; 直接実行する場合 CREATE FUNCTION pl_py_test(x text)   RETURNS text AS $$ r_set = plpy.execute("select empno from emp where ename='SMITH'") return r_set[0]["empno"] $$ LANGUAGE plpythonu;
  18. 18. PythonのWEBフレームワーク 1ファイルに全てが含まれる 最軽量
  19. 19. PG2LAYER 構成 でも2層構造 graph LR id1(クライアント: ブラウザ)-->id2(DBサーバ: ロジック) style id1 fill:#955,stroke:#111,stroke-width:4px; style id2 fill:#555,stroke:#111,stroke-width:4px;
  20. 20. 低依存性 (bottole.py /DBLINK / ドライバ不要) 低レイヤー (ブラウザ <-> データベース) 低管理コスト (DBのバックアップ = APのバックアップ, htmlファイル レス)
  21. 21. 起動 使用ポートを引数にしてファンクションを起動するだけ pg2layer_db=# select start_bottole_httpd(1192);
  22. 22. ソース CREATE OR REPLACE FUNCTION start_bottole_httpd(v_port integer) RETURNS text AS $$ """ PG2LAYER ­­ PostgreSQL Two Layer Management System * Low dependency ( only bottole.py , only dblink) * Low layer (browser <­> DB, never AppServer or HTTP Server) * Low manage cost ( backup database backup the Web App) It's Joke Web App. 2015/05 sayamada """ import sys import os import json import signal # $PGDATA配下に配置したbottole.pyをPYTHONPATHに追加しないといけないので sys.path.append(os.getcwd()+"/PL_Python_Httpd") from bottle import route, run, template, response, request, get, post, redirect # for DBLINK DB_NAME = "pg2layer_db" DB_USER = "sayamada" DB_HOST = "localhost"
  23. 23. api(select_list,table_name,where_col=None,col=None,col_val=None): URLからクエリを生成しJSONのレスポンスを戻す static(content_type,file_name): 静的なソース(css/js等)を戻す 静的ファイルは表データとして格納 get_tmplt(tmplt_name=None): bottoleのテンプレートファイルを戻す ほぼHTML 表データとして格納 do_query_over_dblink(v_query_string): DBLIK経由でSQL処理しJSONで戻す(後述) edit(): POSTリクエストに基づきテンプレートを更新
  24. 24. 構想1時間 作成1日(のはずが1週間)
  25. 25. はまったところ (きっと自分だけ)
  26. 26. 問題その1 ImportError: No module named bottole カレントに配置したbottole.pyが読み込めない 普通はカレントディレクトリは指定しなくてもいい PostgreSQLの子プロセスは$PGDATAがカレント pg2layer_db=# CREATE OR REPLACE FUNCTION test() RETURNS text AS pg2layer_db­# $$ pg2layer_db$#  pg2layer_db$# import bottole pg2layer_db$# $$ pg2layer_db­# LANGUAGE 'plpythonu' VOLATILE; CREATE FUNCTION pg2layer_db=#  pg2layer_db=# select test(); ERROR:  ImportError: No module named bottole  CONTEXT:  Traceback (most recent call last):   PL/Python function "test", line 3, in <module>     import bottole PL/Python function "test"
  27. 27. 原因 PYTHONPATHに$PGDATAが含まれていない javaのCLASSPATHとかLD_LIBRARY_PATH的なもの ライブラリを読み込むディレクトリ
  28. 28. 対処 AP内で動的に$PGDATA配下をPYTHONPATH に追加 import sys # $PGDATA配下に配置したbottole.pyをPYTHONPATHに追加 sys.path.append(os.getcwd()+"/PL_Python_Httpd")
  29. 29. 問題その2 トランザクションがCommitできない HTMLソース編集機能を追加 フォームからPOSTしたら反映はされる PG2LAYERが異常終了したら変更データが戻る(!?)
  30. 30. PL/Pythonでトランザクション管理 をしてもだめだった(というかcommitないし) 43.8. 明示的サブトランザクション
  31. 31. 原因 呼び出し元のstart_bottole_httpdファンクションが終了しないから 行った変更はstart_bottole_httpdを正常停止させない限りrollback
  32. 32. 対処 DBLINKで自律型トランザクション DBLINKでループバックして自律型トランザクションで対応     base_query = "SELECT * FROM dblink('host=%s port=%s dbname=%s user=%s',%s) AS t(r text)"     target_query = "update pg_2_template set src=%s where file_name=%s returning file_name" % (                                                 plpy.quote_literal(edit_src),                                                 plpy.quote_literal(edit_file_name)                                             )     last_query = base_query % (         DB_HOST,         DB_PORT,         DB_NAME,         DB_USER,         plpy.quote_literal(target_query)     )     r_set = plpy.execute(last_query)
  33. 33. 問題3 稼動統計が意図せず読み取り一貫性を発揮 api()関数で発生 pg_stat_activity等がstart_bottole_httpd起動時点の結果しかとれない repetable read 的な挙動 普通の表はちゃんとread commited
  34. 34. 原因 不明(多分問題2と同じ?)
  35. 35. 対処 DBLINK 稼動統計系だけ分けるもの面倒なのでAPIは全部DBLINK経由に変更
  36. 36. 問題4 DBLINK経由のクエリで列リストが不定 APIでは列リストをURLで指定する実装 url query /api/*/hoge -> select * from hoge /api/col1,col2/hoge -> select col1,col2 from hoge DBLINKは戻り値のデータ型を明示しないといけない
  37. 37. 対処 そうだJSONにしよう 元クエリをjson_aggでラップ 戻り値はかならずJSON dblink('dbname=pg_2_layer', 'select json_agg(t) from (元クエリ) t') AS t(result json) # DBLINKで自律型トランザクションとする # 型にしばられないため、json_aggでラップしている # 戻りは全部JSON def do_query_over_dblink(v_query_string):     # DBLINKの大枠     base_query = "SELECT * FROM dblink('host=%s port=%s dbname=%s user=%s', %s) AS t(result json)     last_query = base_query % (         DB_HOST,         DB_PORT,         DB_NAME,         DB_USER,         plpy.quote_literal("select json_agg(t) from (" + v_query_string+ ") t") # クエリも引数なので     )     plpy.log( last_query)     r_set = plpy.execute(last_query)     plpy.log(r_set)     # jsonで戻しても取得時はstrになってたのでstrとして統一     result_json_str = "[]"     if r_set[0]["result"] is not None:         result_json_str = r_set[0]["result"]     # 利用側の利便性を考えてjsonで戻す
  38. 38. 元のクエリを select * from emp json_aggでラップして select json_agg(t) from (     select * from emp ) t dblinkでラップする select * from dblink('     select json_agg(t) json from (         select * from emp     ) t' ) AS t(result json)
  39. 39. こんだけラップで何がパフォーマ ンスか
  40. 40. 問題5 停止できない start_bottole_httpdが止められない ctrl+c/pg_terminate_backendできず
  41. 41. ctrl+c pg2layer_db=# select start_bottole_httpd(1192); ^CCancel request sent
  42. 42. ctrl+c pg2layer_db=# select start_bottole_httpd(1192); ^CCancel request sent ^CCancel request sent
  43. 43. ctrl+c pg2layer_db=# select start_bottole_httpd(1192); ^CCancel request sent ^CCancel request sent ^CCancel request sent
  44. 44. ctrl+c pg2layer_db=# select start_bottole_httpd(1192); ^CCancel request sent ^CCancel request sent ^CCancel request sent ^CCancel request sent
  45. 45. 落ちないorz
  46. 46. 原因 実行中はpostgresqlのコンテキストではなくPythonのコンテキスト シグナルハンドラがPythonコンテキストで動作してない?
  47. 47. 対処 自分でシグナルハンドラ書く PythonコンテキストでSIGINTを処理するように 受け取ったら落ちる def signal_handler(num, frame):     plpy.log("SIGINT_restart")     sys.exit(0) signal.signal(signal.SIGINT, signal_handler) run(host='0.0.0.0', port=v_port)
  48. 48. いないと思いますが PL/Pythonを使ったWEBアプリを作成されようと思っている方の 一助になれば幸いです。
  49. 49. 終わり

×