状況に応じてシリアル化されたアクセスを必要とする関数がある場合に問題が発生しています。これは、アドバイザリ ロックを使用する良いケースのように思えました。ただし、かなりの負荷がかかると、シリアル化されたアクセスが発生しておらず、関数への同時アクセスが発生していることがわかりました。
この機能の目的は、イベントの「在庫管理」を提供することです。つまり、イベントが売り過ぎないように、特定のイベントの同時購入を制限することを目的としています。これらは、アプリケーション/データベース内で使用される唯一のアドバイザリ ロックです。
イベントには、eventTicketMax の値よりも多くのチケットが含まれていることがあります。これは、アドバイザリ ロックのために可能であるようには見えません。少量 (またはロックを取得した後に pg_sleep などの手動で導入された遅延) でテストすると、期待どおりに動作します。
CREATE OR REPLACE FUNCTION createTicket(
userId int,
eventId int,
eventTicketMax int
) RETURNS integer AS $$
DECLARE insertedId int;
DECLARE numTickets int;
BEGIN
-- first get the event lock
PERFORM pg_advisory_lock(eventId);
-- make sure we aren't over ticket max
numTickets := (SELECT count(*) FROM api_ticket
WHERE event_id = eventId and status <> 'x');
IF numTickets >= eventTicketMax THEN
-- raise an exception if this puts us over the max
-- and bail
PERFORM pg_advisory_unlock(eventId);
RAISE EXCEPTION 'Maximum entries number for this event has been reached.';
END IF;
-- create the ticket
INSERT INTO api_ticket (
user_id,
event_id,
created_ts
)
VALUES (
userId,
eventId,
now()
)
RETURNING id INTO insertedId;
-- update the ticket count
UPDATE api_event SET ticket_count = numTickets + 1 WHERE id = eventId;
-- release the event lock
PERFORM pg_advisory_unlock(eventId);
RETURN insertedId;
END;
$$ LANGUAGE plpgsql;
私の環境設定は次のとおりです。
- Django 1.8.1 (django.db.backends.postgresql_psycopg2 w/ CONN_MAX_AGE 300)
- PGBouncer 1.7.2 (セッションモード)
- Amazon RDS の Postgres 9.3.10
チューニングを試みた追加の変数:
- CONN_MAX_AGE を 0 に設定する
- pgbouncer を削除して DB に直接接続する
私のテストでは、イベントが売れ過ぎた場合、チケットは別の Web サーバーから購入されたので、共有セッションについて面白いビジネスはないと思いますが、確かなことは言えません.