<현상>
- AWS Athena사용시 quota제한 으로 인하여 다음과 같은 문제가 발생하였습니다.
- 동시에 많은 요청이 몰리는 것을 방지하기 위해서 다음과 같이 API호출수를 제한하도록 하였으며 object wait/notify를 활용하였습니다.
<로직>
- API 호출시 기본적으로 @Async호출
- API호출전에 현재 호출count를 확인하여 일정수치 이상일 경우 object.wait()로 대기
- 호출이 성공하면 count를 증가
- 비동기호출을 마치고 응답을 받으면 callback으로 호출count를 감소시키고 object.notify
CompletableFuture<Map<String, Integer>> completableFutureForRule;
...
synchronized (this){
waitBeforeCall();
completableFutureForRule = apiCallService.call(request);
resultCount.incrementAndGet();
}
completableFutureForRule.thenAccept(retMap -> {
synchronized (this){
notifyAfterCall();
}
private void waitBeforeCall(){
try {
while (apiCallService.getCallCount() >= SIZE) {
this.wait();
}
} catch (InterruptedException e) {
log.error("Interruped");
}
apiCallService.incrementAndGet();
}
private void notifyAfterCall(){
apiCallService.decrementAndGet();
this.notifyAll();
}
- blocking queue를 구현할때 와 매우 유사한 로직임을 알수 있습니다.
<참고사항>
- caller service 와 callee service가 같은 비율로 증가한다면 이론상 문제는 없습니다. (e.g 10, 20, 30)
그러나 실제는 두 서비스간에 연관관계가 없을 것으로 예상되며, 또한 이 로직은 한 jvm내 단일 service에서만 유용하기 때문에 ECS와 같이 scale out 이 되는 구조에서 전체 API 호출수를 제한하기 위해서는 다른 방법을 사용해야 합니다.
e.g) A. cluster shared lock
B. API gateway를 활용한 limit rate
C. Redis를 활용한 global count개념
- wait후 notify의 경우 잠들어 있는 하나의 쓰레드만 깨우기 때문에 여러가지 예외 상황에 대응하거나 개별제어가 어려워서 일반적으로 notifyAll을 많이 사용합니다.
- while()문으로 체크해야 하는 이유는 여러 쓰레드가 다시 lock을 획득하기 위해 경쟁하기 때문입니다. 또한 Spurious wakeups 처럼 이유없이 쓰레드가 깨어난 경우에도 다시 wait로 진입하기 위해서 필요합니다.
- 이와 같이 일정시간 대기하는 로직을 작성할때 sleep을 사용하는 경우를 간혹 볼 수 있는데, sleep의 경우 wait와 다르게 lock을 반환하지 않으므로 주의해서 사용해야 합니다. (stackoverflow 참조)
https://stackoverflow.com/questions/1036754/difference-between-wait-and-sleep
- 로직이 복잡해질 경우 CountDownLatch 를 이용해서 구현하는 것을 권장합니다!
'Application Design' 카테고리의 다른 글
사례로 배워보는 디자인패턴 #5 - 추상화를 통한 파티션 테이블 자동 갱신하기 (1) (0) | 2024.07.03 |
---|---|
사례로 배워보는 디자인패턴 #3 - 비슷하지만 다른로직이 추가될 때 (0) | 2019.10.17 |
사례로 배워보는 디자인패턴 #2 - 비지니스 로직을 담자 (0) | 2019.10.17 |
사례로 배워보는 디자인패턴 #1 - 기본적인 MVC (0) | 2019.10.17 |
Azure IoT Hub를 이용한 Firmware Update기능 + Java enum을 활용한 디자인패턴 적용 (0) | 2019.10.08 |