커스텀 메트릭으로 AI 응답 품질 판별 기준을 만들어보자
·
Spring
문제 상황Edukit 서비스는 교사들을 위해 AI를 활용하여 학생부 작성 업무를 돕는 서비스다. 하지만 이 서비스를 만드는 구성원은 현직 교사가 아닌 개발자 3명이었기 때문에, AI가 생성하는 응답의 품질을 명확하게 판단하기 어려웠다. 이를 보완하기 위해 설문조사를 통해 만족도를 수집했지만, 운영 서버에 새 기능을 배포할 때마다 질문을 초기화해야 하는 번거로움이 있었고, 사용자 수에 비해 응답률도 높지 않아 한계가 있었다. 이러한 상황이 반복되면서 서비스 개선 과정에서도 응답 품질에 대한 확신을 점점 잃게 되었다.그러던 중 소마 전담 멘토링을 통해 커스텀 메트릭이라는 개념을 알게 되었고, 이를 활용하면 실제 사용 패턴을 기반으로 AI 응답 품질을 정량적으로 확인할 수 있지 않을까? 라는 생각을 하게 되었..
Read View, Undo Log를 통한 Repeatable Read 구현
·
데이터베이스
들어가며이전의 글들에서 트랜잭션 격리 수준에 따라 데이터 일관성이 어떻게 관리되는지 살펴보았다. 특히 Repeatable Read 격리 수준에서 Undo Log를 활용하여 어떻게 데이터 일관성을 보장하는지에 대해 다루었는데, 설명이 다소 간략해 아쉬운 부분이 있었다. 이번 글에서는 그 내용을 보완하여, InnoDB가 Consistent Read와 Read View를 통해 일관성을 유지하는 과정을 좀 더 자세히 살펴보고자 한다. 실습 환경은 아래 그림과 같다. 데이터베이스에는 users 테이블과 wallet 테이블, 총 두 개의 테이블이 존재하며 각각의 테이블에 3개의 데이터가 저장되어 있다.단일 트랜잭션 상황먼저 기본적인 단일 트랜잭션 상황을 가정해보자.하나의 트랜잭션에서 wallet 테이블의 Money..
메시징 패턴 (feat. Pub/Sub, Queue, Event Stream)
·
Etc
메시징이란메시징은 발신자와 수신자를 분리하는 방법이다. 메시징 시스템은 가장 단순하게 말하면 시스템의 한 부분에서 다른 부분으로 정보를 전달하는 것을 의미한다. 생산자가 메시지를 보내면, 브로커가 이를 저장하고 라우팅하며, 소비자가 가져가서 필요한 작업을 수행한다. 이 과정에서 서비스는 다른 서비스가 작업을 완료할 때까지 기다리지 않는다. 단지 메시지를 전달하고 곧바로 다음 일을 진행한다. 메시지는 브로커에 안전하게 저장되며, 수신자는 준비가 되었을 때 메시지를 처리한다. 만약 수신자가 실패하더라도 메시지는 대기 상태로 남아 있다. 하지만 모든 메시징 시스템이 동일한 동작 방식을 제공하는 것은 아니다. 이번 글에서는 자주 나타나는 3가지 주요 패턴에 대해 살펴 보고자한다.핵심 개념생산자, 소비자, 브로커..
I/O Multiplexing이란?
·
운영체제
I/O 작업이란멀티플렉싱에 대한 내용에 앞서, 먼저 I/O 작업이 무엇인지 어떻게 동작하는지부터 알아야 한다. I/O란, input/output의 약자로 말 그대로 데이터의 입출력을 의미한다. I/O에도 여러 종류가 존재하는데, 대표적으로 네트워크(socket) I/O, 파일 I/O 등등이 있다.I/O 작업은 사용자 공간에서 직접 수행할 수 있기 때문에 커널에 I/O 작업을 요청하고 응답을 받는 구조다. 응답을 어떤 순서로 받는지(synchronous/asynchronous), 어떤 타이밍에 받는지(blocking/non-blocking)에 따라 여러 모델로 분류된다.이전의 포스트에서 동기와 비동기에 대해서 자세히 다루었으니, 이번 글에서는 간단하게 짚고 넘어가도록 하겠다.Synchronous, 동기모든..
트러블슈팅 - 로드밸런서의 동작 원리 (feat. Redis Pub/Sub)
·
네트워크
문제 상황SSE(Server-Sent Events)를 활용해 AI 응답을 단계별로 스트리밍하여 사용자 경험을 개선하는 기능을 구현한 후, 테스트를 하였을 때 답변이 전달되지 않는 문제가 발생하였다.흥미로운 점은, 도메인 주소가 아닌 서버 인스턴스의 IP 주소로 직접 요청을 보낼 경우 모든 단계 응답이 정상적으로 도착했다는 것이다. 이를 통해 코드 로직 자체의 문제가 아니라, 서버 앞단에서 발생하는 문제임을 파악할 수 있었다.현재 서비스는 API 서버 2대를 운영하는 분산 환경으로, 사용자의 요청은 ALB(Application Load Balancer)를 통해 분산되고 있는 상황이었고 서비스 로직 흐름으로는 사용자가 생성 요청(POST)을 보내면 즉시 AI를 호출하여 비동기적으로 응답을 생성하고, 이후 G..
Flyway 도입기
·
Etc
도입한 계기프로젝트를 진행하면서 데이터베이스 스키마 변경이 빈번하게 발생했다. 1차 MVP를 배포한 이후에는 사용자 피드백에 따라 새로운 테이블이 추가되었고, 개발 도중 기획이 바뀌면서 기존의 테이블 연관관계가 끊어지는 경우도 있었다. 시간이 지날수록 변경 범위가 커지고 데이터 구조 자체가 자주 달라지다 보니, 매번 데이터베이스에 직접 접속해 쿼리를 작성해 수정하는 과정이 번거롭게 느껴졌다. 게다가 코드만 수정하고 DB 스키마를 반영하지 않아 에러가 발생하는 일도 잦았다. 이런 경험을 겪으며 “DB 스키마도 Git처럼 형상 관리할 수 없을까?”라는 고민을 하게 되었고, 그 과정에서 Flyway를 알게 되어 프로젝트에 도입하게 되었다.Flyway란?Flyway는 오픈소스 데이터베이스 마이그레이션 툴이다. ..
[EduKit] 사용자 경험을 개선해보자 (feat. Polling, SSE)
·
Spring
문제점1차 MVP를 마친 이후, Edukit는 사용자의 피드백을 바탕으로 지속적으로 개선을 이어가고 있다. 성능 및 부하 테스트 과정에서, 서비스의 핵심 기능인 생기부 응답 AI 생성 로직에서 부하 테스트 도중 톰캣 쓰레드 풀이 고갈되는 문제가 발견되었다.현재 구조를 살펴보면, 사용자가 프롬프트를 입력하면 이를 기반으로 OpenAI 서버에 AI 응답 생성을 요청하는 전 과정이 동기식 처리로 이루어져 있다. 이로 인해 각 요청이 톰캣의 스레드를 점유하게 되고, 스레드 풀이 모두 소진되면서 다른 요청들이 대기 상태에 놓였다. 결과적으로 전체 API 응답 속도가 지연되는 병목 현상이 발생한 것이다.톰캣 스레드 고갈도 문제지만, 사용자 체감 속도가 느리다는 점이 더 큰 문제였다. 현재 서비스는 사용자의 한 번의..
Bulk 연산의 문제점은 없을까?
·
데이터베이스
들어가며여러 건의 데이터를 변경하거나 추가 또는 삭제를 할 때, 단건 쿼리를 반복 실행하는 방식 대신 Bulk Update 쿼리를 사용한다면 단건 처리보다 훨씬 효율적이고 성능도 향상시킬 수 있다. 하지만 여기서 한 가지 의문이 생겼다. 만약 처리 대상 데이터가 수십만 건 이상으로 많아진다면 어떻게 될까? Bulk 연산은 만능인가? 이번 글에서는 별크 연산의 문제점에 대해 살펴보고자 한다.Bulk 연산이란?Bulk 연산이란 데이터베이스에서 다수의 데이터를 한 번에 삽입, 수정 또는 삭제하는 작업을 의미한다. 예를 들어 수십만 건의 데이터를 하나씩 반복하여 처리하는 대신, 한 번의 SQL 실행으로 대량의 데이터를 처리하는 것이다. 이러한 방식은 단건 처리와 비교했을 때 여러 면에서 효율적이다.단건 연산의 ..
AWS Lambda를 활용한 일괄 인증 자동화 시스템 구축기
·
Spring
매 학기 교사 인증이 필요해요처음 Edukit을 기획했을 때 교사분들을 인터뷰하며 가장 많이 들었던 요구는 “교사 인증이 철저했으면 좋겠다”는 것이었다. 신뢰할 수 있는 교육 플랫폼을 만들기 위해서는 교사 신분을 확실히 확인하는 절차가 필요했고, 이를 최대한 간편하면서도 정확하게 구현할 방법을 고민했다. 여러 방안을 검토한 끝에, 교육청에서 현직 교사에게만 발급하는 교육청 교사 이메일 도메인을 활용해 인증 메일을 발송하는 방식으로 결정했다. 이렇게 하면 복잡한 서류 절차 없이도 해당 도메인을 통해 교사 신분을 간접적으로 보증할 수 있다는 장점이 있었다.하지만 여기서 한 가지 추가 요구가 있었다. 현재 활동 중인 교사만 인증 대상이 되길 원한다는 것이었다. 이미 가입 시점에서 인증을 받은 회원이라도 실제로..
MDC를 활용한 로깅 개선기
·
Spring
문제상황아래 화면은 현재 우리 서버에서 기록되고 있는 로그 일부이다. 회원이 가입을 하면 비동기로 인증 이메일을 발송하는 로직이 동작하는데, 현행 로그 추적 체계에서는 이 요청이 동일한 흐름(즉, 하나의 요청에서 파생된 작업)인지 식별하기가 어렵다는 문제가 있었다.이 때문에 장애가 발생했을 때도 어떤 요청에서 비롯된 문제인지 추적하기 쉽지 않았다.또한 비동기 작업 흐름과 별개로, 어떤 사용자가 보낸 요청인지 식별할 수 없는 점도 불편함을 키웠다. 실제로 사용자 문의 메일을 통해 장애 보고가 접수되더라도, 해당 상황과 관련된 로그를 바로 찾아내기가 어려워 즉각적인 대응이 지연되곤 했다. 물론 타임스탬프를 통해서 어느정도 구분이 가능하지만, 톰캣의 경우 스레드 풀을 재사용하기 때문에 스레드 이름만으로 로그를..