Como os engenheiros do Instagram Direct criaram uma arquitetura de interface nativa de IA com o Jetpack Compose e reduziram o custo de tokens por sessão de agente em 33%
Leitura de 11 min
Esta postagem do blog foi escrita em colaboração com a equipe da Meta
O Instagram Direct é uma das principais plataformas do Instagram, processando bilhões de mensagens de usuários todos os dias. Ao longo de anos de iteração, a equipe aproveitou todas as micro-otimizações possíveis do sistema de visualização legado do Android. No entanto, manter e expandir uma plataforma legada altamente otimizada cria uma dívida técnica significativa e overhead de engenharia, especialmente à medida que as equipes adotam cada vez mais a interface declarativa e os assistentes de assistência de programação de IA.
A adoção do Jetpack Compose para o Instagram Direct foi além de uma modernização típica de interface. A equipe criou uma base de código de interface nativa de IA que é 50% menor do que a implementação original, além de alcançar uma redução de 35% no tempo de execução do agente de IA, 32% menos trocas entre engenheiros e agentes e uma redução de 33% no custo de tokens. Em parceria com o Google, a equipe adotou o Jetpack Compose e manteve um alto nível de performance. Com as otimizações de performance, a Meta e o Google melhoraram o Compose não apenas para o Instagram, mas também para o ecossistema mais amplo de desenvolvedores Android.
Modernização da base de código em grande escala
A IA se tornou rapidamente uma companheira diária para os engenheiros do setor, e aplicá-la a uma base de código em grande escala como o Instagram já gera ganhos reais de produtividade. A equipe do Instagram Direct definiu uma meta mais ambiciosa. Em vez de simplesmente apontar as ferramentas de IA para o código atual, a equipe reformulou a base de código e a arquitetura para que fossem nativas de IA, multiplicando o impacto da IA muito além do que a modernização por si só pode oferecer.
A equipe do Instagram Direct escolheu o Jetpack Compose como um componente essencial para criar uma arquitetura de interface nativa de IA. A natureza declarativa garante que o código seja conciso, previsível e estruturalmente mais fácil para os modelos de IA raciocinarem, com menos efeitos colaterais, menos estado implícito e limites de componentes mais claros.
A migração para o Jetpack Compose exigiu um planejamento cuidadoso. Centenas de milhões de pessoas enviam mensagens no Instagram todos os dias. Por isso, a migração precisava ser gradual, tranquila e sem interrupções na experiência, enquanto a equipe reestruturava a base. Para ilustrar a escala do desafio: os componentes individuais da interface podem ser renderizados em mais de 160 permutações de estado distintas, e uma única tela de conversa lida com mais de 200 tipos de mensagens diferentes.
Ao migrar uma base de código desse tamanho para o Compose, é tentador escolher o caminho mais fácil e incorporar componentes de interface do Compose à hierarquia de visualização atual. Como uma etapa incremental durante uma migração gradual, isso é perfeitamente válido. No entanto, a longo prazo, integrar o Compose a uma base de código baseada em visualização é um desafio. As ferramentas de IA geralmente seguem o caminho mais fácil. Se você misturar código de IU declarativo e imperativo, é provável que a IA os combine de forma incorreta, introduzindo bugs sutis, dívida técnica e regressões de desempenho.
Como criar uma arquitetura de interface nativa de IA
Na escala do Instagram, um grau de abstração arquitetônica é inevitável, e é isso que mantém o app sustentável à medida que ele cresce. Considere um padrão comum, em que cada tipo de item RecyclerView é modelado como um descendente de uma classe de base RecyclerViewItem personalizada que expõe hooks de ciclo de vida comuns, como onBind.
Exemplo 1
class ChatItem( val features: FeatureFlagProvider ) : RecyclerViewItem<ComposeViewHolder, ChatUiState> { // Imperative context: // AI could often take the path of least resistance and generate a mutable // state here, dispatched outside the ChatUiState. This class survives // re-bindings and is shared across multiple items, ultimately leading to // unexpected, hard-to-reproduce bugs. var isPinned: Boolean = false override fun onBind(holder: ComposeViewHolder, uiState: ChatUiState) { // Imperative context val isPinnedChatsEnabled = features.isEnabled("pinned_chats_feature") // Declarative context holder.composeView.setContent { // Blending imperative and declarative contexts if (isPinnedChatsEnabled) { Button(onClick = { isPinned = !isPinned }) { Text(if (isPinned) "Unpin" else "Pin") } } ... } } }
No snippet acima, dois problemas aparecem. Primeiro, a flag isPinnedChatsEnabled é lida no código imperativo e, em seguida, capturada dentro de uma lambda do Compose, um acoplamento sutil entre paradigmas. Em segundo lugar, isPinned é um campo mutável no próprio item, e não em ChatUiState. Portanto, ele sobrevive à nova vinculação e reciclagem de RecyclerView em várias linhas, vazando e produzindo bugs difíceis de reproduzir.
Mesmo quando o código é limpo ao dar ao item uma função @Composable dedicada, os mesmos problemas permanecem.
Exemplo 2
class ChatItem( val features: FeatureFlagProvider ) : ComposeRecyclerViewItem<ChatUiState> { // Imperative context val isPinnedChatsEnabled = features.isEnabled("pinned_chats_feature") var isPinned: Boolean = false // Declarative context @Composable override fun Content(uiState: ChatUiState) { // Blending imperative and declarative contexts if (isPinnedChatsEnabled) { Button(onClick = { isPinned = !isPinned }) { Text(if (isPinned) "Unpin" else "Pin") } } ... } }
Este é um exemplo simples, mas ilustra um problema mais amplo: quanto menos limites a IA tiver, menor será a qualidade do código que ela produzirá ao longo do tempo. As proteções e as habilidades ajudam, mas não são suficientes por si só, porque quando a IA encontra atrito, ela geralmente as contorna para se desbloquear.
Para que a base de código seja compatível com a IA, ela precisa seguir duas regras práticas:
- Minimize a dependência do contexto personalizado. Quanto mais conhecimento específico da base de código um agente de IA precisar para fazer uma mudança correta, menor será a qualidade da saída. Quanto mais a base de código estiver alinhada às práticas recomendadas, melhores serão os resultados da IA.
- Uma base de código focada em IA precisa aplicar os próprios limites. Corrigir lacunas de design com habilidades de IA não é escalonável, já que cada habilidade carregada no contexto custa tokens e pode prejudicar o desempenho do agente. Em vez disso, a própria arquitetura precisa carregar esse peso. Os agentes de IA naturalmente seguem o caminho de menor resistência. Por isso, o design precisa fazer com que esse caminho leve a um código correto e de alta qualidade, enquanto dificulta e encarece a expressão de decisões de design ruins.
Um item de lista ainda pode ser representado pela própria abstração, mas, nesse caso, todo o código do Compose fica no construtor. Portanto, ele não tem acesso aos membros ou ao estado da classe, e a única fonte de argumentos é o construtor. Isso a torna equivalente a uma função @Composable simples, mas em conformidade com a arquitetura atual.
Exemplo 3
class ChatItem( val features: FeatureFlagProvider, val onPin: (Boolean) -> Unit, ) : ComposeItem<ChatUiState>( // Compose UI content = { uiState: ChatUiState -> val isPinnedChatsEnabled = features.isEnabled("pinned_chats_feature") if (isPinnedChatsEnabled) { Button(onClick = { onPin(!uiState.isPinned) }) { Text(if (uiState.isPinned) "Unpin" else "Pin") } } ... }, )
Migrar uma base de código desse tamanho é uma tarefa enorme. Por muito tempo, as centenas de componentes de IU que compõem a maior parte da Direct UI precisaram coexistir com as versões legadas, sendo mantidas em paralelo. Os fluxos de trabalho de IA ajudaram a tornar essa migração paralela possível, acelerando o processo de escrita de grandes quantidades de código. Essa abordagem permitiu que a equipe do Direct fizesse a migração em tempo recorde, sem atrapalhar o restante da equipe, que continuou lançando os recursos que melhoram a experiência de milhões de pessoas todos os dias.
Vários engenheiros executaram os próprios agentes de IA em uma base de conhecimento compartilhada de habilidades e convenções reutilizáveis criadas durante a migração. Isso manteve os fluxos de trabalho e as práticas recomendadas em sincronia em toda a equipe, em vez de cada engenheiro ter que redescobri-los. Em cada plataforma, a equipe realizou a migração nas seguintes etapas:
- Escreva todo o código do Compose com IA.
- Ajustamos os casos extremos e as lacunas de desempenho até que a interface fosse lançada para usuários reais em um teste público.
Dividir o trabalho em duas etapas por tela permite que um engenheiro se mova rapidamente por toda a superfície, definindo a arquitetura e os casos extremos complicados com antecedência. Com essa base, outras pessoas podem se concentrar em preparar a interface para produção sem precisar parar para tomar essas decisões técnicas, o que acelera a migração geral.
Os resultados da migração validaram a abordagem. Para as plataformas migradas do Instagram Direct, o Jetpack Compose permitiu que a equipe reduzisse o total de código da interface em 50%. Menos código para a IA gerar está associado a uma saída de maior qualidade e menor custo de token por tarefa.
Uma análise de dados interna da base de código do Android para o Instagram Direct comparou as sessões de agentes de IA trabalhando na interface do Compose com as mesmas tarefas usando as visualizações do Android. Os ganhos de eficiência foram claros em duas dimensões:
- Por caractere de código finalizado:o Compose exigiu 32% menos interações entre engenheiro e agente e 35% menos tempo de execução do agente(o tempo decorrido desde que um agente começa a trabalhar em uma solicitação de um engenheiro até retornar uma resposta).
- Por sessão do agente:o custo geral de tokens caiu 33% com o Compose em comparação com as visualizações.
Relatamos a eficiência da saída e os números de sessões típicas porque são resultados úteis de forma independente. As trocas entre engenheiro e agente e os números de tempo de execução comparam o uso de recursos por unidade de saída final, enquanto o número de tokens compara o custo total de uma sessão típica de agente.
Os dados também revelaram uma diferença consistente na forma como os dois frameworks lidam com códigos complexos ou frágeis. A Meta monitora isso usando uma pontuação de riscos de mudanças de código, que avalia a qualidade geral do código e a probabilidade de uma mudança causar incidentes de produção. A análise mediu a eficiência de recursos de um agente usando uma combinação de consumo de tokens, tempo de execução do agente e interações entre o engenheiro e o agente. À medida que os arquivos acumulam uma pontuação de risco mais alta, as sessões do agente de IA naturalmente se tornam menos eficientes em termos de recursos.
Quando a pontuação de risco acumulada de um arquivo dobra, a interface implementada com as visualizações do Android reduz a eficiência dos recursos do agente em 30% (por caractere inserido). Nas mesmas circunstâncias, a redução da interface do Jetpack Compose é de apenas 9%.
Com a parceria entre o Google e a Meta, a equipe do Instagram Direct trouxe uma nova perspectiva para a adoção do Compose, abordando-a pela lente da prontidão para IA da base de código, não apenas uma reescrita da interface. Esse trabalho revelou a força do Compose como base para criar bases de código e arquiteturas que priorizam a IA, especialmente quando aplicadas à escala de apps como o Instagram.
Otimizações de desempenho
O Instagram Direct é uma das plataformas mais importantes do app, e as pessoas esperam que ele seja rápido e responsivo o tempo todo. Adotar o Jetpack Compose significou uma reescrita substancial da interface, e o objetivo principal era preservar a experiência de alta qualidade sem regressões.
Anos de iteração já tinham levado a implementação legada baseada em visualizações no Instagram a um nível de desempenho excepcionalmente alto, e a equipe precisava atender a esse mesmo padrão ao migrar para um framework de IU totalmente novo.
O Instagram mede centenas, se não milhares, de métricas de performance. Para a adoção do Compose, os três mais importantes foram:
- Time to Interactive: o tempo entre a abertura da tela e o momento em que ela pode ser usada.
- Tempo para carregar totalmente : o tempo entre a abertura da tela e o carregamento completo de todo o conteúdo (por exemplo, imagens).
- Desempenho de rolagem: a suavidade da rolagem da tela, sem frames perdidos.
Essas métricas são rastreadas no ambiente de execução em produção, o que possibilita realizar testes A/B comparando a IU migrada do Compose com a IU legada e avaliar o impacto desse esforço no desempenho.
Uma maneira comum de abordar uma migração como essa é começar aos poucos, movendo alguns componentes da interface, coletando dados e estudando como eles se comportam. Embora sejam úteis, esses resultados iniciais mostram apenas uma parte do cenário, fornecendo falsos negativos contra a adoção do Compose porque:
- Não representativo: um componente de interface migrado pode fornecer dados úteis sobre o desempenho geral em uma tela específica. No entanto, componentes diferentes se comportam de maneira diferente por motivos que não são generalizados. Portanto, nem sempre é possível extrapolar a partir disso.
- Custo de interoperabilidade: uma pequena parte do Compose dentro de uma grande base de código de visualização paga um custo de ponte imprevisível entre os dois sistemas. Essa sobrecarga distorce a medição, então os resultados iniciais em pequena escala não refletem como seria a migração completa.
Como resultado, as migrações pequenas, embora úteis, nem sempre refletem o impacto total do Compose. Quanto mais uma superfície é migrada de ponta a ponta sem interrupções de ponte, mais clara e melhor se torna a imagem em termos de desempenho.
As telas principais do Instagram Direct são criadas com base em listas longas de vários tipos de itens, originalmente implementadas com RecyclerView. A arquitetura depende de abstrações personalizadas para escalonabilidade, mas permanece vinculada ao ciclo de vida do sistema baseado em visualizações.
A principal tarefa da equipe foi uma migração gradual de centenas de itens de lista individuais para o Compose na arquitetura atual baseada em RecyclerView, lançando-os em produção em pequenos grupos independentes em testes A/B, tudo sem mudanças visíveis na experiência de mensagens do usuário.
A maior desvantagem dessa configuração é uma dependência significativa do sistema de visualização legado por uma arquitetura principal do RecyclerView, mesmo após a migração completa de todos os itens da lista para o Compose. Como uma próxima etapa natural, a equipe decidiu investir na substituição da arquitetura principal baseada em RecyclerView pela alternativa nativa do Compose: LazyColumn.
Isso significa que os componentes da interface do Compose precisam ser abstraídos do framework em que estão incluídos, mas ainda precisam ser compatíveis com RecyclerView e LazyColumn ao mesmo tempo. Igualmente importante é a capacidade de alternar entre os dois no ambiente de execução usando flags de recurso para permitir testes A/B.
Embora os novos itens do Compose sejam nativamente compatíveis com LazyColumn e possam ser conectados a uma árvore de composição ininterrupta, uma API de interoperabilidade foi criada para que eles também possam ser inseridos em um RecyclerView. Isso possibilitou o lançamento da configuração do LazyColumn em um teste A/B lado a lado com o RecyclerView, reutilizando os mesmos itens do Compose e melhorando o desempenho, sem interromper o restante da equipe que estava criando e refinando recursos.
A escala, a complexidade e a sensibilidade do Instagram até mesmo às menores regressões representaram um desafio único para o Jetpack Compose. Para resolver esses problemas, foi necessária uma parceria prática e iterativa. Trabalhando em conjunto, os engenheiros do Google e da Meta analisaram métricas para identificar e projetar novos recursos do Compose para atender ou superar os comparativos de mercado baseados em visualizações. Como resultado dessa parceria, as seguintes adições ao Jetpack Compose se destacam: composição pausável com LazyLayoutCacheWindows e rastreamento de visibilidade.
Composição pausável com LazyLayoutCacheWindows
A composição pausável (ativada por padrão no Compose 1.10) permite que itens de lista lenta caros sejam compostos de forma incremental em frames para evitar instabilidade. Quando pareada com LazyLayoutCacheWindow (adicionada no Compose 1.9), a combinação melhora significativamente a suavidade da rolagem. Em testes internos recentes na Meta, a combinação da composição pausável com um LazyLayoutCacheWindow de uma janela de visualização reduziu as quedas de frames grandes por minuto (LFDs/m, na sigla em inglês) em cerca de 13% em comparação com o Compose padrão. A janela de cache por si só reduziu em cerca de 8% em relação ao mesmo valor de referência. LFDs/m é uma métrica interna que a Meta usa para rastrear travamentos perceptíveis durante a rolagem.
O uso de um LazyLayoutCacheWindow no app prepara e retém itens fora da tela em uma faixa baseada em pixels ao redor da janela de visualização para permitir movimentos rápidos. Para aproveitar o LazyLayoutCacheWindows no seu app, use a versão mais recente do Compose 1.13.0-alpha03 e configure-a como mostrado no exemplo abaixo:
val cacheWindow = LazyLayoutCacheWindow(ahead = 150.dp, behind = 100.dp) // OR val cacheWindow = LazyLayoutCacheWindow(aheadFraction = 0.5f, behindFraction = 0.3f) LazyColumn(state = state, cacheWindow = cacheWindow) { ... }
Há duas maneiras de configurar a janela de cache. Ambos descrevem a mesma coisa: quanto conteúdo fora da tela deve ser mantido composto, mas em unidades diferentes.
- Dp: comprimento absoluto fixo. O
ahead = 150.dpmantém 150 dp de conteúdo composto além da borda visível, seja qual for o dispositivo. - Flutuante: fração da janela de visualização. O
aheadFraction = 0.5fmantém metade de uma tela composta à frente, de modo que a quantidade absoluta é dimensionada de acordo com a altura da tela, oferecendo suporte a vários formatos: mais em um tablet ou dispositivo dobrável aberto, menos em um smartphone compacto.
A equipe do Instagram ajustou as frações flutuantes da janela de cache especificamente para a estrutura de conteúdo e os tamanhos de itens do Direct. Como os valores ideais variam de acordo com os parâmetros específicos da interface, encontrar o equilíbrio certo requer alguns testes.
Registro de impressões com onVisibilityChanged
A API onVisibilityChanged (adicionada no Compose 1.9.0) foi outro resultado importante da parceria técnica entre o Google e a Meta. Ele oferece às plataformas do Jetpack Compose em grande escala uma maneira consistente de saber quando um elemento combinável está realmente visível na tela, substituindo as implementações personalizadas e manuais usadas no passado. Só no Instagram Direct, esses indicadores de visibilidade são usados em centenas de arquivos para apoiar métricas de qualidade do produto que dependem de os elementos da interface terem sido realmente mostrados às pessoas.
Desempenho de inicialização
A adoção do Jetpack Compose para o Instagram Direct levou a melhorias de desempenho inesperadas em outras plataformas do app. O ambiente de execução do Jetpack Compose tem um custo de inicialização que você paga apenas uma vez. Como as mensagens são uma plataforma de alto tráfego, geralmente visitada no início de uma sessão do usuário, outras plataformas do Instagram que dependem do Compose tiveram melhorias de desempenho notáveis.
A performance de inicialização da interface do Compose no Instagram Direct foi otimizada com o uso de perfis de referência, que pré-compilam caminhos de código ativos no momento da instalação para que o Compose seja renderizado rapidamente desde a primeira inicialização.
Lições da migração do Instagram Direct para o Jetpack Compose
- O Jetpack Compose tem um retorno do investimento imediato:você não precisa usar fluxos de trabalho de IA avançados para se beneficiar do Compose. Com a redução de cerca de 50% no código, há menos código para manter e uma área de superfície reduzida para bugs.
- A criação de uma arquitetura nativa de IA levou a vitórias significativas, incluindo uma redução de 35% no tempo de execução do agente de IA, 32% menos trocas entre engenheiros e agentes e uma redução de 33% no custo de tokens.
- Embora existam muitas APIs de interoperabilidade e suporte para combinar as visualizações e o Compose, tente migrar superfícies maiores em vez de componentes pequenos individuais. Isso mantém a interface em uma única hierarquia de composição ininterrupta e desbloqueia todas as melhores otimizações de desempenho nativas do Compose.
- Combine a composição pausável com LazyLayoutCacheWindow: combinar esses dois elementos gera resultados melhores do que usar apenas janelas de cache. Com apenas a janela de cache, um item pesado ainda pode tentar ser composto em uma única passagem, o que pode exceder o orçamento de frames.
- Contribua com o próprio Compose. A Meta fez uma parceria com a equipe do Jetpack Compose para dar vida ao feedback e às ideias deles no Compose. Trabalhar em um kit de ferramentas de código aberto significa que todos se beneficiam quando bugs e melhorias de desempenho são feitos de forma centralizada. Por isso, envie seu feedback.
A adoção do Jetpack Compose gerou ganhos significativos no desenvolvimento assistido por IA e simplificou a engenharia de interface do Instagram. A abordagem declarativa reduz o código boilerplate, facilita a compreensão do estado e melhora a produtividade geral do desenvolvedor. A equipe de engenharia do Instagram está ansiosa para levar o Compose a mais plataformas no app e continuar a colaboração entre o Google e a Meta para trazer mais melhorias aos usuários do Instagram e do Jetpack Compose.
Se você ainda não testou o Compose, agora com assistência de IA, migrar para o Jetpack Compose está mais fácil do que nunca.
Agradecimentos. Agradecemos a Michal Zielinski e Matthew Du, da Meta, e a Andrei Shikov e George Mount, do Google, pelo trabalho de melhoria de desempenho do Compose, resultado da colaboração entre a Meta e o Google. Também agradecemos a Gary Ye, da Meta, por ajudar a levar o Compose para o Instagram Direct, e a Gopal Juneja, da Meta, por apoiar esse esforço com a ciência de dados.
-
Estudos de casoO Grab é o principal superapp do Sudeste Asiático, oferecendo serviços de entrega, mobilidade e serviços financeiros para milhões de usuários em oito países em um único app.
Ajesh Pai, Nguyen Van Minh, Tracy Agyemang • Leitura de 5 min -
Estudos de casoO WhatsApp é a maior plataforma de mensagens do mundo, atendendo a bilhões de usuários em todo o mundo. É a ferramenta de comunicação padrão para pessoas em diversas regiões, conectando usuários por mensagens privadas, confiáveis e seguras.
Niharika Arora, Tracy Agyemang, Mayank Jain • Leitura de 8 min -
Estudos de casoA missão do Tinder é incentivar e inspirar conexões reais, facilitando e tornando mais divertido o encontro de pessoas solteiras de todas as gerações.
Ajesh Pai, Ulises Uriel Verduzco Díaz , Tracy Agyemang • Leitura de 4 min
Receba os insights mais recentes sobre o desenvolvimento para Android na sua caixa de entrada toda semana.