MongoDB’de dosya saklama meselesine gelince, çoğu geliştirici ve sistem yöneticisi ilk başta aynı soruyla karşılaşır: “16 MB sınırını aşan dosyaları ne yapacağız?” İşte tam bu noktada GridFS devreye giriyor. GridFS, MongoDB’nin büyük dosyaları parçalara bölerek saklayan ve yöneten yerleşik bir spesifikasyonu. Klasik dosya sistemi ile veritabanının avantajlarını bir araya getiriyor ve büyük medya dosyaları, log arşivleri, yedek dosyalar gibi senaryolarda gerçekten işe yarıyor.
GridFS Nedir ve Nasıl Çalışır
GridFS aslında iki koleksiyondan oluşan bir yapı. fs.files koleksiyonu dosyaların metadata bilgilerini tutuyor: dosya adı, boyutu, yükleme tarihi, içerik tipi ve isteğe bağlı özel alanlar. fs.chunks koleksiyonu ise dosyanın gerçek içeriğini 255 KB’lık parçalar halinde saklıyor. Her chunk, files koleksiyonundaki belgeyle files_id alanı üzerinden ilişkilendiriliyor.
Bu yapı sayesinde çok büyük bir video dosyasını bile MongoDB’ye yükleyip okuyabiliyorsunuz. Okuma sırasında MongoDB ilgili chunk’ları sırayla getiriyor ve siz bunu tek bir akış olarak alıyorsunuz. Streaming desteği de bu yüzden oldukça güçlü.
Peki neden sadece dosya sistemi kullanmıyoruz? Çünkü GridFS ile dosyalarınız MongoDB’nin replikasyon, sharding ve yetkilendirme altyapısından otomatik olarak yararlanıyor. Ayrıca metadata sorgulama konusunda çok daha esnek oluyorsunuz. “2023’te yüklenen, boyutu 100 MB’ın üzerinde olan tüm PDF dosyaları” gibi sorgular GridFS ile çok kolay.
Ortam Kurulumu
Önce bir test ortamı hazırlayalım. Mevcut MongoDB kurulumunuzda GridFS için ayrı bir yapılandırmaya gerek yok, doğrudan kullanmaya başlayabilirsiniz. Ama önce mongofiles aracının sisteminizde olduğundan emin olun.
# MongoDB araçlarını kontrol et
which mongofiles
mongofiles --version
# Ubuntu/Debian'da eksikse
sudo apt-get install mongodb-database-tools
# RHEL/CentOS'ta
sudo yum install mongodb-database-tools
# Test için örnek büyük dosya oluştur (500 MB)
dd if=/dev/urandom of=/tmp/test_large_file.bin bs=1M count=500
Şimdi bir Python ortamı kuralım çünkü gerçek dünyada GridFS’i çoğunlukla uygulama kodu üzerinden kullanıyorsunuz:
# Python bağımlılıklarını kur
pip3 install pymongo gridfs
# Motor (async) için
pip3 install motor
# Node.js kullanıyorsanız
npm install mongodb
mongofiles ile Temel İşlemler
mongofiles komutu GridFS için en hızlı komut satırı aracı. Özellikle yedekleme scriptleri yazarken veya hızlı test yaparken çok işe yarıyor.
# Dosya yükleme
mongofiles --db=medya_arsivi put /var/backup/sunucu_yedek_2024.tar.gz
# Kimlik doğrulama ile yükleme
mongofiles
--host=mongodb01.sirket.local:27017
--username=gridfs_user
--password=gizli_sifre
--authenticationDatabase=admin
--db=medya_arsivi
put /srv/videos/tanitim_videosu.mp4
# Tüm dosyaları listele
mongofiles --db=medya_arsivi list
# Belirli bir dosyayı indir
mongofiles --db=medya_arsivi get tanitim_videosu.mp4
# Farklı isimle indir
mongofiles --db=medya_arsivi get_id ObjectId("648a1f2b3c4d5e6f7a8b9c0d")
# Dosya silme
mongofiles --db=medya_arsivi delete tanitim_videosu.mp4
# Prefix ile arama
mongofiles --db=medya_arsivi search tanitim
Dikkat etmeniz gereken bir nokta var: put komutu aynı isimde dosya varsa yeni bir versiyon ekliyor, eskisini silmiyor. Üst üste yüklemelerde dosya boyutu hızla büyüyebilir.
Python ile GridFS Kullanımı
Gerçek dünya uygulamalarında doğrudan pymongo ve gridfs modüllerini kullanmak çok daha fazla kontrol sağlıyor.
#!/usr/bin/env python3
# gridfs_operations.py
from pymongo import MongoClient
import gridfs
from datetime import datetime
import os
import hashlib
def get_file_hash(filepath):
"""Dosyanın MD5 hash'ini hesapla"""
md5 = hashlib.md5()
with open(filepath, 'rb') as f:
while chunk := f.read(8192):
md5.update(chunk)
return md5.hexdigest()
def upload_file(db, filepath, metadata=None):
"""Dosyayı GridFS'e yükle"""
fs = gridfs.GridFS(db)
filename = os.path.basename(filepath)
file_hash = get_file_hash(filepath)
# Aynı hash'te dosya zaten var mı kontrol et
existing = db.fs.files.find_one({'md5_hash': file_hash})
if existing:
print(f"Bu dosya zaten mevcut: {existing['_id']}")
return existing['_id']
extra_meta = metadata or {}
extra_meta['md5_hash'] = file_hash
extra_meta['upload_date'] = datetime.utcnow()
extra_meta['original_path'] = filepath
with open(filepath, 'rb') as f:
file_id = fs.put(
f,
filename=filename,
content_type=get_content_type(filename),
**extra_meta
)
print(f"Yüklendi: {filename} -> ID: {file_id}")
return file_id
def get_content_type(filename):
"""Dosya uzantısına göre content type belirle"""
ext_map = {
'.mp4': 'video/mp4',
'.mp3': 'audio/mpeg',
'.pdf': 'application/pdf',
'.tar.gz': 'application/gzip',
'.zip': 'application/zip',
'.jpg': 'image/jpeg',
'.png': 'image/png',
}
for ext, ctype in ext_map.items():
if filename.lower().endswith(ext):
return ctype
return 'application/octet-stream'
def download_file(db, file_id, output_dir='/tmp'):
"""GridFS'ten dosya indir"""
fs = gridfs.GridFS(db)
grid_out = fs.get(file_id)
output_path = os.path.join(output_dir, grid_out.filename)
with open(output_path, 'wb') as f:
# Büyük dosyalar için chunk chunk oku
while True:
chunk = grid_out.read(1024 * 1024) # 1 MB chunk
if not chunk:
break
f.write(chunk)
print(f"İndirildi: {output_path}")
return output_path
def list_files_by_type(db, content_type, min_size_mb=0):
"""Belirli tipte dosyaları listele"""
min_size_bytes = min_size_mb * 1024 * 1024
query = {
'contentType': content_type,
'length': {'$gt': min_size_bytes}
}
files = db.fs.files.find(query).sort('uploadDate', -1)
for f in files:
size_mb = f['length'] / (1024 * 1024)
print(f"Ad: {f['filename']} | Boyut: {size_mb:.2f} MB | Tarih: {f['uploadDate']}")
if __name__ == '__main__':
client = MongoClient('mongodb://gridfs_user:sifre@localhost:27017/medya_arsivi?authSource=admin')
db = client['medya_arsivi']
# Test yüklemesi
file_id = upload_file(db, '/tmp/test_video.mp4', {
'kategori': 'egitim',
'etiketler': ['python', 'mongodb'],
'yuklendi_tarafindan': 'admin'
})
# Dosyaları listele
list_files_by_type(db, 'video/mp4', min_size_mb=10)
Streaming ile Video Sunma
GridFS’in en güçlü kullanım alanlarından biri medya streaming. Flask ile basit bir video sunucu yazalım:
#!/usr/bin/env python3
# video_server.py
from flask import Flask, Response, request, abort
from pymongo import MongoClient
import gridfs
from bson.objectid import ObjectId
app = Flask(__name__)
client = MongoClient('mongodb://localhost:27017/')
db = client['medya_arsivi']
fs = gridfs.GridFS(db)
def generate_stream(grid_out, start_byte=0, chunk_size=1024*1024):
"""Verilen offset'ten itibaren stream oluştur"""
grid_out.seek(start_byte)
while True:
data = grid_out.read(chunk_size)
if not data:
break
yield data
@app.route('/video/<file_id>')
def stream_video(file_id):
try:
grid_out = fs.get(ObjectId(file_id))
except Exception:
abort(404)
file_size = grid_out.length
range_header = request.headers.get('Range', None)
if range_header:
# Range request'i parse et (HTTP Range: bytes=0-1023)
byte_range = range_header.replace('bytes=', '').split('-')
start = int(byte_range[0])
end = int(byte_range[1]) if byte_range[1] else file_size - 1
content_length = end - start + 1
response = Response(
generate_stream(grid_out, start),
206, # Partial Content
mimetype=grid_out.content_type,
direct_passthrough=True
)
response.headers['Content-Range'] = f'bytes {start}-{end}/{file_size}'
response.headers['Content-Length'] = content_length
response.headers['Accept-Ranges'] = 'bytes'
else:
response = Response(
generate_stream(grid_out),
200,
mimetype=grid_out.content_type,
direct_passthrough=True
)
response.headers['Content-Length'] = file_size
return response
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000, debug=False)
Bu streaming yapısı sayesinde tarayıcılar videoyu baştan sona indirmek zorunda kalmıyor. Range request desteği ile kullanıcı videonun herhangi bir noktasına atlayabiliyor.
İndeks Yönetimi ve Performans
GridFS’in performansı doğrudan indeks yapılandırmasına bağlı. Varsayılan olarak fs.files ve fs.chunks koleksiyonlarında temel indeksler oluşturuluyor ama özel sorgularınız için ek indeksler eklemeniz gerekiyor.
# MongoDB shell'de indeks durumunu kontrol et
mongosh --eval "
use medya_arsivi;
// Mevcut indeksleri görüntüle
db.fs.files.getIndexes();
db.fs.chunks.getIndexes();
// Zorunlu olan chunks indeksi (files_id + n)
db.fs.chunks.createIndex(
{ files_id: 1, n: 1 },
{ unique: true }
);
// Dosya arama için ek indeksler
db.fs.files.createIndex({ filename: 1 });
db.fs.files.createIndex({ uploadDate: -1 });
db.fs.files.createIndex({ 'metadata.kategori': 1, uploadDate: -1 });
db.fs.files.createIndex({ 'metadata.etiketler': 1 });
// Content type ile boyut kombinasyonu
db.fs.files.createIndex({ contentType: 1, length: -1 });
// İndeks kullanımını analiz et
db.fs.files.find({ contentType: 'video/mp4' }).explain('executionStats');
"
Büyük dosya koleksiyonlarında dikkat etmeniz gereken bir performans noktası daha var: fs.chunks koleksiyonu zamanla devasa boyutlara ulaşabiliyor. Şöyle bir senaryo düşünün: 1000 adet 100 MB’lık dosya yüklediniz. Bu durumda chunks koleksiyonunuzda yaklaşık 400.000 belge oluyor ve her belge 255 KB. Sorgu planlarınızı düzenli olarak gözden geçirmek önemli.
Yedekleme ve Arşivleme Senaryosu
GridFS’in gerçekten parladığı alanlardan biri sistem yedekleri. Diyelim ki bir e-ticaret platformunuzda günlük sipariş raporlarını PDF olarak arşivliyorsunuz. Aşağıdaki script bunu otomatik yapıyor:
#!/bin/bash
# gridfs_backup_archiver.sh
# Sunucu yedeklerini GridFS'e arşivle
MONGO_HOST="mongodb01.sirket.local:27017"
MONGO_DB="yedek_arsivi"
MONGO_USER="backup_user"
MONGO_PASS="${MONGO_BACKUP_PASS}" # Crontab'da env var olarak set et
BACKUP_DIR="/var/backups/gunluk"
LOG_FILE="/var/log/gridfs_backup.log"
RETENTION_DAYS=90
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}
archive_to_gridfs() {
local filepath="$1"
local kategori="$2"
local filename=$(basename "$filepath")
local filesize=$(stat -c%s "$filepath")
log "Arşivleniyor: $filename ($((filesize/1024/1024)) MB)"
# mongofiles ile yükle ve çıktıyı kontrol et
output=$(mongofiles
--host="$MONGO_HOST"
--username="$MONGO_USER"
--password="$MONGO_PASS"
--authenticationDatabase=admin
--db="$MONGO_DB"
put "$filepath" 2>&1)
if [ $? -eq 0 ]; then
log "Basarili: $filename"
# Yerel dosyayı sil (disk tasarrufu)
rm -f "$filepath"
return 0
else
log "HATA: $filename - $output"
return 1
fi
}
# 90 günden eski dosyaları GridFS'ten temizle
cleanup_old_archives() {
log "Eski arsivler temizleniyor (${RETENTION_DAYS} gundan eski)..."
mongosh
--host "$MONGO_HOST"
--username "$MONGO_USER"
--password "$MONGO_PASS"
--authenticationDatabase admin
"$MONGO_DB"
--eval "
var cutoffDate = new Date();
cutoffDate.setDate(cutoffDate.getDate() - ${RETENTION_DAYS});
var oldFiles = db.fs.files.find({
uploadDate: { $lt: cutoffDate }
});
oldFiles.forEach(function(file) {
db.fs.chunks.deleteMany({ files_id: file._id });
db.fs.files.deleteOne({ _id: file._id });
print('Silindi: ' + file.filename);
});
" 2>&1 | tee -a "$LOG_FILE"
}
# Ana işlem
log "=== GridFS Yedek Arsivleme Basladi ==="
for backup_file in "$BACKUP_DIR"/*.tar.gz; do
[ -f "$backup_file" ] || continue
archive_to_gridfs "$backup_file" "sunucu_yedegi"
done
cleanup_old_archives
log "=== Arsivleme Tamamlandi ==="
# Cron job: Her gece 02:00'de calistir
# 0 2 * * * /usr/local/bin/gridfs_backup_archiver.sh
Sharding ile GridFS Ölçeklendirme
Dosya koleksiyonunuz büyüyünce sharding kaçınılmaz oluyor. GridFS ile sharding yaparken dikkat edilmesi gereken önemli noktalar var:
# MongoDB sharding ortamında GridFS yapılandırması
mongosh --host mongos01:27017 --eval "
// Veritabanını sharding için etkinleştir
sh.enableSharding('medya_arsivi');
// fs.files koleksiyonunu shard et
// filename + uploadDate bileşik key kullan
sh.shardCollection(
'medya_arsivi.fs.files',
{ filename: 1, uploadDate: 1 }
);
// fs.chunks koleksiyonunu shard et
// files_id üzerinden shard et (büyük dosyaların chunk'ları aynı shard'da kalsın)
sh.shardCollection(
'medya_arsivi.fs.chunks',
{ files_id: 1, n: 1 }
);
// Shard dağılımını kontrol et
sh.status();
// Balancer durumunu kontrol et
sh.isBalancerRunning();
"
Önemli Not: fs.chunks koleksiyonunu shard ederken files_id bazlı bir key kullanmak kritik öneme sahip. Eğer başka bir key seçerseniz aynı dosyanın chunk’ları farklı shard’lara dağılabilir ve okuma performansı ciddi ölçüde düşer.
Monitoring ve Disk Kullanım Takibi
GridFS’in sağlıklı çalışıp çalışmadığını düzenli kontrol etmeniz gerekiyor. İşte işe yarar bir monitoring scripti:
#!/usr/bin/env python3
# gridfs_monitor.py
from pymongo import MongoClient
from datetime import datetime, timedelta
import json
def get_gridfs_stats(db_name='medya_arsivi'):
client = MongoClient('mongodb://localhost:27017/')
db = client[db_name]
stats = {}
# Toplam dosya sayisi ve boyutu
pipeline = [
{
'$group': {
'_id': None,
'toplam_dosya': {'$sum': 1},
'toplam_boyut': {'$sum': '$length'},
'ortalama_boyut': {'$avg': '$length'},
'max_boyut': {'$max': '$length'}
}
}
]
result = list(db.fs.files.aggregate(pipeline))
if result:
r = result[0]
stats['toplam_dosya'] = r['toplam_dosya']
stats['toplam_boyut_gb'] = round(r['toplam_boyut'] / (1024**3), 2)
stats['ortalama_boyut_mb'] = round(r['ortalama_boyut'] / (1024**2), 2)
stats['max_boyut_mb'] = round(r['max_boyut'] / (1024**2), 2)
# Content type dagılımı
type_pipeline = [
{'$group': {'_id': '$contentType', 'sayi': {'$sum': 1}, 'boyut': {'$sum': '$length'}}},
{'$sort': {'boyut': -1}}
]
stats['tip_dagilimi'] = []
for doc in db.fs.files.aggregate(type_pipeline):
stats['tip_dagilimi'].append({
'tip': doc['_id'],
'sayi': doc['sayi'],
'boyut_mb': round(doc['boyut'] / (1024**2), 2)
})
# Son 24 saatte yuklenen dosyalar
son_24_saat = datetime.utcnow() - timedelta(hours=24)
son_yukleme = db.fs.files.count_documents({
'uploadDate': {'$gt': son_24_saat}
})
stats['son_24_saat_yukleme'] = son_yukleme
# Orphan chunk kontrolu (files_id'si olmayan chunk'lar)
all_file_ids = set(str(f['_id']) for f in db.fs.files.find({}, {'_id': 1}))
orphan_count = 0
for chunk in db.fs.chunks.find({}, {'files_id': 1}):
if str(chunk['files_id']) not in all_file_ids:
orphan_count += 1
stats['orphan_chunk_sayisi'] = orphan_count
# Koleksiyon istatistikleri
files_stats = db.command('collstats', 'fs.files')
chunks_stats = db.command('collstats', 'fs.chunks')
stats['fs_files_boyut_mb'] = round(files_stats['size'] / (1024**2), 2)
stats['fs_chunks_boyut_mb'] = round(chunks_stats['size'] / (1024**2), 2)
return stats
if __name__ == '__main__':
stats = get_gridfs_stats()
print(json.dumps(stats, indent=2, ensure_ascii=False))
# Kritik uyarilar
if stats.get('orphan_chunk_sayisi', 0) > 0:
print(f"nUYARI: {stats['orphan_chunk_sayisi']} adet orphan chunk tespit edildi!")
if stats.get('toplam_boyut_gb', 0) > 100:
print(f"nBILGI: GridFS boyutu 100 GB'i asti. Arşivleme politikanizi gözden gecirin.")
Sık Karşılaşılan Sorunlar ve Çözümleri
Orphan Chunk Problemi: Yükleme işlemi yarıda kesildiğinde veya uygulama hatası oluştuğunda fs.chunks‘ta sahipsiz parçalar kalabilir. Bu hem disk alanı israfına yol açar hem de sorgu performansını etkiler. Monitoring scriptinizdeki orphan kontrolünü düzenli çalıştırın ve bulduğunuzda temizleyin.
Yavaş Yükleme Performansı: Çok sayıda küçük dosyayı tek tek yüklemek yerine mümkünse toplu işlem yapın. Ayrıca writeConcern değerini workload’ınıza göre ayarlayın. Yedek arşivleri için w:1 yeterli olabilirken kritik belge arşivleri için w:majority tercih edin.
Büyük Chunk Sayısı: Varsayılan 255 KB chunk boyutu çok sayıda küçük dosya için gereksiz overhead yaratabilir. Büyük video dosyaları için chunk boyutunu artırmak okuma performansını iyileştirebilir:
# Özel bucket ile chunk boyutunu ayarla
import gridfs
# 1 MB chunk boyutu ile yeni bir bucket
fs = gridfs.GridFS(db, collection='large_media')
# Not: Chunk boyutu put() çağrısında belirtilmeli
file_id = fs.put(
file_data,
filename='buyuk_video.mp4',
chunk_size=1024*1024 # 1 MB
)
GridFS mi, Dosya Sistemi mi? Bu sorunun net bir cevabı yok. Eğer dosyalarınıza MongoDB’nin replikasyon ve sharding özelliklerini uygulamak istiyorsanız, metadata ile içeriği birlikte tutmanız gerekiyorsa ve zaten bir MongoDB altyapınız varsa GridFS mantıklı. Ama saf dosya sunucu ihtiyacı için NFS veya bir object storage (MinIO, S3) genellikle daha uygun. Hibrit yaklaşımlar da işe yarıyor: metadata MongoDB’de, dosya içeriği S3’te, iki taraf arasında referans.
Sonuç
GridFS, MongoDB ekosistemi içinde kalarak büyük dosyaları yönetmenin pratik ve güvenilir bir yolu. Özellikle mevcut MongoDB altyapınız varsa ve ayrı bir object storage sistemi kurmak istemiyorsanız güçlü bir seçenek. Medya arşivlemeden log saklama sistemlerine, yedek arşivlerinden belge yönetimi uygulamalarına kadar geniş bir kullanım yelpazesi sunuyor.
Önemli olan nokta şu: GridFS’i kör bir şekilde her büyük dosya sorununa uygulamak yerine, kullanım senaryonuzu değerlendirin. Dosyalarınıza nasıl erişiyorsunuz, ne sıklıkla okuyorsunuz, metadata sorgulama ne kadar kritik, replikasyon gerekli mi? Bu sorulara verdiğiniz yanıtlar GridFS’in sizin için doğru çözüm olup olmadığını belirleyecek.
Monitoring ve temizleme scriptlerini baştan kurun, orphan chunk sorununu ihmal etmeyin ve indekslerinizi workload’ınıza göre optimize edin. Bu üç maddeye dikkat ederseniz GridFS size uzun süre sorunsuz hizmet verecek.