7 coisas que aprendi ao modelar dados para as estatísticas do YouTube
Avalie esse Artigo
Mark Smith, Maxime Beugnet e eu recentemente embarcamos em um projeto para recuperar automaticamente estatísticas diárias sobre vídeos no canal do MongoDB no YouTube. Nossa equipe de gerenciamento extraía essas estatísticas todos os meses em uma planilha complicada. Em um esforço para ganhar pontos com nossa equipe de gerenciamento e ter um pouco de tempo de programação, trabalhamos juntos como uma equipe de três pessoas durante duas semanas para desenvolver rapidamente um aplicativo que extrai estatísticas diárias da API do Youtube e as armazena em um MongoDB Atlas database e os exibe em um dashboard do MongoDB Charts.
Mark, Max e eu temos uma parte do projeto. Mark cuidou da autenticação OAuth, Max criou os gráficos no dashboard e eu fui responsável por descobrir como recuperar e armazenar as estatísticas do YouTube.
Nesta publicação, vou compartilhar sete coisas que aprendi ao modelar os dados para este aplicativo. Mas, antes de entrar no que aprendi, vou compartilhar um pouco de contexto sobre como modelei os dados.
Se você preferir assistir a um vídeo em vez de ler um texto, sem problemas.
Para saber mais sobre o que construímos e por que construímos dessa maneira, confira a gravação do stream do Twitch abaixo, onde Mark, Max e eu compartilhamos sobre nosso aplicativo.
Se você quiser a versão em vídeo deste artigo, confira a transmissão ao vivo que eu fiz com Mark e Max. Recebemos perguntas fantásticas do público, então você descobrirá alguns tópicos interessantes na gravação.
Se você preferir um vídeo mais conciso que abranja apenas o conteúdo deste artigo, confira a gravação abaixo.
Nosso projeto tinha um prazo apertado de duas semanas, então tomamos decisões rápidas em nosso esforço para desenvolver rapidamente um produto mínimo viável. Quando começamos, nem sabíamos como queríamos exibir os dados, o que tornou a modelagem dos dados ainda mais desafiadora.
Acabei criando duas coleções:
youtube_videos
: armazena metadados sobre cada um dos vídeos no canal do MongoDB no YouTube.youtube_stats
: armazena estatísticas diárias do YouTube (distribuídas por mês) sobre todos os vídeos da coleçãoyoutube_videos
.
Todos os dias, um programado trigger chama uma função sem servidor responsável por chamar a Youtube PlaylistItems do API. Essa API retorna metadados sobre todos os vídeos no MongoDB Youtube canal do no . Os metadados são armazenados na
youtube_videos
coleção . Abaixo está um documento da youtube_videos
collection (algumas das informações foram editadas):1 { 2 "_id":"8CZs-0it9r4", 3 "kind": "youtube#playlistItem", 4 "isDA": true, 5 ... 6 "snippet": { 7 "publishedAt": 2020-09-30T15:05:30.000+00:00, 8 "channelId": "UCK_m2976Yvbx-TyDLw7n1WA", 9 "title": "Schema Design Anti-Patterns - Part 1", 10 "description": "When modeling your data in MongoDB...", 11 "thumbnails": { 12 ... 13 }, 14 "channelTitle": "MongoDB", 15 ... 16 } 17 }
Todos os dias, outro trigger chama uma função sem servidor responsável por chamar a de Youtube relatórios do API. As estatísticas que essa API retorna são armazenadas na
youtube_stats
coleção . Abaixo está um documento da coleção (algumas das estatísticas são removidas para manter o documento curto):1 { 2 "_id": "8CZs-0it9r4_2020_12", 3 "month": 12, 4 "year": 2020, 5 "videoId": "8CZs-0it9r4", 6 "stats": [ 7 { 8 "date": 2020-12-01T00:00:00.000+00:00, 9 "views": 21, 10 "likes": 1 11 ... 12 }, 13 { 14 "date": 2020-12-02T00:00:00.000+00:00, 15 "views": 29, 16 "likes": 1 17 ... 18 }, 19 ... 20 { 21 "date": 2020-12-31T00:00:00.000+00:00, 22 "views": 17, 23 "likes": 0 24 ... 25 }, 26 ] 27 }
Para ser claro, não estou dizendo que esta foi a melhor maneira de modelar nossos dados; este é o modelo de dados que temos após duas semanas de rápido desenvolvimento. Discutirei alguns dos prós e contras do nosso modelo de dados no restante desta publicação.
Se quiser dar uma olhada em nosso código e saber mais sobre nosso aplicativo, visite https://github.com/mongodb-developer/dream.
Sem mais demora, vamos para as sete coisas que eu aprendi modelando rapidamente os dados do YouTube.
Uma das regras de ouro ao modelar dados para o MongoDB é que os dados que são acessados juntos devem ser armazenados juntos. Ensinamos aos desenvolvedores que não há problema em duplicar dados, especialmente se você não os atualiza com frequência.
Quando comecei a entender como usar a API do YouTube e quais dados poderia recuperar, me dei conta de que precisaria fazer duas chamadas de API: uma para recuperar uma lista de vídeos com todos os metadados e outra para recuperar as estatísticas desses vídeos. Para facilitar o desenvolvimento, decidi armazenar as informações dessas duas chamadas de API em coleções separadas.
Eu não tinha certeza de quais dados precisariam ser exibidos junto com as estatísticas (em outras palavras, eu não tinha certeza de quais dados seriam acessados juntos), então não dupliquei nenhum dos dados. Eu sabia que, se fosse duplicar os dados, precisaria manter a consistência desses dados duplicados. E, para ser sincero, a manutenção de dados duplicados era um pouco assustadora devido à escassez de tempo e à falta de um processo de desenvolvimento de software que estávamos seguindo.
No modelo de dados atual, posso coletar estatísticas facilmente sobre curtidas, descurtidas, visualizações etc. para um determinado ID de vídeo, mas terei que usar $lookup para unir os dados com a coleção
youtube_videos
para lhe dizer qualquer coisa mais. Mesmo algo que parece relativamente simples, como listar o nome do vídeo junto com as estatísticas, requer o uso de $lookup
. A operação $lookup
necessária para unir os dados nas duas coleções não é tão complicada, mas as práticas recomendadas sugerem limitar $lookup
, pois essas operações podem afetar negativamente o desempenho.Enquanto desenvolvíamos nosso produto mínimo viável, avaliei a facilidade de desenvolvimento ao evitar a duplicação de dados em relação ao impacto potencial de dividir nossos dados no desempenho. A facilidade de desenvolvimento venceu.
Agora que sei que preciso de informações como o nome do vídeo e a data de publicação com as estatísticas, posso implementar o Padrão de referência estendida. Posso duplicar algumas das informações da coleção
youtube_videos
na coleçãoyoutube_stats
. Em seguida, posso criar um trigger do Atlas que observará as alterações na coleçãoyoutube_videos
e enviará automaticamente essas alterações para a coleção youtube_stats
. (Observe que se eu estivesse usando um banco de dados auto-hospedado em vez de um banco de dados hospedado no Atlas, eu poderia usar um fluxo de alterações em vez de um trigger do Atlas para garantir que os dados permanecessem consistentes.)Duplicar dados não é tão perigoso quando (1) você tem certeza de quais dados precisam ser duplicados e (2) usa Atlas triggers ou change streams para garantir que os dados permaneçam consistentes.
Adoro padrões de projetos de esquemas (confira esta série de blogs ou este curso gratuito da MongoDB University para saber mais) e antipadrões de projeto de esquema (confira esta série de blogs ou esta série de vídeos do YouTube para saber mais).
Quando estava decidindo como armazenar as estatísticas diárias do Youtube, percebi que tinha dados de série temporal. Eu sabia que o padrão bucket era útil para dados de série temporal, então decidi implementá-lo. Decidi criar um bucket de estatísticas para um determinado período de tempo e armazenar todas as estatísticas desse período para um único vídeo em um documento.
Eu não tinha certeza do tamanho que meus buckets deveriam ter. Eu sabia que não queria cair na armadilha do Antipadrão de arrays massivos, então não queria que meus buckets fossem muito grandes. Para avançar rapidamente, decidi que um mês seria um bom tamanho de bucket e imaginei que poderia ajustar conforme necessário.
Os buckets acabaram sendo muito úteis durante o desenvolvimento, pois eu podia ver facilmente todas as estatísticas de um vídeo para um determinado mês para garantir que elas estavam sendo extraídas corretamente.
No entanto, os buckets não ajudaram muito os membros da minha e equipe e eu em nosso aplicativo. Não tínhamos muitos dados a ponto de nos preocupar em reduzir o tamanho dos nossos índices. Não implementamos o padrão computado para pré-computar estatísticas mensais. E não executamos queries que se beneficiaram de ter os dados agrupados por mês.
Olhando para trás, criar um documento para cada vídeo todos os dias não teria problema. Não nos beneficiamos de nenhuma das vantagens do padrão bucket. Se nossos requisitos mudassem, certamente poderíamos nos beneficiar do padrão bucket. No entanto, neste caso, eu adicionei a complexidade de agrupar as estatísticas em buckets, mas não obtive os benefícios, então não valeu a pena.
Conforme descrito na seção anterior, decidi agrupar minhas estatísticas de vídeo do YouTube por mês. Eu precisava de uma maneira de indicar o intervalo de datas para cada grupo, portanto, cada documento contém um campo chamado
year
e um campo chamado month
. Ambos os campos armazenam valores do tipo long
. Por exemplo, um documento para o mês de janeiro 2021 teria "year": 2021
e "month": 1
.Poderíamos comparar meses de vários anos (por exemplo, poderíamos comparar estatísticas em janeiro de 2019, 2020 e 2021), e esse modelo de dados nos permitiria fazer isso.
Outra opção teria sido usar um único campo do tipo
date
para indicar o intervalo de datas. Por exemplo, para o mês de janeiro, eu poderia ter definido "date": new Date("2021-01")
. Isso me permitiria realizar cálculos baseados em datas em minhas queries.Tal como acontece com todas as considerações de modelagem de dados no MongoDB, a melhor opção depende do seu caso de uso e de como você fará a query dos dados. Use um campo do tipo
date
para buckets baseados em datas se desejar fazer uma query usando datas.Como mencionei no início desta publicação, eu era responsável por recuperar e armazenar os dados do YouTube. Meu colega de equipe Max era responsável por criar os gráficos para ver os dados.
Não prestei muita atenção em como os dados que estava obtendo da API foram formatados — apenas os coloquei no banco de dados. (Já mencionei que estamos trabalhando o mais rápido possível?)
Quando Max começou a criar os gráficos, ele levantou algumas preocupações sobre a maneira como os dados foram formatados. A data em que o vídeo foi publicado estava sendo armazenada como
string
em vez de date
. Além disso, o mês e o ano estavam sendo armazenados como string
em vez de long
.Max era capaz de fazer conversões de tipo em MongoDB Charts, mas, no final das contas, queríamos armazenar os dados de uma forma que fosse fácil de usar, seja ao ver os dados em Charts ou consultando os dados usando a linguagem de query do MongoDB (MQL).
As correções foram simples. Depois de recuperar os dados da API, eu os converti para o tipo ideal antes de enviá-los ao banco de dados. Dê uma olhada na linha 37 da minha função se quiser ver um exemplo de como fiz isso.
Se você estiver extraindo dados de uma API, considere se vale a pena remodelar ou reformatar os dados antes de armazená-los. É uma coisa pequena que pode tornar o seu trabalho e o de seus colegas de equipe muito mais fácil no futuro.
Sim, isso é meio obvio.
Vou explicar melhor.
Quando começamos a trabalhar em nosso aplicativo, sabíamos que queríamos exibir visualmente as estatísticas do YouTube em um dashboard. Mas não sabíamos quais estatísticas poderíamos extrair da API ou como queríamos visualizar os dados. Nossa abordagem foi colocar os dados no banco de dados e depois descobrir.
Ao modelar nossos dados, não fazia ideia de qual seria nosso caso de uso final – não tinha certeza de como os dados seriam acessados. Então, em vez de seguir a regra prática de que os dados acessados juntos devem ser armazenados juntos, modelei os dados da maneira que fosse mais fácil para eu trabalhar ao recuperar e armazenar os dados.
Uma das vantagens de usar o MongoDB é que você tem muita flexibilidade em seu esquema, para que possa fazer alterações à medida que os requisitos mudam. (O padrão de versionamento de esquema fornece um padrão de como fazer isso com sucesso.)
Como Max estava se exibindo na criação dos nossos charts, vi que ele criou um pipeline de agregação dentro dos Charts que calcula o trimestre do ano fiscal (por exemplo, janeiro de 2021 está em Q4 do ano fiscal 2021) e o adiciona a cada documento na coleção
youtube_stats
. Vários de nossos charts agrupam os dados por trimestre, portanto, precisamos deste campo.Eu estava muito satisfeito com a pipeline de agregação que Max criou para calcular o ano fiscal. No entanto, se eu soubesse que calcular o trimestre seria um de nossos requisitos quando eu estivesse modelando os dados, poderia ter calculado o trimestre do ano fiscal e armazenado dentro da coleção
youtube_stats
para que qualquer gráfico ou query pudesse usá-lo. Se eu tivesse seguido esse caminho, teria usado o Padrão Computado.Agora que sei que temos um requisito para exibir o trimestre do ano fiscal, posso escrever um script para adicionar o campo
fiscal_year_quarter
aos documentos existentes. Também poderia atualizar a função que cria novos documentos na coleção youtube_stats
para calcular o trimestre do ano fiscal e armazená-lo em novos documentos.A modelagem de dados no MongoDB tem tudo a ver com seu caso de uso. Quando você não sabe qual é o seu caso de uso, a modelagem de dados se torna um jogo de adivinhação. Lembre-se de que tudo bem se seus requisitos mudarem; o esquema flexível do MongoDB permite que você atualize seu modelo de dados conforme necessário.
Confesso que já disse aos desenvolvedores que são novos no uso do MongoDB exatamente isso: não há uma "maneira certa" de modelar seus dados. Dois aplicativos que utilizam os mesmos dados podem ter modelos de dados ideais diferentes com base em como os aplicativos usam os dados.
No entanto, o perfeccionista em mim ficou um pouco perturbado ao modelar os dados para este aplicativo. Em mais de uma reunião de nossa equipe, eu disse a Mark e Max que não estava gostando do modelo de dados que havia criado. Eu não sentia que estava "acertando a mão".
Como mencionei acima, o problema era que eu não sabia o caso de uso para o qual estava otimizando enquanto desenvolvia o modelo de dados. Eu estava fazendo suposições e me sentindo desconfortável. Como eu estava usando um banco de dados relacional, não podia simplesmente normalizar os dados sistematicamente e afirmar que os havia modelado corretamente.
A flexibilidade do MongoDB lhe dá muito poder, mas também pode fazer você se perguntar se chegou ao modelo de dados ideal. Você pode descobrir, como eu fiz, que talvez precise revisitar seu modelo de dados à medida que seus requisitos se tornam mais claros ou mudam. E tudo bem.
(Não deixe a flexibilidade do esquema do MongoDB te assustar. Você pode usar a validação de esquema do MongoDB quando estiver pronto para bloquear parte ou todo o seu esquema.)
Com base no que aprendi anteriormente, que não existe "da maneira correta" para modelar seus dados, os modelos de dados provavelmente sempre podem ser aprimorados. Ao identificar quais serão suas queries ou se elas mudarem, você provavelmente encontrará novas maneiras de otimizar seu modelo de dados.
A questão é: quando seu modelo de dados é bom o suficiente? O perfeccionista em mim lutou com essa questão. Devo continuar otimizando? Ou o modelo de dados que temos é bom o suficiente para nossos requisitos?
Para responder a essa pergunta, me vi fazendo mais duas perguntas:
- Meus colegas e eu conseguimos trabalhar facilmente com os dados?
- O desempenho do nosso aplicativo é bom o suficiente?
As respostas para as perguntas podem ser um pouco subjetivas, especialmente se você não tiver requisitos de desempenho rígidos, como uma página da web deve carregar em X milissegundos.
No nosso caso, não definimos nenhum requisito de desempenho. Atualmente, nosso front-end é um dashboard Charts. Então, eu me perguntava: nosso Charts está carregando rápido o suficiente? E a resposta é sim, nosso Charts carrega muito rapidamente. O Charts utiliza cache com uma atualização padrão de uma hora para garantir que os gráficos carreguem rapidamente. Depois que um usuário carrega o dashboard em seu navegador, os gráficos permanecem exibidos, mesmo enquanto se espera que eles obtenham os dados mais recentes quando o cache for atualizado.
Se seus desenvolvedores são capazes de trabalhar facilmente com os dados e o desempenho do seu aplicativo é bom o suficiente, seu modelo de dados provavelmente é bom o suficiente.
Toda vez que trabalho com o MongoDB, aprendo algo novo. No processo de trabalhar com uma equipe para criar rapidamente um aplicativo, aprendi muito sobre modelagem de dados no MongoDB:
Se você tiver interesse em aprender mais sobre modelagem de dados, recomendo os seguintes recursos:
- Curso gratuito da MongoDB University : caminho de modelagem de dados do MongoDB
- Série de blogs: padrões de projeto de esquema do MongoDB
- Série de vídeos do YouTube: antipadrões de projeto de esquema do MongoDB
- Série de blogs: Antipadrões de design de esquema do MongoDB
Lembre-se de que cada caso de uso é diferente, portanto, cada modelo de dados será diferente. Concentre-se em como você usará os dados.
Se você tiver alguma dúvida sobre modelagem de dados, recomendamos que você faça parte da MongoDB Community. É um ótimo lugar para tirar dúvidas. Os funcionários e membros da comunidade do MongoDB estão lá todos os dias para responder a perguntas e compartilhar suas experiências. Espero ver você lá!