Tenho duas tabelas PostgreSQL: objects
e metadata
. Cada objeto tem uma size
propriedade que representa seu tamanho em bytes e pode ser armazenado em um local exclusivo com storage_id
. Nos metadados, a total_size
propriedade de todos os objetos de cada armazenamento com um dado storage_id
é mantida. Tabelas simplificadas:
CREATE TABLE IF NOT EXISTS objects (
object_id UUID PRIMARY KEY,
storage_id UUID NOT NULL,
size BIGINT NOT NULL,
FOREIGN KEY (storage_id) REFERENCES metadata(storage_id)
);
CREATE TABLE IF NOT EXISTS metadata (
storage_id UUID PRIMARY KEY,
total_size BIGINT DEFAULT 0
);
Para manter o total_size
na metadata
tabela, tenho gatilhos que sempre que um objeto é inserido ou excluído, ele total_size
é atualizado, ou seja, aqui está o gatilho para inserção:
CREATE OR REPLACE FUNCTION update_size_on_insert() RETURNS TRIGGER AS $$
BEGIN
UPDATE metadata
SET total_size = total_size + NEW.size
WHERE storage_id = NEW.storage_id;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE TRIGGER trg_update_size_on_insert
AFTER INSERT ON objects
FOR EACH ROW
EXECUTE FUNCTION update_size_on_insert();
O problema é que inserções/exclusões simultâneas podem sobrescrever o comando, o total_size
que leva a dados inválidos. Como posso alterar o gatilho para que ele contenha um bloqueio em nível de linhaSET total_size = total_size + NEW.size
quando o comando for executado? Eu estava pensando na FOR UPDATE
instrução, mas isso requer um comando SELECT
. Posso usar PERFORM ... FOR UPDATE;
para bloquear a linha?
Você poderia, mas isso apenas duplicaria o bloqueio que você
update
já adquiriu por conta própria, como imediatamente apontado por @Iłya Bursov . Você pode ver isso empg_locks
:demo em db<>fiddle
Uma está na mesa, a outra no índice, reforçando sua chave primária .
Conforme sugerido por @Frank Heikens , você pode querer trocar o
update
por um upsert , para garantir também que suas inserções iniciais sejam tratadas com segurança, e adicionar umcoalesce()
para cobrir quaisquer atualizações e exclusões, conforme recomendado por @Charlieface :Para deixá-lo como
after
gatilho, você precisará alterar a restrição FK. Aproveite e considere umaCHECK
restrição para garantir que ninguém insira objetos de tamanho negativo:A diferibilidade permite que o gatilho tenha tempo para adicionar a nova
storage_id
restrição antes que ela seja validada. Sem isso, o gatilho teria que ser disparadobefore
.demonstração no db<>fiddle
Agora os metadados foram preenchidos automaticamente:
Adicionando um objeto ao armazenamento existente:
novos objetos aumentaram o tamanho total
Atualizando o tamanho de qualquer objeto arbitrário:
Opcionalmente, você pode expandir o gatilho para também remover os totais de armazenamento não utilizado que
total_size
foram reduzidos a zero.Para manipular um
truncate
evento, você também precisa de um gatilho de nível de instrução separado - mesmo que isso exclua todas as linhas de uma tabela, ele não disparaon delete
gatilhos.@Adrian Klaver é
view
uma boa coisa a se considerar:materialized view
e armazená-los em cache periodicamente.