Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package site.holliverse.customer.application.usecase.log;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.f4b6a3.tsid.Tsid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Profile;
Expand All @@ -14,6 +15,7 @@
import site.holliverse.customer.web.dto.log.UserLogRequest;
import site.holliverse.shared.monitoring.CustomerMetrics;

import java.util.ArrayList;
import java.time.Instant;
import java.util.List;

Expand All @@ -30,35 +32,49 @@ public class UserLogAdminDispatchOutboxService {
private final CustomerMetrics customerMetrics;

@Transactional
public void enqueue(Long eventId, Long memberId, UserLogEventName eventName, UserLogRequest request) {
public void enqueueBatch(Long memberId, List<UserLogRequest> requests) {
if (requests == null || requests.isEmpty()) {
return;
}

List<UserLogAdminDispatchOutbox> rows = new ArrayList<>();
for (UserLogRequest request : requests) {
UserLogEventName eventName = UserLogEventName.from(request.eventName());
if (!isAdminTarget(eventName)) {
continue;
}

try {
rows.add(buildOutboxRow(decodeTsidToLong(request.tsid()), memberId, eventName, request));
} catch (IllegalArgumentException e) {
customerMetrics.recordAdminLogFeatureOutbox("invalid_tsid");
log.warn("[UserLog][Outbox] invalid tsid. memberId={}, eventName={}, tsid={}",
memberId, eventName.value(), request.tsid(), e);
} catch (Exception e) {
customerMetrics.recordAdminLogFeatureOutbox("store_error");
log.warn("[UserLog][Outbox] build failed memberId={} eventName={}",
memberId, eventName.value(), e);
}
}

if (rows.isEmpty()) {
return;
}

try {
UserLogPayload payload = new UserLogPayload(
eventId,
request.timestamp(),
request.event(),
eventName.value(),
memberId,
request.eventProperties()
);

repository.saveAndFlush(UserLogAdminDispatchOutbox.builder()
.eventId(eventId)
.memberId(memberId)
.eventName(eventName.value())
.eventType(request.event())
.eventTimestamp(Instant.parse(request.timestamp()))
.payload(objectMapper.valueToTree(payload))
.status(UserLogDispatchStatus.READY)
.attemptCount(0)
.build());
customerMetrics.recordAdminLogFeatureOutbox("stored");
repository.saveAll(rows);
rows.forEach(ignored -> customerMetrics.recordAdminLogFeatureOutbox("stored"));
} catch (DataIntegrityViolationException e) {
customerMetrics.recordAdminLogFeatureOutbox("duplicate");
} catch (Exception e) {
customerMetrics.recordAdminLogFeatureOutbox("store_error");
log.warn("[UserLog][Outbox] batch store fallback. size={}", rows.size(), e);
rows.forEach(this::storeRow);
}
Comment thread
tkv00 marked this conversation as resolved.
}

@Transactional
public void enqueue(Long eventId, Long memberId, UserLogEventName eventName, UserLogRequest request) {
storeRow(buildOutboxRow(eventId, memberId, eventName, request));
}

public void dispatchReadyBatch(int batchSize) {
List<Long> claimedIds = stateService.claimReadyBatch(batchSize);
for (Long eventId : claimedIds) {
Expand All @@ -71,4 +87,59 @@ public void dispatchReadyBatch(int batchSize) {
}
}
}

private UserLogAdminDispatchOutbox buildOutboxRow(
Long eventId,
Long memberId,
UserLogEventName eventName,
UserLogRequest request
) {
UserLogPayload payload = new UserLogPayload(
eventId,
request.timestamp(),
request.event(),
eventName.value(),
memberId,
request.eventProperties()
);

return UserLogAdminDispatchOutbox.builder()
.eventId(eventId)
.memberId(memberId)
.eventName(eventName.value())
.eventType(request.event())
.eventTimestamp(Instant.parse(request.timestamp()))
.payload(objectMapper.valueToTree(payload))
.status(UserLogDispatchStatus.READY)
.attemptCount(0)
.build();
}

private void storeRow(UserLogAdminDispatchOutbox row) {
try {
repository.save(row);
customerMetrics.recordAdminLogFeatureOutbox("stored");
} catch (DataIntegrityViolationException e) {
customerMetrics.recordAdminLogFeatureOutbox("duplicate");
log.debug("[UserLog][Outbox] duplicate event_id={} memberId={} eventName={}",
row.getEventId(), row.getMemberId(), row.getEventName());
} catch (Exception e) {
customerMetrics.recordAdminLogFeatureOutbox("store_error");
log.warn("[UserLog][Outbox] store failed event_id={} memberId={} eventName={}",
row.getEventId(), row.getMemberId(), row.getEventName(), e);
}
}

private boolean isAdminTarget(UserLogEventName eventName) {
return eventName == UserLogEventName.CLICK_COMPARE
|| eventName == UserLogEventName.CLICK_PENALTY
|| eventName == UserLogEventName.CLICK_CHANGE;
}
Comment thread
tkv00 marked this conversation as resolved.

private static long decodeTsidToLong(String tsid) {
if (tsid == null || tsid.isBlank()) {
throw new IllegalArgumentException("TSID must not be null or blank");
}
return Tsid.from(tsid).toLong();
}
Comment thread
tkv00 marked this conversation as resolved.
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@ public class UserLogAdminDispatchScheduler {
@Value("${app.userlog.admin-dispatch.batch-size:100}")
private int batchSize;

@Scheduled(fixedDelayString = "${app.userlog.admin-dispatch.fixed-delay-ms:1000}")
@Scheduled(
initialDelayString = "${app.userlog.admin-dispatch.initial-delay-ms:5000}",
fixedDelayString = "${app.userlog.admin-dispatch.fixed-delay-ms:3000}"
)
public void dispatch() {
outboxService.dispatchReadyBatch(batchSize);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,7 @@ public void publishBatch(Long memberId, List<UserLogRequest> requests) {
doPublish(memberId, request);
}

// 이벤트 전달
requests.forEach(request -> sendAdminTarget(memberId, request));
userLogAdminDispatchOutboxService.enqueueBatch(memberId, requests);
}

@Async("userLogTaskExecutor")
Expand Down
7 changes: 4 additions & 3 deletions src/main/resources/application-customer.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,10 @@ app:
read-timeout-ms: ${ADMIN_READ_TIMEOUT_MS:5000}
userlog:
admin-dispatch:
fixed-delay-ms: ${USERLOG_ADMIN_DISPATCH_FIXED_DELAY_MS:1000}
batch-size: ${USERLOG_ADMIN_DISPATCH_BATCH_SIZE:100}
retry-delay-ms: ${USERLOG_ADMIN_DISPATCH_RETRY_DELAY_MS:10000}
initial-delay-ms: ${USERLOG_ADMIN_DISPATCH_INITIAL_DELAY_MS:5000}
fixed-delay-ms: ${USERLOG_ADMIN_DISPATCH_FIXED_DELAY_MS:3000}
batch-size: ${USERLOG_ADMIN_DISPATCH_BATCH_SIZE:20}
retry-delay-ms: ${USERLOG_ADMIN_DISPATCH_RETRY_DELAY_MS:30000}
max-attempts: ${USERLOG_ADMIN_DISPATCH_MAX_ATTEMPTS:5}
jwt:
secret: ${JWT_SECRET:change-this-secret-key-at-least-32-characters}
Expand Down
Loading