I am trying to batch-persist a Flux-stream of articles into a Postgres r2dbc
The application has two identical article tables (article_origin and article_destiny) The origin table has a million article which I try to batch-insert into the empty article_destiny table! A web-client calls a article-endpoint and requests a single Flux stream. The application works without the batch-insert, but takes more than 20 minutes to persist all the articles one by one.
The articles arrive at the articleDestinyBatchStream and the buffer (with the back-pressure as argument) produces the batch-size chunks.
But the .map(connection -> { is skipped for some reason I do not understand!
Please help
Next find the relevant code snippets:
The web-client
public void getArticleBatchStreamToOrigin(Long backPressure) {
try {
client.get()
.uri("/article-batch-stream-from-origin/" + backPressure)
.accept(MediaType.TEXT_EVENT_STREAM) //APPLICATION_NDJSON TEXT_EVENT_STREAM
.retrieve()
.bodyToFlux(ArticleDestiny.class)
.buffer(backPressure.intValue())
.subscribe(articles -> {
streamService.articleDestinyBatchStream(articles);
log.info("at client article batch size={} ", articles.size());
});
} catch (WebClientResponseException wcre) {
log.error("Error Response Code is {} and Response Body is {}", wcre.getRawStatusCode(), wcre.getResponseBodyAsString());
log.error("Exception in method getArticleStream()", wcre);
throw wcre;
} catch (Exception ex) {
log.error("Exception in method getArticleStream()", ex);
throw ex;
}
}
The article streaming service (one million articles) called by the client
public Flux<ArticleOrigin> getAllArticleStreamFromOrigin(Long backPressure) {
return articleOriginRepository
.findAll()
.doOnNext(a -> log.debug("at stream service article id={}", a.getId()));
}
The article batch persist:
public Mono<Void> articleDestinyBatchStream(List<ArticleDestiny> articles) {
log.info("connectionFactory {}", connectionFactory.getMetadata().getName());
return Mono.from(connectionFactory.create())
.map(connection -> {
Batch batch = connection.createBatch();
for (ArticleDestiny article :articles) {
batch.add("INSERT INTO article_destiny (country_code, art_number, name, price) " +
"values ('${article.countryCode}','${article.artNumber}', '${article.name}', '${article.price}')");
}
return batch.execute();
}).then();
}
here find config details
@SpringBootApplication(exclude = { R2dbcDataAutoConfiguration.class, R2dbcAutoConfiguration.class })
and next the config class that extends the AbstractR2dbcConfiguration
@Bean
public ConnectionFactoryInitializer initialize(ConnectionFactory connectionFactory) {
var initializer = new ConnectionFactoryInitializer();
initializer.setConnectionFactory(connectionFactory());
CompositeDatabasePopulator populator = new CompositeDatabasePopulator();
populator.addPopulators(
new ResourceDatabasePopulator(new ClassPathResource("/db-config/schema.sql"))
);
initializer.setDatabasePopulator(populator);
return initializer;
}
@Override
@Primary
@Bean
public ConnectionFactory connectionFactory() {
return new PostgresqlConnectionFactory(PostgresqlConnectionConfiguration.builder()
.host("localhost")
.port(5677)
.username("admin")
.password("secret")
.database("article_flux_db")
.build());
}
another attempt to create the batches
public Mono<Void> articleDestinyBatchStream(List<ArticleDestiny> articles) {
return Mono.from(connectionFactory.create())
.flatMap(c -> {
Batch batch = c.createBatch();
for (ArticleDestiny article :articles) {
batch.add("INSERT INTO article_destiny (country_code, art_number, name, price) " +
"values ('${article.countryCode}','${article.artNumber}', '${article.name}', '${article.price}')");
}
return Mono.empty();
}).then();
}