Fix for memory issue during backup

This commit is contained in:
Stefan Hardegger
2025-10-20 08:58:09 +02:00
parent 30c0132a92
commit 3dd2ff50d8

View File

@@ -7,7 +7,6 @@ import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware; import org.springframework.context.ApplicationContextAware;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.Resource; import org.springframework.core.io.Resource;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@@ -141,9 +140,12 @@ public class DatabaseManagementService implements ApplicationContextAware {
/** /**
* Create a comprehensive backup including database and files in ZIP format * Create a comprehensive backup including database and files in ZIP format
* Returns a streaming resource to avoid loading large backups into memory
*/ */
public Resource createCompleteBackup() throws SQLException, IOException { public Resource createCompleteBackup() throws SQLException, IOException {
// Create temp file with deleteOnExit as safety net
Path tempZip = Files.createTempFile("storycove-backup", ".zip"); Path tempZip = Files.createTempFile("storycove-backup", ".zip");
tempZip.toFile().deleteOnExit();
try (ZipOutputStream zipOut = new ZipOutputStream(Files.newOutputStream(tempZip))) { try (ZipOutputStream zipOut = new ZipOutputStream(Files.newOutputStream(tempZip))) {
// 1. Add database dump // 1. Add database dump
@@ -156,11 +158,30 @@ public class DatabaseManagementService implements ApplicationContextAware {
addMetadataToZip(zipOut); addMetadataToZip(zipOut);
} }
// Return the ZIP file as a resource // Return the ZIP file as a FileSystemResource for streaming
byte[] zipData = Files.readAllBytes(tempZip); // This avoids loading the entire file into memory
Files.deleteIfExists(tempZip); return new org.springframework.core.io.FileSystemResource(tempZip.toFile()) {
@Override
return new ByteArrayResource(zipData); public InputStream getInputStream() throws IOException {
// Wrap the input stream to delete the temp file after it's fully read
return new java.io.FilterInputStream(super.getInputStream()) {
@Override
public void close() throws IOException {
try {
super.close();
} finally {
// Clean up temp file after streaming is complete
try {
Files.deleteIfExists(tempZip);
} catch (IOException e) {
// Log but don't fail - deleteOnExit will handle it
System.err.println("Warning: Could not delete temp backup file: " + e.getMessage());
}
}
}
};
}
};
} }
/** /**
@@ -289,20 +310,34 @@ public class DatabaseManagementService implements ApplicationContextAware {
System.err.println("PostgreSQL backup completed successfully"); System.err.println("PostgreSQL backup completed successfully");
// Read the backup file into memory // Return the backup file as a streaming resource to avoid memory issues with large databases
byte[] backupData = Files.readAllBytes(tempBackupFile); tempBackupFile.toFile().deleteOnExit();
return new ByteArrayResource(backupData); return new org.springframework.core.io.FileSystemResource(tempBackupFile.toFile()) {
@Override
public InputStream getInputStream() throws IOException {
// Wrap the input stream to delete the temp file after it's fully read
return new java.io.FilterInputStream(super.getInputStream()) {
@Override
public void close() throws IOException {
try {
super.close();
} finally {
// Clean up temp file after streaming is complete
try {
Files.deleteIfExists(tempBackupFile);
} catch (IOException e) {
// Log but don't fail - deleteOnExit will handle it
System.err.println("Warning: Could not delete temp backup file: " + e.getMessage());
}
}
}
};
}
};
} catch (InterruptedException e) { } catch (InterruptedException e) {
Thread.currentThread().interrupt(); Thread.currentThread().interrupt();
throw new RuntimeException("Backup process was interrupted", e); throw new RuntimeException("Backup process was interrupted", e);
} finally {
// Clean up temporary file
try {
Files.deleteIfExists(tempBackupFile);
} catch (IOException e) {
System.err.println("Warning: Could not delete temporary backup file: " + e.getMessage());
}
} }
} }