Estou tendo um problema com deadlocks em uma inserção de muitos para muitos e estou muito fora do meu alcance neste momento.
Tenho uma tweet
tabela que recebe milhares de registros por segundo. Uma das colunas é um array[]::text[]
tipo PostgreSQL com urls de zero a muitos na matriz. Parece {www.blah.com, www.blah2.com}
.
O que estou tentando realizar a partir de um gatilho na tweet
tabela é criar uma entrada em uma urls_starting
tabela e, em seguida, adicionar o relacionamento tweet/url_starting em um arquivo tweet_x_url_starting
.
Observação lateral: A url_starting
tabela está vinculada a uma url_ending
tabela onde residem os caminhos de URL totalmente resolvidos.
O problema que enfrento são os impasses e não sei mais o que tentar.
Eu fui em uma farra de aprendizado Erwin Brandstetter . (se você está por aí cara... OBRIGADO! ?)
- Como implementar um relacionamento muitos-para-muitos no PostgreSQL?
- Deadlock com INSERTs de várias linhas apesar de ON CONFLICT NÃO FAZER NADA
- Postgres ATUALIZAÇÃO … LIMITE 1
(skip locked help)
Eu tentei adicionar ORDER BY para ordens determinísticas e estáveis e FOR UPDATE SKIP LOCKED, mas não tenho certeza se estou fazendo nada disso corretamente.
Aqui está a estrutura. Usando PostgreSQL 10.5 .
CREATE TABLE tweet(
id integer NOT NULL GENERATED BY DEFAULT AS IDENTITY,
twitter_id text NOT NULL,
created_at timestamp NOT NULL,
content text NOT NULL,
urls text[],
CONSTRAINT tweet_pk PRIMARY KEY (id)
);
CREATE TABLE url_starting(
id integer NOT NULL GENERATED BY DEFAULT AS IDENTITY,
url text NOT NULL,
CONSTRAINT url_starting_pk PRIMARY KEY (id),
CONSTRAINT url_starting_ak_1 UNIQUE (url)
);
CREATE TABLE tweet_x_url_starting(
id_tweet integer NOT NULL,
id_url_starting integer NOT NULL,
CONSTRAINT tweet_x_url_starting_pk PRIMARY KEY (id_tweet,id_url_starting)
ALTER TABLE tweet_x_url_starting ADD CONSTRAINT tweet_fk FOREIGN KEY (id_tweet)
REFERENCES tweet (id) MATCH FULL
ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE tweet_x_url_starting ADD CONSTRAINT url_starting_fk FOREIGN KEY (id_url_starting)
REFERENCES url_starting (id) MATCH FULL
ON DELETE CASCADE ON UPDATE CASCADE;
Aqui está o tweet
gatilho da tabela.
CREATE TRIGGER create_tweet_relationships
AFTER INSERT OR UPDATE
ON tweet
FOR EACH ROW
EXECUTE PROCEDURE create_tweet_relationships();
E por fim, a função.
CREATE FUNCTION create_tweet_relationships ()
RETURNS trigger
LANGUAGE plpgsql
VOLATILE
CALLED ON NULL INPUT
SECURITY INVOKER
COST 1
AS $$
BEGIN
IF (NEW.urls IS NOT NULL) AND cardinality(NEW.urls::TEXT[]) > 0 THEN
WITH tmp_url AS (
INSERT INTO url_starting (url)
SELECT UNNEST(NEW.urls)
ORDER BY 1
ON CONFLICT (url) DO UPDATE
SET url = EXCLUDED.url
RETURNING id
)
INSERT INTO tweet_x_url_starting (id_tweet, id_url_starting)
SELECT NEW.id, id
FROM tmp_url
ORDER BY 1, 2
FOR UPDATE SKIP LOCKED
ON CONFLICT DO NOTHING;
END IF;
RETURN NULL;
END
$$;
Eu joguei cegamente as coisas que li na função sem sucesso.
O erro se parece com isso.
deadlock detected
DETAIL: Process 11281 waits for ShareLock on transaction 1317; blocked by process 11278.
Process 11278 waits for ShareLock on transaction 1316; blocked by process 11281.
HINT: See server log for query details.
CONTEXT: while inserting index tuple (494,33) in relation "url_starting"
SQL statement "WITH tmp_url AS (
INSERT INTO url_starting (url)
SELECT UNNEST(NEW.urls)
ORDER BY 1
ON CONFLICT (url) DO UPDATE
SET url = EXCLUDED.url
RETURNING id
)
INSERT INTO tweet_x_url_starting (id_tweet, id_url_starting)
SELECT NEW.id, id
FROM tmp_url
ORDER BY 1, 2
FOR UPDATE SKIP LOCKED
ON CONFLICT DO NOTHING"
PL/pgSQL function create_tweet_relationships() line 12 at SQL statement
Error causing transaction rollback (deadlocks, serialization failures, etc).
Como posso parar os impasses? Obrigado! ?
Isso acabou se resumindo a 2 coisas.
As gravações simultâneas acabarão travando se os dados inseridos não forem classificados antes da inserção. Dentro da minha função de gatilho, todas as inserções foram classificadas, mas não havia como classificar todas as urls adicionadas simultaneamente . A única maneira de resolver esse problema era fazer backup de um nível e fazer a inserção/classificação com todo o lote de tweets, tendo assim acesso a todas as urls de uma só vez.
Mais aqui. ? Como usar RETURNING com ON CONFLICT no PostgreSQL?
Fazer isso fez uma grande diferença, mas não resolveu completamente o problema. ?
A
ON CONFLICT
cláusula pode evitar erros de chave duplicada, mas não pode impedir que transações simultâneas tentem inserir as mesmas chaves .Mais aqui. ? Deadlock com INSERTs de várias linhas apesar de ON CONFLICT NÃO FAZER NADA
Conforme mostrado na mensagem de erro na minha pergunta, houve conflitos no índice de tupla do sistema
ctid
ao fazerON CONFLICT (column) DO UPDATE
. Felizmente não precisei atualizar nenhum dado, então não houve necessidade deDO UPDATE
parte das minhas consultas.Corrigindo isso 100% parou os impasses ! ?
Aqui ? é a consulta final enviada usando python com a
execute_values()
função empsycopg2
).Isso pode exigir um exame cuidadoso, conhecimento da situação completa e mais tempo do que posso gastar agora. Ou talvez eu esteja perdendo algo óbvio. Qualquer outra coisa que possa dar errado aqui, algumas coisas se destacam:
Remova o
FOR UPDATE SKIP LOCKED
completamente. Não faz sentido onde você o usa. Ao selecionar no CTE, que já contém linhas com bloqueio exclusivo, isso não faz sentido. Também não faria sentido pular nenhuma linha neste estágio da consulta.COST 1
é enganoso. O padrão éCOST 100
e sua função de disparo está mais na esfera do COST 5000. Deixe o padrão ou defina-o mais alto. Provavelmente não relacionado ao impasse.Um
AFTER
gatilho pode ser mais suscetível a deadlocks do que reescrever todo o fluxo de trabalho com uma única consulta (com vários CTEs de modificação de dados).Meu tiro no escuro: o impasse é causado pela restrição FK tentando ativar um ShareLock
url_starting
enquanto transações simultâneas tentam modificar a mesma linha depois de terem obtido um ShareLock semelhante e vice-versa. Uma solução rápida e suja pode ser descartar a restrição FKtweet_x_url_starting
se você puder pagar por isso. Você poderia pelo menos tentar isso para verificar se é parte do problema.Se você quiser continuar sua maratona de aprendizado - aqui está mais uma que parece relevante: