Soundmind · MX Team Lead · 2025.02 - Present
KOCCA 한국어 평가
외국인 학생의 한국어 발음을 정확하게 평가하려면 STT가 받는 음성 포맷을 클라이언트에서 만들어 변환 손실을 없애야 했고, 발음과 말하기의 응시 흐름을 단계 단위로 분리해 안정적으로 운영해야 했습니다. Soundmind 시기 정부 R&D 과제로 진행된 외국인 학생 대상 한국어 평가 플랫폼을 팀장으로서 리딩하며 자체 WAV 인코더부터 외부 한국형 STT 폴링 통신, 응시 state machine, 미들웨어 RBAC, 컨테이너 보안 강화까지 풀스택으로 책임지고 산출물로 납품했습니다.
개요
정부 R&D 과제로 진행한 외국인 학생 대상 한국어 평가 플랫폼입니다. 4역할(학생·교사·교사보조·관리자) RBAC, 학교별 멀티테넌트, 다단계 시험 회차 state machine 구조 안에서, 팀장으로서 App Router·Server Action·Route Handler·DB 스키마·컨테이너 배포까지 리딩하며 핵심 설계를 담당했습니다.
마주한 문제
핵심은 STT 포맷 제약이었습니다. 외부 한국형 STT가 특정 PCM 포맷만 받기 때문에 MediaRecorder의 webm/opus를 그대로 보내면 서버 ffmpeg 변환이 필요해 응답 지연, 트랜스코딩 손실, iOS Safari 호환성 문제가 누적됩니다. 동시에 응시 흐름(발음·말하기 단계 분리)의 안정적인 운영, 4역할 RBAC, 학교별 멀티테넌트 격리까지 한 설계 안에서 잡아야 했습니다.
- STT 포맷 제약 — 외부 한국형 STT는 특정 PCM 포맷만 수용 (MediaRecorder webm/opus 그대로 보내면 서버 ffmpeg 변환 + 트랜스코딩 손실 + iOS Safari 호환성 문제)
- 응시 흐름 — 발음·말하기 state machine을 단계별 beep·녹음·자동 전환까지 운영
- 4역할 RBAC — 학생·교사·교사보조·관리자를 인증·인가 흐름에 자연스럽게 녹임
- 학교별 멀티테넌트 — 한 학교 사고가 다른 학교로 번지지 않도록 격리 + 컨테이너 공격 표면 축소
풀이 가설
클라이언트에서 STT가 받는 정확한 포맷을 직접 만들면 서버 변환을 통째로 제거할 수 있고 트랜스코딩 손실도 없습니다. STT는 결과를 받기까지 시간이 걸리는 작업이라, 단발 요청보다 폴링 패턴이 적합하다고 판단했습니다.
검토한 대안
MediaRecorder는 서버 변환 비용을 피할 수 없고, Google/AWS STT는 외국인 한국어 학습자 도메인에 학습 데이터가 약했습니다. 무엇보다 R&D 사양상 한국형 STT가 요구 항목이라 외부 STT는 계약상 후보 자체가 아니었습니다.
| 후보 | 장점 | 단점 |
|---|---|---|
| MediaRecorder API (webm/opus) | 브라우저 기본 — 구현 비용 0 | 서버 ffmpeg 변환 필수 — 응답 지연·트랜스코딩 손실·iOS Safari 호환성 문제 |
| Google Cloud STT / AWS Transcribe | 관리형 — 인프라 부담 0 | 외국인 한국어 학습자 도메인 학습 데이터 약함 + R&D 사양상 한국형 STT 요구 (계약상 불가) |
| ★ 자체 WAV 인코더 + 외부 STT 폴링 | 서버 변환 단계 제거 + STT 포맷 정확히 일치 | ScriptProcessor는 W3C deprecated — 장기적으로 AudioWorklet 마이그레이션 필요 |
선택과 근거
자체 WAV 인코더(STT가 요구하는 sample rate로 클라이언트 캡처 + ScriptProcessor + 표준 WAV 헤더 직접 작성)와 외부 STT 폴링으로 풀스택을 구성했습니다.
구현과 시행착오
처음에는 녹음 데이터를 `useState`로 관리했는데 단계 전환 사이에 일어나는 리렌더가 음성 일부를 날리는 케이스가 보였습니다. 원인은 React 상태 업데이트의 비동기 특성이라 `useRef`로 옮겨 음성 누적을 리렌더 사이클 밖으로 빼냈고, 동시에 발음과 말하기는 흐름이 달라 한 state machine으로 묶으면 분기 조건이 복잡해져 따로 잘랐습니다. 학교별 격리는 쿼리 한 곳만 빠뜨려도 다른 학교 데이터가 새는 위험이 있어, 모든 응시·채점 쿼리에 학교 식별자 필터 강제 + 미들웨어가 토큰 소속 학교와 요청 경로 학교 식별자 일치를 검증하는 이중 계층으로 묶었습니다. 녹음 음성은 오브젝트 스토리지에 적재해 채점·재청취 경로를 유지합니다.
- ① STT가 요구하는 sample rate로 클라이언트 직접 캡처 — 서버 리샘플링 + 트래픽 폭증 동시 회피
- ② ScriptProcessor 기반 처리 — AudioWorklet 호환이 부족했던 시기의 지연·CPU 부하 균형점 + 모노 1채널로 STT 입력 사양 정확 일치 + 트래픽/메모리 절반
- ③ 표준 WAV 헤더 직접 작성 — STT가 받는 정확한 포맷을 클라이언트에서 생성
- ★ 응시 흐름 안정성 — 발음·말하기 state machine 분리, 녹음 데이터는 `useState`가 아닌 `useRef`로 누적해 리렌더 사이 음성 유실 방지
- ★ Server Action(인증, 부수효과) vs Route Handler(데이터, 단순 조회) 의도적 분리 — 클라이언트 번들 가벼움
- ★ 학교별 격리 — 모든 응시·채점 쿼리에 학교 식별자 필터 강제 + 미들웨어가 토큰 소속 학교와 요청 경로 학교 식별자 일치 검증
성과
정부 R&D 산출물 납품을 완료했습니다. 자체 WAV 인코더와 STT 폴링이 안정 동작하면서 외부 STT 호환과 음성 정확도를 동시에 확보했고, 학교별 멀티테넌트로 한 DB에서 다수 학교가 동시에 응시·채점 회차를 운영할 수 있게 됐습니다. 컨테이너 보안 강화로 외부에 노출되는 runner 이미지의 공격 표면이 줄어든 상태로 배포됐습니다.
다른 프로젝트