무엇을 만들었나
K-Saju에 그룹 기능을 붙였다. 플로우는 단순하다.
- 누군가 크루를 만들고 링크를 단톡방에 던진다
- 각자 링크를 열어 생년월일(+닉네임)만 입력한다
- 전원이 모이면 — 사주 기반으로 K-Pop 그룹 캐스팅이 나온다
각자에게 포지션이 배정되고(리더 · 비주얼 · 메인보컬 · 래퍼 · 막내 등 8종), 멤버 전원의 페어별 케미 점수 매트릭스, 그중 최고 케미 듀오와 텐션 라인(케미 최하위 페어 — 40분째 메뉴로 싸우는 그 둘)이 지목된다. 그룹 전체에는 “국민 그룹(완전체)” 같은 그룹 컨셉이 한 줄로 붙는다.
👉 데모: https://ksaju.piyaklabs.com/crew

문제: 이 서비스는 아무것도 저장하지 않는다
K-Saju의 아키텍처 원칙은 처음부터 하나였다. 개인정보 무저장, 완전 스테이트리스.
로그인도 DB도 없고, 모든 결과 페이지는 GET 파라미터를 서버에서 렌더링한다.
/card?date=1995-08-28, /match?a=...&b=... — URL이 곧 상태의 전부다.
그런데 그룹 기능의 이상적인 UX는 “링크 공유 → 각자 자기 생일 기입 → 누적”이다. 누군가는 참여자 입력을 모아야 하고, 그건 상태다. 원칙과 UX가 정면으로 충돌한다.
검토한 옵션은 세 가지였다.
- A. 완전 스테이트리스 — 한 명이 전원의 생일을 URL에 인코딩. 저장 0이지만 “총무 한 명이 다 입력”하는 모델이라 셀프 기입의 재미(그리고 바이럴 구조)가 죽는다.
- B. 최소 임시 저장 — 랜덤 ID의 경량 스토어에 생년월일만, TTL로 자동 삭제.
- C. 클라이언트 누적 — 입력을 클라이언트에서 모아 공유 상태를 갱신. 저장은 없지만 “링크 하나를 모두가 여는” 단톡방 시나리오와 안 맞고 복잡도만 올라간다.
해법: 로비는 30일, 결과는 영원히
결론은 B와 A의 하이브리드다. 상태를 “모으는 동안”과 “다 모인 뒤”로 쪼개면 긴장이 풀린다.
- 로비(모으는 동안): 30일 TTL 임시 스토어. 저장하는 건 생년월일 + 타임존 + 선택적 닉네임뿐 — 이름도, 연락처도, 계정도 없다. 기한이 지나면 통째로 삭제된다.
- 결과(다 모인 뒤): 전원 데이터를 파라미터로 인코딩한 영구 스테이트리스 퍼머링크
(
/crew/r?...)를 발급한다. 로비가 만료돼 사라져도 결과 링크는 계속 동작한다. 기존 원칙 그대로 — URL이 곧 상태다.
만든 사람의 삭제 권한은 URL fragment에 담은 adminKey로 처리했다
(/crew/abc123#k=...). fragment는 브라우저가 서버로 전송하지 않으니, 서버 로그 어디에도
관리 키가 남지 않는다. 작지만 마음에 드는 디테일.
어뷰즈 가드는 두 겹: join 요청 직렬화(동시 가입 레이스 방지), IP별 일일 생성/가입 캡.
캐스팅 엔진: 랜덤이 아니라 결정론
포지션 배정을 랜덤으로 하면 재미가 하루를 못 간다. 같은 멤버로 다시 돌리면 같은 결과가 나와야 하고(“이거 진짜야?” → 재검증이 바이럴의 절반이다), 왜 그 포지션인지 근거가 있어야 단톡방에서 싸울 거리가 된다.
- 기존 2인 케미 엔진(
computeChemistry)을 N명 페어와이즈로 돌려 매트릭스를 만들고, 평균 + 오행 커버리지 보너스로 그룹 점수를 집계한다. 최고/최저 페어가 곧 베스트 듀오와 텐션 라인이 된다. - 포지션은 각자의 오행 분포 · 십성 축 · 신강약 신호에서 결정론적으로 유도한다. 8개 포지션 각각에 “왜 이 캐스팅인가” 설명 라인이 붙는다 — 점수만 던지지 않고 근거를 보여주는 게 이런 엔터테인먼트 기능에서도 의외로 중요했다.
- 분포는 300개 시뮬레이션 그룹으로 튜닝했다. 특정 포지션이 과점되거나, 그룹 컨셉 6종 중 하나만 계속 나오면 재미가 없으니까.
// engine/src/groupChemistry.ts — 시그니처만
export function computeGroupChemistry(members: CardPayload[]): GroupChemistryResult;
// 3~7명. 페어와이즈 매트릭스 + 8포지션 캐스팅 + 그룹 컨셉. 순수함수, 결정론.
인원은 3~7명으로 제한했다. 아이돌 그룹 크기의 감성이기도 하고, 페어 수가 C(n,2)로 늘어 매트릭스 가독성과 OG 렌더 부하가 함께 나빠지기 때문이기도 하다.
배운 것
- 원칙은 쪼개면 지킬 수 있다. “무저장 vs 그룹 기능”은 양자택일처럼 보였지만, 상태의 수명을 로비/결과로 분리하니 둘 다 가져갈 수 있었다. TTL이 지나면 우리 손에는 아무것도 남지 않고, 사용자 손에는 영구 링크가 남는다.
- 엔터테인먼트일수록 결정론. 같은 입력 → 같은 결과 + 설명 가능한 근거가 “재미로 보는” 콘텐츠의 신뢰(그리고 재공유)를 만든다.
- 바이럴 구조는 기능 설계에서 결정된다. 2인 케미는 1:1 공유로 끝나지만, 그룹은 N명이 각자 자기 포지션 카드를 재공유한다. 곱셈기는 마케팅이 아니라 설계 단계에서 심는 것.
K-Saju는 재미로 보는 K-컬처 엔터테인먼트 서비스입니다. 사주 리딩은 예언이 아니라 콘텐츠로 즐겨주세요. 🐣
댓글
댓글 불러오는 중...
댓글 남기기
작성하신 댓글은 관리자 승인 후 게시됩니다.