The hardware and bandwidth for this mirror is donated by dogado GmbH, the Webhosting and Full Service-Cloud Provider. Check out our Wordpress Tutorial.
If you wish to report a bug, or if you are interested in having us mirror your free-software or open-source project, please feel free to contact us at mirror[@]dogado.de.
SafeMapper achieves fault tolerance through three core mechanisms:
This article provides an in-depth explanation of how these mechanisms work.
┌─────────────────────────────────────────────────────────────────────────────┐
│ SafeMapper System Architecture │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ User Code Layer │
│ ┌──────────────────────────────────────────────────────────────────┐ │
│ │ s_map() s_map2() s_pmap() s_future_map() s_walk() ... │ │
│ └─────────────────────────────┬────────────────────────────────────┘ │
│ │ │
│ Core Engine Layer ▼ │
│ ┌──────────────────────────────────────────────────────────────────┐ │
│ │ .safe_execute() │ │
│ │ ┌────────────┐ ┌────────────┐ ┌────────────┐ ┌────────────┐ │ │
│ │ │Fingerprint │ │ Checkpoint │ │ Batch │ │ Error │ │ │
│ │ │ Generator │ │ Manager │ │ Processor │ │ Retry │ │ │
│ │ └────────────┘ └────────────┘ └────────────┘ └────────────┘ │ │
│ └─────────────────────────────┬────────────────────────────────────┘ │
│ │ │
│ Storage Layer ▼ │
│ ┌──────────────────────────────────────────────────────────────────┐ │
│ │ R User Cache Directory (~/.cache/R/SafeMapper/) │ │
│ │ └── checkpoints/ │ │
│ │ ├── session_abc123.rds │ │
│ │ └── session_def456.rds │ │
│ └──────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
A fingerprint is a unique identifier that identifies a specific computational task. SafeMapper automatically generates fingerprints by analyzing input data characteristics.
┌─────────────────────────────────────────────────────────────────┐
│ Fingerprint Generation │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Input Data │
│ [1, 2, 3, ..., 1000] │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────┐ │
│ │ Extract Features │ │
│ │ ┌─────────────────────────────────────┐ │ │
│ │ │ mode: "map" │ │ │
│ │ │ length: 1000 │ │ │
│ │ │ class: "numeric" │ │ │
│ │ │ first: 1 │ │ │
│ │ │ last: 1000 │ │ │
│ │ └─────────────────────────────────────┘ │ │
│ └───────────────────┬─────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────┐ │
│ │ xxhash64 Hash Calculation │ │
│ └───────────────────┬─────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────┐ │
│ │ Fingerprint: "map_7a3b9c2d1e8f4a5b" │ │
│ └─────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
# These two calls will generate the same fingerprint
data <- 1:100
result1 <- s_map(data, ~ .x^2)
#> [1%] Processing items 1-100 of 100
#> Completed 100 items
# If re-run immediately, same fingerprint will be detected
# This call generates a different fingerprint (different data)
data2 <- 1:200
result2 <- s_map(data2, ~ .x^2)
#> [0%] Processing items 1-100 of 200
#> [50%] Processing items 101-200 of 200
#> Completed 200 itemsWhy not hash the entire dataset?
For more precise control, you can manually specify a session ID:
┌─────────────────────────────────────────────────────────────────┐
│ Checkpoint File Structure │
├─────────────────────────────────────────────────────────────────┤
│ │
│ checkpoint_file.rds │
│ │ │
│ ├── results: list() │
│ │ └── [1], [2], [3], ..., [completed items] │
│ │ │
│ └── metadata: list() │
│ ├── session_id: "map_7a3b9c2d..." │
│ ├── total_items: 1000 │
│ ├── completed_items: 500 │
│ ├── mode: "map" │
│ ├── created: "2026-01-23 10:30:00" │
│ └── last_updated: "2026-01-23 10:35:00" │
│ │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ Batch Processing & Checkpoints │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Data: [1, 2, 3, ..., 1000] Batch Size: 100 │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Batch 1: [1-100] │ │
│ │ │ │ │
│ │ ├── Complete ──────────► 💾 Save checkpoint (100) │ │
│ │ ▼ │ │
│ │ Batch 2: [101-200] │ │
│ │ │ │ │
│ │ ├── Complete ──────────► 💾 Save checkpoint (200) │ │
│ │ ▼ │ │
│ │ Batch 3: [201-300] │ │
│ │ │ │ │
│ │ └── ❌ INTERRUPTED! │ │
│ │ │ │
│ │ 💾 Checkpoint saved: 200 items completed │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ Auto-Recovery Flow │
├─────────────────────────────────────────────────────────────────┤
│ │
│ s_map(data, func) is called │
│ │ │
│ ▼ │
│ ┌─────────────────────┐ │
│ │ Generate Fingerprint│ │
│ │ "map_7a3b9c2d..." │ │
│ └──────────┬──────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────┐ ┌─────────────────────┐ │
│ │ Checkpoint Exists? │── Yes ─►│ Validate Checkpoint │ │
│ └──────────┬──────────┘ │ - Data length match?│ │
│ │ │ - File not corrupt? │ │
│ │ No └──────────┬──────────┘ │
│ │ │ │
│ ▼ │ Valid │
│ ┌─────────────────────┐ │ │
│ │ Start Fresh │ ▼ │
│ │ start_idx = 1 │ ┌─────────────────────┐ │
│ └──────────┬──────────┘ │ Resume Progress │ │
│ │ │ "Resuming from 200" │ │
│ │ │ start_idx = 201 │ │
│ │ └──────────┬──────────┘ │
│ │ │ │
│ └───────────────┬───────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────┐ │
│ │ Continue Batch │ │
│ │ Processing │ │
│ └──────────┬──────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────┐ │
│ │ Delete Checkpoint │ │
│ │ Return Results │ │
│ └─────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
# Simulate a task that might be interrupted
simulate_task <- function(x) {
Sys.sleep(0.01)
x^2
}
# First run
result <- s_map(1:30, simulate_task, .session_id = "recovery_demo")
#> [3%] Processing items 1-30 of 30
#> Completed 30 items
# If task is interrupted, simply re-run the same code:
# result <- s_map(1:30, simulate_task, .session_id = "recovery_demo")
# Output: "Resuming from item XX/30"┌─────────────────────────────────────────────────────────────────┐
│ Error Retry Mechanism │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Processing Batch [101-200] │
│ │ │
│ ▼ │
│ ┌─────────────────────┐ │
│ │ Attempt 1 │ │
│ │ ❌ Error: Timeout │ │
│ └──────────┬──────────┘ │
│ │ │
│ ▼ Wait 1 second │
│ ┌─────────────────────┐ │
│ │ Attempt 2 │ │
│ │ ❌ Error: Server Busy│ │
│ └──────────┬──────────┘ │
│ │ │
│ ▼ Wait 1 second │
│ ┌─────────────────────┐ │
│ │ Attempt 3 │ │
│ │ ✅ Success! │ │
│ └──────────┬──────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────┐ │
│ │ Save Checkpoint │ │
│ │ Continue Next Batch │ │
│ └─────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
SafeMapper uses R’s standard user cache directory to store checkpoints:
┌─────────────────────────────────────────────────────────────────────────────┐
│ SafeMapper Complete Execution Flow │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌───────────────────────────────────────────────────────────────────┐ │
│ │ User Invocation │ │
│ │ s_map(data, func, .session_id = NULL) │ │
│ └───────────────────────────────┬───────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌───────────────────────────────────────────────────────────────────┐ │
│ │ 1. Generate Fingerprint │ │
│ │ session_id = .make_fingerprint(data, "map") │ │
│ └───────────────────────────────┬───────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌───────────────────────────────────────────────────────────────────┐ │
│ │ 2. Try Recovery │ │
│ │ restored = .try_restore(session_id, length(data)) │ │
│ │ ├── Has checkpoint ──► results = restored$results │ │
│ │ │ start_idx = completed_items + 1 │ │
│ │ └── No checkpoint ───► results = vector("list", n) │ │
│ │ start_idx = 1 │ │
│ └───────────────────────────────┬───────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌───────────────────────────────────────────────────────────────────┐ │
│ │ 3. Batch Processing Loop │ │
│ │ for batch in batches(start_idx:n): │ │
│ │ │ │ │
│ │ ├── Show progress: "[XX%] Processing items X-Y of N" │ │
│ │ │ │ │
│ │ ├── Execute batch (with retry) │ │
│ │ │ batch_results = .execute_batch_with_retry(...) │ │
│ │ │ │ │
│ │ ├── Store results │ │
│ │ │ results[batch_indices] = batch_results │ │
│ │ │ │ │
│ │ └── Save checkpoint │ │
│ │ .save_checkpoint(session_id, results, ...) │ │
│ └───────────────────────────────┬───────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌───────────────────────────────────────────────────────────────────┐ │
│ │ 4. Completion │ │
│ │ .cleanup_checkpoint(session_id) # Delete checkpoint │ │
│ │ message("Completed N items") │ │
│ │ return(.format_output(results, output_type)) │ │
│ └───────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
SafeMapper follows these design principles:
┌─────────────────────────────────────────────────────────────────┐
│ Design Principles │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 1. Zero Configuration First │
│ ├── Works out of the box, no setup required │
│ └── All configuration is optional │
│ │
│ 2. Non-Invasive │
│ ├── API fully compatible with purrr/furrr │
│ ├── Just change function name, no code restructuring │
│ └── Can switch back to native purrr anytime │
│ │
│ 3. Transparent Operation │
│ ├── Checkpoints managed automatically │
│ ├── Users don't need to worry about details │
│ └── Automatic cleanup after success │
│ │
│ 4. Safe and Reliable │
│ ├── Save per batch, minimize data loss │
│ ├── Automatic error retry │
│ └── Corrupted checkpoints safely ignored │
│ │
└─────────────────────────────────────────────────────────────────┘
These binaries (installable software) and packages are in development.
They may not be fully stable and should be used with caution. We make no claims about them.
Health stats visible at Monitor.