DEV Community

Leandro Luiz G. Cavalheiro
Leandro Luiz G. Cavalheiro

Posted on

5 1 1 1

GUID como Chave Primária

A muito se discute sobre usar ou não usar GUID ( ou UUID para outras linguagens ). Fato é, que temos vantagens e desvantagens ao usarmos essa abordagem, e que não irei abordar todas por aqui, já que existe muitos vídeos e artigos sobre o assunto. Aqui pretendo olhar para apenas um desses pontos "contra" ou pelo menos polêmico: o uso do GUID como chave primária.
A polêmica toda é em torno da perda de performance ao gravar esse dado no banco de dados, devido a sua característica de não ser ordenável, uma vez que é gerado aleatoriamente ( mesmo que ainda siga algumas regras ).
Então bora entender se realmente devemos evitar um GUID como chave primária. E lembrando, que minhas referências, logicamente serão para .NET nos códigos mostrados, mas esse assunto é agnóstico quanto a linguagem de programação.

Comumente, no .NET, usamos o gerador de GUID nativo, lá do namespace System e esse gerador nos devolve, um GUID na versão 4, que é um modelo de GUID não ordenável. Exemplo a seguir:

var myId = Guid.NewGuid();
//75bdd495-698a-487e-a792-3a5be46bdf6e
//3e448b53-b53b-42d0-9ce2-afa666b20671
Enter fullscreen mode Exit fullscreen mode

E por se tratar de um padrão já conhecido, não vou me aprofundar, mas, notamos que realmente são valores aleatórios, fazendo o banco banco trabalhar além do que deveria para reordenar essa PK a cada novo registro inserido no banco e em um possível select ordenado pela PK, o segundo valor gerado em nosso exemplo acima, viria para a primeira posição, "bagunçando" assim nossos registros.

Mas então, não é boa ideia utilizarmos GUID em uma PK, certo?
Olhando única e exclusivamente para esse ponto, realmente o uso seria desencorajado. Mas como disse no início do post, existem outros pontos a serem levados em consideração, para a escolha do um GUID como PK.

Então como solucionar ( ou minimizar ) esse efeito colateral?
O .Net gera nativamente GUIDs na versão 4, que são GUIDs randômicos, mas a boa notícia é que temos o GUID na versão 7. Na v7, esses GUIDs são ordenáveis: sim, isso mesmo ordenáveis. E melhor, ainda assim, é mantida a aleatoriedade, ou seja, temos um dado ordenável, que não exigirá trabalho extra do banco de dados ao inserir um novo registro e de quebra ainda é um valor com parte randômica, ideal para uso em nossas APIs. E lembrando, que teremos essa nova versão do GUID nativamente no .NET, que será lançado em Novembro de 2024.

Mas como é possível?
Aí que entra a "mágica".
Um GUID é formado por 16 bytes, e na v7 os 6 primeiros bytes representa a data em que foi gerado. O código a seguir exemplifica como é gerado essa parte do GUID:

Span<byte> uuidAsBytes = stackalloc byte[16];
var currentDate = BitConverter.GetBytes(DateTimeOffset.UtcNow.ToUnixTimeMilliseconds());
if (BitConverter.IsLittleEndian)        
   Array.Reverse(currentDate);
currentDate.AsSpan(2..8).CopyTo(uuidAsBytes);
Enter fullscreen mode Exit fullscreen mode

ToUnixTimeMilliseconds, retorna um long, que representa os milissegundos passados desde 01/01/1970 e que pode representar um range quase infinito de datas, tanto para o passado, quanto para o futuro.
Então, nessa linha, temos os bytes que representam o ponto no tempo onde foi gerado o valor.

Temos que nos atentar, a existência de sistemas BigEndian e LittleEndian, onde resumindo, são formas que os bytes são organizados na memória. Sendo que em sistemas LittleEndian, vamos inverter os bytes, para que o byte mais significativo sejam armazenados primeiro, e isso, é importante para o próximo passo.

Como dito antes, usamos 6 bytes para representação de data, e temos 8 bytes gerados nos passos anteriores. Então, como não precisamos das representação de milhões de anos, podemos descartar os 2 primeiros bytes (aqui a importância dos bytes mais significativos primeiro), e utilizar apenas os 6 últimos, assim temos nosso carimbo de tempo, para nosso UUID.

Agora, temos a parte randômica que é mais simples:

RandomNumberGenerator.Fill(uuidAsBytes[6..]);
Enter fullscreen mode Exit fullscreen mode

Simplesmente iremos usar a RandomNumberGenerator do namespace System.Security.Cryptography, para preencher o restante do bytes, com números randômicos.

O próximo passo é ajustar o byte 6, para definir que é um GUID v7, com as seguintes ações:

uuidAsBytes[6] &= 0x0F;
uuidAsBytes[6] += 0x70;
Enter fullscreen mode Exit fullscreen mode

uuidAsBytes[6] &= 0x0F: Limpa (zera) os 4 bits mais significativos do 6º byte.
uuidAsBytes[6] += 0x70: Define os 4 bits mais significativos como 0111 para indicar que este GUID é da versão 7.

E pronto, temos nosso GUID v7.
Abaixo o código completo:

public static Guid GuidV7()
{
    Span<byte> uuidAsBytes = stackalloc byte[16];
    var currentDate = BitConverter.GetBytes(DateTimeOffset.UtcNow.ToUnixTimeMilliseconds());
    if (BitConverter.IsLittleEndian)        
        Array.Reverse(currentDate);
    currentDate.AsSpan(2..8).CopyTo(uuidAsBytes);

    RandomNumberGenerator.Fill(uuidAsBytes[6..]);
    uuidAsBytes[6] &= 0x0F;
    uuidAsBytes[6] += 0x70;
    return new(uuidAsBytes, true);
}
Enter fullscreen mode Exit fullscreen mode

E aqui um benchmark de geração de GUIDs que fiz comparando as versões 4 e 7:

Image description

Mesmo com a chegada desse recurso nativamente logo mais no .NET, sei que muitos devs só utilizam as versões LTS e como o .NET 9 não é LTS, criei um método e adicionei em um Nuget: WeNerds.Commons.

Para utilização é bem simples, basta instalar o pacote e após importar com using, chamar o método estático WeMethods.NewGuid();

using WeNerds.Commons;
var myGuidV7 = WeMethods.NewGuid();

Enter fullscreen mode Exit fullscreen mode

Billboard image

The fastest way to detect downtimes

Join Vercel, CrowdStrike, and thousands of other teams that trust Checkly to streamline monitoring.

Get started now

Top comments (1)

Collapse
 
pedrostefanogv profile image
Pedro Stéfano

Bacana a abordagem, vou testar em um projeto

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs

👋 Kindness is contagious

Dive into an ocean of knowledge with this thought-provoking post, revered deeply within the supportive DEV Community. Developers of all levels are welcome to join and enhance our collective intelligence.

Saying a simple "thank you" can brighten someone's day. Share your gratitude in the comments below!

On DEV, sharing ideas eases our path and fortifies our community connections. Found this helpful? Sending a quick thanks to the author can be profoundly valued.

Okay