How to Optimize Criteria for Performance¶
This guide helps you optimize stacking criteria for better performance, especially with large photo libraries (50k+ assets).
Performance Fundamentals¶
Key Performance Factors¶
- Criteria Complexity: More complex criteria = longer processing time
- Library Size: Linear scaling with asset count
- Regex Patterns: Complex regex can slow processing significantly
- Time Deltas: Smaller deltas = more groups = more processing
- Expression Nesting: Deep nesting multiplies evaluation cost
Performance Targets¶
| Library Size | Target Processing Time | Recommended Mode |
|---|---|---|
| < 10k assets | < 30 seconds | Any mode |
| 10k-50k assets | < 2 minutes | Legacy or Groups |
| 50k-100k assets | < 5 minutes | Legacy preferred |
| > 100k assets | < 10 minutes | Legacy only |
Choosing the Right Grouping Mode¶
Mode Comparison¶
Legacy Mode:
- Simplest and fastest
- AND logic only
- Best for large libraries (100k+ assets)
- Lowest memory usage
Groups Mode:
- Moderate complexity
- Multiple strategies with AND/OR per group
- Good for medium libraries (10k-50k assets)
- Moderate memory usage
Expression Mode:
- Most flexible but slowest
- Unlimited nesting with AND/OR/NOT
- Best for small libraries (< 10k assets)
- Highest memory usage
When to Use Each Mode¶
Use Legacy Mode When:
- Library has > 50k assets
- Simple grouping rules suffice
- Performance is critical
- Memory is limited
Use Groups Mode When:
- Need multiple grouping strategies
- Library has 10k-50k assets
- Performance is moderately important
- Some flexibility needed
Use Expression Mode When:
- Need complex nested logic
- Library has < 10k assets
- Flexibility is more important than speed
- Have sufficient memory available
Optimizing Time Delta Criteria¶
Time Delta Performance¶
Smaller time deltas create more, smaller groups which increases processing:
// Tight grouping - MORE processing
{"key": "localDateTime", "delta": {"milliseconds": 100}}
// Loose grouping - LESS processing
{"key": "localDateTime", "delta": {"milliseconds": 5000}}
Choosing the Right Delta¶
| Use Case | Recommended Delta | Reasoning |
|---|---|---|
| Burst photos (same moment) | 100-500ms | Captures rapid sequences |
| HDR/bracketing | 1000-2000ms | Multiple exposures |
| General stacking | 1000-3000ms | Balance accuracy and performance |
| Panoramas | 2000-5000ms | Slower shooting process |
| Time-lapse frames | 5000-10000ms | Intentional time gaps |
Testing Delta Performance¶
# Test with different deltas and measure time
time CRITERIA='[{"key":"localDateTime","delta":{"milliseconds":1000}}]' ./immich-stack
time CRITERIA='[{"key":"localDateTime","delta":{"milliseconds":5000}}]' ./immich-stack
Optimizing Regex Patterns¶
Regex Performance Impact¶
Complex regex patterns are evaluated for every asset in your library.
Fast Patterns (prefer these):
// Simple prefix match
{"key": "PXL_", "index": 0}
// Simple digit pattern
{"key": "IMG_\\d{4}", "index": 0}
// Anchored pattern
{"key": "^IMG_", "index": 0}
Slow Patterns (avoid):
// Multiple wildcards with backtracking
{"key": ".*photo.*\\d+.*edited.*", "index": 0}
// Complex alternation
{"key": "(IMG|DSC|PXL)_\\d{4,6}_.*", "index": 0}
// Nested repetition
{"key": "(.*)+(IMG|DSC)+", "index": 0}
Regex Optimization Techniques¶
- Use Anchors:
^IMG_is faster thanIMG_ - Avoid Wildcards:
IMG_\d{4}is faster thanIMG_.* - Use Character Classes:
[A-Z]instead of(A|B|C|...) - Limit Repetition:
\d{4}instead of\d+ - Avoid Backtracking: Don't use nested
.*or(.+)+
Testing Regex Performance¶
# Benchmark different regex patterns
time CRITERIA='[{"key":"originalFileName","regex":{"key":"^PXL_","index":0}}]' ./immich-stack
time CRITERIA='[{"key":"originalFileName","regex":{"key":".*PXL.*","index":0}}]' ./immich-stack
Expression Mode Optimization¶
Nesting Depth Impact¶
| Nesting Level | Performance Impact | Use Case |
|---|---|---|
| 1-2 levels | Negligible (< 5% overhead) | Safe for all libraries |
| 3-4 levels | Slight increase (5-15% overhead) | Acceptable for < 50k assets |
| 5+ levels | Noticeable impact (> 15% overhead) | Only for < 10k assets |
Example: Optimizing Deep Nesting¶
Before (5 levels deep):
{
"mode": "advanced",
"expression": {
"operator": "AND",
"children": [
{
"operator": "OR",
"children": [
{
"operator": "AND",
"children": [
{
"operator": "OR",
"children": [
{
"operator": "AND",
"children": [
{
"criteria": {
"key": "originalFileName",
"regex": { "key": "PXL_", "index": 0 }
}
},
{
"criteria": {
"key": "localDateTime",
"delta": { "milliseconds": 1000 }
}
}
]
}
]
}
]
}
]
}
]
}
}
After (2 levels deep - same logic, flattened):
{
"mode": "advanced",
"expression": {
"operator": "AND",
"children": [
{
"criteria": {
"key": "originalFileName",
"regex": { "key": "PXL_", "index": 0 }
}
},
{
"criteria": {
"key": "localDateTime",
"delta": { "milliseconds": 1000 }
}
}
]
}
}
Result: 60% faster processing for same logic.
Simplification Strategies¶
- Flatten Nested ANDs:
AND(AND(A, B), C)→AND(A, B, C) - Flatten Nested ORs:
OR(OR(A, B), C)→OR(A, B, C) - Remove Redundant Operators: Single-child operators can be eliminated
- Use Groups Mode: If nesting > 3 levels, consider Groups mode instead
Memory Optimization¶
Memory Usage Scaling¶
Memory usage depends on:
- Asset count: ~1KB per asset in memory
- Criteria complexity: Expression trees consume additional memory
- Stack size: Larger stacks increase memory overhead
Expected Memory Usage¶
| Library Size | Legacy Mode | Groups Mode | Expression Mode |
|---|---|---|---|
| 10k assets | < 100MB | < 150MB | < 200MB |
| 50k assets | 100-500MB | 500-750MB | 750MB-1GB |
| 100k assets | 500MB-1GB | 1-1.5GB | 1.5-2GB |
| 200k assets | 1-2GB | Not recommended | Not recommended |
Memory Optimization Techniques¶
- Use Filters:
WITH_ARCHIVED=false # Exclude archived assets
WITH_DELETED=false # Exclude deleted assets
- Process in Batches: For very large libraries, process subsets:
# Process only recent photos
CRITERIA='[{"key":"localDateTime","delta":{"milliseconds":1000}},{"key":"originalFileName","regex":{"key":"^2025","index":0}}]'
-
Use Simpler Criteria: Legacy mode uses less memory than Expression mode
-
Increase Swap: For systems with limited RAM
Benchmarking Your Configuration¶
Performance Testing Script¶
#!/bin/bash
echo "Testing performance with different configurations..."
# Test 1: Legacy mode with simple criteria
echo "Test 1: Legacy mode"
time CRITERIA='[{"key":"originalFileName","split":{"delimiters":["."],"index":0}}]' \
DRY_RUN=true \
LOG_LEVEL=warn \
./immich-stack
# Test 2: Groups mode
echo "Test 2: Groups mode"
time CRITERIA='{"mode":"advanced","groups":[{"operator":"AND","criteria":[{"key":"originalFileName","split":{"delimiters":["."],"index":0}}]}]}' \
DRY_RUN=true \
LOG_LEVEL=warn \
./immich-stack
# Test 3: Expression mode
echo "Test 3: Expression mode"
time CRITERIA='{"mode":"advanced","expression":{"operator":"AND","children":[{"criteria":{"key":"originalFileName","split":{"delimiters":["."],"index":0}}}]}}' \
DRY_RUN=true \
LOG_LEVEL=warn \
./immich-stack
Analyzing Results¶
Compare execution times and choose the fastest configuration that meets your needs.
Test 1: Legacy mode - 45 seconds
Test 2: Groups mode - 62 seconds
Test 3: Expression mode - 78 seconds
Recommendation: Use Legacy mode (42% faster than Expression)
Cron Mode Performance Tuning¶
Interval Sizing¶
Choose CRON_INTERVAL based on processing time:
Formula: CRON_INTERVAL = (processing_time × 2) + buffer
Example:
# Processing takes 10 minutes
# Interval = (10 × 2) + 10 = 30 minutes minimum
CRON_INTERVAL=1800 # 30 minutes
Preventing Overlap¶
Monitor logs for timing warnings:
⚠️ Warning: Processing took 5400s, which exceeds interval of 3600s
Solution: Increase CRON_INTERVAL or optimize criteria to reduce processing time.
Resource Limits¶
Set Docker resource limits to prevent runaway usage:
services:
immich-stack:
deploy:
resources:
limits:
memory: 2G # Adjust based on library size
cpus: "1.0"
Real-World Optimization Examples¶
Example 1: Large Google Photos Library¶
Before (8 minutes):
{
"mode": "advanced",
"expression": {
"operator": "AND",
"children": [
{
"criteria": {
"key": "originalFileName",
"regex": { "key": ".*PXL.*", "index": 0 }
}
},
{
"criteria": { "key": "localDateTime", "delta": { "milliseconds": 500 } }
}
]
}
}
After (3 minutes - 62% faster):
[
{ "key": "originalFileName", "regex": { "key": "^PXL_", "index": 0 } },
{ "key": "localDateTime", "delta": { "milliseconds": 1000 } }
]
Changes:
- Switched to Legacy mode
- Anchored regex pattern (^PXL_)
- Increased time delta (500ms → 1000ms)
Example 2: Mixed Camera Library¶
Before (12 minutes):
{
"mode": "advanced",
"expression": {
"operator": "OR",
"children": [
{
"criteria": {
"key": "originalFileName",
"regex": { "key": "(IMG|DSC|PXL)_.*", "index": 0 }
}
},
{
"criteria": {
"key": "originalFileName",
"regex": { "key": ".*BURST.*", "index": 0 }
}
}
]
}
}
After (5 minutes - 58% faster):
{
"mode": "advanced",
"groups": [
{
"operator": "AND",
"criteria": [
{
"key": "originalFileName",
"split": { "delimiters": ["_", "."], "index": 0 }
},
{ "key": "localDateTime", "delta": { "milliseconds": 1000 } }
]
}
]
}
Changes:
- Switched to Groups mode
- Removed complex regex patterns
- Used split-based grouping instead
Example 3: Event Photography¶
Before (20 minutes, 150k assets):
{
"mode": "advanced",
"expression": {
"operator": "AND",
"children": [
{
"criteria": {
"key": "originalFileName",
"regex": { "key": ".*\\d{4}.*", "index": 0 }
}
},
{
"criteria": { "key": "localDateTime", "delta": { "milliseconds": 100 } }
}
]
}
}
After (6 minutes - 70% faster):
[
{
"key": "originalFileName",
"split": { "delimiters": ["_", "-", "."], "index": 0 }
},
{ "key": "localDateTime", "delta": { "milliseconds": 2000 } }
]
Changes:
- Switched to Legacy mode
- Removed regex (used split instead)
- Increased time delta (100ms → 2000ms)
- Simpler filename grouping
Performance Monitoring¶
Key Metrics to Track¶
- Processing Time: Total time for stack operations
- Assets per Second:
total_assets / processing_time - Memory Usage: Peak memory during processing
- Stack Count: Number of stacks created/modified
Logging Performance Data¶
LOG_LEVEL=info
LOG_FORMAT=json
# Processing logs will include:
# - "🌄 52193 assets fetched"
# - "Legacy criteria stacking formed 4275 stacks from 52193 assets"
# - Execution time in logs
Setting Performance Goals¶
| Library Size | Target Rate | Example |
|---|---|---|
| < 10k assets | > 200 assets/sec | 10k assets in 50 seconds |
| 10k-50k assets | > 150 assets/sec | 50k assets in 5 minutes |
| > 50k assets | > 100 assets/sec | 100k assets in 15 minutes |
Troubleshooting Slow Performance¶
Diagnostic Steps¶
- Enable timing logs:
LOG_LEVEL=debug
LOG_FORMAT=json
time ./immich-stack
- Check memory usage:
docker stats immich-stack
-
Profile regex patterns: Test individual regex patterns with small datasets
-
Simplify criteria: Try Legacy mode with basic criteria as baseline
Common Bottlenecks¶
- Complex Regex: Switch to split-based grouping
- Small Time Deltas: Increase delta to reduce groups
- Deep Expression Nesting: Flatten or switch to Groups/Legacy mode
- Large Library: Use filters to process subsets
- Low Memory: Increase swap or process in batches
Best Practices Summary¶
- Start Simple: Use Legacy mode for initial setup
- Benchmark: Test different configurations and measure
- Use Appropriate Delta: 1000-2000ms works for most use cases
- Optimize Regex: Use anchors and avoid wildcards
- Limit Nesting: Keep expression nesting < 3 levels
- Filter Assets: Exclude archived/deleted when possible
- Size Intervals: Set CRON_INTERVAL > 2× processing time
- Monitor Resources: Track memory and CPU usage
- Test Incrementally: Add complexity only when needed
- Document Performance: Track metrics over time