본문 바로가기

개발기/아무거나(메뉴 맞히기)

2편: 엔진은 브라우저에 두고, 서버는 딱 세 가지만

반응형

서비스 구조

가장 먼저 정한 것: 엔진을 어디에 둘까

메뉴를 맞히는 "머리" 역할을 하는 엔진(질문을 고르고, 답을 반영해 후보를 좁히는 로직)을 어디에 둘지가 첫 번째 결정이었습니다. 서버에 두는 게 일반적인 그림이지만, 저는 브라우저(클라이언트) 에 두기로 했습니다.

이유는 코드 주석에 그대로 남겨뒀습니다.

"엔진은 클라이언트에 그대로 둔다. 질문마다 서버를 왕복하면 체감이 나빠지고, 이미 223개 전수 검증을 통과한 코드를 건드릴 이유도 없다. 서버가 맡는 일은 셋이다: 데이터 공급, 학습 영속화, 관측 로그."

정리하면 두 가지입니다. 첫째, 질문 하나 답할 때마다 서버를 왕복하면 반응이 느려집니다. 스무고개는 리듬이 생명인데, 매 질문마다 로딩이 걸리면 재미가 확 깎이죠. 엔진이 브라우저 안에 있으면 답을 누르는 즉시 다음 질문이 뜹니다. 둘째, 이미 전수 검증을 통과한 엔진 코드를 굳이 서버로 옮기면서 깨뜨릴 이유가 없었습니다.

그래서 질문·답변 루프는 전부 브라우저 안에서 돕니다. 이 구간엔 서버 호출이 아예 없습니다.

그럼 서버는 뭘 하나: 딱 세 가지

엔진을 클라이언트로 보냈으니 서버는 홀가분해졌습니다. 서버가 맡는 건 딱 셋입니다.

데이터 공급 — 게임 시작에 필요한 모든 것(메뉴 목록, 질문, 4개국어 문구, 지금까지의 학습 결과, 엔진 상수)을 GET /api/bootstrap 한 번으로 통째로 내려줍니다.

학습 영속화 — 한 판이 끝나 정답이 확정되면, 그 판의 답변들을 데이터베이스에 누적합니다. 이게 서비스가 점점 똑똑해지는 핵심인데, 자세한 건 4편에서 다루겠습니다.

관측 로그 — 세션, 답변, 사용자 제보를 기록합니다. 나중에 "얼마나 잘 맞히나"를 분석하는 데 쓰입니다.

시작할 때 딱 한 번, 통째로 받아온다

부팅 데이터 흐름이 깔끔합니다. 브라우저가 켜지면 /api/bootstrap를 한 번 호출하고, 서버는 이런 응답을 내려줍니다.

 
js
res.json({
  qkeys,     // 질문 키 배열
  qids,      // 인덱스 → 서버 question.id
  menuIds,   // 인덱스 → 서버 menu.id
  pops,      // 메뉴별 사전 인기도(prior)
  yes, no,   // [메뉴][질문] 가상 카운트 2차원 배열
  i18n,      // { ko/en/ja/zh: { qShort[], qText[], menus[] } }
  engine,    // 엔진 상수 뭉치
});

클라이언트는 이걸 받아 메모리 안에 게임용 구조로 재구성하고, 엔진 상수를 덮어쓴 뒤 상관 행렬·배타 맵까지 만들어 둡니다. 그다음부터는 서버 없이 혼자 게임을 굴립니다.

실제로 통으로 받은 응답값

배포 환경이 뭘 줘도 굴러가게: DB 드라이버 이중화

설계 얘기가 나온 김에 서버 쪽 결정 하나를 더 말씀드릴게요. 데이터 저장은 SQLite를 쓰는데, SQLite 드라이버를 이중화해 뒀습니다.

1순위는 better-sqlite3인데, 이건 네이티브 빌드가 필요한 패키지입니다. 빌드 툴체인이 없는 배포 환경에선 설치가 실패할 수 있죠. 그래서 이걸 optionalDependencies에 넣어, 설치에 실패해도 전체 npm install이 죽지 않게 했습니다. 그리고 실패하면 2순위로 Node에 내장된 node:sqlite로 자동 폴백합니다.

 
1순위 better-sqlite3  (require 시도)
   └ 실패 → 2순위 node:sqlite (Node 내장)
        └ 둘 다 실패 → 명확한 에러 + 안내

두 드라이버는 API가 살짝 다른데(트랜잭션 처리 방식 등), 그 차이는 tx() 헬퍼 하나로 흡수했습니다. 덕분에 네이티브 컴파일이 막힌 환경에서도 코드 수정 없이 그대로 굴러갑니다. 배포하다가 빌드 문제로 막히는 게 개인 프로젝트에서 제일 김빠지는 순간이라, 이 부분은 미리 방어해 뒀습니다.

 

직접 사용해보세요. 👇

👉 https://amugeona.up.railway.app/

다음 편에서는

드디어 "머리" 이야기입니다. 예/아니오 답변 몇 개로 어떻게 후보를 좁히는지, 그 많은 질문 중에 다음 질문을 어떻게 고르는지 — 베이지안 엔진의 실제 공식을 뜯어보겠습니다.

반응형