0

Currenlty I'm developing an application to automate database backup and restore operations. We are dealing with different types of databases, but one of them is MongoDb. Our MongoDb are hosted by MongoDB Atlas, so we use their MongoDB Atlas Administration API do manage all components.

Once I requested for snapshot of cluster with Take One On-Demand Snapshot method, I need to poll Return One Cluster Cloud Backup endpoint to check what is current process status. I do that as long as one of final statuses are reached (COMPLETED, FAILED).

I'm unsure about the best approach for polling the API until I get a final status. My ideas so far:

  • Use Thread.sleep() in a loop
  • Use a retry utility (like Spring Retry)
  • Use Resilience4j library

However, I'm not convinced either approach is optimal, especially regarding scalability and resource usage.

Question: What are the best practices in a Spring Boot application for polling an external REST API until a certain status is reached?
Thanks in advance for all replies and discussions.

Below my simplified code.
I tried apprach with Thread.sleep().

@Service @Slf4j public class MongodbAtlasService implements MongodbService { private final MongodbClient mongodbClient; private final long pollingIntervalMs; private final long snapshotTimeoutMs; private final long accessTokenExpirationTimeMs; @Override @Retryable(retryFor = {...}) public SnapshotStatus waitForSnapshotStatus(String projectId, String cluster, String snapshotId) throws ... { String accessToken = getAccessToken(); long start = System.currentTimeMillis(); while (System.currentTimeMillis() - start <= snapshotTimeoutMs) { SnapshotStatus status = mongodbClient.getSnapshotStatus(projectId, cluster, snapshotId, accessToken); if (status == SnapshotStatus.COMPLETED || status == SnapshotStatus.FAILED) { return status; } Thread.sleep(pollingIntervalMs); } } 
@Slf4j @Component public class MongodbClient { private final RestClient administrationMongodbRestClient; public SnapshotStatus getSnapshotStatus(String projectId, String clusterName, String snapshotId, String accessToken) throws UnknownSnapshotStatusException { ResponseEntity<GetSnapshotInfoResponse> snapshotInfoResponse = administrationMongodbRestClient.get() .uri("/groups/{projectId}/clusters/{clusterName}/backup/snapshots/shardedCluster/{snapshotId}", projectId, clusterName, snapshotId) .header("Authorization", "Bearer " + accessToken) .retrieve() .onStatus( httpStatusCode -> (httpStatusCode.is4xxClientError() || httpStatusCode.is5xxServerError()), (request, response) -> { throw new UnknownSnapshotStatusException(snapshotId); }) .toEntity(GetSnapshotInfoResponse.class); return Optional.ofNullable(snapshotInfoResponse.getBody()) .map(GetSnapshotInfoResponse::getStatus) .orElseThrow(() -> new UnknownSnapshotStatusException(snapshotId)); } } 
1
  • There is one new idea that come to my mind. Since I'm using java 21 I can use virtual threds. Maybe I can change MongodbAtlasService.waitForSnapshotStatus to @Async, and execture it as CompletableFuture<SnapshotStatus> with Executors.newVirtualThreadPerTaskExecutor()? Even when Threda.sleep() is inside I don't need to waried about that. Commented Jul 18 at 13:03

1 Answer 1

0

Looking at your MongoDB polling code, it seems the main issue is that Thread.sleep() blocks threads which kills scalability when handling multiple snapshots. You'd be better off switching to Spring's @Async with CompletableFuture so you can return a promise immediately and do the actual polling in the background, or even better use @Scheduled methods with a task scheduler to avoid blocking entirely.

As a quick change, instead of your while loop, try something like:

@Async public CompletableFuture<SnapshotStatus> waitForSnapshotStatus(...) { return CompletableFuture.supplyAsync(() -> { // your existing logic but wrapped in async }); } 

Or replace the Thread.sleep(pollingIntervalMs) with:

taskScheduler.schedule(() -> checkStatus(...), Instant.now().plusMillis(pollingIntervalMs)); 

Resilience4j's async retry is another solid option that handles the backoff logic cleanly.

The key insight is making it non-blocking so your app can monitor multiple snapshots concurrently without tying up threads in sleep loops.

Sign up to request clarification or add additional context in comments.

1 Comment

Thanks for you answer. I thnik I will go with ScheduledThreadPoolExecutor.scheduleAtFixedRate()

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.