MySQL'de Eski Verileri Arşivleme

GoArchive ile Sakila veri tabanı üzerinde uçtan uca uygulama: kurulum, ön uçuş kontrolleri, tekil ve ilişkisel senaryolar, doğrulama

Bir sunucu kabinindeki eski veri bloklarının doğrulanarak arşiv kabinine taşındığını gösteren şema

Giriş ve problem tanımı

Büyük ölçekli kurumsal veri tabanı (database) yönetiminde en kritik operasyonel zorluklardan biri, zamanla devasa boyutlara ulaşan hareket (transactional) tablolarının yönetimidir. Kontrolsüz büyüyen veri tabanları, yedekleme (backup) ve geri yükleme (restore) sürelerinin uzamasına, bellek ve disk kaynaklarının tükenmesine, dizin (index) verimsizliklerine ve replikasyon gecikmelerine (replication lag) yol açar.

Bu tür tabloların temizlenmesinde geleneksel yaklaşımlar (örneğin kontrolsüz DELETE sorguları veya geçici kabuk / shell betikleri) üretim ortamlarında ciddi riskler barındırır:

  • Kilitlenme (Locking) Problemleri: Milyonlarca satırlık büyük silme işlemleri tabloları kilitler ve canlı işlemleri tıkar.
  • Replikasyon Gecikmesi (Replication Lag): Üretilen yoğun işlem günlükleri (binary logs), replikasyon düğümlerinin (read replicas) ana sunucunun (source/primary) çok gerisinde kalmasına sebep olur.
  • Veri Bütünlüğü (Data Integrity) Kaybı: Doğrulama adımı olmadan yapılan işlemler, silinen verinin hedefe eksik veya hatalı aktarılmasına yol açabilir.

GoArchive , kurumsal MySQL ve Percona Server ortamlarında veri arşivleme (archiving), temizleme (purge) ve yalnızca kopyalama (copy-only) süreçlerini sıfır veri kaybı ve minimum üretim etkisi prensibiyle otomatikleştiren, Go diliyle geliştirilmiş modern bir arşivleme aracıdır.

Bu blog yazısında, GoArchive’ın mimari yaklaşımını, E2E test ortamındaki docker compose topolojisini ve endüstri standardı örnek Sakila veri tabanını (database) kullanarak adım adım inceleyeceğiz.

Mimari ve test topolojisi: Docker Compose altyapısı

GoArchive, test ve doğrulama süreçlerinde gerçekçi bir küme (cluster) topolojisi kullanır. Projenin tests/compose.yml dosyasında tanımlanan mimari, gerçek bir üretim ortamının minyatür bir modelini sunar:

DüğümRolPortVeri tabanı
db1Kaynak (source)3305sakila
db2Hedef (destination)3307sakila_archive
db3Replika (replica), GTID ile db1‘i izler3308

GoArchive db1 üzerinden okur, db2 üzerine kopyalar, db2 üzerinde doğrular ve db1 üzerinden parçalar hâlinde siler. Silme sırasında db3 üzerindeki replikasyon gecikmesini izler.

Düğüm tanımları ve görevleri

  1. Kaynak Düğüm (Source - db1, Port 3305): Aktif Sakila veri kümesinin bulunduğu ana veri tabanı (source database). Arşivlenecek veriler buradan okunur ve doğrulandıktan sonra silinir.
  2. Hedef Düğüm (Destination - db2, Port 3307): Arşiv verilerinin taşındığı hedef veri tabanı (destination database: sakila_archive).

    Önemli Not: db2 konteyneri kasten +03:00 saat diliminde (time zone) çalıştırılır. Kaynak (db1) ise UTC‘dedir. Bu durum, GoArchive’ın oturum bazlı time_zone='+00:00' sabitlemesini ve zaman damgası (timestamp) değerlerini bölgesel saat dönüşümlerinden bağımsız olarak birebir doğrulayabilme kabiliyetini sınar.

  3. Replika Düğümü (Replica - db3, Port 3308): db1 sunucusunu GTID (Global Transaction Identifier) üzerinden takip eden canlı kopya (replica). GoArchive, silme işlemleri sırasında replikasyon gecikmesini (replication lag) izleyerek gecikme belirli bir eşiğin üzerine çıktığında silme işlemlerini otomatik olarak duraklatır (throttling / pacing).

İsimlendirilmiş birimler (Named Volumes)

tests/compose.yml yapılandırmasında veri dizinleri için ana makine bağlama noktaları (bind mounts) yerine Docker isimlendirilmiş birimleri (named volumes: db1_data, db2_data, db3_data) tercih edilmiştir. Bu sayede işletim sistemi uyumsuzlukları (özellikle macOS altındaki büyük/küçük harf duyarlılığı ve dosya sistemi eşzamanlama kısıtları) engellenmiş, gerçekçi Linux POSIX dosya sistemi performansı garanti altına alınmıştır.

Ortamın hazırlanması ve ilk kurulum

Uygulamalı adımlara geçmeden önce test ortamını ayağa kaldırmak ve Sakila şemasını oluşturmak için aşağıdaki adımları izliyoruz:

# 1. Ortam değişkenleri dosyasını (.env) hazırlayın
cp tests/dot.env tests/.env

# 2. Docker konteynerlerini (db1, db2, db3) başlatın ve Sakila veri tabanını yükleyin
./scripts/run-tests.sh --setup

# 3. GoArchive ikili dosyasını (binary) derleyin
go build -o bin/goarchive ./cmd/goarchive

Kurulum tamamlandığında kaynak (3305), hedef (3307) ve replika (3308) servisleri hazır hâle gelir.

GoArchive güvenlik döngüsü ve ön uçuş kontrolleri (Preflight Checks)

GoArchive, hiçbir veri işlemine başlamadan önce veri tabanı şemasını ve yapılandırma dosyasını (configuration file) çok sıkı bir ön uçuş denetiminden (preflight check) geçirir. Hatalı yapılandırmalar daha ilk saniyede tespit edilerek operasyon durdurulur (fail-fast prensibi).

Öne çıkan kritik denetimler şunlardır:

  1. Depolama Motoru Denetimi (STORAGE_ENGINE_CHECK): Arşivlenecek tüm tabloların InnoDB motoruna sahip olması zorunludur. İşlemsel (transactional) bütünlük sağlamayan motorlar reddedilir.
  2. Bileşik Birincil Anahtar Kısıtı (COMPOSITE_PK_CHECK): GoArchive, satırları tekil bir birincil anahtar (single-column primary key) üzerinden izler ve siler. Bileşik anahtara (composite primary key) sahip tablolar (örneğin Sakila’daki film_actor veya film_category tabloları) doğrudan silme aşamasında hedef dışı satırların silinmesi riskini doğurabileceğinden koruma amacıyla reddedilir.
  3. Yabancı Anahtar Kapsam Denetimi (FK_COVERAGE_CHECK): Arşivlenen bir tabloya, arşiv grafiği dışındaki başka bir tablodan yabancı anahtar (foreign key) referansı varsa ve bu ilişki yapılandırmada çözülmemişse operasyon durdurulur. Aksi hâlde kaynakta yetim kayıtlar (orphan records) veya kısıt ihlalleri oluşacaktır.
  4. İç Yabancı Anahtar Kapsamı (INTERNAL_FK_COVERAGE): Arşiv grafiğinde yer alan iki tablo arasındaki tüm yabancı anahtar (foreign key) ilişkileri açıkça ebeveyn-çocuk (parent-child) ağaç yapısı olarak tanımlanmalıdır.
    • Örnek (GDPR Elması Kısıtı): Sakila’da customer -> rental -> payment ilişkisinde, payment tablosu hem rental hem de customer tablosuna bağlıdır (elmas topolojisi). GoArchive katı bir ağaç hiyerarşisini şart koştuğu için bu elmas yapı test11 örneğinde görüleceği üzere INTERNAL_FK_COVERAGE hatası ile güvenle engellenir. En doğru çalışan model rental -> payment hiyerarşik modelidir.
  5. Silme Tetikleyicisi Uyarısı (DELETE_TRIGGER_CHECK): Kaynak tablolarda BEFORE/AFTER DELETE tetikleyicileri (triggers) varsa, GoArchive beklenmeyen yan etkileri önlemek için işlemi durdurur. Operatörün bilerek devam etmesi için --force-triggers bayrağı (flag) gereklidir.

Senaryo 1: Tekil tablo ve yüksek hacimli arşivleme (payment)

İlk senaryomuzda Sakila veri tabanındaki en yoğun hareket tablosu olan payment (ödeme) tablosunun arşivlenmesini ele alacağız. Bu senaryoda yığın (batch) mekanizmasını, hız sınırlandırmayı (throttling/pacing) ve iki farklı doğrulama (verification) yöntemini inceleyeceğiz.

1. Yapılandırma dosyası: test03_payment_batch.yaml

Aşağıdaki yapılandırma, payment_id <= 2000 koşulunu sağlayan satırları 100’lük yığınlar (batch size) hâlinde kopyalayıp 20’şerlik alt parçalar (batch delete size / chunks) hâlinde siler:

source:
  host: 127.0.0.1
  port: 3305
  user: root
  password: ${MYSQL_ROOT_PASSWORD}
  database: sakila

destination:
  host: 127.0.0.1
  port: 3307
  user: root
  password: ${MYSQL_ROOT_PASSWORD}
  database: sakila_archive

safety:
  disable_foreign_key_checks: true

jobs:
  archive-payment-rows:
    root_table: payment
    primary_key: payment_id
    where: "payment_id <= 2000"
    processing:
      batch_size: 100           # Her çevrimde aktarılacak satır adedi
      batch_delete_size: 20     # Kaynakta silinirken kullanılacak alt parça boyutu
      sleep_seconds: 0.2        # Yığınlar arası bekleme süresi
      delete_sleep_seconds: 0.2 # Silme parçaları arası bekleme süresi

2. Adım adım komut satırı iş akışı

Adım 2.1: Tanımlı işleri listeleme (list-jobs)

Yapılandırma dosyasındaki geçerli iş emirlerini (jobs) listeler:

goarchive list-jobs --config test03_payment_batch.yaml
$ goarchive list-jobs --config test03_payment_batch.yaml
Jobs defined in test03_payment_batch.yaml:
1. archive-payment-rows
Root Table: payment
Primary Key: payment_id
WHERE: payment_id <= 2000
Relations: 0 table(s)
Processing: Custom (batch_size=100, batch_delete_size=20)
Total: 1 job(s)

Adım 2.2: Çalışma planını çıkarma (plan)

Hangi tabloların hangi sırada işleneceğini, ebeveyn-çocuk bağımlılıklarını ve yaklaşık satır sayılarını gösterir:

goarchive plan --job archive-payment-rows --config test03_payment_batch.yaml
$ goarchive plan --job archive-payment-rows --config test03_payment_batch.yaml
[Job Overview]
Root Table: payment (PK: payment_id)
Total Tables: 1
WHERE Clause: payment_id <= 2000
[Copy Order (parent tables first)]
[1] payment (root)
[Delete Order (child tables first)]
[1] payment (root)
[Configuration]
Batch Size: 100 (job-specific)
Batch Delete Size: 20 (job-specific)
Sleep Between Batches: 0.2s (job-specific)
Verification Method: count

Kopyalama sırası her zaman ebeveynden çocuğa, silme sırası ise daima çocuktan ebeveyne doğru planlanır. Ayrıntılar arşivleme rehberinde yer alır.

Adım 2.3: Yapılandırma doğrulama (validate)

Veri tabanlarına bağlanır, şema, dizinler, motor tipleri ve tetikleyicileri denetler:

goarchive validate --config test03_payment_batch.yaml
$ goarchive validate --config test03_payment_batch.yaml
=== Configuration Validation ===
Config file: test03_payment_batch.yaml
Jobs to validate: 1
⚠️ WARNING: safety.disable_foreign_key_checks is ENABLED.
[1/1] Job: archive-payment-rows
Root Table: payment
Relations: 0 table(s)
✅ PASSED
=== Validation Summary ===
Passed: 1/1

Kaynak tabloda silme tetikleyicisi bulunduğunda komut --force-triggers bayrağı olmadan durur. Sakila’nın tek silme tetikleyicisi film tablosundadır, bu iki senaryoda bayrağa gerek kalmaz.

Adım 2.4: Kuru çalıştırma (dry-run)

Hiçbir veriyi değiştirmeden, where koşuluna uyan satırları sorgular ve operasyonun olası etkisini simüle eder:

goarchive dry-run --job archive-payment-rows --config test03_payment_batch.yaml
$ goarchive dry-run --job archive-payment-rows --config test03_payment_batch.yaml
=== Dry-Run Execution Plan ===
Root Table: payment
WHERE: payment_id <= 2000
Matching rows: 1999
Batch size: 100
Estimated batches: 20
Copy Order (parent-first): 1. payment (~1999 rows)
Delete Order (child-first): 1. payment (~1999 rows)
ℹ️ No data was modified. Use 'archive' command to execute.
batch_size payload validation passed.

Adım 2.5: Arşivlemeyi başlatma (archive)

Tam döngü çalıştırılır: kopyala, doğrula, sil.

goarchive archive --job archive-payment-rows --config test03_payment_batch.yaml
$ goarchive archive --job archive-payment-rows --config test03_payment_batch.yaml
Processing batch batch=20 root_ids=99
Discovery complete: 1 tables, 99 records, 1 levels, duration: 57.708µs
Copy phase complete: 1 tables, 99 rows, duration: 10.507209ms
Verification complete: 1 tables verified, 1 passed, 0 failed, 99 total rows
Delete phase complete: 1 tables, 99 rows deleted, duration: 841.00125ms
=== Archive Complete ===
Duration: 21.405376625s
Records Copied: 1999
Records Deleted: 1999
Success: true

3. Doğrulama yöntemleri: satır sayısı (count) ve sha256 sağlama toplamı

GoArchive, kaynak veriyi silmeden önce hedefte verinin eksiksiz olduğunu doğrulamak zorundadır. İki ana doğrulama yöntemi sunulur:

  1. Satır Sayısı Doğrulaması (count): Varsayılan yöntemdir. İlgili yığındaki birincil anahtar (primary key) satır sayılarının kaynak ve hedefte birebir eşit olduğunu kontrol eder.

  2. Kriptografik Sağlama Toplamı (sha256): test05_payment_verify_sha256.yaml yapılandırmasında kullanıldığı gibi:

    verification:
      method: sha256
    

    Bu yöntemde GoArchive, kopyalanan satırların tüm sütun değerlerini birleştirerek bir SHA-256 sağlama toplamı (checksum hash) üretir ve hedefteki satırların hash değeriyle kıyaslar.

    • Ek Güvenlik Davranışı: sha256 modu devredeyken ve hedef tabloda birincil anahtar haricinde tekil dizin (unique index) yoksa, kopyalama işlemi INSERT IGNORE mantığıyla çalıştırılarak olası mükerrerliklerde hata yerine satır uyumsuzluğu üretilip doğrulama aşamasında güvenle yakalanır.

Senaryo 2: İlişkisel ve hiyerarşik arşivleme (rental -> payment)

Gerçek hayat senaryolarında nadiren tek bir tablo arşivlenir. Çoğunlukla ebeveyn-çocuk ilişkisi olan tabloların birlikte taşınması gerekir. Sakila veri tabanındaki kiralama (rental) ve buna bağlı ödeme (payment) tabloları bu model için mükemmel bir örnektir.

1. Yapılandırma: test04_rental_payment.yaml

Bu senaryoda rental tablosu kök (root), payment tablosu ise yabancı anahtarla (rental_id) bağlı bir alt ilişkidir (1-N child relation):

jobs:
  archive-rental-payments:
    root_table: rental
    primary_key: rental_id
    where: "rental_id <= 200"
    relations:
      - table: payment
        primary_key: payment_id
        foreign_key: rental_id
        dependency_type: "1-N"

2. Kritik mimari prensipler

  1. Hiyerarşik İşlem Sırası:
    • Kopyalama Aşaması (Ebeveynden Çocuğa): Önce rental satırları hedefe kopyalanır, ardından bu kayıtlara bağlı payment satırları kopyalanır.
    • Silme Aşaması (Çocuktan Ebeveyne): Silme işlemi tam ters sırada yapılır, önce alt tablodaki payment satırları silinir, ardından ana rental satırları silinir. Bu kural yabancı anahtar kısıt ihlallerini (foreign key constraint violations) tamamen önler.
  2. Yetim Kayıt (Orphan Row) Kontrolü: Hedefe aktarılan tüm payment kayıtlarının geçerli bir rental ebeveynine bağlı olduğu doğrulanır. Hedefte hiçbir yetim kayıt kalmasına izin verilmez.
  3. Yabancı Anahtar Denetimlerinin Geçici Kapatılması (disable_foreign_key_checks: true): Hedef veri tabanında (destination database) müşteri (customer) veya personel (staff) tabloları bulunmayabilir. Yalnızca ilgili alt grafik taşındığından, aktarım sırasında hedefte yabancı anahtar denetimleri güvenle devre dışı bırakılır, veri bütünlüğü ise GoArchive’ın yazılımsal denetimleriyle sağlanır.

3. Çalıştırma

goarchive validate --config test04_rental_payment.yaml
goarchive archive --job archive-rental-payments --config test04_rental_payment.yaml
$ goarchive validate --config test04_rental_payment.yaml
[1/1] Job: archive-rental-payments
Root Table: rental
Relations: 1 table(s)
✅ PASSED
$ goarchive archive --job archive-rental-payments --config test04_rental_payment.yaml
=== Archive Complete ===
Duration: 1.070846875s
Tables Copied: 2
Records Copied: 370
Records Deleted: 370
Success: true

İlişkili veri taşımayan senaryolar için yalnızca kopyalama ve temizleme rehberleri ayrıca hazırlanmıştır.

İzleme, iş takibi ve kesinti toleransı

GoArchive, uzun süren arşivleme operasyonlarında hata toleransını garanti altına almak için hedef veri tabanı üzerinde izleme tabloları (archiver_job ve archiver_job_log_<id>) tutar.

İzleme tablosuGörevi
archiver_jobİş emrinin genel durumu (job_status), başlangıç ve bitiş zamanları, son işlenen birincil anahtar (last_processed_root_pk_id).
archiver_job_log_<id>Her bir yığının anlık durumu: kopyalandı, doğrulandı, silindi.

İki senaryo tamamlandıktan sonra iş tablosu şu hâli alır:

mysql> SELECT * FROM sakila_archive.archiver_job\G
id: 1
job_name: archive-payment-rows
root_table: payment
job_type: archive
last_processed_root_pk_id: 2000
job_status: 0
id: 2
job_name: archive-rental-payments
root_table: rental
last_processed_root_pk_id: 200
job_status: 0

Kesinti durumlarında davranış biçimi

  • Zarif Durdurma (Graceful Stop - SIGTERM): İşlem sonlandırıldığında GoArchive mevcut yığını tamamlar ve kontrol noktasını (last_processed_root_pk_id) günceller. Süreç yeniden başlatıldığında doğrudan bu anahtardan sonraki satırlardan devam eder (checkpoint resume).
  • Beklenmeyen Çökme (Crash / SIGKILL): Bir elektrik kesintisi veya sistem çökmesi durumunda silme aşamasında kalan satırlar archiver_job_log_<id> tablosunda tespit edilir. Bir sonraki çalıştırmada kurtarma mekanizması devreye girer, kopyalanmış ancak silinmesi yarım kalmış satırlar güvenle temizlenir ve akış kaldığı yerden devam eder (crash replay).

Sonuçların doğrulanması

Arşivleme tamamlandıktan sonra satır sayıları doğrudan MySQL sorgularıyla teyit edilebilir:

tests/scripts/mysql-query.sh 3307 "SELECT COUNT(*) FROM sakila_archive.payment;"
tests/scripts/mysql-query.sh 3305 "SELECT COUNT(*) FROM sakila.payment WHERE payment_id <= 2000;"
-- hedefte aktarılan satırlar
mysql> SELECT COUNT(*) FROM sakila_archive.payment;
2169
mysql> SELECT COUNT(*) FROM sakila_archive.rental;
200
-- kaynakta silinen satırlar, sonuç 0 dönmelidir
mysql> SELECT COUNT(*) FROM sakila.payment WHERE payment_id <= 2000;
0
mysql> SELECT COUNT(*) FROM sakila.rental WHERE rental_id <= 200;
0

Hedefteki 2.169 ödeme kaydı iki senaryonun toplamıdır: birinci senaryodan 1.999, ikinci senaryodaki kiralamalara bağlı 170 kayıt. Kaynakta her iki koşul için de sonuç sıfırdır.

Kurumsal en iyi uygulamalar

Canlı üretim ortamlarında GoArchive kullanırken aşağıdaki kontrol listesini uygulamanız önerilir:

  1. Önce Kuru Çalıştırma (dry-run): Üretim ortamında hiçbir işi doğrudan başlatmayın, önce validate ve dry-run çıktılarını inceleyin.
  2. Küçük Yığınlarla Başlayın: batch_size (örn: 500-1000) ve batch_delete_size (örn: 50-100) değerlerini donanım ve işlem hacmine göre belirleyin.
  3. Hız Sınırlandırmayı (Pacing/Throttling) İhmal Etmeyin: sleep_seconds ve delete_sleep_seconds parametreleriyle disk I/O ve replikasyon kuyruklarının rahatlamasına fırsat tanıyın.
  4. Replikasyon Gecikmesini İzleyin: Hedefte replikasyon izleme parametrelerini aktif tutarak canlı okuma trafiğini koruyun.
  5. Kritik Verilerde SHA-256 Kullanın: Finansal veya yasal düzenlemeye tabi verilerde verification.method: sha256 seçeneğini tercih edin.

Özet

GoArchive, MySQL ortamlarında veri arşivleme süreçlerini bir komut dosyası karmaşasından çıkarıp kurumsal düzeyde yönetilebilir, izlenebilir ve güvenli bir operasyona dönüştürür. Ön uçuş kontrolleri, kriptografik doğrulaması, hız sınırlandırması ve kesinti toleransı ile büyük veri tabanlarında sıfır kesinti ve sıfır veri kaybı hedefine ulaşmanızı sağlar.

Ürünün tamamı ve çalışma kapsamı GoArchive ile MySQL veri arşivleme çözümü sayfasında, kurulum ve komut ayrıntıları ise ürün dokümantasyonunda yer alır.