728x90
1. 도입
앞선 1, 2편에서는 로그 수집이 왜 필요한지, 그리고 이를 어떻게 Kafka 기반으로 아웃박스 패턴과 함께 구성했는지를 살펴봤습니다.
이번 편에서는 수신 측 서비스가 어떻게 Kafka 이벤트를 받아서 저장하고, 분석 가능한 형태로 가공하는지를 중심으로 다룹니다.
2. 설계 목표
항목 설계 의도
| Kafka 이벤트 수신 | 실시간 로그 수신 |
|---|---|
| 데이터 정규화 | 분석 가능한 구조로 저장 |
| 확장성 | 다양한 action/payload 처리 가능 |
| 쿼리 최적화 | URL, 사용자 기반 분석 가능 |
3. 저장 모델 설계
LogEventEntity
@Entity
@Table(name = "user_activity_log")
@Getter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class LogEventEntity {
@Id
private Long id;
private Long userId;
@Enumerated(EnumType.STRING)
private LogActionType actionType;
private String url;
private LocalDateTime actionTime;
private String clientInfo;
private Long durationMillis;
private boolean success;
private String failureReason;
@ElementCollection(fetch = FetchType.LAZY)
@CollectionTable(name = "user_activity_log_context", joinColumns = @JoinColumn(name = "log_id"))
@MapKeyColumn(name = "key")
@Column(name = "value")
private Map<String, String> context;
}- url, durationMillis, clientInfo 등을 모두 컬럼으로 분리
- context는 유연한 key-value 구조로 확장 지원
- 사용자 행동을 쿼리로 분석하기 쉽게 구조화
4. Kafka 수신 및 저장 흐름
KafkaListener 코드
@Component
@RequiredArgsConstructor
public class LogEventConsumer {
private final LogEventRepository repository;
private final Snowflake snowflake;
@KafkaListener(topics = "ACTIVITY_LOG_SAVE", groupId = "log-consumer")
public void consume(String message) {
LogEvent event = DataSerializer.deserialize(message, LogEvent.class).orElseThrow();
LogEventEntity entity = LogEventEntity.builder()
.id(snowflake.nextId())
.userId(event.getUserId())
.actionType(event.getActionType())
.url(event.getMetadata().getContext().get("url"))
.actionTime(event.getTimestamp())
.clientInfo(event.getMetadata().getClientInfo())
.durationMillis(event.getMetadata().getDurationMillis())
.success(event.getMetadata().isSuccess())
.failureReason(event.getMetadata().getFailureReason())
.context(event.getMetadata().getContext())
.build();
repository.save(entity);
}
}5. 의존성 및 구성 정보
build.gradle
implementation 'org.springframework.kafka:spring-kafka'
implementation 'com.fasterxml.jackson.core:jackson-databind'
implementation 'jakarta.persistence:jakarta.persistence-api'application.yml
spring:
kafka:
bootstrap-servers: localhost:9092
consumer:
group-id: log-consumer
auto-offset-reset: earliest
key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
value-deserializer: org.apache.kafka.common.serialization.StringDeserializer6. 회고 및 확장 방향
- Kafka 이벤트를 수신한 후, 정규화된 엔티티로 저장하면 SQL 기반 분석이 매우 쉬워짐
- URL 기반 요청 수, 행동 타입별 사용자 분포 등 BI 도구 연동이나 대시보드 구성이 가능해짐
- 추후 LogActionType에 따른 우선순위 처리, 별도 테이블 분기 저장도 가능
728x90
'BindProject' 카테고리의 다른 글
| [Backend] 유저 프로필 모듈 구현 1편. 유저 프로필 도입과 설계 구조: Enum 기반 프로필 시스템의 선택과 대안 비교 (0) | 2025.06.22 |
|---|---|
| [Backend] 유저 활동 로그 수집 모듈 개발기 5편 : 마무리 회고 (0) | 2025.06.22 |
| [Backend] 유저 활동 로그 수집 모듈 개발기 3편 : 로그 메타데이터 구현 (0) | 2025.06.22 |
| [Backend] 유저 활동 로그 수집 모듈 개발기 2-3편 : Outbox 설계 이후: 테스트 전략과 실제 구현 결과 (0) | 2025.06.21 |
| [Backend] 유저 활동 로그 수집 모듈 개발기 2-2편 :Kafka 전송 최적화를 위한 배치 전송 구조 개선기 (0) | 2025.06.21 |