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.
