hgzero/develop/dev/dev-stt-batch-implementation.md
Minseo-Jo 0209652a90 STT 서비스 배치 방식 구현 완료
주요 구현사항:
- 5초마다 Redis 오디오 버퍼를 배치 처리하여 텍스트 변환
- WebSocket 실시간 오디오 수신 및 Redis Stream 저장
- Azure Speech Service 연동 (시뮬레이션 모드 포함)
- Event Hub 이벤트 발행 (AI 서비스 연동)

아키텍처:
Frontend (오디오 캡처)
  → WebSocket → STT Service
  → Redis Stream (버퍼)
  → @Scheduled(5초) 배치 처리
  → Azure Speech API
  → DB 저장 + Event Hub 발행
  → AI Service (텍스트 분석)

핵심 컴포넌트:
1. AudioWebSocketHandler
   - WebSocket 연결 관리
   - JSON/Binary 메시지 처리
   - Redis Stream에 오디오 저장

2. AudioBufferService
   - Redis Stream 오디오 버퍼링
   - 청크 조회 및 병합
   - 활성 회의 관리

3. AzureSpeechService
   - Azure Speech SDK 연동
   - 배치 단위 음성 인식
   - 시뮬레이션 모드 지원

4. AudioBatchProcessor
   - @Scheduled(5초) 배치 작업
   - 오디오 → 텍스트 변환
   - TranscriptSegment DB 저장
   - Event Hub 이벤트 발행

배치 방식의 장점:
 비용 최적화: Azure API 호출 1/5 감소
 문맥 이해: 5초 분량 한 번에 처리로 정확도 향상
 AI 효율: 일정량 텍스트 주기적 생성
 안정성: 재시도 로직 구현 용이

설정:
- Azure Speech: eastus, ko-KR
- Redis: 포트 6379, DB 3
- WebSocket: /ws/audio
- 배치 주기: 5초 (고정)

다음 단계:
- 프론트엔드 WebSocket 클라이언트 구현
- 실제 Azure Speech API 키 설정
- E2E 통합 테스트 (STT → AI → Frontend)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-27 13:39:22 +09:00

467 lines
13 KiB
Markdown

# STT 서비스 배치 방식 구현 완료 보고서
**작성일**: 2025-10-27
**작성자**: 준호 (Backend Developer)
---
## 📋 개요
**STT 서비스를 배치 처리 방식으로 구현 완료**
- **핵심**: 5초마다 Redis에 축적된 오디오를 배치 처리하여 Azure Speech로 텍스트 변환
- **장점**: 비용 절감, 문맥 이해 향상, AI 분석 효율 증가
- **기술**: Java/Spring Boot, Azure Speech SDK, Redis Stream, WebSocket
---
## 🏗️ 최종 아키텍처
```
┌──────────────────────────────────────────────────────────┐
│ Frontend (회의 화면) │
│ - 오디오 캡처 (매초) │
│ - WebSocket으로 실시간 전송 │
└────────────────────┬─────────────────────────────────────┘
│ WebSocket (ws://localhost:8084/ws/audio)
┌──────────────────────────────────────────────────────────┐
│ STT Service (Java/Spring Boot) │
│ 포트: 8084 │
├──────────────────────────────────────────────────────────┤
│ 1. AudioWebSocketHandler │
│ - WebSocket 메시지 수신 (JSON/Binary) │
│ - Base64 디코딩 │
│ ↓ │
│ 2. AudioBufferService │
│ - Redis Stream에 오디오 청크 저장 │
│ - Key: audio:stream:{meetingId} │
│ - TTL: 1분 │
│ ↓ │
│ 3. Redis Stream (버퍼) │
│ - 오디오 청크 임시 저장 │
│ - 5초 분량 축적 │
│ ↓ │
│ 4. AudioBatchProcessor (@Scheduled) │
│ - 5초마다 실행 │
│ - Redis에서 청크 조회 → 병합 │
│ - Azure Speech API 호출 │
│ - TranscriptSegment DB 저장 │
│ - Event Hub 이벤트 발행 │
└────────────────────┬─────────────────────────────────────┘
│ Event Hub (transcription-events)
┌──────────────────────────────────────────────────────────┐
│ AI Service (Python/FastAPI) │
│ 포트: 8086 │
│ - Event Hub에서 텍스트 수신 │
│ - Redis에 텍스트 축적 (슬라이딩 윈도우 5분) │
│ - Claude API 분석 → SSE로 프론트엔드 전송 │
└──────────────────────────────────────────────────────────┘
```
---
## 🔧 구현 컴포넌트
### 1. Redis Stream 설정
**파일**: `stt/src/main/java/com/unicorn/hgzero/stt/config/RedisStreamConfig.java`
```java
@Configuration
public class RedisStreamConfig {
@Bean(name = "audioRedisTemplate")
public RedisTemplate<String, byte[]> audioRedisTemplate(...) {
// 오디오 데이터 저장용
}
}
```
### 2. AudioChunkDto
**파일**: `stt/src/main/java/com/unicorn/hgzero/stt/dto/AudioChunkDto.java`
```java
@Data
@Builder
public class AudioChunkDto {
private String meetingId;
private byte[] audioData; // 오디오 데이터
private Long timestamp; // 타임스탬프 (밀리초)
private Integer chunkIndex; // 순서
private String format; // audio/webm
private Integer sampleRate; // 16000
}
```
### 3. AudioBufferService
**파일**: `stt/src/main/java/com/unicorn/hgzero/stt/service/AudioBufferService.java`
**핵심 기능**:
- `bufferAudioChunk()`: 오디오 청크를 Redis Stream에 저장
- `getAudioChunks()`: 회의별 오디오 청크 조회
- `mergeAudioChunks()`: 청크 병합 (5초 분량)
- `clearProcessedChunks()`: 처리 완료 후 Redis 정리
### 4. AudioWebSocketHandler
**파일**: `stt/src/main/java/com/unicorn/hgzero/stt/controller/AudioWebSocketHandler.java`
**핵심 기능**:
- WebSocket 연결 관리
- 텍스트 메시지 처리 (JSON 형식)
- `type: "start"`: 녹음 시작
- `type: "chunk"`: 오디오 청크 (Base64)
- `type: "stop"`: 녹음 종료
- 바이너리 메시지 처리 (직접 오디오 데이터)
### 5. AzureSpeechService
**파일**: `stt/src/main/java/com/unicorn/hgzero/stt/service/AzureSpeechService.java`
**핵심 기능**:
- `recognizeAudio()`: 오디오 데이터를 텍스트로 변환
- Azure Speech SDK 사용
- 시뮬레이션 모드 지원 (API 키 없을 때)
**설정**:
```yaml
azure:
speech:
subscription-key: ${AZURE_SPEECH_SUBSCRIPTION_KEY:}
region: ${AZURE_SPEECH_REGION:eastus}
language: ko-KR
```
### 6. AudioBatchProcessor
**파일**: `stt/src/main/java/com/unicorn/hgzero/stt/service/AudioBatchProcessor.java`
**핵심 기능**:
- `@Scheduled(fixedDelay = 5000)`: 5초마다 실행
- 활성 회의 목록 조회
- 각 회의별 오디오 처리:
1. Redis에서 오디오 청크 조회
2. 청크 병합 (5초 분량)
3. Azure Speech API 호출
4. TranscriptSegment DB 저장
5. Event Hub 이벤트 발행
6. Redis 정리
---
## 📊 데이터 흐름
### 1. 오디오 수신 (실시간)
**Frontend → STT Service:**
```javascript
// WebSocket 연결
const ws = new WebSocket('ws://localhost:8084/ws/audio');
// 녹음 시작
ws.send(JSON.stringify({
type: 'start',
meetingId: 'meeting-123'
}));
// 오디오 청크 전송 (매초)
ws.send(JSON.stringify({
type: 'chunk',
meetingId: 'meeting-123',
audioData: base64AudioData,
timestamp: Date.now(),
chunkIndex: 0,
format: 'audio/webm',
sampleRate: 16000
}));
```
**STT Service:**
```java
// AudioWebSocketHandler
handleTextMessage() {
AudioChunkDto chunk = parseMessage(message);
audioBufferService.bufferAudioChunk(chunk);
}
// AudioBufferService
bufferAudioChunk(chunk) {
redis.opsForStream().add("audio:stream:meeting-123", chunk);
}
```
### 2. 배치 처리 (5초마다)
```java
@Scheduled(fixedDelay = 5000)
public void processAudioBatch() {
// 1. 활성 회의 조회
Set<String> meetings = audioBufferService.getActiveMeetings();
for (String meetingId : meetings) {
// 2. 오디오 청크 조회 (최근 5초)
List<AudioChunkDto> chunks = audioBufferService.getAudioChunks(meetingId);
// 3. 청크 병합
byte[] mergedAudio = audioBufferService.mergeAudioChunks(chunks);
// 4. Azure Speech 인식
RecognitionResult result = azureSpeechService.recognizeAudio(mergedAudio);
// 5. DB 저장
saveTranscriptSegment(meetingId, result);
// 6. Event Hub 발행
publishTranscriptionEvent(meetingId, result);
// 7. Redis 정리
audioBufferService.clearProcessedChunks(meetingId);
}
}
```
### 3. Event Hub 이벤트 발행
```java
TranscriptionEvent.SegmentCreated event = TranscriptionEvent.SegmentCreated.of(
segmentId,
meetingId,
result.getText(),
"참석자",
LocalDateTime.now(),
5.0, // duration
result.getConfidence(),
warningFlag
);
eventPublisher.publishAsync("transcription-events", event);
```
### 4. AI 서비스 수신 (Python)
```python
# AI Service (Python)
async def on_event(partition_context, event):
event_data = json.loads(event.body_as_str())
if event_data["eventType"] == "TranscriptSegmentReady":
meetingId = event_data["meetingId"]
text = event_data["text"]
# Redis에 텍스트 축적
await redis_service.add_transcript_segment(meetingId, text, timestamp)
# Claude API 분석 트리거
await analyze_and_emit_suggestions(meetingId)
```
---
## ⚙️ 설정 및 실행
### 1. 환경 변수 설정
**IntelliJ 실행 프로파일** (`.run/STT.run.xml`):
```xml
<option name="env">
<map>
<entry key="AZURE_SPEECH_SUBSCRIPTION_KEY" value="your-key-here"/>
<entry key="AZURE_SPEECH_REGION" value="eastus"/>
<entry key="REDIS_HOST" value="20.249.177.114"/>
<entry key="REDIS_PORT" value="6379"/>
<entry key="REDIS_PASSWORD" value="Hi5Jessica!"/>
<entry key="EVENTHUB_CONNECTION_STRING" value="Endpoint=sb://..."/>
</map>
</option>
```
### 2. 서비스 시작
```bash
# IntelliJ에서 STT 실행 프로파일 실행
# 또는
# Gradle로 실행
cd stt
./gradlew bootRun
```
### 3. 로그 확인
```bash
tail -f stt/logs/stt.log
```
**예상 로그**:
```
2025-10-27 12:00:00 - Azure Speech Service 초기화 완료 - Region: eastus, Language: ko-KR
2025-10-27 12:00:05 - WebSocket 연결 성공 - sessionId: abc123
2025-10-27 12:00:10 - 오디오 청크 버퍼링 완료 - meetingId: meeting-123
2025-10-27 12:00:15 - 배치 처리 시작 - 활성 회의: 1개
2025-10-27 12:00:15 - 음성 인식 성공: 신제품 개발 일정에 대해 논의하고 있습니다.
2025-10-27 12:00:15 - Event Hub 이벤트 발행 완료
```
---
## 🧪 테스트 방법
### 1. WebSocket 테스트 (JavaScript)
```javascript
const ws = new WebSocket('ws://localhost:8084/ws/audio');
ws.onopen = () => {
console.log('WebSocket 연결 성공');
// 녹음 시작
ws.send(JSON.stringify({
type: 'start',
meetingId: 'test-meeting'
}));
// 5초 동안 오디오 청크 전송 (시뮬레이션)
for (let i = 0; i < 5; i++) {
setTimeout(() => {
ws.send(JSON.stringify({
type: 'chunk',
meetingId: 'test-meeting',
audioData: 'dGVzdCBhdWRpbyBkYXRh', // Base64
timestamp: Date.now(),
chunkIndex: i
}));
}, i * 1000);
}
// 10초 후 녹음 종료
setTimeout(() => {
ws.send(JSON.stringify({
type: 'stop'
}));
}, 10000);
};
ws.onmessage = (event) => {
console.log('응답:', JSON.parse(event.data));
};
```
### 2. Redis 확인
```bash
redis-cli -h 20.249.177.114 -p 6379 -a Hi5Jessica!
# 활성 회의 목록
SMEMBERS active:meetings
# 오디오 스트림 확인
XRANGE audio:stream:test-meeting - +
# 스트림 길이
XLEN audio:stream:test-meeting
```
### 3. 데이터베이스 확인
```sql
-- 텍스트 세그먼트 조회
SELECT * FROM transcript_segment
WHERE recording_id = 'test-meeting'
ORDER BY timestamp DESC
LIMIT 10;
```
---
## 📈 성능 특성
| 항목 | 값 | 비고 |
|------|-----|------|
| **배치 주기** | 5초 | @Scheduled(fixedDelay = 5000) |
| **지연 시간** | 최대 5초 | 사용자 경험에 영향 없음 |
| **Azure API 호출 빈도** | 1/5초 | 실시간 방식 대비 1/5 감소 |
| **Redis TTL** | 1분 | 처리 지연 대비 |
| **오디오 청크 크기** | 가변 | 프론트엔드 전송 주기에 따름 |
---
## ✅ 장점
1. **비용 최적화**
- Azure Speech API 호출 빈도 1/5 감소
- 비용 절감 효과
2. **문맥 이해 향상**
- 5초 분량을 한 번에 처리
- 문장 단위 인식으로 정확도 향상
3. **AI 분석 효율**
- 일정량의 텍스트가 주기적으로 생성
- AI가 분석하기 적합한 분량
4. **안정성**
- 재시도 로직 구현 용이
- 일시적 네트워크 오류 대응
5. **확장성**
- 여러 회의 동시 처리 가능
- Redis로 분산 처리 가능
---
## ⚠️ 주의사항
### 1. Azure Speech API 키 관리
- `.run/STT.run.xml`에 실제 API 키 설정 필요
- Git에 커밋하지 않도록 주의
### 2. Redis 연결
- Redis 서버가 실행 중이어야 함
- 연결 정보 확인 필요
### 3. Event Hub 설정
- Event Hub 연결 문자열 필요
- AI 서비스와 동일한 Event Hub 사용
### 4. 배치 주기 조정
- 5초 주기는 기본값
- 필요시 `application.yml`에서 조정 가능
---
## 🔄 다음 단계
1. **프론트엔드 연동**
- WebSocket 클라이언트 구현
- 오디오 캡처 및 전송
2. **E2E 테스트**
- 실제 음성 데이터로 테스트
- Azure Speech API 연동 검증
3. **AI 서비스 통합 테스트**
- Event Hub 통신 확인
- SSE 스트리밍 검증
4. **성능 최적화**
- 배치 주기 조정
- Redis 메모리 사용량 모니터링
---
## 📞 문의
**STT 서비스**: 준호 (Backend Developer)
**AI 서비스**: 서연 (AI Specialist)
---
**최종 업데이트**: 2025-10-27