Skip to content
This repository has been archived by the owner on Oct 24, 2024. It is now read-only.

[#46] 비동기적인 Push 알람 서비스 구현 #48

Open
wants to merge 14 commits into
base: develop
Choose a base branch
from
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public Executor threadPoolTaskExecutor() {
taskExecutor.setMaxPoolSize(MAX_POOL_SIZE);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

core pool size와 max pool size를 다르게 해주신 이유는 어떤 상황을 가정해서 다르게 주신 것일까요?

Copy link
Collaborator Author

@tjdrnr0557 tjdrnr0557 Nov 3, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

core pool size는 스레드 풀 안에 유지되는 스레드 개수이고
max pool size는 작업 큐에 QUEUE_CAPACITY만큼 꽉찼을 시, 작업을 처리하려면 스레드의 개수가 늘어나야하는데 스레드의 개수가 최대로 몇개까지 늘어날 수 있는 사이즈입니다. 즉 스레드 풀에서 관리하는 최대 스레드의 개수입니다.

만약 core pool size를 높게 둔다면 스레드를 여러개만들어 두니 바로 바로 처리할 수 있지만 스레드를 많이 만들어 두는 것 자체가 메모리 낭비일 수 있으므로 작게 두었습니다.
max pool size는 푸쉬알람이 한번에 몇십개를 보내야 한다면 스레드의 개수가 많이 늘어날 수 있으니 높게 설정해두었습니다.

이 둘을 다르게 설정해준 이유는 한번에 갑자기 많은 푸쉬알람을 보내야 할 때 core pool size의 스레드 개수가 알람을 보내고 response가 올떄 까지 blocking이 되면 작업 큐에 있는 작업들이 처리되지 못하여 core pool size의 스레드를 할당받지 못하고 기다리게 될 것이므로 max pool size만큼 스레드를 늘려 푸쉬 알람을 보내기 위해서 입니다.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

스레드를 생성하고 소멸시키면서 생기는 오버헤드보다는 메모리를 적게 사용하는 것을 선택하신 것 같네요~
그러면 메모리에 얼마나 차이가 있을지는 혹시 계산해보셨을까요?

Copy link
Collaborator Author

@tjdrnr0557 tjdrnr0557 Nov 5, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

푸쉬메세지를 보낼 때 Runtime 클래스를 이용해 메모리 사용량을 계산해보았습니다.
Max Pool을 500으로 지정해놓았을때랑 5로 지정해 놓았을 때 푸쉬메세지를 보내보면 스레드를 495개 더 만드는데에 메모리 차이는 70MB정도의 차이가 있었습니다. (매번 다르게 나오는데 이 부분은 정확히 왜 계속 다르게 나오는지 모르겠습니다)
푸쉬메세지가 한번에 폭증하는 것을 병목없이 보내주는데 70MB를 더 쓰는 것이 더 효율적이라고 생각했습니다.
만약 큐에 쌓아놓고 푸쉬메세지요청이 계속 더 들어온다면 병목이 계속 생길 수 있다고 생각하기 때문입니다.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

매번 다르게 나와서 정확한 수치 측정은 불가능하나

Maxpool을 500으로 잡았을때와 5로 잡았을때 메모리 차이는 50~100MB정도의 차이는 꾸준히 있었습니다.

taskExecutor.setQueueCapacity(QUEUE_CAPACITY);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. QUEUE_CAPACITY 가 상당히 낮은데 어떤 이유로 이렇게 산정하셨나요?
  2. QueueCapacity는 크기에 따른 각 어떤 장단점이 있을까요?

Copy link
Collaborator Author

@tjdrnr0557 tjdrnr0557 Nov 3, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


core_pool_size만큼 스레드 풀은 스레드를 만들어놓고 스레드를 처리하다 더 많은 작업량이 들어오면 작업 큐에 넣어놓고 작업 큐는 대기하고 있고 core_pool_size의 스레드들이 작업은 완료하면 작업 큐에서 한개씩 스레드를 할당해서 스레드를 재사용합니다.

만약 푸쉬알람이 폭증해서 동시에 몇십개를 보내야 하는 상황에서 QUEUE_CAPACITY가 20이고 core_pool_size로 정해놓은 스레드들이 3이라면 3개의 스레드가 푸쉬알람을 보내고 response를 받고 이 일 처리를 끝내고 이 3개의 스레드로 20개의 작업큐의 작업을 해야 하므로 작업큐에서도 blocking이 있을것이고 core_pool_size만큼의 스레드들도 푸쉬알람을 보내고 response까지 받을때까진 blocking이 있을것이므로 작업큐에서 대기하는 작업들은 굉장히 늦게 처리될 것입니다.

따라서 queue_capactiy를 아예 낮게 두어 core_pool_size의 스레드가 꽉차고 queue_capacity도 꽉찬다면 작업 큐에서 계속 작업들이 기다리는 것을 방지하고자 아예 max_pool_size를 높게 두어 한번에 몇십개씩 푸쉬알람을 보내야 할 때 스레드의 개수를 늘려 푸쉬알람 병목을 대비하였습니다.


기존 스레드풀의 core_pool_size만큼의 스레드가 모두 일을하고있고 작업큐에 작업이 쌓였다면
기존 core_pool_size만큼의 스레드가 제할일을 끝내고 작업큐에 남아있는 작업들에 할일을 끝낸 스레드를 할당합니다.
기존 스레드들은 계속 재활용되며 작업큐의 작업들에 스레드를 할당합니다.
따라서 queue_capacity가 높다면 시간은 조금 오래걸리더라도 새로운 스레드를 생성하지 않고 재활용 하기 때문에 새로운 스레드를 만드는데 메모리를 더 쓰지 않고 재활용하므로 메모리면에서 효율적입니다.

하지만 queue_capacity가 낮다면 작업큐까지 다 꽉차버리면 max_pool_size까지 스레드를 필요한만큼 늘려버리기 때문에 바로바로 일처리를 할 수 있고 병목이 생길 확률이 줄어듭니다. 시간은 적게 걸리지만 새로운 스레드들을 생성하기 때문에 새로운 스레드를 만드는데 메모리를 더 쓰게 될 것입니다.

taskExecutor.setThreadNamePrefix(NAME_PREFIX);
taskExecutor.setWaitForTasksToCompleteOnShutdown(true);
taskExecutor.setWaitForTasksToCompleteOnShutdown(false);
taskExecutor.setRejectedExecutionHandler(new AbortPolicy());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AbortPolicy로 설정해주신 이유가 있으실까요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

abort policy는 최대 스레드 크기까지 쓰고 작업큐까지 꽉차면 RejectedExecutionException 예외를 던집니다. 이 예외를 잡아서 직접 대응해줘야합니다. 만약 푸쉬 알람을 보내다 실패한다면 이 예외를 푸쉬 알람을 보내려고 한 사용자에게 throw하여 알려야 한다고 생각하여 abortpolicy로 설정하였습니다.

return taskExecutor;
}
Expand Down
14 changes: 9 additions & 5 deletions src/main/java/com/flab/makedel/service/PushService.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,20 +23,24 @@
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
@Log4j2
public class PushService {

@Value("${firebase.config.path}")
private String FIREBASE_CONFIG_PATH;

private final String firebaseConfigPath;
private final DeliveryDAO deliveryDAO;

public PushService(@Value("${firebase.config.path}") String firebaseConfigPath,
DeliveryDAO deliveryDAO) {
this.firebaseConfigPath = firebaseConfigPath;
this.deliveryDAO = deliveryDAO;
}

@PostConstruct
public void init() throws IOException {
FirebaseOptions options = new FirebaseOptions.Builder()
.setCredentials(GoogleCredentials
.fromStream(new ClassPathResource(FIREBASE_CONFIG_PATH).getInputStream()))
.fromStream(new ClassPathResource(firebaseConfigPath).getInputStream()))
.build();
if (FirebaseApp.getApps().isEmpty()) {
FirebaseApp.initializeApp(options);
Expand All @@ -55,7 +59,7 @@ public void sendMessageToStandbyRidersInSameArea(String address, PushMessageDTO
.build())
.collect(Collectors.toList());

BatchResponse response = FirebaseMessaging.getInstance().sendAll(messages);
FirebaseMessaging.getInstance().sendAll(messages);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이거를 비동기로 처리해준다면 @Async는 붙이지 않아도 되지 않을까요?

}

}