spring boot #async with completablefuture for multi-thread - spring

I am trying to configure a multithreaded environment through spring boot.
Service class
for (int i = 1; i <= 1000; j++) {
threadServiceMethod(i);
}
ThreadService class
public void threadServiceMethod(int i) {
System.out.println(i);
}
I confirmed that multithreading works by calling threadservice from service.
ex. console = 1 2 3 8 10 5 ....1000
But I want threadservice to select from DB.
So I modified the service.
Service class
for (int i = 1; i <= 30; j++) {
threadServiceMethod(i);
}
ThreadService class
public CompletableFuture<List<DTO>> threadServiceMethod(int i) {
return CompletableFuture.completedFuture(DAO method);
}
But multithreading doesn't seem to work. Because the logs are output in order.
I don't know if multithreading is working or not. I am using mybatis to make a request to the db.

Related

Spring #Transactional method executed but takes too long to commit

My method annotated with Spring's #Transactional executes in 1s by doing around 5K of inserts in 1 Postgres tables.
I use tableRepo.flush() and entityManager.clear() after every 10 entity inserts.
Here is the code:
#Service
public class ImportService {
...
#Transactional
public void import() {
for (int i = 0; i < 500; i++) {
for (int j = 0; j < 10; j++) {
Entity entity = new Entity();
entity.setValue(String.format("value %d - %d", i, j));
tableRepo.save(entity);
}
log.debug("Saved i = {}", i);
tableRepo.flush();
entityManager.flush();
entityManager.clear();
log.debug("Flushed and cleared i = {}", i);
}
}
}
#Service
public class MainService {
...
public void do() {
importService.import();
log.debug("Completed");
}
}
However, after that method is executed, Spring's proxy class that called that method, takes around 82s to commit all that into database.
These are the logs with relevant execution times:
[DEBUG] 14:02:54,236 ImportService - Saved i = 498
[DEBUG] 14:02:54,239 ImportService - Flushed and cleared i = 498
[DEBUG] 14:02:54,242 ImportService - Saved i = 499
[DEBUG] 14:02:54,245 ImportService - Flushed and cleared i = 499
[DEBUG] 14:02:54,245 org.hibernate.engine.transaction.internal.TransactionImpl:98 - committing
[DEBUG] 14:04:16,868 MainService - Completed
Why does it take 82s to commit those changes to database, is there anything I can do to make it faster?
For those who enter the same situation, I had a hidden trigger that was executed on Postgres side during commiting.
Since trigger function was executed 5000 times, once for each inserted row, and used unindexed selects, it prolonged the commit for such a long time

Runtime modification of Spring #Retryable maxAttempts value

Scenario : I need to modify maxAttempts value of #Retryable at runtime, so that the number of retries can be driven from database
#Service
public class PropertyHolder {
private int retryCount= 2;
public int retryCount() { return retryCount; }
#Scheduled(fixedRate=3600000) //Query DB and update retryCount every 60mins
private void updateRetryCount(int in) {
this.retryCount = 0; //Fetch retryCount from DB and update retryCount
}
}
#Service
public class SimpleService{
#Retryable( value={ Throwable.class }, maxAttemptsExpression="#{#propertyHolder.retryCount()}")
private void performTask() {
// do some opertion that throws Exception
}
}
PropertyHolder will update retryCount once in every 60 minutes.
This PropertHolder#retryCount needs to be wired to #Retryable in SimpleService#performTask .At present, it takes only the initial value of retryCount (2).Is this a right approach or Am I making some terrible mistake?
No; currently the expression is only evaluated during context initialization; there is an open feature request to add runtime evaluation.
https://github.com/spring-projects/spring-retry/issues/184
Currently you have to wire up your own interceptor with a mutable retry policy and configure it via the interceptor property in #Retryable.

Running Tasks in different thread in Spring Webflux Annotated controller

I have a spring Webflux Annotated controller as below,
#RestController
public class TestBlockingController {
Logger log = LoggerFactory.getLogger(this.getClass().getName());
#GetMapping()
public Mono<String> blockForXSeconds(#RequestParam("block-seconds") Integer blockSeconds) {
return getStringMono();
}
private Mono<String> getStringMono() {
Integer blockSeconds = 5;
String type = new String();
try {
if (blockSeconds % 2 == 0) {
Thread.sleep(blockSeconds * 1000);
type = "EVEN";
} else {
Thread.sleep(blockSeconds * 1000);
type = "ODD";
}
} catch (Exception e) {
log.info("Got Exception");
}
log.info("Type of block-seconds: " + blockSeconds);
return Mono.just(type);
}
}
How do I make getStringMono run in a different thread than Netty server threads. The problem I am facing is that as I am running in server thread I am getting basically less throughput (2 requests per second). How do I go about making running getStringMono in a separate thread.
You can use subscribeOn operator to delegate the task to a different threadpool:
Mono.defer(() -> getStringMono()).subscribeOn(Schedulers.elastic());
Although, you have to note that this type of blocking should be avoided in a reactive application at any cost. If possible, use a client which supports non-blocking IO and returns a promise type (Mono, CompletableFuture, etc.). If you just want to have an artificial delay, then use Mono.delay instead.
You can use Mono.defer() method.
The method signature is as:
public static <T> Mono<T> defer(Supplier<? extends Mono<? extends T>> supplier)
Your Rest API should look like this.
#GetMapping()
public Mono<String> blockForXSeconds(#RequestParam("block-seconds") Integer blockSeconds) {
return Mono.defer(() -> getStringMono());
}
The defer operator is there to make this source lazy, re-evaluating the content of the lambda each time there is a new subscriber. This will increase your API throughput.
Here you can view the detailed analysis.

Actuator Healthcheck for Third party APIs

Suppose I have an API which calls another
API and I want to have a health check of this endpoint as well. Is this possible with Actuator or do I need to write custom code?
You can implement custom health indicator by implementing org.springframework.boot.actuate.health.HealthIndicator and adding it as bean in spring context. Something like below should get you started:
#Component
public class CustomHealthCheck implements HealthIndicator {
#Override
public Health health() {
int errorCode = check();
if (errorCode != 0) {
return Health.down()
.withDetail("Error Code", errorCode).build();
}
return Health.up().build();
}
public int check() {
// Custom logic to check health
return 0;
}
}

Why CounterService fails to count the times a method was invoked?

I am using spring AOP and spring boot CounterService to record the times of invocation for a specific method. Each time I visit the target url, the countServiceInvoke would be executed, but the output metrics value will always be 1. "gauge.servo.string_com.yirendai.oss.environment.admin.controller.restcontrollertest.test()": 1.
I want to know why this counter failed? Thanks. The util class is like bellow:
#Aspect
#Component
public class ServiceMonitor {
#Autowired
private CounterService counterService;
#Before("execution(* com.yirendai.oss.environment.admin.controller.*.*(..))")
public void countServiceInvoke(JoinPoint joinPoint) {
System.out.println("##################" + joinPoint.getSignature());
counterService.increment(joinPoint.getSignature() + "");
}
}
I have read the source code of the implemented class of CounterService, key should be started with "meter." in order to correctly counted.
private void incrementInternal(String name, long value) {
String strippedName = stripMetricName(name);
if (name.startsWith("status.")) {
// drop this metric since we are capturing it already with
// ServoHandlerInterceptor,
// and we are able to glean more information like exceptionType from that
// mechanism than what
// boot provides us
}
else if (name.startsWith("meter.")) {
BasicCounter counter = counters.get(strippedName);
if (counter == null) {
counter = new BasicCounter(MonitorConfig.builder(strippedName).build());
counters.put(strippedName, counter);
registry.register(counter);
}
counter.increment(value);
}
else {
LongGauge gauge = longGauges.get(strippedName);
if (gauge == null) {
gauge = new LongGauge(MonitorConfig.builder(strippedName).build());
longGauges.put(strippedName, gauge);
registry.register(gauge);
}
gauge.set(value);
}
}

Resources