Mam aplikację, która obecnie co 3 sekundy odpytuje zewnętrzne API z określonymi parametrami, których jest +/- 300. Oznacza to 300 zapytań REST co 3 sekundy, każde zapytanie zwraca 60 elementów, które porównuje elementami które do tej pory pobrałem, jeżeli pojawi się nowy - publikuje go dalej. Pierwszy problem pojawił się ze strony WebClient
którego używam, lokalnie zaczęło się sypać przy +/- 60-70 query, na VPSie daje radę na 100, powyżej tej liczby ponad 50% zapytań kończy się błędem
reactor.netty.http.client.PrematureCloseException: Connection prematurely closed BEFORE response
Dlatego też, mam 3 równolegle działające aplikację i każda z nich obsługuje po 100 zapytań, a wyniki publikowane są RESTem do kolejnego serwisu, może i lepsza byłaby kolejka, aczkolwiek tych strzałów jest około 100-150 dziennie, tak więc tutaj zdecydowanie nie ma problemów.
Jest za to poważny problem z wydajnością tych zapytań do zewnętrznego API, nowe elementy pojawiają się w aplikacji która obsługuję 30 zapytań o średnio 60 sekund szybciej niż w aplikacji obsługującej 300 zapytań (a dokładnie w 3 aplikacjach po 100). Na monitorze zasobów dostepnym na VPSie widzę 50% użycie CPU, a za to przez ps -p <pid> %cpu, %mem
widzę około 25% użycia CPU i pamięci na 1 aplikację (czyli sumarycznie wychodzi przynajmniej 75%), a przed chwilą poleciało
There is insufficient memory for the Java Runtime Environment to continue.
Native memory allocation (mmap) failed to map 204472320 bytes for committing reserved memory.
Jeszcze jedna rzecz mnie martwi, hosting udostępnia jeszcze jeden monitor, w którym co 5 sekund odpytuje VPSa o użycie zasobów przez poszczególne procesy, prawie nigdy nie jest tak że mam coś w stylu:
service1 25% cpu
service2 25% cpu
service3 25% cpu
A zamiast tego jest
service1 70% cpu
service2 80% cpu
service3 1% cpu
Pomijając fakt, że faktycznie to się nie sumuję do 100%, to zawsze 1 serwis jest z tyłu, a po chwili inny, czyli tak jakby nie pracują one jednocześnie cały czas.
Pytanie więc, co robić? Dołożenie pamięci pomoże? Obecnie było to 4gb. Poza tym, czy problem z tym że ewidentnie zapytania kończyły się wolniej, to wina procesora? 300 równoległych zapytań przez WebClient
na 3 różnych aplikacjach to za dużo?
Niżej zostawiam kod,
Każdy response to maksymalnie 60 takich obiektów
public class Item implements Serializable {
private String id;
private String name;
//constructor, getter, setter
}
No i sam mechanizm odpytywania API, jest on uruchamiany po uwczesnym zainicjalizowaniu initialItems
stanem początkowym.
private final Map<String, Item> initialItems = new ConcurrentHashMap<>();
@Scheduled(fixedRate = 3000)
private void collectData() {
try {
queryRepository.getSearchQueries()
.forEach(query -> reactiveClient.collectData(query)
.subscribe(this::processResponse));
} catch (Throwable t) {
logger.severe(t.getMessage());
}
}
private void processResponse(final ApiResponse response) {
response.collectItems().forEach(item -> {
if (!initialItems.containsKey(item.getId())) {
initialItems.put(item.getId(), item);
eventPublisher.publishEvent(new NewItemEvent(this, item));
}
});
}
public Mono<ApiResponse> collectData(final SearchQuery query) {
final var accessToken = authenticationService.getAccessToken();
return invoke(query.toUrlParams(), accessToken);
}
private Mono<ApiResponse> invoke(final String endpoint, final String accessToken) {
return webClient.get()
.uri(endpoint)
.header("Authorization", accessToken)
.retrieve()
.bodyToMono(ApiResponse.class)
.onErrorReturn(ApiResponse.emptyResponse());
}
queryRepository
działa na zwykłej HashMap
, to nie jest żadna baza danych.
authenticationService
wykonuje zapytanie REST tylko gdy token wygasa, czyli raz na kilka godzin, reszte czasu zwraca zapisany token.