
WAVI
실패 이유를 데이터로 기록해 습관을 자동 조정하는 AI 습관 관리 서비스
AS-IS → TO-BE
AS-IS
- Spring Boot·FastAPI를 각각 다른 EC2에 배포
- 두 서버가 퍼블릭 REST로 통신
- 인스턴스가 늘수록 관리 비용 증가
TO-BE
- Docker Compose로 한 인스턴스에 함께 기동
- 컨테이너 내부 통신으로 전환
- main 푸시 시 이미지 빌드·재기동 자동화

서비스
습관을 만드는 일은 실패를 전제로 합니다. 그런데 대부분의 습관 앱은 성공만 기록합니다.
팀이 대학생·직장인을 대상으로 설문한 결과, 실패 원인을 구체적으로 기억하는 사람은 13% 였습니다. 실패는 반복되는데 그 이유가 남지 않으니 같은 자리에서 또 무너집니다.
WAVI는 실패했을 때 한 번의 클릭으로 이유를 남기게 하고(의지 부족·건강 문제·과도한 목표·시간 부족·일정 충돌), 쌓인 데이터로 AI가 습관의 강도와 시간대를 조정합니다. 「500m 수영, 07:00」이 계속 실패하면 「300m 수영, 08:00」을 제안하는 식입니다.
2025 경희대학교 ‘예술적인 소프트웨어’ 공모전과 2025 SW중심대학 연합 SW페스티벌에 출품했습니다. 수상하지는 못했습니다.
맡은 범위
6인 팀(Backend 2, Frontend 2, AI 1, Design 1)에서 팀 리딩과 백엔드를 맡았습니다.
| 영역 | 한 일 |
|---|---|
| 도메인 | 습관 형성·기록·상태 갱신, 메인 화면 데이터 조회 |
| 리포트 | 주간·월간 리포트 생성·조회, 꾸준함 지수 |
| AI 연동 | 습관 등록 시 Gemini 채팅 분석, 리포트용 FastAPI 서버 통신 |
| 인프라 | Dockerfile, GitHub Actions CI/CD, AWS EC2 배포 |
| 문서 | Swagger 설정과 API 문서화 |
기획은 5월에 시작했고 개발은 7월부터 4개월간 했습니다.
시스템 구성

iOS 앱과 Google OAuth2 로그인이 Spring Boot로 들어오고, Spring Boot가 FastAPI를 거쳐 Gemini를 호출합니다. 세션·캐시는 Redis, 영속 데이터는 RDS입니다. main에 푸시하면 GitHub Actions가 이미지를 빌드해 Docker Hub에 올리고 EC2가 그것을 받아 재기동합니다.
트러블슈팅: 서버 두 개를 어디에 띄울 것인가
백엔드가 Spring Boot(API)와 FastAPI(AI) 두 개였습니다. 처음에는 각각 독립된 EC2 인스턴스에 올리고 REST로 통신하도록 설계했습니다. 역할이 다르니 분리하는 게 당연해 보였습니다.
구현하면서 두 가지가 걸렸습니다.
- 관리 비용 — 인스턴스가 둘이면 배포도 둘, 모니터링도 둘, 비용도 둘입니다. 6인 팀에 인프라 담당은 저 하나였습니다.
- 통신 지연 — 리포트 생성은 Spring Boot가 FastAPI를 호출해 결과를 기다리는 구조라, 퍼블릭 네트워크를 한 번 더 타는 것이 그대로 응답 시간에 붙습니다.
한 인스턴스 안에 컨테이너로 묶는 쪽으로 바꿨습니다.
- Docker Compose로 두 서버를 하나의 EC2에서 함께 기동합니다. 인스턴스가 하나로 줄어 배포·비용·모니터링이 모두 한 곳이 됩니다.
- 서버 간 호출이 컨테이너 내부 통신으로 바뀌었습니다. 지연이 줄고, AI 서버를 외부에 열어 둘 이유도 없어졌습니다.
- GitHub Actions로
main에 푸시되면 이미지를 빌드하고 EC2에서 컨테이너를 재기동합니다. 배포가 푸시 한 번으로 끝납니다.
트래픽이 커지면 다시 분리해야 하는 구조라는 건 알고 있었습니다. 다만 MVP 단계에서 인스턴스를 나눠 얻는 확장성보다, 한 사람이 관리할 수 있는 단순함이 더 급했습니다.
데이터 모델
실패를 다루는 서비스라 스키마의 중심이 habit이 아니라 habit_log와 habit_failure_log 입니다. 실패 사유를 자유 입력이 아니라 failure_reason 테이블로 정규화해, 나중에 AI가 패턴을 집계할 수 있게 했습니다.

리포트 쪽은 habit_report를 중심으로 summary·consistency_index·top_failure_reason·recommendation으로 나눴습니다. 리포트 한 건이 여러 관점의 결과를 담기 때문에 한 테이블에 넣으면 컬럼이 계속 늘어납니다.
중간 심사 피드백을 설계에 반영하기
공모전 중간 심사에서 두 가지를 들었습니다. 하나는 칭찬이고 하나는 경고였는데, 둘 다 그대로 설계에 반영했습니다.
“습관 형성 앱 중에서 ’성공’이 아닌 ’실패’에도 포커스를 둔 점이 독창적이고 의미있게 느껴졌습니다.”
“실질적으로 사용자들이 습관적으로 사용할 수 있게끔 만들어야겠다는 생각이 들어, 설계할 때에 BJ Fogg의 행동모델도 한 번 참고해보시면 좋을 것 같습니다.”
BJ Fogg 모델은 우리가 찾아낸 것이 아니라 심사위원이 자료까지 붙여 제안해 준 것이었습니다. 리마인드를 “정해진 시간에 보낸다”로만 두고 있던 터라, 이 지적을 받고 알림 설계를 다시 짰습니다.
행동은 동기(Motivation)·능력(Ability)·자극(Prompt)이 동시에 성립할 때 일어나고, 셋 중 하나라도 0이면 일어나지 않는다는 모델입니다.

실패 사유를 이 축에 매핑했습니다.
- 의지 부족·동기 저하가 많으면 → 동기 쪽 문제로 보고 공감형 메시지 (
시작하는 것만으로 대단한걸요) - 시간 부족·과도한 목표가 많으면 → 능력 쪽 문제로 보고 AI가 강도·시간대를 낮춤 (
500m 대신 100m로 시작해볼까요?)
동기가 낮으면 행동이 극도로 쉬워야 Action Line을 넘고, 행동이 어려우면 강한 동기가 있어야 넘습니다. 같은 실패라도 어느 축이 무너졌는지에 따라 처방이 달라야 한다 — 이 모델을 받아들이고 나서야 알림의 기준이 생겼습니다.
다만 이 알림은 끝내 구현하지 못했습니다. 설계와 메시지 정책까지는 정리했지만 푸시 발송까지 가지 못한 채 기간이 끝났습니다. 아래 회고에 적었습니다.
MVP 사용자 피드백
경희대 재학생 6명에게 MVP를 써 보게 하고 피드백을 받았습니다.
좋았던 것 — 실패 원인을 분석·요약해 보여주니 개선점이 보인다, 리포트의 칭찬이 동기부여가 된다, 습관 추가 시 아이콘이 자동 선택된다.
걸렸던 것 — 스와이프로 달성 기록 시 확인 창이 없어 오기록 우려, 시간 설정 없이 달성 여부만 기록하고 싶다, 채팅으로만 습관을 추가하는 게 오히려 불편하다.
세 번째가 뼈아팠습니다. AI 채팅 입력을 서비스의 특징이라고 생각했는데, 사용자에게는 절차가 하나 더 늘어난 것이었습니다.
더 뼈아팠던 건 이게 이미 경고받은 내용이었다는 점입니다. 중간 심사에서 심사위원이 이렇게 적었습니다.
“이런 형태의 서비스에서 가장 중요한 것은 결국은 사용성입니다. 입력 과정이 1) 반복적이고 2) 복잡하고 3) 시간이 오래 걸릴수록 실제 사용에서 멀어질 것으로 보입니다.”
채팅 입력은 이 세 가지에 정확히 해당했습니다. 그런데 우리는 그걸 ’특징’으로 분류해 두고 넘어갔고, 넉 달 뒤 사용자에게 같은 말을 들었습니다.
지적을 이해하는 것과 그 지적이 내 결정에 해당한다는 걸 아는 것은 다른 일이었습니다. BJ Fogg 모델은 새 개념이라 바로 반영했지만, 사용성 지적은 이미 아는 원칙이라 흘려들었습니다. 아는 이야기일수록 자기 코드에 대입해 보지 않는다는 걸 이때 알았습니다.
회고: 첫 백엔드 프로젝트에서 남은 것
백엔드로 참여한 첫 프로젝트였습니다. CRUD API를 넘어 AI 서버 연동과 배포 파이프라인까지 맡았고, 그 과정에서 배운 것은 위에 적은 대로입니다.
동시에 끝내지 못한 것이 둘 있습니다.
앱을 출시하지 못했습니다. MVP를 만들어 사용자 테스트까지 했지만 스토어에 올리는 데까지 가지 못했습니다. 만든 것과 내놓은 것 사이의 거리를 이때 처음 실감했습니다.
알림을 구현하지 못했습니다. 심사 피드백을 받아 BJ Fogg 모델로 설계까지 마쳤는데, 정작 푸시 발송을 붙이지 못했습니다. 서비스의 핵심이 “적절한 순간에 자극을 준다”는 것이었으니, 가장 중요한 기능을 설계만 하고 끝낸 셈입니다.
이 두 가지가 이후 프로젝트의 기준이 됐습니다. LinKHU를 실제로 배포해 운영까지 끌고 간 것, 마냑에서 인프라를 먼저 세워 둔 것은 여기서 나온 반작용입니다.
기술 스택
JavaSpring BootMySQLRedisAWS EC2AWS RDSDockerDocker ComposeGitHub ActionsSwaggerGemini API