[{"data":1,"prerenderedAt":1077},["ShallowReactive",2],{"blog-alternates-\u002Fmudanca-schema-alarm-30s-noite-bilhao-linhas":3,"blog-nav-facets-pt":6,"blog-post-pt-mudanca-schema-alarm-30s-noite-bilhao-linhas":13},{"pt":4,"en":5},"\u002Fmudanca-schema-alarm-30s-noite-bilhao-linhas","\u002Fen\u002Fschema-change-30-second-alarm-billion-row-night",{"populatedCategories":7,"authorCount":12},[8,9,10,11],"operacao","produto","dooh","engenharia",1,{"post":14,"authorIndex":1024,"counterpartSlug":1030,"related":1031},{"id":15,"title":16,"authors":17,"body":19,"category":11,"cover":992,"description":995,"discussionUrl":996,"draft":997,"extension":998,"featured":997,"highlights":999,"meta":1010,"navigation":1011,"ogImage":993,"path":1012,"publishedAt":1013,"seo":1014,"slug":1015,"sourceHash":996,"stem":1016,"tags":1017,"translate":1011,"translatedFrom":996,"translationKey":1022,"translationStatus":996,"updatedAt":996,"__hash__":1023},"posts_pt\u002Fpt\u002Fposts\u002Fmudanca-schema-alarm-30s-noite-bilhao-linhas.md","Uma mudança de schema, um alarm de 30 segundos e uma noite de um bilhão de linhas",[18],"paulo-ramires",{"type":20,"value":21,"toc":959},"minimark",[22,26,31,35,38,41,60,70,80,83,86,89,93,96,99,102,108,111,127,130,133,136,139,142,145,148,152,155,158,161,169,172,175,192,195,198,201,205,212,220,224,227,230,234,239,242,256,259,262,265,269,272,275,278,284,287,290,293,297,300,303,306,309,312,315,318,324,327,332,335,339,342,345,348,351,354,357,360,363,366,369,372,376,379,384,387,392,395,398,403,406,409,414,417,420,425,428,432,435,438,441,445,449,452,455,458,461,464,468,471,491,494,499,503,506,509,512,516,519,522,536,541,545,551,554,558,561,564,567,570,573,576,582,585,588,591,594,599,607,610,614,617,637,640,643,647,652,655,658,663,666,669,672,677,680,683,688,691,694,697,700,705,708,711,714,718,721,724,741,744,747,750,760,763,767,770,773,778,781,784,789,792,795,798,801,804,807,810,813,817,820,823,837,840,843,847,850,872,877,884,887,891,894,900,910,916,919,923,926,929,932,935,938,941,944,947,950,953,956],[23,24],"phase-marker",{"tone":25},"incident",[27,28,30],"h2",{"id":29},"a-versão-curta","A versão curta",[32,33,34],"p",{},"Entre 2 e 3 de setembro de 2026, a produção da Lucimark entrou em uma tempestade de armazenamento SQLite em Durable Object.",[32,36,37],{},"Acabávamos de enviar uma mudança de schema de Proof of Play: guardar a localização do player como uma trilha de geohash nos rollups de 15 minutos, em vez de embutir a geografia na chave única de cada linha. A mudança de modelo era sólida. A estratégia de migração não foi dimensionada para o nosso maior tenant — centenas de milhares de linhas de rollup dentro de um único TenantDO.",[32,39,40],{},"Dois modos de falha, em sequência.",[32,42,43,47,48,55,56,59],{},[44,45,46],"strong",{},"Dia um — writes."," Uma reconstrução síncrona no startup do Durable Object produziu cerca de 625 milhões de writes SQLite faturáveis em poucas horas. Nos ",[49,50,54],"a",{"href":51,"rel":52},"https:\u002F\u002Fdevelopers.cloudflare.com\u002Fdurable-objects\u002Fplatform\u002Fpricing\u002F",[53],"nofollow","preços de lista da Cloudflare para SQLite em Durable Objects"," (documentação consultada em 25 de agosto de 2026), writes no plano Workers Paid custam US$ 1,00 \u002F milhão de linhas e reads US$ 0,001 \u002F milhão — cerca de 1.000×. Isso é equivalente bruto de lista, ",[44,57,58],{},"antes"," das franquias incluídas, não o valor efetivamente faturado.",[32,61,62,65,66,69],{},[44,63,64],{},"Noite e manhã seguinte — reads."," Movemos o restante para um backfill assíncrono acionado por alarm — um replay em lotes do trabalho que o boot não conseguiu terminar. Isso parou as falhas de startup, mas, com o índice UNIQUE fora, a máquina de estados repetia SQL O(n) — tempo proporcional ao número de linhas da tabela — a cada ~30 segundos. Os picos horários de reads chegaram a aproximadamente 0,3–1,4 bilhão de linhas faturáveis. Os reads eram bem mais baratos por linha. O perigo era outro: ",[44,67,68],{},"a migração tinha virado um loop sem limite."," Nada na plataforma a interromperia naturalmente.",[71,72],"blog-figure",{":breakout":73,":height":74,":n":75,":width":76,"alt":77,"caption":78,"src":79},"true","941","1","1672","Comparação dos preços de lista de writes e reads do SQLite em Cloudflare Durable Objects com os volumes observados durante o incidente.","Writes causaram o choque imediato de custo. Reads eram cerca de 1.000× mais baratos por linha no preço de lista, mas revelaram um loop sem limite. Equivalentes brutos de lista, antes das franquias incluídas; não é a fatura.","\u002Fimg\u002Fposts\u002Fdo-storage-storm\u002Fdurable-objects-reads-vs-writes-cost.png",[32,81,82],{},"Não foi artefato de GraphQL, glitch de billing nem ingest de Proof of Play descontrolado. O ingest daquele dia foi só milhares de inserts de rollup. O tráfego HTTP dos Workers aumentou de forma modesta.",[32,84,85],{},"Quando entendemos o loop, a contenção foi direta: estacionar o housekeeping acionado por alarm, publicar cooldown e um índice helper, parar de rearmar estados pesados a cada 30 segundos, remover rollups históricos descartáveis de Proof of Play do tenant afetado e marcar a migração como completa. Os reads caíram de dezenas de milhões por hora para perto do baseline na mesma manhã.",[32,87,88],{},"A tempestade estava no data plane — custo e saturação, não uma interrupção de playback na frota.",[27,90,92],{"id":91},"por-que-essa-arquitetura-tornou-o-incidente-possível","Por que essa arquitetura tornou o incidente possível",[32,94,95],{},"A Lucimark é uma plataforma DOOH para operar redes de telas pelo navegador.",[32,97,98],{},"Nossa API roda em Cloudflare Workers. Cada tenant de cliente é representado por um Durable Object — um TenantDO — com seu próprio banco SQLite.",[32,100,101],{},"Os rollups de Proof of Play também vivem aí: resumos de 15 minutos e diários descrevendo o que tocou, onde e quando.",[71,103],{":breakout":73,":height":74,":n":104,":width":76,"alt":105,"caption":106,"src":107},"2","Arquitetura do incidente na Lucimark mostrando API, Durable Object isolado por tenant, armazenamento SQLite de Proof of Play e reprodução nas telas não afetada.","O incidente ficou isolado no SQLite de Proof of Play daquele TenantDO. A frota de telas não era o blast radius.","\u002Fimg\u002Fposts\u002Fdo-storage-storm\u002Flucimark-tenantdo-incident-architecture.png",[32,109,110],{},"Essa arquitetura nos dá propriedades úteis:",[112,113,114,118,121,124],"ul",{},[115,116,117],"li",{},"isolamento por tenant;",[115,119,120],{},"localidade dos dados;",[115,122,123],{},"um blast radius claro;",[115,125,126],{},"nenhum banco relacional compartilhado virando gargalo de todos os clientes.",[32,128,129],{},"Mas também cria uma superfície de faturamento.",[32,131,132],{},"O SQLite de Durable Object cobra por linhas lidas e escritas. Writes são dramaticamente mais caros por linha do que reads, mas qualquer um dos dois pode virar problema operacional quando um algoritmo toca uma tabela grande inteira de forma repetida.",[32,134,135],{},"Um tenant de alto volume nessa arquitetura não é simplesmente “mais HTTP”.",[32,137,138],{},"É mais linhas dentro de um SQLite, potencialmente revisitadas por uma máquina de estados cada vez que aquele Durable Object acorda.",[32,140,141],{},"Os tenants medianos atravessaram essa migração sem incidente.",[32,143,144],{},"Uma tabela grande de rollup não.",[32,146,147],{},"A Cloudflare oferece notificações de budget, mas elas não interrompem o consumo em runtime. O próprio sistema ainda precisa de limites de engenharia.",[27,149,151],{"id":150},"o-que-estávamos-tentando-enviar","O que estávamos tentando enviar",[32,153,154],{},"Nossas linhas de Proof of Play de 15 minutos originalmente carregavam geografia dentro da natural key — a chave de negócio que identifica o rollup, distinta da chave primária interna.",[32,156,157],{},"Isso fazia da geografia, na prática, parte da identidade do registro: mudar informação de localização podia bifurcar o que deveria representar o mesmo rollup lógico.",[32,159,160],{},"O novo modelo guardava uma trilha de geohash em separado e removia a geografia da chave única.",[71,162],{":breakout":73,":height":163,":n":164,":width":165,"alt":166,"caption":167,"src":168,":hideOnMobile":73},"887","3","1774","Comparação do schema de Proof of Play antes e depois, com geo removido da natural_key e armazenado em geohash_trail.","A geografia saiu da identidade do rollup e passou para a coluna geohash_trail. Identificadores técnicos iguais aos do schema.","\u002Fimg\u002Fposts\u002Fdo-storage-storm\u002Fproof-of-play-geohash-schema-before-after.png",[170,171],"schema-compare",{},[32,173,174],{},"Isso exigia que a migração:",[112,176,177,180,183,186,189],{},[115,178,179],{},"introduzisse a nova representação;",[115,181,182],{},"atualizasse as linhas de rollup existentes;",[115,184,185],{},"mudasse a natural key;",[115,187,188],{},"deduplicasse colisões;",[115,190,191],{},"reconstruísse a unicidade.",[32,193,194],{},"Para um tenant pequeno, isso é uma migração.",[32,196,197],{},"Para o maior tenant afetado — cerca de 540k rollups de 15 minutos mais 280k linhas diárias — era, na prática, reescrever uma das tabelas históricas mais quentes do objeto.",[32,199,200],{},"Subestimamos essa tabela.",[27,202,204],{"id":203},"linha-do-tempo","Linha do tempo",[32,206,207,208,211],{},"Horários em ",[44,209,210],{},"BRT (UTC−3)",", o fuso do post-mortem operacional.",[71,213],{":breakout":73,":height":214,":n":215,":width":216,"alt":217,"caption":218,"src":219,":hideOnMobile":73},"724","4","2172","Linha do tempo do incidente de Durable Objects da Lucimark, do deploy em 2 de setembro à recuperação em 3 de setembro.","Do deploy à recuperação, em duas janelas: pico de writes em 2 de setembro, depois read storm acionado pelos alarms. Visão geral; a tabela abaixo é a fonte legível.","\u002Fimg\u002Fposts\u002Fdo-storage-storm\u002Fdurable-objects-incident-timeline.png",[221,222],"incident-timeline",{":items":223},"[{\"when\":\"2 set ~14:16 BRT\",\"event\":\"Deploy da migração geohash-trail\",\"result\":\"Trabalho pesado no startup do Durable Object.\"},{\"when\":\"2 set 14:00–15:00 BRT\",\"event\":\"Pico de writes\",\"result\":\"Dois buckets horários consecutivos, ~333M e ~290M de linhas escritas faturáveis\u002Fh — sem atribuir qual hora a qual total. 503s e resets no tenant grande.\"},{\"when\":\"2 set fim da tarde\",\"event\":\"Migração sai do boot\",\"result\":\"Backfill assíncrono por alarm. Os 503s param; o amplificador só muda de lugar.\"},{\"when\":\"2 set noite\",\"event\":\"Read storm\",\"result\":\"Writes normalizam. Reads faturáveis ~0,3–1,4B\u002Fh enquanto o UNIQUE está fora.\"},{\"when\":\"3 set manhã\",\"event\":\"Alarms estacionados (~09:20) e hotfix\",\"result\":\"O loop de scan para. Residual ainda ~57M–74M reads\u002Fh.\"},{\"when\":\"3 set ~12:01 BRT\",\"event\":\"PoP histórico removido\",\"result\":\"Dataset operacional não autoritativo; ingest forward segue. Migração marcada done.\"}]",[23,225],{"tone":226},"recovery",[32,228,229],{},"A curva horária de reads depois disso está na seção de recuperação — não aqui.",[27,231,233],{"id":232},"causa-raiz-quatro-coisas-que-ficaram-perigosas-juntas","Causa raiz: quatro coisas que ficaram perigosas juntas",[235,236,238],"h3",{"id":237},"_1-trabalho-pesado-em-uma-tabela-grande","1. Trabalho pesado em uma tabela grande",[32,240,241],{},"A migração exigia operações equivalentes a:",[112,243,244,247,250,253],{},[115,245,246],{},"remover temporariamente a unicidade;",[115,248,249],{},"atualizar linhas existentes;",[115,251,252],{},"deduplicar;",[115,254,255],{},"recriar o índice UNIQUE.",[32,257,258],{},"Enquanto a estrutura UNIQUE estava indisponível, operações como deduplicação e reconstrução de índice precisavam tocar grandes partes da tabela.",[32,260,261],{},"Em centenas de milhares de linhas, isso importa.",[32,263,264],{},"De forma repetida, vira o incidente.",[235,266,268],{"id":267},"_2-mover-trabalho-sync-para-async-não-o-tornou-seguro","2. Mover trabalho sync para async não o tornou seguro",[32,270,271],{},"Nossa primeira implementação fazia demais durante o startup do Durable Object.",[32,273,274],{},"Isso causou resets e 503s.",[32,276,277],{},"Mover o trabalho para uma máquina de estados acionada por alarm foi a resposta correta ao problema de startup.",[279,280,281],"pull-quote",{},[32,282,283],{},"Async ≠ seguro.",[32,285,286],{},"A versão síncrona tinha um limite natural de falha: o startup podia estourar timeout ou o objeto podia resetar.",[32,288,289],{},"A versão assíncrona podia continuar acordando indefinidamente.",[32,291,292],{},"Tirar trabalho caro do caminho de request removeu uma restrição operacional sem adicionar outra.",[235,294,296],{"id":295},"_3-a-cadência-do-alarm-virou-multiplicador-de-custo","3. A cadência do alarm virou multiplicador de custo",[32,298,299],{},"Durante a migração, os estados eram aproximadamente:",[32,301,302],{},"pending → updating → deduping → indexing",[32,304,305],{},"Os updates em lote eram gerenciáveis.",[32,307,308],{},"Os estados perigosos eram os que podiam exigir trabalho O(n) — varrer ou reconstruir a tabela inteira.",[32,310,311],{},"O backfill podia se rearmar em cadência de cerca de 30 segundos enquanto incompleto.",[32,313,314],{},"Em uma tabela com mais de meio milhão de linhas de rollup, isso significava que uma operação teoricamente cara podia ser revisitada cerca de 120 vezes por hora.",[32,316,317],{},"É aí que uma migração lenta vira runaway.",[71,319],{":breakout":73,":height":74,":n":320,":width":76,"alt":321,"caption":322,"src":323,":hideOnMobile":73},"5","Loop amplificador de 30 segundos mostrando update, deduplicação, criação de índice unique, falha e nova tentativa cerca de 120 vezes por hora.","Um alarm de ~30 s transformou operações O(n) da migração em um amplificador recorrente. O ×120\u002Fh segue da cadência, não de uma medição à parte.","\u002Fimg\u002Fposts\u002Fdo-storage-storm\u002Fdurable-objects-30-second-amplifier-loop.png",[325,326],"amplifier-loop",{},[32,328,329],{},[44,330,331],{},"A cadência do alarm é um multiplicador de custo.",[32,333,334],{},"Frequência de agendamento não pode ser tratada como detalhe de implementação quando a operação agendada escala com o tamanho da tabela.",[235,336,338],{"id":337},"_4-a-criação-falha-de-unique-não-tinha-breaker","4. A criação falha de UNIQUE não tinha breaker",[32,340,341],{},"Duplicatas permaneceram.",[32,343,344],{},"O rebuild do índice UNIQUE falhou.",[32,346,347],{},"O erro foi logado.",[32,349,350],{},"Então a máquina de estados tentou de novo.",[32,352,353],{},"E de novo.",[32,355,356],{},"Encontramos cerca de 2.800 falhas de tick de migração, todas associadas ao mesmo tenant grande.",[32,358,359],{},"O pior formato possível é direto:",[32,361,362],{},"scan → falha → espera 30 segundos → scan de novo",[32,364,365],{},"Uma operação DDL que falha dentro de uma máquina de estados recorrente não é só uma mensagem de erro.",[32,367,368],{},"É um loop.",[32,370,371],{},"E loops precisam de breakers.",[27,373,375],{"id":374},"o-que-descartamos","O que descartamos",[32,377,378],{},"Várias explicações plausíveis não sobreviveram aos dados.",[32,380,381],{},[44,382,383],{},"Artefato de GraphQL ou billing",[32,385,386],{},"Métricas de Durable Object com escopo de namespace bateram com o comportamento dos logs e com a recuperação abrupta depois de remover o dataset histórico.",[32,388,389],{},[44,390,391],{},"Explosão de ingest de Proof of Play",[32,393,394],{},"O volume de ingest foi só milhares de inserts de rollup naquele dia.",[32,396,397],{},"Isso não explicava centenas de milhões ou bilhões de linhas sendo tocadas.",[32,399,400],{},[44,401,402],{},"Tráfego de Workers em toda a conta",[32,404,405],{},"O tráfego HTTP aumentou cerca de 2–3×.",[32,407,408],{},"O movimento no SQLite foi ordens de magnitude maior.",[32,410,411],{},[44,412,413],{},"Todo tenant falhando",[32,415,416],{},"Outros tenants de produção não mostraram a mesma assinatura de falha de backfill.",[32,418,419],{},"O problema se concentrou em uma tabela grande.",[32,421,422],{},[44,423,424],{},"Outage de frontend",[32,426,427],{},"Os 503s no dia um vieram de resets de migração do TenantDO — wakes que precisavam do objeto durante a reconstrução síncrona. Depois que a migração passou a async, o problema dominante virou churn de housekeeping, não tela em branco na frota.",[27,429,431],{"id":430},"impacto-de-produto","Impacto de produto",[32,433,434],{},"No tenant afetado, durante a migração síncrona, algumas operações que precisavam acordar o TenantDO — inclusive partes da experiência de Telas — podiam falhar com 503. Outros tenants não mostraram a mesma assinatura.",[32,436,437],{},"Para aquele tenant de alto volume, removemos de propósito os rollups históricos de Proof of Play envolvidos na migração. Foi uma decisão operacional específica daquele dataset: aqueles rollups não eram evidência contratual autoritativa e o ingest forward continuou normalmente.",[32,439,440],{},"Apagar dados não é uma estratégia genérica de migração. Neste incidente, continuar uma reescrita sem limite de histórico substituível era pior do que reconstruir o histórico para frente a partir de um estado limpo.",[27,442,444],{"id":443},"como-mitigamos","Como mitigamos",[235,446,448],{"id":447},"primeiro-parar-o-multiplicador","Primeiro: parar o multiplicador",[32,450,451],{},"A migração era conduzida por alarms do TenantDO.",[32,453,454],{},"Então a primeira alavanca útil não foi desligar o ingest de Proof of Play.",[32,456,457],{},"Foi estacionar o housekeeping acionado por alarm.",[32,459,460],{},"Isso parou o loop de scan repetido.",[32,462,463],{},"Essa distinção agora faz parte do nosso modelo operacional: o kill switch correto precisa corresponder ao amplificador que causa o incidente.",[235,465,467],{"id":466},"depois-corrigir-a-máquina-de-estados","Depois: corrigir a máquina de estados",[32,469,470],{},"O hotfix de produção introduziu:",[112,472,473,476,479,482,485,488],{},[115,474,475],{},"cooldowns de aproximadamente 15 minutos em torno de estados pesados de dedupe\u002Findex;",[115,477,478],{},"um índice helper não-unique enquanto a unicidade está temporariamente indisponível;",[115,480,481],{},"short-circuit quando o UNIQUE desejado já está presente;",[115,483,484],{},"sem retry imediato de UNIQUE enquanto ainda existem duplicatas;",[115,486,487],{},"falha de criação de índice voltando para dedupe em vez de repetir a mesma operação full-table;",[115,489,490],{},"progresso da migração desacoplado da cadência ativa normal de 30 segundos.",[32,492,493],{},"A regra-chave é simples:",[279,495,496],{},[32,497,498],{},"Nunca trate SQL O(n) como um heartbeat.",[235,500,502],{"id":501},"depois-eliminar-trabalho-de-migração-desnecessário","Depois: eliminar trabalho de migração desnecessário",[32,504,505],{},"Mesmo com cooldown e índice helper, reconstruir o dataset histórico in-place ainda teria sido caro.",[32,507,508],{},"A correção durável para esse tenant foi eliminar a necessidade de migrar aquelas linhas históricas descartáveis.",[32,510,511],{},"Quando os rollups históricos foram removidos e o estado da migração marcado como completo, a superfície de scan desapareceu.",[235,513,515],{"id":514},"por-fim-verificar-a-recuperação","Por fim: verificar a recuperação",[32,517,518],{},"Não demos o incidente por encerrado porque um deploy teve sucesso.",[32,520,521],{},"Procuramos os sinais reais:",[112,523,524,527,530,533],{},[115,525,526],{},"rows read por hora caíram de forma abrupta;",[115,528,529],{},"falhas de tick de migração foram a zero;",[115,531,532],{},"o comportamento do TenantDO voltou ao baseline;",[115,534,535],{},"o housekeeping normal pôde ser restaurado.",[279,537,538],{},[32,539,540],{},"A curva de recuperação importou mais do que o status do deploy.",[542,543],"recovery-sequence",{":points":544},"[\"74M\",\"2,9M\",\"836 mil\",\"424 mil\"]",[71,546],{":breakout":73,":height":74,":n":547,":width":76,"alt":548,"caption":549,"src":550,":hideOnMobile":73},"6","Gráfico de recuperação em escala logarítmica mostrando os reads do SQLite em Durable Objects caindo após a remoção do histórico de Proof of Play.","Eixo Y logarítmico, linhas lidas por hora. Pontos observados em buckets horários (3 set ~11h–14h BRT) + faixa overnight de 2 set. Não é uma interpolação contínua.","\u002Fimg\u002Fposts\u002Fdo-storage-storm\u002Fdurable-objects-recovery-cliff.png",[23,552],{"tone":553},"after",[27,555,557],{"id":556},"o-storm-radar","O storm radar",[32,559,560],{},"Já tínhamos o Cost Sense, um job de produção a cada 15 minutos que estima o gasto Cloudflare e compara com baselines recentes.",[32,562,563],{},"Antes deste incidente, ele era pensado principalmente para pegar drift mais lento de custo no nível da conta.",[32,565,566],{},"Isso não bastava.",[32,568,569],{},"Essa falha não parecia um outage clássico de aplicação.",[32,571,572],{},"Parecia um Durable Object quieto lendo repetidamente o próprio banco.",[32,574,575],{},"Um alerta genérico dizendo:",[32,577,578],{},[579,580,581],"em",{},"\"O gasto Cloudflare parece alto\"",[32,583,584],{},"teria sido útil na direção certa.",[32,586,587],{},"Mas não diria ao operador o que fazer a seguir.",[32,589,590],{},"Então adicionamos um storm radar de TenantDO.",[32,592,593],{},"O objetivo é simples:",[32,595,596],{},[44,597,598],{},"Detectar cedo um padrão de armazenamento sem limite e identificar a alavanca que pode limitá-lo.",[71,600],{":breakout":73,":height":601,":n":602,":width":603,"alt":604,"caption":605,"src":606,":hideOnMobile":73},"768","7","2048","Fluxo do radar de tempestades TenantDO, das métricas de armazenamento à detecção, classificação, alerta no Slack e mitigação.","Cost Sense transforma telemetria de armazenamento do TenantDO em classificação (read storm ou write storm) e num caminho explícito de mitigação.","\u002Fimg\u002Fposts\u002Fdo-storage-storm\u002Ftenantdo-storm-radar-detect-classify-mitigate.png",[608,609],"storm-radar-steps",{},[235,611,613],{"id":612},"o-que-ele-observa","O que ele observa",[32,615,616],{},"Cada tick de produção agora amostra o namespace Lucimark TenantDO para:",[112,618,619,622,625,628,631,634],{},[115,620,621],{},"rows read do SQLite;",[115,623,624],{},"rows written do SQLite;",[115,626,627],{},"active time do Durable Object;",[115,629,630],{},"buckets horários recentes de armazenamento;",[115,632,633],{},"volume de requests de Worker e Durable Object como sinais de apoio;",[115,635,636],{},"comparação com baselines recentes de produção.",[32,638,639],{},"A amostra anterior fica armazenada para o monitor calcular deltas em vez de depender só dos totais day-to-date.",[32,641,642],{},"Também protegemos contra amostras ausentes depois de deploy. Tratar um valor anterior ausente como zero poderia fabricar um pico falso de um bilhão de linhas.",[235,644,646],{"id":645},"três-formas-de-uma-tempestade-de-armazenamento-disparar","Três formas de uma tempestade de armazenamento disparar",[32,648,649],{},[44,650,651],{},"Pace",[32,653,654],{},"Se o consumo day-to-date já está bem à frente do que o baseline histórico sugere, abrir um finding.",[32,656,657],{},"Isso pega queimas mais lentas cedo no dia.",[32,659,660],{},[44,661,662],{},"Rate",[32,664,665],{},"Extrapolamos o delta da janela curta mais recente.",[32,667,668],{},"Se a inclinação atual produziria um dia inteiro anormal, abrir um finding mesmo que o total acumulado ainda pareça inofensivo.",[32,670,671],{},"Isso pega acelerações súbitas.",[32,673,674],{},[44,675,676],{},"Pisos horários absolutos",[32,678,679],{},"Comparações relativas não bastam para precipícios.",[32,681,682],{},"Por isso o radar tem pisos absolutos calibrados neste incidente.",[32,684,685],{},[44,686,687],{},"Read storm",[32,689,690],{},"≥ 10 milhões de rows read faturáveis\u002Fhora, sustentados em dois buckets horários consecutivos.",[32,692,693],{},"A atividade saudável de TenantDO fica normalmente na casa das centenas de milhares até cerca de um milhão de reads por hora.",[32,695,696],{},"O incidente produziu 50–70M\u002Fhora mesmo na fase mais quieta da manhã e até 0,3–1,4B\u002Fhora durante a noite.",[32,698,699],{},"O ponto do 10M é alertar bem abaixo do nível que vivemos.",[32,701,702],{},[44,703,704],{},"Write storm",[32,706,707],{},"≥ 2 milhões de rows written faturáveis\u002Fhora, também sustentados.",[32,709,710],{},"Writes são o medidor caro.",[32,712,713],{},"Um dia saudável da Lucimark produz só uma fração desse número no total. Milhões de writes concentrados em horas individuais merecem atenção imediata mesmo antes de um baseline relativo acompanhar.",[235,715,717],{"id":716},"a-detecção-deve-nomear-a-alavanca","A detecção deve nomear a alavanca",[32,719,720],{},"Quando o radar dispara, o alerta é feito para alguém que não passou as doze horas anteriores lendo código de migração.",[32,722,723],{},"Ele contém:",[112,725,726,729,732,735,738],{},[115,727,728],{},"métricas atuais de armazenamento do Durable Object;",[115,730,731],{},"o finding anormal;",[115,733,734],{},"informação direcional de custo;",[115,736,737],{},"baseline recente;",[115,739,740],{},"o controle de primeira resposta adequado.",[32,742,743],{},"Um read storm aponta primeiro para o kill switch de alarm\u002Fhousekeeping.",[32,745,746],{},"Um write storm aponta primeiro para os controles de ingest\u002Famplificação de write.",[32,748,749],{},"Essa é a diferença entre observabilidade e mitigação.",[32,751,752,755,756,759],{},[579,753,754],{},"\"O gasto está alto\""," é uma observação.\n",[579,757,758],{},"\"Estacione os alarms\""," é um runbook.",[32,761,762],{},"Num loop que não para sozinho, o segundo é o que limita o incidente.",[235,764,766],{"id":765},"backstops","Backstops",[32,768,769],{},"Um monitor de produção a cada 15 minutos é útil, mas nenhum caminho de monitoramento deve ser a única linha de defesa.",[32,771,772],{},"Adicionamos dois mecanismos extras.",[32,774,775],{},[44,776,777],{},"Checagem diária noturna",[32,779,780],{},"O snapshot operacional diário verifica de forma independente taxas anormais de read em Durable Object e pode emitir uma notificação separada.",[32,782,783],{},"O objetivo é pegar a classe de problema mais propensa a ganhar vantagem enquanto ninguém está olhando.",[32,785,786],{},[44,787,788],{},"Inventário de estado de backfill",[32,790,791],{},"Métricas de namespace dizem que a frota TenantDO está anormal.",[32,793,794],{},"Elas não identificam necessariamente qual tenant é o responsável.",[32,796,797],{},"Por isso o snapshot também inspeciona o estado da migração de Proof of Play nos tenants ativos e reporta migrações incompletas ou desconhecidas.",[32,799,800],{},"Os estados aparecem do pior para o melhor:",[32,802,803],{},"indexing → deduping → updating → pending → done",[32,805,806],{},"Timeouts são classificados como unknown, não como healthy.",[32,808,809],{},"Esse é o inventário que não tínhamos em 2 de setembro.",[32,811,812],{},"Na época, milhares de linhas de erro idênticas tiveram de ser rastreadas manualmente até um único tenant.",[235,814,816],{"id":815},"depois-de-cada-deploy","Depois de cada deploy",[32,818,819],{},"Deploys de produção de API, app e player agora armam uma janela temporária de Cost Sense com sensibilidade maior.",[32,821,822],{},"Durante essa janela:",[112,824,825,828,831,834],{},[115,826,827],{},"os limiares de pace apertam;",[115,829,830],{},"os limiares de rate apertam;",[115,832,833],{},"comportamento anormal de Durable Object fica mais fácil de disparar;",[115,835,836],{},"qualquer alerta inclui contexto do deploy.",[32,838,839],{},"Essa migração entrou em produção como um deploy rotineiro de API.",[32,841,842],{},"Uma regressão futura de armazenamento não deveria ganhar uma noite de vantagem só porque as taxas de erro HTTP parecem aceitáveis.",[27,844,846],{"id":845},"o-que-mudou-nas-nossas-regras-de-engenharia","O que mudou nas nossas regras de engenharia",[32,848,849],{},"O que já vale na plataforma, em resumo:",[112,851,852,855,863,866,869],{},[115,853,854],{},"boot do TenantDO só com schema barato — sem DML em tabela grande nem rebuild caro de unicidade;",[115,856,857,858,862],{},"backfill pesado assíncrono, em lote, resumível, marcado ",[859,860,861],"code",{},"done"," e seguro contra reexecução depois de reset;",[115,864,865],{},"cooldown em estados O(n);",[115,867,868],{},"soak da cauda: testar o maior dataset real, não a mediana;",[115,870,871],{},"storm radar no Cost Sense, kill switch de alarm, janela pós-deploy e inventário de backfill.",[873,874],"shipped-vs-open",{":shipped":875,":stillOpen":876},"[\"Política de boot barato no TenantDO\",\"Cooldown e índice helper na máquina de estados\",\"Cadência de migração desacoplada dos 30 s\",\"Storm radar (pace, rate, pisos horários)\",\"Kill switch de alarm \u002F housekeeping\",\"Janela Cost Sense pós-deploy\",\"Checagem noturna e inventário de backfill\"]","[\"Circuit breaker após falhas repetidas de UNIQUE\",\"Dedupe incremental em vez de GROUP BY full-table\",\"Soak em staging com ≥500k linhas no release gate de PoP\",\"Pausar a migração de um tenant sem estacionar o housekeeping inteiro\",\"Rebuild-from-scratch quando o grain torna irracional migrar histórico\",\"Atribuição de custo SQLite por tenant\",\"Tooling mais seguro para saídas destrutivas\"]",[32,878,879,880,883],{},"Um circuit breaker, aqui, é um limite que ",[44,881,882],{},"para"," o retry depois de N falhas — pausa, alerta e exige mudança de estado — em vez de deixar o tick repetir para sempre. Ainda não está no caminho de UNIQUE; está na coluna da direita.",[32,885,886],{},"O radar não torna uma migração ruim barata. Ele reduz quanto tempo ela pode permanecer sem limite.",[27,888,890],{"id":889},"o-que-diríamos-a-outro-time-rodando-sqlite-em-durable-objects","O que diríamos a outro time rodando SQLite em Durable Objects",[32,892,893],{},"A maior parte já está nas regras acima. O que não cabe num quadro:",[32,895,896,899],{},[44,897,898],{},"Separe observabilidade pela superfície de falha."," Gráficos de HTTP não dizem que o SQLite está se varrendo. Observe rows read \u002F rows written diretamente.",[32,901,902,905,906,909],{},[44,903,904],{},"DDL que falha é transição de máquina de estados."," Se ",[859,907,908],{},"CREATE UNIQUE INDEX"," pode falhar dentro de um worker recorrente, desenhe essa transição antes de publicar.",[32,911,912,915],{},[44,913,914],{},"O alerta útil nomeia a alavanca."," Não “o gasto está alto”. Sim: “esse padrão casa com um loop de scan acionado por alarm — estacione os alarms.”",[32,917,918],{},"Esperança não é circuit breaker.",[27,920,922],{"id":921},"fechamento","Fechamento",[32,924,925],{},"Enviamos uma mudança de modelo de dados de Proof of Play cuja estratégia de migração subestimou um TenantDO grande.",[32,927,928],{},"O primeiro modo de falha foram writes caros.",[32,930,931],{},"O segundo foi mais revelador: um alarm de 30 segundos tinha transformado trabalho O(n) de migração num loop sem condição natural de parada.",[32,933,934],{},"Paramos o multiplicador, corrigimos a máquina de estados, removemos dados históricos que não justificavam uma reescrita sem limite e restauramos o sistema à faixa normal.",[32,936,937],{},"Depois mudamos a plataforma para que uma falha semelhante fique visível bem mais cedo.",[32,939,940],{},"A lição mais importante não foi o número de linhas.",[32,942,943],{},"Foi o formato da falha.",[32,945,946],{},"Uma operação recorrente cujo custo escala com o tamanho da tabela precisa de um limite antes de chegar à produção.",[32,948,949],{},"Para a Lucimark, isso agora significa mecânica de migração mais rigorosa, cooldowns, kill switches, testes realistas de alto volume e um radar que faz mais do que reportar que o custo está subindo.",[32,951,952],{},"Ele nos diz onde está o amplificador — e qual alavanca o interrompe.",[32,954,955],{},"O trabalho da Lucimark é deixar operadores rodarem redes de telas com confiança.",[32,957,958],{},"Isso inclui as partes que ninguém vê na parede: migrações, rollups, comportamento de armazenamento e o custo de estar errado sobre um loop.",{"title":960,"searchDepth":961,"depth":961,"links":962},"",2,[963,964,965,966,967,974,975,976,982,989,990,991],{"id":29,"depth":961,"text":30},{"id":91,"depth":961,"text":92},{"id":150,"depth":961,"text":151},{"id":203,"depth":961,"text":204},{"id":232,"depth":961,"text":233,"children":968},[969,971,972,973],{"id":237,"depth":970,"text":238},3,{"id":267,"depth":970,"text":268},{"id":295,"depth":970,"text":296},{"id":337,"depth":970,"text":338},{"id":374,"depth":961,"text":375},{"id":430,"depth":961,"text":431},{"id":443,"depth":961,"text":444,"children":977},[978,979,980,981],{"id":447,"depth":970,"text":448},{"id":466,"depth":970,"text":467},{"id":501,"depth":970,"text":502},{"id":514,"depth":970,"text":515},{"id":556,"depth":961,"text":557,"children":983},[984,985,986,987,988],{"id":612,"depth":970,"text":613},{"id":645,"depth":970,"text":646},{"id":716,"depth":970,"text":717},{"id":765,"depth":970,"text":766},{"id":815,"depth":970,"text":816},{"id":845,"depth":961,"text":846},{"id":889,"depth":961,"text":890},{"id":921,"depth":961,"text":922},{"src":993,"alt":994},"\u002Fimg\u002Fposts\u002Fmudanca-schema-alarm-30s-noite-bilhao-linhas\u002Fmudanca-schema-alarm-30s-noite-bilhao-linhas-cover.png","Ilustração de um nó de schema, pulsos de alarm e uma tempestade de dados em âmbar sobre fundo escuro.","Como uma migração de Proof of Play em Cloudflare Durable Objects virou um loop de armazenamento sem limite — e o radar que construímos para que o próximo não se esconda.",null,false,"md",[1000,1004,1007],{"value":1001,"label":1002,"hint":1003},"~625 milhões","linhas escritas faturáveis","Writes SQLite, não dinheiro nem registros únicos",{"value":1005,"label":1006},"~30 s","repetição do alarm",{"value":1008,"label":1009},"Telas tocando","reprodução nas telas preservada",{},true,"\u002Fpt\u002Fposts\u002Fmudanca-schema-alarm-30s-noite-bilhao-linhas","2026-09-04",{"title":16,"description":995},"mudanca-schema-alarm-30s-noite-bilhao-linhas","pt\u002Fposts\u002Fmudanca-schema-alarm-30s-noite-bilhao-linhas",[1018,1019,1020,1021],"cloudflare","durable-objects","proof-of-play","cost-observability","schema-change-do-storage-storm","OMjCv8F1EgaW4xNLYw5BLQM43JDGk2Kwo4I4yKj4GTc",{"paulo-ramires":1025},{"handle":18,"name":1026,"role":1027,"avatar":1028,"description":1029},"Paulo R.","Fundador e CTO","\u002Fimg\u002Fauthors\u002Fpaulo-ramires.jpg","Fundador da Lucimark. Constrói a plataforma de ponta a ponta — do player Android na tela até os Workers que servem a API.","schema-change-30-second-alarm-billion-row-night",[1032,1044,1060],{"path":1033,"title":1034,"description":1035,"slug":1036,"translationKey":1036,"category":11,"tags":1037,"authors":1039,"publishedAt":1040,"updatedAt":996,"draft":997,"featured":997,"cover":1041,"ogImage":1042,"discussionUrl":996,"translationStatus":996,"readingMinutes":970},"\u002Fpt\u002Fposts\u002Fplataforma-dooh-cloudflare-native","Por que a Lucimark roda inteira na Cloudflare","Workers, D1, R2 e um Durable Object por cliente. O que ganhamos, o que doeu e por que não existe um Docker no nosso stack.","plataforma-dooh-cloudflare-native",[1018,1038,1019],"arquitetura",[18],"2026-08-12",{"src":1042,"alt":1043},"\u002Fimg\u002Fposts\u002Fplataforma-dooh-cloudflare-native\u002Fplataforma-dooh-cloudflare-native-cover.png","Ilustração de uma rede de telas distribuída ao redor de camadas abstratas de compute, banco, mídia e estado.",{"path":1045,"title":1046,"description":1047,"slug":1048,"translationKey":1048,"category":9,"tags":1049,"authors":1053,"publishedAt":1054,"updatedAt":996,"draft":997,"featured":997,"cover":1055,"ogImage":1056,"discussionUrl":996,"translationStatus":996,"readingMinutes":1059},"\u002Fpt\u002Fposts\u002Fcomo-colocar-primeira-tela-lucimark","Como colocar sua primeira tela no ar com a Lucimark","Um guia direto para transformar uma TV em tela digital, publicar a primeira imagem e testar a instalação.","como-colocar-primeira-tela-lucimark",[1050,1051,1052],"primeira-tela","instalacao","digital-signage",[18],"2026-09-05",{"src":1056,"alt":1057,"credit":1058},"\u002Fimg\u002Fposts\u002Fcomo-colocar-primeira-tela-lucimark\u002Fhero.jpg","Ilustração de uma tela vertical em uma confeitaria, instalada em uma moldura de ACM adesivada.","Ilustração inspirada em foto fornecida pela Nena Bolos.",7,{"path":1061,"title":1062,"description":1063,"slug":1064,"translationKey":1064,"category":10,"tags":1065,"authors":1070,"publishedAt":1054,"updatedAt":996,"draft":997,"featured":997,"cover":1071,"ogImage":1075,"discussionUrl":996,"translationStatus":996,"readingMinutes":1076},"\u002Fpt\u002Fposts\u002Fcomo-vender-anuncios-em-telas-dooh","Como vender anúncios em telas: um plano para conquistar os primeiros 10 anunciantes","Aprenda a estruturar sua oferta de mídia DOOH, encontrar anunciantes locais, apresentar uma proposta e acompanhar campanhas para buscar renovações.","como-vender-anuncios-em-telas-dooh",[10,1066,1067,1068,1069],"rede-dooh","vendas","anunciantes","prospeccao",[18],{"src":1072,"alt":1073,"credit":1074},"\u002Fimg\u002Fposts\u002Fcomo-vender-anuncios-em-telas-dooh\u002Fconversa-com-anunciante-local.webp","Duas pessoas conversam em uma loja de alimentação saudável enquanto observam imagens de telas em academias em um tablet.","Uma proposta começa pela conexão entre o negócio do anunciante e os pontos da rede. Cena ilustrativa gerada por IA.","\u002Fimg\u002Fposts\u002Fcomo-vender-anuncios-em-telas-dooh\u002Fconversa-com-anunciante-local-og.jpg",11,1788649408155]