요즘IT
위시켓
AIDP - AX
Rise ERP
콘텐츠프로덕트 밸리
요즘 작가들컬렉션물어봐
놀이터
콘텐츠
프로덕트 밸리
요즘 작가들
컬렉션
물어봐
놀이터
새로 나온
인기
개발
AI
IT서비스
기획
디자인
비즈니스
프로덕트
커리어
트렌드
스타트업
서비스 전체보기
위시켓요즘ITAIDP - AXRise ERP
고객 문의
02-6925-4867
10:00-18:00주말·공휴일 제외
yozm_help@wishket.com
요즘IT
요즘IT 소개작가 지원
기타 문의
콘텐츠 제안하기광고 상품 보기
요즘IT 슬랙봇크롬 확장 프로그램
이용약관
개인정보 처리방침
청소년보호정책
㈜위시켓
대표이사 : 박우범
서울특별시 강남구 테헤란로 211 3층 ㈜위시켓
사업자등록번호 : 209-81-57303
통신판매업신고 : 제2018-서울강남-02337 호
직업정보제공사업 신고번호 : J1200020180019
제호 : 요즘IT
발행인 : 박우범
편집인 : 노희선
청소년보호책임자 : 박우범
인터넷신문등록번호 : 서울,아54129
등록일 : 2022년 01월 23일
발행일 : 2021년 01월 10일
© 2013 Wishket Corp.
로그인
요즘IT 소개
콘텐츠 제안하기
광고 상품 보기
개발

23년 된 동네슈퍼를 데이터로 분석하기: 무엇이 팔렸나?

평범한사람들의 작은혁신
13분
2시간 전
120
에디터가 직접 고른 실무 인사이트 매주 목요일에 만나요.
newsletter_profile0명 뉴스레터 구독 중

지난 글 ‘23년 동안 살아남은 동네슈퍼를 데이터로 분석해봤습니다’에서 제가 한 일은 단순했습니다. 기존 나들가게 POS에서 월별 매출, 거래 건수, 객단가, 현금매출과 카드매출 데이터를 크롤링하고, 이를 Supabase에 저장해 별도의 대시보드에서 볼 수 있도록 만들었습니다.

 

기존 POS에서도 데이터를 조회할 수는 있었습니다. 그러나 과거 데이터를 조회할 때마다 직접 하나씩 하나하나 조회해야 했고, 여러 메뉴에 정보가 흩어져 있어 전체 흐름을 한눈에 파악하기 어려웠습니다. 그래서 한 번 수집한 과거 데이터는 데이터베이스에 저장하고, 이후부터는 복잡한 POS 사이트에 다시 접속하지 않고 바로 불러오는 구조로 변경했습니다. 그 결과, POS가 처음 도입된 2011년의 매출과 2026년의 매출을 같은 화면에서 비교할 수 있는 기반이 만들어졌습니다.

 

이 단계까지만 해도 나름대로 의미가 있었습니다. 오래된 시스템 안에 갇혀 있던 데이터를 외부로 꺼냈고, 과거의 기록을 빠르게 탐색할 수 있게 되었기 때문입니다.

 

하지만 여전히 알 수 있는 것은 다음 정도였습니다.

 

  • 이번 달 매출이 얼마인지
  • 지난달보다 올랐는지 내렸는지
  • 거래 건수가 늘었는지
  • 객단가가 달라졌는지
  • 현금과 카드의 비중이 어떻게 변했는지

 

매출이 왜 달라졌는지 알려면 결국 무엇이 팔렸는지까지 내려가야 했습니다. 이번 편에서는 프로젝트가 어디까지 확장되었는지, 그리고 데이터를 많이 모으는 것만으로는 왜 점주의 의사결정을 도울 수 없었는지를 정리해 보려고 합니다.

 

이번에는 ‘얼마나 팔렸는가’에서 ‘무엇이 팔렸는가’로 내려갔다

이번에 가장 먼저 추가한 것은 상품 상세 데이터였습니다.

 

기존에는 하루 총매출과 거래 건수만 가져왔다면, 이제는 POS의 다른 메뉴에 들어가 다음 정보까지 수집하도록 만들었습니다.

 

  • 국산 담배류, 아이스크림류, 소주 등 중분류별 매출
  • 에쎄 수, 떡붕어싸만코처럼 실제 판매된 세부 상품
  • 상품별 판매수량과 매출액
  • 해당 상품에서 남긴 매출이익
  • 거래가 발생한 시간
  • 같은 영수증에 포함된 다른 상품

 

동네슈퍼 대시보드 첫 화면, 매출 기여 TOP5는 국산담배류 28%·아이스크림류 12%·소주 7% 순이고 아래엔 오늘 매출 베스트 상품 목록이 나열됨
<출처: 작가>

 

이 데이터는 월매출 달력보다 수집하기 더 어려웠습니다.

 

상품별 이익은 ‘일일 분류별 상품 판매 현황’에 있었고, 몇 시에 어떤 상품이 팔렸는지는 별도의 ‘상품 판매내역 조회’ 화면에 있었습니다. 결국 두 화면을 각각 크롤링한 뒤, 날짜와 상품을 기준으로 다시 연결해야 했습니다.

 

특히 거래 내역 화면은 하루치 영수증을 하나씩 열어 상품을 확인해야 했습니다. 어떤 날은 하루 데이터를 가져오는 데만 몇 분이 걸렸습니다. 그래서 최근 데이터만 가져오는 것으로 끝내지 않고, 과거 날짜를 하루씩 거슬러 올라가며 자동으로 수집하는 백필 과정을 만들었습니다.

 

월별 매출 데이터는 2011년까지 수집을 마쳤지만, 상품과 영수증 단위의 상세 데이터는 양이 훨씬 많아 아직도 수집 중입니다. 현재는 대략 2014년의 데이터까지 내려가며 차근차근 데이터베이스에 쌓고 있습니다.

 

그리고 이 과정을 대시보드에서 직접 확인할 수 있도록 별도의 ‘수집 현황’ 페이지도 만들었습니다.

 

수집 현황 페이지, 수집 대상일 5,529일 중 상품 데이터 담긴 날 65%·거래 상세 완전 68%이며 연도별 수집 커버리지 바는 2021년부터 100%
<출처: 작가>

 

처음에는 단순히 오래 걸리는 작업이라고만 생각했습니다. 하지만 수집 기간이 길어질수록 어느 연도까지 정상적으로 들어왔는지, 누락된 날짜는 없는지, 달력 매출과 세부 상품 매출의 합계가 일치하는지를 확인하는 기능도 중요해졌습니다.

 

데이터가 많아지면 수집 자체보다 제대로 수집되었는지를 검증하는 일이 더 어려워진다는 사실도 알게 되었습니다.

 

 

데이터가 쌓이자 결국 무료 요금제를 벗어나게 됐다

처음에는 개인 프로젝트이기 때문에 Supabase 무료 요금제로도 충분할 것이라고 생각했습니다. 하지만 영수증 거래 데이터만 60만 건을 넘어섰고, 영수증에 포함된 개별 상품 행은 100만 줄 이상 쌓였습니다. 일별 상품 집계 데이터까지 더해지면서 데이터베이스 용량은 빠르게 증가했습니다.

 

결국 Supabase 무료 요금제에서 제공하는 500MB 한도를 넘겼습니다. 오래된 데이터를 삭제하거나, 최근 2년 정도만 보관하는 방법도 생각해 볼 수 있었습니다. 하지만 이 프로젝트를 시작한 중요한 이유 중 하나가 2011년부터 이어진 가게의 데이터를 한곳에 모으는 것이었습니다.

 

과거 데이터를 삭제하면 비용은 줄어들겠지만, 이 프로젝트가 가진 가장 중요한 자산도 함께 사라지게 됩니다. 결국 Supabase Pro 요금제로 업그레이드했고, 현재 매달 약 3만 8천 원 정도를 지불하고 있습니다.

 

개인 사이드 프로젝트에 매달 비용을 내는 것이 부담스럽지만, 10년이 넘는 실제 가게의 원장을 보존하고 분석하는 비용이라고 생각하고 유지하기로 했습니다.

 

 

데이터가 늘어나면서 대시보드의 구조도 다시 나눴다

상품과 시간대 정보가 추가되자 기존 한 화면에 모든 내용을 담기 어려워졌습니다.

 

그래서 상단 메뉴를 크게 다음과 같이 구분했습니다.

  • 오늘
  • 월별
  • 판단
  • 날씨

 

처음 화면을 열면 ‘오늘’ 탭이 나타납니다. 점주가 가게에서 가장 먼저 궁금해할 정보는 과거의 장기 추세보다 오늘 장사가 어떻게 되고 있는지이기 때문입니다.

 

‘오늘’ 탭에서는 현재까지의 매출, 거래 건수, 객단가, 예상 이익과 함께 오늘 판매된 주요 상품군과 세부 상품을 보여줍니다. 반면, ‘월별’ 탭에서는 한 달 동안 누적된 데이터를 기준으로 다음 내용을 확인할 수 있도록 했습니다.

 

  • 일별 매출 흐름
  • 평균적으로 매출이 높은 요일
  • 거래가 가장 많은 시간대
  • 중분류별 매출과 이익
  • 세부 상품별 매출·이익·판매량 순위
  • 현금과 카드 결제 비중

 

나들 매출 대시보드의 월별 탭 ‘7월엔 뭐가 팔렸나’, 이익률 19.1%·피크 18시 391건과 시간대별 판매 추이, 7월 매출 베스트 상품 목록
<출처: 작가>

 

7월 판매 상품 분류별 패널, 국산담배류 마진9%·구성비23%, 아이스크림류 마진28%, 소주 마진20% 등 카테고리별 이익률과 구성비 목록
<출처: 작가>

 

오늘 판매 상품 시간대별 상세 화면, 10시부터 시각별 거래 건수와 던힐1미리·콩나물 등 개별 판매 품목이 분 단위로 나열됨
<출처: 작가>

 

실제로 데이터를 확인해 보니 담배, 소주, 맥주가 전체 매출에서 매우 큰 비중을 차지하고 있었습니다. 세 상품군을 합치면 매출의 절반에 가까운 기간도 있었습니다.

 

그런데 여기서 재미있는 점이 하나 있었습니다. 매출이 높은 날이 반드시 돈을 많이 남긴 날은 아니었습니다.

 

어떤 날은 음식물 쓰레기 종량제 스티커가 많이 판매되면서 매출액 자체는 높게 나타났습니다. 하지만 이 상품은 가게에 남는 이익이 거의 없기 때문에 매출 순위만 보면 좋은 날처럼 보이지만, 실제 이익 측면에서는 그렇지 않았습니다.

 

매출 기여 TOP5 바 차트, 쓰레기봉투가 17%로 1위지만 마진 2%에 그치고 국산담배류·외산담배류·맥주·소주가 뒤를 이음
<출처: 작가>

 

이 경험을 통해 단순한 매출 순위가 점주에게 잘못된 인상을 줄 수 있다는 사실을 확인했습니다. 얼마나 많이 팔렸는가와 실제로 얼마가 남았는가는 전혀 다른 질문이었습니다.

 

 

보기 좋은 대시보드만 만들고 싶었던 것은 아니었다

사실 제가 처음부터 만들고 싶었던 것은 POS를 현대적으로 다시 디자인한 화면이 아니었습니다.

 

기존보다 정보를 보기 쉽게 만들고, 토스처럼 중요한 숫자가 먼저 들어오도록 UI를 구성하는 것도 필요했습니다. 하지만 그것만으로는 기존 POS를 조금 예쁘게 다시 만든 것에 불과합니다.

 

제가 정말 만들고 싶었던 것은 다음 구조였습니다.

 

Raw Data
무엇이 언제 얼마에 팔렸는가
↓  
Calculation
요일·시간·상품·이익의 관계를 코드와 공식으로 계산
↓
Decision
그래서 점주가 무엇을 확인하거나 바꿔볼 것인가

 

여기서 중요한 점은 계산을 LLM에 맡기지 않는 것입니다. 매출 변화율, 판매지수, 동반구매율, 상품별 마진과 같은 숫자는 SQL과 코드로 계산합니다. LLM은 이미 계산된 결과를 점주가 이해할 수 있는 문장으로 바꾸는 역할만 맡도록 했습니다.

 

LLM이 원본 데이터를 보고 자유롭게 판단하게 하면 그럴듯하지만 근거가 불분명한 설명을 만들 가능성이 있기 때문입니다.

 

 

그런데 첫 번째 ‘인사이트’는 별로 유의미하지 않았다

처음에는 이런 판단 카드를 만들었습니다. 13시 방문을 이익으로 전환할 기회가 있어요. 13시는 거래 건수가 많지만 객단가가 낮으므로 계산대 근처에 고마진 상품을 배치해 보라는 내용이었습니다.

 

처음 화면에 띄웠을 때는 꽤 그럴듯해 보였습니다. 하지만 점주의 입장에서 다시 생각해보니 별로 의미 있는 정보가 아니었습니다.

  • 정확히 어떤 상품을 놓아야 하는지
  • 이러한 패턴이 하루만 나타난 것인지 반복되는지
  • 실제로 얼마나 이익이 늘어날 수 있는지
  • 단골 한두 명의 반복 구매로 만들어진 패턴은 아닌지

 

어느 것도 충분히 설명하지 못했습니다. 결국 ‘데이터를 분석한 문장’처럼 보일 뿐, 실제 행동을 바꿀 만큼 구체적인 판단은 아니었습니다. 그래서 이 카드를 제거하고, 계산 계층을 다시 설계했습니다.

 

 

반복되는 패턴만 판단 후보로 올리기 시작했다

이후에는 하루의 숫자 하나를 보고 인사이트를 만들지 않도록 했습니다.

 

예를 들어, 특정 상품이 금요일에 많이 팔렸다고 판단하려면 다음 조건을 함께 계산합니다.

  • 최근 8주 동안 비교 가능한 금요일이 충분히 존재하는지
  • 8주 중 몇 주에서 같은 패턴이 반복됐는지
  • 다른 요일보다 얼마나 많이 팔렸는지
  • 최근 4주 동안 증가하거나 감소하고 있는지
  • 결과를 만들 수 있는 데이터가 충분히 수집되었는지

 

이를 바탕으로 다음과 같은 결과를 만들고자 했습니다. 최근 8주 금요일 중 7주에서 12~14시 아이스크림 판매량이 다른 시간대보다 높았습니다. 단순히 “금요일에 아이스크림이 많이 팔립니다”라고 말하는 것보다, 표본과 반복성을 함께 보여주는 방식입니다. ‘더 준비하세요’라는 표현도 조심했습니다.

 

현재 데이터에는 실제 재고 수량이나 발주 단위가 없기 때문에 정확히 몇 개를 주문해야 하는지는 알 수 없습니다. 그래서 시스템에서는 ‘발주량’이 아니라, 과거 판매량의 중앙값과 최근 추세를 기준으로 목표 준비량을 보여주도록 만들었습니다.

 

 

가게의 시간을 하나의 리듬처럼 보기 시작했다

데이터가 충분히 쌓이면서 각각의 표를 나열하는 것보다, 가게의 운영 구조를 한 번에 보고 싶다는 생각이 들었습니다. 그래서 ‘판단’ 탭을 일종의 가게 디지털 트윈처럼 다시 구성했습니다. 물론 공장의 설비나 물류 흐름을 실시간으로 복제하는 거창한 디지털 트윈은 아닙니다. 이 가게에서 반복되는 시간과 상품의 구조를 데이터로 옮겨놓았다는 의미에 가깝습니다.

 

이 화면은 네 가지 관점으로 구성했습니다.

 

1) 시간대 리듬

요일과 시간을 히트맵으로 연결해, 언제 가게의 거래가 집중되는지 보여줍니다. 실제로 데이터를 확인해 보니, 요일에 관계없이 대체로 오후 5시에서 8시 사이가 가장 중요한 시간대로 나타났습니다.

 

단순히 피크 시간만 보여주는 것이 아니라, 오전 8~12시, 12~16시, 16~20시, 20~24시로 나누어 각 시간대에 어떤 상품과 상품 조합이 주로 판매되는지도 연결했습니다.

 

시간대 리듬 히트맵, 요일×시간대 거래 밀도를 색으로 표시하고 저녁 16~20시엔 진로이즈백·생탁·음식물쓰레기봉투가 많이 팔림
<출처: 작가>

 

2) 계절 달력

월별로 어떤 카테고리의 비중이 달라지는지 여러 해의 평균으로 계산했습니다. 아이스크림처럼 계절성이 명확한 상품뿐 아니라, 같은 계절에도 매년 반복적으로 증가하거나 감소하는 상품군을 확인할 수 있습니다.

 

계절 달력 선그래프, 국산담배류 23%를 축으로 외산담배류·소주·맥주가 뒤를 잇고 아이스크림류는 7월 5.9%에서 11월 1.9%로 떨어짐
<출처: 작가>

 

3) 명목 매출과 실질 성장

2011년보다 지금의 매출이 높다고 해서 가게가 실제로 더 많은 상품을 판매한다고 볼 수는 없습니다. 그동안 상품 가격 자체가 올랐기 때문입니다. 그래서 공식 소비자물가지수와는 별개로, 우리 가게에서 여러 해 동안 공통으로 판매된 상품들의 단가 변화를 이어 붙인 자체 단가 지수를 만들었습니다.

 

이를 통해 매출이 가격 상승 때문에 오른 것인지, 실제 거래와 상품 판매가 늘어 오른 것인지를 구분해보려 했습니다. 이 지수는 공식 물가지표가 아니라, 어디까지나 우리 가게의 판매가격을 기준으로 계산한 지표라는 점도 화면에 함께 표시했습니다.

 

연도 구조 변화 그래프, 2015년을 100으로 놓고 명목매출·실질매출·가격지수·거래건수·객단가 흐름을 겹쳐 그려 2025년 실질 성장 -3.6%를 보여줌
<출처: 작가>

 

4) 상품의 세대교체

같은 카테고리 안에서도 시간이 지나면서 대표 상품이 바뀝니다. 과거에 많이 팔렸던 상품의 점유율이 줄어들고 새로운 상품이 이를 추월하는 시점을 찾아, 어떤 제품이 어떤 제품으로 대체됐는지 시각화했습니다.

 

데이터에서는 박카스 중심이던 에너지음료 수요가 몬스터 같은 새로운 상품으로 이동하는 모습도 확인할 수 있었습니다.

 

상품 세대교체 막대그래프, 동아제약 박카스F 액이 2018년 26%로 정점을 찍은 뒤 줄고 해태음료 몬스터 에너지가 2022년부터 새로 자리잡음
<출처: 작가>

 

과거에 무엇이 팔렸는지를 단순히 보여주는 것은 “그때는 그랬구나”에서 끝날 가능성이 큽니다. 제가 찾고 싶었던 것은 과거의 기록 자체가 아니라, 현재 상품 운영에 영향을 줄 만큼 반복되거나 구조적으로 변한 패턴이었습니다.

 

 

함께 사가는 상품도 데이터로 꺼내보았다

영수증 단위의 상품 데이터가 생기면서 어떤 상품을 함께 구매하는지도 계산할 수 있게 되었습니다. 다만 판매 건수가 많은 상품끼리는 우연히 함께 잡힐 가능성도 높습니다.

 

그래서 단순 동반 구매 횟수뿐 아니라, 두 상품이 각각 팔릴 확률과 비교해 실제로 함께 구매될 가능성이 몇 배 높은지를 나타내는 lift도 함께 계산했습니다.

 

그 결과 POS에 기록된 상품명을 그대로 기준으로 다음과 같은 조합을 발견했습니다.

 

  • [시원] 360ml 보조 상표(통합) + 카스1000L
  • 98회 함께 판매, 일반적인 경우보다 20.5배 높은 조합
  • [시원] 360ml 보조 상표(통합) + 팔리아멘트 아쿠아5
  • 53회 함께 판매, 일반적인 경우보다 22.8배 높은 조합

 

함께 사가는 조합 리스트, [시원]360ml 보조상표와 카스1000L을 같이 사는 경우가 우연 대비 20.5배로 나타나는 등 동시구매 배수를 정리함
<출처: 작가>

 

부모님은 오랫동안 가게를 운영했기 때문에 어떤 상품이 함께 팔리는지 어느 정도 이미 알고 계셨을 수 있습니다. 다만 그동안은 경험과 감각으로 알고 있던 사실을 데이터상에서 정확한 횟수와 수치로 꺼낼 수 있게 되었습니다.

 

그렇다고 이 결과만 보고 곧바로 두 상품을 묶어 팔거나 진열을 바꾸는 것은 아닙니다. 두 상품을 함께 산 사람이 실제로 여러 명인지, 한 명의 단골이 반복해서 구매한 것인지 현재 POS 데이터만으로는 알 수 없기 때문입니다.

 

결국 동반 구매 분석도 정답이라기보다, 점주가 현장을 다시 살펴볼 질문의 출발점에 가깝습니다.

 

 

2011년부터의 날씨도 가게 데이터와 연결했다

이번에는 POS 밖의 데이터도 처음으로 연결했습니다. 기상청 데이터를 활용해 2011년부터 현재까지 부산의 기온, 습도, 강수량, 운량 등의 날씨 정보를 수집했습니다.

 

현재는 약 5,690일의 일별 날씨와 13만 건이 넘는 시간대별 관측 데이터가 쌓여 있습니다. 처음에는 같은 달과 같은 요일 안에서 날씨가 더운 날과 그렇지 않은 날을 비교했습니다. 하지만 15년 가까운 데이터가 생기자 같은 달에만 한정할 필요가 없다는 생각이 들었습니다.

 

작년 8월과 올해 7월이라도 기온과 습도, 강수 조건이 비슷하다면 가게의 입장에서는 충분히 비교할 수 있는 날이기 때문입니다. 그래서 현재는 강수 여부를 먼저 나누고, 기온·습도·운량이 오늘과 가장 비슷했던 과거 날짜를 전 기간에서 찾는 방식으로 변경했습니다.

 

그 뒤 비슷한 날들에 평소보다 더 팔렸던 상품과 덜 팔렸던 상품을 보여줍니다.

 

날씨 연동 화면, 오늘 최고 36.3℃ 폭염 표시와 함께 닮은 날엔 월드콘·탱크보이·메로나가 더 팔리고 쓰레기봉투10L은 덜 팔린다는 예측을 보여줌
<출처: 작가>

 

예를 들어, 단순히 “더운 날에는 아이스크림이 잘 팔립니다”라고 보여주는 것은 큰 의미가 없습니다. 대신 다음과 같이 보여주는 것이 목표입니다. 오늘과 비슷한 날씨였던 과거 20일에서 비비빅은 하루 평균 9개, 메로나는 3.2개 더 판매됐습니다. 판매량이 0.02개에서 0.4개로 올랐다면 수치상으로는 20배지만, 실제로는 하루 0.38개 차이에 불과합니다.

 

그래서 배수보다 하루에 실제로 몇 개 더 팔렸는지를 기준으로 순위를 정했습니다. 다만 이것 역시 인과관계라고 단정할 수는 없습니다. 기온이 비슷한 날에 특정 상품이 함께 많이 팔렸다는 사실을 보여줄 뿐, 날씨 때문에 해당 상품이 팔렸다고 확정하는 것은 아닙니다. 계절, 요일, 상품의 출시 시점과 단종 여부 같은 다른 요인도 함께 영향을 줍니다.

 

따라서 날씨 탭도 현재는 정답을 알려주는 기능이라기보다, 내일 무엇을 조금 더 준비해 볼지 판단할 근거를 제공하는 단계에 있습니다.

 

 

LLM은 계산하지 않고, 계산된 결과만 설명하게 했다

분석 결과를 점주가 읽기 쉬운 문장으로 바꾸기 위해 LLM API도 연결했습니다. 하지만 LLM이 매출 원장을 직접 보고 계산하거나, 자유롭게 상품 운영 방법을 추천하도록 하지는 않았습니다.

 

역할을 다음과 같이 분리했습니다.

SQL과 코드
판매량·마진·반복성·동반구매·날씨 효과를 계산

LLM
계산된 수치와 한계를 사람이 이해하기 쉬운 문장으로 정리

 

LLM이 실패하거나 API를 사용할 수 없는 경우에도 판단 기능이 멈추지 않도록, 같은 내용을 정해진 문장으로 출력하는 템플릿도 함께 만들었습니다. 결국 이 시스템에서 LLM은 판단을 만들어내는 두뇌라기보다, 이미 계산된 결과를 점주의 언어로 번역하는 인터페이스에 가깝습니다.

 

 

기능을 추가할수록 서비스는 느려졌다

이번 프로젝트를 하면서 개발자분들이 새삼 대단하다고 느낀 순간도 많았습니다. 이 서비스는 사실상 한 사람, 많아야 가족 몇 명이 사용하는 개인용 서비스입니다. 대규모 트래픽이 발생하지도 않고, 수많은 사용자가 동시에 요청을 보내지도 않습니다.

 

그런데도 상품 데이터, 판단 탭, 날씨 데이터처럼 기능을 하나씩 추가할 때마다 탭 이동이 느려지고 로딩 시간이 길어졌습니다. 과거 데이터를 매번 다시 계산하면 화면을 열 때 오래 기다려야 했고, 백필 작업이 브라우저를 사용하고 있으면 오늘 데이터를 수집하는 작업이 밀리기도 했습니다.

 

이를 해결하기 위해 다음과 같은 방법을 계속 추가했습니다.

  • 변하지 않는 과거 데이터는 데이터베이스에서 즉시 조회
  • 무거운 분석은 사용자가 접속했을 때가 아니라 야간에 미리 계산
  • 계산 결과는 스냅샷 형태로 저장
  • 사용자가 새로고침하면 과거 백필 작업이 브라우저를 양보
  • 한 번 계산한 값은 캐시에 저장
  • 누락되거나 합계가 맞지 않는 날짜는 별도로 탐지해 재수집

 

혼자 사용하는 서비스에서도 이 정도의 고민이 생겼습니다. 그렇다면 토스처럼 수많은 사용자가 동시에 접속하고, 계속 새로운 기능이 추가되는 서비스에서 백엔드 개발자분들은 얼마나 많은 통신 방식과 캐싱, 동시성, 데이터 정합성 문제를 고민하고 있을까 하는 생각이 들었습니다.

 

화면에서는 버튼을 한 번 누르면 바로 다음 정보가 나오지만, 그 자연스러운 경험 뒤에 얼마나 많은 최적화가 숨어 있는지 조금이나마 체감할 수 있었습니다.

 

 

바이브 코딩에도 기억을 보존하는 장치가 필요했다

기능이 많아지면서 또 다른 문제가 발생했습니다. Claude나 Codex를 이용해 바이브 코딩을 하다 보면, 새로운 터미널과 새로운 대화창에서 작업을 시작하게 됩니다. 그러면 이전 대화에서 왜 특정 구조를 선택했는지, 어떤 오류 때문에 방어 로직을 추가했는지가 사라집니다.

 

새로운 AI는 현재 코드만 보고 다음과 같이 판단할 수 있습니다. 이 부분은 복잡해 보이니 제거해도 되겠습니다. 하지만 실제로는 과거에 발생했던 데이터 오염이나 삭제를 막기 위해 일부러 복잡하게 만들어둔 코드일 수 있습니다.

 

실제로 프로젝트에서는 같은 날 같은 이름을 가진 다른 상품이 한 번에 들어오면서 저장 배치 전체가 실패한 적도 있었습니다. 그 문제로 1,000일이 넘는 상품 데이터가 정상적으로 저장되지 않았고, 백필 작업도 과거로 내려가지 못한 채 같은 구간을 반복하고 있었습니다.

 

또 수집이 2014년에서 멈춘 이유를 처음에는 데이터베이스 용량이나 POS 보관기간 때문이라고 의심했지만, 실제로는 앞선 저장 오류로 미완료 날짜가 쌓여 과거 날짜까지 내려가지 못한 것이 원인이었습니다.

 

이런 맥락을 모르는 AI가 코드를 단순화하면 이미 해결한 문제가 다시 발생할 수 있었습니다.

 

그래서 SSOT.md라는 문서를 만들었습니다.

 

SSOT.md 문서 캡처, ‘선점 가능하게 최적화하지 마라’·‘probe 자체를 제거하지 마라’ 등 바이브 코딩에서 지켜야 할 로직 원칙이 불릿으로 정리됨
<출처: 작가>

 

SSOT는 Single Source of Truth, 즉 하나의 기준이 되는 문서라는 의미입니다.

 

이 문서에는 다음 내용을 계속 기록했습니다.

  • 프로젝트가 어떤 문제를 해결하는지
  • 현재 데이터베이스 구조
  • 각 파일이 맡는 역할
  • 왜 특정 구조를 선택했는지
  • 과거에 발생한 장애와 실제 원인
  • 제거하거나 변경하면 안 되는 로직
  • 현재 진행 중인 작업과 다음 단계

 

AI에 새로운 작업을 맡길 때도 먼저 이 문서를 읽도록 했고, 중요한 구조를 변경하면 코드와 함께 SSOT도 갱신하도록 했습니다. 바이브 코딩은 코드를 빠르게 만드는 데에는 매우 유용합니다.

 

하지만 프로젝트가 길어질수록 중요한 것은 코드를 빨리 생성하는 능력보다, 왜 이런 코드가 존재하는지를 잊지 않는 능력이라는 사실도 알게 되었습니다.

 

 

동네 슈퍼 데이터는 편의점 데이터와 다르다

이 프로젝트에서 계속 경계하고 있는 부분도 있습니다. 우리 가게는 불특정 다수가 끊임없이 방문하는 대형 매장이나 프랜차이즈 편의점이 아닙니다. 주변에 거주하는 단골손님과 매일 담배나 술을 사러 오는 고객의 비중이 높습니다.

 

따라서 특정 상품 조합이 많이 나타났다고 해서 많은 고객이 공통으로 선호한다고 단정할 수 없습니다. 한 명의 단골이 같은 조합을 반복해서 구매했기 때문에 만들어진 결과일 수도 있습니다. 고객 ID가 없는 POS 데이터만으로는 이를 완전히 구분하기 어렵습니다.

 

예를 들어, 데이터를 분석하니 월요일에는 캔커피가 평소보다 약 2.3배 많이 팔리는 패턴이 나타났습니다. 하지만 이 수치만 보고 월요일마다 캔커피를 두 배로 발주하는 것은 위험합니다.

 

먼저 현장에서 다음 질문을 해봐야 합니다.

  • 월요일마다 특정 단골이 대량으로 구매하는 것은 아닌가?
  • 주변 사업장의 근무 일정과 관련이 있는가?
  • 특정 납품이나 작업 일정이 월요일에 몰려 있는가?
  • 실제로 진열 위치를 바꾸면 추가 판매로 이어질 수 있는가?

 

이 시스템이 해야 할 일은 “월요일에는 캔커피를 더 주문하세요”라고 정답을 말하는 것이 아닐 수 있습니다. 오히려 부모님이 오랜 경험 속에서 놓치고 있던 패턴을 꺼내 다음과 같은 질문을 던지는 것이 더 중요할 수 있습니다.

 

  • 월요일에 캔커피가 반복적으로 더 팔리는 이유가 무엇일까?
  • 이 패턴을 이용해 함께 판매할 상품이나 준비 방식을 바꿔볼 수 있을까?

 

 

필요한 데이터는 거의 모았지만 진짜 어려운 문제가 남았다

처음에는 POS 데이터를 가져오는 것 자체가 가장 어려운 문제라고 생각했습니다. 엑셀 다운로드도 제대로 지원하지 않는 오래된 사이트에서 데이터를 크롤링하고, 2011년부터의 기록을 데이터베이스에 저장하는 일이 가장 큰 과제처럼 보였습니다.

 

그런데 지금은 생각이 달라졌습니다. 데이터를 모으는 것은 어렵지만, 언젠가는 끝납니다. 진짜 어려운 문제는 그다음입니다. 수집한 데이터를 어떤 기준으로 연결하고, 어떤 차이를 의미 있는 변화로 판단하며, 어떤 결과만 점주에게 보여줄 것인가.

 

수많은 숫자를 보여주는 것은 쉽습니다. 그러나 그중 실제로 부모님의 다음 행동을 바꿀 만한 신호를 찾아내는 것은 전혀 다른 문제입니다. 현재 시스템은 과거와 비슷한 날, 특정 요일과 시간대의 평균, 함께 팔린 상품, 날씨 조건에 따른 판매 차이를 보여줄 수 있는 단계까지 왔습니다. 하지만 아직은 대부분 관찰과 비교에 가깝습니다.

 

앞으로는 여기서 한 단계 더 나아가고 싶습니다.

 

  • 어떤 패턴이 우연이 아니라 반복되는지
  • 어떤 상품은 늘리고 어떤 상품은 줄여볼지
  • 진열 위치를 바꾸면 실제 이익이 늘어나는지
  • 추천한 행동을 실행한 뒤 결과가 어떻게 달라졌는지
  • 부모님의 현장 경험과 데이터가 서로 충돌할 때 무엇을 다시 확인할지

 

결국 제가 만들고 싶은 것은 가게 운영의 정답을 대신 내려주는 AI가 아닙니다. 점주가 그냥 지나쳤을 수 있는 변화를 발견하고, 무엇을 확인하고 시험해 볼지 더 정확한 출발점을 제시하는 시스템입니다.

 

23년 동안 가게를 운영해 온 부모님의 경험을 데이터로 대체하는 것이 아니라, 그 경험이 새로운 질문을 만날 수 있도록 돕는 것. 그것이 이 프로젝트가 다음 단계에서 풀고 싶은 문제입니다.


<원문>

23년 동안 살아남은 동네슈퍼를 데이터로 분석해보려 합니다. (3편)

 

ⓒ요즘IT의 모든 콘텐츠는 저작권법의 보호를 받는 바, 무단 전재와 복사, 배포 등을 금합니다.