AirHelp, Tecnologia para o maior defensor dos direitos dos passageiros
Todo o desenho técnico deste projeto assenta sobre um conflito único: a mesma página tem de ser entregue a partir da borda da rede, sem tocar no servidor de origem, e ao mesmo tempo mostrar a cada pessoa o estado do seu próprio processo. Cache e personalização puxam em direções opostas por definição, porque o ganho da cache vem exatamente de entregar o mesmo byte a muita gente. Quase tudo o que se segue é uma forma de arbitrar esse conflito.
AirHelp foi fundada em janeiro de 2013 por Henrik Zillmer e tem sede em Berlim. A base legal na Europa é o Regulamento 261/2004 e, fora da União, a empresa trabalha através do UK261, da Convenção de Montreal, da ANAC 400 brasileira e de outros regimes nacionais. Ajudou mais de 13 milhões de pessoas a compreender os seus direitos e a reclamar indemnização por voos atrasados, cancelados ou com sobrelotação, representa-as em litígios com companhias aéreas e faz trabalho de influência junto dos governos.
Concebi e implementei o sítio da AirHelp, liderando uma equipa de cinco pessoas como Team Pilot. A implementação levou cerca de seis semanas, do levantamento de âmbito à publicação, e a disposição dos elementos veio do cliente.
Como a cache e a identidade do visitante foram separadas
A resolução passou por dividir a página em camadas com tempos de vida distintos. O esqueleto do sítio e todo o conteúdo editorial são comuns e ficam em cache de forma agressiva. Tudo o que depende de quem está a olhar é obtido por um pedido próprio, depois da autenticação, e nunca entra na cache partilhada.
Os Edge Side Includes existem precisamente para isto. Uma página de artigo jurídico é idêntica para toda a gente na maior parte da sua área e depende, num fragmento pequeno, de a pessoa ter ou não um processo aberto. Sem essa divisão, o fragmento pequeno invalida a cache da página inteira, e o resultado é um sítio que faz trabalho de base de dados para servir texto que não muda há meses.
Há uma terceira camada que raramente aparece nestas descrições e que é a que mais pesa em dias maus: o modo grace do Varnish. Entrega uma versão ligeiramente desatualizada no instante em que o backend deixa de responder, em vez de entregar um erro. Perante tráfego que chega em ondas, essa é a diferença entre um sítio mais lento e um sítio indisponível, e é uma decisão que se toma antes do incidente ou não se toma de todo.
Objetivo da AirHelp e o seu público
Dois públicos usam este sítio e quase não têm nada em comum.
O primeiro é uma pessoa numa situação atípica: sentada num terminal depois de um cancelamento, ao telemóvel, muitas vezes em roaming, com rede fraca, irritada, com apenas uma fotografia do cartão de embarque. Não veio ler sobre a empresa. Veio saber se tem direito a alguma coisa, e quer a resposta em dois passos.
O segundo chega de um motor de busca meses depois do voo, à procura de um ponto jurídico concreto. Esse tráfego é estável, muito cacheável, lê textos longos e aterra em páginas em 24 idiomas.
Uma plataforma serve os dois padrões sob o mesmo domínio. O que é certo para o primeiro público, marcação mínima, renderização no servidor, nada a bloquear o formulário, é desperdício para o segundo. O que é certo para o segundo é inutilizável para o primeiro, porque o estado de um processo é pessoal por definição.
Por trás do conteúdo está a colaboração com escritórios de advogados em 30 países e uma equipa de 700 colaboradores, incluindo o maior grupo mundial de advogados especializados em direito aéreo. Para o sítio isso significa que o texto jurídico tem um dono real do lado do cliente e passa por aprovação antes de chegar a produção. O modelo de conteúdo teve de suportar esse fluxo em vez de o contornar.
Vinte e quatro idiomas são um problema jurídico
Uma frase sobre quanto é devido a um passageiro não é uma frase traduzida vinte e quatro vezes. São duas dúzias de afirmações jurídicas autónomas, e muitas só são verdadeiras dentro de uma fronteira. Um voo de Varsóvia para Londres cai sob um regime diferente depois do Brexit do que o mesmo voo dentro da União, e um voo a partir de São Paulo segue regras sem relação com nenhum dos dois.
Daí decorre que cada variante linguística tem de ser um documento de pleno direito, com o seu próprio ciclo de aprovação, e não um campo de tradução pendurado no original. Uma variante que não foi revista por um advogado daquela jurisdição não pode cair em silêncio para inglês, porque nessa altura o sítio publica um enquadramento estrangeiro como se fosse o seu. Tem de desaparecer da navegação e do hreflang. É o único comportamento por omissão seguro, e é exatamente aquele que as extensões multilingue genéricas erram, porque a omissão delas é sempre mostrar alguma coisa em vez de nada.
A arquitetura de frontend assenta em Next.js com renderização no servidor e cobre 24 idiomas por i18n, com WCAG 2.1 presente nas revisões e otimização para telemóveis. A renderização no servidor não é aqui uma preferência estética: com conteúdo jurídico que tem de posicionar em dezenas de mercados ao mesmo tempo, e com telemóveis em redes fracas, empurrar a montagem da página para o navegador significa que o telefone mais fraco na pior rede faz o maior esforço.
Funcionalidades técnicas da AirHelp
O processo de indemnização é um formulário de vários passos que carrega dados de voo por GraphQL, liga-se às API das companhias e grava o pedido em PostgreSQL com cifra AES-256. Escolher GraphQL em vez de um conjunto de chamadas REST tem uma razão concreta: cada passo precisa de uma fatia diferente do mesmo registo de voo. Em REST isso acaba ou em trazer tudo à cabeça, ou num número de pedidos que cresce com o número de passos. Pedir exatamente os campos que o passo atual desenha reduz aquilo a uma ida e volta de rede por passo, e num telemóvel em roaming essa diferença sente-se.
A secção informativa com artigos jurídicos carrega por REST API com cache em Redis e é renderizada em React. A separação entre as duas interfaces é deliberada: conteúdo editorial é igual para todos e admite cache agressiva, dados de processo não admitem cache nenhuma. Manter ambos atrás de uma interface teria poupado algum código e custado a possibilidade de definir tempos de vida distintos.
O painel de acompanhamento mostra o estado em tempo real por WebSocket, com cache em Memcached. Vale a pena nomear o compromisso. Uma ligação permanente consome recursos enquanto o separador estiver aberto, e o estado de um processo muda talvez uma vez em cada poucas semanas. Consultar de minuto a minuto seria mais barato de operar e suficiente para a informação em si. O argumento a favor do canal aberto é o comportamento no dia em que o estado muda de facto, porque nesse dia a pessoa está nesta página a recarregá-la à mão, e cada recarga manual custa mais do que manter o canal.
As cópias de segurança seguem automaticamente para a Amazon S3, com replicação entre regiões, versionamento e compressão Zstandard. Aqui o versionamento pesa mais do que a replicação: a replicação protege contra perder um centro de dados, coisa rara, e o versionamento protege contra escrever por cima dos dados por erro próprio numa publicação, coisa nada rara. Uma cópia replicada para três regiões é, nesse caso, o mesmo erro em triplicado.
O SEO técnico cobre expressões como «indemnização por voo atrasado», mapas XML dinâmicos e indexação acelerada. Com conteúdo jurídico que muda depois de cada decisão relevante, um mapa gerado uma única vez na compilação deixa de descrever o sítio ao fim de uma semana, por isso é construído a partir do estado atual da base de dados.
Acessibilidade e resistência do formulário
WCAG 2.1 num sítio de reclamações não é sobretudo uma questão de contraste e texto alternativo. O componente acessível crítico é o formulário de vários passos, e o que decide se funciona é justamente o que uma auditoria automática não apanha: se a mensagem de erro está ligada por código ao campo que descreve, se avançar de passo move o foco para o título do passo novo, se o progresso chega a ser anunciado. Um formulário que salta para o topo depois de um erro de validação sem levar o foco é um ciclo sem saída para quem navega por teclado, e passa em todos os testes automáticos.
Sobreviver a uma quebra de ligação foi tratado como requisito funcional e não como conforto. O estado do formulário é guardado localmente a cada passo, de modo que perder a sessão numa sala de embarque não apaga quinze minutos de trabalho. O custo é real e deve ser dito: os dados incluem número de voo e dados pessoais, e guardá-los no navegador exige uma rotina própria de limpeza depois da submissão e uma regra clara sobre quando expira um rascunho abandonado.
Desafios de desempenho e soluções
A carga de base de dados caía sobre o PostgreSQL. A resposta foi Redis com persistência para as consultas mais frequentes e sharding com réplicas de leitura em Amazon RDS. As réplicas de leitura têm um preço fácil de esquecer: a replicação é assíncrona, por isso uma leitura logo a seguir a uma submissão pode ainda não ver o pedido. A pessoa submete e aterra numa lista vazia, o que se parece exatamente com perda de dados. Por isso as leituras imediatamente a seguir a uma escrita vão à instância primária, e só se distribuem os caminhos que toleram algumas centenas de milissegundos de atraso.
O carregamento lento do formulário vinha da integração com as API das companhias, sobretudo depois de cancelamentos em massa. As chamadas passaram a processamento assíncrono por RabbitMQ, com recurso a dados estáticos em cache no Elasticsearch quando o tempo de espera expira. O recurso alternativo é a metade importante: os sistemas das companhias tendem a estar indisponíveis exatamente quando são mais precisos, porque a mesma perturbação que cancelou os voos está a castigar a infraestrutura deles. Um formulário que mostra um erro nesse momento perde um pedido de alguém com pleno direito a indemnização.
A latência alta de imagens penalizava telemóveis em regiões com má ligação. Fastly com compressão Brotli, WebP e carregamento diferido pela Intersection Observer API encurtou a entrega, e a otimização geográfica encurtou a distância. Ainda assim, o ganho do formato de imagem é secundário face ao ganho de as imagens abaixo da dobra deixarem de competir por largura de banda com o texto que está a ser lido.
Os atrasos no painel em tempo real apareceram à escala de 13 milhões de utilizadores. O Kafka assumiu o streaming, com limitação de ritmo no servidor e um AWS ALB a distribuir o tráfego. A limitação de ritmo é o elemento menos vistoso do conjunto e o mais necessário, porque sem ela um único cliente preso num ciclo de erro ocupa um canal que pertence a todos os outros.
A procura de recursos nas horas de ponta é coberta por auto-scaling em AWS EC2 com CloudWatch, mais Cloudflare Rate Limiting contra tráfego excessivo de bots. O auto-scaling arrasta uma fraqueza que decorre diretamente da forma do tráfego deste negócio: reage a carga que já aconteceu, e uma onda a seguir a um cancelamento em massa forma-se em minutos. Os limiares estão por isso mais baixos do que um cálculo de custos num dia calmo sugeriria, e essa folga é comprada de propósito.
Tecnologias usadas
Yoast SEO trata dos metadados, dos mapas XML dinâmicos e das notificações de atualização aos motores de busca. UpdraftPlus executa as cópias para a Amazon S3 com replicação e cifra AES-256. Cloudflare fornece a camada de borda com Argo Smart Routing, compressão Brotli e proteção DDoS por limitação de ritmo. Redis guarda em memória, separado entre sessões, formulários e painel. Varnish guarda do lado do servidor com VCL próprio, modo grace e ESI para blocos dinâmicos.
Lighthouse corre auditorias de Core Web Vitals dentro da cadeia CI/CD em Jenkins. RabbitMQ põe em fila o processamento de API e o envio de correio, com repetições e fila de mensagens mortas. Elasticsearch sustenta a pesquisa de voos e conteúdos com correspondência difusa e agregação, e a tolerância ao erro é aqui uma exigência e não um requinte: os números de voo são copiados de fotografias de cartões de embarque e os nomes de aeroporto escrevem-se de vinte maneiras em vinte e quatro idiomas. Fastly distribui os media geograficamente e o Kafka transporta o fluxo em tempo real com particionamento.
Gestão e suporte técnico
A AirHelp exige otimização e suporte contínuos. Atualizo o núcleo e as extensões com regularidade, testando num ambiente de testes com cópias na Amazon S3. Esse ambiente é uma cópia de produção e não uma instalação limpa, e é essa distinção que faz o teste valer alguma coisa. Uma consulta que responde de imediato contra duzentos processos pode crescer duas ordens de grandeza contra milhões de registos, e numa base vazia ninguém dá por isso. O mesmo vale para a taxa de acerto da cache, mensurável apenas contra uma distribuição real de páginas de entrada.
Cloudflare, Redis e Fastly sustentam o desempenho sob tráfego global, enquanto Varnish, RabbitMQ e Kafka estabilizam os processos dinâmicos. A monitorização corre sobre Elasticsearch e CloudWatch, as consultas SQL e NoSQL são revistas contra os seus índices e a cache é invalidada nas alterações de conteúdo. O sinal que mais importa não é o tempo de resposta mas a taxa de acerto da cache, porque num sítio com esta forma uma falha leva o pedido à origem por muito bem afinada que a origem esteja.
A plataforma pode ser alargada com integrações ERP, um módulo de análise de voos ou uma secção de relatórios jurídicos. Cada uma dessas extensões esbarra no mesmo conflito que atravessa o projeto inteiro: quanto mais conteúdo depende de quem está a olhar, menos pode ser servido a partir da borda, e a partir daí as decisões tomadas aqui têm de ser recalculadas em vez de transportadas.
Perguntas frequentes
Respostas práticas para aplicar o tema na execução real.
Que âmbito teve o projeto Airhelp?
#Como correu a entrega de Airhelp?
#O que foi mais difícil tecnicamente em Airhelp?
#Que parte de Airhelp pode ser reaproveitada noutro projeto?
#Precisa de FAQ adaptado ao setor e mercado? Criamos uma versão alinhada com os seus objetivos de negócio.
Fale connosco