İçeriğe geç
wedevit

21 Ekim 2026 · 9 dk okuma · yazılım

İlhan Buğra Aslan

Müşteri Excel dosyasını yüklüyor, aktarım yarıda kalıyor: toplu veri aktarımı nasıl tasarlanır?


Toplu veri aktarımı tek bir işlem değil, birbirinden ayrılması gereken üç adımdır: dosyayı okuyup doğrulayan ve hiçbir şey yazmayan bir prova, sonucu tekrar çalıştırıldığında değiştirmeyen bir yazma işi ve ne olduğunu satır satır gösteren bir rapor. Yarıda kalan aktarımların neredeyse tamamı bu üçünü tek bir HTTP isteğinin içinde, canlı tablolara yaparak çalışıyor. İkinci bilmeniz gereken şey de şu: kullanıcının yüklediği dosya veri değil, kullanıcının sizin veri modelinizi nasıl anladığının bir fotoğrafı. Aktarım özelliğinin asıl işi veriyi taşımak değil, bu iki anlayış arasındaki farkı kullanıcıya anlayacağı dilde göstermek.

Tek istekte yapılan aktarım neden yarıda kalır

Senaryo her yerde aynı. Kullanıcı 18.000 satırlık dosyayı yükler, tarayıcıda çember döner, otuz saniye sonra ağ geçidi zaman aşımı hatası düşer. Veritabanında kaç satır var? Kimse bilmiyor. Kullanıcı ne yapar? Dosyayı bir daha yükler.

Doğru akış daha sıkıcı. Yükleme isteği yalnızca dosyayı nesne depolamaya koyar, bir aktarım kaydı açar ve işi kuyruğa atar. HTTP tarafında bunun karşılığı RFC 9110'daki 202 Accepted: istek işlenmek üzere kabul edildi ama işlem tamamlanmadı, cevap da ya durumu taşır ya da durumu izleyecek bir adres verir. Kullanıcı o adresten ilerlemeyi görür, isterse sayfayı kapatır. Arka plan işleri ve kuyruk mimarisi yazısındaki yeniden deneme ve zehirli mesaj kuralları burada birebir geçerli.

Okuma tarafında da bir kural var: dosyayı belleğe tamamen açmayın. Akışla satır satır okuyan bir ayrıştırıcı kullanın. 50 MB'lık bir xlsx dosyası bellekte rahatlıkla bir gigabaytı bulur ve tek bir kullanıcının yüklemesi tüm sunucuyu düşürür.

Dosya size ulaşmadan önce Excel veriyi değiştirmiş olabilir

Aktarım hatalarının büyük kısmı sizin kodunuzda değil, kullanıcının dosyayı kaydettiği anda oluşuyor. Bunun bilimsel literatürde ölçülmüş bir örneği var. Genetik makalelerin ek dosyalarını tarayan 2021 tarihli PLOS Computational Biology çalışması, 2014-2020 arasında Excel gen listesi içeren yayınların yüzde 30,9'unda gen adı hatası buldu. Sebep basit: Excel, SEPT1 ve MARCH1 gibi sembolleri tarihe çeviriyordu. İş o noktaya vardı ki gen adlandırma komitesi yazılıma uymak için sembolleri değiştirdi, SEPT1 artık SEPTIN1.

Sizin dosyalarınızda bunun karşılıkları şunlar:

  • Baştaki sıfır kaybolur. Sıfırla başlayan vergi numarası, posta kodu, banka hesap numarası ve barkod, hücre sayı olarak yorumlandığı anda baştaki sıfırını bırakır.
  • Uzun numaralar bozulur. Excel'in kendi teknik sınırları sayı hassasiyetini 15 hane olarak veriyor. 16 haneli bir kart veya referans numarasının son hanesi sıfır olur, üstelik hiçbir uyarı çıkmaz.
  • Tarihler seri numarasıdır. xlsx dosyasının içinde tarih yoktur, sayı vardır. Excel 1900 tarih sistemini kullanır ve Microsoft'un kendi belgesinde açıkladığı gibi 1900'ü yanlışlıkla artık yıl sayar; bu Lotus 1-2-3 uyumluluğu için bilerek korunan bir davranış. Üstüne çalışma kitabı seviyesinde 1904 tarih sistemi seçeneği de var. Seri numarasını hangi epoch ile çözdüğünüzü varsaymayın, dosyadan okuyun.
  • Ayırıcı ve ondalık işareti yerele bağlıdır. Microsoft'un metin dosyası aktarma belgesi listeyi ayıran karakterin Windows bölge ayarından geldiğini söylüyor. Türkçe Windows'ta ondalık ayracı virgül olduğu için liste ayracı noktalı virgüle döner. Yani kullanıcının "CSV" dediği dosyada hem alanlar noktalı virgülle ayrılır hem de 1.250,75 yazar.
  • Kodlama tahmine kalır. BOM taşımayan bir UTF-8 CSV dosyasını çift tıklayıp açan Excel onu sistem kod sayfasıyla yorumlar ve Türkçe karakterler bozulur. Excel 2016 ile gelen "CSV UTF-8" kaydetme seçeneği bunu çözer ama kullanıcıların çoğu o satırı seçmez.

Pratik sonuç: kullanıcıdan "doğru kaydetmesini" beklemeyin. Hem noktalı virgülü hem virgülü tanıyın, kodlamayı tespit edip BOM'u atın, sayısal görünen kimlik alanlarını metin olarak okuyun ve tarih kolonlarında beklediğiniz biçimi şablonda açıkça yazın. İndirilebilir bir örnek şablon, yazacağınız her doğrulama kuralından daha çok hata önler.

RFC 4180 CSV'yi tanımlar: satırlar CRLF ile ayrılır, virgül, tırnak veya satır sonu içeren alanlar çift tırnağa alınır, alanın içindeki çift tırnak kendisini tekrarlayarak kaçırılır. Bilmeniz gereken, bu belgenin bir standart değil bilgilendirici bir doküman olduğu ve gerçek hayattaki dosyaların yarısının ona uymadığı. Kendi ayrıştırıcınızı yazmayın, olgun bir kütüphane kullanın.

Önce prova, sonra yazma

Aktarım ekranında kullanıcının görmesi gereken ilk şey sonuç değil, özet. Dosya okunur, bütün kurallar çalıştırılır, hiçbir satır yazılmaz ve ekrana şu çıkar: 1.418 satır okundu, 1.102 yeni kayıt oluşacak, 281 kayıt güncellenecek, 35 satır reddedildi. Altında da reddedilen satırların indirilebilir listesi.

Bu listenin tasarımı özelliğin yaşayıp yaşamayacağını belirliyor. Üç kural:

  1. Satır numarası kullanıcının dosyasındaki numara olmalı. Başlık satırını saydığınızda veya boş satırları atladığınızda kayan numara, kullanıcıyı yanlış satıra bakmaya gönderir. Kolon adını da yazın.
  2. Mesaj kuralın kendisini söylemeli. "Geçersiz değer" bir hata mesajı değil. "Vergi numarası 10 veya 11 hane olmalı, 9 hane gönderildi" bir hata mesajı. Hata mesajı yazmanın kuralları burada da aynı.
  3. Çıktı düzeltilip yeniden yüklenebilir olmalı. En iyi hata raporu, kullanıcının açıp düzelteceği ve doğrudan geri yükleyeceği, sadece hatalı satırları içeren bir dosyadır.

Doğrulamayı iki katmanda yapın. Satır seviyesi alanın kendisine bakar: zorunlu mu, tip doğru mu, uzunluk ve biçim tutuyor mu. Küme seviyesi dosyanın bütününe bakar: aynı anahtar dosya içinde iki kez geçiyor mu, referans verilen müşteri veya ürün sistemde var mı, toplamlar kullanıcının beklediği rakamı tutuyor mu. İkinci katman atlandığında dosya satır satır geçerli olur ama bütün olarak saçmalar.

Hep ya da hiç mi, satır satır mı

Bu bir teknik tercih değil, iş kararı. Muhasebe fişi, stok sayımı ve fiyat listesi gibi bütünlüğü anlamlı olan veride yarım aktarım en kötü sonuçtur: tek işlem, hata varsa hiçbir şey yazılmaz. Müşteri listesi veya ürün açıklaması gibi satırların birbirinden bağımsız olduğu veride ise 35 hatalı satır yüzünden 1.383 doğru satırı reddetmek kullanıcıyı boş yere bekletir.

Kararı kullanıcıya sormayın, veri tipine göre siz verin ve ekranda yazın. Yazmazsanız kullanıcı kendi varsayımını kurar ve o varsayım genelde yanlış olur.

Satır bazlı modda da yazmayı tek tek yapmayın. Parçalar halinde işleyin, her parçayı kendi işleminde kapatın ve ilerlemeyi aktarım kaydına yazın. İş yarıda düşerse nerede kaldığını bilerek devam edebilmelisiniz.

Aynı dosya ikinci kez yüklenirse ne olacak

Olacak. Kullanıcı sonucu göremediği için, tarayıcı donduğu için ya da iki kişi aynı dosyayı paylaştığı için. Cevabı önceden verin.

Üç mekanizma yeter. Birincisi, her satırın bir iş anahtarı olmalı ve yazma işlemi ekleme değil varsa güncelleme mantığıyla çalışmalı. Anahtarı bulmak sizin değil müşterinin işi: dosyada bir dış kimlik kolonu isteyin, boşsa aktarımı reddedin. Anahtarsız aktarım, mükerrer kayıt üretmenin en hızlı yoludur.

İkincisi, yüklenen dosyanın özetini (hash) saklayın. Aynı özet son 24 saat içinde aynı hesaptan geldiyse kullanıcıya sorun: "Bu dosyayı bugün 14:20'de yüklediniz, 1.418 satır işlendi. Tekrar çalıştırmak istiyor musunuz?"

Üçüncüsü, istek seviyesinde idempotency. Aktarımı API üzerinden başlatan entegrasyonlar için Idempotency-Key başlığı hâlâ taslak durumda ama fiili standart haline geldi. Stripe'ın yaklaşımı iyi bir referans: anahtar istemci tarafından üretilir, ilk isteğin sonucu saklanır, aynı anahtar farklı parametrelerle gelirse hata döner ve anahtarlar 24 saat sonra temizlenebilir.

Veritabanına yazarken dikkat edilecek iki şey

Birincisi, ham veriyi önce bir hazırlık (staging) tablosuna alın. Bütün kolonlar metin tipinde, ek olarak satır numarası ve aktarım kimliği. Doğrulamayı ve dönüşümü orada SQL ile yapmak, satır satır uygulama kodu çalıştırmaktan hem hızlı hem denetlenebilirdir; hatalı satırları da sorguyla raporlarsınız. PostgreSQL'in veri yükleme belgesi bu yolu doğruluyor: çok sayıda satırı COPY ile yüklemek, hazırlanmış ifadelerle toplu INSERT yapmaktan bile neredeyse her zaman hızlıdır. Aynı belge yükleme sonrası ANALYZE çalıştırmayı da hatırlatıyor, çünkü planlayıcının istatistikleri bir anda eskir.

İkincisi ve daha sık unutulanı: aktarım, satır başına çalışan yan etkileri tetikler. 1.400 kayıt güncellediğinizde 1.400 "kaydınız değişti" e-postası, 1.400 webhook çağrısı ve 1.400 arama indeksi güncellemesi üretebilirsiniz. Bir müşteriye tek seferde binlerce bildirim gitmesi, aktarımın başarısız olmasından daha kötü bir olaydır. Aktarım bağlamında bildirimleri ya kapatın ya da toplu özet olarak gönderin; bildirim sistemi tasarımı yazısındaki gruplama mantığı tam olarak bunun için var.

Yüklenen dosya da indirilen dosya da güvenlik konusu

Yükleme tarafında xlsx aslında içinde XML bulunan bir zip arşividir. Yani zip bombası ve XML varlık genişletme saldırıları bu kapıdan girer. Bu teorik bir risk değil: openpyxl belgeleri açıkça uyarıyor, kütüphane varsayılan olarak quadratic blowup ve billion laughs saldırılarına karşı koruma sağlamıyor, korumak için defusedxml kurmanız gerekiyor. Buna dosya boyutu ve satır sayısı sınırı, uzantıya değil içeriğe bakan tip kontrolü ve ayrıştırmayı sınırlı yetkili bir işçide çalıştırmayı ekleyin. Dosya yükleme özelliğinin genel kuralları burada da geçerli, üstüne bir de yüklenen dosyanın kendisi müşteri verisi taşıdığı için saklama süresi belirlemek gerekiyor.

İndirme tarafında ise ters yönde bir açık var: CSV enjeksiyonu. OWASP'ın tanımına göre =, +, -, @ karakterleriyle ve sekme, satır başı, satır sonu karakterleriyle başlayan hücreler elektronik tablo yazılımında formül olarak işlenir. Kullanıcının yazdığı bir alanı dışa aktardığınızda o formül başka bir kullanıcının bilgisayarında çalışır. Aynı sayfanın dürüst uyarısını da aklınızda tutun: bütün tablo yazılımları için güvenli, tek bir evrensel temizleme yöntemi yok. Dışa aktarımda bu karakterleri kaçırın, indirilen dosyayı Content-Disposition: attachment ile verin ve mümkünse xlsx üretin.

Aktarım tek seferlik bir iş değil, bir ürün özelliğidir

Çoğu ekip aktarımı "veriyi bir kere içeri alalım" diye yazıyor, sonra o ekran yıllarca çalışıyor. Fiyat listesi her ay, stok her hafta, yeni şube verisi her çeyrekte aynı yerden giriyor. Bunu baştan kabul ederseniz üç şeyi en baştan yaparsınız.

Her aktarımın bir kaydı olur: kim, ne zaman, hangi dosya, kaç satır okundu, kaç yazıldı, kaç reddedildi, ne kadar sürdü. Her yazılan satır o aktarım kimliğini taşır. Ve geri alma mümkün olur, çünkü "dün yüklenen 1.102 kaydı geri al" sorusu er geç gelir ve aktarım kimliği olmadan cevabı yoktur. Bu kayıt zaten denetim izinin bir parçası; toplu değişiklikleri izlemeyen bir denetim izi, en büyük değişiklikleri kaçırıyor demektir.

Bir de eşleme şablonlarını saklayın. Aynı müşteri her ay aynı kolon başlıklarıyla geliyorsa kolon eşlemesini bir kez yapıp kaydetmek, aktarım süresini dakikalardan saniyelere indirir. Tek seferlik bir göç planlıyorsanız kurallar biraz farklı, eski sistemden yeni sisteme veri taşıma yazısı o tarafı anlatıyor.

Bu hafta atılabilecek ilk adım

Son üç başarısız aktarımın dosyalarını isteyin ve açın. Hatayı kodunuzda aramadan önce dosyada arayın: ayırıcı ne, kodlama ne, tarih hangi biçimde, kimlik kolonunda baştaki sıfır duruyor mu. Çoğu ekip bu beş dakikalık incelemede hataların üçte ikisinin aynı iki sebepten geldiğini görüyor.

Sonra tek bir şey yazın: hiçbir şey yazmayan bir prova modu. Dosyayı okusun, kuralları çalıştırsın, "şu kadar yeni, şu kadar güncelleme, şu kadar ret" raporunu üretsin ve reddedilenleri satır numarasıyla indirilebilir hale getirsin. Yazma tarafına dokunmadan bile bu tek ekran, destek ekibinizin aktarım yüzünden aldığı çağrıların çoğunu bitirir. Aktarım özelliğiniz sürekli yarıda kalıyorsa ve nereden başlayacağınıza karar veremiyorsanız, mevcut akışı birlikte gözden geçirip bu üç adımı hangi sırayla ekleyeceğinizi çıkarabiliriz.


Bu konuda yardıma mı ihtiyacınız var?

iletişime geç →← tüm yazılar