StudyAce 개발 과정 및 회고

김태윤5
StudyAce 개발 과정 및 회고

[회고] 단어장 앱에서 서버리스 기반 올인원 학습 플랫폼 StudyAce로 고도화하기

개인 사이드 프로젝트로 시작했던 StudyAce(스터디에이스)의 첫 프로덕션 런칭 과정을 돌아보며, 그동안 겪었던 기술적 고민과 아키텍처 전환 과정을 공유하고자 합니다.

작은 필요성에서 출발한 토이 프로젝트가 어떻게 클라우드플레어(Cloudflare) 생태계를 품은 풀스택 애플리케이션으로 성장했는지, 그리고 그 과정에서 마주한 크고 작은 문제들을 어떻게 해결해 나갔는지에 대한 기록입니다.


1. 시작: 가벼운 단어장에서 출발하다

이 프로젝트의 시작은 매우 단순하고 명확했습니다. '중국어 수행평가 대비를 좀 더 재미있고 효율적으로 할 수 없을까?'라는 고민이었습니다. 학생들이 종이에 쓰면서 무작정 외우는 것보다, 게임처럼 인터랙티브하게 학습할 수 있는 도구가 필요하다고 느꼈습니다.

그래서 처음에는 HTML/CSS와 Vanilla JS만을 활용하여 '어순 배열 타임어택' 앱을 만들었습니다. 섞인 단어 조각을 클릭해 올바른 문장을 맞추고, Web Speech API를 활용해 원어민 TTS 음성과 병음을 제공하는 가벼운 웹 앱(chinesepractice)이었습니다.

반응은 생각보다 좋았습니다. 하지만 앱을 사용하는 학생들의 피드백을 받다 보니, 단순히 중국어뿐만 아니라 한국사 핵심 요약, 기출문제 풀이, 오답 노트 등 다양한 과목과 기능에 대한 요구사항이 쏟아지기 시작했습니다. 결국 단일 과목용 단어장은 올인원 학습 플랫폼, StudyAce로 진화해야만 했습니다.


2. 아키텍처의 한계와 마주하다

기능이 눈덩이처럼 불어나면서, 초기 아키텍처는 금세 명확한 한계를 드러냈습니다.

초기에는 빠른 프로토타이핑을 위해 백엔드에 Node.js와 Express를 사용했고, 데이터베이스 대신 로컬의 대용량 JSON 파일들(data_*.json)을 읽어와 클라이언트에 제공하는 방식을 사용했습니다.

  1. 데이터 정합성 및 관리의 복잡성: 200문항이 넘는 기출문제 데이터, 단원별 해설, 유저별 오답 노트, 대주제별 정답률 통계 등을 단순 JSON 파일로 관리하다 보니 데이터를 업데이트하거나 관계를 맺어주는 것이 불가능에 가까웠습니다.
  2. 확장성(Scalability) 문제: 트래픽이 늘어날 경우 단일 Node.js 서버와 파일 I/O 방식으로는 응답 속도가 현저히 느려지고 병목이 발생할 것이 뻔했습니다.
  3. 배포의 비효율성: 프론트엔드와 백엔드가 강하게 결합되어 있어, 클라이언트 측의 사소한 UI 수정 하나에도 전체 서버를 다시 빌드하고 재시작해야 하는 불편함이 컸습니다.

3. 서버리스 마이그레이션: Cloudflare 생태계 도입

이러한 문제를 근본적으로 해결하기 위해 아키텍처 전면 개편이라는 과감한 결정을 내렸습니다. 1인 개발 체제에서 인프라 관리 리소스를 최소화하면서도 글로벌 엣지(Edge) 네트워크의 이점을 누릴 수 있는 Cloudflare Pages와 D1을 새로운 기술 스택으로 채택했습니다.

⚡ Cloudflare Pages Functions 도입

기존 Node.js 백엔드를 Cloudflare Pages Functions 기반의 서버리스(Serverless) 환경으로 마이그레이션했습니다. 정적 에셋(HTML, CSS, JS)은 Cloudflare의 글로벌 CDN을 통해 초고속으로 서빙하고, 동적인 API 요청은 Functions가 엣지 환경에서 처리하도록 분리했습니다. API 라우팅 구조를 /backend 하위의 독립적인 함수 단위로 쪼개어 배포함으로써, 프론트엔드와 백엔드의 생명주기를 분리하고 유지보수성을 극대화했습니다.

💾 엣지 데이터베이스, Cloudflare D1

가장 큰 골칫거리였던 JSON 기반의 데이터 관리는 Cloudflare D1(Edge SQLite)을 도입하여 해결했습니다. schema.sql을 설계하여 유저 정보, 학습 세션, 퀴즈 데이터를 RDBMS 형태로 정규화했습니다. wrangler.toml (또는 wrangler.jsonc)을 통해 프로덕션 D1 Binding 설정을 적용했고, 그 결과 서버리스 함수에서 지연 시간(Latency) 없이 데이터베이스에 쿼리를 날릴 수 있게 되었습니다. 이를 통해 유저별 맞춤형 오답 노트와 학습 통계 데이터를 실시간으로, 그리고 안정적으로 제공할 수 있는 기반이 마련되었습니다.


4. 사용자 경험(UX)과 콘텐츠 고도화

백엔드가 안정화된 이후에는 프론트엔드의 사용자 경험과 학습 콘텐츠의 질을 높이는 데 집중했습니다.

  • 스마트 이어풀기 (상태 관리): 50문항 단위의 실전 세트를 풀다가 실수로 창을 닫더라도 걱정할 필요가 없도록 만들었습니다. 클라이언트의 localStorage와 서버의 D1 DB를 동기화하여, 사용자가 이탈했던 정확한 문항 위치와 선택 상태를 복원하도록 상태 관리(State Management)를 구현했습니다.
  • Flashcards (암기 카드) 및 암기 검토 모드: 단순 문제 풀이를 넘어 능동적 학습(Active Recall)을 유도하기 위해 핵심 키워드를 블라인드 처리하는 기능을 추가했습니다. 큰 단원별 키워드는 암기 카드로 빠르게 훑어볼 수 있도록 UI를 구성했습니다.

5. 위기 극복: 3,000줄 코드 증발 사건

물론 개발 과정이 늘 순탄치만은 않았습니다. 가장 아찔했던 순간은 AI 어시스턴트(Gemini)를 활용하여 대규모 리팩토링을 진행하던 중 발생했습니다.

AI 모델의 컨텍스트 처리 한계 및 덮어쓰기 오류로 인해 무려 3,000줄에 달하는 핵심 로직이 한순간에 유실되는 대형 사고가 있었습니다. 눈앞이 아득해졌지만, 다행히 로컬 깃(Git) 히스토리에 주기적으로 저장해 둔 커밋들 덕분에 코드를 안전하게 롤백할 수 있었습니다. 이 사건은 "AI가 코딩을 도와주는 시대에도, 결국 버전을 관리하고 아키텍처를 통제하는 시각은 개발자의 몫"이라는 사실을 다시금 뼈저리게 느끼게 해 준 중요한 터닝 포인트였습니다.


6. 보안 정책과 플랫폼의 안정화

플랫폼이 실제 학생들에게 서비스되기 시작하면서, 학습 콘텐츠의 무단 추출 방지와 서비스 안정성을 위한 몇 가지 방어 로직을 추가했습니다.

  1. Single Device Session: 한 계정을 여러 명이 공유하여 어뷰징하는 것을 방지하기 위해 단일 기기 세션 제한 로직을 구현했습니다.
  2. 안티 디버깅 (Anti-Debugging): 클라이언트 사이드에서 주요 기출문제 데이터나 정답 로직이 개발자 도구(DevTools)를 통해 무단으로 스크래핑되는 것을 방지하고자, 코드 난독화와 기본적인 디버깅 방어 로직을 적용했습니다.
  3. 프리미엄 빌드 자동화: 초기 기여자(Supporters)와 일반 유저의 권한을 분리하기 위해 build_premium.py 스크립트를 작성하여 배포 파이프라인 단에서 기능 플래그(Feature Flags)를 제어하도록 자동화했습니다.

7. 회고 및 향후 계획

작은 단어장 토이 프로젝트가 어느새 Cloudflare 서버리스 생태계 위에서 돌아가는 풀스택 서비스로 성장했습니다. 이 과정에서 아키텍처의 전환이 서비스의 확장성에 얼마나 큰 영향을 미치는지, 그리고 트래픽과 데이터베이스 구조에 대해 깊이 있는 고민을 해볼 수 있었습니다.