Requirements & Scope
Functional Specifications
- Full and Incremental Backups: Full backups capture total file states; incremental backups only capture changes/checksum drifts since the last backup.
- Reconstruct state (Restore): Reconstruct file system directories at specific backup states by rolling up the FULL backup and subsequent incremental blocks in order.
- Dependency Protection (Retention Policy): Safely purge aged backups without breaking intermediate chain links required by active incremental sets.
- Recovery Point Objective (RPO) monitoring: Continuously track time drift since the last backup and flag alarms when data loss limits are exceeded.
Backup Manager Class Architecture
Structural layout of manifests, file metadata tracking, and restore handlers:
Loading...
Design Patterns
- Memento Pattern (Backup Manifests): Saves file snapshots at key timestamps so users can roll back states without saving entire directory sets in redundant spaces.
- Chain of Responsibility (Reconstruction Rollups): Links incremental units sequentially, resolving dependencies by rolling down parent references in order.
Execution Workflows
Incremental Backup Validation Workflow
- Trigger Check: Query the backup registry to locate the latest successful backup ID.
- Checksum Check: Run comparisons against each file's size and contents.
- Isolate Delta: Collect files with mismatching checksums or files missing in the previous backup's snapshot.
- Link Parent: Generate an incremental block and link it to the preceding backup ID.
Clean Code Blueprint
Production reference implementations demonstrating incremental comparisons, backup registries, chain recoveries, and dependency retention guards:
// โโโ JAVA BLUEPRINT โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
import java.util.*;
import java.util.concurrent.*;
enum BackupType { FULL, INCREMENTAL }
class BackupManager {
private final Map<String, BackupManifest> backupRegistry = new ConcurrentHashMap<>();
private final ConcurrentLinkedQueue<BackupManifest> backupHistory = new ConcurrentLinkedQueue<>();
private final long rpoThresholdMs;
public BackupManager(long rpoThresholdMs) {
this.rpoThresholdMs = rpoThresholdMs;
}
public synchronized BackupManifest performBackup(String sourceId, BackupType type, Map<String, FileMetadata> activeFiles) {
String lastBackupId = getLatestBackupId(sourceId);
BackupManifest manifest;
if (type == BackupType.FULL) {
manifest = new BackupManifest(UUID.randomUUID().toString(), sourceId, BackupType.FULL, System.currentTimeMillis(), null);
for (FileMetadata file : activeFiles.values()) {
manifest.addFile(file);
}
} else {
// INCREMENTAL: Find changes since last backup
if (lastBackupId == null) {
throw new IllegalStateException("Cannot run INCREMENTAL backup without a prior FULL backup.");
}
manifest = new BackupManifest(UUID.randomUUID().toString(), sourceId, BackupType.INCREMENTAL, System.currentTimeMillis(), lastBackupId);
BackupManifest lastManifest = backupRegistry.get(lastBackupId);
Map<String, String> lastState = resolveState(lastBackupId);
for (FileMetadata file : activeFiles.values()) {
String lastHash = lastState.get(file.getPath());
if (lastHash == null || !lastHash.equals(file.getChecksum())) {
// File is new or changed
manifest.addFile(file);
}
}
}
backupRegistry.put(manifest.getId(), manifest);
backupHistory.add(manifest);
return manifest;
}
public Map<String, String> resolveState(String backupId) {
BackupManifest target = backupRegistry.get(backupId);
if (target == null) throw new IllegalArgumentException("Backup not found.");
List<BackupManifest> chain = new ArrayList<>();
BackupManifest current = target;
while (current != null) {
chain.add(0, current);
if (current.getType() == BackupType.FULL) {
break;
}
if (current.getParentBackupId() == null) {
throw new IllegalStateException("Broken backup chain.");
}
current = backupRegistry.get(current.getParentBackupId());
}
Map<String, String> restored = new HashMap<>();
for (BackupManifest manifest : chain) {
if (manifest.getType() == BackupType.FULL) {
restored.clear();
}
for (FileMetadata file : manifest.getFiles().values()) {
restored.put(file.getPath(), file.getChecksum());
}
}
return restored;
}
public void applyRetention(int keepCount) {
// Simple retention keeping last N backups, protecting intermediate incremental dependencies
if (backupHistory.size() <= keepCount) return;
Set<String> activeIds = new HashSet<>();
List<BackupManifest> history = new ArrayList<>(backupHistory);
// Keep the latest N backups and trace all dependencies they rely on
int start = history.size() - keepCount;
for (int i = start; i < history.size(); i++) {
BackupManifest m = history.get(i);
activeIds.add(m.getId());
// Trace parent dependencies back to FULL
String parentId = m.getParentBackupId();
while (parentId != null) {
activeIds.add(parentId);
BackupManifest parent = backupRegistry.get(parentId);
parentId = (parent != null) ? parent.getParentBackupId() : null;
}
}
// Delete manifests not in dependency set
for (BackupManifest m : history) {
if (!activeIds.contains(m.getId())) {
backupRegistry.remove(m.getId());
backupHistory.remove(m);
System.out.printf("Retention pruned unneeded backup: %s%n", m.getId());
}
}
}
public long getRpoStatusMs(String sourceId) {
String latestId = getLatestBackupId(sourceId);
if (latestId == null) return Long.MAX_VALUE;
return System.currentTimeMillis() - backupRegistry.get(latestId).getTimestamp();
}
private String getLatestBackupId(String sourceId) {
BackupManifest latest = null;
for (BackupManifest m : backupHistory) {
if (m.getSourceId().equals(sourceId)) {
latest = m;
}
}
return (latest != null) ? latest.getId() : null;
}
public static class FileMetadata {
private final String path;
private final String checksum;
private final long lastModified;
public FileMetadata(String path, String checksum, long lastModified) {
this.path = path;
this.checksum = checksum;
this.lastModified = lastModified;
}
public String getPath() { return path; }
public String getChecksum() { return checksum; }
public long getLastModified() { return lastModified; }
}
public static class BackupManifest {
private final String id;
private final String sourceId;
private final BackupType type;
private final long timestamp;
private final String parentBackupId;
private final Map<String, FileMetadata> files = new HashMap<>();
public BackupManifest(String id, String sourceId, BackupType type, long timestamp, String parentBackupId) {
this.id = id;
this.sourceId = sourceId;
this.type = type;
this.timestamp = timestamp;
this.parentBackupId = parentBackupId;
}
public void addFile(FileMetadata file) { files.put(file.getPath(), file); }
public String getId() { return id; }
public String getSourceId() { return sourceId; }
public BackupType getType() { return type; }
public long getTimestamp() { return timestamp; }
public String getParentBackupId() { return parentBackupId; }
public Map<String, FileMetadata> getFiles() { return files; }
}
}
public class Main {
public static void main(String[] args) {
System.out.println("=== BACKUP MANAGER DEMO (JAVA) ===");
BackupManager manager = new BackupManager(300000); // 5 min RPO limit
Map<String, BackupManager.FileMetadata> filesState1 = new HashMap<>();
filesState1.put("index.html", new BackupManager.FileMetadata("index.html", "hash-index-1", System.currentTimeMillis()));
filesState1.put("style.css", new BackupManager.FileMetadata("style.css", "hash-style-1", System.currentTimeMillis()));
// Perform first FULL backup
BackupManager.BackupManifest fullBackup = manager.performBackup("source-1", BackupType.FULL, filesState1);
System.out.println("FULL Backup created with ID: " + fullBackup.getId() + " containing " + fullBackup.getFiles().size() + " files.");
// Modify a file and create a new file
Map<String, BackupManager.FileMetadata> filesState2 = new HashMap<>(filesState1);
filesState2.put("index.html", new BackupManager.FileMetadata("index.html", "hash-index-2", System.currentTimeMillis())); // Modified
filesState2.put("app.js", new BackupManager.FileMetadata("app.js", "hash-app-1", System.currentTimeMillis())); // Added new
// Perform first INCREMENTAL backup
BackupManager.BackupManifest incBackup = manager.performBackup("source-1", BackupType.INCREMENTAL, filesState2);
System.out.println("INCREMENTAL Backup created with ID: " + incBackup.getId() + " containing " + incBackup.getFiles().size() + " changed/new files.");
// Resolve state of the incremental backup
System.out.println("\nResolving restored state for " + incBackup.getId() + ":");
Map<String, String> restored = manager.resolveState(incBackup.getId());
for (Map.Entry<String, String> entry : restored.entrySet()) {
System.out.println("File: " + entry.getKey() + " -> Hash: " + entry.getValue());
}
// Check RPO Status
System.out.println("\nRPO Status (ms since last backup): " + manager.getRpoStatusMs("source-1"));
}
}
Interactive Simulator
โ Recovery-Point-Aware Backup Manager
Full & incremental backup sets, restoration dependency chains, and automated RPO monitors.
โฑ RPO Status: โ ๏ธ DATA LOSS DANGER: LIMIT EXCEEDED
Safe backup interval limit: 5 min. Time elapsed since last backup: 6.7 min.
๐ Active Local Directory State
File PathActive HashLast Modified
index.htmlhash-index-15:32:35 AM
style.csshash-style-15:34:15 AM
app.jshash-app-15:35:55 AM
๐ฅ Create New File
โ๏ธ Modify File State
๐ Backups Registry (Dependency chain)
No backups created yet.
Backup System Auditing Logs
[13:50:00] In-memory incremental backup agent started.
[13:50:02] RPO monitor online. Safe data window set to 5 minutes.
Review
Help Us Improve
How helpful was this walkthrough?
Click a star to rate. We actively use this feedback to refine and update our system design content.
Placeholder
Discussion
Share your thoughts, ask questions, or help others.
Loading comments...