Ordovia'da 2,6 milyon request bana SaaS mimarisi hakkında ne öğretti?
Bu vaka Supabase'in pahalı olup olmamasından daha büyük bir ders verdi: sistemin network davranışını açıklayamıyorsam maliyeti de performansı da güvenilir biçimde yönetemem. Önce görünürlük, sonra mimari, en son altyapı değişimi.
Ordovia büyürken Supabase tarafında aylık yaklaşık 2,6 milyon request seviyesine ulaştık. Bu sayı tek başına felaket değildi. Ama ürünün henüz devasa kullanıcı hacmine sahip olmadığı düşünüldüğünde önemli bir soru üretiyordu: bu kadar konuşmayı kim yapıyordu?
Backend faturası bazen fiyat problemi değil, sistemin kendi davranışını açıklayamamasının faturasıdır.
İlk hata: toplam request sayısına bakmak
İlk bakışta 2,6 milyon request tek bir sayı. Fakat optimizasyon için işe yaramıyor. Hangi route? Hangi feature? Kaçı kullanıcı aksiyonuyla, kaçı polling ile, kaçı duplicate mount nedeniyle oluşuyor? Bunları bilmeden yalnızca toplamı düşürmeye çalışırsınız.
type UsefulTelemetry = { requestsPerActiveUser: number; requestsPerSession: number; requestsPerRoute: Record<string, number>; requestsPerFeature: Record<string, number>;};İkinci ders: request sayısı ürün mimarisinin yan ürünü
Görevler, takvim, ritüeller, notlar ve başka domain'ler kendi refresh mantığını kurduğunda her biri lokal olarak mantıklı görünebilir. Toplamda ise aynı kullanıcının tek oturumunda birbirinden habersiz network döngüleri oluşur.
feature-local convenience -> system-wide network costBu yüzden performans audit'inde yalnızca yavaş endpoint değil, domain'lerin birbirinden bağımsız verdiği fetch kararlarını da inceliyorum.
Üçüncü ders: cloud-first ile cloud-every-second farklı
Ordovia'nın cihazlar arasında tutarlı olması için cloud kaynak doğrusu önemli. Ama bu her component'in sürekli cloud'a sorması gerektiği anlamına gelmiyor.
const architecture = { sourceOfTruth: 'cloud', readStrategy: 'cache-aware', refreshStrategy: 'event-or-stale-driven',};Dördüncü ders: polling varsayılan olmamalı
Polling kolaydır çünkü state değişimini düşünmek yerine belirli aralıklarla tekrar sorarsınız. Fakat 30 saniyelik küçük bir loop kullanıcı ve domain sayısıyla çarpıldığında büyük hacim yaratabilir.
1 loop x 2 request/min x 24h x 30d = 86,400 request/monthÜstelik kullanıcı sekmeye bakmıyor olabilir. Bu nedenle visibility, focus, stale time ve gerçek event sinyalleri refresh stratejisinin parçası olmalı.
Beşinci ders: cache performans özelliği değil, sistem politikası
Cache'i yalnızca sayfayı hızlandıran teknik detay olarak görmek eksik. Cache, backend yükünü ve maliyeti kontrol eden ürün altyapısıdır.
const syncPolicy = { tasks: { staleAfter: 5 * 60_000, refreshOnFocus: true }, calendar: { staleAfter: 10 * 60_000, refreshOnFocus: true }, profile: { staleAfter: 60 * 60_000, refreshOnFocus: false },};Her domain aynı tazelik ihtiyacına sahip değil. Kullanıcı profili ile yaklaşan takvim etkinliği aynı refresh politikasını kullanmamalı.
Altıncı ders: mutation sonrası her şeyi refetch etmek kolay ama pahalı
Bir görevi tamamladıktan sonra tasks, stats, today, project ve reward verisini topluca yeniden çekmek implementation'ı kolaylaştırır. Fakat sistem büyüdükçe tek kullanıcı aksiyonu request patlamasına dönüşür.
const mutationEffects = { taskCompleted: ['task', 'today', 'projectStats'], // Yalnız gerçekten etkilenen domain'leri invalidate et.};Yedinci ders: altyapı değiştirmek ilk çözüm olmamalı
Yüksek faturayı görünce Supabase'den Firebase'e veya Cloudflare'a geçmek çekici. Fakat aynı polling ve duplicate fetch davranışını yeni servise taşırsanız yalnızca problemin adresi değişir.
measure -> reduce waste -> fix architecture -> then compare infrastructureBenim için optimizasyon sırası bu hale geldi. Servis değişimi ancak uygulama davranışını düzelttikten sonra anlamlı karşılaştırılabilir.
Sekizinci ders: sistem gözlemlenemiyorsa sistem yönetilemiyor
Bu vaka sonunda en çok önem verdiğim metriklerden biri request / active user / day oldu. Toplam request kullanıcı büyümesiyle doğal olarak artabilir. Kullanıcı başına request artıyorsa mimari davranış değişiyor demektir.
const healthSignal = totalRequests / activeUsers / activeDays;Ordovia açısından daha büyük ders
Ordovia birçok domain'i tek günlük komuta merkezinde birleştiren bir ürün. Bu ürün avantajı aynı zamanda mimari risk: her yeni domain diğerleriyle ilişki kurdukça görünmeyen coupling artabilir. Bu nedenle ürün seviyesinde 'tek sistem' olmak, kod seviyesinde 'her şey birbirine bağlı' olmak anlamına gelmemeli.
Entegre ürün ile birbirine dolanmış sistem aynı şey değildir.
Bugün başlangıçta neyi farklı yapardım?
- Her domain için baştan sync/cache policy tanımlardım.
- Component'lerin doğrudan database erişimini sınırlardım.
- Request/user/day metriğini erken görünür yapardım.
- Background polling'i varsayılan olarak kapalı tutardım.
- Mutation invalidation'ını explicit domain etkileriyle kurardım.
- Altyapı fiyatını ancak uygulama davranışı normalize olduktan sonra karşılaştırırdım.
2,6 milyon request bana ucuz backend bulmayı değil, kendi sistemimin neden konuştuğunu bilmeyi öğretti. Bu ders yalnız Ordovia için değil, bugün kurduğum bütün SaaS sistemleri için geçerli.
SaaS Sistemleri: Mimari, Maliyet ve Sadelik
Backend faturası, cloud seçimi ve ürün karmaşıklığını aynı sistem problemi olarak ele alan saha notları ve karar çerçeveleri.