feat: 문서 구조 변경 및 tech-visual 스킬 추가
This commit is contained in:
+86
@@ -0,0 +1,86 @@
|
||||
---
|
||||
id: e1e0e2a0-c6b6-45bf-be42-f697ba5e2fff
|
||||
kind: QUESTION
|
||||
slug: cardinality-estimate-after-analyze
|
||||
title: ANALYZE 이후 Cardinality Estimate는 어떻게 달라지는가
|
||||
topic: JPA 피드 조회 성능
|
||||
project: Liner N + 1문제
|
||||
status: 게시 전
|
||||
studio: "https://hyeonworks.com/studio/documents/e1e0e2a0-c6b6-45bf-be42-f697ba5e2fff/edit"
|
||||
questionStatus: OPEN
|
||||
---
|
||||
|
||||
# ANALYZE 이후 Cardinality Estimate는 어떻게 달라지는가
|
||||
|
||||
반복되는 하이라이트 조회의 실행계획에서 추정 행수는 1이고 실제 행수는 500이었다. 대량 데이터를 넣은 직후 통계를 갱신하지 않아 편중을 담지 못했다는 가설을 세웠지만 아직 검증하지 않았다.
|
||||
|
||||
## 관계
|
||||
|
||||
- **PostgreSQL Query Plan 측정 기준**
|
||||
추정과 실제의 차이를 기록하는 기준이다.
|
||||
- **Fetch 타입이 아니라 조회 방식이 만든 ToOne N+1**
|
||||
이 실행계획이 나온 기록이다.
|
||||
|
||||
## 사실
|
||||
|
||||
- 대량 시드 직후 측정한 계획에서 추정 rows는 1, 실제 rows는 500이었다. 500배 차이다.
|
||||
- 이 계획은 feed_item_id 조건을 인덱스로 처리했고 실행시간은 0.173 ms였다.
|
||||
- 읽은 블록은 모두 캐시에서 왔다. 디스크 읽기는 0이었다.
|
||||
- 하이라이트 개수는 순위 기반 편중 분포라 feed_item_id별 자식 수가 크게 다르다. 상한 500, 하한 1이다.
|
||||
- fetch join 조인 계획에서도 추정 4,202와 실제 1,961의 차이가 있었다.
|
||||
- 시드 직후 통계 갱신 명령을 실행하지 않았다.
|
||||
|
||||
## 가정
|
||||
|
||||
- 통계를 갱신하면 feed_item_id별 분포가 반영되어 추정이 실제에 가까워진다.
|
||||
- 추정이 달라지면 플래너가 다른 계획을 고를 수 있다.
|
||||
- 편중이 큰 컬럼은 기본 통계 대상 수로 부족할 수 있다.
|
||||
|
||||
## 미지수
|
||||
|
||||
- 통계를 갱신한 뒤 추정 행수가 실제에 얼마나 가까워지는가.
|
||||
- 추정이 바뀌면 스캔 방식이 바뀌는가. 인덱스에서 순차 스캔으로, 또는 그 반대로 뒤집히는가.
|
||||
- 편중이 큰 컬럼에 통계 대상 수를 늘리면 추정이 더 좋아지는가.
|
||||
- 실행시간과 읽은 블록 수가 달라지는가.
|
||||
- 이 차이가 지금까지의 결론을 바꾸는가. 왕복 수와 전송 행수에 관한 판단은 통계와 무관하다.
|
||||
- 운영에서 대량 적재 후 통계 갱신을 절차에 넣을 것인가.
|
||||
|
||||
## 제약
|
||||
|
||||
- 통계 갱신 전후를 비교하려면 같은 데이터에서 연속으로 재야 한다. 캐시 상태가 섞이면 비교가 흐려진다.
|
||||
- 지금까지 기록한 실행계획은 모두 갱신 전 값이다. 갱신 후 값과 섞어 읽지 않도록 표기를 구분해야 한다.
|
||||
- 실행하기 전에는 수치를 채우지 않는다.
|
||||
|
||||
## 선택지
|
||||
|
||||
### 1. 통계를 갱신하고 전후를 비교한다
|
||||
|
||||
같은 데이터에서 갱신 전후 계획을 나란히 기록한다. 추정 행수, 스캔 방식, 읽은 블록, 실행시간을 대조한다.
|
||||
|
||||
측정이 한 번 더 필요하지만 가설을 닫을 수 있다.
|
||||
|
||||
### 2. 통계 대상 수까지 조절해 본다
|
||||
|
||||
편중이 큰 컬럼의 통계 대상 수를 늘린 뒤 다시 잰다. 기본값으로 부족한지 확인한다.
|
||||
|
||||
변수가 하나 더 늘어 비교가 복잡해진다.
|
||||
|
||||
### 3. 갱신 전 값만 두고 넘어간다
|
||||
|
||||
왕복 수와 전송 행수에 관한 결론은 통계와 무관하다. 추정 차이를 한계로만 적고 진행한다.
|
||||
|
||||
플래너가 다른 계획을 고를 가능성을 확인하지 못한 채 남는다.
|
||||
|
||||
## 다음 검증
|
||||
|
||||
1. 대량 시드 직후 현재 계획을 다시 기록한다. 갱신 전 값임을 명시한다.
|
||||
|
||||
2. 통계를 갱신한다.
|
||||
|
||||
3. 같은 쿼리를 같은 실행 안에서 다시 EXPLAIN한다. 추정 행수, 스캔 방식, 읽은 블록, 실행시간을 기록한다.
|
||||
|
||||
4. 두 계획을 나란히 두고 무엇이 달라졌는지 적는다.
|
||||
|
||||
5. 계획이 바뀌었다면 지금까지의 결론 중 영향을 받는 항목이 있는지 확인한다.
|
||||
|
||||
6. 운영 절차에 대량 적재 후 통계 갱신을 넣을지 판단한다.
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
---
|
||||
id: 6cbe963f-86f8-4df6-be5a-900712970d01
|
||||
kind: QUESTION
|
||||
slug: concurrency-stability
|
||||
title: 실제 동시 트래픽에서도 이 구조가 안정적인가
|
||||
topic: JPA 피드 조회 성능
|
||||
project: Liner N + 1문제
|
||||
status: 게시 전
|
||||
studio: "https://hyeonworks.com/studio/documents/6cbe963f-86f8-4df6-be5a-900712970d01/edit"
|
||||
questionStatus: OPEN
|
||||
---
|
||||
|
||||
# 실제 동시 트래픽에서도 이 구조가 안정적인가
|
||||
|
||||
지금까지의 측정은 단일 스레드 퍼시스턴스 통합 테스트에서 조회 횟수의 증가 형태를 확인한 것이다. 처리량, 커넥션 풀 안정성, 동시성은 이 측정의 범위 밖이라 조회 구조가 실제 부하를 견디는지 아직 모른다.
|
||||
|
||||
## 관계
|
||||
|
||||
- **PostgreSQL Query Plan 측정 기준**
|
||||
측정 범위와 도구 선택을 정한 기준이다.
|
||||
- **feed_visible을 Production CQRS로 승격할 것인가**
|
||||
부하 결과가 필요한 다른 판단이다.
|
||||
|
||||
## 사실
|
||||
|
||||
- 측정은 단일 스레드에서 이미 열린 테스트 트랜잭션 안의 어댑터 호출만 쟀다. HTTP 종단을 거치지 않았다.
|
||||
- 지연 값은 warm cache 상태의 로컬 비교값이다. 표본은 7회 중 앞 2회를 버린 5개다.
|
||||
- 표본이 적어 백분위수 대신 중앙값과 최댓값으로 기록했다.
|
||||
- 왕복 수는 2,022개에서 23개로, 엔티티 로드는 1,569개에서 0개로, 자식 전송은 1,509행에서 최대 60행으로 줄었다.
|
||||
- 깊은 페이지 조회는 2,000행 대신 20행을 읽었다.
|
||||
- 문서 첫머리에 고트래픽 처리량, 커넥션 풀 안정성, 동시성이 범위 밖임을 명시했다.
|
||||
|
||||
## 가정
|
||||
|
||||
- 왕복 수가 줄면 같은 트래픽에서 DB 부하도 줄어든다.
|
||||
- 요청당 왕복이 처리량에 곱해지므로 왕복 감소는 처리량 한계를 올린다.
|
||||
- 단일 스레드에서 확인한 조회 형태는 동시 실행에서도 유지된다.
|
||||
|
||||
## 미지수
|
||||
|
||||
- 목표 처리량에서 커넥션 풀이 포화되는가. 풀 크기와 대기 시간은 어떻게 되는가.
|
||||
- 동시 실행에서 지연 분포가 어떻게 되는가. 꼬리 지연이 어디까지 늘어나는가.
|
||||
- 배치 크기 설정이 동시 실행에서 어떻게 작동하는가. 세션마다 독립인가.
|
||||
- 깊은 페이지 요청이 섞이면 전체 지연에 어떤 영향을 주는가.
|
||||
- 캐시가 차갑거나 통계가 갱신되지 않은 상태에서 계획이 달라지는가.
|
||||
- 어느 지표를 운영 알람 기준으로 삼을 것인가.
|
||||
|
||||
## 제약
|
||||
|
||||
- 단일 스레드 값으로는 동시성 질문에 답할 수 없다. 도구를 바꿔야 한다.
|
||||
- 안정적인 꼬리 지연을 말하려면 워밍업 후 반복 횟수를 크게 늘린 독립 세트가 여러 개 필요하다.
|
||||
- 부하 테스트 환경이 운영과 다르면 결과를 그대로 옮길 수 없다. 데이터 규모와 하드웨어를 맞춰야 한다.
|
||||
- 로컬에서 확인한 것을 운영에서 확인한 것으로 승격하지 않는다.
|
||||
|
||||
## 선택지
|
||||
|
||||
### 1. 부하 테스트와 APM으로 확인한다
|
||||
|
||||
목표 처리량을 정하고 종단 지연, 처리량, 커넥션 풀 지표를 함께 잰다. 조회 구조의 개선이 부하에서도 나타나는지 본다.
|
||||
|
||||
환경 구성과 데이터 준비에 시간이 든다.
|
||||
|
||||
### 2. 반복 횟수를 늘린 지연 측정부터 한다
|
||||
|
||||
같은 단일 스레드 조건에서 표본을 크게 늘려 꼬리 지연을 먼저 안정화한다. 동시성은 아직 다루지 않는다.
|
||||
|
||||
동시성 질문에는 여전히 답하지 못한다.
|
||||
|
||||
### 3. 현재 범위를 명시하고 운영 판단은 미룬다
|
||||
|
||||
조회 형태 개선까지만 주장하고 처리량은 다루지 않는다. 필요해질 때 부하 테스트를 연다.
|
||||
|
||||
운영에 올린 뒤 문제를 발견할 위험이 남는다.
|
||||
|
||||
## 다음 검증
|
||||
|
||||
1. 목표 처리량과 허용 지연을 먼저 정한다. 기준이 없으면 결과를 판정할 수 없다.
|
||||
|
||||
2. 운영과 비슷한 데이터 규모를 준비한다. 편중 분포를 유지한다.
|
||||
|
||||
3. 부하 테스트로 종단 지연과 처리량을 측정한다. 커넥션 풀 사용률과 대기 시간을 함께 본다.
|
||||
|
||||
4. 개선 전후 구조를 같은 조건에서 비교한다. 왕복 감소가 처리량으로 이어지는지 확인한다.
|
||||
|
||||
5. 깊은 페이지와 얕은 페이지 요청을 섞어 지연 분포를 본다.
|
||||
|
||||
6. 운영 알람으로 쓸 지표를 정한다.
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
---
|
||||
id: 5159c415-232d-424a-970a-b0db52746767
|
||||
kind: QUESTION
|
||||
slug: isolate-round-trip-and-row-volume
|
||||
title: Round Trip과 Row Volume을 독립 측정할 것인가
|
||||
topic: JPA 피드 조회 성능
|
||||
project: Liner N + 1문제
|
||||
status: 게시 전
|
||||
studio: "https://hyeonworks.com/studio/documents/5159c415-232d-424a-970a-b0db52746767/edit"
|
||||
questionStatus: OPEN
|
||||
---
|
||||
|
||||
# Round Trip과 Row Volume을 독립 측정할 것인가
|
||||
|
||||
현재 데이터셋은 N을 키우면 반환 부모 수, 자식 총 행수, 엔티티 생성량, DB 왕복이 동시에 늘어난다. 지연이 늘어난 원인을 어느 하나에 돌릴 수 없다. 변수를 하나씩 격리한 데이터셋을 만들지 결정하지 않았다.
|
||||
|
||||
## 관계
|
||||
|
||||
- **JPA N+1 정량 진단 기준**
|
||||
왕복과 행수를 다른 축으로 세는 기준이다.
|
||||
- **Fetch 타입이 아니라 조회 방식이 만든 ToOne N+1**
|
||||
왕복 수와 한 번에 읽는 행 수를 같은 조회에서 확인한 기록이다.
|
||||
|
||||
## 사실
|
||||
|
||||
- 하이라이트 개수는 순위 기반 편중 분포로 생성된다. 상한 500, 하한 1이다.
|
||||
- 하이라이트 총량은 N에 정비례하지 않는다. N=10에서 1,285개인데 N=1,000에서 2,917개다.
|
||||
- 조회 수는 하이라이트 총량이 아니라 부모 수 N에 정비례한다.
|
||||
- N을 키우면 반환 부모 수, 자식 총 행수, 엔티티 생성량, 왕복이 함께 늘어난다.
|
||||
- 지연은 단일 스레드에서 7회 반복하고 앞 2회를 버린 뒤 5개 표본의 중앙값과 최댓값으로 기록했다.
|
||||
- 격리 데이터셋 세 종류를 계획했지만 아직 실행하지 않았다.
|
||||
|
||||
## 가정
|
||||
|
||||
- 왕복 수와 전송 행수는 지연에 서로 다른 방식으로 기여한다.
|
||||
- 부모 수만 바꾸고 자식 수를 고정하면 왕복의 기여를 분리할 수 있다.
|
||||
- 부모 수를 고정하고 자식 수만 바꾸면 과조회의 기여를 분리할 수 있다.
|
||||
|
||||
## 미지수
|
||||
|
||||
- 격리 데이터셋을 추가로 유지할 가치가 있는가. 시더와 테스트가 늘어난다.
|
||||
- 부모 수만 바꾼 데이터셋에서 지연이 왕복 수에 선형으로 붙는가.
|
||||
- 자식 수만 바꾼 데이터셋에서 지연이 전송 행수에 어떻게 붙는가.
|
||||
- 두 기여를 분리해도 전략 선택이 달라지는가. 이미 배치와 프로젝션으로 둘 다 줄였다.
|
||||
- 편중 분포를 유지한 데이터셋과 격리 데이터셋을 모두 유지할 것인가, 격리 데이터셋으로 대체할 것인가.
|
||||
|
||||
## 제약
|
||||
|
||||
- 현재 지연 값은 단일 스레드·warm cache 상대값이라 절대값 비교에 쓸 수 없다. 격리 데이터셋을 만들어도 이 한계는 그대로다.
|
||||
- 실행하기 전에는 수치를 채우지 않는다. 예상값으로 표를 메우지 않는다.
|
||||
- 시더가 복잡해지면 기존 측정의 재현성에 영향을 줄 수 있다. 기존 데이터셋은 유지한 채 추가해야 한다.
|
||||
|
||||
## 선택지
|
||||
|
||||
### 1. 세 데이터셋을 모두 만든다
|
||||
|
||||
부모 수만 바꾼 것, 자식 수만 바꾼 것, 편중을 유지한 것 세 가지를 유지한다. 각 변수의 기여를 따로 볼 수 있다.
|
||||
|
||||
시더와 테스트가 늘어나고 실행 시간도 길어진다.
|
||||
|
||||
### 2. 편중 데이터셋만 유지하고 격리는 하지 않는다
|
||||
|
||||
전략 선택이 이미 정해졌다면 원인 분해가 결정을 바꾸지 않는다. 현재 데이터셋으로 회귀만 지킨다.
|
||||
|
||||
나중에 지연 원인을 따져야 할 때 다시 만들어야 한다.
|
||||
|
||||
### 3. 필요할 때만 한시적으로 만든다
|
||||
|
||||
특정 판단이 필요해지는 시점에 격리 데이터셋을 만들고 측정한 뒤 남기지 않는다.
|
||||
|
||||
측정 시점마다 시더를 다시 맞춰야 해서 재현성이 떨어진다.
|
||||
|
||||
## 다음 검증
|
||||
|
||||
1. 부모 수만 바꾼 데이터셋을 만든다. 부모마다 자식을 정확히 1개씩 둔다.
|
||||
|
||||
2. 부모 수를 고정하고 자식 수만 바꾼 데이터셋을 만든다.
|
||||
|
||||
3. 두 데이터셋에서 왕복 수, 전송 행수, 지연을 각각 측정한다.
|
||||
|
||||
4. 지연이 어느 변수에 어떻게 붙는지 확인한다.
|
||||
|
||||
5. 분해 결과가 이미 내린 전략 선택을 바꾸는지 본다. 바꾸지 않는다면 격리 데이터셋을 상시 유지할 필요가 있는지 다시 판단한다.
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
---
|
||||
id: b099ca65-bf9f-4d61-814c-74722453fa3c
|
||||
kind: QUESTION
|
||||
slug: nullable-first-highlighted-at
|
||||
title: Highlight 없는 FeedItem을 허용할 것인가
|
||||
topic: JPA 피드 조회 성능
|
||||
project: Liner N + 1문제
|
||||
status: 게시 전
|
||||
studio: "https://hyeonworks.com/studio/documents/b099ca65-bf9f-4d61-814c-74722453fa3c/edit"
|
||||
questionStatus: OPEN
|
||||
---
|
||||
|
||||
# Highlight 없는 FeedItem을 허용할 것인가
|
||||
|
||||
정렬키 first_highlighted_at이 nullable이라 하이라이트 없는 FeedItem이 존재할 수 있는 스키마다. 시더는 하이라이트가 만든 FeedItem만 넣어 항상 값이 차지만, 허용 여부를 정하지 않으면 정렬과 커서 비교식의 경계 동작이 정의되지 않는다.
|
||||
|
||||
## 관계
|
||||
|
||||
- **Keyset Pagination 설계 기준**
|
||||
정렬키의 null 처리가 커서 설계에 걸리는 지점이다.
|
||||
- **Visibility OR이 Keyset Index를 깨뜨린 문제**
|
||||
같은 정렬키 인덱스를 다루는 기록이다.
|
||||
|
||||
## 사실
|
||||
|
||||
- 현재 스키마의 first_highlighted_at은 timestamptz nullable이다. NOT NULL이 아니다.
|
||||
- 시더는 하이라이트가 만든 FeedItem만 생성하므로 이 값을 항상 채운다. 그래서 지금까지의 측정에서는 null이 나타나지 않았다.
|
||||
- FeedItem은 (user, page) 조합당 하나이고 UNIQUE(user_id, page_id) 제약이 있다.
|
||||
- keyset 커서는 (first_highlighted_at, id)를 비교식으로 사용한다.
|
||||
- 정렬키 전용 인덱스는 (first_highlighted_at DESC, id DESC)로 만들었다.
|
||||
|
||||
## 가정
|
||||
|
||||
- 하이라이트가 하나도 없는 FeedItem이 생기는 경로가 실제로 있을 수 있다.
|
||||
- null이 섞이면 커서 비교식이 경계에서 행을 빠뜨리거나 중복시킬 수 있다.
|
||||
- 정렬 위치를 정의하지 않으면 페이지를 넘길 때 순서가 흔들릴 수 있다.
|
||||
|
||||
## 미지수
|
||||
|
||||
- 하이라이트 없는 FeedItem을 만드는 경로가 도메인에 존재하는가. 존재한다면 어떤 상황인가.
|
||||
- NOT NULL로 좁힐 것인가, null을 허용하고 정렬 위치를 정의할 것인가.
|
||||
- null을 허용한다면 정렬에서 어디에 두는가. 그 위치를 인덱스가 지원하는가.
|
||||
- 커서가 null을 만났을 때 비교식을 어떻게 표현하는가.
|
||||
- 부분 인덱스 조건을 쓴다면 null 행이 인덱스에서 빠지는데 그 행은 어떻게 조회되는가.
|
||||
- FeedItem 생성 시점과 첫 하이라이트 시점이 다를 수 있는가.
|
||||
|
||||
## 제약
|
||||
|
||||
- 결정 시점은 keyset 페이징을 프로덕션에 반영하기 전이다. 커서 비교식과 인덱스 정의가 이 결정에 달려 있다.
|
||||
- 스키마를 NOT NULL로 좁히려면 기존 데이터에 null이 없어야 한다. 마이그레이션 전에 확인이 필요하다.
|
||||
- 현재 시더로는 이 경계가 재현되지 않는다. null이 섞인 데이터셋을 따로 만들어야 검증할 수 있다.
|
||||
|
||||
## 선택지
|
||||
|
||||
### 1. NOT NULL로 좁힌다
|
||||
|
||||
FeedItem이 항상 하이라이트와 함께 만들어진다면 정렬키를 NOT NULL로 정의한다. 커서 비교식이 단순해지고 인덱스도 그대로 쓸 수 있다.
|
||||
|
||||
대신 하이라이트 없는 FeedItem을 만드는 경로가 나중에 필요해지면 스키마와 생성 흐름을 다시 바꿔야 한다.
|
||||
|
||||
### 2. null을 허용하고 정렬 위치를 정의한다
|
||||
|
||||
정렬에서 null을 어디에 둘지 명시하고 인덱스도 같은 위치로 만든다. 커서 비교식은 null 구간을 따로 다룬다.
|
||||
|
||||
생성 흐름은 자유로워지지만 커서 표현과 인덱스 정의가 복잡해진다.
|
||||
|
||||
### 3. 부분 인덱스로 null 행을 제외한다
|
||||
|
||||
정렬키가 있는 행만 인덱스에 담는다. 피드 목록에는 하이라이트가 있는 항목만 노출한다는 정책이 된다.
|
||||
|
||||
인덱스는 작아지지만 null 행을 조회하는 별도 경로가 필요하다.
|
||||
|
||||
## 다음 검증
|
||||
|
||||
1. 도메인에서 하이라이트 없는 FeedItem이 생기는 경로가 있는지 확인한다.
|
||||
|
||||
2. 기존 데이터에 first_highlighted_at이 null인 행이 있는지 센다.
|
||||
|
||||
3. null이 섞인 데이터셋을 만들어 현재 커서 비교식이 경계에서 어떻게 동작하는지 재현한다.
|
||||
|
||||
4. 세 선택지 각각에서 커서로 넘긴 페이지가 OFFSET 페이지와 같은 행·같은 순서인지 대조한다.
|
||||
|
||||
5. 정렬 위치를 정의한 경우 인덱스가 그 순서를 그대로 주는지 실행계획으로 확인한다.
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
---
|
||||
id: 5088ce14-b096-41d3-abba-64b7afb48bb9
|
||||
kind: QUESTION
|
||||
slug: promote-feed-visible-to-cqrs
|
||||
title: feed_visible을 Production CQRS로 승격할 것인가
|
||||
topic: JPA 피드 조회 성능
|
||||
project: Liner N + 1문제
|
||||
status: 게시 전
|
||||
studio: "https://hyeonworks.com/studio/documents/5088ce14-b096-41d3-abba-64b7afb48bb9/edit"
|
||||
questionStatus: OPEN
|
||||
---
|
||||
|
||||
# feed_visible을 Production CQRS로 승격할 것인가
|
||||
|
||||
사용자별 가시성을 미리 계산한 테이블은 조회를 커버링 인덱스 하나로 만들었다. 상시 유지하려면 원본 변경을 투영에 동기화해야 하고, 이는 별도 물리 읽기 저장소를 두는 결정이 된다.
|
||||
|
||||
## 관계
|
||||
|
||||
- **Feed Visibility Query Pattern**
|
||||
세 방식을 비교한 기준이다.
|
||||
- **현재 Read Model은 CQRS-lite로 유지한다**
|
||||
지금 유지하기로 한 범위다.
|
||||
- **Visibility OR이 Keyset Index를 깨뜨린 문제**
|
||||
사전계산이 풀려던 문제다.
|
||||
|
||||
## 사실
|
||||
|
||||
- 사전계산 조회는 커버링 인덱스의 단일 스캔이었다. OR도 조인도 정렬도 없었다.
|
||||
- 단일 OR은 후보 1,500을 훑고 상위 20을 정렬로 만들었다. UNION 분해는 분기별로 스캔했다.
|
||||
- 세 방식은 같은 조회 사용자에게 같은 항목 집합을 반환했다.
|
||||
- 통합 쿼리에서 부모 선택을 사전계산으로 두면 깊은 페이지에서 인덱스 범위로 19행만 읽었다. 단일 OR로 두면 가시성 분기와 멘션 조건을 다시 계산하며 200행을 읽었다.
|
||||
- 현재 구현은 사전계산 테이블을 테스트 안에서 만들고 지운다. 상시 유지하지 않는다.
|
||||
- 현재 읽기 경로는 쓰기와 같은 저장소 위에 읽기 전용 포트·DTO·쿼리만 분리한 형태다.
|
||||
|
||||
## 가정
|
||||
|
||||
- 고트래픽 읽기에서는 조회 비용 차이가 실제 부하로 나타난다.
|
||||
- 상시 유지하면 원본 변경마다 투영 갱신이 필요하다.
|
||||
- 투영이 어긋나면 사용자가 볼 수 없는 항목을 보거나 볼 수 있는 항목을 놓친다.
|
||||
|
||||
## 미지수
|
||||
|
||||
- 현재 트래픽에서 단일 OR이나 UNION 분해로 충분한가. 사전계산이 필요한 임계가 어디인가.
|
||||
- 동기화를 어떤 방식으로 하는가. 도메인 이벤트인가 아웃박스인가.
|
||||
- 투영 갱신이 늦어졌을 때 허용 가능한 지연은 얼마인가.
|
||||
- 가시성이 바뀌는 사건이 무엇인가. 아이템 공개 범위 변경, 멘션 추가·삭제, 사용자 삭제까지 포함하는가.
|
||||
- 사용자 수만큼 늘어나는 저장 공간이 감당 가능한가.
|
||||
- 투영이 어긋났을 때 어떻게 발견하고 복구하는가.
|
||||
- 이 변경이 현재 정한 계약의 범위를 넘는가. 넘는다면 계약과 가드레일을 어떻게 개정하는가.
|
||||
|
||||
## 제약
|
||||
|
||||
- 현재 계약에서 별도 물리 읽기 저장소는 에스컬레이션 대상으로 남겨 두었다. 승격하려면 계약을 먼저 개정해야 한다.
|
||||
- 가시성은 보안에 걸린 조건이다. 투영이 어긋나면 노출 사고가 된다. 지연 허용치를 느슨하게 잡을 수 없다.
|
||||
- 지금까지의 측정은 단일 스레드 로컬 값이다. 고트래픽에서 어느 방식이 필요한지는 이 측정으로 답할 수 없다.
|
||||
|
||||
## 선택지
|
||||
|
||||
### 1. 현재 범위를 유지하고 요청 시 조회로 푼다
|
||||
|
||||
단일 OR이나 UNION 분해로 조회한다. 동기화 비용이 없고 정합성 문제도 없다.
|
||||
|
||||
고트래픽에서 조회 비용이 그대로 남는다.
|
||||
|
||||
### 2. 사전계산을 상시 유지하는 읽기 저장소로 승격한다
|
||||
|
||||
쓰기 변경을 투영에 반영하고 조회는 투영만 읽는다. 조회 비용이 가장 낮다.
|
||||
|
||||
동기화 경로, 지연 허용치, 정합성 검증, 복구 절차를 모두 설계해야 한다. 계약 개정도 필요하다.
|
||||
|
||||
### 3. 일부만 사전계산한다
|
||||
|
||||
접근이 잦은 구간만 투영으로 유지하고 나머지는 요청 시 조회한다.
|
||||
|
||||
두 경로를 함께 운영해야 하고 어느 구간을 투영에 둘지 정하는 기준이 필요하다.
|
||||
|
||||
## 다음 검증
|
||||
|
||||
1. 부하 테스트로 현재 조회 방식이 목표 트래픽을 견디는지 확인한다. 이 판단은 단일 스레드 측정으로 대신할 수 없다.
|
||||
|
||||
2. 가시성이 바뀌는 사건을 모두 열거하고 각각이 투영의 어느 행에 영향을 주는지 정리한다.
|
||||
|
||||
3. 사용자 수와 아이템 수를 곱한 투영 크기를 계산한다.
|
||||
|
||||
4. 동기화 지연의 허용치를 정한다. 가시성은 보안 조건이므로 이 값이 설계를 좌우한다.
|
||||
|
||||
5. 투영과 원본이 어긋났는지 확인하는 방법과 복구 절차를 정의한다.
|
||||
|
||||
6. 위 결과를 보고 계약을 개정할지 판단한다.
|
||||
Reference in New Issue
Block a user