Imagine o seguinte cenário:
A solução que encontrei é simples: Advisory Lock global no PostgreSQL. O Advisory Lock controla a concorrência no nível lógico, não depende de índice, não bloqueia a tabela inteira. Ele impede que duas transações executem a mesma operação crítica ao mesmo tempo. Neste tutorial você aprenderá como implementar um Lock Global no CodeIgniter 3, passo a passo.
A função usada aqui é a:
pg_try_advisory_xact_lock
Ela retorna true se conseguiu o lock e, retorna false se outra transação já possui o lock. Ela não espera, falha imediatamente.
Isso resolveu o meu problema de concorrência entre contêineres.
Use lock global quando:
Fluxo da solução:
Obs: Quando a transação termina, o lock é liberado automaticamente.
Presumindo que uses o PostgreSQL no seu projeto, crie o arquivo:
application/libraries/PgLock.php
<?php
defined('BASEPATH') OR exit('No direct script access allowed');
/**
* Classe de bloqueio lógico para controle de concorrência entre aplicações
* distribuídas.
*
* @version 1.0
*
*/
class PgLock
{
protected $CI;
/**
* Método construtor
*
* @return $this
*/
public function __construct()
{
$this->CI =& get_instance();
}
/**
* Tenta obter lock global
*
* @param string $key
*
* @return boolean
*/
public function acquireGlobalLock($key)
{
$sql = "
SELECT pg_try_advisory_xact_lock(
hashtext(?)
) AS locked
";
$query = $this->CI->db->query($sql, [$key]);
return (bool) $query->row()->locked;
}
}
A função hashtext garante que todos os contêineres gerem o mesmo valor para a mesma chave.
Exemplo em um model qualquer:
<?php
class Pedido_model extends CI_Model
{
public function salvar($dados)
{
$this->db->trans_begin();
$this->load->library('PgLock');
$locked = $this->pglock->acquireGlobalLock('APP:PEDIDO:WRITE');
if (!$locked) {
$this->db->trans_rollback();
return false;
}
$this->db->insert('pedidos', $dados);
if ($this->db->trans_status() === false) {
$this->db->trans_rollback();
return false;
}
$this->db->trans_commit();
return true;
}
}
Abra duas sessões no PostgreSQL.
BEGIN;
SELECT pg_try_advisory_xact_lock(hashtext('APP:PEDIDO:WRITE'));
O resultado será true.
BEGIN;
SELECT pg_try_advisory_xact_lock(hashtext('APP:PEDIDO:WRITE'));
O resultado será false.
Isso demonstra que o lock global funciona entre conexões diferentes.
Use quando:
O Advisory Lock é leve, não trava a tabela, trava apenas a chave lógica definida por você. O impacto é menor que o LOCK TABLE.
Você reduz:
Com isso, você mantém controle de concorrência no nível da aplicação, sem alterar o banco e sem sacrificar desempenho.
E assim, chegamos ao fim do tutorial, espero que tenhas gostado. Obrigado e até a próxima postagem!