Estratégia para geração de sequencial

Evandro Pires
Conta Azul Engineering Blog

--

Em diversos momentos da minha carreira me deparei com a necessidade de controlar a sequencial de alguma entidade de negócio.

Recentemente precisamos fazer isso em mais de um lugar no nosso sistema contábil.

Por conta disso, tivemos que estudar algumas possibilidades para implementar a sequencial. São dois cenários diferentes e com requisitos/restrições diferentes também.

No primeiro cenário, temos uma sequencial com a necessidade de ser controlada por tenant e tem a possibilidade de perda de numeração entre um registro e outro.

No segundo cenário, precisamos controlar a sequencial por mais critérios além do tenant e, além disso, é necessário manter rigorosamente a sequência sem perdas entre os registros.

Consideramos as seguintes possibilidades:

Lock com Redis

Utilizar o Redis como um semáforo para gerenciar as sequências pelos critérios necessários e, com isso, garantir que os números não vão colidir e nem serão perdidos.

Essa é uma abordagem funcional e que tem uma performance aceitável. Consegue atender bem sem perda de numeração entre os registros.

Entretanto, vejo um problema relevante. Como o processo é transacional, caso tenhamos algum problema nessa comunicação entra a aplicação e o Redis podemos sofrer com um idle in transaction difícil de resolver.

Além disso, é necessário alguns controles extras para a garantia da sequencial.

Tabela no banco de dados

Utilizar uma tabela no banco de dados relacional que possui os critérios necessários e também a sequencial. Para que a solução funcione adequadamente nessa abordagem é necessário realizar uma alteração atômica no registro para que o banco de dados possa fazer o controle de forma natural.

Para isso, basta realizar um update na tabela onde já é feito a adição do número sequencial e, ao mesmo tempo, é retornado o valor.

Segue um exemplo:

Fazendo isso dentro de uma transação, o banco de dados vai gerenciar automaticamente a concorrência pelo mesmo recurso e fará com que as demais transações aguardem pelo término da transação que fez a alteração do recurso anteriormente. O funcionamento prático acaba sendo semelhante a abordagem citada anteriormente, mas com a diferença que se a conexão ou transação for finalizada no banco de dados o lock é liberado. Mesmo num cenário adverso é possível, com uma intervenção simples no banco de dados, liberar o recurso com lock.

Essa abordagem funciona bem para os cenários que não tem tolerância de perda de sequencial entre os registros.

O ponto negativo fica por conta do semáforo que é gerado. Se há uma concorrência muito grande por essa sequencial, seu throughput vai ficar bem comprometido.

Sequence nativo por tenant

Utilizar sequence nativo do banco de dados para gerar a numeração. Basicamente, cada tenant tem sua sequence no banco de dados. Para que cada tenant possa ter essa sequence sem que seja necessário criar manualmente toda vez, a abordagem é utilizar uma function que trata de criar a sequence caso não exista.

Essa abordagem é interessante, pois possibilita um throughput muito bom. Ele não depende de reserva de recurso e, por esse motivo, consegue entregar muito rápido para cada vez que é requisitado.

O ponto negativo fica pelo fato de não ser transacional. Ou seja, se você solicitou a sequencial e fez rollback da transação, esse número foi perdido. Se seu cenário tolera isso, ótimo!

Basicamente a utilização seria algo como:

Dentro dessa função, teriamos a validação do catálogo do banco de dados para saber da existência da sequence e a sua criação caso não tenha. Também teríamos algo, como abaixo, para carregar a sequência:

Conclusões

Ao analisar as possibilidades citadas anteriormente decidimos pela abordagem da tabela com a sequencial para ambos cenários citados.

No caso do cenário que tem tolerância de perda de numeração entre os registros ainda temos a intenção de utilizar o sequence, para reduzir o semáforo gerado pela concorrência do registro. Mas foi uma decisão por timing e que num primeiro momento não compromete o funcional e não funcional.

*Os exemplos de SQL estão em PostgreSQL.

--

--

AWS Serverless Hero, CTO at Senior SA, Founder at Sem Servidor podcast, Husband, father of Teodoro and Olivia