Nesne depolama vs veritabanı blob'ları: dosya meta verisini Postgres'te tutun, baytları nesne depolamada saklayın ve indirilmeleri hızlı, maliyetleri öngörülebilir tutun.

Kullanıcı yüklemeleri basit gözükür: bir dosya kabul et, kaydet, sonra göster. Az kullanıcı ve küçük dosyalarla bu işler. Sonra hacim artar, dosyalar büyür ve sorun yükleme düğmesiyle alakası olmayan yerlerde ortaya çıkar.
İndirmeler yavaşlar çünkü uygulama sunucunuz ya da veritabanınız ağır yükü taşıyor. Yedeklemeler devasa ve yavaş olur; geri yüklemeler, ihtiyaç duyduğunuz anda daha uzun sürer. Depolama faturaları ve bant genişliği (egress) faturaları, dosyalar verimsiz servis edildiğinde, çoğaltıldığında veya hiç temizlenmediğinde fırlayabilir.
İstediğiniz genellikle sıkıcı ama güvenilir bir şeydir: yük altında hızlı aktarım, net erişim kuralları, basit operasyonlar (yedek, geri yükleme, temizlik) ve kullanım arttıkça öngörülebilir kalan maliyetler.
Buna ulaşmak için sık karışan iki şeyi ayırın:
Meta veri dosya hakkında küçük bilgiler demektir: kimin dosyayı yüklediği, adı, boyutu, tipi, ne zaman yüklendiği ve nerede durduğu. Bunlar sorgulama, filtreleme ve kullanıcılar, projeler ve izinlerle join gerektirdiği için veritabanına (ör. Postgres) ait olmalı.
Dosya baytları dosyanın gerçek içeriğidir (fotoğraf, PDF, video). Baytları veritabanı blob'larında tutmak işe yarayabilir, ama veritabanını ağırlaştırır, yedekleri büyütür ve performansı öngörülmez kılar. Baytları nesne depolamada tutmak veritabanını en iyi yaptığı işe odaklı tutar; dosyalar ise bu iş için inşa edilmiş sistemler tarafından hızlı ve ucuz servis edilir.
İnsanlar "yüklemeleri veritabanında saklayın" dediklerinde genellikle veritabanı blob'larını kastediyor: ya bir BYTEA sütunu (satır içinde ham bayt) ya da Postgres "large objects" (büyük değerleri ayrı saklayan bir özellik). İkisi de çalışır, ama ikisi de veritabanınızı dosya baytlarını servis etmekle sorumlu kılar.
Nesne depolama farklı bir fikir: dosya bir bucket içinde bir nesne olarak yaşar, bir anahtarla adreslenir (ör. uploads/2026/01/file.pdf). Büyük dosyalar, ucuz depolama ve akış halinde indirmeler için tasarlanmıştır. Ayrıca birçok eşzamanlı okumayı iyi idare eder, veritabanı bağlantılarınızı meşgul etmez.
Postgres sorgularda, kısıtlamalarda ve işlemlerde parlıyor. Kimin dosyaya sahip olduğu, dosyanın ne olduğu, ne zaman yüklendiği ve indirilebilir olup olmadığı gibi meta veriler için mükemmeldir. Bu meta veri küçüktür, indekslenmesi kolaydır ve tutarlılığı sağlamak basittir.
Pratik bir kural:
Kısa bir akıl testi: yedekler, replikalar ve migration'lar dosya baytları dahil olduğunda zahmetli hale gelecekse, baytları Postgres dışında tutun.
Çoğu ekip sonunda şu basit düzen ile ilerler: baytları nesne depolamada saklayın, dosya kaydını (kimin sahibi olduğu, ne olduğu, nerede durduğu) Postgres'te saklayın. API'niz koordinasyon ve yetkilendirme yapar, ama büyük yüklemeleri ve indirmeleri proxy etmez.
Bu size üç net sorumluluk verir:
file_id, sahibi, boyutu, içerik tipi ve nesne göstergesi.Bu stabil file_id her şey için birincil anahtar olur: ekine referans veren yorumlar, bir PDF'ye işaret eden faturalar, denetim logları ve destek araçları. Kullanıcı dosyayı yeniden adlandırabilir, siz bucket'lar arasında taşıyabilirsiniz; file_id aynı kalır.
Mümkünse kaydedilen nesneleri immutable (değişmez) olarak ele alın. Bir kullanıcı bir belgeyi değiştiriyorsa, genellikle mevcut baytları üzerine yazmak yerine yeni bir nesne (ve genellikle yeni bir satır veya yeni bir versiyon satırı) oluşturun. Bu önbellekleme basitliği sağlar, "eski link yeni dosyayı döndürüyor" sürprizlerini önler ve temiz bir rollback hikâyesi verir.
Gizliliğe erken karar verin: varsayılan olarak özel, istisna olarak genel. İyi bir kural: veritabanı bir dosyaya kimlerin erişebileceği konusunda gerçek kaynaktır; nesne depolama API'nizin verdiği kısa ömürlü izinleri uygular.
Temiz ayırmayla Postgres dosya hakkındaki gerçekleri tutar, nesne depolama baytları tutar. Bu veritabanınızı daha küçük, yedekleri daha hızlı ve sorguları daha basit tutar.
Pratik bir uploads tablosu gerçek soruları cevaplamak için birkaç alana ihtiyaç duyar: "bu kimin?", "nerede saklanıyor?" ve "indirilmeye uygun mu?"
CREATE TABLE uploads (
id uuid PRIMARY KEY,
owner_id uuid NOT NULL,
bucket text NOT NULL,
object_key text NOT NULL,
size_bytes bigint NOT NULL,
content_type text,
original_filename text,
checksum text,
state text NOT NULL CHECK (state IN ('pending','uploaded','failed','deleted')),
created_at timestamptz NOT NULL DEFAULT now()
);
CREATE INDEX uploads_owner_created_idx ON uploads (owner_id, created_at DESC);
CREATE INDEX uploads_checksum_idx ON uploads (checksum);
Birkaç karar ileride ağrıdan kurtarır:
bucket + object_key kullanın. Yüklendikten sonra değişmez tutun.pending bir satır ekleyin. Sistem nesnenin gerçekten var olduğunu ve boyutun (ve ideal olarak checksum'un) eşleştiğini onayladıktan sonra uploaded yapın.original_filename yalnızca gösterim içindir. Tür veya güvenlik kararları için buna güvenmeyin.Eğer değişiklikleri destekliyorsanız (ör. kullanıcı bir faturayı yeniden yüklüyorsa), upload_versions gibi ayrı bir tablo ekleyin: upload_id, version, object_key ve created_at. Bu şekilde geçmişi tutabilir, hataları geri alabilir ve eski referansları kırmaktan kaçınabilirsiniz.
API'nizin koordinasyon yapıp baytlarla uğraşmamasıyla yüklemeleri hızlı tutun. Veritabanınız yanıt verirken, nesne depolama bant genişliği yükünü taşır.
Her şey gönderilmeden önce bir yükleme kaydı oluşturarak başlayın. API'niz bir upload_id, dosyanın nerede duracağı (object_key) ve kısa ömürlü bir yükleme izni döndürsün.
Yaygın bir akış:
pending) bir satır oluşturur, beklenen boyutu ve hedeflenen içerik tipini kaydeder.upload_id ve depolama yanıtı alanlarını (örn. ETag) içeren finalize çağrısı yapar. Sunucunuz boyutu, checksum'u (kullanıyorsanız) ve içerik tipini doğrular, sonra satırı uploaded olarak işaretler.failed yapın ve isteğe bağlı olarak nesneyi silin.Tekrarlar ve kopyalar normaldir. Finalize çağrısını idempotent yapın: aynı upload_id iki kez finalize edilirse, başarı döndürün ve hiçbir şeyi değiştirmeyin.
Tekrarlardan ve yeniden yüklemelerden kaynaklanan kopyaları azaltmak için bir checksum saklayın ve "aynı sahip + aynı checksum + aynı boyut" durumunu aynı dosya olarak ele alın.
İyi bir indirme akışı uygulamada tek bir stabil URL ile başlar; hatta baytlar başka yerde olsa bile. Düşünün: /files/{file_id}. API'niz file_id ile Postgres'te meta veriyi arar, izinleri kontrol eder, sonra dosyanın nasıl teslim edileceğine karar verir.
file_id ile ister.uploaded olduğunu doğrular.Yönlendirmeler, genel veya yarı-genel dosyalar için basit ve hızlıdır. Özel dosyalar için presigned GET URL'ler depolamayı gizli tutar ama tarayıcının yine de doğrudan indirmesine izin verir.
Video ve büyük indirmeler için nesne depolamanızın (ve varsa proxy katmanınızın) aralık isteklerini (Range header'ları) desteklediğinden emin olun. Bu, aramayı ve devam ettirilebilir indirmeyi mümkün kılar. Baytları API'niz üzerinden geçirirseniz, Range desteği genellikle bozulur veya pahalı olur.
Hızın kaynağı önbelleklemedir. Stabil /files/{file_id} uç noktanız genellikle önbelleğe alınmamalıdır (bu bir yetki kapısıdır), oysa nesne depolama yanıtı içeriğe göre önbelleğe alınabilir. Dosyalar immutable ise (yeni yükleme = yeni anahtar), uzun önbellek süresi ayarlayabilirsiniz. Dosyaları üzerine yazıyorsanız, önbellek sürelerini kısa tutun veya versiyonlanmış anahtarlar kullanın.
Küresel kullanıcılarınız veya büyük dosyalarınız varsa bir CDN yardımcı olur. Hedef kitleniz küçük veya tek bölgede yoğunsa, nesne depolama tek başına genellikle yeterli ve başlamak için daha ucuzdur.
Sürpriz faturalar genellikle disk üzerinde duran ham baytlardan değil, indirmelerden ve churn'dan gelir.
Maliyeti etkileyen dört sürücüye fiyat verin: ne kadar depoluyorsunuz, ne sıklıkla okuma ve yazma yapılıyor (istekler), sağlayıcınızdan ne kadar veri çıkıyor (egress) ve tekrar eden origin indirmelerini azaltmak için CDN kullanıp kullanmadığınız. Kimse dokunmayan büyük bir dosyadan çok, küçük bir dosyanın 10.000 kez indirilmesi daha maliyetli olabilir.
Harcamayı sabit tutan kontroller:
Yaşam döngüsü kuralları genellikle en kolay kazanımdır. Örneğin: orijinal fotoğrafları 30 gün "sıcak" tutun, sonra daha ucuz bir depo sınıfına taşıyın; faturaları 7 yıl saklayın; başarısız yükleme parçalarını 7 gün sonra silin. Basit saklama politikaları bile depolama artışını durdurur.
Dedup basit olabilir: dosya meta veri tablonuzda bir içerik hash'i (örn. SHA-256) saklayın ve sahip başına benzersizliği zorunlu kılın. Kullanıcı aynı PDF'yi iki kere yüklediğinde mevcut nesneyi yeniden kullanabilir ve sadece yeni bir meta veri satırı oluşturabilirsiniz.
Son olarak, kullanım takibini zaten yaptığınız yerde tutun: Postgres. Her kullanıcı veya çalışma alanı için bytes_uploaded, bytes_downloaded, object_count ve last_activity_at saklayın. Bu, UI'da limitleri göstermek ve fatura gelmeden önce uyarı tetiklemek için işe yarar.
Yüklemeler için güvenlik iki şeye dayanır: bir dosyaya kim erişebilir ve bir şey ters giderse sonradan ne kanıtlayabilirsiniz.
Erişim modeline net başlayın ve bunu servisler arasında dağınık kurallarda değil Postgres meta verisinde kodlayın.
Çoğu uygulamayı kapsayan basit bir model:
Özel dosyalar için ham nesne anahtarlarını ifşa etmekten kaçının. Zaman sınırlı, kapsam sınırlı presigned yükleme ve indirme URL'leri verin ve bunları sık döndürün.
Hem taşınma sırasında hem de dinlenirken şifrelemeyi doğrulayın. Taşınma sırasında HTTPS uçtan uca (doğrudan depolamaya yüklemelerde bile) kullanın. Dinlenme halinde depolama sağlayıcınızın sunucu tarafı şifrelemesini kullanın ve yedeklerin de şifrelendiğinden emin olun.
Güvenlik ve veri kalitesi için kontrol noktaları ekleyin: bir yükleme URL'si vermeden önce içerik tipi ve boyutu doğrulayın, sonra yüklemeden sonra (dosya adı yerine gerçek depolanan baytlara göre) tekrar doğrulayın. Risk profiliniz yüksekse, asenkron olarak kötü amaçlı yazılım taraması çalıştırın ve dosya temizlenene kadar karantinaya alın.
Olayları araştırmak ve temel uyumluluk ihtiyaçlarını karşılamak için denetim alanları saklayın: uploaded_by, ip, user_agent ve last_accessed_at pratik bir tabandır.
Veri bölgeliliği (data residency) gereksinimleriniz varsa, depolama bölgesini kasti seçin ve bunu hesaplama yaptığınız yerle tutarlı tutun.
Çoğu yükleme problemi ham hızla ilgili değildir. Erken aşamada kullanışlı görünen tasarım tercihleri, gerçek trafik, gerçek veri ve gerçek destek talepleriyle acı verir.
invoice.pdf yükleyebilir) ve garip karakterler uç durumlar yaratır. Dosya adlarını gösterim için saklayın, depolama için benzersiz bir anahtar üretin.Somut bir örnek: bir kullanıcı profil fotoğrafını üç kez değiştirirse, eski üç nesne için ödeme yapıyor olabilirsiniz; temizleme planı yoksa bunlar sonsuza kadar kalabilir. Güvenli bir desen: Postgres'te yumuşak silme (soft delete) yapın, sonra arka plan işi nesneyi kaldırıp sonucu kaydetsin.
İlk büyük dosya geldiğinde, bir kullanıcı yükleme sırasında sayfayı yenilediğinde ya da bir hesap silindiğinde baytların arkada kalmasıyla çoğu problem ortaya çıkar.
Postgres tablonuzun dosyanın boyutunu, checksum'unu (bütünlüğü doğrulamak için) ve açık bir durum yolunu kaydettiğinden emin olun (örn: pending, uploaded, failed, deleted).
Son mil kontrol listesi:
Range) desteklediğini doğrulayın ki büyük dosyalar hızlı başlasın ve bir duraklamadan sonra devam etsin.Bir somut test: 2 GB'lık bir dosya yükleyin, %30'da sayfayı yenileyip sonra devam edin. Sonra yavaş bir bağlantıda indirin ve ortasına atlayın. Bu akışlardan biri zayıfsa, bunu lansmandan önce düzeltin.
Basit bir SaaS uygulaması genellikle iki çok farklı yükleme türüne sahiptir: profil fotoğrafları (sık, küçük, önbelleğe alınması güvenli) ve PDF faturalar (hassas, özel kalmalı). Meta veriyi Postgres'te, baytları nesne depolamada tutma ayrımı burada kendini ödüllendirir.
Aşağıda tek bir files tablosunda meta verinin nasıl görünebileceğine dair birkaç davranışı etkileyen alan örneği var:
| field | profil fotoğraf örneği | fatura PDF örneği |
|---|---|---|
kind | avatar | invoice_pdf |
visibility | private (imzalı URL ile servis edilir) | private |
cache_control | public, max-age=31536000, immutable | no-store |
object_key | users/42/avatars/2026-01-17T120102Z.webp | orgs/7/invoices/INV-1049.pdf |
status | uploaded | uploaded |
size_bytes | 184233 | 982341 |
Kullanıcı bir fotoğrafı değiştirdiğinde bunu üzerine yazma yerine yeni bir dosya olarak ele alın. Yeni bir satır ve yeni object_key oluşturun, sonra kullanıcı profilini yeni dosya ID'sine işaret edecek şekilde güncelleyin. Eski satırı replaced_by=<new_id> (veya deleted_at) ile işaretleyin ve eski nesneyi daha sonra arka plan işi ile silin. Bu geçmişi korur, rollback'i kolaylaştırır ve yarış durumlarını önler.
Destek ve hata ayıklama kolaylaşır çünkü meta veri bir hikâye anlatır. Birisi "yüklemem başarısız oldu" dediğinde destek status, insan tarafından okunabilir last_error, storage_request_id veya etag (depolama loglarında izlemek için), zaman damgaları (takıldı mı?), ve owner_id ile kind (erişim politikası doğru mu?) gibi alanlara bakabilir.
Küçük başlayın ve mutlu yolu sıkıcı hale getirin: dosyalar yüklenir, meta veri kaydedilir, indirmeler hızlıdır ve hiçbir şey kaybolmaz.
İyi bir ilk kilometre taşı minimal bir Postgres meta veri tablosu artı bir doğrudan-depolama yükleme akışı ve bir indirime gating yapan indirme akışı olsun; bunu beyaz tahta üzerinde açıklayabileceğiniz şekilde kurun. Bu uçtan uca çalıştıktan sonra versiyonlar, kotalar ve yaşam döngüsü kuralları ekleyin.
Her dosya tipi için bir açık depolama politikası seçin ve yazılı hale getirin. Örneğin, profil fotoğrafları önbelleğe alınabilir, faturalar ise özel olmalı ve yalnızca kısa ömürlü indirme URL'leriyle erişilmeli. Bir bucket ön ekinin içinde politika karıştırmak veetkilenenleri yanlışlıkla açığa çıkarmak için iyi bir yoldur.
Erken enstrümantasyon ekleyin. İlk günden itibaren istediğiniz metrikler: finalize hata oranı, yetim oranı (DB satırı olmayan nesneler ve tam tersi), dosya türüne göre egress hacmi, P95 indirme gecikmesi ve ortalama nesne boyutu.
Bu deseni hızlı prototiplemek isterseniz, Koder.ai (koder.ai) sohbetten tam uygulamalar üretecek şekilde tasarlandı ve burada kullanılan yaygın yığınla (React, Go, Postgres) uyumludur. Şema, uç noktalar ve arka plan temizlik işlerini tekrardan yazmadan hızla yinelemenizi sağlar.
Bundan sonra yalnızca bir cümlede açıklayabileceğiniz şeyleri ekleyin: "eski sürümleri 30 gün tutuyoruz" veya "her çalışma alanına 10 GB veriyoruz." Gerçek kullanım sizi zorlayana kadar basit tutun.
Postgres'i sorgulamanız ve güvenle saklamanız gereken meta veriler için kullanın (sahibi, izinler, durum, checksum, gösterici). Baytları ise object storage'a koyun ki indirmeler ve büyük transferler veritabanı bağlantılarını tüketmesin veya yedekleri şişirmesin.
Veritabanını bir dosya sunucusu olarak iki iş yapmaya zorlar. Bu, tablo boyutunu artırır, yedekleri ve geri yüklemeleri yavaşlatır, replika yükü ekler ve çok sayıda kullanıcı aynı anda indirirken performansı öngörülemez hale getirebilir.
Evet. Uygulamada tek bir stabil file_id tutun, meta veriyi Postgres'te saklayın ve baytları bucket ve object_key ile işaretlenmiş object storage'a koyun. API'niz erişimi yetkilendirmeli ve dosya baytlarını proxy etmek yerine kısa ömürlü yükleme/indirme izinleri vermelidir.
Önce bir pending satırı oluşturun, benzersiz bir object_key üretin, sonra istemcinin kısa ömürlü bir izinle doğrudan depolamaya yüklemesine izin verin. Yüklemeden sonra istemci finalize uç noktasını çağırarak sunucunun boyutu ve checksum'u (kullanıyorsanız) doğrulamasını sağlayın; doğrulama başarılıysa satırı uploaded olarak işaretleyin.
Çünkü gerçek yüklemeler başarısız olur ve tekrar dener. Bir state alanı pending (beklemede), uploaded (yüklendi), failed (başarısız) ve deleted (silindi) gibi durumları ayırt etmenizi sağlar; bu sayede UI, temizlik işleri ve destek araçları doğru davranır.
original_filename'i yalnızca gösterim için kullanın. Çakışmaları, garip karakterleri ve güvenlik sürprizlerini önlemek için depolama anahtarı olarak benzersiz bir anahtar (genellikle UUID tabanlı bir yol) üretin. Arayüzde orijinal adı göstermeye devam edebilirsiniz, ama depolama yollarını temiz ve öngörülebilir tutun.
Uygulamanızda /files/{file_id} gibi stabil bir URL kullanın ve burada erişim kontrolünü yapın. Postgres'te erişimi doğruladıktan sonra istemciye bir yönlendirme veya kısa ömürlü imzalı indirme izni verin; böylece istemci dosyayı doğrudan object storage'dan indirir ve API'niz cazip yolun dışında kalır.
Çıkış (egress) ve tekrar eden indirmeler genellikle maliyete hükmeder, ham disk üzerinde duran baytlar değil. Dosya boyutu limitleri ve kotaları belirleyin, saklama/kalan süre kuralları uygulayın, mantıksal olarak dedupe edin ve faturanın artmadan önce uyarı verebilmek için kullanım sayacı tutun.
İzinleri ve görünürlüğü Postgres'te kaynak olarak tutun ve depolamayı varsayılan olarak özel yapın. Yükleme öncesinde ve sonrasında tip ve boyut doğrulaması yapın, uçtan uca HTTPS kullanın, at-rest şifreleme sağlayın ve sorunları araştırmak için uploaded_by, ip, user_agent ve last_accessed_at gibi denetim alanları ekleyin.
Tek bir meta veritabanı tablosu, doğrudan-depolamaya yükleme akışı ve bir indirme gate uç noktasından başlayın; sonra yetim nesneler ve yumuşak silinen satırlar için temizlik işleri ekleyin. React/Go/Postgres yığını üzerinde hızlı prototip istiyorsanız, Koder.ai (koder.ai) uçtan uca uç noktalar, şema ve arka plan görevlerini sohbetten üretebilir ve tekrarlamayı azaltır.