하츠옹 체크
한국어↔일본어 학습자의 녹음을 원어민 선생님이 억양(피치) 곡선 위에 직접 첨삭해 돌려주는 발음 교정 서비스입니다. 베타로 운영 중입니다.
한국어와 일본어는 글자를 아는 것과 자연스럽게 들리는 것 사이가 유난히 먼 언어입니다. 일본어는 높낮이(피치 액센트)가 단어의 뜻을 가르고, です의 す처럼 모음이 사라지는 무성화는 글자에 드러나지 않습니다. 한국어는 연음과 경음화처럼 표기와 소리가 어긋나는 규칙이 많습니다. 그래서 문장을 읽을 줄 알아도 억양이 어색하면 바로 티가 나는데, 정작 그 어색함은 본인 귀로 듣기 어렵습니다.
하츠옹 체크는 이 억양을 눈에 보이게 만듭니다. 매주 금요일 3문장 미션이 나가고, 수요일까지 제출한 녹음은 피치 곡선이 그려진 "피드백 카드"가 됩니다. 원어민 선생님이 그 위에 그림과 영상으로 첨삭해 돌려줍니다. 일본어를 배우는 한국인과 한국어를 배우는 일본인, 양방향으로 운영합니다.
AI를 배제하는 서비스가 아니라, 기계가 잘하는 일과 사람만 할 수 있는 일을 나누는 서비스입니다. 억양을 추출해 곡선으로 그리는 반복 작업은 기술이 맡아 선생님이 첨삭에만 집중할 수 있게 하고, 사람의 귀로만 잡히는 "언어의 결"은 원어민 선생님이 맡습니다.
학생은 점수나 통계가 아니라 피드백 카드라는 눈에 보이는 형태로 자기 발음이 어떻게 변해왔는지 계속 추적할 수 있습니다. 피치 추출은 브라우저 안에서 돌고, 언어별 처리 정책은 감이 아니라 실측 데이터로 결정했습니다. 카드는 선생님 화면용과 학생 전달용 PNG를 서로 다른 코드가 그리는데, 두 그림이 어긋나면 에러 없이 조용히 다른 카드가 전달되므로 좌표 단위로 일치하는지 테스트로 검증합니다.
이틀 만에 PoC를 만들어 시작했습니다. 첨삭 워크플로가 실제로 돌아가는 시연과 서버 비용이 거의 들지 않는 운영 구조를 보여주며 한국·일본 원어민 운영진을 설득했고, 클로즈 베타 → SNS·커뮤니티 테스터 모집을 거쳐 지금은 베타 서비스로 운영 중입니다.
운영진과 함께 서비스 전체를 한국어·일본어 2개 국어로 만들고, 양국 유저를 대상으로 테스트를 진행했습니다. 운영진의 QA 메모를 버그 재현 → 원인 추적 → 수정 PR로 잇는 파이프라인을 만들어, 비개발자의 리포트가 곧바로 수정으로 이어집니다.
서비스와 별도로, 억양 카드 제작을 자동화할 수 있을지 검증하는 내부 검수 툴 "Into"를 만들었습니다. 음성과 문장을 넣으면 브라우저에서 피치를 추출해 음절(한국어)과 모라(일본어에서 한 박자로 세는 소리 단위. 대체로 가나 한 글자가 한 박자) 단위의 차트를 자동 생성하는데, 핵심 실험은 STT(Whisper)의 단어 타임스탬프를 정렬의 시드로 삼아 음절과 시간의 어긋남을 어디까지 보정할 수 있는가였습니다. 자동은 초안, 선생님의 드래그가 최종본입니다.
선생님의 드래그 보정 데이터를 정답으로 승격해 정렬 알고리즘의 변경을 ms 단위로 채점하는 하네스(harness, 수정할 때마다 정답과 비교해 자동으로 점수를 내는 채점 장치)를 만들었고, 한·일 선생님과 243건의 검수 라운드를 돌려 시작점 밀림을 26건에서 2건으로, 정렬 오차 p90을 34% 줄였습니다.
검수 페이지 자체도 검수자가 빠르고 정확하게 판단하는 데 맞춰 단순화했습니다. 자유 서술 대신 문제 유형 태그로 지적을 남기게 해서, 검수 결과가 그대로 집계 가능한 지표가 되게 했습니다.
전체를 Cloudflare 안에서 운영합니다. 화면과 API는 워커 하나(web-beta)가 맡고, 매일 도는 배치와 알림은 별도 워커(agent-service)와 Durable Object가 처리합니다. 데이터는 D1과 R2에, 첨삭 영상은 Stream에 두고, 로그인은 소셜 OAuth로 처리합니다.
초기에는 속도를 우선해 AI와 함께 빠르게 만들었고, 운영에 들어간 뒤에는 그 코드를 실측하며 하나씩 다듬었습니다. "D1이 느려서 AWS로 옮겨야 하나"라는 이야기가 나왔을 때 wallTime과 cpuTime을 대조해 보니, 정작 느린 것은 세션을 빠르게 하려고 넣어둔 KV 캐시였습니다. 캐시를 걷어내자 세션 조회는 573ms에서 20ms로, 쓰지 않는 데이터까지 나르던 갤러리 응답은 6.6MB에서 186KB로 줄었습니다.
디스코드 알림이 두 번씩 오는 장애도 있었습니다. 두 환경에 cron이 겹쳐 등록된 것이 원인이었는데, 워크플로가 둘 다 "정상 완료"라 에러 로그가 없어서 실행 기록의 시각을 맞대 보고서야 찾았습니다. 같은 일이 반복되지 않게 규칙으로 남겼습니다.
운영진이 던진 질문도 측정으로 답했습니다. "쓸 사람이 없는 시간에 트래픽이 뜬다"의 정체는 60초마다 랜딩 페이지를 통째로 렌더링시키던 업타임 봇(하루 1,440회)이었고, 이른 아침의 트래픽은 등교 전에 몰리는 학생들의 과제 제출이었습니다. 제출은 요청 한 건이 아니라 문장 수만큼의 오디오 업로드에 워크플로와 알림이 붙는 일이라, 몇 명이 같은 시간에 제출하면 수십에서 수백 요청이 됩니다. "카드에 한국어가 빠지는 것 아니냐"는 우려는 캔버스에 그려진 픽셀을 직접 세어 정상임을 확인했습니다.