Campos compostos ou hash, qual é mais eficiente?

Andre Luis Wisnheski
5 min readJul 6, 2024

--

Gerado com IA ∙ 26 de junho de 2024 às 8:07 AM

Ao longo da evolução tecnológica, a eficiência e a segurança no armazenamento de dados têm se tornado questões cruciais para desenvolvedores e administradores de bancos de dados. Em um estudo recente, investiguei a performance do banco de dados ao utilizar diferentes abordagens para a estruturação de tabelas: uma gravando dados com hash SHA-512 e outra utilizando uma chave composta de três atributos. Este artigo busca explorar os resultados desse estudo, oferecendo insights sobre as vantagens e desvantagens de cada método baseadas nas evidências do teste realizado

Configuração

SO: Linux Mint 21.2
Processador: 11th Gen Intel(R) Core(TM) i7–1185G7 @ 3.00GHz
Memória: 32Gb
Python 3.10.12
PostgreSQL 15.3

Insersão dos dados

No código apresentado, realizamos um estudo comparativo entre duas abordagens de inserção de dados em um banco de dados PostgreSQL: utilizando um hash SHA-512 e uma chave composta. O objetivo é avaliar a performance de cada método ao gravar um grande volume de registros. Vamos detalhar cada parte do código:

Conexão com o Banco de Dados

Primeiramente, estabelecemos a conexão com o banco de dados PostgreSQL usando a biblioteca `psycopg2`:

import psycopg2

conn = psycopg2.connect(
host="localhost",
database="postgres",
user="guest",
password="guest"

cur = conn.cursor()
cur.execute("SET search_path TO public")

2. Funções de Inserção de Dados

Definimos duas funções para inserir dados nas respectivas tabelas: `tabela_hash` e `tabela_composta`.

Função insert_data_hash:
Esta função insere registros na tabela que utiliza um hash SHA-512 como um dos campos. Utiliza a função `generate_hash` para criar o hash a partir do valor do índice.

def insert_data_hash(qtde_registro):
for i in range(qtde_registro):
hex_dig = generate_hash(i)
cur.execute("INSERT INTO \"public\".tabela_hash (id, hash, documento_id) VALUES (%s, %s, %s)", (i, hex_dig, i))
conn.commit()

Função insert_data_composto:
Esta função insere registros em uma tabela que utiliza uma chave composta de três campos (atributo, tipo_documento, valor).

def insert_data_composto(qtde_registro):
for i in range(qtde_registro):
cur.execute("INSERT INTO \"public\".tabela_composta (id, atributo, tipo_documento, valor, documento_id) VALUES (%s, %s, %s, %s, %s)", (i, 'NUMERO_NOTA_FISCAL', 0, str(i), i))
conn.commit()

3. Função de Geração de Hash

A função generate_hash utiliza o algoritmo SHA-512 para gerar um hash hexadecimal a partir de um valor numérico:

import hashlib
def generate_hash(value):
hash_object = hashlib.sha512(str(value).encode())
hex_dig = hash_object.hexdigest()
return hex_dig

4. Execução do Teste de Performance

No bloco principal do script, definimos a quantidade de registros (qtde_registro) a serem inseridos e medimos o tempo de execução para cada abordagem utilizando a biblioteca time.

import time

if __name__ == '__main__':
qtde_registro = 1000000
start_time = time.time()
insert_data_hash(qtde_registro)
print(f"tempo gravacao hash….:", time.time() - start_time)
start_time = time.time()
insert_data_composto(qtde_registro)
print(f"tempo gravacao composto:", time.time() - start_time)
# Close the cursor and connection
cur.close()
conn.close()

Resultados

Os tempos de gravação obtidos foram os seguintes:

  • Tempo de gravação com Hash SHA-512: 56.52 segundos
  • Tempo de gravação com Chave Composta: 54.99 segundos

Análise

Os resultados mostram uma diferença relativamente pequena entre os dois métodos de inserção:

  • Hash SHA-512: A geração do hash e a inserção dos registros levou um total de 56.52 segundos. Este tempo inclui o processamento adicional necessário para gerar o hash SHA-512 para cada registro.
  • Chave Composta: A inserção dos registros utilizando uma chave composta levou 54.99 segundos. Este método foi ligeiramente mais rápido, possivelmente devido à ausência do processamento extra necessário para gerar um hash.

Apesar da diferença de aproximadamente 1.53 segundos em favor da chave composta, ambos os métodos demonstraram eficiência na gravação de um milhão de registros em um tempo relativamente curto. A escolha entre as duas abordagens pode depender de fatores adicionais, como:

  • Necessidades de Segurança: O uso de hashes, como o SHA-512, pode oferecer benefícios de segurança adicionais, como a anonimização e integridade dos dados.
  • Complexidade da Consulta: Chaves compostas podem facilitar determinadas consultas que dependem de múltiplos atributos para a identificação dos registros.
  • Manutenção e Legibilidade: Tabelas com chaves compostas podem ser mais intuitivas para manutenção e leitura direta dos dados armazenados.

Performace na Consulta

Comparação de Desempenho em Consultas no PostgreSQL: Uso de Hash vs. Pesquisa Composta

1. Código do Experimento

O código a seguir se conecta a um banco de dados PostgreSQL e executa uma série de consultas usando dois métodos diferentes: hashing e consultas compostas. A função search_data realiza ambas as pesquisas e compara o tempo de execução de cada uma.

import psycopg2
import random
import hashlib
import time

# Conexão com o banco de dados PostgreSQL
conn = psycopg2.connect(
host="localhost",
database="postgres",
user="guest",
password="guest"
)

# Criação de um cursor
cur = conn.cursor()

cur.execute("SET search_path TO public")

def generate_hash(value):
hash_object = hashlib.sha512(str(value).encode())
return hash_object.hexdigest()

def search_data(qtde_registro):
start_time = time.time()
random_id = random.randint(0, qtde_registro - 1)
hex_dig = generate_hash(random_id)

# Pesquisa usando hash
cur.execute("SELECT * FROM public.tabela_hash WHERE hash = %s", (hex_dig,))
time_hash = time.time() - start_time

# Pesquisa composta
start_time = time.time()
cur.execute("SELECT * FROM public.tabela_composta WHERE atributo = %s AND tipo_documento = %s AND valor = %s",
('NUMERO_NOTA_FISCAL', 0, str(random_id)))
time_composto = time.time() - start_time

count_hash = 1 if time_hash > time_composto else 0
count_composto = 1 if time_composto > time_hash else 0

return count_hash, count_composto

if __name__ == '__main__':
qtde_registro = 1000000
count_total_hash = 0
count_total_composto = 0
count_search = 2000

for _ in range(count_search):
count_hash, count_composto = search_data(qtde_registro)
count_total_hash += count_hash
count_total_composto += count_composto

print(f"hash --> {count_total_hash} > {((count_total_hash / count_search) * 100):.2f}%",
f"completo --> {count_total_composto} > {((count_total_composto / count_search) * 100):.2f}%")

cur.close()
conn.close()

2. Resultados do Experimento

Os resultados do experimento mostram a porcentagem de vezes que cada método foi mais rápido ao executar 2000 consultas. Aqui estão os resultados gerados:

hash --> 1519  >  75.95%   completo --> 481  >  24.05%
hash --> 1666 > 83.30% completo --> 334 > 16.70%
hash --> 1687 > 84.35% completo --> 313 > 15.65%
hash --> 1705 > 85.25% completo --> 295 > 14.75%
hash --> 1722 > 86.10% completo --> 278 > 13.90%
hash --> 1743 > 87.15% completo --> 257 > 12.85%

3. Análise dos Resultados

Os dados indicam uma clara vantagem na utilização de hash para a maioria das consultas. A pesquisa com hash foi mais rápida em aproximadamente 75–87% das vezes, enquanto as consultas compostas foram mais eficientes apenas em 13–24% das vezes.

Conclusão

Este experimento demonstra que o uso de hashing pode ser significativamente mais eficiente do que consultas compostas em cenários específicos. No entanto, é essencial considerar o contexto e a natureza dos dados antes de implementar uma abordagem definitiva. Consultas compostas podem ser mais vantajosas em situações onde a integridade e a precisão dos dados são críticas, enquanto o hashing pode acelerar o tempo de resposta para grandes volumes de dados.

Para desenvolvedores e DBAs, realizar testes como este em seus próprios ambientes de banco de dados é crucial para tomar decisões informadas sobre otimização de consultas.

--

--