由3分鐘到500ms:完全冇道理嘅Signup Bug
我追蹤咗一個3分鐘嘅signup delay,結果發現係薛定諤嘅user——因為database寫入同讀取之間嘅replication lag,佢同時存在又唔存在。
你知唔知用戶report一個完全冇道理嘅bug嗰種感覺?「個app sign up之後要3分鐘先load到。」三分鐘?嗰個唔係loading time,嗰個係去沖杯咖啡。呢個係我追蹤DIALØGUE歷史上最奇怪嘅bug之一嘅故事。
謎題開始
佢開始得好innocent。一個新用戶用Google SSO sign up,好excited想試DIALØGUE。然後...咩都冇。好吧,唔係完全咩都冇——佢哋見到我哋設計得好靚嘅loading skeleton。成三分鐘。
奇怪嘅地方?佢只發生喺_新_用戶身上。Existing用戶可以即刻login。而且唔一致——有時1分鐘,有時3分鐘,偶爾即刻就work。
我第一個想法:「一定係cold start問題。」(旁白:唔係cold start問題。)
調查
第1輪:Blame Frontend
```typescript
// 第一個嫌疑人:Profile loading hook
useEffect(() => {
if (user) {
fetchUserProfile(); // 呢個take forever
}
}, [user]);
```
四圍加timer。API call的確take 3分鐘。但點解?Backend應該要麼return data,要麼error out,唔係就咁...等。
第2輪:Blame Backend
深入我哋嘅Supabase Edge Functions:
```typescript
// Edge Function for getting user profile
const { data: profile } = await supabase
.from('users')
.select('*')
.eq('id', userId)
.single();
if (!profile) {
// New user - create profile
await createUserProfile(userId);
}
```
呢個睇落冇問題。應該好快,對唔對?係時候加更多logging。
第3輪:劇情加深
四圍加咗logs(我係話_四圍_)之後,我發現咗啲bizarre嘅嘢:
[00:00] User signs in with Google
[00:01] Auth trigger fires - creates user record
[00:01] Frontend requests profile
[00:01] Edge Function queries for user... NO RESULT
[00:02] Edge Function tries to create user...
[00:02] Database constraint error: User already exists
[00:03] Function retries...
[03:00] Function finally times out
等等,咩話?User唔存在,但同時又已經存在?薛定諤嘅user?T.T
真相
盯住database logs睇到眼攰之後,我終於見到喇。我哋嘅database有competing processes:
- Supabase Auth Trigger:喺signup時create user record
- Edge Function:如果搵唔到就試create user
- Database Replication Lag:Trigger嘅INSERT仲未replicate到read replica
以下係發生緊嘅嘢:
-- Auth trigger (on primary database)
INSERT INTO users (id, email) VALUES ($1, $2);
-- Edge Function (reading from replica)
SELECT * FROM users WHERE id = $1; -- Returns nothing!
-- Edge Function (trying to help)
INSERT INTO users (id, email) VALUES ($1, $2); -- CONFLICT!
Function會用exponential backoff retry,每次都撞到同一個race condition直到:
- Replication終於catch up(1-3分鐘)
- Function timeout(3分鐘)
更新:真正嘅元兇同最終Robust Solution
最初post之後,我哋繼續debug,雖然backend嘅改動係improvements,但mystery嘅root一直搵唔到。突破係當我哋將focus由backend轉到client-side嘅hydration同authentication flow。
真正嘅元兇:Client-Side Race Condition
問題唔係慢嘅database trigger或cold Edge Function。真正嘅問題係經典嘅client-side race condition:
- OAuth Redirect: 新用戶用Google sign in然後被redirect返我哋嘅app。
- Async Session: Supabase client library(
supabase-js)開始處理URL入面嘅token嚟建立session。呢個係async process。 - Premature Render: 但我哋嘅React app即刻render。佢_喺_步驟2嘅async process完成之前就ask用戶嘅session。
- Failure: App得到
nullsession,判定用戶未login,render blank或error state。幾秒之後session available喇,但太遲——UI已經做咗決定。
我哋最初嘅workarounds,好似加client-side retries,只係fight緊呢個fundamental race condition嘅symptoms。
Solution:Authentication嘅Single Source of Truth
正確嘅最終solution係re-architect我哋嘅frontend authentication state management,令佢truly event-driven同robust。
1. SupabaseProvider入面嘅Centralized Logic:
我哋refactor咗SupabaseProvider做authentication嘅single authoritative source of truth。移除咗其他hooks入面嘅所有其他listeners同checks。
2. 正確使用onAuthStateChange:
Fix嘅core係_exclusively_依賴Supabase嘅onAuthStateChange listener。
// Simplified logic in SupabaseProvider.tsx
export function SupabaseProvider({ children }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true); // Start in a loading state
useEffect(() => {
const { data: { subscription } } = supabase.auth.onAuthStateChange(
(event, session) => {
setUser(session?.user ?? null);
// 只有當呢個listener fire咗先consider authentication「完成」。
setLoading(false);
}
);
return () => subscription.unsubscribe();
}, []);
// ...
}
呢個pattern確保成個application保持loading state直到Supabase confirm用戶嘅session係valid定null。再冇race condition喇。
Happy Ending
DIALØGUE依家一秒內就可以onboard新用戶。唔使喺signup嘅時候去沖咖啡喇。唔使再有confused嘅用戶以為佢哋整壞咗啲嘢。
呢個fix已經喺production上面3個禮拜喇。零timeout issues。零race conditions。只有smooth、快嘅signups,好似一開始就應該係咁。
花一個禮拜debug呢個值唔值得?當我見到新用戶可以喺sign up幾分鐘內seamlessly create佢哋第一個podcast——absolutely。:D
你有冇追蹤過一個bug,入面嘅嘢同時存在又唔存在?我覺得每個developer都至少有一個薛定諤bug嘅故事。我好想聽吓你嘅!
祝好,
Chandler
想試吓依家快咗嘅signup?喺DIALØGUECreate你嘅AI podcast。我promise唔會再要3分鐘喇 :)





