Tengo que iterate más de 130 objetos de transferencia de datos, y cada vez que va a generar un JSON para ser subido a AWS S3.
Sin mejoras, se tarda alrededor de 90 segundos la completa todo el proceso. He intentado utilizar lamba y no usar Lamba, mismos resultados para ambos.
for(AbstractDTO dto: dtos) {
try {
processDTO(dealerCode, yearPeriod, monthPeriod, dto);
} catch (FileAlreadyExistsInS3Exception e) {
failedToUploadDTOs.add(e.getLocalizedMessage() + ": " + dto.fileName() + ".json");
}
}
dtos.stream().forEach(dto -> {
try {
processDTO(dealerCode, yearPeriod, monthPeriod, dto);
} catch (FileAlreadyExistsInS3Exception e) {
failedToUploadDTOs.add(e.getLocalizedMessage() + ": " + dto.fileName() + ".json");
}
});
Después de algunas investigaciones, llegué a la conclusión de que el método processDTO lleva alrededor 0.650ms por artículo a plazo.
Mi primer intento fue utilizar corrientes paralelas , y los resultados fueron bastante buenos, teniendo alrededor de 15 segundos para completar todo el proceso:
dtos.parallelStream().forEach(dto -> {
try {
processDTO(dealerCode, yearPeriod, monthPeriod, dto);
} catch (FileAlreadyExistsInS3Exception e) {
failedToUploadDTOs.add(e.getLocalizedMessage() + ": " + dto.fileName() + ".json");
}
});
Pero todavía tengo que disminuir ese tiempo. He investigado sobre la mejora de corrientes paralelas, y descubrí la ForkJoinPool truco:
ForkJoinPool forkJoinPool = new ForkJoinPool(PARALLELISM_NUMBER);
forkJoinPool.submit(() ->
dtos.parallelStream().forEach(dto -> {
try {
processDTO(dealerCode, yearPeriod, monthPeriod, dto);
} catch (FileAlreadyExistsInS3Exception e) {
failedToUploadDTOs.add(e.getLocalizedMessage() + ": " + dto.fileName() + ".json");
}
})).get();
forkJoinPool.shutdown();
Desafortunadamente, los resultados fueron un poco confuso para mí.
- Cuando PARALLELISM_NUMBER es 8, que toma alrededor de 13 segundos para completar todo el proceso. No es una gran mejora.
- Cuando PARALLELISM_NUMBER es 16, se tarda alrededor de 8 segundos para completar todo el proceso.
- Cuando PARALLELISM_NUMBER es de 32, se tarda alrededor de 5 segundos para completar todo el proceso.
Todas las pruebas se realizaron utilizando las solicitudes de cartero, llamando al método controlador que terminará-up iterando las 130 artículos
Estoy satisfecho con 5 segundos con 32 como PARALLELISM_NUMBER, pero estoy preocupado por las consecuencias.
- ¿Está bien para mantener a 32?
- ¿Cuál es el PARALLELISM_NUMBER ideal?
- ¿Qué tengo que tener en cuenta a la hora de decidir su valor?
Estoy corriendo en un Mac 2,2 GHz I7
sysctl hw.physicalcpu hw.logicalcp
hw.physicalcpu: 4
hw.logicalcpu: 8
Esto es lo que processDTO hace:
private void processDTO(int dealerCode, int yearPeriod, int monthPeriod, AbstractDTO dto) throws FileAlreadyExistsInS3Exception {
String flatJson = JsonFlattener.flatten(new JSONObject(dto).toString());
String jsonFileName = dto.fileName() + JSON_TYPE;;
String jsonFilePath = buildFilePathNew(dto.endpoint(), dealerCode, yearPeriod, monthPeriod, AWS_S3_JSON_ROOT_FOLDER);
uploadFileToS3(jsonFilePath + jsonFileName, flatJson);
}
public void uploadFileToS3(String fileName, String fileContent) throws FileAlreadyExistsInS3Exception {
if (s3client.doesObjectExist(bucketName, fileName)) {
throw new FileAlreadyExistsInS3Exception(ErrorMessages.FILE_ALREADY_EXISTS_IN_S3.getMessage());
}
s3client.putObject(bucketName, fileName, fileContent);
}
He conseguido reducir a 8 segundos , gracias a todos sus votos consejos y explicaciones.
Dado que el cuello de botella era la carga de AWS S3, y se menciona una API no bloqueo de AWS, después de algunas investigaciones, descubrí que la clase TransferManager contiene un no-bloqueo de carga.
Así que en lugar de utilizar ForkJoinPool para aumentar el número de hilos, me quedé con el parallelStream simple:
dtos.parallelStream().forEach(dto -> {
try {
processDTO(dealerCode, yearPeriod, monthPeriod, dto);
} catch (FileAlreadyExistsInS3Exception e) {
failedToUploadDTOs.add(e.getLocalizedMessage() + ": " + dto.fileName() + ".json");
}
});
Y el uploadToS3Method cambió un poco, en lugar de utilizar un AmazonS3 , he utilizado la TransferManager :
public Upload uploadAsyncFileToS3(String fileName, String fileContent) throws FileAlreadyExistsInS3Exception {
if (s3client.doesObjectExist(bucketName, fileName)) {
throw new FileAlreadyExistsInS3Exception(ErrorMessages.FILE_ALREADY_EXISTS_IN_S3.getMessage());
}
InputStream targetStream = new ByteArrayInputStream(fileContent.getBytes());
ObjectMetadata metadata = new ObjectMetadata();
metadata.setContentLength(fileContent.getBytes().length);
return transferManager.upload(bucketName, fileName, targetStream, metadata);
}
De esta manera, cuando la carga se llama, no espera a que termine, dejando que otra DTO para ser procesados. Cuando se procesan todas DTO, puedo comprobar su estado de carga para ver posibles errores (fuera de la primera forEach)