<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Breno Ferreira</title>
    <description>The latest articles on DEV Community by Breno Ferreira (@brenoferreira).</description>
    <link>https://dev.to/brenoferreira</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F194919%2Fd2b3a57d-35e9-440d-9e09-ab05f1199108.jpeg</url>
      <title>DEV Community: Breno Ferreira</title>
      <link>https://dev.to/brenoferreira</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/brenoferreira"/>
    <language>en</language>
    <item>
      <title>Testes de UI com Testing Library</title>
      <dc:creator>Breno Ferreira</dc:creator>
      <pubDate>Mon, 03 Aug 2020 11:50:41 +0000</pubDate>
      <link>https://dev.to/brenoferreira/testes-de-ui-com-testing-library-4h1b</link>
      <guid>https://dev.to/brenoferreira/testes-de-ui-com-testing-library-4h1b</guid>
      <description>&lt;p&gt;&lt;em&gt;Aviso: isso não é um tutorial&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Acho que qualquer dev com alguns anos de experiência em desenvolvimento web, em algum momento, teve que fazer testes de interface.&lt;/p&gt;

&lt;p&gt;Algumas ferramentas bem comuns para esses tipos de testes são Selenium (e outros parecidos como Cypress por exemplo) que são focados no que chamamos de end-to-end tests, que possuem uma API de automatização do browser para simular o uso da página. Eu particularmente não gosto desses testes pois são geralmente muito frágeis. Como são testes de mega-integração do sistema, Além deles serem muito lentos, qualquer módulo do sistema não respondendo perfeitamente pode causar uma falha do teste. Então o desenvolvedor acaba não rodando esses testes na própria máquina, rodando-o somente no servidor de CI. Quando algum teste falha no CI (e certamente vai falhar com bastante frequência), é mais dificil debugar remotamente. Então o dev roda na sua máquina, e surpresa: &lt;em&gt;works on my machine&lt;/em&gt;! Essa inconsistência e fragilidade acaba inevitavelmente no time decidindo não manter mais esses testes. Já vi isso acontecendo trocentas mil vezes.&lt;/p&gt;

&lt;p&gt;Hoje em dia, as libs de frontend modernas, seja React, Angular, Vue, etc., possuem um framework para testar componentes. Não importa qual lib voce escolha, testar seus componentes vai ajudar a garantir que sua UI está funcionando adequadamente. Arriscaria dizer que se voce tem bons testes de unidade no frontend e bons testes de unidade e integração no backend, vai ser suficiente para garantir o mínimo de qualidade e confiabilidade para deploys frequentes e tranquilos. Aqueles testes end-to-end com Selenium ou outra ferramenta qualquer se tornarão desnecessários e não vão ajudar (muito) a identificar bugs mais complexos. Mas enfim, isso é assunto pra outro post. Voltemos aos testes de componentes.&lt;/p&gt;

&lt;p&gt;A maioria dessas libs de teste funciona da seguinte forma: o componente o HTML e alguma lógica de interação, e no teste utiliza-se funções para fazer asserções sobre esse markup e lógica, geralmente utilizando selectors. Por exemplo (com React):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const Produto = ({ produto }) =&amp;gt; (
 &amp;lt;div class="produto"&amp;gt;
  &amp;lt;span class="produto-nome"&amp;gt;{produto.nome}&amp;lt;/span&amp;gt;
  &amp;lt;span class="produto-descricao"&amp;gt;{produto.descricao}&amp;lt;/span&amp;gt;
  &amp;lt;span class="produto-preco"&amp;gt;R${produto.preco}&amp;lt;/span&amp;gt;
 &amp;lt;/div&amp;gt;
)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Estou usando React aqui como exemplo, mas com Vue ou Angular não é muito diferente.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;describe('Componente Produto', () =&amp;gt; {
 it('exibe preço do produto', () =&amp;gt; {
  const produto = { nome: 'TV', descricao: 'TV 4k', preco: 2000 };
  const component = render(&amp;lt;Produto produto={produto} /&amp;gt;)

  const preco = ReactTestUtils.findRenderedDOMComponentWithClass(componente, 'produto-preco');

  expect(preco.textContent).toBe('R$ 2000');
 });
})
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;É um exemplo simples, e que funciona razoavelmente bem. Porém, tem um problema nesse teste e que é comum à praticamente todos os frameworks de teste de interface, que é utilizar selectors, no exemplo, class selectors. Esses frameworks forçam o teste a fazer queries no markup via selectors, seja por class, id ou tag html.&lt;/p&gt;

&lt;p&gt;Isso causa um certo acoplamento do teste com a implementação. Se voce muda o HTML da página, seu teste quebra, mesmo que o conteúdo em si tenha se mantido. No exemplo acima do produto, se por acaso eu tiver que mudar o nome da classe para outra coisa por alguma necessidade de CSS, isso vai quebrar todos os testes daquele componente sem nenhuma necessidade, pois a funcionalidade não mudou, o que mudou foram detalhes visuais.&lt;/p&gt;

&lt;h3&gt;
  
  
  Testing Library
&lt;/h3&gt;

&lt;p&gt;O &lt;a href="https://testing-library.com/docs/intro"&gt;Testing Library&lt;/a&gt; tem uma filosofia e abordagem bem diferente nos testes. Sua API não possui uma função para obter um elemento via class, id nem tag. Sua API é voltada ao conteúdo do componente. Reescrevendo o teste acima, ele ficaria da seguinte forma:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;describe('Componente Produto', () =&amp;gt; {
 it('exibe preço do produto', () =&amp;gt; {
  const produto = { nome: 'TV', descricao: 'TV 4k', preco: 2000 };
  const componente = render(&amp;lt;Produto produto={produto} /&amp;gt;)
  expect(componente.getByText('R$ 2000')).toBeInTheDocument();
 });
})
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Repare que em momento algum eu fiz uma consulta de selector. O teste simplesmente verifica se o componente está renderizando o preço corretamente. Se o HTML mudar, mas o preço continuar lá, o teste não quebra, como deveria ser.&lt;/p&gt;

&lt;p&gt;O Testing-Library tem diferentes funções de consultas, definidas da seguinte forma: &lt;code&gt;getBy*&lt;/code&gt;, &lt;code&gt;getAllBy*&lt;/code&gt;, &lt;code&gt;queryBy*&lt;/code&gt;, &lt;code&gt;queryAllBy*&lt;/code&gt;, &lt;code&gt;findBy*&lt;/code&gt; e &lt;code&gt;findAllBy*&lt;/code&gt;. E existem funções com "sufixos" variados para diferentes tipos de consulta.&lt;/p&gt;

&lt;p&gt;No caso de formulários, as funções de consulta disponíveis são por: texto do label &lt;code&gt;*ByLabelText&lt;/code&gt;, texto do placeholder do input &lt;code&gt;*ByPlaceholderText&lt;/code&gt;, e pelo value do input &lt;code&gt;*ByDisplayValue&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;No caso de texto livre, existe o &lt;code&gt;*ByText&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Para imagens, existe o&lt;code&gt;*ByAltText&lt;/code&gt;. E existem outras queries mais genéricas como o &lt;code&gt;*ByTitle&lt;/code&gt;, para elementos que possuem o atributo &lt;code&gt;title&lt;/code&gt;, &lt;code&gt;*ByRole&lt;/code&gt; para consultar elementos por &lt;code&gt;aria-role&lt;/code&gt;. Essas funções inclusive podem ajudar os testes a guiarem à um HTML mais acessível.&lt;/p&gt;

&lt;p&gt;Existe também o &lt;code&gt;*ByTestId&lt;/code&gt; que te possibilita fazer consultas à elementos com um atributo &lt;code&gt;data-testid&lt;/code&gt;, porém é preferível usar os outros métodos de consulta e deixar o uso dessa função para casos onde fica muito difícil usar as outras funções dificulta muito o teste.&lt;/p&gt;

&lt;p&gt;A &lt;a href="https://testing-library.com/docs/dom-testing-library/api-queries"&gt;documentação&lt;/a&gt; das queries explica com mais detalhes como cada uma funciona.&lt;/p&gt;

&lt;p&gt;É possível ver que o framework vai te forçar a escrever seus testes de maneira bem diferente, sempre direcionados ao conteúdo da página, e não à detalhes de implementação do HTML. Também acho que deu para notar que essa abordagem possibilita ter testes menos acoplados à implementação e assim bem mais resistentes à mudanças. Além disso, a lib possui implementação para quase todos os frameworks frontend mais conhecidos: &lt;a href="https://testing-library.com/docs/dom-testing-library/install"&gt;React, Angular, Vue entre outros&lt;/a&gt;. Isso ajuda em casos de projetos usando libs diferentes à terem uma suite de testes mais uniforme e não obrigar as pessoas a aprenderem uma API de testes para cada framework.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>testing</category>
      <category>react</category>
    </item>
    <item>
      <title>Dados distribuídos — Particionamento/Sharding</title>
      <dc:creator>Breno Ferreira</dc:creator>
      <pubDate>Mon, 27 Jul 2020 14:46:37 +0000</pubDate>
      <link>https://dev.to/brenoferreira/dados-distribuidos-particionamento-sharding-pip</link>
      <guid>https://dev.to/brenoferreira/dados-distribuidos-particionamento-sharding-pip</guid>
      <description>&lt;p&gt;Parte da &lt;a href="https://dev.to/brenoferreira/designing-data-intensive-apps-um-resumo-2df9"&gt;série&lt;/a&gt; sobre o resumo do livro &lt;a href="https://amzn.to/2YaKEhB" rel="noopener noreferrer"&gt;Designing Data Intensive Apps&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Até aqui, falamos sobre alguns temas gerais sobre bancos de dados: &lt;a href="https://dev.to/brenoferreira/designing-data-intensive-apps-um-resumo-390c"&gt;Sistemas de dados&lt;/a&gt;, &lt;a href="https://dev.to/brenoferreira/designing-data-intensive-apps-resumo-cap-2-4b75"&gt;Modelos de Dados&lt;/a&gt;, &lt;a href="https://dev.to/brenoferreira/designing-data-intensive-apps-capitulo-3-1693"&gt;Armazenamento de Dados&lt;/a&gt;, &lt;a href="https://dev.to/brenoferreira/encoding-e-dataflow-4p60"&gt;Serialização de Dados&lt;/a&gt; e &lt;a href="https://dev.to/brenoferreira/dados-distribuidos-replicacao-de-dado-1n96"&gt;Replicação de Dados&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Uma outra técnica que pode ser utilizado para aumentar a escalabilidade do banco de dados é particionamento (ou também conhecido como sharding) de dados que pode ser aplicado em conjuntos de dados muito grandes com com um volume de queries muito alto.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1600%2F1%2AeFvhv5GyO_1rgMKFo1XDUQ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1600%2F1%2AeFvhv5GyO_1rgMKFo1XDUQ.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Como particionar os dados?
&lt;/h3&gt;

&lt;p&gt;Uma parte mais dificil de particionar dados é: como particionar? Existem técnicas diferentes, algumas funcionam melhores que outras.&lt;/p&gt;

&lt;p&gt;Técnicas mais ingênuas de particionamento costumam causar hot-spots em algumas partições, ou seja, um número muito alto de acessos à uma única partição enquanto as outras estão paradas. Por exemplo:&lt;/p&gt;

&lt;p&gt;Numa rede social, particionar por username. Entao, cada partição armazenaria dados de usuarios com username começando de A à C, de D à F, etc..&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1600%2F1%2AERByZVRYhLILPKVnMHm22w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1600%2F1%2AERByZVRYhLILPKVnMHm22w.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;No Instagram por exemplo, os &lt;a href="https://en.wikipedia.org/wiki/List_of_most-followed_Instagram_accounts" rel="noopener noreferrer"&gt;usuários com mais seguidores&lt;/a&gt; estão distribuídos da seguinte forma:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1600%2F1%2ATPovGfwzmjPgHyPgBqNxpw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1600%2F1%2ATPovGfwzmjPgHyPgBqNxpw.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Dá pra perceber que nesse caso a primeira partição seria um hot spot, pois 2 dos 3 usuarios com mais seguidores tem seu username começando com A, B ou C (Ariana Grande e Cristiano Ronaldo), além de Beyonce em 9 lugar. Todos com centenas de milhões de seguidores. Olhando o ranking dá pra perceber que usuários com +100 milhões de usuários tem as iniciais mais comuns T, K, J, N.&lt;/p&gt;

&lt;p&gt;Outro exemplo: particionar por data. Digamos que dados escritos num dia são escritos em partições diferentes. Caso haja um volume muito alto de escrita, em cada dia uma partição será o hotspot, enquanto as outras só irão atender à requisições de leitura.&lt;/p&gt;

&lt;h4&gt;
  
  
  Consistent Hashing Partitioning
&lt;/h4&gt;

&lt;p&gt;Uma técnica que ajuda a diminuir (mas não eliminar) esses hotspots é particionar por um hash. No exemplo da rede social, voce pode calcular um hash do username e armazenar em alguma partição. Esses hashes devem ser distribuídos uniformemente entre as partições. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1600%2F1%2ATsj5HOfQTHELQExjClZqPg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1600%2F1%2ATsj5HOfQTHELQExjClZqPg.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Particionar por hash ainda não elimina hotspots. Por exemplo a partição onde os dados do Cristiano Ronaldo estão armazenados vai continuar sendo muito acessada. Mas assim voce consegue distribuir melhor os dados e a carga.&lt;/p&gt;

&lt;h3&gt;
  
  
  Problemas com particionamento
&lt;/h3&gt;

&lt;p&gt;Claro que os problemas não iriam acabar aí.&lt;/p&gt;

&lt;p&gt;No caso de bancos de dados relacionais, joins agora podem potencialmente ser distribuídos em varios nós,  consequentemente, bem menos eficientes. Por isso saber escolher bem sua estratégia de particionamento é importante.&lt;/p&gt;

&lt;p&gt;Outro problema ocorre com índices secundários. No caso do índice primário no hash da chave, os dados estão na mesma partição, então não costuma ser muito problemático. Mas em um índice secundário os dados provavelmente vão estar em partições diferentes. Por exemplo: armazenando dados de livros, voce particiona pelo hash do código ISBN, mas com certeza vai precisar também de um índice secundário no título dos livros para buscas eficientes. Então, os livros sobre "Computação Distribuída" não vão estar necessariamente na mesma partição.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1600%2F1%2AidIsm7Q4aRMYh24jSkpm_Q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1600%2F1%2AidIsm7Q4aRMYh24jSkpm_Q.png"&gt;&lt;/a&gt;&lt;br&gt;
&lt;span&gt;Índice secundário local&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;Uma estratégia é cada partição manter seus índices secundários para os seus próprios dados. Então, quando um cliente executa uma consulta, é necessario consultar os índices de cada partição, numa operação conhecida como scatter/gather. Essas consultas geralmente costumam ser bem lentas.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1600%2F1%2AYfguGRoWVv-UxOOlO96F-w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1600%2F1%2AYfguGRoWVv-UxOOlO96F-w.png"&gt;&lt;/a&gt;&lt;br&gt;
&lt;span&gt;Índice secundário global&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;Outra estratégia é criar um índice global particionado. No exemplo de livros, o termo "Computação" estaria no índice na partição 1, enquanto o termo "Medicina" estaria no índice na partição 3. Os dados em si podem estar em outras partições, mas ao contrário do índice local por partição, não é necessário fazer uma busca em &lt;strong&gt;todas&lt;/strong&gt; as partições, pois o índice por termo garante que voce encontra todos os documentos associados àquele termo e buscar diretamente nas partições que contêm os dados, melhorando a performance de consulta. O problema com essa estratégia é que a performance de escrita é ruim, pois esse índice distribuído precisa ser atualizado à cada escrita. Essa atualização geralmente é feita de maneira assíncrona, então ela sofre com o Lag de Replicação que foi abordado no post anterior. Manter um índice distribuído fortemente consistente requer uma transação distribuída, que é bem custosa.&lt;/p&gt;

</description>
      <category>database</category>
      <category>distributedsystems</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Dados distribuídos - Replicação de dados</title>
      <dc:creator>Breno Ferreira</dc:creator>
      <pubDate>Tue, 07 Jul 2020 22:21:02 +0000</pubDate>
      <link>https://dev.to/brenoferreira/dados-distribuidos-replicacao-de-dado-1n96</link>
      <guid>https://dev.to/brenoferreira/dados-distribuidos-replicacao-de-dado-1n96</guid>
      <description>&lt;p&gt;Parte da &lt;a href="https://dev.to/brenoferreira/designing-data-intensive-apps-um-resumo-2df9"&gt;série&lt;/a&gt; sobre o resumo do livro &lt;a href="https://amzn.to/2YaKEhB"&gt;Designing Data Intensive Apps&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Até aqui, falamos sobre alguns temas gerais sobre bancos de dados: &lt;a href="https://dev.to/brenoferreira/designing-data-intensive-apps-um-resumo-390c"&gt;Sistemas de dados&lt;/a&gt;, &lt;a href="https://dev.to/brenoferreira/designing-data-intensive-apps-resumo-cap-2-4b75"&gt;Modelos de Dados&lt;/a&gt;, &lt;a href="https://dev.to/brenoferreira/designing-data-intensive-apps-capitulo-3-1693"&gt;Armazenamento de Dados&lt;/a&gt;e &lt;a href="https://dev.to/brenoferreira/encoding-e-dataflow-4p60"&gt;Serialização de Dados&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Agora, é hora de começarmos a falar sobre dados distribuídos e quais são os desafios inerentes à essa prática. Quando queremos garantir escalabilidade e confiabilidade do nosso sistema, é inevitável que em algum momento, nossos dados estejam distribuídos em mais de um lugar. Caso contrário, basta uma falha de acesso ao servidor de banco de dados e a aplicação para de funcionar. Se isso for inaceitável para seu negócio, aceite o fato de que algum nivel de computação distribuída vai existir.&lt;/p&gt;

&lt;p&gt;Em uma arquitetura web típica, é comum ter um cluster de servidores web respondendo à requisições e um proxy reverso que as distribui aos nós do cluster. Essa arquitetura é fácil de implementar por que os nós do cluster não compartilham nada entre si (Shared Nothing Architecture). Como servidores web também não armazenam estado (chamados stateless web servers), caso um nó do cluster falhe, basta tirá-lo do cluster, enviar as requisições para os nós que ainda funcionam e adicionar um nó novo. Ter escalabilidade horizontal assim não é dificil.&lt;/p&gt;

&lt;p&gt;Em um cluster de banco de dados, ainda é possível usar essa Shared-Nothing Architecture, ou seja, não compartilhamos nenhum recurso computacional. Porém, vai ser inevitável compartilhar uma coisa nos nós do cluster de banco de dados: os dados. Podemos distribuir os dados em vários servidores de um cluster de duas formas: Replicação e Particionamento. Repare que essas técnicas não são exclusivas. Pode haver dados particionados e replicados ao mesmo tempo. Nesse post iremos abordar o tema de replicação de dados. Particionamento de dados fica para um próximo post.&lt;/p&gt;

&lt;p&gt;Implementar replicação de dados não é uma tarefa trivial que envolve alguns desafios e que existem algumas estratégias já conhecidas para resolver os problemas que podem aparecer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Estratégias de replicação de dados
&lt;/h2&gt;

&lt;p&gt;Toda vez que algum dado é escrito no banco, é necessário ter uma cópia desses dados nas outras réplicas do banco. Um cliente que envia uma requisição de escrita (ex: INSERT, UPDATE ou DELETE) para algum nó no cluster, o cluster deve seguir alguma estratégia para replicar esse dado escrito nos outros nós. As estratégias mais comuns de replicação são: Replicação com líder único, Replicação com múltiplos líderes e replicação sem líder.&lt;/p&gt;

&lt;h3&gt;
  
  
  Replicação de líder único
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--lsd146Vb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1600/1%2A2pzT6Dcsi6EWoiTJu0Tg8A.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--lsd146Vb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1600/1%2A2pzT6Dcsi6EWoiTJu0Tg8A.jpeg" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A estratégia mais comum para se garantir disponibilidade em caso de falha. Nessa estratégia, o cluster sempre elege um líder que será o único responsável por aceitar requisições de escrita. Requisições de leitura são aceitas por qualquer nó, líder ou réplica.&lt;/p&gt;

&lt;p&gt;Em uma operação de escrita, o nó líder commita a escrita e envia uma resposta ao cliente. Em seguida, de maneira assíncrona, é enviado aos nós replicas a mesma operação para que o mesmo dado seja escrito nos outros nós e manter todas as réplicas consistentes.&lt;/p&gt;

&lt;p&gt;A escrita nas réplicas também pode ser feita de maneira síncrona, com o cliente recebendo a resposta da requisição somente depois da escrita ter sido replicada em todos os nós. Isso evita alguns problemas que veremos depois, mas a performance é bem ruim e pode causar um enorme gargalo caso escritas sejam muito frequentes e principalmente se algum dos nós falhar. É mais comum que se a replicação for síncrona, que apenas um dos nós seja replicado sincronamente e os outros de forma assíncrona.&lt;/p&gt;

&lt;p&gt;O líder é eleito pelo cluster usando algum tipo de algoritmo de consenso como &lt;a href="https://en.wikipedia.org/wiki/Paxos_(computer_science)"&gt;Paxos&lt;/a&gt;ou &lt;a href="https://en.wikipedia.org/wiki/Raft_(computer_science)"&gt;Raft&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Replicação com múltiplos líderes
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--5sfjkti6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1600/1%2A-SxCKzy0L5LZ3iTru8GcUQ.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--5sfjkti6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1600/1%2A-SxCKzy0L5LZ3iTru8GcUQ.jpeg" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Essa é uma estratégia mais comum em ambientes multi-datacenter. Por exemplo, caso exista versões da aplicação rodando em mais de um datacenter diferente, para acesso mais rápido em diferentes regiões (Latam e América do Norte por exemplo). É possível ter um cluster em multiplas regiões e ter um líder por região. Os líderes que commitou o dado replica a  operação de escrita com os líderes das outras regiões, que por sua vez, enviam os dados para seus respectivos nós replicas.&lt;/p&gt;

&lt;h3&gt;
  
  
  Replicação sem líder
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--DiKaurEN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1600/1%2AiUwye4cbYTiOCCoQryHzbg.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--DiKaurEN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1600/1%2AiUwye4cbYTiOCCoQryHzbg.jpeg" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Nesse caso o cliente envia a requisição para varios nós. Como não há um líder coordenando que garante a escrita dos dados e a replicação dos dados escritos no banco, é necessário utilizar uma técnica chamada Quorum, que consiste basicamente em ter confirmação de uma maioria dos nós no cluster. Uma operação em um cluster com N nós, para ser considerada bem sucedida, deve ser confirmada por no mínimo K nós. O número K é o que chamamos de Quorum. Esse número K pode ser diferente para operações de leitura e escrita, ou ser igual para ambas operações, desde que esse número satisfaça a seguinte condição:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;K_leitura + K_escrita &amp;gt; N&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Voce pode definir o valor de &lt;em&gt;K&lt;/em&gt; como sendo &lt;em&gt;(N + 1) / 2&lt;/em&gt;, arredondado para cima. Então caso haja 4 nós no cluster, as operações teriam que ser confirmadas por um Quorum de ao menos 3 nós.&lt;/p&gt;

&lt;p&gt;Esse tipo de replicação sem lider é conhecido como Dynamo-Style Replication. Não confundir com o Amazon DynamoDB que usa replicação com líder único&lt;/p&gt;

&lt;h2&gt;
  
  
  Desafios
&lt;/h2&gt;

&lt;p&gt;A partir do momento que voce tem mais de uma instancia do seu servidor de banco de dados rodando, diga adeus aos confortos da computação monolítica e abrace a dor e sofrimento da computação distribuída.&lt;/p&gt;

&lt;p&gt;Os maiores problemas que costumam aparecer quando há mais de uma réplica dos dados são relacionados a consistência dos dados e, no caso de estratégias com líder, uma falha e recuperação do líder.&lt;/p&gt;

&lt;p&gt;Recuperação de falha de um líder se dá da seguinte forma:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Detectar uma falha do líder: Geralmente detecta-se uma falha quando o servidor para de responder e ocorrem timouts. Uma falha pode acontecer por diversas razões como crash do sistema ou falta de energia por exemplo. Em alguns casos o servidor não falhou, mas houve falha da rede e ele fica incomunicável por um tempo. É impossível saber na hora qual a razão da falha, somente que ela ocorreu. Por essa razão, definir um valor de timeout pode ser um pouco complicado. Um timeout muito curto pode causar failovers desnecessários, e um timeout muito longo pode causar um tempo maior para recuperar o sistema de uma falha.&lt;/li&gt;
&lt;li&gt;Escolha de um novo líder: Para eleger um novo líder, usa-se algum algoritmo de consenso distribuído como Paxos ou Raft para que todos os nós entrem em acordo sobre qual dos nós réplicas irá tornar-se o novo líder.&lt;/li&gt;
&lt;li&gt;Reconfigurar o sistema e passar a usar o novo líder: os clientes agora precisarão se comunicar com o novo líder eleito. Caso o antigo líder volte a funcionar, ele deve detectar a existencia desse novo líder e passar a funcionar como uma réplica, caso contrário pode acontecer o que chamamos de split-brain e isso pode causar conflitos e corrupção dos dados.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Outro problema com dados distribuídos é na consistência dos dados. Como a replicação dos dados não é imediata, e os clientes lêem os dados de réplicas que podem ainda não ter os dados mais atuais que foram escritos pelo líder, pode ser bem comum haver o que chamamos de atraso de replicação, ou Replication Lag.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--5sw90Hcf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1600/1%2AIRCAGQWA7Q1ZGAl4yKV0DQ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--5sw90Hcf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1600/1%2AIRCAGQWA7Q1ZGAl4yKV0DQ.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;No exemplo da imagem acima, o usuário faz um INSERT de um dado e o líder envia as requisições de replicação. Como qualquer requisição de rede, uma pode ser mais rápida que a outra, com menor latencia e tempo de resposta menor, então a requisição de replicação no nó Replica 1 é mais rápida do que no nó réplica 2. Porém, o cliente tenta ler o dado que acabou de ser inserido enviando uma requisição para a Réplica 2 (já que leituras podem ser feitas em qualquer nó), porém a requisição chega antes da Replica 2 receber a requisição de replicação do insert, e logo, ela ainda não tem o dado e envia uma resposta com zero resultados.&lt;/p&gt;

&lt;p&gt;Uma possível solução para esse problema é ter um controle mais fino sobre a conexão do cliente com o banco de dados para garantir que, quando o cliente ler algum dado que ele próprio tiver escrito, realizar requisição de leitura com o líder e não com réplicas. Essa técnica é conhecida como Read Your Own Writes. Isso garante que pelo menos o usuário vai ler o que ele mesmo escreveu, mas a leitura não é garantida para outros usuários. Por exemplo: em uma rede social, o usuário consegue ler seus próprios comentários, mas pode haver algumas eventuais inconsistências com os comentários dos outros.&lt;/p&gt;




&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--rt5YkgTX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1600/1%2A3V-hsmU2qhBixbQHvMes1A.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--rt5YkgTX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1600/1%2A3V-hsmU2qhBixbQHvMes1A.jpeg" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Outro problema pode acontecer é Usuario 1 inserir um valor, e o Usuário 2 ter leituras inconsistentes por causa de conexão com réplicas diferentes que ainda não estão 100% consistentes. No caso acima, um cliente 1  insere um valor e esse dado é replicado. O cliente 2 então tenta ler o dado escrito em uma réplica que já teve esse dado replicado em sua base e retorna o valor. Num momento seguinte, o mesmo cliente tenta ler o dado de novo, mas dessa vez em uma réplica que ainda não tem o valor replicado, então ela responde um resultado vazio. Isso pode causar confusão pro usuário pois uma hora ele vê uma coisa, e logo depois não vê mais.&lt;/p&gt;

&lt;p&gt;Monotonic Reads é uma garantia que o banco de dados pode prover para que esse tipo de anomalia não ocorra. Ela garante que o usuário ao ler um dado x no tempo t, continuará lendo esse dado no futuro e não lerá uma versão antiga de x em tempo t1. Uma possível implementação pode ser definir a replica de conexão baseado no ID do usuario. Assim, o cliente conecta sempre na mesma réplica. Caso a replica caia, a requisição é redirecionada para outra réplica.&lt;/p&gt;




&lt;p&gt;Ambientes de replicação sem líder são otimizados para aplicações com uma tolerância maior à consistência eventual, pois a probabilidade de valores desatualizados é maior (pois não há um líder como uma fonte de consistência). Garantia de consistência geralmente requer transações distribuídas ou algoritmos de consenso distribuído, que são operações com custo alto de performance.&lt;/p&gt;

&lt;p&gt;Em casos de falha de algum nó, após ele voltar a funcionar ele deve receber os dados que foram perdidos  enquanto ele estava fora do ar. Isso pode acontecer de duas maneiras:&lt;/p&gt;

&lt;p&gt;Read repair: o cliente, quando faz uma requisição de leitura pra varios nós, caso receba um valor desatualizado de um nó, envia uma requisição de atualização para o nó que enviou o dado desatualizado.&lt;/p&gt;

&lt;p&gt;Processo anti-entropia: um processo que fica continuamente monitorando dados inconsistentes e realiza as atualizações necessárias copiando dados de uma réplica pra outra.&lt;/p&gt;

&lt;p&gt;Outro problema que pode ocorrer também é quando, em cenários multi-líder ou sem líder, há escritas concorrentes e é necessário detectar e corrigir dados conflitantes.&lt;/p&gt;

&lt;p&gt;Uma estratégia é o que é chamado de Última Escrita Vence (Last Write Wins). Porém, como veremos em posts futuros, determinar ordem de eventos no tempo em ambientes distribuídos não é tão simples. Então pode acontecer de decidir-se por um dado que não é o mais recente, ou até mesmo detectar eventos concorrentes quando na verdade não há concorrencia, mas sim problemas com relógios distribuídos.&lt;/p&gt;

&lt;p&gt;Nas linhas de pesquisa mais recentes sobre resolução de conflitos existem alguns algoritmos que permitem uma resolução mais inteligente e automática: &lt;a href="https://arxiv.org/abs/1805.06358"&gt;Conflict-free replicated datatypes (CRDTs)&lt;/a&gt;,&lt;br&gt;
&lt;a href="http://gazagnaire.org/pub/FGM15.pdf"&gt;Mergeable Persistent Data Structures&lt;/a&gt; e&lt;br&gt;
&lt;a href="http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.53.933&amp;amp;rep=rep1&amp;amp;type=pdf"&gt;Operational Transformations&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>database</category>
      <category>architecture</category>
      <category>distributedsystems</category>
    </item>
    <item>
      <title>Encoding e Dataflow</title>
      <dc:creator>Breno Ferreira</dc:creator>
      <pubDate>Wed, 24 Jun 2020 18:58:04 +0000</pubDate>
      <link>https://dev.to/brenoferreira/encoding-e-dataflow-4p60</link>
      <guid>https://dev.to/brenoferreira/encoding-e-dataflow-4p60</guid>
      <description>&lt;p&gt;Parte da &lt;a href="https://dev.to/brenoferreira/designing-data-intensive-apps-um-resumo-2df9"&gt;série&lt;/a&gt; sobre o resumo do livro &lt;a href="https://amzn.to/2YaKEhB"&gt;Designing Data Intensive Apps&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;No &lt;a href="https://dev.to/brenoferreira/designing-data-intensive-apps-um-resumo-390c"&gt;Capítulo 1&lt;/a&gt; foi falado sobre características de sistemas de dados, no &lt;a href="https://dev.to/brenoferreira/designing-data-intensive-apps-resumo-cap-2-4b75"&gt;Capítulo 2&lt;/a&gt; sobre modelos de dados. No &lt;a href="https://dev.to/brenoferreira/designing-data-intensive-apps-capitulo-3-1693"&gt;Capítulo 3&lt;/a&gt;, sobre o tema de armazenamento de dados. Agora iremos abordar o tema de encoding de dados.&lt;/p&gt;

&lt;p&gt;O banco de dados, como qualquer aplicação, escreve e lê dados. Escreve dados em memória e disco, e lê esses dados e os envia pela rede para clientes remotos. Em memória, esses dados ficam em estruturas de dados como Hashmaps e/ou Árvores. Porém, tanto no processo de escrita em disco quanto no processo de envio desses dados para os clientes remotos, é necessário converter esses dados em memória para algum formato mais apropriado, num processo conhecido como encoding. Quem lê esses dados por sua vez faz o decoding para converter novamente em alguma estrutura em memória (não necessariamente a mesma estrutura original, podendo ser, por exemplo, uma simples lista ao invés de uma árvore).&lt;/p&gt;

&lt;p&gt;Esse encoding/decoding (também chamado de serialização) pode ser feito de várias formas, e existem vários formatos que podem ser usados.&lt;/p&gt;

&lt;p&gt;Muitas linguagens de programação possuem seu próprio mecanismo de encode/decode de dados, como as classes java.io.Serializable. Porém não é recomendado usar essas bibliotecas pois não são compatíveis com outros ambientes. Então se um código Java serializa os dados usando a biblioteca nativa do Java e envia para uma aplicação rodando em Python, os dados provavelmente não vão poder ser deserializados. Por isso precisamos usar formatos padrões que qualquer linguagem e ambiente de programação entenda e consiga ler e escrever.&lt;/p&gt;

&lt;p&gt;Alguns dos formatos mais comuns são: CSV, XML e JSON que são formatos de texto e legíveis tanto por pessoas quanto por máquinas. Thrift e Protocol Buffers (ProtoBuf) são formatos de serialização binária, logo, legíveis somente por máquinas. Existe também o format Avro, que usa JSON para definição de Schema mas serializa os dados de forma binária. Recomendo ler a documentação de como esses formatos funcionam para definição de Schemas e serialização antes de continuar a leitura.&lt;/p&gt;

&lt;p&gt;Destes formatos, o único que não possui uma linguagem para definição de schema é CSV. Por isso é comum dados serializados nesse formato contarem com uma documentação complementar para definição dos tipos de dados. Ou simplesmente deixarem por conta de quem lê os dados a tarefa de interpretar os tipos utilizados. Porém, isso não é muito recomendável.&lt;/p&gt;

&lt;p&gt;Tanto JSON quanto XML contam com ferramentas para definição de schemas (XML Schema e JSON Schema), apesar de também ser razoavelmente comum dados transmitidos em formato JSON não terem também disponíveis um JSON Schema associado e contarem mais com documentação suplementar.&lt;/p&gt;

&lt;p&gt;Dados serializados de forma binária porém, sem um schema, são somente uma sequencia aparentemente aleatória de bytes. Thrift, ProtoBuf e Avro todos tem suas definições próprias de schema.&lt;/p&gt;

&lt;h2&gt;
  
  
  Evolução de Schema
&lt;/h2&gt;

&lt;p&gt;Os dados vão inevitavelmente mudar com o tempo. Com mudança nos dados, obviamente o schema também muda. Nessas mudanças de schema, devemos nos preocupar com compatibilidade:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;backward compatibility&lt;/em&gt;: código novo consegue ler dados antigos&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;forward compatibility&lt;/em&gt;: código antigo consegue ler dados novos&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Não mude seu schema com campos novos obrigatórios, mas sim opcionais. Assim, um código que desconhece o schema mais atualizado ainda consegue manipular os dados, pois os novos campos podem conter um valor padrão (0, uma string vazia ou NULL por exemplo).&lt;/p&gt;

&lt;p&gt;Caso use formatos binários como Thrift ou ProtoBuf, que fazem o encoding dos dados em uma ordem definida no schema, essa ordem deverá ser mantida.&lt;/p&gt;

&lt;p&gt;Mudanças de tipos de dados podem ser complicadas. Em alguns casos, pode ser que um tipo seja automaticamente convertido durante leitura ou escrita (ex: converter um int de 32bits para um int de 64bits, mas não o contrário). ProtoBuf permite também que valores opcionais sejam convertidos para um array de valores, pois na sequencia de bytes final um valor opcional e um array vazio são serializados da mesma forma, e durante deserialização, código que ainda acha que o dado é um valor opcional pode ler somente o último elemento da lista.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fluxo de dados
&lt;/h2&gt;

&lt;p&gt;Aplicações uma hora vão ter que ler os dados do banco e, em alguns casos, serializar esses dados e enviar para outra aplicação. Por isso devemos entender esses formatos de serialização, evolução de schema e manutenção de compatibilidade.&lt;/p&gt;

&lt;h3&gt;
  
  
  Fluxo de dados via Banco de Dados
&lt;/h3&gt;

&lt;p&gt;É comum aplicações diferentes acessarem o mesmo banco de dados. E nem sempre precisa ser aplicação X e Y. Pode ser aplicação X v1.0 e aplicação X v2.0 rodando ao mesmo tempo, como por exemplo para manter versões antigas de uma API web funcionando, ou durante um rolling upgrade, onde é feito de forma gradual o deploy de versões novas nos nós do cluster da aplicação e duas versões coexistem por um tempo.&lt;/p&gt;

&lt;p&gt;Por isso é importante se preocupar com a evolução do schema de dados para que uma schema migration quebre a compatibilidade entre as versões diferentes das aplicações que acessam o banco de dados. Durante schema migrations também é importante se preocupar como isso irá impactar a disponibilidade do servidor do banco de dados. Adicionar campos novos opcionais, novas tabelas ou views geralmente é bem rápido e não tem muito impacto. Já mudança de tipos de dados pode impactar a disponibilidade do servidor pois terá que ser feita a conversão ou re-escrita de todos os dados na tabela.&lt;/p&gt;

&lt;h3&gt;
  
  
  Fluxo de dados via serviços REST ou RPC
&lt;/h3&gt;

&lt;p&gt;Não vou entrar nos detalhes técnicos de cada um dos estilos de comunicação, até por que cada um é bem complexo por si só. Mas alguns pontos de atenção ao transmitir dados com esses diferentes tipos de serviços:&lt;/p&gt;

&lt;h4&gt;
  
  
  REST
&lt;/h4&gt;

&lt;p&gt;Geralmente serviços REST usam XML ou JSON como formato de encoding de dados. Então é importante ficar atento a como manter compatibilidade com esses formatos de dados.&lt;/p&gt;

&lt;p&gt;Um ponto interessante é tentar manter um schema para os dados, usando XML Schema ou JSON Schema. Muitas APIs REST que usam esses formatos não possuem um schema associado, e dependem de documentação auxiliar onde os tipos de dados são definidos para ajudar os clientes da API. Ter uma ferramenta que gere um XML/JSON Schema automaticamente é bem útil, pois manter esse schema em dois lugares diferentes, no código, mesmo q implicitamente, e na documentação, não é mistério nenhum que facilmente a documentação fica desatualizada.&lt;/p&gt;

&lt;h4&gt;
  
  
  Remote Procedure Calls
&lt;/h4&gt;

&lt;p&gt;Uma parte boa de alguns protocolos de Remote Procedure Call (RPC) é que o schema costuma ser obrigatório, como é o caso de tecnologias meio defasadas como SOAP Web Services, RMI, DCOM, e também com tecnologias mais recentes como Thrift e gRPC (que usa ProtoBuf).&lt;/p&gt;

&lt;p&gt;Alguns dos problemas dessa abordagem é que ela tenta criar uma abstração em cima de chamadas remotas parecida com chamadas a funções locais, que são conceitos fundamentalmente diferentes.&lt;/p&gt;

&lt;p&gt;Uma função local retorna um valor com sucesso ou retorna um erro, dependendo dos parametros. Uma chamada remota é imprevisível por natureza, pois a rede nem o servidor remoto são confiáveis e podem falhar por razões adversas.&lt;/p&gt;

&lt;p&gt;Uma função local pode entrar em um loop infinito ou um deadlock, mas daí a aplicação inteira trava. Uma requisição remota pode não retornar por causa de um timeout, seja por falha na rede ou um servidor não responsivo, e não dá pra saber a priori por que a requisição falhou.&lt;/p&gt;

&lt;p&gt;Uma requisição remota pode chegar ao servidor, ser processada, mas a resposta pode falhar em chegar ao cliente. Requisições repetidas podem ser problemáticas.&lt;/p&gt;

&lt;p&gt;Enfim, são paradigmas completamente diferentes e, filosoficamente falando, tratar chamadas remotas como chamada à funções é tratar conceitos diferentes de forma parecida.&lt;/p&gt;

&lt;p&gt;Apesar de que esses problemas de chamadas remotas são tratados por tecnologias modernas de RPC, com uso de Promises por exemplo. Mas uma "vantagem" conceitual do REST é que ele não tenta mascarar a existencia de uma chamada remota.&lt;/p&gt;

&lt;p&gt;Importante deixar claro que não tem certo ou errado e que esses problemas são só conceituais e que na prática é perfeitamente possível lidar com problemas de rede em chamadas RPC.&lt;/p&gt;

&lt;h3&gt;
  
  
  Fluxo de dados via envio de mensagens
&lt;/h3&gt;

&lt;p&gt;Uma outra maneira de ter fluxo de dados é via envio de mensagens (message-passing). Dessa forma, a comunicação é indireta, através de um intermediário conhecido como Message Queue ou Message Broker. Ferramentas conhecidas são RabbitMQ, ActiveMQ e Apache Kafka. Outra maneira de se trabalhar com message-passing é com algum framework de Actor Model. Frameworks como Akka e Orleans são alguns exemplos.&lt;/p&gt;

&lt;p&gt;Algumas vantagens desse modelo de message passing é que há desacoplamento entre o cliente que envia a mensagem e o processo que executa a requisição. Nesse modelo algumas coisas ficam mais fáceis de fazer do que nos outros modelos:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;O Message Broker funciona como um buffer entre request e response, assim se acontecer dos processos executando as requisições estarem sobrecarregados, as requisições são enfileiradas pelo broker até que os processos fiquem livres para continuar processando&lt;/li&gt;
&lt;li&gt;Os processos que executam as mensagens podem ser escalados independentemente&lt;/li&gt;
&lt;li&gt;Uma mensagem pode ser enviada à vários processos diferentes&lt;/li&gt;
&lt;li&gt;Se um processo falhar durante o processamento de uma mensagem, a mesma pode ser reenviada a outro processo redundante.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Importante lembrar que as mesmas preocupações com versionamento de schema das mensagens vale nesse modelo, para que processos que executam as mensagens continuem mantendo compatibilidade.&lt;/p&gt;

</description>
      <category>database</category>
      <category>architecture</category>
      <category>distributedsystems</category>
    </item>
    <item>
      <title>Designing Data Intensive Apps — Um resumo</title>
      <dc:creator>Breno Ferreira</dc:creator>
      <pubDate>Wed, 17 Jun 2020 17:49:06 +0000</pubDate>
      <link>https://dev.to/brenoferreira/designing-data-intensive-apps-um-resumo-2df9</link>
      <guid>https://dev.to/brenoferreira/designing-data-intensive-apps-um-resumo-2df9</guid>
      <description>&lt;p&gt;Estou lendo o livro &lt;a href="https://amzn.to/2YaKEhB"&gt;Designing Data Intensive Apps&lt;/a&gt; e resolvi fazer esse resumo aqui para servir de referencia para algumas ideias principais explicadas no livro. Esse resumo não substitui a leitura do livro. Irei abordar os temas do livro de forma mais superficial e quem quiser se aprofundar, &lt;a href="https://amzn.to/2YaKEhB"&gt;leia o livro&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to/brenoferreira/designing-data-intensive-apps-um-resumo-390c"&gt;Capítulo 1 — Sistemas de dados&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to/brenoferreira/designing-data-intensive-apps-resumo-cap-2-4b75"&gt;Capítulo 2 — Principais modelos de banco de dados&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to/brenoferreira/designing-data-intensive-apps-capitulo-3-1693"&gt;Capítulo 3 — Como funciona o storage de um banco de dados&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to/brenoferreira/encoding-e-dataflow-4p60"&gt;Capítulo 4 — Serialização e fluxo de dados&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to/brenoferreira/dados-distribuidos-replicacao-de-dado-1n96"&gt;Capítulo 5 — Dados distribuídos - Replicação de dados&lt;/a&gt;&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>database</category>
      <category>distributedsystems</category>
    </item>
    <item>
      <title>Como funciona o storage de um banco de dados</title>
      <dc:creator>Breno Ferreira</dc:creator>
      <pubDate>Tue, 16 Jun 2020 12:56:21 +0000</pubDate>
      <link>https://dev.to/brenoferreira/designing-data-intensive-apps-capitulo-3-1693</link>
      <guid>https://dev.to/brenoferreira/designing-data-intensive-apps-capitulo-3-1693</guid>
      <description>&lt;p&gt;Parte da &lt;a href="https://dev.to/brenoferreira/designing-data-intensive-apps-um-resumo-2df9"&gt;série&lt;/a&gt; sobre o resumo do livro &lt;a href="https://amzn.to/2YaKEhB"&gt;Designing Data Intensive Apps&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;No &lt;a href="https://dev.to/brenoferreira/designing-data-intensive-apps-um-resumo-390c"&gt;Capítulo 1&lt;/a&gt; foi falado sobre características de sistemas de dados, e no &lt;a href="https://dev.to/brenoferreira/designing-data-intensive-apps-resumo-cap-2-4b75"&gt;Capítulo 2&lt;/a&gt; sobre modelos de dados. Agora no capítulo 3 será abordado o tema de armazenamento de dados.&lt;/p&gt;

&lt;p&gt;Durante muito tempo, me perguntei como exatamente os dados do banco eram armazenados. Quando usava SQL Server reparava que havia os arquivos .mdf e .ldf de cada database, mas tinha a curiosidade de descobrir como funcionava esses arquivos. Enfim, dívidas técnicas da graduação.&lt;/p&gt;

&lt;p&gt;No livro, o autor explica algumas técnicas mais comuns utilizadas por DBs conhecidos.&lt;/p&gt;

&lt;h2&gt;
  
  
  Por que storage dos dados?
&lt;/h2&gt;

&lt;p&gt;Em toda aplicação, os dados são armazenados inicialmente em memória, em alguma estrutura de dados, seja um mapa, lista, fila, árvore, etc.. Essa estruturas de dados formam como se fosse uma "in-memory database" temporária. Temporária por que memória RAM é volátil e não compartilhada com outros processos e/ou servidores. Em algum momento precisamos persistir esses dados de forma mais permanente, ou arriscamos perder todos os dados à qualquer crash da aplicação ou servidor.&lt;/p&gt;

&lt;p&gt;Uma estratégia poderia ser serializar a estrutura de dados inteira em algum formato (JSON por exemplo) em um arquivo. Porém, temos que carregar o arquivo inteiro em memoria para podermos trabalhar sobre os dados. Em conjuntos de dados pequenos (de alguns MBs até alguns GBs) pode ser até possível. Mas isso não escala pois memória RAM tem um certo limite e preço alto. Armazenamento em disco é muito mais barato.&lt;/p&gt;

&lt;h2&gt;
  
  
  Como armazenar os dados?
&lt;/h2&gt;

&lt;p&gt;Como escrever e ler todos os dados de uma vez é muito ineficiente, precisamos de alguma forma ganhar eficiencia nessas duas operações.&lt;/p&gt;

&lt;p&gt;Um exemplo bem rudimentar de um banco de um key-value store pode ser criado da seguinte forma:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#!/bin/bash
db_set () {
    echo "$1,$2" &amp;gt;&amp;gt; database
}
db_get () {
    grep "^$1," database | sed -e "s/^$1,//" | tail -n 1
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;A função db_set insere um dado com uma chave e um valor. A função db_get retorna o valor associado a uma determinada chave.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ db_set 42 '{"name":"San Francisco","attractions":["Golden Gate Bridge"]}'
$ db_get 42
{"name":"San Francisco","attractions":["Golden Gate Bridge"]}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Repare que a função db_set faz somente um append no arquivo (com o uso do operador &amp;gt;&amp;gt;). Fazer append em algum arquivo funciona de maneira bem eficiente e mais rápida que uma escrita randômica, ou seja, em uma posição aleatória do arquivo.&lt;/p&gt;

&lt;p&gt;Porém, a função db_get já tem alguns problemas. Fazer uma pesquisa por uma chave no arquivo requer percorrer potencialmente todo o arquivo (o que é comumente conhecido como full-table scan). Quem já trabalhou com qualquer banco de dados sabe que a solução para full-table scans é um índice.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hashmap Index
&lt;/h2&gt;

&lt;p&gt;Digamos que voce tenha os seguintes dados em disco:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--i7Vf73zO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1600/1%2Av73u1oxrVYP5D2-SCYWMDA.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--i7Vf73zO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1600/1%2Av73u1oxrVYP5D2-SCYWMDA.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Podemos construir um índice usando um Hashmap Index, que é uma estrutura de dados que contêm pares chave e valor, ordenados por chave, e o índice funcionaria mapeando as chaves para um offset de bytes no arquivo.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--GlgrJeKH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1600/1%2A1cRyYITN21X6avP74MqxoQ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--GlgrJeKH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1600/1%2A1cRyYITN21X6avP74MqxoQ.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Como dissemos antes, para mantermos performance, escritas são um simples append no arquivo. Porém, se formos adicionando dados no nosso arquivo para sempre, muito rápido ele fica enorme. Além disso, precisamos também de operações de update e delete, além de insert.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--32KKJG1Z--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1600/1%2A2rAr-gCxOA0n-4s2uO6dxQ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--32KKJG1Z--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1600/1%2A2rAr-gCxOA0n-4s2uO6dxQ.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A ideia é escrever até que o tamanho do arquivo atinga um certo valor (64KB digamos), e quando esse tamanho for atingido, cria-se um novo segmento de escrita. De tempos em tempos, roda-se um algoritmo que faz compacta esses segmentos com um merge das chaves, geralmente assumindo-se que os últimos valores escritos são os mais atuais, e descartando-se os valores antigos.&lt;/p&gt;

&lt;p&gt;Obs: não confunda os dois key-value pairs. Um é armazenado em disco e contém os dados em si. O índice (que armazena os offsets em disco) é armazenado em memória.&lt;/p&gt;

&lt;h2&gt;
  
  
  Otimização com LSM Tree
&lt;/h2&gt;

&lt;p&gt;Vamos ver agora uma estrutura mais robusta para construção de índices construída em cima dessa ideia de um Hashmap Index.&lt;/p&gt;

&lt;p&gt;A ideia é ter duas estruturas de dados separadas. Uma chamada Memtable e outra chamada Sorted String Table (SSTable).&lt;/p&gt;

&lt;p&gt;A Memtable é alguma estrutura de dados ordenada, geralmente alguma árvore balanceada como Red-Black Tree ou AVL Tree. Ambas possuem boa eficiencia nas operações de inserção e busca, O(log n). Então podemos manter essa estrutura em memória que irá manter as atualizações mais recentes. Quando essa estrutura chegar a um tamanho limite definido, os dados são escritos em disco, em uma Sorted String Table (SSTable).&lt;/p&gt;

&lt;p&gt;A estrutura da SSTable é bem parecida com a descrita acima do Hashmap Index, com a diferença que ao invés dos dados estarem em ordem de escrita, eles são ordenados por chave. Isso aumenta a eficiencia da compactação dos segmentos, pois os dados já estão ordenados. Além disso, o índice em memória não precisa necessariamente conter todas as chaves, pois os dados em disco estão sempre ordenados, caso uma chave não esteja no índice, é possível achar uma chave de valor mais próximo e fazer o scan em disco a partir desse ponto, e assim não ter que fazer um full-table scan em disco, que seria muito custoso.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--saCN_OkY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1600/1%2AcH829VRcyhGpl-Nl4Td7kA.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--saCN_OkY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1600/1%2AcH829VRcyhGpl-Nl4Td7kA.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Essa estrutura de dados dualizada tem o nome de &lt;a href="https://en.wikipedia.org/wiki/Log-structured_merge-tree"&gt;Log-Structured Merge Tree&lt;/a&gt; (LSM Tree).&lt;/p&gt;

&lt;p&gt;Alguns problemas ainda existem com essa solução:&lt;/p&gt;

&lt;p&gt;Crash recovery: caso haja algum crash no servidor ou aplicação, podemos perder os dados da Memtable que ainda não foram persistidos em disco. Para contornar esse problema, ao mesmo tempo que inserimos dados na Memtable, salvamos uma réplica desses dados em disco em ordem de escrita, assim é possível recuperar a Memtable com os dados em disco depois de uma falha.&lt;/p&gt;

&lt;p&gt;Outro problema de performance que pode ocorrer é numa busca por uma chave que não existe. Para não ter que fazer uma busca tanto na Memtable quanto na SSTable inteiras, podemos utilizar &lt;a href="https://en.wikipedia.org/wiki/Bloom_filter"&gt;Bloom Filters&lt;/a&gt;, que é uma estrutura de dados probabilística que pode dizer de maneira eficiente que um dado &lt;strong&gt;talvez&lt;/strong&gt; exista no conjunto, mas que diz que um dado &lt;strong&gt;com certeza&lt;/strong&gt; não existe no conjunto.&lt;/p&gt;

&lt;h2&gt;
  
  
  B-Trees
&lt;/h2&gt;

&lt;p&gt;Índice com LSM Tree é uma técnica até recente. E a técnica mais comum e mais utilizada é diferente: B-Trees.&lt;/p&gt;

&lt;p&gt;B-Tree é uma estrutura de dados interessante pois permite que cada nó da arvore tenha mais de um elemento. Isso permite que a arvore tenha uma altura não muito grande, possibilitando armazenar muitos dados de maneira eficiente.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--UBjdb15f--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1600/1%2Avi9CXdoxuLDz8R5Ae0vGvw.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--UBjdb15f--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1600/1%2Avi9CXdoxuLDz8R5Ae0vGvw.jpeg" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Na B-Tree, os dados em si são armazenados nas folhas. Esses dados podem estar armazenados em memória ou em disco. E os nós, podem servir de índice para esses dados. Como cada nó possui mais de um elemento, é possível carregar blocos de dados de uma vez para serem processados mais eficientemente em memória e depois persistidos em disco novamente.&lt;/p&gt;

&lt;p&gt;Armazenando dados com B-Trees também requer um log append-only, similar ao da LSM-Tree, geralmente chamado de Write-Ahead log (WAL), para evitar perda de dados em caso de falhas, com a diferença que os dados são escritos primeiro no WAL, depois persistidos na árvore.&lt;/p&gt;

&lt;h2&gt;
  
  
  Vantagens e desvantagens de cada abordagem
&lt;/h2&gt;

&lt;p&gt;LSM Trees costumam ter performance de escrita maior, pois os dados podem ser persistidos mais rapidamente na Memtable, e as escritas em disco de SSTables são sequenciais. Apesar de que configurações ruins de merge e compactação de SSTables podem impactar negativamente a performance de escrita.&lt;/p&gt;

&lt;p&gt;B-Trees têm a desvantagem de ter que sempre escrever os dados duas vezes (no WAL e na arvore) e tem maior incidencia de escritas randômicas em diferentes segmentos da árvore, que é bem menos eficiente que escrita sequencial, principalmente em discos magnéticos ao invés de SSDs.&lt;/p&gt;

&lt;p&gt;Ao contrário de LSM-Trees que podem ter as chaves na Memtable e nas SSTables, B-Tree possuem  as chaves existem somente em um lugar, o que é uma enorme vantagem para manutenção de dados consistentes e isolamento de transações.&lt;/p&gt;

&lt;p&gt;Esse é um assunto bem extenso e complexo e aqui eu só dei uma resumida bem rápida e de alto nível. O livro aborda outros assuntos como storage para bancos de dados de analytics, column storage, múltiplos índices. Para não tornar esse post muito longo, recomendo a &lt;a href="https://amzn.to/2YaKEhB"&gt;leitura do livro&lt;/a&gt; e de literatura adicional sobre o assunto.&lt;/p&gt;

&lt;p&gt;originalmente publicado em: &lt;a href="https://medium.com/@breno_ferreira/designing-data-intensive-apps-cap%C3%ADtulo-3-eeec8782ab22"&gt;https://medium.com/@breno_ferreira/designing-data-intensive-apps-cap%C3%ADtulo-3-eeec8782ab22&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Conteúdo sob a licensa &lt;a href="https://creativecommons.org/licenses/by-nc/2.0/"&gt;CC-BY-NC&lt;/a&gt;&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>database</category>
      <category>distributedsystems</category>
    </item>
    <item>
      <title>Principais modelos de banco de dados</title>
      <dc:creator>Breno Ferreira</dc:creator>
      <pubDate>Fri, 05 Jun 2020 16:51:08 +0000</pubDate>
      <link>https://dev.to/brenoferreira/designing-data-intensive-apps-resumo-cap-2-4b75</link>
      <guid>https://dev.to/brenoferreira/designing-data-intensive-apps-resumo-cap-2-4b75</guid>
      <description>&lt;p&gt;Parte da &lt;a href="https://dev.to/brenoferreira/designing-data-intensive-apps-um-resumo-2df9"&gt;série&lt;/a&gt; sobre o resumo do livro &lt;a href="https://amzn.to/2YaKEhB"&gt;Designing Data Intensive Apps&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;No &lt;a href="https://dev.to/brenoferreira/designing-data-intensive-apps-um-resumo-390c"&gt;capítulo 1&lt;/a&gt; vimos alguns atributos de sistemas de dados: reliability, scalability e maintainability.&lt;/p&gt;

&lt;p&gt;No capítulo 2, o autor explica diferentes tipos de modelos de dados, principalmente os modelos relacional, documento e grafo.&lt;/p&gt;

&lt;h3&gt;
  
  
  Modelo relacional
&lt;/h3&gt;

&lt;p&gt;Nos anos 60 e 70, os modelos de dados dominantes eram o &lt;a href="https://en.wikipedia.org/wiki/Network_model"&gt;Network&lt;br&gt;
Model&lt;/a&gt; e o &lt;a href="https://en.wikipedia.org/wiki/Hierarchical_database_model"&gt;Hierarchical Model&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--XZfAl6wr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1600/1%2AJ1L0WS76HtUBv0XDrbKMZA.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--XZfAl6wr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1600/1%2AJ1L0WS76HtUBv0XDrbKMZA.png" alt=""&gt;&lt;/a&gt;&lt;br&gt;
&lt;span class="figcaption_hack"&gt;&lt;a href="https://commons.wikimedia.org/w/index.php?curid=65721948"&gt;“File:Bachman order processing model.tiff”&lt;/a&gt; by &lt;a href="https://commons.wikimedia.org/w/index.php?title=User:Mhkay&amp;amp;action=edit&amp;amp;redlink=1"&gt;Mhkay&lt;/a&gt; is licensed under &lt;a href="https://creativecommons.org/licenses/by-sa/4.0?ref=ccsearch&amp;amp;atype=rich"&gt;CC BY-SA4.0&lt;/a&gt;&lt;/span&gt;&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ITZzDnZS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1600/1%2AQxPvcO7VKDM6pMv5jnazpg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ITZzDnZS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1600/1%2AQxPvcO7VKDM6pMv5jnazpg.png" alt=""&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://commons.wikimedia.org/w/index.php?curid=58276075"&gt;“File:Hire.png”&lt;/a&gt; by &lt;a href="https://commons.wikimedia.org/w/index.php?title=User:Tsedenjav.Sh&amp;amp;action=edit&amp;amp;redlink=1"&gt;Tsedenjav.Sh&lt;/a&gt; is licensed under &lt;a href="https://creativecommons.org/licenses/by-sa/4.0?ref=ccsearch&amp;amp;atype=rich"&gt;CC BY-SA 4.0&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Mas nos anos 80, o modelo relacional dominou o mercado e esses outros dois modelos se tornaram obsoletos. Isso se deve a dois problemas principais com esses modelos.&lt;/p&gt;
&lt;h4&gt;
  
  
  Relações many-to-many
&lt;/h4&gt;

&lt;p&gt;No modelo hierárquico, era bem possível representar relações one-to-many, mas não era possível representar relacionamentos many-to-many. Vendo na figura acima, o modelo hierárquico é como uma árvore, ou seja, os nós podem ter muitos filhos, mas cada nó só pode ter um nó ancestral. Nessa estrutura, representar um modelo de "Produtos" e "Pedidos" fica difícil, pois um pedido pode ter vários produtos, mas não é possível o mesmo produto também estar em varios pedidos.&lt;/p&gt;
&lt;h4&gt;
  
  
  Linguagem de consulta
&lt;/h4&gt;

&lt;p&gt;Os modelos de rede, &lt;a href="https://en.wikipedia.org/wiki/CODASYL"&gt;CODASYL&lt;/a&gt; o mais conhecido, relacionamentos many-to-many eram possíveis. Inclusive é um modelo um pouco semelhante ao modelo de grafos que vamos ver mais adiante. Isso já era uma grande vantagem sobre o modelo hierárquico. Porém, o principal problema deste modelo na época era com consultas aos dados.&lt;/p&gt;

&lt;p&gt;Por exemplo, se em um modelo de dados como: &lt;em&gt;Escola → Turmas → Alunos,&lt;/em&gt; se voce quisesse acessar o dado de um aluno, era necessário percorrer os nós Escola e Turmas para chegar ao dado do aluno.&lt;/p&gt;

&lt;p&gt;Além disso, essas consultas eram escritas em linguagens bem imperativas, que, apesar de ter boa performance para a época, eram difíceis de escrever e relativamente frágeis com relação a mudanças no modelo de dados.&lt;/p&gt;

&lt;p&gt;Em meados da década de 80, surge os primeiros RDBMS, que além de suportar relacionamentos many-to-many, também tinham uma linguagem de consultas, SQL, bem poderosa, expressiva e declarativa, que permitia ao analista escrever quais&lt;br&gt;
dados a consulta deve retornar, e não como a consulta deve retornar os dados. Com avanços no desenvolvimento das engines de planos de execução de SQL, performance deixou de ser um problema.&lt;/p&gt;
&lt;h3&gt;
  
  
  Modelo de documentos
&lt;/h3&gt;

&lt;p&gt;No inicio dos anos 2010, depois de quase 30 anos de dominio do modelo relacional, começou a entrar em voga questionar o absolutismo do SQL, por algumas razões, principalmente:&lt;/p&gt;

&lt;p&gt;Necessidade de escalabilidade com alto volume de escrita. Ao contrário da década de 80 e 90, agora em 2010 acesso à computadores é mais democratizado e existe a internet, no começo do boom das redes sociais e bilhões de posts por dia. Em um banco de dados relacional, dados consistentes são garantidos, e isso pode after performance de escrita.&lt;/p&gt;

&lt;p&gt;Em bancos de dados relacionais, os dados também devem seguir um schema pré-definido. Muitos dos grandes sites dessa época eram escritos em linguagens como Ruby/Rails que pregam uma abordagem mais dinâmica, flexível e expressiva. RDBMSs com seus requerimentos de schema, e também com o problema da &lt;a href="https://en.wikipedia.org/wiki/Object-relational_impedance_mismatch"&gt;Impedância Objeto-Relacional&lt;/a&gt; criaram um ambiente que trouxe ideias lá da década de 70 de volta, com uma cara nova. Os modelos NoSQL (Não Relacionais) como Key-Value Stores e Document&lt;br&gt;
Databases começaram a aparecer nesse periodo.&lt;/p&gt;

&lt;p&gt;O modelo de documentos por exemplo, é bem parecido com o modelo hierárquico e tem nativamente a mesma restrição com relacionamentos many-to-many, além disso, Document Databases não costumam oferecer suporte a joins entre tabelas. Porém, modelos de documentos favorecem manter dados relacionados próximos, no mesmo documento, evitando assim a necessidade de joins.&lt;/p&gt;
&lt;h3&gt;
  
  
  Relacional ou Não relacional
&lt;/h3&gt;

&lt;p&gt;A resposta é sempre: depende.&lt;/p&gt;

&lt;p&gt;Algumas &lt;strong&gt;vantagens do modelo não relacional sobre o relacional&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Flexibilidade de schema&lt;/strong&gt;: Isso não quer dizer que não há schema. Ele existe. Só que em um modelo de documentos, existe o que é chamado de Schema on Read: o schema não é forçado pelo banco de dados, mas geralmente validado e tratado pela aplicação.&lt;/p&gt;

&lt;p&gt;Localidade de dados: um documento geralmente contem todos os dados relacionados a uma entidade, sem a necessidade de joins em outros documentos.&lt;/p&gt;

&lt;p&gt;Não há necessidade de ORMs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Vantagens do banco relacional sobre o não relacional&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Suporte nativo a relacionamentos entre entidades, inclusive many-to-many, e joins entre entidades feitos pela própria linguagem de consultas, SQL, removendo a responsabilidade da aplicação de fazer esses joins. Em Document DBs caso seja necessário fazer um join, as vezes há múltiplos roundtrips ao banco, para trazer dados de dois ou mais documentos, enquanto uma única query SQL pode trazer dados de varias tabelas.&lt;/p&gt;

&lt;p&gt;Normalização evita dados duplicados e tira a responsabilidade da aplicação de manter consistência dos dados, que é garantida pelo RDBMS.&lt;/p&gt;

&lt;p&gt;Exemplo:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--bKBNwlJZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1600/1%2AS_FDzbWZQwfR7pno9RJdbA.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--bKBNwlJZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1600/1%2AS_FDzbWZQwfR7pno9RJdbA.jpeg" alt=""&gt;&lt;/a&gt;&lt;br&gt;
&lt;span class="figcaption_hack"&gt;Cliente, Pedidos e Produtos — Modelo Relacional&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ZyXO-SSw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1600/1%2A_SntHbbuw_z1lFTdvbN-_w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ZyXO-SSw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1600/1%2A_SntHbbuw_z1lFTdvbN-_w.png" alt=""&gt;&lt;/a&gt;&lt;br&gt;
&lt;span class="figcaption_hack"&gt;Exemplo Cliente Pedido Produto - Modelo de Documentos&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;Com esses exemplos dá pra se ter uma ideia das vantagens e desvantagens de cada modelo descritas acima. Repare que o documento de Pedidos já possui quase todos os dados no mesmo documento, com uma certa duplicação dos dados (dos produtos) e uma "chave estrangeira" para o documento de Clientes, que irá forçar a aplicação a fazer o "join" para obter os dados do cliente.&lt;/p&gt;

&lt;p&gt;Já o modelo relacional está bem normalizado, sem duplicação de dados, mas que para obter os dados completos do pedido, é necessário fazer join em outras tres tabelas (Produto, Item Produto e cliente). Porém essa consulta é facilmente executada em somente uma requisição ao banco de dados.&lt;/p&gt;
&lt;h3&gt;
  
  
  Modelo de Grafos
&lt;/h3&gt;

&lt;p&gt;E se seu modelo de dados tem muitas relações many-to-many? Como esse tipo de relacionamento não é suportado em Document Databases, e ter varias tabelas N-N em um modelo relacional (como a tabela ItemPedido acima), torna o modelo e consultas demasiadamente complexas.&lt;/p&gt;

&lt;p&gt;O modelo de grafos nesse caso é bom para isso, onde cada entidade pode estar relacionada com qualquer outra entidade.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--MBG6hR7D--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1600/1%2ACRHAtN2U-QBcC2DwoSvYrw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--MBG6hR7D--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1600/1%2ACRHAtN2U-QBcC2DwoSvYrw.png" alt=""&gt;&lt;/a&gt;&lt;br&gt;
&lt;span class="figcaption_hack"&gt;&lt;a href="https://commons.wikimedia.org/w/index.php?curid=58138802"&gt;“File:Property graph model.png”&lt;/a&gt; by &lt;a href="https://commons.wikimedia.org/w/index.php?title=User:%D0%9C.%D0%9E%D1%8E%D1%83%D0%BD%D0%B1%D0%BE%D0%BB%D0%BE%D1%80&amp;amp;action=edit&amp;amp;redlink=1"&gt;М.Оюунболор&lt;/a&gt; is licensed under &lt;a href="https://creativecommons.org/licenses/by-sa/4.0?ref=ccsearch&amp;amp;atype=rich"&gt;CC BY-SA 4.0&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;Nesse modelo, entidades, também chamadas de nós ou vertices, e os&lt;br&gt;
relacionamentos são as arestas.&lt;/p&gt;

&lt;p&gt;Alguns bons exemplos de uso de Graph Databases:&lt;/p&gt;

&lt;p&gt;Social Graph: onde vertices podem ser pessoas interconectadas, locais onde empresas estão localizadas, pessoas fazem checkin, posts que tem comentários, likes, shares, etc.&lt;/p&gt;

&lt;p&gt;Modelar isso com um modelo de documentos ou relacional pode ser bem mais dificil do que com um modelo de grafos.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--x5wcblaN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1600/1%2AVSnhc_cmP13gV3iz7My0iw.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--x5wcblaN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1600/1%2AVSnhc_cmP13gV3iz7My0iw.jpeg" alt=""&gt;&lt;/a&gt;&lt;br&gt;
&lt;span class="figcaption_hack"&gt;Social Graph&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;Graph Databases tem uma linguagem de consultas, as mais comuns sendo SparQL, Cypher e Datalog.&lt;/p&gt;

&lt;p&gt;Um banco de dados de grafos disponivel abertamente para consultas usando a linguagem SparQL é o &lt;a href="https://query.wikidata.org"&gt;Wikidata&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/1jHoUkj_mKw"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h3&gt;
  
  
  Como escolher o modelo?
&lt;/h3&gt;

&lt;p&gt;Uma estratégia é entender primeiro os requerimentos dos relacionamentos entre as entidades. Se houver relacionamentos many-to-many, Document Databases&lt;br&gt;
provavelmente não vai ser a melhor escolha. Já se houver relacionamentos one-to-many onde os dados podem ficar co-localizados na mesma entidade,&lt;br&gt;
desnormalizados e sem requerimentos rigorosos de consistência, um modelo de documentos pode ser adequado. E se voce verificar que as entidade pode ser relacionar com qualquer outra entidade, grafos podem ser uma boa solução.&lt;/p&gt;

&lt;p&gt;originalmente publicado em: &lt;a href="https://medium.com/@breno_ferreira/designing-data-intensive-apps-resumo-cap-2-4ddf1d5659a1"&gt;https://medium.com/@breno_ferreira/designing-data-intensive-apps-resumo-cap-2-4ddf1d5659a1&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Conteúdo sob a licensa &lt;a href="https://creativecommons.org/licenses/by-nc/2.0/"&gt;CC-BY-NC&lt;/a&gt;&lt;/p&gt;

</description>
      <category>database</category>
      <category>architecture</category>
      <category>distributedsystems</category>
    </item>
    <item>
      <title>Características de um sistema de dados</title>
      <dc:creator>Breno Ferreira</dc:creator>
      <pubDate>Wed, 03 Jun 2020 11:58:50 +0000</pubDate>
      <link>https://dev.to/brenoferreira/designing-data-intensive-apps-um-resumo-390c</link>
      <guid>https://dev.to/brenoferreira/designing-data-intensive-apps-um-resumo-390c</guid>
      <description>&lt;p&gt;Parte da &lt;a href="https://dev.to/brenoferreira/designing-data-intensive-apps-um-resumo-2df9"&gt;série&lt;/a&gt; sobre o resumo do livro &lt;a href="https://amzn.to/2YaKEhB"&gt;Designing Data Intensive Apps&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Em qualquer sistema que manipule dados (ou seja, todo sistema de informação), há tres pilares sobre os quais as pessoas responsáveis devem pensar:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reliability&lt;/li&gt;
&lt;li&gt;Scalability&lt;/li&gt;
&lt;li&gt;Maintainability&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Reliability
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Propriedade de um sistema de dados de tolerar falhas.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Falhas são invevitáveis, em qualquer sistema (mais sobre esse assunto nos próximos capítulos). O máximo que dá para fazer é tolerá-las. Ou seja, o sistema deveria continuar funcionado mesmo quando houver falhas. Essas falhas podem ser: falhas de hardware, erros de software e erros humanos.&lt;/p&gt;

&lt;h3&gt;
  
  
  Falhas de hardware
&lt;/h3&gt;

&lt;p&gt;Todo hardware falha. E conforme voce aumenta a escala do seu hardware, a probabilidade de falha aumenta. Em algum momento alguma coisa vai quebrar, e deveria haver hardware redundante para assumir o controle assim que um problema for detectado, idealmente de forma automatizada.&lt;/p&gt;

&lt;h3&gt;
  
  
  Erros de software
&lt;/h3&gt;

&lt;p&gt;Problemas no software podem causar falhas tão catastróficas quanto um rack no servidor desconectado. Esses podem ser as vezes bugs que podem ser detectados com uma suite de testes melhor e de maior cobertura, mas também podem ser coisas que as vezes são inesperadas, como por exemplo:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Bugs em bibliotecas ou frameworks&lt;/li&gt;
&lt;li&gt;Crash do sistema operacional&lt;/li&gt;
&lt;li&gt;Algum processo que causa uso muito alto de algum recurso computacional (CPU, memoria, disco ou rede)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;E o pior dos casos, quando em um sistema onde há dependencias entre modulos, uma falha em um módulo cascateia e gera falha em outros N módulos dependentes.&lt;/p&gt;

&lt;p&gt;O que ajuda a melhorar a tolerancia a falhas de software: testes melhores, diminuir dependencias, restart automático de processos e monitoramento.&lt;/p&gt;

&lt;h3&gt;
  
  
  Erros humanos
&lt;/h3&gt;

&lt;p&gt;Humanos fazem e operam sistemas. E humanos são conhecidamente não muito confiaveis. E muitos problemas tem sua causa algum erro humano (update sem where em produção, alguém?).&lt;/p&gt;

&lt;p&gt;Algumas estratégias para minimizar o impacto de falhas humanas:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Construa o sistema de forma que minimize a chance de errors acontecerem. Boa documentação e interfaces que facilitem o uso e deixem facil fazer a coisa certa e mais dificil fazer a coisa errada.&lt;/li&gt;
&lt;li&gt;Desacople os lugares onde as pessoas podem cometer erros dos lugares onde elas causam falhas. Por exemplo, ter um ambiente de testes onde as pessoas podem testar o sistema de forma que erros não causam falhas em produção com maior impacto.&lt;/li&gt;
&lt;li&gt;Facilite rollback de mudanças&lt;/li&gt;
&lt;li&gt;Boas práticas de liderança para evitar apontar culpados quando falhas humanas acontecem.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Scalability
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Primeiramente, precisamos descrever a carga atual do sistema; somente depois podemos discutir questões de crescimento.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Para se ter ideia da escalabilidade de um sistema, é necessario medir a carga e performance atual do sistema. A partir daí, será possível medir e investigar o que acontece quando a carga aumenta. Algumas coisas que é necessario medir:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;uso de recursos de hardware (CPU, Memoria, uso de rede, etc.)&lt;/li&gt;
&lt;li&gt;tempo de resposta, geralmente medido em percentis (p95, p99, p999). Ou seja, se seu tempo de resposta p95 é 50ms, significa de 95% dos requests responde em 50ms ou menos. Os outros 5% respondem em tempo maior. Aí entram no jogo possíveis SLAs definidos por contrato, que podem definir um p99 ou p999 (99.9%) com alguns limites.&lt;/li&gt;
&lt;li&gt;Latencia de comunicação. Não confundir latencia com tempo de resposta. Latencia é o tempo de transporte de mensagens, e não leva em consideração tempo de processamento. Geralmente dá pra assumir que tempo de resposta = latencia + processamento.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;É claro que a arquitetura do sistema vai ter um impacto enorme na escalabilidade do sistema. Para definir a arquitetura do sistema, é necessario saber os parametros de carga com os quais o sistema irá trabalhar. Onde vai haver maior demanda? Vai ter mais escrita ou leitura? Qual o tamanho dos dados? Construir um sistema para responder 100K req/s, cada um processando alguns KBs de dados é bem diferente de um sistema que processa 10 de req/s processando 1GB.&lt;/p&gt;

&lt;h2&gt;
  
  
  Maintainability
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Um sistema de facil manutenção torna a vida dos desenvolvedores e dos sys-admins mais facil.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Um sistema com boa manutenibilidade tenta minimizar as dores de manter e operar um sistema, focando em tres areas:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;Operabilidade&lt;/em&gt;: facilidade de operação do sistema que manter as coisas funcionando bem&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Simplicidade&lt;/em&gt;: facilidade de compreender como o sistema e suas partes funciona&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Capacidade de evolução&lt;/em&gt;: quão facil é fazer mudanças no sistema? Requerimentos e casos de uso mudam frequentemente. Complexidade que não é inerente ao problema sendo resolvido mas sim à implementação torna o software mais dificil de mudar e evoluir.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Os próximos capítulos do livro explicam como sistemas de dados diferentes impactam em cada uma dessas tres areas.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;originalmente postado em: &lt;a href="https://medium.com/@breno_ferreira/designing-data-intensive-apps-um-resumo-1a62de5358f4"&gt;https://medium.com/@breno_ferreira/designing-data-intensive-apps-um-resumo-1a62de5358f4&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Conteúdo sob a licensa CC-BY-NC&lt;/p&gt;

</description>
      <category>database</category>
      <category>distributedsystems</category>
      <category>architecture</category>
    </item>
  </channel>
</rss>
