Big Project · AI's EYE 25

샘플 JSON에서 매장별 운영 데이터로: 시스템 변화의 본질

발표용 메뉴와 정책을 JSON 파일에 두어도 챗봇 답변에는 충분해 보인다. 왜 DB와 점주 화면, 품절 토글까지 제품 범위에 들어갔는지 궁금하다.

근거 · 프로젝트 문서 · 코드 · 테스트

1장: JSON, 챗봇에 충분한가? 정적 데이터의 한계

솔라는 노트북 화면에 떠 있는 프로젝트 회고 문서의 한 문장에 시선을 고정한 채 미간을 찌푸렸다. 문장은 간결했지만, 솔라에게는 군데군데가 안개에 싸인 것처럼 희미했다.

“초기 메뉴와 정책은 샘플 JSON과 AICC 설정에 가까워 점주가 바꿔도 Dashboard, 주문 생성기와 챗봇이 함께 갱신되지 않았다.”

몇 번을 다시 읽어도 마찬가지였다. 고개를 갸웃거리던 솔라는 결국 노트북을 들고 거실에 있는 루나에게 다가갔다.

“언니, 나 이거 이상해. ‘점주가 메뉴를 바꿔도 챗봇이 갱신되지 않았다’는 게 왜 그렇게 큰 문제였던 거지? 그냥 개발자가 JSON 파일 하나 고쳐서 다시 배포하면 되는 거 아냐? 챗봇이 메뉴판 답변만 잘 해주면 되는 거니까, 발표용 샘플 데이터라도 충분했을 것 같은데.”

솔라는 마치 당연한 사실을 확인하려는 듯 말했다. 그녀의 머릿속에서 JSON 파일은 단순하고 명쾌한 해결책이었다. 데이터베이스니, 점주용 관리 화면이니 하는 복잡한 시스템을 도입하는 건 과한 대응처럼 느껴졌다.

루나는 소파에 앉아 책을 읽다 말고 솔라를 보며 조용히 고개를 끄덕였다. 솔라의 질문을 가만히 듣고 난 루나는 책을 덮고 테이블 위로 손을 뻗었다. 그리고는 근처에 있던 메모지 넉 장과 펜을 가져왔다.

“네가 잠시 동안 이 카페의 시스템 관리자가 되어보는 건 어때?”

루나는 메모지 석 장에 각각 ‘챗봇’, ‘주문 시스템’, ‘매장 대시보드’라고 큼지막하게 썼다. 그리고 마지막 한 장에는 ‘마스터 메뉴 (JSON)’라고 적었다.

“상황은 이래. 우리 카페엔 세 가지 시스템이 있어. 고객 응대용 ‘챗봇’, 실제 주문을 처리하는 ‘주문 시스템’, 그리고 점주가 매장 상황을 보는 ‘대시보드’야. 그리고 지금 이 모든 시스템은 개발팀이 관리하는 이 ‘마스터 메뉴’ JSON 파일을 복사해서 사용하고 있어.”

루나는 ‘마스터 메뉴’ 메모지를 솔라 앞에 놓인 세 장의 메모지 위로 살짝 스치며 복사하는 시늉을 했다. 이내 세 장의 시스템 메모지에는 똑같은 내용이 적혔다.

아메리카노: 4500원 (주문 가능) 카페라떼: 5000원 (주문 가능)

“자, 이제 점주가 라떼 가격을 5500원으로 올리기로 결정했어. 솔라 관리자, 어떻게 해야 할까?”

“간단하지.” 솔라는 자신 있게 펜을 집어 들었다. “‘마스터 메뉴’ 파일의 라떼 가격을 5500원으로 고치고, 개발팀에 연락해서 이 파일을 각 시스템에 업데이트해달라고 요청하면 돼.”

솔라는 ‘마스터 메뉴’ 메모지의 ‘5000원’을 지우고 ‘5500원’으로 고쳤다. 그러고는 ‘챗봇’, ‘주문 시스템’, ‘매장 대시보드’ 메모지의 가격도 하나씩 지우고 새로 썼다. 조금 귀찮긴 했지만 불가능한 작업은 아니었다.

“좋아. 그런데 방금 점주에게서 긴급 연락이 왔어. 오늘 아메리카노 원두가 다 떨어져서 더는 팔 수 없대. 당장 메뉴에서 빼야 해. 어떻게 할래?”

“음… 그것도 똑같지.” 솔라는 다시 펜을 들었다. “‘마스터 메뉴’에서 ‘아메리카노: 주문 가능’을 ‘주문 불가’로 바꾸고…”

솔라가 ‘마스터 메뉴’ 메모지를 수정하는 순간, 루나가 손을 들어 막았다.

“잠깐. 네가 파일을 수정하고 개발팀에 전달하고, 개발팀이 각 시스템에 배포하는 데 5분이 걸린다고 해보자. 바로 그 5분 사이에 손님이 챗봇에게 물었어. ‘아메리카노 주문되나요?’ 챗봇은 뭐라고 답할까?”

”…‘주문 가능’이라고 하겠네.” 솔라의 목소리가 조금 작아졌다. 그녀는 아직 가격이 수정되지 않은 ‘챗봇’ 메모지를 내려다보았다.

“맞아. 그리고 다른 손님은 ‘주문 시스템’ 앱으로 아메리카노를 주문했어. 주문이 들어갈까?”

”…들어가겠지.” 솔라의 얼굴이 굳어졌다. 주문 시스템 메모지에도 여전히 ‘주문 가능’이라고 적혀 있었다.

“이제 매장에 있는 점주는 어떨까? 점주는 원두가 떨어진 걸 아는데, ‘매장 대시보드’에는 여전히 아메리카노가 주문 가능하다고 떠 있어. 방금 들어온 주문을 보고 한숨을 쉬며 고객에게 전화해서 주문을 취소해야겠지.”

루나는 테이블 위의 메모지들을 가리켰다. ‘마스터 메뉴’만 ‘주문 불가’로 바뀌었을 뿐, 나머지 세 시스템은 모두 제각각의 낡은 정보를 고객과 점주에게 보여주고 있었다. 한순간에 모든 데이터가 거짓말을 하고 있었다.

솔라는 펜을 내려놓았다. 눈앞에 놓인 네 장의 종이가 마치 서로 다른 시간을 살고 있는 시스템의 파편처럼 보였다. 문제는 JSON 파일 자체가 아니었다. 각자 자신의 복사본을 들고 있었기 때문에, ‘진실’이 전파되는 동안 모든 시스템이 일시적으로 어긋나는 상태에 빠진다는 것이 문제였다. 그 짧은 어긋남이 고객의 불만과 운영의 혼란을 만들고 있었다.

“알겠다.” 솔라가 마침내 입을 열었다. “샘플 데이터가 문제인 게 아니었어. 각 시스템이 자기만의 ‘복사본’을 가지고 있었던 게 문제였네. 점주가 머릿속으로 ‘품절’을 결정한 순간과, 모든 시스템이 그 사실을 알게 되는 순간 사이에 시차가 생겨. 그 시차 동안 데이터는 전부 틀린 정보가 되는 거구나.”

솔라는 아까 무심코 넘겼던 문서의 문장을 다시 떠올렸다. ‘점주가 바꿔도 Dashboard, 주문 생성기와 챗봇이 함께 갱신되지 않았다.’ 이제 그 문장은 단순한 사실의 나열이 아니라, 시스템이 실패하는 장면을 생생하게 보여주는 영상처럼 느껴졌다. 데이터의 불일치가 어떻게 실제 운영의 실패로 이어지는지 명확히 보였다.

“그럼… 복사본을 없애야겠네. 모두가 똑같은 원본 하나만 쳐다보게 만들어야 하는 거 아냐? 실시간으로 바꿀 수 있는 단 하나의 원본을.”

2장: 매장별 동적 운영 데이터, 왜 DB여야 하는가?

솔라는 결심한 듯 테이블 위의 메모지들을 다시 배열하기 시작했다. ‘챗봇’, ‘주문 시스템’, ‘매장 대시보드’라고 적힌 세 장의 종이를 치우고, 중앙에 ‘마스터 메뉴 (JSON)’라고 적힌 한 장만 남겼다. 어지럽던 데이터의 흐름을 정리하고 단 하나의 원본만 남기겠다는 의지의 표현이었다.

그리고는 마치 당연하다는 듯, 그 ‘마스터 메뉴’ 종이에서 다른 시스템들을 향해 화살표를 그어 나갔다. 모든 길이 하나의 원본으로 통하는, 깔끔하고 이상적인 그림이었다. 솔라는 만족스러운 표정으로 그림을 바라보았다. 데이터 불일치 문제는 이제 해결된 것처럼 보였다.

루나는 솔라가 그린 그림을 조용히 들여다보았다. 그리고는 솔라가 막 완성한 중앙집중적인 구조의 한가운데, ‘마스터 메뉴 (JSON)’ 메모지를 손가락으로 가볍게 톡 쳤다.

“이 단 하나의 원본, 아주 좋은 생각이야. 그런데 솔라, 이 파일은 서울에 있는 서버에 저장되어 있어. 부산 해운대점 점주님이 아메리카노가 다 떨어졌다는 걸 방금 알았어. 점주님은 어떻게 이 파일을 수정할까?”

“어…?” 솔라는 순간 말문이 막혔다. “개발팀에 전화해서… 바꿔달라고 해야 하나?”

“100개 매장에서 1분마다 그런 요청이 들어온다면? 그리고 대구 동성로점 점주님은 라떼 가격을 500원 올리고 싶고, 제주 애월점 점주님은 우리 매장만 파는 한라봉 에이드를 새로 추가하고 싶다면? 이 모든 요청을 개발팀이 받아서 하나의 JSON 파일을 계속 고쳐야 할까?”

솔라의 표정이 복잡해졌다. 그녀가 만든 깔끔한 그림에 금이 가기 시작했다. 하나의 원본이라는 아이디어는 옳았지만, 그 원본이 ‘JSON 파일’이라는 점이 새로운 문제를 낳고 있었다. 모든 매장의 메뉴와 정책, 실시간 재고 상황을 단 하나의 파일에 담고 수동으로 관리하는 것은 불가능에 가까웠다.

“파일 하나로는 안 되겠구나. 매장마다 자기 메뉴를 직접, 실시간으로 고칠 수 있어야 해. 다른 매장에 영향을 주지 않으면서.” 솔라는 중얼거렸다.

루나는 고개를 끄덕이며 새 메모지를 꺼내 솔라 앞에 놓았다. 그리고는 마치 데이터베이스 테이블을 설계하듯 칸을 나누기 시작했다.

“네 말이 맞아. 그래서 ‘단일 원본’의 형태를 바꾼 거야. 파일이 아니라, 훨씬 더 구조화된 형태로.”

루나는 메모지에 store_menus 라고 제목을 적고 몇 개의 열을 추가했다.

store_idmenu_item_idpriceis_available
busan-01americano4500false
seoul-01americano4500true
seoul-01cafe-latte5500true
jeju-01hallabong-ade6000true

“이게 바로 PostgreSQL 데이터베이스에 만든 테이블의 모습이야. store_id로 어떤 매장의 메뉴인지 구분하고, 각 매장마다 메뉴별 가격(price)과 판매 가능 여부(is_available)를 따로 저장하는 거지. 부산점 점주님이 아메리카노를 품절 처리해도(is_availablefalse로 바꿔도), 서울점의 아메리카노에는 아무 영향이 없어.”

솔라의 눈이 커졌다. 이 표는 지난 장에서 겪었던 모든 문제를 해결하고 있었다. 데이터는 매장별로 완벽히 격리되어 있었고, 각자의 상황에 맞게 수정될 수 있었다. 더 이상 한 매장의 변경이 다른 매장의 데이터를 덮어쓸 위험도, 모든 매장의 정보를 하나의 거대한 파일에서 뒤져야 할 불편함도 없었다.

“그리고 점주님들은 이 테이블을 직접 보지 않아.” 루나가 덧붙였다. “점주님들께는 웹사이트 형태의 ‘관리 화면’이 주어져. 거기서 ‘아메리카노 품절’ 버튼을 누르면, 시스템이 알아서 busan-01 매장의 아메리카노 is_available 값을 false로 바꿔주는 거지. 이게 바로 점주가 메뉴를 직접 CRUD(생성, 조회, 수정, 삭제)한다는 의미야.”

루나는 솔라가 처음에 그렸던 그림 위에 새로운 그림을 겹쳐 그렸다.

점주 관리 화면CRUD API[PostgreSQL DB]

그리고 이전의 ‘챗봇’, ‘주문 시스템’, ‘대시보드’는 모두 이 CRUD API를 바라보도록 화살표를 새로 그렸다.

[PostgreSQL DB]CRUD API챗봇, 주문 시스템, 대시보드

솔라는 두 그림의 차이를 명확하게 깨달았다. 이전에는 모든 시스템이 하나의 파일을 복사해가는 방식이었다면, 이제는 모든 시스템이 하나의 ‘창구(API)’를 통해 단 하나의 ‘진실(DB)’을 묻고 답하는 방식이었다.

솔라는 다시 한번 회고 문서의 문장을 떠올렸다. ‘작성자는 store_menusstore_policies를 PostgreSQL에 두고 매장별 CRUD API와 점주 관리 화면을 연결했다.’ 처음에는 그저 복잡하게만 보였던 문장이, 이제는 데이터의 일관성을 지키기 위한 치밀한 설계도로 보였다. 데이터베이스와 관리 화면, API는 시스템을 복잡하게 만든 게 아니라, 운영의 복잡성을 해결하기 위한 필수적인 도구였다. 이것이 바로 ‘일관된 데이터 흐름 설계’의 핵심이었다.

“알겠어. 이제 모든 시스템이 같은 창구를 통해 이야기하니까 데이터가 어긋날 일은 없겠네. 점주님이 관리 화면에서 품절 버튼을 누르면, 데이터베이스 값이 바뀌고, 챗봇이든 주문 시스템이든 API에 물어볼 때마다 항상 최신 정보를 받게 되는 거구나.”

솔라는 명쾌하게 정리했다. 하지만 그녀의 얼굴에 새로운 궁금증이 떠올랐다.

“그런데 언니, 챗봇이 손님과 대화하려고 1초 전에 메뉴 정보를 API에서 가져갔다고 해봐. 바로 그 순간, 점주님이 품절 버튼을 눌렀어. 그럼 챗봇은 이미 낡아버린 정보를 가지고 있는 거 아냐? 챗봇이 그걸 어떻게 실시간으로 알 수 있지?“

3장: 품절 토글 하나로 만드는 진정한 ‘운영 데이터’

솔라의 질문이 남긴 미세한 균열 위로 침묵이 흘렀다. 분명 단 하나의 데이터베이스, 단 하나의 API라는 해법으로 데이터 불일치 문제는 해결된 것 같았다. 하지만 솔라가 제기한 ‘순간의 차이’는 그 완벽해 보이는 설계도 위에 미묘한 그림자를 드리웠다. 챗봇이 메뉴 정보를 조회한 직후, 점주가 품절 버튼을 누르는 그 찰나의 순간. 시스템은 과연 그 시간차를 어떻게 감당할 수 있을까.

루나는 말없이 테이블 위에 펼쳐져 있던 시스템 구성도를 옆으로 밀어두었다. 대신 깨끗한 메모지 한 장을 가져와 가운데에 수직선을 길게 그었다. 타임라인이었다. 루나는 선의 맨 위에 T-1이라고 적고, 그 아래에 T=0, 맨 아래에 T+1이라고 표시했다. 지난 장에서 솔라가 만족스럽게 바라보던 정적인 시스템 구조도가 아닌, 시간이 흐르는 동적인 세계가 펼쳐진 것이다.

루나는 T-1 지점 옆에 ‘고객, 챗봇에 메뉴 질문’이라고 적었다. 그리고 T=0 지점에는 ‘점주, 아메리카노 품절 토글 켬’이라고 썼다. 솔라가 던진 질문의 상황이 그대로 종이 위에 옮겨졌다.

“솔라, 네 질문이 바로 이 시간선 위에서 일어나고 있는 일이야. 자, 챗봇은 T-1 시점에 API를 호출해서 ‘아메리카노 주문 가능’이라는 정보를 이미 가져갔어. 아직 고객에게 답하기 전이야. 그런데 T=0에 점주가 품절 처리를 했지. 그리고 T+1에 챗봇이 고객에게 답변을 한다면, 뭐라고 말해야 할까?”

솔라는 타임라인을 뚫어지라 쳐다보았다. “당연히 ‘품절’이라고 답해야지. 하지만 챗봇은 T-1의 정보, 즉 ‘주문 가능’이라는 낡은 정보를 갖고 있잖아.” 그녀는 자신이 만든 함정에 스스로 빠진 기분이었다. API를 통해 최신 정보를 가져오게 만들었지만, 그 ‘최신’이라는 것 역시 가져오는 순간 과거가 되어버리는 운명이었다.

“맞아. 여기서 중요한 건 ‘데이터가 무엇인가’가 아니라 ‘데이터가 언제의 것인가’야. 우리 예전에 분석 수치랑 이미지가 서로 다른 순간을 보여줘서 혼란스러웠던 적 있었지? 그때도 문제는 각 시스템이 그저 최신 정보를 가져오는 게 아니라, ‘같은 순간’의 정보를 공유하지 못했던 거였어.”

루나의 말에 솔라의 머릿속에 흐릿했던 기억이 떠올랐다. 단순히 각자가 최신이면 괜찮을 거라 생각했지만, 결국 어긋난 정보 때문에 신뢰가 깨졌던 경험. 지금 이 문제도 본질은 같았다.

“그럼 챗봇이 답변하기 직전에 매번 다시 API를 호출해야 하나? 너무 비효율적인데….”

“좋은 접근이지만, 더 근본적인 변화가 필요해.” 루나는 T=0의 ‘품절 토글 켬’ 이벤트에서 챗봇, 주문 시스템, 대시보드를 향해 뻗어 나가는 화살표 세 개를 그렸다. 이전 장의 그림과 방향이 정반대였다. 이전에는 시스템들이 API를 ‘향해’ 갔다면, 이제는 어떤 ‘사건’으로부터 정보가 뿜어져 나오고 있었다.

“이게 바로 ‘운영 데이터’의 핵심이야. 정보는 어딘가에 조용히 저장되어 있다가 요청받을 때만 대답하는 수동적인 존재가 아니야. 특히 ‘품절’처럼 시스템의 행동 규칙을 바꾸는 중요한 상태 변화는, 발생한 즉시 관련된 모든 곳에 그 사실을 알려야 하는 능동적인 신호가 되어야 해.”

루나는 말을 이었다. “점주가 품절 토글을 켠다는 건, 단순히 데이터베이스의 is_available 값을 false로 바꾸는 행위가 아니야. 그건 ‘지금부터 아메리카노 주문을 받지 마라’는 운영 정책의 선포거든. 이 선포는 고객과 대화하는 챗봇, 실제 주문을 생성하는 시스템, 매장 상황판을 보는 점주 모두에게 즉시, 그리고 같은 의미로 전달되어야만 해.”

그제야 솔라는 모든 조각이 맞춰지는 것을 느꼈다. 품절 토글은 단순히 데이터베이스의 한 필드를 바꾸는 기능 추가가 아니었다. 그것은 흩어져 있던 시스템들을 하나의 ‘운영’이라는 관점 아래 동기화시키는, 살아있는 맥박과도 같은 것이었다. 한 매장에서 일어난 작은 상태 변화 하나가 관련된 모든 시스템의 행동을 일관되게 바꾸는 것. 그것이 바로 샘플 데이터나 단순 기록 데이터와 구별되는 ‘운영 데이터’의 본질이었다.

솔라는 처음 이 모든 고민을 시작하게 했던 회고 문서의 마지막 문장을 떠올렸다.

‘점주가 매장별 메뉴와 정책을 CRUD하고 품절 available 상태를 전환하면 Dashboard와 AICC가 같은 API를 읽는다.’

이제 이 문장은 솔라에게 전혀 다른 의미로 읽혔다. ‘같은 API를 읽는다’는 것은 단순히 동일한 정보 소스를 가리킨다는 기술적 사실을 넘어, 모든 시스템이 ‘품절’이라는 하나의 사건이 일으키는 파장을 함께 맞고, 그에 맞춰 일사불란하게 움직인다는 뜻이었다. 고객 응대, 주문 생성, 매장 관리라는 각기 다른 역할들이 ‘품절’이라는 하나의 진실 앞에서 완벽한 합주를 펼치는 그림이 그려졌다.

“알겠다.” 솔라가 나지막이 말했다. 그녀는 루나가 그렸던 타임라인 옆에 놓인 빈 메모지를 가져왔다. 그리고는 ‘신규 기능: 점심 시간 30분 타임 세일’이라고 적었다.

스스로에게 문제를 내듯, 그녀는 망설임 없이 데이터 흐름을 그리기 시작했다. 단순히 ‘할인율’과 ‘적용 시간’을 저장할 데이터베이스 테이블을 그리는 데 그치지 않았다. 그녀는 ‘타임 세일 시작’이라는 이벤트를 중앙에 크게 그리고, 그 이벤트가 발생했을 때 챗봇, 주문 시스템, 대시보드가 각각 어떻게 반응해야 하는지를 화살표와 함께 명시했다. ‘챗봇: 고객에게 할인 정보 선제적 안내’, ‘주문 시스템: 해당 시간 동안 할인 가격 적용’, ‘대시보드: 실시간 매출에 할인율 반영’.

그녀가 그리고 있는 것은 단순한 기능 명세가 아니었다. 실시간으로 변하는 데이터를 모든 관련 시스템이 일관되게 인지하고 행동하게 만드는 ‘운영 데이터 기준점’을 세우고 있는 것이었다. 샘플 데이터와 운영 데이터의 차이를 온몸으로 깨달은 사람만이 그릴 수 있는 설계도였다.