마음기록 앱 오프라인 저장 큐 설계, AI 분석은 뒤로 미루기
네트워크가 끊겨도 마음기록이 사라지지 않게 로컬 우선 큐를 두고, AI 분석은 동기화 뒤 별도 워크플로우로 돌리는 설계를 정리했어요.
마음을 적는 앱에서 글이 날아가는 건 다른 앱의 데이터 유실과 무게가 달라요. 지하철에서 한 줄 적다가 터널에 들어가면 그 순간의 문장은 다시 못 써요. 그래서 플랜티를 설계하면서 첫 원칙을 이렇게 잡았어요. 글은 먼저 기기에 남고, 서버와 AI는 그다음이에요.
저장과 분석을 한 요청에 묶지 않기
처음엔 저장 버튼을 누르면 서버 전송, 모델 분석, 결과 표시까지 한 호출로 끝내려 했어요. 직접 만들어 보니 이게 함정이었어요. 모델 응답이 느리면 저장까지 같이 기다리고, 응답이 실패하면 저장도 실패한 것처럼 보여요. 사용자 입장에선 글이 사라진 것과 구분이 안 돼요.
그래서 기록 하나를 두 단계로 쪼갰어요. 저장 단계는 로컬 큐가 책임지고, 분석 단계는 동기화가 끝난 항목만 골라 AI 워크플로우로 넘겨요.
상태는 4개로 충분했어요. draft, queued, synced, analyzed. 글 본문은 draft 시점에 이미 기기에 써져 있고, 나머지 상태는 메타데이터만 바뀌어요.
재시도는 코드가, 판단은 모델이
큐 워커는 네트워크가 돌아오면 queued 항목을 순서대로 보내요. 실패하면 1초, 2초, 4초, 8초, 16초로 간격을 늘려 다시 시도해요. 5회를 다 써도 31초면 끝나니 사용자가 체감할 정도로 길진 않아요. 그래도 안 되면 다시 queued로 돌려놓고 다음 네트워크 복귀 때 이어가요.
여기서 AI는 아무 일도 안 해요. 재시도, 순서, 중복 판단은 전부 결정론 코드 몫이에요. 모델이 "이 글 저장할까요?"를 결정하게 두면 안 돼요. 모델은 synced 상태가 된 글을 받아 감정 요약 같은 분석만 해요.
같은 글이 두 번 분석되지 않게
테스트해 보면 가장 자주 터지는 게 중복이에요. 동기화 응답이 유실돼서 큐가 같은 글을 다시 보내면, 서버는 새 글로 착각하고 모델도 두 번 돌아요. 그래서 기록마다 클라이언트에서 만든 UUID 하나를 idempotency key로 붙였어요. 서버는 같은 키가 오면 이미 있는 결과를 돌려주고 모델 호출은 건너뛰어요.
프롬프트도 이 키를 중심으로 짰어요. 입력엔 기록 ID와 본문만 넣고, 출력엔 그 ID를 그대로 돌려달라고 요구해요. 분석 결과가 돌아왔을 때 어느 글에 붙일지 코드가 헷갈리지 않게요. 모델 ID는 설정값으로 빼두고 코드엔 플레이스홀더만 남겼어요.
- 본문은 draft에 즉시 저장
- 재시도는 코드
- 중복은 UUID 키
- AI 결과는 별도 필드
AI 결과는 글 본문을 덮어쓰지 않고 별도 필드에 저장해요. 모델 출력이 이상하면 그 필드만 비우고 다시 돌리면 되고, 사용자가 쓴 원문은 손대지 않아요.
비행기 모드로 돌려 보니
비행기 모드를 켜고 10개를 연속으로 적은 뒤 네트워크를 다시 켰어요. 돌려 보니 10개 모두 synced를 거쳐 analyzed까지 갔고, 서버에 중복으로 생긴 글은 0개였어요. 적용해 봤더니 흐름 자체는 3단계로 정리돼요. 기기에 쓰고, 큐가 올리고, 모델이 읽어요. 순서를 바꾸지 않는 게 설계의 전부예요.
마음을 적는 도구라면 모델이 똑똑한 것보다 글이 안 사라지는 게 먼저예요. 플랜티는 아직 개발 중이지만, 이 순서만큼은 바꾸지 않을 생각이에요.
관련 글
댓글
아직 댓글이 없어요. 첫 댓글을 남겨주세요.