sábado, 8 de outubro de 2016

Trabalhando com transações no BRCache com PHP



Resumidamente, uma transação é uma coleção de operações que desempenha uma função lógica única. Ela tem que ter as seguintes propriedades:

  • atomicidade: todas as operações contidas na transação são tratadas como uma única unidade. Todas as operações são feitas ou nada é feito.
  • consistência: uma transação concluída deixa o sistema em um estado interno consistente.
  • isolamento: em um ambiente de múltiplas transações, uma não interfere na outra.
  • durabilidade: os resultados das transações são armazenados permanentemente no sistema.

Níveis de isolamento suportados


Na atual versão do BRCache, somente o nível de isolamento READ COMMITTED é suportado. Ele permite que uma transação leia e manipule os dados já confirmados por outras transações. As alterações dentro de uma transação somente ficarão visíveis para as demais após a sua confirmação. Esse nível de isolamento evita a leitura suja, porém, permite a leitura fantasma e a leitura não repetitiva.

Iniciando uma transação


No BRCache, uma transação é iniciada com o uso do método setAutoCommit() com o parâmetro igual a false e finalizada com o método commit(), rollback() ou setAutoCommit com o parâmetro true.

O método setAutoCommit


O método setAutoCommit() define o modo de confirmação automática. Se o modo de confirmação automática estiver ligado, todas as operações serão tratadas como transações individuais. Caso contrário, as operações serão agrupadas em uma transação que deve ser confirmada com o método commit() ou descartadas com o método rollback. Por padrão, cada nova conexão inicia com o modo de confirmação automática ligada.

//inicia uma transação desligando o modo de confirmação automática.
$con->setAutoCommit(false);
...
//confirma todas as operações ligando o modo de confirmação automática.
$con->setAutoCommit(true);

O método commit


O método commit() confirma todas as operações da transação atual e libera todos os bloqueios detidos pela atual sessão.

//inicia uma transação.
$con->setAutoCommit(false);
...
//confirma todas as operações.
$con->commit();

O método rolback


O método rollback() desfaz todas as operações da transação atual e libera todos os bloqueios detidos pela atual sessão.

try{
    //inicia uma transação.
    $con->setAutoCommit(false);
    ...
    //confirma todas as operações.
    $con->commit();
}
catch(Exception $e){
    //Desfaz todas as operações.
    $con->rollback();
}

Evitando a leitura fantasma e leitura não repetitiva


Para resolver o problema de leitura fantasma e leitura não repetitiva, o BRCache oferece a opção de bloquear um determinado item no momento de sua seleção. A opção é o parâmetro forUpdate do método get.
No exemplo abaixo, depois que o método get() é executado, o item key fica bloqueado até que o método commit() ou rollback seja executado.

try{
    //inicia uma transação.
    $con->setAutoCommit(false);
    //bloqueia o item key.
    $value = $con->get("key", true);
    if($value != null && strcmp($value,"val") == 0){
        //remove o item key se existir e for igual ao valor 'val'
        $con->remove($key);
    }
    //confirma todas as operações.
    $con->commit();
}
catch(Exception $e){
    //Desfaz todas as operações.
    $con->rollback();
}

Exemplo


Para exemplificar uma transação com o BRCache, será usado o clássico exemplo de uma transação bancária.
Basicamente, uma transação bancária é dividida nas operações de débito e crédito. Um determinado valor é debitado de uma conta e creditado em outra.

No exemplo serão criadas as funções init(), printValue(), transaction() e exception().

A função init() irá iniciar a conta #00001 com 1000 e a conta #00002 com 300.

function init($con){
 $con->put("#00001", 1000); 
 $con->put("#00002", 300); 
}

A função printValue() irá imprimir o valor total de cada conta.

function printValue($con){
 
 echo "-----------------\r\n";
 $money = $con->get("#00001");
 echo "#00001";
 echo "total: " . $money . "\r\n";
 
 $money = $con->get("#00002");
 echo "#00002";
 echo "total: " . $money . "\r\n";
}

A função transaction() irá transferir um valor da conta #00001 para a conta #00002. Neste processo, pode ocorrer um erro na função exception(). Justamente no local onde os dados do sistema podem ficar inconsistentes.

function transaction($con, $value){
 
 $originMoney = $con->get("#00001");
 $originMoney = $originMoney - $value;
 $con->put("#00001", $originMoney);
 
 exception();
 
 $destMoney = $con->get("#00002");
 $destMoney = $destMoney + $value;
 $con->put("#00002", $destMoney);
 
}

A função exception() simula um erro. Ela pode ou não lançar uma exceção.

function exception(){
 throw new Exception("some error");
}

Sem o controle transacional, o trecho principal ficaria:

$con = new BRCacheConnection();
init($con);
echo "before transaction\r\n";
printValue($con);
try{
    transaction($con, 300);
}
catch(Exception $e){
    echo "erro: " + $e->getMessage();
    echo "-----------------\r\n";
}
echo "after transaction\r\n";
printValue($con);

A saída do trecho principal em um cenário sem erros seria:

before transaction
-----------------
#00001
total: 1000
#00002
total: 300
after transaction
-----------------
#00001
total: 700
#00002
total: 600

Note que a transação bancária ocorreu normalmente, mas a saída do mesmo trecho em um cenário com um erro seria:

before transaction
-----------------
#00001
total: 1000
#00002
total: 300
erro: some error
-----------------
after transaction
-----------------
#00001
total: 700
#00002
total: 300

Perceba que os dados agora estão inconsistentes. Foi debitado da conta de origem, mas não foi creditado na conta de destino. Para resolver esse problema isolamos a função transaction() dentro de uma transação. O novo trecho principal seria:

$con = new BRCacheConnection();
init($con);
echo "before transaction\r\n";
printValue($con);
$con->setAutoCommit(false);
try{
    transaction($con, 300);
    $con->commit();
}
catch(Exception $e){
    $con->rollback();
    echo "erro: " + $e->getMessage();
    echo "-----------------\r\n";
}
echo "after transaction\r\n";
printValue($con);

A saída do novo trecho em um cenário sem erros seria:

before transaction
-----------------
#00001
total: 1000
#00002
total: 300
after transaction
-----------------
#00001
total: 700
#00002
total: 600

A transação bancária ocorreu normalmente, mas a saída do mesmo trecho em um cenário com um erro seria:

before transaction
-----------------
#00001
total: 1000
#00002
total: 300
erro: some error
-----------------
after transaction
-----------------
#00001
total: 1000
#00002
total: 300

Perceba que mesmo ocorrendo um erro, os dados do sistema permaneceriam consistentes.

Conclusão 


 O uso de transações no BRCache é intuitivo e fácil de usar. Se possuir familiaridade com transações em banco de dados relacional, o entendimento será mais fácil.

Nenhum comentário:

Postar um comentário