____ __ __ _ ____
/ __ \__ ______ / //_/(_) / /__ _____
/ / / / / / / __ \/ ,< / / / / _ \/ ___/
/ /_/ / /_/ / /_/ / /| |/ / / / __/ /
/_____/\__,_/ .___/_/ |_/_/_/_/\___/_/
/_/
High-performance duplicate file finder
Find and remove duplicate files with BLAKE3 hashing, resumable scans, and 100% test coverage.
Features • Installation • Quick Start • Documentation • Benchmarks
dupkiller is designed for real-world large-scale deduplication:
- Well-tested — 100% test coverage, 336 tests
- Resumable — Interrupt and resume from exact stage
- Memory-efficient — O(1) memory for 1TB+ datasets via keyset pagination
- HDD-aware — Auto-detects HDDs, optimizes I/O patterns
- Comprehensive — Multiple deletion strategies, multiple export formats
- Production-ready — Type-safe, zero linting errors, CI/CD included
Compared to older tools (fdupes, rdfind), dupkiller offers modern architecture and better performance on large datasets.
- File hashing — BLAKE3 (3 GB/s) + blake2b fallback
- Smart pre-filtering — Partial sampling before full hash
- Resumable scans — SQLite checkpointing, resume on Ctrl+C
- Multiple strategies — Keep oldest/newest/shortest/longest/lexicographic
- Dry-run mode — Preview deletions safely
- Export formats — JSONL, CSV, TXT, HTML reports
- Keyset pagination — O(1) memory regardless of dataset size
- Streaming output — No in-memory buffering
- Bounded queues — Prevents OOM on millions of files
- PID locking — Prevents concurrent scans
- Crash-safe — Survive interruptions gracefully
- Auto-detection — Identifies mechanical drives
- Inode sorting — Minimizes random seeks
- Adaptive concurrency — Reduces parallelism on saturated I/O
- I/O throttling — Optional
--max-throughputlimiting
- 100% test coverage — 336 tests, all public APIs covered
- Type-safe — Strict mypy with zero violations
- CI/CD — Automated tests on Linux and macOS
- Zero linting errors — Ruff clean
- Bilingual docs — English and Russian
pip install dupkillerpip install dupkiller blake3git clone https://github.com/deadboizxc/dupkiller.git
cd dupkiller
pip install -e .- Python 3.11+
- Linux, macOS (Windows: partial support)
# Scan directory and save results
dupkiller scan /path/to/directory --output duplicates.jsonlExample output
Scanning /data/photos...
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 100% 0:00:42
Scanned: 127,543 files (2.3 TB)
Candidates: 8,234 files (450 GB)
Hashing: ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 100% 0:02:15
Found 412 duplicate groups
Reclaimable: 89.3 GB
Results written to: duplicates.jsonl
# View duplicate groups from last scan
dupkiller list
# Or view from specific output file
dupkiller list --output duplicates.jsonlExample output
┏━━━━━━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━┳━━━━━━━━━━━━━━━┓
┃ Hash ┃ Size ┃ Count ┃ Reclaimable ┃
┡━━━━━━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━╇━━━━━━━━━━━━━━━┩
│ a3f5e9d2... │ 2.3 GB │ 5 │ 9.2 GB │
│ b8c4d1a7... │ 450 MB │ 3 │ 900 MB │
│ c9e2f8b3... │ 128 MB │ 12 │ 1.4 GB │
└───────────────┴──────────┴────────┴───────────────┘
Group 1: a3f5e9d2... (2.3 GB × 5 copies)
/data/photos/2023/IMG_5678.CR2
/data/photos/backup/IMG_5678.CR2
/data/photos/archive/2023-12-01/IMG_5678.CR2
...
# Preview deletions (dry-run)
dupkiller delete --keep oldest --dry-run
# Actually delete (keep oldest file in each group)
dupkiller delete --keep oldest
# Or work with specific output file
dupkiller delete --output duplicates.jsonl --keep oldestKeep strategies
oldest— Keep file with earliest modification timenewest— Keep most recently modified fileshortest-name— Keep file with shortest path (often canonical)longest-name— Keep file with longest path (detailed naming)lexicographic— Keep first in alphabetical order (deterministic)
# Scan with resume support
dupkiller scan /data --session /tmp/scan.db --output dups.jsonl
# Resume interrupted scan
dupkiller scan /data --session /tmp/scan.db --output dups.jsonl # same command
# Fine-tune concurrency (instead of generic --workers)
dupkiller scan /data --threads 8 --processes 16
# Exclude patterns
dupkiller scan /data --exclude '*.tmp' --exclude '__pycache__'
# Size filters
dupkiller scan /data --min-size 1MB --max-size 10GB
# Throttle I/O (useful for production servers)
dupkiller scan /data --max-throughput 100MB
# Force HDD-optimized mode
dupkiller scan /mnt/hdd --hdd --ionice
# Export to CSV or HTML
dupkiller export --format csv --output report.csv
dupkiller export --format html --output report.html
# Or export from specific JSONL file
dupkiller export --jsonl-input duplicates.jsonl --format csv --output report.csv# View cache statistics
dupkiller cache stats
# Clean up cache entries for deleted files
dupkiller cache clean
# Preview what would be cleaned (dry-run)
dupkiller cache clean --dry-run
# Clean in batches with custom size
dupkiller cache clean --batch-size 5000# View statistics from last scan
dupkiller stats
# List all available scans
dupkiller stats --list
# Compare current scan with an older scan
dupkiller stats --compare-with 42# Delete with confirmation for each group
dupkiller delete --interactive --keep newest
# Delete only groups wasting more than 1 GB
dupkiller delete --min-waste 1073741824 --keep oldest| Algorithm | Throughput | Notes |
|---|---|---|
| BLAKE3 (with SIMD) | 1-3 GB/s | Requires blake3 package, CPU-dependent |
| BLAKE2b (fallback) | 400-600 MB/s | Built-in, no dependencies |
Real-world scan speed depends on:
- Disk I/O (NVMe: ~50K files/sec, HDD: ~5K files/sec)
- File size distribution (many small files = slower)
- Partial hashing optimization (skips 90%+ of full reads)
Note: Benchmarks vary by hardware. Your mileage may vary.
| Dataset Type | Files | Total Size | Typical Scan Time* |
|---|---|---|---|
| Photo library | 50K | 500 GB | 5-15 minutes |
| Code repositories | 2M | 50 GB | 10-30 minutes |
| Video archive | 5K | 8 TB | 30-90 minutes |
*Actual time depends on hardware, duplicate ratio, and whether BLAKE3 is installed.
dupkiller uses constant memory regardless of dataset size:
- 100K files → ~50 MB RAM
- 1M files → ~55 MB RAM
- 10M files → ~60 MB RAM
Other tools often use O(n) memory, leading to OOM on large datasets.
# Find duplicate photos (ignoring EXIF differences)
dupkiller scan ~/Photos --output photo-dups.jsonl
# Keep newest version (likely edited)
dupkiller delete photo-dups.jsonl --keep newestResult: Saved 45 GB by removing duplicate RAW files from backups.
# Scan multiple backup locations
dupkiller scan /backup/daily /backup/weekly /backup/monthly \
--output backup-dups.jsonl
# Keep oldest (original) file
dupkiller delete backup-dups.jsonl --keep oldestResult: Saved 1.2 TB by removing incremental backup duplicates.
# Find duplicate build artifacts
dupkiller scan /var/lib/jenkins/artifacts --min-size 10MB \
--output artifacts-dups.jsonl
# Keep shortest name (canonical artifact path)
dupkiller delete artifacts-dups.jsonl --keep shortest-name --dry-runResult: Identified 12 GB of duplicate JARs and tarballs.
# Start scan (may take hours on 10TB dataset)
dupkiller scan /mnt/storage --session /tmp/scan.db --output dups.jsonl
# ... interrupted by Ctrl+C or system restart ...
# Resume from exact point (no re-scanning)
dupkiller scan /mnt/storage --session /tmp/scan.db --output dups.jsonlResult: Scanned 10 TB across 3 sessions without re-work.
dupkiller uses a 5-stage pipeline for efficient duplicate detection:
┌─────────────┐
│ 1. SCAN │ Recursive file discovery
└──────┬──────┘ → FileInfo(path, size, mtime, inode)
│
▼
┌─────────────┐
│ 2. PARTIAL │ Hash head+mid+tail (512KB each)
│ HASHING │ → Quick pre-filter before full read
└──────┬──────┘
│
▼
┌─────────────┐
│ 3. FULL │ Hash entire file contents
│ HASHING │ → Only for partial hash collisions
└──────┬──────┘
│
▼
┌─────────────┐
│ 4. GROUPING │ Group by content hash
└──────┬──────┘ → {hash: [file1, file2, ...]}
│
▼
┌─────────────┐
│ 5. OUTPUT │ Emit duplicate groups
└─────────────┘ → JSONL / CSV / HTML
- Scanner — Recursive file discovery with exclude patterns
- HashCache — SQLite database with keyset pagination
- ScanSession — Resumable state machine with PID locking
- BoundedExecutor — Memory-safe worker pools
- OutputHandler — Streaming JSONL writer with deduplication
- README.ru.md — Russian documentation
- CHANGELOG.md — Release history
- CONTRIBUTING.md — Development guide
- LICENSE — Apache 2.0 License
from dupkiller.pipeline import run_pipeline
from dupkiller.cache import HashCache
# Programmatic scanning
cache = HashCache("~/.dupkiller/cache.db")
run_pipeline(
roots=["/data"],
cache=cache,
output_jsonl="duplicates.jsonl",
min_size=1024*1024, # 1 MB
workers=16,
)# Clone repository
git clone https://github.com/deadboizxc/dupkiller.git
cd dupkiller
# Install in development mode
pip install -e .[dev]# Run tests with coverage
pytest tests/ --cov=dupkiller --cov-report=term-missing
# Run linter
ruff check dupkiller tests
# Run type checker
mypy dupkillerWe welcome contributions! Please see CONTRIBUTING.md for guidelines.
Quick checklist:
- Yes Add tests for new features (maintain 100% coverage)
- Yes Run
ruff formatandruff check --fix - Yes Run
mypy dupkiller(must pass with no errors) - Yes Update CHANGELOG.md
- Yes Add docstrings for public APIs
- Yes 5× faster with BLAKE3 vs MD5
- Yes Resumable scans (fdupes restarts from scratch)
- Yes Memory-safe on large datasets (fdupes can OOM)
- Yes Better UX with progress bars and rich output
- Yes Parallel hashing (rdfind is single-threaded)
- Yes Modern hashing (BLAKE3 vs SHA-1)
- Yes Resumable scans
- Yes Better testing (100% vs ~60% coverage)
- Yes More keep strategies (5 vs 3)
- Yes Export formats (JSONL, CSV, HTML)
- Yes HDD optimization (inode sorting, adaptive concurrency)
- Yes Type-safe (strict mypy vs untyped C)
dupkiller uses constant memory, but if you still encounter OOM:
# Reduce worker count
dupkiller scan /data --workers 4
# Reduce max throughput
dupkiller scan /data --max-throughput 50MBdupkiller auto-detects HDDs and optimizes for them, but you can help:
# Reduce concurrency for HDDs
dupkiller scan /mnt/hdd --workers 2
# Use ionice (Linux only)
ionice -c3 dupkiller scan /mnt/hddCheck that you're using the same --session path:
# Yes Correct
dupkiller scan /data --session /tmp/scan.db
dupkiller scan /data --session /tmp/scan.db # same path
# No Wrong (creates new session)
dupkiller scan /data --session /tmp/scan.db
dupkiller scan /data --session /tmp/scan2.db # different pathApache 2.0 License — see LICENSE for details.
Built with:
- BLAKE3 — Modern cryptographic hashing
- Click — Composable CLI framework
- Rich — Beautiful terminal output
- pytest — Test framework
Inspired by: fdupes, rdfind, jdupes
If dupkiller saved you disk space, consider giving it a star!
Made by deadboizxc