Skip to content
··4 min de leitura

De 3 Minutos para 500ms: O Bug de Cadastro Que Não Fazia Sentido

Fui atrás de um atraso de 3 minutos no cadastro que acabou sendo o usuário de Schrödinger — existindo e não existindo ao mesmo tempo por causa do lag de replicação do banco de dados entre escritas e leituras.

Você conhece aquela sensação quando usuários reportam um bug que não faz nenhum sentido? "O app leva 3 minutos para carregar depois que eu me cadastro." Três minutos? Isso não é tempo de carregamento, é uma pausa para o café. Esta é a história de como rastreei um dos bugs mais estranhos na história do DIALØGUE.

O Mistério Começa

Começou de forma inocente. Um novo usuário se cadastrou usando Google SSO, animado para experimentar o DIALØGUE. Depois… nada. Bem, não exatamente nada — eles receberam nosso skeleton de carregamento lindamente desenhado. Por três minutos inteiros.

A parte estranha? Só acontecia com usuários novos. Usuários existentes conseguiam fazer login instantaneamente. E não era consistente — às vezes era 1 minuto, às vezes 3, ocasionalmente funcionava imediatamente.

Meu primeiro pensamento: "Deve ser um problema de cold start." (Narrador: Não era um problema de cold start.)

A Investigação

Rodada 1: Culpar o Frontend

```typescript
// Primeiro suspeito: O hook de carregamento de perfil
useEffect(() => \{
  if (user) {
    fetchUserProfile(); // Isso estava demorando uma eternidade
  \}
}, [user]);
```

Adicionei timers em todo lugar. A chamada de API de fato estava levando 3 minutos. Mas por quê? O backend deveria ou retornar dados ou dar erro, não simplesmente… esperar.

Rodada 2: Culpar o Backend

Mergulhei nas nossas Edge Functions do Supabase:

```typescript
// Edge Function para obter perfil do usuário
const \{ data: profile \} = await supabase
  .from('users')
  .select('*')
  .eq('id', userId)
  .single();

if (!profile) \{
  // Novo usuário - crie o perfil
  await createUserProfile(userId);
\}
```

Isso parecia correto. Deveria ser rápido, certo? Hora de adicionar mais logging.

Rodada 3: A Trama Engrossa

Depois de adicionar logs em todo lugar (e quero dizer todo lugar), descobri algo bizarro:

[00:00] Usuário faz login com Google
[00:01] Auth trigger dispara - cria registro do usuário
[00:01] Frontend solicita perfil
[00:01] Edge Function consulta o usuário... SEM RESULTADO
[00:02] Edge Function tenta criar usuário...
[00:02] Erro de restrição no banco: Usuário já existe
[00:03] Função tenta novamente...
[03:00] Função finalmente dá timeout

Espera, o quê? O usuário não existe, mas já existe? O usuário de Schrödinger? T.T

A Revelação

Depois de olhar para logs de banco de dados até os olhos doerem, finalmente vi. Nosso banco de dados tinha processos concorrentes:

  1. Auth Trigger do Supabase: Cria registro do usuário no cadastro
  2. Edge Function: Tenta criar usuário se não encontrado
  3. Lag de Replicação do Banco: O INSERT do trigger ainda não tinha replicado para a réplica de leitura

Aqui está o que estava acontecendo:

-- Auth trigger (no banco primário)
INSERT INTO users (id, email) VALUES ($1, $2);

-- Edge Function (lendo da réplica)
SELECT * FROM users WHERE id = $1; -- Não retorna nada!

-- Edge Function (tentando ajudar)
INSERT INTO users (id, email) VALUES ($1, $2); -- CONFLITO!

A função tentaria novamente com backoff exponencial, cada tentativa batendo na mesma race condition até que:

  • A replicação finalmente alcançasse (1-3 minutos)
  • A função desse timeout (3 minutos)

Atualização: O Culpado Real e a Solução Final Robusta

Depois do post inicial, continuamos debugando, e embora nossas mudanças no backend fossem melhorias, a raiz do mistério permanecia esquiva. A virada veio quando mudamos nosso foco do backend para a hidratação client-side e o fluxo de autenticação.

O Culpado Real: Uma Race Condition Client-Side

O problema não era um trigger de banco de dados lento ou uma Edge Function fria. O verdadeiro problema era uma race condition clássica do lado do cliente:

  1. Redirecionamento OAuth: Um novo usuário faz login com Google e é redirecionado de volta para nosso app.
  2. Sessão Assíncrona: A biblioteca cliente Supabase (supabase-js) começa a processar o token da URL para estabelecer uma sessão. Este é um processo assíncrono.
  3. Render Prematuro: Nosso app React, entretanto, renderiza imediatamente. Ele solicita a sessão do usuário antes que o processo assíncrono do passo 2 esteja completo.
  4. A Falha: O app recebe uma sessão null, conclui que o usuário não está logado e renderiza um estado em branco ou de erro. Alguns momentos depois, a sessão se torna disponível, mas já é tarde demais — a UI já tomou sua decisão.

Nossas soluções iniciais, como adicionar retries do lado do cliente, eram apenas sintomas de lutar contra essa race condition fundamental.

A Solução: Uma Única Fonte de Verdade para Autenticação

A solução correta e final foi rearquitetar nosso gerenciamento de estado de autenticação no frontend para ser verdadeiramente orientado a eventos e robusto.

1. Lógica Centralizada no SupabaseProvider: Refatoramos nosso SupabaseProvider para ser a única fonte autoritativa de verdade para autenticação. Removemos todos os outros listeners e verificações de outros hooks.

2. Usando onAuthStateChange Corretamente: O núcleo da correção foi depender exclusivamente do listener onAuthStateChange do Supabase.

// Lógica simplificada no SupabaseProvider.tsx

export function SupabaseProvider(\{ children \}) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true); // Começa em estado de carregamento

  useEffect(() => {
    const { data: { subscription } } = supabase.auth.onAuthStateChange(
      (event, session) => \{
        setUser(session?.user ?? null);
        // Só consideramos a autenticação "finalizada" quando este listener dispara.
        setLoading(false);
      \}
    );

    return () => subscription.unsubscribe();
  }, []);

  // ...
}

Esse padrão garante que toda a aplicação permanece em estado de loading até que o Supabase confirme que a sessão do usuário é válida ou null. Não há mais race condition.

O Final Feliz

O DIALØGUE agora integra novos usuários em menos de um segundo. Sem mais pausas para o café durante o cadastro. Sem mais usuários confusos se perguntando se quebraram algo.

A correção está em produção há 3 semanas. Zero problemas de timeout. Zero race conditions. Apenas cadastros suaves e rápidos como deveria ter sido desde o começo.

Valeu a pena passar uma semana debugando isso? Quando vejo novos usuários criar seu primeiro podcast em minutos após o cadastro — absolutamente. :D

Você já rastreou um bug onde a coisa simultaneamente existia e não existia? Sinto que todo desenvolvedor tem pelo menos uma história de bug de Schrödinger. Adoraria ouvir a sua!

Abraços,

Chandler

Quer experimentar o cadastro agora ultra-rápido? Crie seu podcast de IA no DIALØGUE. Prometo que não vai demorar 3 minutos mais :)

Continuar Lendo

Minha Jornada
Conectar
Idioma
Preferências