앱 디자인 README 「검색」 절, flutter 스킬 표 · 함정 두 줄 · CI 경로, 계약 2절
"검색 — 14단계"의 앱 클라이언트 줄과 목록 항목의 searchHighlight 칸이 늘 있다는
줄, 두 표본 표 머리말(앱 시험이 백엔드 표를 읽는다)을 적는다.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
14단계 3차다. 같은 대화(답장 · 전달로 이어진 메일)를 읽기 화면에 보이고, 목록을 대화로
묶어 볼 수 있게 한다. 색인이 준비된 직원은 우리 대화 키로, 채우는 중인 직원은 엔진의
스레드로 묶는다(응답의 basis — 첫 쪽에서 정하고 커서가 잇는다).
- 대화 키: 색인 쓰기 때 넣은 메일의 부모 · 자식(References · In-Reply-To) 가운데 말머리
(회신 · 답장 · 전달 포함)를 뗀 제목이 같은 것을 가장 작은 키로 합친다. 늦게 온 원본도
잇고, 다시 넣어도 키는 풀리지 않는다. 색인 판 4
- GET /api/messages/{id}/conversation — 오래된 차례, 휴지통 · 스팸메일함에만 든 메일과
발송 중인 내부 초안은 빼고 지금 메일은 늘 넣는다
- GET /api/conversations?mailboxId= — 그 메일함의 대화 한 줄씩(참여자 10명 · 통 수 ·
messageIds 최신 500 · pendingSendIds). 직원 주소 btree 로 먼저 좁힌다(10명 흉내 큰 메일함
첫 쪽 81ms)
- 설정 groupByConversation(처음 끔) — 서버는 저장만 하고 묶을지는 화면이 정한다
- 마이그레이션 AddMailSearchSender — 참여자는 따로 둔 첫 보낸 사람 칸에서만 읽는다
리뷰 반영: 엔진 방식에 개수 · 시간 상한(같은 대화 한 요청 1,000통 · 묶은 목록 스레드마다
500 · 한 요청 2,000 · 15초, 요약 속성은 내보낼 메일만), Mailbox/get 장애는 503,
꾸민 커서는 엔진 · DB 보다 먼저 400, 같은 대화 한 문장, 이름 안의 <주소>로 참여자를
꾸미지 못하게, 로그 추적 ID. 검색 표본 표 머리 주석(frontend 로 옮겼다)도 함께 고친다.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
14단계 2차다. 색인이 준비된 직원은 서버의 자체 색인으로, 채우는 중인 직원은 지금처럼
엔진으로 찾는다(방식은 첫 쪽에서 정하고 커서가 잇는다).
- 조건 from · to(참조 포함) · subject · body · after · before · hasAttachment — q 와 합친 낱말 8개까지
- 부분 일치(대소문자 · 전각 · NFC), 구절은 줄바꿈 · NBSP 로 갈린 글에도, 한 글자 낱말뿐이면
제목 · 사람 · 첨부 이름에서만(2026-11-05 사용자 확정)
- 찾기: GIN(lower(user_email), grams) 후보 → 기본 키로 확인, 한 요청 확인 3,000줄 상한 +
이어 찾기 커서, total 은 첫 쪽에서 1,000 까지(못 세면 null) — 1만 통에서 가장 무거운 검색 0.8초
- 목록 항목 searchHighlight(제목 · 맞은 말 둘레 미리 보기), 읽기 화면은 정화한 HTML 의 글
조각에 <mark> · 제목 · 첨부 이름 · 글 본문 위치
- 마이그레이션 SearchIndexUserGrams(btree_gin), 색인 판 2(공백 접기) — 판이 바뀌면 그 직원의
색인을 지우고 밤에 다시 채운다
리뷰 반영: 옛 판 줄 지우기, 꾸민 커서 · NUL 은 VAL_001 · U+FFFD, 한 글자 길 한 트랜잭션,
다른 직원 줄 격리 e2e.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
14단계 자체 검색 색인의 1차다. 검색 API 를 색인으로 바꾸는 일과 대화 키 잇기는 다음 차례다.
- 마이그레이션 AddMailSearchIndex 하나: 표 mail_search_messages · mail_search_states 와
mail_settings.group_by_conversation 열(3차의 대화로 묶어 보기 설정 — 같은 배포에서 한 번에 만든다)
- 접기 · 글자 두 개 묶음 · 맞은 곳 · 말머리 떼기 순수 함수와 표본 표(나중에 frontend 와 짝)
- 엔진 포트 MAIL_INDEX_SOURCE + JMAP 어댑터: Email/changes 맞추기(실시간 · 5분 · 검색 전,
서버 전체 동시 4), 밤 창(KST 22:00~02:45 · 05:00~07:30) 지난 메일 채우기, 전체 대조
- GET /api/search-index, 로그인 · 새 비밀번호 때 상태 줄, 퇴사 마무리에 표 둘
- 로그 가림에 조건 검색 이름 넷, 개발 환경 DB 쿼리 로그 끔(메일 글이 DB 에 든다)
리뷰 반영: 시간 끝은 실패가 아니라 이어 하기, 본문 읽기 묶음의 메일 크기 상한(32MiB ·
16MiB 넘으면 혼자), 겹친 id 빼기, 사내 도메인 주소만 색인(개발 서버 cron 이 e2e 줄을 건드리지 않게).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>