Arşivlemede Akıllı Sıkıştırma: Dosya Tipine Göre Dinamik Seviye Belirleme

Yıllarca farklı şirketlerde sistem yöneticiliği yaptıktan sonra şunu fark ettim: çoğu ekip arşivleme işlemlerini hep aynı şekilde yapıyor. Bir script yazılmış, tar -czf ile her şeyi sıkıştırıyor, kimse sorgulamıyor. Oysa bir JPEG dosyasını gzip ile sıkıştırmaya çalışmak hem CPU zamanı harcıyor hem de neredeyse hiç yer kazandırmıyor. Bu yazıda dosya tipine göre dinamik sıkıştırma seviyesi belirlemenin nasıl yapılacağını, hangi araçların ne zaman kullanılacağını ve bunu production ortamında nasıl otomatize edebileceğinizi anlatacağım.

Neden “Herkese Aynı Sıkıştırma” Yaklaşımı Yanlış?

Sıkıştırma algoritmaları temelde entropi üzerine çalışır. Yüksek entropili veriler (görseller, videolar, zaten sıkıştırılmış dosyalar) tekrar sıkıştırmaya direnir. Düşük entropili veriler (log dosyaları, düz metin, XML, JSON) ise inanılmaz oranlarda küçülür.

Şöyle düşünün: 100 GB’lık bir backup aldığınızı varsayalım. Bu backup’ın içinde:

  • 40 GB log dosyası var (gzip ile %70-80 küçülür)
  • 30 GB PNG/JPEG görsel var (gzip ile %2-3 küçülür, ama CPU yanıyor)
  • 20 GB veritabanı dump var (gzip ile %60-65 küçülür)
  • 10 GB video dosyası var (gzip ile neredeyse hiç küçülmez)

Hepsine gzip -9 uygularsanız saatlerce beklersiniz. Akıllıca davranırsanız aynı işlemi çok daha kısa sürede, daha az CPU tüketimiyle ve benzer sonuçla tamamlarsınız.

Dosya Tipini Programatik Olarak Tanımlama

Önce temel aracımızla başlayalım: file komutu. Bu komut dosyanın içeriğine bakarak gerçek tipini söyler, uzantısına değil.

file --mime-type -b resim.jpg
# image/jpeg

file --mime-type -b access.log
# text/plain

file --mime-type -b dump.sql.gz
# application/gzip

file --mime-type -b video.mp4
# video/mp4

MIME tipini aldıktan sonra kategorize edebiliriz. Bunun üzerine bir fonksiyon inşa edelim:

#!/bin/bash

get_compression_strategy() {
    local filepath="$1"
    local mime_type

    mime_type=$(file --mime-type -b "$filepath" 2>/dev/null)

    case "$mime_type" in
        # Zaten sıkıştırılmış formatlar - sıkıştırma uygulamaya gerek yok
        image/jpeg|image/png|image/gif|image/webp|
        video/*|audio/*|
        application/gzip|application/zip|
        application/x-bzip2|application/x-xz|
        application/x-7z-compressed|
        application/pdf)
            echo "store"
            ;;
        # Yüksek sıkıştırma kazancı olan metin tabanlı formatlar
        text/*|application/json|application/xml|
        application/javascript|application/x-yaml)
            echo "high"
            ;;
        # Orta düzey sıkıştırma - veritabanı dump'ları, binary loglar
        application/octet-stream|application/x-sqlite3|
        application/x-tar)
            echo "medium"
            ;;
        # Bilinmeyen tipler için güvenli orta seviye
        *)
            echo "medium"
            ;;
    esac
}

Araç Seçimi ve Seviye Konfigürasyonu

Farklı durumlar için farklı araçlar kullanmak gerekir. İşte pratik bir rehber:

gzip: Hız/oran dengesi için ideal, evrensel uyumluluk

  • -1: En hızlı, en az sıkıştırma
  • -6: Varsayılan, iyi denge
  • -9: En iyi sıkıştırma, yavaş

bzip2: gzip’ten daha iyi sıkıştırma, daha yavaş, paralel desteği sınırlı

  • -1 ile -9: gzip ile benzer mantık

xz: En iyi sıkıştırma oranı, en fazla CPU/RAM tüketimi

  • -0: Çok hızlı, düşük sıkıştırma
  • -6: Varsayılan
  • -9: Maksimum (dikkatli olun, RAM çok tüketir)

zstd (Zstandard): Modern tercih, hız ve oran konusunda mükemmel denge, Facebook geliştirdi

  • -1: Ultra hızlı
  • -3: Varsayılan denge noktası
  • -19: Maksimum sıkıştırma

Şimdi strateji fonksiyonumuzu araç seçimiyle birleştirelim:

#!/bin/bash

compress_file() {
    local input_file="$1"
    local output_dir="${2:-.}"
    local strategy
    local filename
    local output_file

    if [[ ! -f "$input_file" ]]; then
        echo "HATA: Dosya bulunamadı: $input_file" >&2
        return 1
    fi

    strategy=$(get_compression_strategy "$input_file")
    filename=$(basename "$input_file")

    echo "Dosya: $filename | Strateji: $strategy"

    case "$strategy" in
        "store")
            # Sıkıştırma yapma, sadece kopyala veya sembolik link oluştur
            cp "$input_file" "${output_dir}/${filename}"
            echo "  -> Sıkıştırma atlandı (zaten sıkıştırılmış/binary format)"
            ;;
        "high")
            # zstd varsa kullan, yoksa xz ile düşük seviye
            output_file="${output_dir}/${filename}.zst"
            if command -v zstd &>/dev/null; then
                zstd -15 --long -T0 -q "$input_file" -o "$output_file"
            else
                output_file="${output_dir}/${filename}.xz"
                xz -6 -T0 "$input_file" -c > "$output_file"
            fi
            echo "  -> Yüksek sıkıştırma uygulandı: $output_file"
            ;;
        "medium")
            output_file="${output_dir}/${filename}.zst"
            if command -v zstd &>/dev/null; then
                zstd -3 -T0 -q "$input_file" -o "$output_file"
            else
                output_file="${output_dir}/${filename}.gz"
                gzip -6 -c "$input_file" > "$output_file"
            fi
            echo "  -> Orta sıkıştırma uygulandı: $output_file"
            ;;
    esac
}

Gerçek Dünya Senaryosu: Çok Katmanlı Backup Scripti

Bir e-ticaret şirketinde çalışırken günlük backup sürecimiz yaklaşık 4 saat sürüyordu. Dosya tipi bazlı sıkıştırma stratejisi uyguladıktan sonra bu süre 1.5 saate düştü, depolama kullanımı da %30 azaldı. İşte o ortamda geliştirdiğimize benzer bir script:

#!/bin/bash
# smart_archive.sh - Akıllı sıkıştırma ile dizin arşivleme

set -euo pipefail

BACKUP_ROOT="/data/backups"
LOG_FILE="/var/log/smart_archive.log"
DATE=$(date +%Y%m%d_%H%M%S)

# Renk kodları
RED='33[0;31m'
GREEN='33[0;32m'
YELLOW='33[1;33m'
NC='33[0m'

log() {
    local level="$1"
    shift
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] [$level] $*" | tee -a "$LOG_FILE"
}

get_mime() {
    file --mime-type -b "$1" 2>/dev/null || echo "application/octet-stream"
}

is_already_compressed() {
    local mime="$1"
    local compressed_types=(
        "image/jpeg" "image/png" "image/gif" "image/webp" "image/avif"
        "video/mp4" "video/x-matroska" "video/quicktime" "video/x-msvideo"
        "audio/mpeg" "audio/mp4" "audio/ogg" "audio/flac"
        "application/gzip" "application/zip" "application/x-bzip2"
        "application/x-xz" "application/x-7z-compressed" "application/x-rar"
        "application/pdf" "application/epub+zip"
        "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
        "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
    )

    for ct in "${compressed_types[@]}"; do
        if [[ "$mime" == "$ct" ]]; then
            return 0
        fi
    done
    return 1
}

process_directory() {
    local source_dir="$1"
    local archive_name="$2"
    local temp_dir
    temp_dir=$(mktemp -d)

    log "INFO" "İşlem başlatılıyor: $source_dir"

    local total_files=0
    local skipped=0
    local compressed=0
    local original_size=0
    local final_size=0

    # find ile tüm dosyaları tara
    while IFS= read -r -d '' filepath; do
        local mime
        local rel_path
        local dest_path

        mime=$(get_mime "$filepath")
        rel_path="${filepath#$source_dir/}"

        ((total_files++)) || true

        if is_already_compressed "$mime"; then
            # Sıkıştırma olmadan kopyala
            dest_path="${temp_dir}/${rel_path}"
            mkdir -p "$(dirname "$dest_path")"
            cp "$filepath" "$dest_path"
            ((skipped++)) || true
            log "DEBUG" "Atlandı: $rel_path ($mime)"
        else
            # Zstd ile sıkıştır
            dest_path="${temp_dir}/${rel_path}.zst"
            mkdir -p "$(dirname "$dest_path")"

            local level=3
            # Metin dosyaları için daha agresif sıkıştırma
            if [[ "$mime" == text/* ]] || 
               [[ "$mime" == "application/json" ]] || 
               [[ "$mime" == "application/xml" ]]; then
                level=15
            fi

            zstd -${level} --long -T0 -q "$filepath" -o "$dest_path"
            ((compressed++)) || true
            log "DEBUG" "Sıkıştırıldı (level ${level}): $rel_path"
        fi

    done < <(find "$source_dir" -type f -print0)

    # Geçici dizini tar ile arşivle (sıkıştırma yok, çünkü içerikler zaten işlendi)
    local final_archive="${BACKUP_ROOT}/${archive_name}_${DATE}.tar"
    tar -cf "$final_archive" -C "$temp_dir" .

    # Temizlik
    rm -rf "$temp_dir"

    log "INFO" "Tamamlandı: $total_files dosya"
    log "INFO" "  Sıkıştırılan: $compressed | Atlanan: $skipped"
    log "INFO" "  Arşiv: $final_archive"
}

# Ana çalıştırma
if [[ $# -lt 2 ]]; then
    echo "Kullanım: $0 <kaynak_dizin> <arşiv_adı>"
    exit 1
fi

process_directory "$1" "$2"

Entropi Tabanlı Dinamik Seviye Belirleme

İşleri biraz daha ileri götürelim. Bazen MIME tipi tek başına yeterli değil. Özellikle application/octet-stream olarak gelen dosyalar için dosyanın gerçek entropisini ölçmek daha doğru karar vermenizi sağlar.

#!/bin/bash

# Dosya entropisi hesapla (0-8 arası, 8 = tamamen rastgele/sıkıştırılmış)
calculate_entropy() {
    local file="$1"
    local sample_size=65536  # 64KB örnek al

    python3 - "$file" "$sample_size" << 'PYEOF'
import sys
import math
from collections import Counter

filepath = sys.argv[1]
sample_size = int(sys.argv[2])

try:
    with open(filepath, 'rb') as f:
        data = f.read(sample_size)
    
    if not data:
        print("0.0")
        sys.exit(0)
    
    counts = Counter(data)
    total = len(data)
    entropy = 0.0
    
    for count in counts.values():
        prob = count / total
        entropy -= prob * math.log2(prob)
    
    print(f"{entropy:.2f}")
except Exception as e:
    print("4.0")  # Hata durumunda orta değer
PYEOF
}

# Entropiye göre sıkıştırma seviyesi belirle
entropy_to_level() {
    local entropy="$1"
    local tool="${2:-zstd}"

    # Yüksek entropi (7.5+) = zaten sıkıştırılmış gibi, sıkıştırma işe yaramaz
    # Düşük entropi (0-4) = çok iyi sıkıştırılabilir
    # Orta entropi (4-7.5) = makul sıkıştırma

    if (( $(echo "$entropy > 7.5" | bc -l) )); then
        echo "store"
    elif (( $(echo "$entropy > 6.0" | bc -l) )); then
        echo "low"    # zstd -1 veya gzip -3
    elif (( $(echo "$entropy > 4.0" | bc -l) )); then
        echo "medium" # zstd -3 veya gzip -6
    else
        echo "high"   # zstd -15 veya gzip -9
    fi
}

# Test çalıştırması
test_files=(
    "/var/log/syslog"
    "/usr/bin/ls"
    "/boot/vmlinuz"
    "/etc/hosts"
)

for f in "${test_files[@]}"; do
    if [[ -f "$f" ]]; then
        entropy=$(calculate_entropy "$f")
        level=$(entropy_to_level "$entropy")
        echo "$(basename $f): entropi=$entropy | strateji=$level"
    fi
done

Paralel İşleme ile Performansı Artırma

Büyük dizinlerde tek tek dosya işlemek çok yavaş kalır. GNU Parallel ile bunu dramatik şekilde hızlandırabiliriz:

#!/bin/bash
# parallel_smart_compress.sh

JOBS="${1:-$(nproc)}"
SOURCE_DIR="${2:-/data}"
OUTPUT_DIR="${3:-/backup}"

compress_single_file() {
    local filepath="$1"
    local source_base="$2"
    local output_base="$3"

    local mime
    local rel_path
    local dest_dir

    mime=$(file --mime-type -b "$filepath" 2>/dev/null)
    rel_path="${filepath#${source_base}/}"
    dest_dir="${output_base}/$(dirname "$rel_path")"

    mkdir -p "$dest_dir"

    # Yüksek entropi formatları - direkt kopyala
    case "$mime" in
        image/jpeg|image/png|image/gif|video/*|audio/*|
        application/gzip|application/zip|application/x-bzip2|
        application/x-xz|application/pdf)
            cp "$filepath" "${dest_dir}/$(basename "$filepath")"
            echo "COPY: $rel_path"
            return
            ;;
    esac

    # Diğerleri için zstd
    local level=3
    [[ "$mime" == text/* || "$mime" == application/json || 
       "$mime" == application/xml ]] && level=12

    zstd -${level} -T1 -q "$filepath" 
        -o "${dest_dir}/$(basename "$filepath").zst" 2>/dev/null

    echo "COMPRESSED(zstd-${level}): $rel_path"
}

export -f compress_single_file

# GNU Parallel ile paralel işleme
find "$SOURCE_DIR" -type f -print0 | 
    parallel -0 -j "$JOBS" --bar 
    compress_single_file {} "$SOURCE_DIR" "$OUTPUT_DIR"

echo ""
echo "İşlem tamamlandı. Kaynak: $SOURCE_DIR | Hedef: $OUTPUT_DIR"

# Boyut karşılaştırması
echo ""
echo "Boyut raporu:"
echo "  Kaynak: $(du -sh "$SOURCE_DIR" | cut -f1)"
echo "  Hedef:  $(du -sh "$OUTPUT_DIR" | cut -f1)"

Zstd ile Kare Arşivler ve Sözlük Eğitimi

Özellikle çok sayıda küçük benzer dosya (log satırları, JSON kayıtları gibi) arşivlerken zstd’nin sözlük özelliği şaşırtıcı sonuçlar verir:

#!/bin/bash
# Zstd sözlük eğitimi ve kullanımı

DICT_FILE="/tmp/zstd_dict_$(date +%s)"
SAMPLE_DIR="/var/log/nginx"
OUTPUT_DIR="/backup/nginx_logs"

mkdir -p "$OUTPUT_DIR"

# 1. Örnek dosyalardan sözlük eğit
echo "Sözlük eğitiliyor..."
zstd --train 
    --maxdict=112640 
    -o "$DICT_FILE" 
    "${SAMPLE_DIR}"/*.log.1 2>/dev/null || 
    echo "Uyarı: Sözlük eğitimi için yeterli örnek bulunamadı"

# 2. Eğitilmiş sözlük ile sıkıştır
if [[ -f "$DICT_FILE" ]]; then
    echo "Sözlük tabanlı sıkıştırma uygulanıyor..."
    for logfile in "${SAMPLE_DIR}"/*.log; do
        basename_file=$(basename "$logfile")
        zstd -19 -D "$DICT_FILE" 
            -T0 -q "$logfile" 
            -o "${OUTPUT_DIR}/${basename_file}.zst"
    done

    # Sözlüğü de kaydet (decompress için gerekli)
    cp "$DICT_FILE" "${OUTPUT_DIR}/compression.dict"
    echo "Sözlük kaydedildi: ${OUTPUT_DIR}/compression.dict"
else
    # Sözlük yoksa normal sıkıştırma
    for logfile in "${SAMPLE_DIR}"/*.log; do
        basename_file=$(basename "$logfile")
        zstd -15 -T0 -q "$logfile" 
            -o "${OUTPUT_DIR}/${basename_file}.zst"
    done
fi

# Açma komutu hatırlatıcısı
echo ""
echo "Dosyaları açmak için:"
echo "  Sözlük ile: zstd -d -D ${OUTPUT_DIR}/compression.dict dosya.zst"
echo "  Normal:     zstd -d dosya.zst"

Karar Ağacını Yönetilebilir Hale Getirme

Tüm bu mantığı birleştiren, konfigürasyon dosyasından okuyan ve düzgün loglayan production kalitesinde bir yaklaşım:

#!/bin/bash
# /etc/smart_compress.conf içeriği şöyle olabilir:
# STORE_PATTERNS="*.jpg *.jpeg *.png *.mp4 *.gz *.zip *.pdf"
# HIGH_PATTERNS="*.log *.json *.xml *.yaml *.yml *.csv *.sql"
# MEDIUM_PATTERNS="*.db *.bin *.dat"
# DEFAULT_TOOL="zstd"
# PARALLEL_JOBS=4

load_config() {
    local config_file="${1:-/etc/smart_compress.conf}"

    # Varsayılan değerler
    STORE_PATTERNS=("*.jpg" "*.jpeg" "*.png" "*.gif" "*.mp4" "*.mov"
                    "*.avi" "*.mp3" "*.ogg" "*.gz" "*.bz2" "*.xz"
                    "*.zip" "*.7z" "*.rar" "*.pdf")
    HIGH_PATTERNS=("*.log" "*.json" "*.xml" "*.yaml" "*.yml"
                   "*.csv" "*.sql" "*.txt" "*.html" "*.css" "*.js")
    MEDIUM_PATTERNS=("*.db" "*.sqlite" "*.bin" "*.dat" "*.iso")
    DEFAULT_TOOL="zstd"
    PARALLEL_JOBS=$(nproc)

    [[ -f "$config_file" ]] && source "$config_file"
}

classify_by_extension() {
    local filename="$1"

    for pattern in "${STORE_PATTERNS[@]}"; do
        [[ "$filename" == $pattern ]] && echo "store" && return
    done

    for pattern in "${HIGH_PATTERNS[@]}"; do
        [[ "$filename" == $pattern ]] && echo "high" && return
    done

    for pattern in "${MEDIUM_PATTERNS[@]}"; do
        [[ "$filename" == $pattern ]] && echo "medium" && return
    done

    # Uzantı eşleşmezse MIME tipine bak
    local mime
    mime=$(file --mime-type -b "$1" 2>/dev/null)
    
    [[ "$mime" == text/* ]] && echo "high" && return
    [[ "$mime" == image/* || "$mime" == video/* || "$mime" == audio/* ]] && 
        echo "store" && return

    echo "medium"
}

load_config

Sonuç

Arşivleme işlemlerinde “herkese tek tip sıkıştırma” yaklaşımı gerçek bir kaynak israfıdır. Dosya tipine göre akıllı strateji belirlemek; CPU zamanını, I/O bant genişliğini ve depolama alanını aynı anda optimize eder.

Özetlemek gerekirse:

  • JPEG, PNG, MP4, zaten sıkıştırılmış formatlar: Sıkıştırma uygulamayın, direkt depolayın. CPU yakıyorsunuz, kazanacağınız neredeyse sıfır.
  • Log dosyaları, JSON, XML, SQL dump’lar: Agresif sıkıştırma uygulayın. Zstd -12 ile -15 arası veya xz -6 iyi sonuç verir.
  • Binary veriler, veritabanı dosyaları: Orta seviye yeterli. Zstd -3 ile -6 arası makul.
  • Araç seçimi: Zstd’yi modern sisteminiz varsa her zaman tercih edin. Gzip’ten 3-5 kat hızlı, genellikle daha iyi sıkıştırıyor.
  • Paralel işleme: GNU Parallel ile hem çok çekirdekli sistemlerden faydalanın hem de I/O’yu verimli kullanın.
  • Entropi ölçümü: Uzantı ve MIME tipi yetmediğinde 64KB örnek alarak entropi hesaplamak güvenilir fallback mekanizmasıdır.

Bu yaklaşımı production’a almadan önce test ortamınızda farklı veri setleriyle kıyaslama yapmanızı öneririm. Her ortamın dosya dağılımı farklı, ve bu dağılım hangi stratejinin size en fazla kazanç sağlayacağını doğrudan etkiler. Başlangıç olarak mevcut backup dizininizi analiz edin: kaç GB gerçekten sıkıştırılabilir, kaçı zaten binary. Cevap sizi şaşırtabilir.

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir