2014-09-02 1 views
2

У меня есть таблица, в которой есть следующие поля:Принудительного рыхлое ограничения в Postgres между несколькими клиентами

entry_id BIGSERIAL PRIMARY KEY, 
site_id BIGINT NOT NULL, 
uuid VARCHAR(256) NOT NULL, 
session_start TIMESTAMP NOT NULL, 
session_end TIMESTAMP NOT NULL, 
user_ip VARCHAR(40) NOT NULL, 
user_agent VARCHAR(256) NOT NULL, 

Теперь у меня есть много входящих запросы, которые имеют кортежи данных по линиям (site_id, uuid, timestamp, user_ip, user_agent).

Мое правило состоит в том, что если в базе данных есть запись менее 3 часов (session_end), то входящий запрос обновляет session_end = timestamp. Если нет, создайте новую запись (где session_start = session_end = timestamp).

Входящие запросы обрабатываются несколькими процессами. Так сказать, 3-4 входящие запросы попали на мои серверы с теми же данными (разные временные метки, но в миллисекундах) и обрабатываются тремя различными процессами - как мне избежать создания трех разных записей (если они все проверяют одновременно, см. нет записей соответствия и каждый создать новый)? Это вопрос состояния гонки, и я не уверен, как обеспечить его соблюдение.

Столбчатый замок кажется излишним, так как это тяжелая таблица с записью, но какие альтернативы у меня есть вне механизма блокировки третьей стороны?

Пример:

Format: 
(site_id, uuid, timestamp, user_ip, user_agent) 

Incoming requests/data: 
(1, 123, 2014-01-01T10:00:32, '123.123.123.123', 'Mozilla/Chrome') 
(1, 123, 2014-01-01T10:00:33, '123.123.123.123', 'Mozilla/Chrome') 
(1, 123, 2014-01-01T10:00:34, '123.123.123.123', 'Mozilla/Chrome') 

Result tuple: 
entry_id | site_id | uuid | session_start  | session_end   | user_ip | user_agent 
-------------------------------------------------------------------------------------------- 
<auto> |  1 | 123 | 2014-01-01T10:00:32 | 2014-01-01T10:00:34 | ...  | ... 
+0

насчет 'индекса UNIQUE' и обработки ошибок вставки в приложениях? –

+0

Как бы вы сделали UNIQUE-заявление за период времени? (site_id, uuid, user_ip, user_agent) могут быть установлены уникально вместе, но допускается несколько строк, если две метки времени session_start/session_end не находятся в пределах 3 часов друг от друга. –

+0

Похоже, эти временные метки являются естественными клавишами. – supertopi

ответ

2

Создать gist исключения ограничения с timestamp range типа

create table request (
    entry_id bigserial primary key, 
    site_id bigint not null, 
    uuid varchar(256) not null, 
    session_start timestamp not null, 
    session_end timestamp not null, 
    user_ip varchar(40) not null, 
    user_agent varchar(256) not null, 
    constraint session_overlap exclude using gist (
     site_id with =, 
     uuid with =, 
     user_ip with =, 
     user_agent with =, 
     tsrange(session_end, session_end + interval '3 hours', '[)') with && 
    ) 
); 

Теперь, когда вставка не удается:

insert into request (site_id, uuid, session_start, session_end, user_ip, user_agent) 
select site_id, uuid, ts::timestamp, ts::timestamp, user_id, user_agent 
from (values 
    (1, '123', '2014-01-01T10:00:32', '123.123.123.123', 'Mozilla/Chrome'), 
    (1, '123', '2014-01-01T10:00:33', '123.123.123.123', 'Mozilla/Chrome'), 
    (1, '123', '2014-01-01T10:00:34', '123.123.123.123', 'Mozilla/Chrome') 
) s(site_id, uuid, ts, user_id, user_agent) 
; 
ERROR: conflicting key value violates exclusion constraint "session_overlap" 
DETAIL: Key (site_id, uuid, user_ip, user_agent, tsrange(session_end, session_end + '03:00:00'::interval, '[)'::text))=(1, 123, 123.123.123.123, Mozilla/Chrome, ["2014-01-01 10:00:33","2014-01-01 13:00:33")) conflicts with existing key (site_id, uuid, user_ip, user_agent, tsrange(session_end, session_end + '03:00:00'::interval, '[)'::text))=(1, 123, 123.123.123.123, Mozilla/Chrome, ["2014-01-01 10:00:32","2014-01-01 13:00:32")). 

Вам может понадобиться установить btree_gist расширение как суперпользователь

create extension btree_gist; 

http://www.postgresql.org/docs/current/interactive/btree-gist.html

+0

Хорошее предложение, но для простоты внедрения и для упрощения смены ограничений (скажем, от 3 до 5 часов) я пойду с помощью решения для консультативной блокировки. Я не знал об исключении ограничений gist, так что спасибо за то, что научил меня, что :) –

+0

@Christian: Я не понимаю. Первый процесс блокирует и вставляет. Затем другой процесс ждет, пока блокировка не будет отпущена, и вставляет нарушающий кортеж. Разве это не то, чего вы хотите избежать? Что вы набираете, просто ожидая? –

+0

Я делаю некоторые другие проверки, поэтому я в основном удерживаю блокировку до начала всего конвейера, а затем отпускаю его в конце, предотвращая неправильное вложение любых кортежей. Следствие. Что происходит быстрее - попытка и наличие ошибки ограничения или проверки, а затем вставки? –

1

Заканчивать рекомендательный locks.

SELECT pg_advisory_lock(key); 
// INSERT OR UPDATE... 
SELECT pg_advisory_unlock(key); 

Или с помощью Номве блокирующие версии:

SELECT pg_try_advisory_lock(key) INTO :acquired; 
// if (acquired) then INSERT OR UPDATE... 
SELECT pg_advisory_unlock(key); 
+0

А, это позволит мне только заблокировать небольшой подмножество запросов одновременно, это было бы прекрасно! –

+0

@ChristianP. да, вы здесь гибкие, так как вы можете определить свой собственный ключ, который может охватывать многие наборы, которые вы хотите. – Vlad

+0

Точно. Я, вероятно, просто добавлю разные поля вместе, исключая метку времени и заблокирую ее (вычисление a.Образец состоит в том, что первые 2-4 запроса поступают сразу после друг друга, а затем они выделяются, что делает его менее проблематичным. Ура! –

 Смежные вопросы

  • Нет связанных вопросов^_^