I am building an app in java.I hit api more than 15000 times in loop and get the response ( response is static only )
Example
**
username in for loop
GET api.someapi/username
processing
end loop
**
It is taking hours to complete all the calls. Suggest me any way (any cache technology) to reduce the call time.
P.S :
1) i am hitting api from java rest client(Spring resttemplate)
2) that api i am hitting is the public one, not developed by me
3) gonna deploy in heroku
Try using Springs Cache Abstraction, docs.spring.io/spring/docs/current/spring-framework-reference/html/cache.html.
You can use this abstraction in the method which has the restTemplate call.
Any method calls response can be cached using this abstraction, with the method parameters as the keys and the return type as the response.
#Cacheable("username")
public UserResponse getUser(String username){
// Code to call your rest api
}
This creates a Spring AOP advice around the method. Every time the method is called it checks if the data is available in the cache for this key(username), if yes then returns the response from the Cache and not calls the actual method. If the data is not available in the Cache then it calls the actual method and caches the data in the cache, so next time when the same method is called with same key the data can be picked from Cache.
This cache abstraction can be backed by simple JVM caches like Guava or more sophisticated cache implementations like EHCache, Redis, HazelCast as well.
One very important note to that answer: If you ever plan to update those (cached) values, don't forget to use #CacheEvict on save() and delete() in the repositories. Else you will have problems fetching the new record when it is updated.
I have implemented my solution (with EhCache) this way (in the repository):
CurrencyRepository.java:
// define a cacheable statement
#Cacheable("currencyByIdentifier")
public Currency findOneByIdentifier(String identifier);
CacheConfiguration.java: // Define that cache in EhCache Configuration
#Bean
public JCacheManagerCustomizer cacheManagerCustomizer() {
return cm -> {
cm.createCache("currencyByIdentifier", jcacheConfiguration);
cm.createCache("sourceSystemByIdentifier", jcacheConfiguration);
};
}
CurrencyRepository.java:
// evict on save and delete by overriding the default method
#Override
#CacheEvict("currencyByIdentifier")
<S extends Currency> S save(S currency);
#Override
#CacheEvict("currencyByIdentifier")
void delete(Currency currency);
I hope that helps :)
Related
I'm trying to migrate my project to Quarkus Reactive with Hibernate Reactive Panache and I'm not sure how to deal with caching.
My original method looked like this
#Transactional
#CacheResult(cacheName = "subject-cache")
public Subject getSubject(#CacheKey String subjectId) throws Exception {
return subjectRepository.findByIdentifier(subjectId);
}
The Subject is loaded from the cache, if available, by the cache key "subjectId".
Migrating to Mutiny would look like this
#CacheResult(cacheName = "subject-cache")
public Uni<Subject> getSubject(#CacheKey String subjectId) {
return subjectRepository.findByIdentifier(subjectId);
}
However, it can't be right to store the Uni object in the cache.
There is also the option to inject the cache as a bean, however, the fallback function does not support to return an Uni:
#Inject
#CacheName("subject-cache")
Cache cache;
//does not work, cache.get function requires return type Subject, not Uni<Subject>
public Uni<Subject> getSubject(String subjectId) {
return cache.get(subjectId, s -> subjectRepository.findByIdentifier(subjectId));
}
//This works, needs blocking call to repo, to return response wrapped in new Uni
public Uni<Subject> getSubject(String subjectId) {
return cache.get(subjectId, s -> subjectRepository.findByIdentifier(subjectId).await().indefinitely());
}
Can the #CacheResult annotations be used with Uni / Multi and everything is handled under the hood correctly?
Your example with a #CacheResult on a method that returns Uni should actually work. The implementation will automatically "strip" the Uni type and only store the Subject in the cache.
The problem with caching Unis is that depending on how this Uni is created, multiple subscriptions can trigger some code multiple times. To avoid this you have to memoize the Uni like this:
#CacheResult(cacheName = "subject-cache")
public Uni<Subject> getSubject(#CacheKey String subjectId) {
return subjectRepository.findByIdentifier(subjectId)
.memoize().indefinitely();
}
This will ensure that every subscription to the cached Uni will always return the same value (item or failure) without re-executing anything of the original Uni flow.
Problem
I have a simple Spring Boot app with a basic RestController (full code available here). It consumes JSON and uses Jackson to convert request from JSON and response to JSON.
#RestController("/")
#RequestMapping(consumes = MediaType.APPLICATION_JSON_VALUE)
public class SomeController {
#Autowired
private SomeService someService;
#PostMapping
public ResponseEntity<SomeResponseDto> post(#RequestBody #Valid SomeRequestDto someRequestDto) {
final SomeResponseDto responseDto = new SomeResponseDto();
responseDto.setMessage(someRequestDto.getInputMessage());
responseDto.setUuid(someService.getUuid());
return ResponseEntity.ok(responseDto);
}
After start-up, the 1st request is about 10-times slower than any sub-sequent request. I debugged and profiled the app and it seems that on first request a Jackson JSON parser is getting initialized somewhere in AbstractMessageConverterMethodArgumentResolver.readWithMessageConverters and AbstractJackson2HttpMessageConverter.
In sub-sequent requests, it seems to get re-used.
Question
How do I initialize Jackson JSON parsing during start-up so that also 1st request is fast?
I know how to trigger a method after Spring started. In PreloadComponent I added as an example how to do a REST request against the controller.
#Component
public class PreloadComponent implements ApplicationListener<ApplicationReadyEvent> {
private final Logger logger = LoggerFactory.getLogger(PreloadComponent.class);
#Autowired
private Environment environment;
#Autowired
private WebClient.Builder webClientBuilder;
#Override
public void onApplicationEvent(ApplicationReadyEvent event) {
// uncomment following line to directly send a REST request on app start-up
// sendRestRequest();
}
private void sendRestRequest() {
final String serverPort = environment.getProperty("local.server.port");
final String baseUrl = "http://localhost:" + serverPort;
final String warmUpEndpoint = baseUrl + "/warmup";
logger.info("Sending REST request to force initialization of Jackson...");
final SomeResponseDto response = webClientBuilder.build().post()
.uri(warmUpEndpoint)
.header(CONTENT_TYPE, APPLICATION_JSON_VALUE)
.body(Mono.just(createSampleMessage()), SomeRequestDto.class)
.retrieve()
.bodyToMono(SomeResponseDto.class)
.timeout(Duration.ofSeconds(5))
.block();
logger.info("...done, response received: " + response.toString());
}
private SomeRequestDto createSampleMessage() {
final SomeRequestDto someRequestDto = new SomeRequestDto();
someRequestDto.setInputMessage("our input message");
return someRequestDto;
}
}
This only works in this toy example. In reality, I have many REST endpoints with complex DTOs and I would need to add a "warm-up" endpoint next to each "real" endpoint as I can't call my real endpoints.
What I already tried?
I added a second endpoint with a different DTO and called it in my PreloadComponent. This doesn't solve the problem. I assume that an Jackson / whatever instance is created for each type.
I autowired ObjectMapper into my PreloadComponent and parsed JSON to my DTO. Again, this doesn't solve the issue.
Full source available at: https://github.com/steinsag/warm-me-up
It turns out that Jackson validation is the problem. I added the JVM option
-verbose:class
to see when classes get loaded. I noticed that on 1st request, there are many Jackson validation classes getting loaded.
To confirm my assumption, I re-worked my example and added another independent warm-up controller with a distinct DTO.
This DTO uses all Java validation annotations also present like in the real DTO, e.g. #NotNull, #Min, etc. In addition, it also has a custom enum to also have validation of sub-types.
During start-up, I now do a REST request to this warm-up endpoint, which doesn't need to contain any business logic.
After start-up, my 1st request is now only 2-3 times slower than any sub-sequent requests. This is is acceptable. Before, the 1st request was 20-40 times slower.
I also evaluated if really a REST request is needed or if it is sufficient to just do JSON parsing or validation of a DTO (see PreloadComponent). This reduces runtime of 1st request a bit, but it is still 5-15 times slower than with proper warm-up. So I guess a REST request is needed to also load other classes in Spring Dispatcher, etc.
I updated my example at: https://github.com/steinsag/warm-me-up
I believe, that a lot of classes will be lazy-loaded. If first call performance is important, then I think warming up by calling each endpoint is the way to go.
Why do you say, that you cannot call the endpoints? If you have a database and you don't want to change the data, wrap everything in a transaction and roll it back after the warm up calls.
I haven't seen any other method to solve this, which doesn't necessarily mean, that it doesn't exist ;)
I can't understand why cache evict is not working in my scenario. I have an application that has a scheduled service in it and has MVC for user to click some stuff.
#Cacheable(value = "applicationToken")
public Optional<String> getToken() {
return settingsRepository.getToken();
}
#CacheEvict(value = "applicationToken", allEntries = true)
public void evictApplicationTokenCache() {
log.info("Evicting token cache.");
}
public void updateToken(String token) {
log.info("Updating token.");
settingsRepository.updateToken(token);
evictApplicationTokenCache();
}
The method getToken() is called inside the scheduled service and when I tried some test to evict cache from there it worked.
However, on the MVC side, if the user updates the token, the method updateToken() gets called and although it goes inside the evictApplicationTokenCache(), on the next retrieval of the token, I still get the same token and it doesn't step into the method getToken() to actually grab the token from the repository.
The only relation I found is that the threads are different for the MVN call and for the Scheduled call. From what I know, the cache should live on the context level, not the thread level. Therefore, it shouldn't matter which thread asks for the cache to be evicted.
It seems that the updateToken() and evictApplicationTokenCache() methods are in the same class.
In that case, the #CacheEvict annotation will be ignored, because cache handling is implemented by interceptors that are only involved when you call a method from one component to a different (injected) component.
If that's the situation, you can move the evictApplicationTokenCache() method to a helper #Component, or put the #CacheEvict annotation on the updateToken() method.
I've written a custom Validation Annotation and a ConstraintValidator implementation, which uses a Spring Service (and executes a Database Query):
public class MyValidator implements ConstraintValidator<MyValidationAnnotation, String> {
private final MyService service;
public MyValidator(MyService service) {
this.service = service;
}
#Override
public void initialize(MyValidationAnnotation constraintAnnotation) {}
#Override
public boolean isValid(String value, ConstraintValidatorContext context) {
return service.exists(value);
}
}
It's used like this:
public class MyEntity {
#Valid
List<Foo> list;
}
public class Foo {
#MyValidationAnnotation
String id;
}
This works quite nice, but service.exists(value) is getting called for every item within the list, which is correct, but could/should be optimized.
Question:
When validating an instance of MyEntity, I'd like to cache the results of the service.exists(value) calls.
I don't want to use a static HashMap<String, Boolean>, because this would cache the results for the entire application lifetime.
Is it possible to access some kind of Constraint Validation Context, which only exists while this particular validation is running, so I can put there the cached results?
Or do you have some other solution?
Thanks in advance!
You can use Spring's cache support. There might be other parts in the application which needs caching and this can be reused. And the setup is very simple too. And it will keep your code neat and readable.
You can cache your service calls. You need to put annotation on your service methods and a little bit of configuration.
And for cache provider you can use Ehcache. You have many options like setting ttl and max number of elements that can be cached and eviction policy etc etc if needed.
Or you can implement your own cache provider if your needs are simple. And if it is web request, In this cache you may find ThreadLocal to be useful. you can do all caching for this running thread using threadlocal. When the request is processed you can clear the threadlocal cache.
Spring allows a method annotated with #RequestMapping to return a variety of objects, including a CompletableFuture or a Future. This allows me to spawn off an async method and let spring return the value whenever it is ready. What I am not sure I am understanding is if there are any benefits to this. For instance:
#RestController
public class MyController {
#RequestMapping("/user/{userId}")
public CompletableFuture<User> getUser(#PathVariable("userId") String userId) {
return CompletableFuture.supplyAsync(
() -> this.dataAccess.getUser(userId));
}
In this case, even though the actual computation is happening in the background, the connection will still not close and the request thread will still be active till it is done. How is it better than say:
#RequestMapping("/user/{userId}")
public User getUser(#PathVariableS("userId") String userId) {
return this.dataAccess.getUser(userId);
}
From first glances, this seems to be a better approach as there is no overhead with an additional thread and a watcher that looks for completion.
This takes advantage of Servlet 3 asynchronous request processing, using request.startAsync() method. Read here and here
To achieve this, a Servlet 3 web application can call request.startAsync() and use the returned AsyncContext to continue to write to the response from some other separate thread. At the same time from a client's perspective the request still looks like any other HTTP request-response interaction. It just takes longer to complete. The following is the sequence of events: