Skip to content

Commit 6e172f6

Browse files
authored
refactor: 알림 처리 성능 개선 및 스레드풀 설정 정비 (#279)
* refactor: NotificationHandler 스레드풀 거부 정책 추가 및 반환 타입 명시 (#278) * refactor: 스레드풀 공통 설정 buildExecutor 메서드로 분리 (#278) * refactor: notificationExecutor 거부 정책 CallerRunsPolicy로 변경 및 공통 메서드에 handler 인자 추가 (#278) * refactor: GroupMember에서 userId만 조회하도록 쿼리 최적화 (#278) * fix: NotificationService에서 잘못된 커서 클래스명 사용 수정 (#278) * refactor: markAsRead() 메서드에서 불필요한 조건문 제거 (#278)
1 parent 413ba2e commit 6e172f6

File tree

5 files changed

+30
-29
lines changed

5 files changed

+30
-29
lines changed

src/main/java/com/moa/moa_server/config/AsyncConfig.java

Lines changed: 21 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package com.moa.moa_server.config;
22

3-
import java.util.concurrent.Executor;
3+
import java.util.concurrent.RejectedExecutionHandler;
44
import java.util.concurrent.ThreadPoolExecutor;
55
import org.springframework.context.annotation.Bean;
66
import org.springframework.context.annotation.Configuration;
@@ -14,7 +14,7 @@ public class AsyncConfig {
1414
// 기본 비동기 풀 (전제 @Async의 기본)
1515
// 명시적으로 기본 풀을 등록해 둠
1616
@Bean(name = "taskExecutor")
17-
public Executor taskExecutor() {
17+
public ThreadPoolTaskExecutor taskExecutor() {
1818
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
1919
executor.setCorePoolSize(5);
2020
executor.setMaxPoolSize(10);
@@ -25,28 +25,31 @@ public Executor taskExecutor() {
2525
}
2626

2727
// 댓글 롱폴링 전용 스레드풀
28+
// - 특징: 대기 시간이 길 수 있고, 동시 요청이 많을 수 있음
29+
// - 큐/풀 초과 시: 작업 거부 및 예외 발생시켜 클라이언트에게 즉시 실패 응답 → 재요청 유도
2830
@Bean(name = "commentPollingExecutor")
2931
public ThreadPoolTaskExecutor commentPollingExecutor() {
30-
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
31-
executor.setCorePoolSize(10); // 스레드풀 크기
32-
executor.setMaxPoolSize(20); // 최대 스레드풀 크기
33-
executor.setQueueCapacity(200); // 작업 대기 큐 용량 (초과 시 새로운 스레드 생성 또는 거부 정책 적용)
34-
executor.setRejectedExecutionHandler(
35-
new ThreadPoolExecutor.AbortPolicy() // 작업 거부 및 예외 발생 (FE에서 재요청 유도)
36-
); // 풀이 가득 찼을 때 대응
37-
executor.setThreadNamePrefix("comment-polling-"); // 스레드 이름
38-
executor.initialize();
39-
return executor;
32+
return buildExecutor("comment-polling-", 10, 20, 200, new ThreadPoolExecutor.AbortPolicy());
4033
}
4134

42-
// NotificationHandler 전용 스레드풀
35+
// NotificationHandler (알림 비동기 처리) 전용 스레드풀
36+
// - 특징: 빠르게 끝나는 경량 작업
37+
// - 큐/풀 초과 시: 호출한 스레드에서 처리하여 알림 유실 방지
4338
@Bean(name = "notificationExecutor")
44-
public Executor notificationExecutor() {
39+
public ThreadPoolTaskExecutor notificationExecutor() {
40+
return buildExecutor(
41+
"notification-async-", 3, 6, 100, new ThreadPoolExecutor.CallerRunsPolicy());
42+
}
43+
44+
// 공통 executor 생성 로직
45+
private ThreadPoolTaskExecutor buildExecutor(
46+
String name, int core, int max, int queue, RejectedExecutionHandler handler) {
4547
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
46-
executor.setCorePoolSize(3); // 자주 발생하지만 빠르게 끝나는 작업이므로 작게 시작
47-
executor.setMaxPoolSize(6);
48-
executor.setQueueCapacity(100); // 초당 수십 건 수준이면 충분
49-
executor.setThreadNamePrefix("notification-async-");
48+
executor.setCorePoolSize(core); // 스레드풀 크기
49+
executor.setMaxPoolSize(max); // 최대 스레드풀 크기
50+
executor.setQueueCapacity(queue); // 작업 대기 큐 용량
51+
executor.setThreadNamePrefix(name); // 스레드 이름
52+
executor.setRejectedExecutionHandler(handler); // 풀이 가득 찼을 때 대응
5053
executor.initialize();
5154
return executor;
5255
}

src/main/java/com/moa/moa_server/domain/group/repository/GroupMemberRepository.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,4 +42,8 @@ Optional<GroupMember> findByGroupAndUserIncludingDeleted(
4242

4343
@Query("SELECT gm.group.id FROM GroupMember gm WHERE gm.user = :user")
4444
List<Long> findGroupIdsByUser(@Param("user") User user);
45+
46+
@Query("select gm.user.id from GroupMember gm where gm.group = :group and gm.user.id <> :ownerId")
47+
List<Long> findUserIdsByGroupExcludingOwner(
48+
@Param("group") Group group, @Param("ownerId") Long ownerId);
4549
}

src/main/java/com/moa/moa_server/domain/notification/application/producer/GroupNotificationProducerImpl.java

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,12 @@ public class GroupNotificationProducerImpl implements NotificationProducer {
1919
public void notifyAllMembersGroupDeleted(Group group) {
2020
Long ownerId = group.getUser().getId();
2121

22-
List<Long> members =
23-
groupMemberRepository.findAllByGroup(group).stream()
24-
.map(GroupMember -> GroupMember.getUser().getId())
25-
.filter(id -> !id.equals(ownerId)) // 그룹 소유자 제외
26-
.toList();
22+
List<Long> memberIds = groupMemberRepository.findUserIdsByGroupExcludingOwner(group, ownerId);
2723

2824
String content = group.getName() + " 그룹이 삭제되었습니다.";
2925
NotificationEvent event =
30-
NotificationEvent.forMultipleUsers(members, NotificationType.GROUP_DELETED, content, null);
26+
NotificationEvent.forMultipleUsers(
27+
memberIds, NotificationType.GROUP_DELETED, content, null);
3128
eventPublisher.publish(event);
3229
}
3330
}

src/main/java/com/moa/moa_server/domain/notification/application/service/NotificationService.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package com.moa.moa_server.domain.notification.application.service;
22

3-
import com.moa.moa_server.domain.global.cursor.CreatedAtCommentIdCursor;
43
import com.moa.moa_server.domain.global.cursor.CreatedAtNotificationIdCursor;
54
import com.moa.moa_server.domain.notification.dto.NotificationItem;
65
import com.moa.moa_server.domain.notification.dto.NotificationListResponse;
@@ -52,7 +51,7 @@ public NotificationListResponse getNotifications(Long userId, String cursor, Int
5251

5352
String nextCursor =
5453
hasNext
55-
? new CreatedAtCommentIdCursor(
54+
? new CreatedAtNotificationIdCursor(
5655
notifications.getLast().getCreatedAt(), notifications.getLast().getId())
5756
.encode()
5857
: null;

src/main/java/com/moa/moa_server/domain/notification/entity/Notification.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,6 @@ public class Notification extends BaseTimeEntity {
3636
private String redirectUrl;
3737

3838
public void markAsRead() {
39-
if (!this.isRead) {
40-
this.isRead = true;
41-
}
39+
this.isRead = true;
4240
}
4341
}

0 commit comments

Comments
 (0)