Untitled
alp
plain_text
10 months ago
17 kB
39
Indexable
"""
Iceberg Tablosu Optimizasyon Scripti
- Mevcut düz tabloyu aylık partition'lara dönüştürme
- Bloom filter ekleme
- Z-ordering ile optimize etme
- Sorgu performansını artırma
"""
from pyspark.sql import SparkSession
from pyspark.sql.functions import *
from datetime import datetime
import time
# ============================================================================
# KONFIGURASYON
# ============================================================================
# Tablo bilgileri
SOURCE_TABLE = "iceberg.sompodatabase2.T001PSRCVP"
TARGET_TABLE = "iceberg.sompodatabase2.T001PSRCVP_partitioned"
TEMP_TABLE = "iceberg.sompodatabase2.T001PSRCVP_temp"
# Kolon tanımları
DATE_COL = "CONFIRM_DATE"
PK_COLS = ["FIRM_CODE", "COMPANY_CODE", "PRODUCT_NO", "POLICY_NO", "RENEWAL_NO", "ENDORS_NO"]
# Optimize parametreleri
TARGET_FILE_SIZE = 512 * 1024 * 1024 # 512 MB
MAX_CONCURRENT_REWRITES = 8
ENABLE_BLOOM_FILTER = True
BLOOM_FILTER_FPP = 0.05
# ============================================================================
# YARDIMCI FONKSİYONLAR
# ============================================================================
def log(message):
"""Zaman damgalı log mesajı"""
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
print(f"[{timestamp}] {message}")
def format_bytes(bytes_val):
"""Byte'ı okunabilir formata çevir"""
for unit in ['B', 'KB', 'MB', 'GB', 'TB']:
if bytes_val < 1024.0:
return f"{bytes_val:.2f} {unit}"
bytes_val /= 1024.0
return f"{bytes_val:.2f} PB"
def get_table_stats(spark, table_name):
"""Tablo istatistiklerini getir"""
try:
stats = spark.sql(f"""
SELECT
COUNT(*) as file_count,
SUM(file_size_in_bytes) as total_size,
AVG(file_size_in_bytes) as avg_file_size,
MIN(file_size_in_bytes) as min_file_size,
MAX(file_size_in_bytes) as max_file_size
FROM {table_name}.files
""").first()
return stats
except:
return None
# ============================================================================
# 1. MEVCUT TABLO DURUMUNU KONTROL ET
# ============================================================================
def check_current_state(spark, table_name, date_col):
"""Mevcut tablo durumunu kontrol et"""
log("="*80)
log("ADIM 1: Mevcut Tablo Durumu Kontrol Ediliyor")
log("="*80)
# Dosya istatistiklerinden bilgi al (daha hızlı)
stats = get_table_stats(spark, table_name)
if stats:
log(f"Dosya sayısı: {stats['file_count']}")
log(f"Toplam boyut: {format_bytes(stats['total_size'])}")
log(f"Ortalama dosya boyutu: {format_bytes(stats['avg_file_size'])}")
log(f"Min dosya boyutu: {format_bytes(stats['min_file_size'])}")
log(f"Max dosya boyutu: {format_bytes(stats['max_file_size'])}")
# Sadece MIN/MAX al (COUNT DISTINCT yapma - çok yavaş)
date_range = spark.sql(f"""
SELECT
MIN({date_col}) as min_date,
MAX({date_col}) as max_date
FROM {table_name}
""").first()
min_date = date_range['min_date']
max_date = date_range['max_date']
# Ay sayısını hesapla
if min_date and max_date:
month_count = (max_date.year - min_date.year) * 12 + (max_date.month - min_date.month) + 1
else:
month_count = 0
log(f"Tarih aralığı: {min_date} - {max_date}")
log(f"Tahmini ay sayısı: {month_count}")
# Toplam satır sayısını table properties'den al (varsa)
try:
row_count_estimate = spark.sql(f"SELECT COUNT(*) as cnt FROM {table_name}.files").first()['cnt']
log(f"Dosya metadata'sından tahmini satır bilgisi alındı")
except:
log(f"NOT: Tam satır sayımı yapılmadı (çok uzun sürer)")
row_count_estimate = None
return row_count_estimate, {'min_date': min_date, 'max_date': max_date, 'month_count': month_count}
# ============================================================================
# 2. PARTITION'LI YENİ TABLO OLUŞTUR
# ============================================================================
def create_partitioned_table(spark, source_table, target_table, date_col, pk_cols):
"""Aylık partition'lı yeni tablo oluştur"""
log("="*80)
log("ADIM 2: Partition'lı Yeni Tablo Oluşturuluyor")
log("="*80)
# Eski tabloyu sil (eğer varsa)
spark.sql(f"DROP TABLE IF EXISTS {target_table}")
log(f"Eski tablo silindi (eğer varsa): {target_table}")
# Tablo özelliklerini al
source_schema = spark.table(source_table).schema
# Yeni partition'lı tablo oluştur
spark.sql(f"""
CREATE TABLE {target_table}
USING iceberg
PARTITIONED BY (months({date_col}))
TBLPROPERTIES (
'write.format.default' = 'parquet',
'write.parquet.compression-codec' = 'zstd',
'write.metadata.compression-codec' = 'gzip',
'write.target-file-size-bytes' = '{TARGET_FILE_SIZE}',
'write.distribution-mode' = 'hash',
'commit.retry.num-retries' = '10',
'commit.retry.min-wait-ms' = '100',
'history.expire.max-snapshot-age-ms' = '432000000'
)
AS SELECT * FROM {source_table} WHERE 1=0
""")
log(f"Yeni partition'lı tablo oluşturuldu: {target_table}")
# ============================================================================
# 3. VERİYİ AY AY KOPYALA
# ============================================================================
def migrate_data_by_month(spark, source_table, target_table, date_col):
"""Veriyi ay ay yeni tabloya kopyala"""
log("="*80)
log("ADIM 3: Veri Ay Ay Kopyalanıyor")
log("="*80)
# Tüm ayları listele
months = spark.sql(f"""
SELECT DISTINCT DATE_TRUNC('MONTH', {date_col}) as month
FROM {source_table}
ORDER BY month
""").collect()
total_months = len(months)
log(f"Toplam {total_months} ay kopyalanacak")
# Her ayı ayrı ayrı kopyala
for idx, row in enumerate(months, 1):
month = row['month']
month_str = month.strftime('%Y-%m')
log(f"\n[{idx}/{total_months}] {month_str} ayı işleniyor...")
start_time = time.time()
# Veriyi kopyala
spark.sql(f"""
INSERT INTO {target_table}
SELECT * FROM {source_table}
WHERE DATE_TRUNC('MONTH', {date_col}) = DATE'{month}'
""")
elapsed = time.time() - start_time
# Kopyalanan satır sayısını kontrol et
count = spark.sql(f"""
SELECT COUNT(*) as cnt FROM {target_table}
WHERE DATE_TRUNC('MONTH', {date_col}) = DATE'{month}'
""").first()['cnt']
log(f"✓ {month_str}: {count:,} satır kopyalandı ({elapsed:.1f} saniye)")
log("\n✓ Tüm veri başarıyla kopyalandı!")
# ============================================================================
# 4. BLOOM FILTER EKLE
# ============================================================================
def add_bloom_filters(spark, table_name, pk_cols, fpp):
"""Bloom filter ekle"""
log("="*80)
log("ADIM 4: Bloom Filter Ekleniyor")
log("="*80)
cols_str = ",".join(pk_cols)
spark.sql(f"""
ALTER TABLE {table_name}
SET TBLPROPERTIES (
'bloom_filter.columns' = '{cols_str}',
'bloom_filter.fpp' = '{fpp}'
)
""")
log(f"✓ Bloom filter eklendi: {cols_str}")
log(f"✓ False Positive Probability: {fpp}")
# ============================================================================
# 5. HER PARTITION'I OPTİMİZE ET
# ============================================================================
def optimize_partitions(spark, table_name, date_col, pk_cols):
"""Her partition'ı ayrı ayrı optimize et"""
log("="*80)
log("ADIM 5: Partition'lar Optimize Ediliyor")
log("="*80)
# Tüm partition'ları listele
partitions = spark.sql(f"""
SELECT DISTINCT DATE_TRUNC('MONTH', {date_col}) as month
FROM {table_name}
ORDER BY month DESC
""").collect()
total_partitions = len(partitions)
log(f"Toplam {total_partitions} partition optimize edilecek")
sort_order = ",".join(pk_cols)
# Her partition'ı optimize et
for idx, row in enumerate(partitions, 1):
month = row['month']
month_str = month.strftime('%Y-%m')
log(f"\n[{idx}/{total_partitions}] {month_str} optimize ediliyor...")
start_time = time.time()
try:
# Rewrite data files with sort
result = spark.sql(f"""
CALL iceberg.system.rewrite_data_files(
table => '{table_name}',
strategy => 'sort',
sort_order => '{sort_order}',
where => "{date_col} >= DATE'{month}' AND {date_col} < DATE'{month}' + INTERVAL 1 MONTH",
options => map(
'target-file-size-bytes', '{TARGET_FILE_SIZE}',
'partial-progress.enabled', 'true',
'partial-progress.max-commits', '20',
'max-concurrent-file-group-rewrites', '{MAX_CONCURRENT_REWRITES}'
)
)
""").first()
elapsed = time.time() - start_time
log(f"✓ {month_str} optimize edildi:")
log(f" - Yeniden yazılan dosya: {result['rewritten_data_files_count']}")
log(f" - Eklenen dosya: {result['added_data_files_count']}")
log(f" - Yeniden yazılan veri: {format_bytes(result['rewritten_bytes_count'])}")
log(f" - Süre: {elapsed:.1f} saniye")
except Exception as e:
log(f"✗ HATA: {month_str} optimize edilemedi: {str(e)}")
continue
log("\n✓ Tüm partition'lar optimize edildi!")
# ============================================================================
# 6. MANIFEST DOSYALARINI OPTİMİZE ET
# ============================================================================
def optimize_manifests(spark, table_name):
"""Manifest dosyalarını optimize et"""
log("="*80)
log("ADIM 6: Manifest Dosyaları Optimize Ediliyor")
log("="*80)
start_time = time.time()
result = spark.sql(f"""
CALL iceberg.system.rewrite_manifests(
table => '{table_name}'
)
""").first()
elapsed = time.time() - start_time
log(f"✓ Manifest optimize edildi:")
log(f" - Yeniden yazılan manifest: {result['rewritten_manifests_count']}")
log(f" - Eklenen manifest: {result['added_manifests_count']}")
log(f" - Süre: {elapsed:.1f} saniye")
# ============================================================================
# 7. ESKİ SNAPSHOT'LARI TEMİZLE
# ============================================================================
def cleanup_old_snapshots(spark, table_name, retain_days=7):
"""Eski snapshot'ları temizle"""
log("="*80)
log("ADIM 7: Eski Snapshot'lar Temizleniyor")
log("="*80)
# Expire snapshots
spark.sql(f"""
CALL iceberg.system.expire_snapshots(
table => '{table_name}',
older_than => DATE_SUB(CURRENT_DATE(), {retain_days}),
retain_last => 5
)
""")
log(f"✓ {retain_days} günden eski snapshot'lar temizlendi")
# Remove orphan files
spark.sql(f"""
CALL iceberg.system.remove_orphan_files(
table => '{table_name}',
older_than => TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL {retain_days} DAY)
)
""")
log(f"✓ Orphan dosyalar temizlendi")
# ============================================================================
# 8. ESKİ TABLOYU YENİSİYLE DEĞİŞTİR
# ============================================================================
def swap_tables(spark, source_table, target_table, temp_table):
"""Eski tabloyu yenisiyle değiştir"""
log("="*80)
log("ADIM 8: Tablolar Değiştiriliyor")
log("="*80)
# Eski tabloyu temp'e taşı
spark.sql(f"ALTER TABLE {source_table} RENAME TO {temp_table}")
log(f"✓ Eski tablo temp'e taşındı: {temp_table}")
# Yeni tabloyu eski adla değiştir
spark.sql(f"ALTER TABLE {target_table} RENAME TO {source_table}")
log(f"✓ Yeni tablo eski adla değiştirildi: {source_table}")
log("\n" + "!"*80)
log("! DİKKAT: Eski tablo şimdi temp olarak saklanıyor: {temp_table}")
log("! Her şey yolundaysa şu komutla silebilirsiniz:")
log(f"! spark.sql('DROP TABLE {temp_table}')")
log("!"*80)
# ============================================================================
# 9. FİNAL İSTATİSTİKLER
# ============================================================================
def show_final_stats(spark, table_name, date_col):
"""Final istatistiklerini göster"""
log("="*80)
log("FİNAL İSTATİSTİKLER")
log("="*80)
# Genel istatistikler
stats = get_table_stats(spark, table_name)
if stats:
log(f"\nGenel İstatistikler:")
log(f" - Dosya sayısı: {stats['file_count']}")
log(f" - Toplam boyut: {format_bytes(stats['total_size'])}")
log(f" - Ortalama dosya boyutu: {format_bytes(stats['avg_file_size'])}")
# Partition bazında istatistikler
log(f"\nPartition Bazında İstatistikler:")
partition_stats = spark.sql(f"""
SELECT
DATE_TRUNC('MONTH', {date_col}) as month,
COUNT(*) as file_count,
ROUND(SUM(file_size_in_bytes) / 1024 / 1024 / 1024, 2) as size_gb,
ROUND(AVG(file_size_in_bytes) / 1024 / 1024, 2) as avg_file_mb
FROM {table_name}.files
GROUP BY DATE_TRUNC('MONTH', {date_col})
ORDER BY month DESC
LIMIT 10
""")
partition_stats.show(truncate=False)
# Performans önerileri
log("\n" + "="*80)
log("PERFORMANS ÖNERİLERİ")
log("="*80)
log("""
1. Sorgularınızda mutlaka partition filtresi kullanın:
WHERE CONFIRM_DATE >= '2024-01-01' AND CONFIRM_DATE < '2024-02-01'
2. Primary key kolonlarında filtre kullanın (Bloom filter aktif):
WHERE FIRM_CODE = 'XXX' AND COMPANY_CODE = 'YYY'
3. Broadcast join için küçük tablolar:
df.join(broadcast(small_df), "key")
4. Predicate pushdown için Parquet filtreleri:
spark.sql("SELECT * FROM table WHERE col > 100")
5. Düzenli bakım yapın:
- Ayda bir: expire_snapshots + remove_orphan_files
- 3 ayda bir: rewrite_manifests
- Yeni veri geldiğinde: ilgili partition'ı optimize edin
""")
# ============================================================================
# ANA FONKSİYON
# ============================================================================
def main():
"""Ana optimizasyon fonksiyonu"""
# Spark session
spark = SparkSession.builder.getOrCreate()
overall_start = time.time()
try:
# 1. Mevcut durumu kontrol et
row_count, date_range = check_current_state(spark, SOURCE_TABLE, DATE_COL)
# 2. Partition'lı yeni tablo oluştur
create_partitioned_table(spark, SOURCE_TABLE, TARGET_TABLE, DATE_COL, PK_COLS)
# 3. Veriyi ay ay kopyala
migrate_data_by_month(spark, SOURCE_TABLE, TARGET_TABLE, DATE_COL)
# 4. Bloom filter ekle
if ENABLE_BLOOM_FILTER:
add_bloom_filters(spark, TARGET_TABLE, PK_COLS, BLOOM_FILTER_FPP)
# 5. Partition'ları optimize et
optimize_partitions(spark, TARGET_TABLE, DATE_COL, PK_COLS)
# 6. Manifest'leri optimize et
optimize_manifests(spark, TARGET_TABLE)
# 7. Eski snapshot'ları temizle
cleanup_old_snapshots(spark, TARGET_TABLE)
# 8. Tabloları değiştir
swap_tables(spark, SOURCE_TABLE, TARGET_TABLE, TEMP_TABLE)
# 9. Final istatistikler
show_final_stats(spark, SOURCE_TABLE, DATE_COL)
overall_elapsed = time.time() - overall_start
log(f"\n{'='*80}")
log(f"✓ TÜM İŞLEMLER BAŞARIYLA TAMAMLANDI!")
log(f"✓ Toplam süre: {overall_elapsed/60:.1f} dakika")
log(f"{'='*80}")
except Exception as e:
log(f"\n{'='*80}")
log(f"✗ HATA OLUŞTU: {str(e)}")
log(f"{'='*80}")
raise
# ============================================================================
# ÇALIŞTIR
# ============================================================================
if __name__ == "__main__":
main()Editor is loading...
Leave a Comment