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.
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"));
}
}