Building Data Deletion Infrastructure
Building Data Deletion Infrastructure
Implementing the right to deletion (GDPR) or right to delete (CCPA) requires sophisticated infrastructure that can identify all instances of user data across systems, delete data completely while maintaining referential integrity, handle exceptions for legal obligations, and provide proof of deletion. The complexity increases with distributed systems, microservices architectures, and backup retention requirements.
Deletion must be comprehensive yet selective. Some data might need retention for legal obligations (financial records, legal holds) while other data must be deleted. Anonymous or aggregated data might remain after deletion if it cannot be linked to individuals. The system must track what was deleted, what was retained and why, creating an audit trail for compliance demonstration.
// Comprehensive data deletion system
class DataDeletionService {
constructor() {
this.deletionStrategies = new Map();
this.retentionExceptions = new RetentionExceptionHandler();
this.deletionVerifier = new DeletionVerifier();
this.setupStrategies();
}
// Define deletion strategies for different data types
setupStrategies() {
// User profile data
this.deletionStrategies.set('user_profile', {
primary: async (userId) => {
await this.db.transaction(async (trx) => {
// Soft delete first for recovery window
await trx('users')
.where('id', userId)
.update({
deleted_at: new Date(),
deletion_requested: true
});
// Schedule hard delete
await this.scheduleHardDelete('users', userId, 30); // 30 days
});
},
cascade: ['user_preferences', 'user_sessions', 'user_devices']
});
// User content
this.deletionStrategies.set('user_content', {
primary: async (userId) => {
// Delete user-generated content
const content = await this.db('user_content')
.where('user_id', userId)
.select('id', 'type', 'storage_path');
for (const item of content) {
// Delete from storage
if (item.storage_path) {
await this.deleteFromStorage(item.storage_path);
}
// Delete database record
await this.db('user_content')
.where('id', item.id)
.delete();
}
},
cascade: ['content_metadata', 'content_analytics']
});
// Analytics data
this.deletionStrategies.set('analytics', {
primary: async (userId) => {
// Anonymize rather than delete
await this.db('analytics_events')
.where('user_id', userId)
.update({
user_id: this.generateAnonymousId(),
ip_address: '0.0.0.0',
user_agent: 'ANONYMIZED'
});
},
cascade: []
});
}
// Process deletion request
async processDeletionRequest(request) {
const deletionReport = {
requestId: request.id,
userId: request.userId,
startTime: new Date().toISOString(),
deletedCategories: [],
retainedCategories: [],
errors: []
};
try {
// Identify all data locations
const dataMap = await this.mapUserData(request.userId);
// Check for retention exceptions
const exceptions = await this.retentionExceptions.check(request.userId);
// Process each data category
for (const [category, locations] of dataMap.entries()) {
try {
if (exceptions.has(category)) {
deletionReport.retainedCategories.push({
category,
reason: exceptions.get(category).reason,
retentionUntil: exceptions.get(category).until
});
// Anonymize instead of delete
await this.anonymizeCategory(category, request.userId);
} else {
// Execute deletion strategy
await this.executeeDeletion(category, request.userId);
// Verify deletion
const verified = await this.deletionVerifier.verify(category, request.userId);
deletionReport.deletedCategories.push({
category,
locations: locations.length,
verified
});
}
} catch (error) {
deletionReport.errors.push({
category,
error: error.message
});
}
}
// Handle cross-system deletion
await this.propagateDeletion(request.userId);
// Clear caches
await this.clearUserCaches(request.userId);
// Generate deletion certificate
const certificate = await this.generateDeletionCertificate(deletionReport);
deletionReport.completedAt = new Date().toISOString();
deletionReport.certificate = certificate;
return deletionReport;
} catch (error) {
deletionReport.errors.push({
category: 'general',
error: error.message
});
throw error;
}
}
// Map all user data locations
async mapUserData(userId) {
const dataMap = new Map();
// Query all databases
const databases = [
{ name: 'primary', tables: ['users', 'user_content', 'user_activity'] },
{ name: 'analytics', tables: ['events', 'sessions', 'conversions'] },
{ name: 'support', tables: ['tickets', 'conversations'] }
];
for (const db of databases) {
for (const table of db.tables) {
const hasData = await this.checkTableForUser(db.name, table, userId);
if (hasData) {
if (!dataMap.has(table)) {
dataMap.set(table, []);
}
dataMap.get(table).push({ database: db.name, table });
}
}
}
// Check file storage
const fileStorage = await this.mapFileStorage(userId);
if (fileStorage.length > 0) {
dataMap.set('files', fileStorage);
}
// Check cache systems
const cacheData = await this.mapCacheData(userId);
if (cacheData.length > 0) {
dataMap.set('cache', cacheData);
}
return dataMap;
}
// Execute deletion with cascade
async executeDeletion(category, userId) {
const strategy = this.deletionStrategies.get(category);
if (!strategy) {
throw new Error(`No deletion strategy for category: ${category}`);
}
// Execute primary deletion
await strategy.primary(userId);
// Execute cascade deletions
for (const cascadeTarget of strategy.cascade) {
await this.cascadeDelete(cascadeTarget, userId);
}
}
// Propagate deletion to external systems
async propagateDeletion(userId) {
const externalSystems = [
{ name: 'email_service', endpoint: process.env.EMAIL_SERVICE_API },
{ name: 'analytics_platform', endpoint: process.env.ANALYTICS_API },
{ name: 'support_system', endpoint: process.env.SUPPORT_API }
];
const propagationResults = [];
for (const system of externalSystems) {
try {
const result = await this.callDeletionAPI(system, userId);
propagationResults.push({
system: system.name,
success: true,
confirmationId: result.confirmationId
});
} catch (error) {
propagationResults.push({
system: system.name,
success: false,
error: error.message
});
}
}
return propagationResults;
}
// Generate cryptographic proof of deletion
async generateDeletionCertificate(report) {
const certificate = {
certificateId: this.generateCertificateId(),
issuedAt: new Date().toISOString(),
subject: {
requestId: report.requestId,
userId: report.userId
},
deletionSummary: {
categoriesDeleted: report.deletedCategories.length,
categoriesRetained: report.retainedCategories.length,
completionTime: report.completedAt
},
hash: null,
signature: null
};
// Generate hash of report
const reportHash = crypto
.createHash('sha256')
.update(JSON.stringify(report))
.digest('hex');
certificate.hash = reportHash;
// Sign certificate
const signature = await this.signCertificate(certificate);
certificate.signature = signature;
return certificate;
}
}