位置情報を使ったサービス「スマポ」をPostgreSQLで作ってみた db tech showcase...

Preview:

Citation preview

位置情報を使ったサービス「スマポ」をPostgreSQLで作ってみた

株式会社スポットライト 浅羽義之

はじめに

•この資料はスマポではこうやって運用していますという事例を紹介します。

•他の環境でもそのまま使えるかについてはみなさんのご判断の下、参考にしていただけると幸いですが、何かあっても無保証でお願いします。

会社紹介

会社紹介

http://www.smapo.jp/

スマポ紹介

なぜPostgreSQLか

PostgreSQL選定理由

•位置情報を扱いたい

•(特に初期段階では)サービス運営のランニングコストを抑えたい

•マニュアルが充実している、等

POSTGIS

現在地から 近い順にソート

shop A

shop B

shop C

100m

200m

300m

http://postgis.net/docs/manual-2.0/ST_Distance.html

運用編

サーバ構成

• AWSを利用

• Instance

• m1.large * 2 (master/slave, availability zoneを分けている)

• ELB→App Servers→DB Servers

• EBS

• provisioned IOPS volume (IOPS=1000)

•分析環境は別のデータセンターに配置

MONITORING

NEWRELIC

• EnterpriseDBが公開しているnewrelic pluginを利用

• webappのパフォーマンス監視も可能

VACUUM/ANALYZE

VACUUM/ANALYZE

• VACUUM/ANALYZEは今のところautovacuum任せ

• UPDATEの多いテーブルはFILLFACTORを設定

• HOT UPDATEと呼ばれる最適化を効かせるため

• http://lets.postgresql.jp/documents/tutorial/hot_1/

例:FILLFACTOR=90•すごくざっくりした説明です

8KB

8KB

8KB

8KB

PageHeader lineptr1(lp) lp2 lp4lp3 …… lpN

SpecialSpacetuple1tuple2tuple3

tuple4…tupleN

FreeSpace (90%超えたら次のページ)

REPLICATION

REPLICATION != BACKUP

• replicationは冗長化・負荷分散が目的

• backupはオペミスした時にも復旧できないといけない

• PITR(Point In Time Recovery)

STREAMING REPLICATION• PostgreSQL 9.0から導入された機能

• SLAVEはRead-onlyなDBとして動かすことも可能

• hot standby

wal_level = hot_standby max_wal_senders = 5 wal_keep_segments = 16

hot_standby = on max_standby_streaming_delay = 90s

standby_mode = 'on' primary_conninfo = 'host=x.x.x.x port=5432 user=repl_user password=XXXXXXX' restore_command = 'envdir /etc/wal-e.d/env /usr/local/bin/wal-e wal-fetch "%f" "%p"'masterの

postgresql.conf

slaveのpostgresql.conf

slaveのrecovery.conf

archive_mode = on archive_command = 'envdir /etc/wal-e.d/env /usr/local/bin/wal-e wal-push %p'

WAL-E• https://github.com/wal-e/wal-e

standby_mode = 'on' restore_command = 'envdir /etc/wal-e.d/env /usr/local/bin/wal-e wal-fetch "%f" "%p"'

postgresql.conf

recovery.conf

Amazon S3

PostgreSQL (master)

PostgreSQL (slave)

PostgreSQL (analytics)

backup-push

backup-fetchwal-push

wal-fetch

REPLICATION監視

• select * from pg_stat_replication;

• replicationが正常に動いていればslaveの数だけ結果が帰ってくる

• masterで実行

ANALYTICS

ANALYTICS DB

• fluentdでログデータを回収し、分析専用のPostgreSQLへ格納

•各種KPIをPostgreSQLで集計

•マシンパワーが必要なので、物理サーバで構築

WARMUP

データをキャッシュに載せる• cache?

• PostgreSQLのshared_buffer

• OSのページキャッシュ(free -mで確認)

•他にもあるが省略

•サーバ再起動時などはキャッシュがクリアされている

載せ方• shared_buffer

• SELECT count(*) FROM sample_table

•インデックスは載らないので注意

• page cache

• (ionice -c 3) cat 物理ファイル > /dev/null

ファイルの場所test=# SELECT relname, current_setting('data_directory') || '/' || pg_relation_filepath(oid) as filepath, pg_relation_size(oid) as filesize FROM pg_class WHERE relname = 'sample_table'; ! relname | filepath | filesize --------------+-----------------------------------------------+---------- sample_table | /var/lib/postgresql/9.2/test/base/16385/18572 | 1351680 (1 row)

test=# SELECT relname, current_setting('data_directory') || '/' || pg_relation_filepath(oid) as filepath, pg_relation_size(oid) as filesize FROM pg_class WHERE oid IN (SELECT indexrelid FROM pg_index WHERE indrelid = (SELECT oid FROM pg_class WHERE relname = ‘sample_table')); ! relname | filepath | filesize --------------------+-----------------------------------------------+---------- sample_table_pkey | /var/lib/postgresql/9.2/test/base/16385/22203 | 16384 idx_sample_table_b | /var/lib/postgresql/9.2/test/base/16385/22205 | 16384 idx_sample_table_c | /var/lib/postgresql/9.2/test/base/16385/22206 | 16384

運用トラブル集

#1 SLOW QUERY

何を見ようか?

• newrelicのレスポンスタイム

• slow query log

• log_min_duration_statement = 1s

• newrelic/cloudwatchのサーバステータス

参照のチューニング• EXPLAIN ANALYZEでどの実行プランが遅いか確認

• table scanが遅い

• indexが無い or indexが不適切?

• table joinが遅い

• ANALYZEが足りないか確認

• limit句がある場合はsubqueryにしてJOINの回数を減らせるか試す

EXPLAIN ANALYZE•実行計画がそれぞれどれくらい時間かかったか見ることが可能

test=# EXPLAIN ANALYZE SELECT t1.a, t2.a FROM t1, t2 WHERE t1.a = t2.a and t1.a < 100; QUERY PLAN -------------------------------------------------------------------------------------------------------------------------- Nested Loop (cost=0.85..805.31 rows=95 width=8) (actual time=0.038..3.319 rows=99 loops=1) -> Index Only Scan using t1_pkey on t1 (cost=0.42..10.09 rows=95 width=4) (actual time=0.012..0.449 rows=99 loops=1) Index Cond: (a < 100) Heap Fetches: 99 -> Index Only Scan using idx_t2_a on t2 (cost=0.42..8.36 rows=1 width=4) (actual time=0.006..0.011 rows=1 loops=99) Index Cond: (a = t1.a) Heap Fetches: 99 Total runtime: 3.782 ms (8 rows)

プランノード コスト 実行時間

やったこと•使ってほしいインデックスが使われていないのでクエリを書き換え

•インデックスないものは追加

• JOINしたあとにLIMITしていた箇所は、先にLIMITしてからJOINするように変更SELECT *FROM a, b WHERE a.id = b.id and a.hoge >= 1000 LIMIT10;

↓ SELECT* FROM (SELECT * FROM a WHERE a.hoge >= 1000 LIMIT10) as aa,

b WHERE aa.id = b.id

#2 MAJOR VERSION UP

PostgreSQL 9.1→9.2 PostGIS 1.5→2.0

tool down time comment

pg_upgrade △postgisのupgradeに対応していない?

pg_dump/pg_restore ☓ 停止時間が長い

slony-I ◯ 面倒

MASTER切り替え

PostgreSQL (旧master)

PostgreSQL (旧slave)

PostgreSQL (新master)

PostgreSQL (新slave)

AppServer

slonyのreplication

streaming replication

MASTER切り替え

PostgreSQL (旧master)

PostgreSQL (旧slave)

PostgreSQL (新master)

PostgreSQL (新slave)

AppServer

slonyのreplication 停止

streaming replication

検証環境で実験

問題なし!

切り替え当日

SIGSEGV!!!

結局pg_dump/pg_restoreやりました。すみません。

9.2→9.3でリベンジ予定。

#3 LOCK待ち問題

SELECT WAITING

• DB migration時などにロック待ちが発生

• psコマンド

• ps aux | grep postgres | grep waiting

• SELECT * FROM pg_locks;

•もう少し細かくロックの獲得状況を確認できる

クエリキャンセル

•クエリをキャンセル

• SELECT pg_cancel_backend(pid);

• or kill -INT pid

•クエリをキャンセルしてバックエンドプロセスを落とす

• SELECT pg_terminate_backend(pid)

• or kill -TERM pid

#4 DEADLOCK

ERROR: deadlock detected

デッドロックの原因

•ロックを取る順番が異なるため

•テーブルロック、行ロックなど

•ロック獲得

•明示的なロック

•暗黙的なロック

実際に起きたケース(簡略版)2013-10-30 00:11:22 JST DETAIL: Process 3225 waits for ShareLock on transaction 11759339; blocked by process 3040. Process 3040 waits for ShareLock on transaction 11759337; blocked by process 3225. Process 3225: insert into foo (x,y,z) select x, 12345, z from bar where id in (( values (1), (2), (3) ) except ( select x from foo where date = '...')) ! Process 3040: insert into foo (x,y,z) select x, 12345, z from bar where id in (( values (2), (3), (1) ) except( select x from foo where date = '...')) !

調査

•前提:開発環境でやること

• postgresql.conf

• log_statement = all

• log_line_prefixに%pをつける

• COMMIT直前のpg_locksを確認

一次調査でわかったこと

•テーブルロックの順番は同じ

•そもそも強いロックレベルでテーブルロック取ってない

• INSERTなのでfooに対する行ロックはあるのか?

•要はよくわからなかった

•ただ、確かにinsertで片方が待たされる

止まっている箇所を調べる

• PostgreSQLをデバッグビルド

• CFLAGS=-O0 ./configure —prefix=$HOME —enable-debug

•片方でSELECT pg_backend_pid()

• gdb -p backendのpid

BACK TRACE(gdb) bt … #5 0x00000000006f0ada in LockAcquire (locktag=0x7fff7ce28d20, lockmode=5, sessionLock=0 '\000', dontWait=0 '\000') at lock.c:662 #6 0x00000000006effec in XactLockTableWait (xid=768) at lmgr.c:495 #7 0x00000000004890e4 in _bt_doinsert (rel=0x7fa0c1634838, itup=0x2122468, checkUnique=UNIQUE_CHECK_YES, heapRel=0x7fa0c162f820) at nbtinsert.c:168 #8 0x000000000048f3b4 in btinsert (fcinfo=0x7fff7ce28e40) at nbtree.c:257 #9 0x0000000000819bb5 in FunctionCall6Coll (flinfo=0x2115650, collation=0, arg1=140328416004152, arg2=140735288611488, arg3=140735288611840, arg4=34743148, arg5=140328415983648, arg6=1) at fmgr.c:1439 #10 0x0000000000487c64 in index_insert (indexRelation=0x7fa0c1634838, values=0x7fff7ce292a0, isnull=0x7fff7ce29400 "", heap_t_ctid=0x212236c, heapRelation=0x7fa0c162f820, checkUnique=UNIQUE_CHECK_YES) at indexam.c:216 #11 0x00000000005f29aa in ExecInsertIndexTuples (slot=0x21167c0, tupleid=0x212236c, estate=0x2115e60) at execUtils.c:1087 #12 0x0000000000605273 in ExecInsert (slot=0x21167c0, planSlot=0x21167c0, estate=0x2115e60, canSetTag=1 '\001') at nodeModifyTable.c:248

create table tt(a text unique); select pg_backend_pid(); begin; insert into tt values (‘aa’), (‘bb’)

select pg_backend_pid(); begin; insert into tt values (‘bb’), (‘aa’)

(gdb) b _bt_doinsert Breakpoint 1 at 0x488fc7: file nbtinsert.c, line 106. (gdb) c Continuing. !Breakpoint 1, _bt_doinsert (….) at nbtinsert.c:106 (gdb) c Continuing. !Breakpoint 1, _bt_doinsert (….) at nbtinsert.c:106 ‘aa’をinsertしたところで止める !(gdb) c

(gdb) b _bt_doinsert Breakpoint 1 at 0x488fc7: file nbtinsert.c, line 106. (gdb) c Continuing. !Breakpoint 1, _bt_doinsert (….) at nbtinsert.c:106 (gdb) c Continuing. !Breakpoint 1, _bt_doinsert (….) at nbtinsert.c:106 ‘bb’をinsertしたところで止める !(gdb) c

psql psql

gdb gdb

回避方法2013-10-30 00:11:22 JST DETAIL: Process 3225 waits for ShareLock on transaction 11759339; blocked by process 3040. Process 3040 waits for ShareLock on transaction 11759337; blocked by process 3225. Process 3225: insert into foo (x,y,z) select x, 12345, z from bar where x in (( values (1), (2), (3) ) except ( select x from foo where date = ‘…’)) ORDER BY x ! Process 3040: insert into foo (x,y,z) select x, 12345, z from bar where x in (( values (2), (3), (1) ) except( select x from foo where date = ‘...')) ORDER BY x !

#5 TV放映

9/9 19:00-21:00 NTV

有吉ゼミ

AWS

• instanceをscale up

• m1.large → m3.2xlarge

• app server増強

• Elastic LoadBalancerのpre-warming

PERFORMANCE TEST

• pgbenchを使用

• tps(transaction per second)を知ることができる

• -f 実行したいSQLファイル

• -c 並列数

• -t トランザクション数

• New Relic/CloudWatchでパフォーマンスを監視

当日

• warmupを直前で実施

• master/slave

•あとは祈るのみ

振り返り

• 3000over req/sec

• app serverが持ちこたえられない時間帯(数分)が発生

•ゴールデン放送のアクセスはすげー

• DBについては、checkpointのチューニングが甘く、disk書き込みが結構発生した

#6 TV放映(再放送)

10/614:00-16:00 NTV

有吉ゼミ(再)

再放送をたまたま気がついたのが2日前

(教えてよ。。。)

postgresql.conf• memory

• shared_buffer=搭載メモリの1/4程度

• wal_buffer=16MB

• checkpoint

• checkpoint_segments = 64

• checkpoint_timeout = 1h

• checkpoint_completion_target = 0.9

!

• planner

• effective_cache_size=page cacheのサイズ

• random_page_cost=2.0

• lock

• deadlock_timeout = 10s

振り返り

•問題なく捌けた

•再放送は連絡こないので要注意

4000%?

まとめ

PostgreSQL使ってみて

•スタートアップのサービスで使うRDBMSとしてよい

•機能面、コスト面

•もちろんMySQLもすごいと思います

•運用も工夫次第で色々できる

•一台で結構なリクエスト数を耐えられる!

宣伝

エンジニア絶賛募集中! (特にインフラやりたい人)

http://www.smapo.jp/recruit/index.html

おわり