인덱스 b tree ← fcm 토큰 저장 관련
B-Tree 인덱스
가 기본 인덱스 구조(특히 InnoDB 엔진)
_id
필드는 기본적으로 B+Tree 인덱스가 생성-- 사용자 테이블
CREATE TABLE Users (
user_id VARCHAR(255) PRIMARY KEY
);
-- FCM 토큰 테이블
CREATE TABLE FCM_Tokens (
token_id INT AUTO_INCREMENT PRIMARY KEY,
user_id VARCHAR(255),
fcm_token VARCHAR(255),
UNIQUE(fcm_token), -- 중복 방지
FOREIGN KEY (user_id) REFERENCES Users(user_id)
);
-- 인덱스 생성
CREATE INDEX idx_user_fcm ON FCM_Tokens (user_id, fcm_token);
토큰이 수백 개 수준: MongoDB가 간단하고 빠르지만, 수천~수백만 개: MySQL이 더 나은 성능 제공
멘토님이 MySQL을 제안하신 것은 FCM 토큰이 많아질 경우 효율적인 조회, 중복 방지, 관계형 관리가 더 유리할 수 있기 때문으로 추측
MySql 사용 시, 기본 키(PK)를 설정하면 자동으로 클러스터형 B-Tree 인덱스 생성
@Table
의 indexes
속성으로도 설정 가능
여러 필드로 복합 인덱스를 생성할 수 있음
@Entity
@Table(name = "fcm_tokens",
indexes = @Index(name = "idx_user_fcm", columnList = "userId, fcmToken"))
public class FcmToken {
// name = "idx_user_fcm": 생성될 인덱스의 이름.
// columnList = "userId, fcmToken": 인덱스를 적용할 컬럼 목록.
JPA로 생성하는 인덱스는 기본적으로 B-Tree로 처리
JPA에서 @Index
, @Id
, @Column(unique = true)
등을 설정하면 MySQL은 자동으로 B-Tree 인덱스를 생성
MySQL 엔진이 InnoDB라면 jpa 통해 인덱스 설정해도 b-tree 가능
kafka topic 환경 변수 처리
feed:
create: "feed-create"
create-join-subscribe: "feed-create-join-subscribe"
@Value("${feed.create-join-subscribe}")
private String feedCreateTopic;
@KafkaListener(topics = "${feed.create}"
, groupId = "feed-join-subscribe"
, containerFactory = "feedEventListenerContainerFactory")
public void consumeFeedEvent(FeedKafkaRequestDto kafkaFeedRequestDto) {
...
sendMessage(feedCreateTopic, notificationKafkaRequestDto);
}
public void sendMessage(String topic, NotificationKafkaRequestDto kafkaAlarmRequestDto) {
kafkaTemplate.send(topic, kafkaAlarmRequestDto);
}
컨슈머 그룹 id 분리
[Consumer clientId=consumer-comment-notification-consumer-5, groupId=comment-notification-consumer]
clientId
는 특정 컨슈머 인스턴스groupId
는 컨슈머 그룹을 식별합니다. 동일한 groupId
에 속한 컨슈머들은 파티션을 나누어 메시지를 처리Discovered group coordinator localhost:39092 (id: 2147483645 rack: null)
Successfully joined group with generation Generation{generationId=1, memberId='...', protocol='range'}
Successfully joined group with generation Generation{generationId=5, memberId='consumer-comment-notification-consumer-group-5-7c2e184b-11de-4c7c-95a3-f5bb922f1c46', protocol='range'}
generationId=5
는 다섯 번째 리밸런싱을 의미Found no committed offset for partition comment-create-0
[Consumer clientId=consumer-favorite-notification-consumer-3, groupId=favorite-notification-consumer] Node -3 disconnected.
Setting offset for partition comment-create-0 to the committed offset FetchPosition{offset=8, ...}
comment-create-0
파티션의 마지막 커밋된 메시지 위치feed-notification-consumer-group: partitions assigned: [feed-create-join-subscribe-0]
favorite-notification-consumer-group: partitions assigned: [comment-favorite-create-0]
Range
할당 전략을 사용하여 파티션 개수 / 컨슈머 개수를 기준으로 분산@KafkaListener
의 concurrency
설정과 분산 처리
@KafkaListener
에서 concurrency = "10"
을 설정하면, Kafka Consumer가 10개의 스레드를 사용하여 동시에 메시지를 처리concurrency
값은 실제 Kafka Consumer가 처리하는 스레드 수
concurrency
값을 설정하는 것이 좋음concurrency = 10
정도로 설정할 수 있지만, 1000명 이상에게 보내야 한다면 concurrency = 50
또는 그 이상의 설정을 고려할 수 있음
concurrency = 50~100
정도로 설정할 수 있으며 이 값은 실험을 통한 최적화가 필요하나의 토픽(예: topic.feed.create
)을 여러 Consumer 그룹(예: feed-group-1
, feed-group-2
, ...)에서 구독할 수 있음
각 Consumer 그룹은 해당 토픽의 메시지를 독립적으로 처리
// Consumer 그룹 1
@KafkaListener(
topics = "${topic.feed.create}",
groupId = "feed-group-1", // 그룹 1
concurrency = "10",
containerFactory = "notificationFeedEventListenerContainerFactory")
public void consumeFeedNotificationEvent(NotificationFeedRequestDto notificationFeedRequestDto) {
// 알림 처리
}
// Consumer 그룹 2
@KafkaListener(
topics = "${topic.feed.create}",
groupId = "feed-group-2", // 그룹 2
concurrency = "10",
containerFactory = "notificationFeedEventListenerContainerFactory")
public void consumeFeedNotificationEvent(NotificationFeedRequestDto notificationFeedRequestDto) {
// 알림 처리
}
각 그룹은 메시지를 한번만 처리하고, 그룹마다 독립적으로 처리하므로 부하 분산이 가능