MyStock - DB를 구성하다

MyStock을 처음 만들 때는 DB를 넣을 생각이 없었다. 한국투자증권에 계좌가 여러 개 있는데, 각 계좌를 하나씩 들어가 보지 않고 보유 종목을 한 화면에서 빠르게 훑어보고 싶었다. 시작은 정말 그 정도였다.

 

계좌 정보의 원본은 어차피 한국투자증권에 있기 때문에 내가 따로 계좌 정보를 저장하고 관리할 이유가 없다고 생각했다. 프로그램을 실행하면 REST API로 조회하고, 메모리에 올리고, 화면에 보여주면 된다. 종료하면 버리고 다음에 다시 조회하면 된다. 그 당시 요구사항만 놓고 보면 꽤 합리적인 선택이었다.

저장하지 않는 설계

처음부터 로컬 데이터를 전혀 사용하지 않았던 것은 아니다. 필요한 데이터는 JSON 파일로 간단하게 구성했고, 한때는 Google Drive 동기화 기능도 붙여 사용했다. 그런데 이전 리비전으로 전환한 뒤 몇 가지 기능을 확인하는 과정에서 Google Drive 동기화가 깨졌다. 그때 생각은 단순했다.

나 혼자 쓸 건데 뭔 로컬 파일이고 동기화야.
복잡도만 올라가잖아.
그냥 REST API로 조회해서 보면 되지.

JSON 파일을 따로 관리하고, 로컬과 Google Drive의 상태를 맞추고, 어느 쪽이 최신인지 판단하고, 동기화 실패까지 처리하는 구조는 내가 원했던 프로그램에 비해 과하다고 봤다. 그래서 Google Drive 동기화와 그에 딸린 로컬 데이터 관리 기능을 걷어냈다.

 

당시 MyStock의 데이터 흐름은 다시 단순해졌다.

KIS REST API
    ↓
메모리
    ↓
화면

KIS가 원본이고 MyStock은 그걸 조회해서 보여주는 프로그램. 여기까지는 DB가 없어도 별문제가 없었다.

커져버린 MyStock

문제는 기능이 계속 늘었다는 것이다. AI와 같이 개발해보니 생각했던 것보다 구현 속도가 나왔다. “이것도 되나?” 싶어서 하나 붙여보고, 되면 또 하나 붙였다. 실시간 시세가 보고 싶어서 WebSocket을 붙였다. 종목을 좀 더 자세히 보려니 과거 일봉이 필요했다. 3년치 데이터를 조회하기 시작했고, 수급을 붙이고, 시장지표를 붙이고, 투자분석 화면도 점점 커졌다.

 

처음에는 여러 계좌의 보유 종목을 한 화면에서 훑어보는 프로그램이었는데, 어느 순간 현재 상태만 보여주는 프로그램이 아니라 과거 데이터까지 같이 보는 분석 프로그램이 되어가고 있었다. 그래도 DB는 없었다.

REST-only의 한계

기능이 늘면서 REST API 중심 구조의 단점이 눈에 들어오기 시작했다. 특히 과거 데이터가 필요한 탭에 들어갈 때마다 3년치 데이터를 다시 받아오는 게 점점 답답해졌다. 한 번 받아본 데이터를 다음 화면에서도 다시 받고, 프로그램을 다시 실행하면 또 받고 있었다. 처음에는 “어차피 API로 받으면 되지”라고 생각했던 구조가 데이터의 양이 커지면서 UX를 잡아먹기 시작했다. 그래도 이것만이었다면 캐시를 조금 더 잘 쓰는 정도로 끝냈을 수도 있다.

 

DB를 다시 생각하게 만든 결정적인 이유는 따로 있었다.

결정타는 JSON

MyStock의 기본 기능이 거의 완성되어 가던 시점에 다음에 하고 싶은 일이 생겼다. 내 실제 계좌 정보와 시장 데이터를 JSON으로 만들어 ChatGPT에 전달하고, 오늘 시장 상황을 기준으로 내가 어떤 종목과 어떤 부분을 확인해야 하는지 분석하고 싶었다.

 

여기서 기존 구조를 다시 보게 됐다. 화면 하나를 그릴 때는 필요한 API를 호출해서 메모리에 넣으면 된다. 그런데 JSON 하나에 계좌, 보유 종목, 과거 시세, 수급, 시장지표 같은 데이터를 같이 담으려면 이야기가 달라진다. DB가 없다면 JSON을 만들 때마다 필요한 REST API를 다시 호출하고, 데이터를 다시 모으고, 다시 정규화해서 하나의 상태로 조립해야 한다. 그 순간 생각이 바뀌었다.

잠깐.
DB가 없으면 JSON 만들 때마다 이걸 다시 다 받아야 하잖아?

복잡도를 줄이려고 로컬 데이터와 동기화를 걷어냈는데, 기능이 확장되고 나니 오히려 DB가 없어서 더 복잡해지고 있었다. 처음 설계가 틀렸다고 생각하지는 않는다. 처음 요구사항은 “여러 계좌의 보유 종목을 한 화면에서 빠르게 본다”였고, 그 요구에는 REST API와 메모리만으로 충분했다.

 

MyStock이 단순한 현재 상태 조회 프로그램에서, 데이터를 누적해서 분석하고 외부 분석에도 전달하는 프로그램으로 바뀌고 있었다.

다시 필요해진 저장 구조

이번에는 예전에 사용했던 JSON 파일 몇 개를 다시 만드는 정도로 끝낼 수 있는 문제가 아니었다. 여러 화면에서 반복해서 사용하는 과거 데이터가 있었고, 나중에는 같은 데이터를 JSON Export에서도 꺼내 써야 했다. 화면마다 필요한 데이터를 따로 저장하는 방식보다는 한 번 받아 검증한 데이터를 공통으로 누적하고, UI와 JSON Export가 같은 데이터를 사용할 수 있는 기반이 필요했다.

 

그래서 DB를 단순한 화면 캐시가 아니라 MyStock이 계속 재사용할 데이터를 보존하는 저장소로 두는 방향을 잡았다. 화면에서 다시 계산할 수 있는 값까지 모두 저장하기보다, 여러 기능에서 공통으로 사용할 데이터를 중심으로 누적하는 구조다. 또 새로운 API 조회가 실패했다고 이미 정상적으로 받아둔 데이터까지 사용할 수 없게 만들고 싶지는 않았다. 새 데이터를 가져오는 과정과 기존에 저장된 데이터를 분리하고, 갱신에 실패하더라도 마지막으로 정상 저장된 데이터는 계속 사용할 수 있도록 했다.

 

이 방향을 기준으로 DB/Data Architecture를 검토했고, 현재 MyStock의 canonical store는 SQLite 기반으로 구현하기 시작했다.

복잡도를 더해 단순해지다

처음에는 로컬 데이터나 DB가 들어오면 복잡도가 늘어난다고 생각했다. 실제로 저장 구조와 갱신 상태처럼 새로 관리해야 할 것은 생겼다.

하지만 프로그램 전체 관점에서는 역할이 오히려 더 분명해졌다.

Provider
  └─ 외부 데이터를 가져온다

Canonical DB
  └─ 검증한 데이터를 누적해서 보존한다

UI
  └─ 필요한 데이터를 읽어서 보여준다

JSON Export
  └─ 같은 데이터를 분석 가능한 형태로 꺼낸다

화면을 열 때마다 API에서 모든 것을 다시 조립하는 구조보다, 한 번 검증해서 저장한 데이터를 여러 기능이 같이 사용하는 쪽이 커져버린 MyStock에는 더 단순했다.

 

처음에는 “KIS가 원본인데 내가 왜 저장해?”라고 생각했고, KIS가 원본이라는 사실은 변하지 않는다. 다만 MyStock이 분석에 필요한 데이터를 매번 처음부터 다시 조립하지 않으려면, 한 번 조회하고 검증한 데이터를 내 프로그램에서도 다시 사용할 수 있어야 했다.

진짜 문제의 시작

DB에 데이터를 쌓을 수 있는 기반이 만들어졌을 때는 큰 산을 넘었다고 생각했다. 이제 남은 건 기존 화면이 API와 메모리에만 의존하지 않고, 저장된 데이터를 먼저 활용하도록 연결하는 일이라고 생각했다.

 

적어도 그때는 그게 제일 쉬운 일인 줄 알았다. 그런데 DB를 실제 UX에 연결하기 시작하면서 전혀 다른 문제가 터지기 시작했다. 오늘 데이터는 언제 확정된 데이터로 봐야 하는지, 장중에 받은 일봉은 어떻게 해야 하는지, 주말과 휴장일의 데이터를 어떻게 판단할지. 문제 하나가 생길 때마다 그 문제만 해결하는 조건을 붙이다 보니 생각보다 훨씬 먼 길을 돌아가게 됐다.