Repository navigation
Fix/#61 company follow question #62
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
d29afd5
3e9925b
7c10ed0
ddfd64c
bb04778
8badeef
cc49f9d
5334826
ff577d0
a4f5cfd
1c7c997
21321dc
5651bd8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,9 +1,12 @@ | ||
| package com.person.repit.tts.service; | ||
|
|
||
| import com.person.repit.common.exception.AlreadyUsingException; | ||
| import com.person.repit.interview.domain.ChatInterviewSession; | ||
| import com.person.repit.interview.domain.ChatQuestion; | ||
| import com.person.repit.interview.domain.PersonaSnapshot; | ||
| import com.person.repit.interview.service.InterviewOperationLock; | ||
| import com.person.repit.interview.type.InterviewRole; | ||
| import com.person.repit.interview.type.InterviewStatus; | ||
| import com.person.repit.tts.domain.AudioState; | ||
| import com.person.repit.tts.domain.TtsProfile; | ||
| import lombok.RequiredArgsConstructor; | ||
|
|
@@ -13,6 +16,9 @@ | |
| import org.springframework.data.redis.core.ReactiveRedisTemplate; | ||
| import org.springframework.stereotype.Component; | ||
| import reactor.core.publisher.Mono; | ||
| import reactor.util.retry.Retry; | ||
|
|
||
| import java.time.Duration; | ||
|
|
||
| @Slf4j | ||
| @Component | ||
|
|
@@ -24,23 +30,31 @@ public class TtsRecoveryService { | |
| private final AudioStateRepository stateRepository; | ||
| private final InterviewTtsScheduler scheduler; | ||
| private final PersonaTtsProfileResolver profileResolver; | ||
| private final InterviewOperationLock operationLock; | ||
|
|
||
| @EventListener(ApplicationReadyEvent.class) | ||
| public void recover() { | ||
| stateRepository.findRecoverable() | ||
| .flatMap(this::recoverState) | ||
| .onErrorContinue((error, ignored) -> log.warn("TTS 작업 복구 실패", error)) | ||
| .concatMap(state -> recoverState(state) | ||
| .retryWhen(Retry.fixedDelay(Long.MAX_VALUE, Duration.ofSeconds(1)) | ||
| .filter(AlreadyUsingException.class::isInstance)) | ||
| .onErrorResume(error -> { | ||
| log.warn("TTS 작업 복구 실패", error); | ||
| return Mono.empty(); | ||
| })) | ||
| .subscribe(); | ||
| } | ||
|
|
||
| private Mono<Void> recoverState(AudioState state) { | ||
| return redisTemplate.opsForValue().get(SESSION_KEY_PREFIX + state.getSessionId()) | ||
| return operationLock.execute(state.getSessionId(), () -> | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 같은 세션의 음성 상태가 여러 개면 재시작 복구 때 하나만 복구됩니다
준비 시점에 재현: 실제 세션별로 묶어서 한 번만 잠그거나(
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [P2] 같은 세션의 복구 항목이 잠금 경합으로 누락될 수 있습니다. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 다른 요청이 잠금을 잡고 있으면 그 상태는 복구되지 않고 그대로 버려집니다
재시작 직후에는 이런 충돌이 실제로 생기기 쉽습니다.
이러면 그 질문의 QUEUED/GENERATING 상태가 스케줄러 작업 없이 Redis에 남아서, 음성을 미리 만들어 두지 못합니다. 사용자가 그 질문의 음성을 요청해야
|
||
| redisTemplate.opsForValue().get(SESSION_KEY_PREFIX + state.getSessionId()) | ||
| .ofType(ChatInterviewSession.class) | ||
| .filter(session -> session.getStatus() == InterviewStatus.IN_PROGRESS) | ||
| .flatMap(session -> recoverQuestion(session, state).thenReturn(true)) | ||
| .defaultIfEmpty(false) | ||
| .flatMap(recovered -> recovered | ||
| ? Mono.empty() | ||
| : stateRepository.delete(state.getSessionId(), state.getQuestionId())); | ||
| : stateRepository.delete(state.getSessionId(), state.getQuestionId()))); | ||
| } | ||
|
|
||
| private Mono<Void> recoverQuestion(ChatInterviewSession session, AudioState state) { | ||
|
|
@@ -54,7 +68,7 @@ private Mono<Void> recoverQuestion(ChatInterviewSession session, AudioState stat | |
| TtsProfile profile = profileFor(session, question.getAskedByPersonaId()); | ||
| int order = session.getQuestions().indexOf(question); | ||
| return stateRepository.delete(state.getSessionId(), state.getQuestionId()) | ||
| .then(scheduler.register(session.getSessionId(), question, profile, 1, order)) | ||
| .then(Mono.defer(() -> scheduler.register(session.getSessionId(), question, profile, 1, order))) | ||
| .then(); | ||
| } | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Redis 포트 접근을 '제한'한 게 아니라 아예 '제거'했습니다
커밋 메시지는 "접근 범위 제한"인데, 실제로는 호스트 포트 매핑이 통째로 빠졌습니다. 부하 테스트 compose 쪽은
127.0.0.1:6380:6379로 바인딩 범위만 줄였습니다. 이렇게 되면 아래 두 경우가 깨집니다..env의REDIS_HOST가 compose 서비스 이름(redis)이 아니라 호스트 주소를 가리키면, chat 컨테이너가 Redis에 연결하지 못합니다.localhost:6379)로bootRun하던 로컬 실행도 연결되지 않습니다.외부 노출만 막는 게 목적이라면
"127.0.0.1:6379:6379"로 바인딩하면 기존 사용처가 깨지지 않습니다. 운영.env의REDIS_HOST값도 한 번 확인 부탁드립니다.