본문으로 건너뛰기

데이터 아키텍처 — 소유권 · DB 분리 · 흐름

갱신일 2026-07-26 · 기준: server@bf192cc · ai@60ee59a · web@87e0834 누가 원본을 갖고 데이터가 어느 방향으로 흐르는가만 소유한다. 스키마 필드는 ../contracts/, 요청 순서는 ../interfaces/server-ai.md의 §1 분석(202 + callback)·§2 채팅(SSE passthrough)이 갖는다.

확인한 사실

원본은 하나, 나머지는 파생

소유자갖는 것성격
heymoa-server유저·워크스페이스·프로젝트·노트, 전사 세그먼트, 분석 잡 상태와 결과, 채팅 히스토리, 도구 승인 상태, OAuth refresh token, 알림원본. 지우면 복구할 수 없다
heymoa-ai분석 결과 캐시, 전사 임베딩, LangGraph checkpoint파생. 통째로 날아가도 원본으로 다시 만든다
heymoa-web없음영속 저장소를 갖지 않는다. 화면 상태를 무엇이 어떻게 들고 있는지는 ../web/README.md가 갖는다

같은 Postgres를 쓰더라도 database를 나누고, cross-database 쿼리를 금지한다. 데이터는 API payload로만 오간다. 그래서 어느 쪽도 상대 테이블을 직접 읽지 않는다.

전사는 흐름마다 반대 방향으로 간다

흐름방식
분석push — server가 세그먼트 전체를 요청 payload에 실어 보낸다회의가 끝난 확정 전사라 한 번에 넘기면 된다
채팅pull — ai가 매 턴 내부 조회 API로 당겨간다진행 중 회의라 매 턴 최신이 필요하고, payload 비대를 계약이 막았다

pull 경로는 두 개다 — GET /internal/v1/notes/{noteId}/contextGET /internal/v1/workspaces/{workspaceId}/notes. 둘 다 X-Internal-Token을 요구하고, ai는 네트워크·상태코드·JSON·스키마 실패를 전부 CONTEXT_UNAVAILABLE 하나로 좁힌다(context_client.py).

파생 데이터 셋의 성격이 서로 다르다

테이블지워도 되나만드는 주체
analysis_resultsanalysis_id (server가 발급한 13자 TSID). 상태 컬럼 없음, 저장은 upsert예 — 다시 분석하면 된다Alembic 0002
transcript_embeddings(note_id, chunk_index) 유니크. 재적재는 note 단위 DELETE 후 INSERT예 — 재분석하면 다시 쌓인다Alembic 0003 (HNSW 인덱스를 비었을 때 미리 만든다)
checkpoint*thread_id = chatId지우면 그 대화의 멀티턴 맥락이 사라진다. server의 표시용 히스토리는 남는다LangGraph — Alembic이 배제하고(migrations/env.pyinclude_object), 지금은 기동 시 saver.setup()이 만든다

분석 그래프에는 checkpoint가 없다. 한 번 돌고 끝나는 실행이라 이어 붙일 상태가 없다.

대화 상태는 소유가 갈라져 있다

무엇어디용도
루프용 full state (messages + profile_key)ai의 checkpoint다음 턴에 모델이 볼 것
표시·히스토리 사본server의 agent_chat_message화면과 감사. SSE 중계 중 tee한 결과다

ai에는 히스토리 조회 API가 없다. 그리고 notes.read 도구를 탄 턴은 노트 전사 전문이 ToolMessage로 checkpoint에 남는다 — 매 턴 조립하는 컨텍스트는 노드 로컬 변수라 저장되지 않지만, 도구 결과는 messages에 들어가므로 예외다.

비밀정보의 자리

무엇어디에 있나어디에 없나
OAuth refresh tokenserver의 tool_connection(암호화)ai는 갖지 않는다
단기 access token (toolCredentials)요청 스코프 메모리뿐DB·파일·로그 금지. ai는 LangGraph runtime context로만 넘긴다 — state는 물론 configurable도 안 되는데, 문자열이어도 checkpoint metadata로 복사되기 때문이다
DB 비밀번호RDS 관리형 Secret (EC2 IAM Role로 읽음)저장소에 없다

설계 전제

  • heymoa_ai가 통째로 사라져도 heymoa 데이터로 재구성할 수 있어야 한다. 이 한 문장이 cross-database 금지와 "파생만 보관"을 강제한다. 재구성이 불가능한 것을 ai에 두기 시작하면 백업·복구 책임이 둘로 늘어난다.
  • 잡의 진실은 server가 갖는다. ai의 analysis_results에는 상태 컬럼이 없고 "끝난 것"만 들어온다. 진행 상태는 server의 analysis_job이 본다.
  • 멱등성 키는 server가 발급한다. ai는 id를 만들지 않고 analysisId를 그대로 캐시 키로 쓴다. 그래서 server의 재시도가 중복 분석이 되지 않는다.
  • 잡 유실을 감수한다. ai의 큐가 인메모리라 프로세스가 죽으면 접수분이 사라지지만, 재시도·워치독이 server 소유이므로 ai에 워커를 두면 같은 일을 두 번 하게 된다. 유실이 실제로 관측되면 그때 별도 워커를 도입한다.
  • 임베딩 실패는 분석을 죽이지 않는다. 요약이 본체고 임베딩은 부산물이라 실패를 노드에서 삼킨다. 대신 실패한 노트는 재분석 전까지 검색에 안 잡힌다 — 재적재 경로가 아직 없다.

미확인

무엇지금 아는 것어디를 봐야 하나
운영에서 두 database가 정말 한 인스턴스인지설계 전제는 "같은 인스턴스, 다른 database"이고, Terraform이 만드는 RDS의 db_nameheymoa 하나다aws-architecture.md의 미확인 표 — heymoa_ai database와 CREATE EXTENSION vector를 누가 만드는지
임베딩 재적재 경로지금은 없다. 재분석이 유일한 복구 수단이다빠진 노트가 실제로 생기면 이슈로 판다
전사 길이 상한정하지 않았다. 실제 전사가 몇 토큰인지 측정한 적이 없다컨텍스트 초과가 실제로 나거나 길이를 측정한 뒤
대화 프롬프트 누적도구 결과를 자르지 않아 대화가 길어질수록 커진다Langfuse의 턴별 토큰 계측
파생 데이터 보존 기간삭제 정책이 없다노트 삭제 시 ai 쪽 임베딩·checkpoint를 지우는 경로가 필요한지 판단