감정 태그를 5개로 줄이니 기록 완료율이 달라졌어요
선택지가 많을수록 기록이 끊깁니다. 감정 태그를 5개로 고정하면서 바뀐 스키마 설계와 API 응답 구조를 정리했어요.
플랜티(plan-t)를 만들면서 감정 태그를 처음엔 자유 입력으로 뒀어요. 사용자가 뭐라고 쓸지 모르니까 TEXT 컬럼 하나면 되겠다 싶었죠. 직접 만들어 보니 이게 백엔드 입장에서 제일 게으른 선택이 아니었습니다.
자유 입력이 만든 백엔드 부채
emotion TEXT 컬럼은 편해 보이지만, 저장되는 순간부터 문제가 시작돼요. "불안"과 "불안함"과 "불안해"가 전부 다른 행으로 쌓입니다. 주간 통계를 내려고 GROUP BY emotion 한 번 돌려 보니 값이 수십 종류로 흩어져 있었어요. 집계를 하려면 정규화 테이블을 또 만들어야 하고, 오타 보정 로직도 붙여야 합니다.
그래서 태그를 5개로 고정했어요. 기쁨·평온·불안·분노·무기력. 심리학 교과서에서 뽑은 완벽한 분류는 아니고, "이 다섯 중 하나는 반드시 걸린다"는 실용 기준으로 좁혔습니다.
스키마가 얇아지면 API도 얇아져요
태그를 닫힌 집합으로 정의하면 Postgres에서 선택지가 생깁니다. CHECK 제약이나 enum 타입으로 DB가 직접 값을 막아 줘요. 애플리케이션 코드에서 if 분기로 검증하던 걸 DB 제약 한 줄로 내리는 거죠.
create table mood_logs (
id uuid primary key default gen_random_uuid(),
user_id uuid not null references auth.users(id),
emotion text not null
check (emotion in ('joy','calm','anxious','angry','flat')),
logged_at timestamptz not null default now()
);테스트해 보면 잘못된 값은 INSERT 단계에서 바로 튕깁니다. API 레이어에서 검증이 빠져도 데이터는 오염되지 않아요. 물론 서버 코드에서도 검증은 하지만, 최종 경계가 DB에 있다는 게 중요합니다.
응답 구조도 단순해집니다. 프론트가 받아야 할 건 5개 키를 가진 고정 객체뿐이에요.
type EmotionCount = {
joy: number; calm: number; anxious: number; angry: number; flat: number;
};배열 길이가 가변인 응답과 달리, 키가 5개로 고정된 객체는 타입이 정확히 떨어져요. 프론트에서 차트 렌더링할 때 빈 값 처리 분기가 사라집니다.
인덱스와 집계 비용
여기서 부수 효과가 하나 더 있었어요. 카디널리티가 5로 고정되니까 인덱스 설계가 쉬워집니다. (user_id, logged_at) 복합 인덱스 하나로 대부분의 조회가 커버돼요. emotion은 따로 인덱스를 걸 필요가 없습니다. 값이 5종류뿐이라 인덱스를 타는 것보다 스캔 후 필터가 빠른 구간이 대부분이거든요.
- CHECK 제약으로 최종 경계
- 고정 키 5개 응답 타입
- 복합 인덱스 1개면 충분
주간 집계 쿼리도 한 줄로 끝나요.
select emotion, count(*) from mood_logs
where user_id = $1 and logged_at >= now() - interval '7 days'
group by emotion;돌려 보니 결과 행이 항상 5개 이하입니다. 애플리케이션에서 받아서 빈 감정에 0을 채우는 게 전부예요. 정규화 테이블도, 오타 매핑 딕셔너리도 필요 없습니다.
선택지를 줄이는 게 백엔드 일이기도 해요
기획에서 "선택지가 많으면 사용자가 지쳐서 기록을 그만둔다"는 얘기가 나왔을 때, 처음엔 프론트 문제라고 생각했어요. 지금은 다르게 봅니다. 닫힌 집합인지 열린 집합인지는 데이터 모델 결정이고, 그 결정이 스키마·제약·인덱스·응답 타입·집계 쿼리에 전부 내려옵니다.
플랜씨(plan-c)에서 계산기를 만들 때도 비슷했어요. 입력 필드를 줄이면 검증 로직이 줄고, 검증 로직이 줄면 서버 경로가 짧아집니다. 로그인 없이 쓸 수 있는 구조도 이런 단순화 덕분이에요.
감정 기록처럼 매일 반복되는 행동은 마찰이 조금만 있어도 끊깁니다. 그 마찰을 줄이는 결정이 백엔드 쪽에서는 "테이블을 하나 덜 만드는 일"로 돌아온다는 게, 제가 이번에 얻은 소득이었어요. 플랜티는 아직 개발 중이지만 이 부분 하나는 초기에 잡아 둔 게 다행이다 싶습니다.
관련 글
댓글
아직 댓글이 없어요. 첫 댓글을 남겨주세요.