Emaranhados: uma história de remodelagem de dados e redução de armazenamento 10x
Nenad Milosavljevic5 min read • Published Dec 14, 2023 • Updated Dec 14, 2023
Avalie esse Artigo
Um dos projetos mais distintos em que trabalhei é um aplicativo chamado Entulated. Desenvolvido em associação com o Laboratório de Pesquisa de Anormalidades de Engenharia (PEAR), o Projeto de Consciência Global e o Instituto de Pesquisas Noéticas, Entled tem como objetivo testar a ConsciênciaHumana.
O aplicativo utiliza um gerador de números aleatórios quânticos para medir a influência da consciência humana. Esse gerador quântico é essencial porque os computadores convencionais, devido à sua natureza determinística, não podem gerar números verdadeiramente aleatórios. O gerador quático produz sequências aleatórias de 0s e 1s. Em grandes conjuntos de dados, deve haver um número igual de 0s e 1s.
Para a geração de números aleatórios quânticos, usamos um dispositivoUSBQuantis QRNGinterno . Este dispositivo está conectado ao nosso servidor e, por meio de drivers especializados, obtemos programaticamente as sequências aleatórias diretamente do dispositivo USB.
Experimentos foram realizados para determinar se uma pessoa poderia influenciar esses dispositivos quânticas com seus ideias, especificamente ao pensar em mais 0s ou 1s. Os resultados foram surpreendentes, demonstrando o real potencial dessa influencia.
Para expandir este teste globalmente, desenvolvemos um novo aplicativo. Essa plataforma permite que os usuários se inscrevam e acompanhem suas contribuições. O sistema gera um novo número aleatório para cada usuário a cada segundo. A cada hora, essas contribuições são agrupadas para análise em nível pessoal, urbano e global. Calculamos o desvio padrão dessas contribuições e, se esse desvio exceder um determinado limite, os usuários receberão notificações.
Esses dados suportam vários experimentos. Por exemplo, no experimento "Predição de Terremoto", usamos as contribuições de todos os usuários em uma área específica. Se o desvio padrão for maior do que o limite definido, isso pode indicar que os usuários previram um abalo.
Como principal desenvolvedor de back-end, e sendo o MongoDB o meu banco de dados preferido para todos os projetos, foi uma escolha natural para a Entangled.
Para o desenvolvimento de backend, escolhai o Node.js (Express), juntamente com a biblioteca Mongoose para definição de esquema e modelagem de dados. O Mongoose, uma biblioteca de modelagem de dados de objetos (ODM) para MongoDB, é amplamente usado no ecossistema Node.js por sua capacidade de fornecer uma maneira direta de modelar os dados do aplicativo.
A modelagem cuidadosa do esquema foi crucial devido ao dimensionamento previsto do banco de dados. Lembre-se de que estávamos gerando um número aleatório por segundo para cada usuário.
Meu instinto inicial foi criar esquemas baseados em hora em hora, alinhando-se com nossos instantâneos de análise por hora. O esquema inicial foi estruturado da seguinte forma:
- Usuário: uma referência à coleção "Usuários"
- Soma Total: a soma dos números aleatórios de cada usuário; 1ou 0s, de modo que sua soma foi suficiente para análise posterior
- Gerado em: o carimbo de data/hora do snapshot
- Arquivo de dados: uma referência à coleção " Data Files ", que contém todos os números aleatórios gerados por todos os usuários em uma determinada hora
1 const { Schema, model } = require("mongoose"); 2 3 const hourlyMetricSchema = new Schema({ 4 user: { type: Schema.Types.ObjectId, ref: "Users" }, 5 total_sum: { type: Number }, 6 generated_at: { type: Date }, 7 data_file: { type: Schema.Types.ObjectId, ref: "DataFiles" } 8 }); 9 10 // Compound index forr "user" (ascending) and "generated_at" (descending) fields 11 hourlyMetricSchema.index({ user: 1, generated_at: -1 }); 12 13 const HourlyMetrics = model("HourlyMetrics", hourlyMetricSchema); 14 15 module.exports = HourlyMetrics;
Embora intuitivo, esse esquema encontrou um significativo desafio de dimensionamento. Estimamos mais de 100,000 usuários logo após o lançamento. Isso MEAN cerca 2.4 milhões de registros diários ou 72 milhões de registros mensais. Consequentemente, estávamos analisando aproximadamente 5GB de dados (incluindo armazenamento e índices) a cada mês.
Isso me motivou a explorar abordagens alternativas.
Eu explorei se abordagens alternativas de modelagem poderiam otimizar ainda mais os requisitos de armazenamento e, ao mesmo tempo, melhorar a escalabilidade e a economia.
Uma observação significativa foi que de 5GB de armazenamento total, 3.5GB estava ocupado por índices, consequência do grande volume de documentos.
Isso me levou a experimentar uma reformulação do esquema, mudando de métricas horárias para métricas diárias. O novo esquema foi estruturado da seguinte forma:
1 const { Schema, model } = require("mongoose"); 2 3 const dailyMetricSchema = new Schema({ 4 user: { type: Schema.Types.ObjectId, ref: "Users" }, 5 date: { type: Date }, 6 samples: [ 7 { 8 total_sum: { type: Number }, 9 generated_at: { type: Date }, 10 data_file: { type: Schema.Types.ObjectId, ref: "DataFiles" } 11 } 12 ] 13 }); 14 15 // Compound index forr "user" (ascending) and "date" (descending) fields 16 hourlyMetricSchema.index({ user: 1, date: -1 }); 17 18 const DailyMetrics = model("DailyMetrics", dailyMetricSchema); 19 20 module.exports = DailyMetrics;
Em vez de armazenar métricas por apenas uma hora em cada documento, agora agreguei as métricas de um dia inteiro em um único documento. Cada documento incluía uma matriz de "amostras" com 24 entradas, uma para cada hora do dia.
É importante observar que esse método é uma boa solução porque a array tem um tamanho fixo — um dia tem apenas 24 horas. Isso é muito diferente do antipadrão de usar arrays grandes e massivas no MongoDB.
Esta pequena modificação teve um impacto significativo. O requisito de armazenamento para os dados de um mês começou drasticamente de 5GB para apenas 0.49GB. Isso se deveu principalmente à diminuição no tamanho do índice, de 3.5GB a 0.15GB. O número de documentos necessários a cada mês caiu de 72 milhões para 3 milhões.
Incentivado por esses resultados, não parei por aqui. Meu próximo passo foi considerar os benefícios potenciais de mudar para um esquema de métricas mensais. Isso poderia otimizar ainda mais nosso armazenamento? Essa foi a pergunta que impulsionou minha próxima fase de pesquisa.
O esquema de métricas mensais era essencialmente idêntico ao esquema de métricas diárias. A principal diferença estava em como os dados eram armazenados na matriz "samples", que agora continha aproximadamente 720 registros que representavam as métricas de um mês inteiro.
1 const { Schema, model } = require("mongoose"); 2 3 const monthlyMetricSchema = new Schema({ 4 user: { type: Schema.Types.ObjectId, ref: "Users" }, 5 date: { type: Date }, 6 samples: [ 7 { 8 total_sum: { type: Number }, 9 generated_at: { type: Date }, 10 data_file: { type: Schema.Types.ObjectId, ref: "DataFiles" } 11 } 12 ] 13 }); 14 15 // Compound index forr "user" (ascending) and "date" (descending) fields 16 monthlyMetricSchema.index({ user: 1, date: -1 }); 17 18 const MonthlyMetrics = model("MonthlyMetrics", monthlyMetricSchema); 19 20 module.exports = MonthlyMetrics;
Esperava-se que esse ajuste reduzisse ainda mais a contagem de documentos para cerca de 100,000 documentos por um mês, levando-me a antecipar uma otimização de armazenamento ainda maior. No entanto, os resultados reais foram surpreendentes.
Ao armazenar os dados de um mês sob esse novo esquema, o tamanho do armazenamento aumentou inesperadamente de 0.49GB a 0.58GB. Esse aumento provavelmente se deve aos métodos que o storage engine WiredTiger do MongoDB usa para comprimir arrays internamente.
Abaixo está um resumo detalhado das diferentes abordagens e seus respectivos resultados para um mês de dados:
Documento por hora | Documento diário | Documento mensal | |
---|---|---|---|
Tamanho do documento | 0.098 KB | 1.67 KB | 49.18 KB |
Total de documentos (por mês) | 72,000,000 (100,000 usuários * 24 horas * 30 dias) | 3,000,000 (100,000 usuários * 30 dias) | 100,000 (100,000 usuários) |
Tamanho do armazenamento | 1.45 GB | 0.34 GB | 0.58 GB |
Tamanho do Índice | 3.49 GB | 0.15 GB | 0.006 GB |
Armazenamento Total (Dados + Índice) | 4.94 GB | 0.49 GB | 0.58 GB |
Nesta Exploração da Modelagem de Esquema para o Projeto Enredado, pesquisamos os desafios e soluções para gerenciar dados de grande escala no MongoDB.
Nossa jornada começou com métricas por hora, que, embora intuitivas, apresentavam desafios significativos de dimensionamento devido ao grande volume de dados e ao tamanho do índice.
Isso provocou uma mudança para as métricas diárias, reduzindo drasticamente os requisitos de armazenamento em mais de 10 vezes, principalmente devido a uma diminuição significativa no tamanho do índice.
A experiência com métricas mensais ofereceu uma reviravolta inesperada. Embora tenha reduzido ainda mais o número de documentos, ele aumentou o tamanho geral do armazenamento, provavelmente devido à mecânica interna de compressão do storage engine WiredTiger do MongoDB.
Este estudo de caso destaca a importância crítica do design de esquemas no gerenciamento de banco de dados, especialmente ao lidar com grandes volumes de dados. Ele também enfatiza a necessidade de experimentação e otimização contínuas para equilibrar a eficiência, a escalabilidade e o custo do armazenamento.
Se você quiser saber mais sobre como projetar esquemas eficientes com o MongoDB, recomendamos conferir a sériePadrões de modelagem de dados do MongoDB.