Bir tabloyu silmeden önce maliyeti söyle: DynamoDB Bulk Executor’ı yazarken öğrenilenler
Bulk Executor for Amazon DynamoDB, Part 2: Lessons we learned the hard way · Jason Hunter
Bulk Executor, bir DynamoDB tablosundaki çok sayıda ya da tüm öğeler üzerinde toplu işlem yapan açık kaynak bir araç: eski kayıtları silmek, iki tabloyu karşılaştırmak gibi. Yazının ikinci bölümü aracın nasıl yazıldığını, tasarım tercihlerini ve çarpılan duvarları anlatıyor. Üç gereksinim kararların çoğunu belirlemiş: komut yerel bir betik gibi hissettirmeli (anlık çıktı, ^C ile kesilebilmeli), yüzlerce paralel işçiye altyapı yönetmeden ölçeklenmeli ve yeni komut eklemek çerçeveye sıkı bağlı olmamalı.
Neden AWS Glue? Paralel yürütmeyi Glue sürüyor: serverless, işi yüzlerce işçiye dağıtıyor ve hazır bir DynamoDB bağlayıcısı var. SQS ile Lambda, Fargate, EMR ve Ray da düşünülmüş. Spark DataFrame’in filter, orderBy ve limit yetenekleri sayesinde ilk komut olan find, --where, --orderby, --limit parametreleriyle neredeyse hiç kod yazılmadan çıkmış. Bağlayıcının bir bedeli var: veri tiplerini tam temsil etmiyor, örneğin string kümesi ile string listesini ayırt etmiyor.
Çalışmadan önce maliyet tahmini. Büyük tablolarda iş başlamadan fiyat veriliyor. Okuma için tablonun metadata’sındaki boyuttan (yaklaşık 6 saatte bir güncellenir, yani yeni yüklenmiş tablo olduğundan küçük görünebilir) tam tarama için gereken okuma birimi hesaplanıp Pricing API’deki bölge fiyatıyla çarpılıyor. Provisioned modda tahmin zor olduğundan sezgisel bir kural: on-demand fiyatın üçte ikisi (1,5’e bölme). Mantık: yüzde 100 kullanımda provisioned maliyet on-demand’ın yaklaşık yüzde 30’u, yüzde 50 kullanımda yaklaşık yüzde 60’ı; yüzde 100’e pratikte ulaşılamadığı için yüzde 67 seçilmiş, çünkü ‘biraz yüksek söylemek daha iyi’. delete iki aşamalı fiyatlıyor: önce okuma maliyeti, eşleşenler bulunduktan sonra yazma maliyeti. Glue işlem birimi (DPU) maliyeti tahmin edilmiyor, çünkü büyük işlerde DynamoDB maliyetinin küçük bir kesri çıkıyor.
Güvenlik kararları. ^C yalnızca istemci betiğini durduruyor, Glue işi zaman aşımına kadar çalışmaya devam ediyordu; çözüm, kesme sinyalini kodla yakalayıp bir bayrak koymak ve sürekli çalışan bir izleyici döngüsünün işi durdurması. Silme gibi değiştiren her komut için tabloda zamana göre kurtarma (PITR) zorunlu: --where beklenenden fazlasını silerse geri dönülebilsin. Bunun için PITR’ye dayalı artımlı dışa aktarmayla yazmaları yerinde geri alan bir revert-export komutu da yazılmış.
Çıktı ve hatalar. İlk sürüm Glue çıktısını CloudWatch Logs’tan yoklayarak alıyordu: gecikmeli ve parça parça. CloudWatch Live Tail’e geçince çıktı neredeyse anlık akmaya başladı; ama Live Tail saniyede en çok 500 olay gösteriyor, fazlasında örnekleme yapıyor. Çözüm: büyük çıktıyı S3’e yazmak, terminale yalnızca ilk 10 sonucu ve tam veriye bir bağlantı basmak. Hata yönetiminde dört desen: istemci tarafında parametreleri Glue başlamadan doğrulamak (yazım hatası yerelde yakalanır); Spark accumulator ile yüzlerce aynı istisna yerine ilkini tek mesaj olarak göstermek; AccessDeniedException, OutOfMemoryError gibi kendiliğinden düzelmeyecek hataları akıştan tanıyıp işi hemen durdurmak; ve ‘where’ cümlesindeki yazım hatası için korkutucu yığın izi yerine yalnızca mesaj gösteren özel bir istisna türü.
Bağlayıcı ile doğrudan erişim. Bağlayıcı DataFrame okuyup yazıyor ama öğe silemiyor; silme için doğrudan DynamoDB API’si ve batch_writer kullanılmış, Spark bölümleri paralel çalıştırmış. Bu desen o kadar iyi çalışmış ki ilk komutlar da yeniden düşünülmüş: bağlayıcısız scancount (segmented scan, filtreyi sunucuda uygulayıp yalnızca sayıyı döndürüyor) sürekli daha hızlı çıkmış, çünkü bağlayıcı tüm öğeleri Glue bağlamına yüklüyor. diff ve copy ise hız için değil, veri tipi aslına uygunluğu için doğrudan okuyor. diff her görevin çıktısını kendi S3 nesnesine yazıyor, sürücüye toplamıyor, çünkü o ölçeklenmez. Bağlayıcının yerleşik rate limit var; doğrudan API çağıran komutlarda (delete, fill, copy, scancount) yok.
Öne çıkanlar
- Yıkıcı bir işlem başlamadan maliyetini ve etkisini göstermek, aracın en değerli özelliğidir.
- Değiştiren komutlar için rollback yolunu (PITR) zorunlu kılmak, ‘yanlış where’ riskini güvenli hâle getirir.
- ‘Biraz yüksek söylemek’ tahminde iyi bir yanlılıktır: kullanıcı sürprizle karşılaşmaz.
- Bir kütüphane bağlayıcısı kolaylık sağlar ama tipleri bozabilir; doğruluk gerekince doğrudan API’ye in.
- Hatayı erken yakala, tek mesaja indir ve son satırda başarılı mı başarısız mı olduğunu söyle.
Neden önemli?
Üretim verisine dokunan her betik, eninde sonunda yanlış bir koşulla çalıştırılır. Bu yazı, ‘dikkatli ol’ demek yerine aracın içine fren koymanın somut örnekleri: önce fiyat, zorunlu rollback, erken durdurma.
Pratikte
Bu desenleri bugün kendi betiklerine ekleyebilirsin. Eski veriyi silen ya da dokümanları toplu yeniden indeksleyen bir betik yazarsan: (1) varsayılan olarak --dry-run çalıştır ve ‘şu kadar kayıt etkilenecek’ diye yaz; gerçekten uygulamak için ayrı bir --yes iste. (2) Silmeden önce o tablonun backup’ının ya da silinen satırların bir arşiv tablosunun var olduğunu kontrol et. (3) Toplu işi küçük gruplar hâlinde yürüt ve ilk gruptaki hatada dur. Yazının DynamoDB’ye özgü kısmı (Glue, PITR) şimdi lazım olmayabilir ama ‘önce fiyat, sonra izin, sonra uygula’ sırası her veritabanı için geçerli.
Sözlük
- point-in-time recovery (PITR)
- Veritabanını geçmişteki belirli bir ana döndürme yeteneği.
- dry run
- Hiçbir şeyi değiştirmeden bir işlemin ne yapacağını gösteren deneme koşusu.
- segmented scan
- Tabloyu birbirinden bağımsız dilimlere bölüp her dilimi ayrı işçiyle paralel taramak.
- AWS Glue
- İşi birçok işçiye dağıtan serverless veri işleme servisi (Spark tabanlı).
- idempotent
- Birden fazla kez çalıştırılınca sonucu değişmeyen işlem.