Files
document-haness/.agents/skills/rewriting-technical-prose-naturally/references/regression-examples.md
T

470 lines
23 KiB
Markdown

# Regression Examples
Use these examples to calibrate decisions, not as sentence templates. The acceptable rewrites are intentionally plain. Reusing their sentence frames across a corpus would create another AI pattern.
한국 기술 블로그가 각 자리에서 실제로 쓰는 표현은 [korean-tech-blog-register.md](korean-tech-blog-register.md)에 있다. 이 파일은 그 규범을 어겼을 때 어떤 문장이 나오는지를 모은 것이다.
## 1. 수치를 추세 표현으로 바꾸지 않는다
원문:
> `Page`는 N=10, 100, 1,000에서 추가 쿼리가 각각 10번, 100번, 1,000번 발생했다. `User`는 3번, 20번, 20번 발생했다. 여러 `Feed Item`이 같은 `User`를 참조했고, 한 번 조회한 `User`는 1차 캐시에 남아 있었다.
잘못 고친 예:
> 데이터가 증가하면서 Page 조회 비용은 선형적으로 증가한 반면 User는 캐시 효과로 일정하게 유지됐다.
허용하는 예:
> `Page`는 N=10, 100, 1,000에서 각각 10번, 100번, 1,000번의 추가 쿼리가 발생했다. `User` 같은 경우는 여러 `Feed Item`에서 같은 사용자를 참조하고 있어서 추가 쿼리가 3번, 20번, 20번 발생했다. 한 번 조회한 `User`는 1차 캐시에 남아 있었다.
실패 이유: 잘못 고친 문장은 수치를 삭제하고 `조회 비용`, `선형적`, `캐시 효과`, `일정하게 유지`라는 더 넓은 해석으로 바꿨다. `User`의 3, 20, 20도 일정한 값이 아니다.
## 2. 제목에 대비를 만들지 않는다
원문에서 확인한 내용:
> `Page`와 `User`는 모두 `@ManyToOne(EAGER)`였다. 두 연관 관계에서 발생한 추가 쿼리 수가 달랐다.
잘못 고친 제목:
> 같은 EAGER가 정반대 곡선을 그린다
허용하는 제목:
> `EAGER` 연관 관계에서 발생한 추가 조회
실패 이유: `정반대 곡선`은 원문에 없는 모양과 대비를 만든다.
## 3. 추상적인 결정 요인으로 압축하지 않는다
원문:
> 이미 조회한 `User`는 1차 캐시에 남아 있어서 다시 조회하지 않았다.
잘못 고친 예:
> Persistence Context의 재사용 여부가 비용을 결정했다.
허용하는 예:
> 한 번 조회한 `User`는 1차 캐시에 남아 있어서 다시 조회하지 않았다.
실패 이유: `비용을 결정했다`는 측정 대상과 범위를 넓힌다.
## 4. 구체적인 변화는 그대로 적는다
원문:
> `Feed Item`을 100개에서 1,000개로 늘리자 `Page` 추가 쿼리도 100번에서 1,000번으로 늘었다.
잘못 고친 예:
> 조회 비용이 데이터셋의 카디널리티에 비례했다.
허용하는 예:
> `Feed Item`을 100개에서 1,000개로 늘리자 `Page` 추가 쿼리도 100번에서 1,000번으로 늘었다.
실패 이유: 구체적인 대상과 수치가 사라지고, 원문보다 넓은 비례 관계가 생겼다.
## 5. 비용을 다른 곳으로 이동시켰다고 포장하지 않는다
원문:
> `fetch join`을 적용한 뒤 쿼리 수는 줄었다. 조인으로 조회되는 행 수와 메모리 사용량은 늘었다.
잘못 고친 예:
> 비용이 네트워크와 메모리로 이동했다.
허용하는 예:
> 쿼리 수는 줄었지만 조인으로 조회되는 행 수와 메모리 사용량은 늘었다.
실패 이유: 원문에 없는 네트워크를 추가했고, 서로 다른 관측값을 하나의 `비용`으로 일반화했다.
## 6. 내부 측정 용어는 정확한 뜻이 있을 때만 푼다
원문:
> 총 `PreparedStatement`에서 collection fetch를 제외한 뒤에도 추가 쿼리가 남았다.
잘못 고친 예:
> ORM 내부 실행 비용을 제거한 뒤에도 숨은 부하가 존재했다.
허용하는 예:
> 컬렉션을 조회하는 쿼리를 제외하고도 추가 쿼리가 남았다.
실패 이유: `PreparedStatement``실행 비용`으로, 추가 쿼리를 `숨은 부하`로 바꿔 의미를 넓혔다. 허용 예는 이 문서에서 collection fetch가 컬렉션 조회 쿼리를 뜻한다고 앞 문맥이 확인해 준 경우에만 사용할 수 있다.
## 7. 문장을 짧게 압축하기보다 설명 흐름을 남긴다
원문:
> 여러 `Feed Item`이 같은 `User`를 참조하고 있었다. 한 번 조회한 `User`는 1차 캐시에 남았다.
잘못 고친 예:
> 동일 User 참조가 Persistence Context에서 재사용됐다.
허용하는 예:
> `User` 같은 경우는 여러 `Feed Item`에서 같은 사용자를 참조하고 있어서, 한 번 조회한 `User`는 1차 캐시에 남았다.
실패 이유: 잘못 고친 문장은 무엇을 다시 사용했는지와 실제 조회 동작을 압축했다.
## 8. 원문에 없는 교훈을 붙이지 않는다
원문:
> N=100에서 `Page` 추가 쿼리가 100번 발생했다. 각 `Feed Item`이 서로 다른 `Page`를 참조하고 있었기 때문이다.
잘못 고친 예:
> N=100에서 `Page` 추가 쿼리가 100번 발생했다. 따라서 EAGER 연관 관계는 반드시 피해야 한다.
허용하는 예:
> N=100에서 `Page` 추가 쿼리가 100번 발생했다. 각 `Feed Item`이 서로 다른 `Page`를 참조하고 있었기 때문이다.
실패 이유: 측정 결과만으로 일반적인 설계 권고를 만들었다.
## 9. 확인하지 않은 결과를 확정하지 않는다
원문:
> 같은 refresh token의 두 번째 사용은 rotation 정책 때문에 거부될 가능성이 있다. 실제 응답과 session 영향은 아직 재현하지 않았다.
잘못 고친 예:
> rotation이 적용되므로 두 번째 refresh token 사용은 거부된다.
허용하는 예:
> 같은 refresh token을 두 번째로 사용했을 때 거부될 가능성이 있다. 실제 응답과 session에 미치는 영향은 아직 확인하지 않았다.
실패 이유: 가능성을 확정된 결과로 바꾸고 미검증 범위를 삭제했다.
## 10. 대상을 `기준선`이라고 부르지 않는다
원문:
> 피드 아이템을 엔티티로 조회한 뒤 Stream으로 DTO에 옮기는 구현을 기준선으로 삼았다.
잘못 고친 예:
> 같은 기준선에 두 가지 위반이 함께 있었다.
허용하는 예:
> 피드 아이템을 엔티티로 조회한 뒤 Stream으로 DTO에 옮기는 코드를 그대로 두고 측정했다.
>
> 하이라이트가 아무리 많아도 조회량이 그에 비례해 늘지 않아야 한다는 요구가 두 가지 방식으로 깨졌다.
실패 이유: `기준선`은 그 코드가 무엇인지 말하지 않고 비교 대상이라는 역할만 붙인다. 뒤에서 `같은 기준선에`로 되풀이되면 무엇을 가리키는지 더 흐려진다. `위반`도 무엇을 어긴 것인지 말하지 않는다. 어긴 요구를 문장에 적는다.
## 11. API·지표 이름은 남기고 뜻을 옆에 적는다
원문:
> | N | 초기화 Highlight 컬렉션 | 총 PreparedStatement |
>
> N=1,000에서 총 PreparedStatement는 2,022개였다.
잘못 고친 예:
> N=1,000에서 총 쿼리가 2,022개 실행됐다.
허용하는 예:
> 총 PreparedStatement는 Hibernate가 SQL 한 건을 실행하려고 JDBC에서 얻은 문장 객체 수다. 이 요청이 SQL 문장을 몇 건 준비했는지를 뜻한다.
>
> N=1,000에서 총 PreparedStatement는 2,022개였다.
실패 이유: 잘못 고친 문장은 이름을 지우면서 뜻까지 바꿨다. 원문은 이 값이 SQL 실행 수와 항상 같지는 않다고 적었다. 이름은 그대로 두고, 처음 나오는 자리에 그것이 무엇인지 한 문장으로 적는다. 평문 칸처럼 이름을 그대로 쓰기 어려운 자리에서는 `준비된 SQL 문장(PreparedStatement)`처럼 뜻을 앞에 두고 이름을 괄호에 남긴다.
## 12. 지표를 지키려다 문장을 비틀지 않는다
원문(측정값):
> 초기화 Highlight 컬렉션 : N=10에서 10, N=100에서 100, N=1,000에서 1,000
> 총 PreparedStatement : 25, 222, 2,022
> 본문에 적힌 조건 : batch나 subselect가 없어 컬렉션 하나를 초기화할 때 SQL 하나가 나간다
잘못 고친 예:
> 매핑이 getHighlights()에 접근할 때마다 비워 뒀던 하이라이트 목록이 하나씩 채워졌고, 채워진 목록 수가 반환 아이템 수 N과 정확히 같았다. N=1,000에서 이 요청 하나가 준비한 SQL 문장은 2,022건이었다.
허용하는 예:
> 매핑이 getHighlights()에 접근하는 시점에 N개의 쿼리가 추가로 나갔다.
> N=1,000이라면 추가 쿼리를 포함해 총 2,022개가 나갔다.
실패 이유: 잘못 고친 문장은 `초기화된 컬렉션 수는 SELECT 수가 아니다`라는 주의를 요약 칸에서까지 지키려다 사건을 명사구(`채워진 목록 수`, `준비한 SQL 문장`)로 바꿨다. 정확하지만 아무도 그렇게 말하지 않는다. 문서가 조건(batch 없음)을 이미 밝혔으므로 요약과 결론에서는 일어난 일을 동사로 적고, 지표 이름과 주의는 본문 표 옆에 남긴다.
## 13. 준비하거나 되풀이하는 문장은 지운다
원문:
> ## 반복되는 하이라이트 조회 하나의 실행계획
>
> 반복되는 하이라이트 조회 하나를 실행계획으로 확인했다.
>
> ```text
> Index Scan using ...
> ```
허용하는 예:
> ## 반복되는 하이라이트 조회 하나의 실행계획
>
> ```text
> Index Scan using ...
> ```
실패 이유: 제목이 이미 말한 것을 문장이 한 번 더 말한다. `코드에 반복문은 없다`, `이 관찰은 두 위반을 드러낸다`처럼 다음 문장을 준비하기만 하는 문장도 같다. 측정한 사실과 자료에 있는 이유만 남긴다.
## 14. 예시는 한 규모로 고정한다
잘못 고친 예:
> N=10에서 10개, N=100에서 100개, N=1,000에서 1,000개였다. 총 쿼리는 25개, 222개, 2,022개였다. N=1,000에서 피드 한 번 로딩은 194 ms였다.
허용하는 예:
> N=100이면 100번이고, 추가 쿼리를 포함한 총 쿼리는 222개였다. 피드 한 번 로딩의 지연 중앙값은 85.9 ms였다.
실패 이유: 세 규모를 문장마다 늘어놓으면 읽는 사람이 매번 어느 규모의 이야기인지 다시 맞춰야 한다. 어떻게 늘어나는지는 표가 이미 보여 준다. 설명은 한 규모에서 하고, 그 규모의 수치만 문장에 남긴다. 가장 큰 N을 고르는 것은 설명이 아니라 과장이다.
## 15. 문장을 그림으로 옮기지 않는다
본문에 있던 그림의 `<text>`:
> loadFeed(0, N) → FeedItem N개 · Highlight 컬렉션 초기화 N회 · Highlight SELECT N회
바로 옆 문단:
> 매핑이 getHighlights()에 접근하는 시점에 아이템마다 쿼리가 한 번씩 나갔다.
실패 이유: 그림이 문단을 다시 그렸을 뿐이라 읽는 사람이 그림에서 새로 얻는 것이 없다. `alt`까지 같은 말을 세 번째로 반복한다. 그림은 순서·구조·측정값·실제 산출물(로그, 실행계획, 화면)처럼 문장이 담지 못하는 것을 담을 때만 남긴다.
## 16. 없는 관용구를 만들어 쓰지 않는다
원문(측정값):
> returned : N=10에서 10, N=100에서 20, N=1,000에서 20
> feedItemLoaded : 10, 100, 1,000
잘못 고친 예:
> `returned`는 페이지 크기에 고정되었지만 `feedItemLoaded`는 N을 따라 늘었다.
> 조회 수는 아이템 수 100을 따라갔다.
허용하는 예:
> `returned`는 페이지 크기인 20에 그대로 머물렀지만, `feedItemLoaded`는 N이 커지는 만큼 같이 늘어 N=1,000에서 1,000이 되었다.
실패 이유: 한국어에서 `따라가다`의 목적어는 사람, 길, 기준 같은 것이지 개수가 아니다. `100을 따라갔다`
한국어 문장이 아니다. 앞의 규칙(`캐시 효과`처럼 뭉뚱그리지 말 것)을 지키려다 아무도 쓰지 않는 관용구를
새로 만든 경우다. **금지 표현을 피한 자리에 들어가는 대체 표현도 똑같이 검사한다.** 수가 같이 늘어난다는
말은 `~에 비례해`, `~가 커지는 만큼`, `~와 같은 수로`, 또는 그냥 값을 적어 `아이템이 100개면 조회도
100번 나갔다`로 쓴다.
## 17. 논증 속 역할로 부르지 않는다 — `비교 대상`, `최소한의 선`, `관계`
잘못 고친 예:
> 초기화 컬렉션 수와 `PreparedStatement` 수는 비교 대상이 아니다.
> 이 구현도 최소한의 선은 지켰다.
> 두 엔티티의 관계 때문에 추가 쿼리가 생겼다.
허용하는 예:
> 두 값은 세는 것이 다르다. 초기화 컬렉션 수는 지연 로딩이 채운 컬렉션 개수이고, `PreparedStatement` 수는 JDBC에서 얻은 문장 객체 수다.
> 이 구현도 공개 범위 판정은 요구대로 적용했다.
> `FeedItem.page`에 걸린 `@ManyToOne(EAGER)` 매핑 때문에 아이템마다 `Page` 조회가 한 번씩 더 나갔다.
실패 이유: `비교 대상이 아니다`는 두 값이 왜 다른지를 말하지 않고 독자에게 비교하지 말라는 지시만 남긴다.
`최소한의 선`은 무엇을 지켰는지 말하지 않는다. `관계`는 어느 매핑인지 말하지 않는다. 세 낱말 모두
글쓴이의 머릿속에 있는 논증 구조를 가리킬 뿐 코드나 측정값을 가리키지 않는다. `관계``연관 관계`,
`@ManyToOne 관계`처럼 이름의 일부일 때만 쓴다.
## 18. 설명 앞에 상황 서술을 덧대지 않는다
잘못 고친 예:
> 이 코드는 반복문 없이 목록을 매핑하는 상황이다. 그런데도 매핑이 `getHighlights()`에 접근하는 시점에 아이템마다 쿼리가 한 번씩 나갔다.
허용하는 예:
> 매핑 코드에 반복문은 없지만, `getHighlights()`에 접근하는 시점에 아이템마다 쿼리가 한 번씩 나갔다.
실패 이유: 첫 문장이 말한 내용을 두 번째 문장이 그대로 다시 말한다. `~한 상황이다`, `여기서는 ~를 다룬다`,
`이 절은 ~에 관한 내용이다`는 설명을 미루기만 한다. 조건이 정말 필요하면 설명 문장 안에 `~지만`,
`~인데`로 넣는다. 앞으로 어떤 각도에서 볼지 알려 주는 예고 문장(`이번에는 ~를 ~ 중심으로 살펴보겠습니다`)은
새 정보를 주므로 다르다.
## 19. 이유를 문장 밖으로 밀어내지 않는다
잘못 고친 예:
> 폴리곤 데이터는 실시간으로 변경되지 않는다. 그래서 메모리에 올렸다. 메모리에 올려도 문제가 없었다.
허용하는 예:
> 폴리곤 데이터는 실시간으로 변경되는 데이터가 아니기 때문에 메모리에 올려놓고 사용해도 무방했다.
실패 이유: 한 문장에 사실 하나라는 규칙을 기계적으로 적용하면 주어가 같은 문장이 셋으로 쪼개지고,
`그래서`가 접착제로 붙는다. 한국어 기술 문장은 이유를 `~기 때문에`, `~다 보니`, `~어서`로 문장 안에
넣는다. 문장을 끊는 자리는 두 번째 절이 아니라 **주어가 바뀌는 지점**이다.
## 20. 처음 쓰는 말은 그 자리에서 정의한다
잘못 고친 예:
> MDC에 진입점 정보를 넣고 스레드가 바뀔 때 복사했다.
허용하는 예:
> MDC(Mapped Diagnostic Context)는 slf4j 같은 자바 로깅 프레임워크가 제공하는, 실행 중인 스레드 단위로 메타 정보를 담아 두는 공간이다. 여기에 진입점 정보를 넣고, 스레드가 바뀔 때 새 스레드로 복사했다.
실패 이유: 잘못 고친 문장은 독자가 MDC를 이미 안다고 가정한다. TechLog 기록은 짧아도 되지만, 핵심 내용을
이해하는 데 필요한 선수 지식은 글 안에 있어야 한다. 처음 나오는 API·지표·도메인 용어는 **그것이 무엇이고
무엇을 하는지** 한 문장으로 적고 이름은 그대로 둔다. 약어는 처음 나올 때 괄호로 편다. 이렇게 붙이는 정의는
`자료에 없는 내용 추가`가 아니다. 금지되는 것은 이 시스템·이 측정·이 결정에 대한 새 주장이다.
## 21. 코드와 측정의 시제를 섞지 않는다
잘못 고친 예:
> `RedisCommandGuard`는 요청받은 명령을 catalog에서 찾았고, 등록되지 않은 명령이면 거절한다.
허용하는 예:
> `RedisCommandGuard`는 요청받은 명령을 catalog에서 찾고, 등록되지 않은 명령이면 거절한다.
> 테스트에서 `EVAL`을 보내자 이 guard가 거절했고, 응답에는 `command not allowed`가 담겼다.
실패 이유: 코드가 늘 하는 일은 현재형(`~한다`, `~합니다`)으로, 실제로 재거나 겪은 일은 과거형(`~했다`,
`~했습니다`)으로 쓴다. 한 문장 안에서 섞이면 독자가 지금 읽는 것이 코드 동작인지 측정 결과인지 알 수 없다.
## 22. 이름만 보고 지표의 뜻을 지어내지 않는다
원문:
> `deniedCommandCount`와 `rejectedRequestCount`는 비교 대상이 아니다.
> `deniedCommandCount`는 1이었고 `rejectedRequestCount`는 200이었다.
잘못 고친 예:
> `deniedCommandCount`와 `rejectedRequestCount`는 세는 것이 다르다. `deniedCommandCount`는 거절된 명령의 수이고, `rejectedRequestCount`는 거절된 요청의 수다.
허용하는 예:
> `deniedCommandCount`는 1이었고 `rejectedRequestCount`는 200이었다. 두 값은 세는 단위가 달라서 함께 놓고 크기를 견주면 안 되는데, 각각이 정확히 무엇을 세는지는 이 문서에서 확인하지 않았다.
실패 이유: 원문은 두 값이 다르다고만 적었고 각각이 무엇을 세는지는 적지 않았다. 잘못 고친 문장은
`비교 대상이 아니다`(규칙 17)를 고치고 지표에 뜻을 붙이라는 규칙(규칙 11, 20)을 따르다가, **식별자
이름에서 뜻을 추론해 확정 사실로 적었다.** 그럴듯해 보이지만 이것은 측정 대상에 대한 새 주장이다.
정의를 가져올 수 있는 곳은 셋뿐이다. **원문, 코드, 그 프레임워크의 공식 문서.** 셋 다 답을 주지
않으면 이름을 그대로 두고, 원문이 말한 것까지만 적고, 확인하지 않았다고 밝힌다. 규칙 17을 지키려고
규칙 11을 넘겨 쓰지 않는다. 두 규칙이 부딪히면 **원문 보존이 이긴다.**
## 23. 영어 일반명사를 한국어로 쓴다
원문:
> `RedisCacheRegionAdapter`는 단일 key invalidation에서 `GETDEL`을 호출해 `INVALIDATED`와 `ALREADY_ABSENT`를 구분합니다.
잘못 고친 예:
> 단일 key invalidation은 값을 읽으면서 그 자리에서 지우는 `GETDEL`을 호출하기 때문에, 지워진 값이 있었으면 `INVALIDATED`를, 처음부터 없었으면 `ALREADY_ABSENT`를 돌려줍니다.
허용하는 예:
> 키 하나를 무효화할 때는 값을 읽으면서 그 자리에서 지우는 `GETDEL`을 부릅니다. 지워진 값이 있었으면 `INVALIDATED`를, 처음부터 없었으면 `ALREADY_ABSENT`를 돌려줍니다.
실패 이유: 잘못 고친 문장은 규칙을 다 지켰다. 정의를 앞에 뒀고, 이유를 문장 안에서 이었고, 금지어도
없다. 그런데도 기계가 쓴 것처럼 읽힌다. `key``invalidation`이 라틴 문자로 남아 있기 때문이다.
둘 다 식별자가 아니다. `GETDEL`, `INVALIDATED`, `ALREADY_ABSENT`는 식별자라 그대로 두고,
`key``키`, `invalidation``무효화`로 적는다.
우아한형제들 5편과 이 저장소 9개 절을 재보면 이렇다.
| | 우아한형제들 | 이 저장소 |
|---|---|---|
| 문장당 맨몸 영문 낱말 | 1.4 | 4.4 |
| 글자 중 한글 비율 | 0.58 | 0.34 |
| 쿼리 / `query` | 66 / 5 | 5 / 4 |
| 캐시 / `cache` | 2 / 0 | 0 / 19 |
| 상태 / `status` | 48 / 0 | 6 / 7 |
기술 블로그는 `쿼리`, `캐시`, `인덱스`, `라이브러리`, `컴포넌트`, `스레드`, `플러그인`처럼
자리잡은 외래어를 한글로 적는다. 라틴 문자는 진짜 식별자에만 쓴다. 이것을 고치면 사실은 하나도
바뀌지 않는다. 맨몸 일반명사는 애초에 보호 구간이 아니기 때문이다.
**첫 등장 뒤에는 한국어로 받는다.** `optional contributor는 … optional contributor가 …`
`… 이 항목이 …`로 받는다. 매 문장에 영어 이름을 되풀이하는 것이 문장당 영문 낱말을 넷까지 끌어올린다.
## 24. 확인한 것을 끝에서 목록으로 다시 포장하지 않는다
잘못 고친 예:
> ## 현재 구현 공백과 잘못 읽기 쉬운 지점
>
> - semantic Redis 조립은 … 4/5입니다.
> - `CacheRegionPort` 빈은 있지만 …
> - `CacheRefreshCoordinationPort` 운영 구현은 없습니다.
> - … (여덟 개)
허용하는 예:
> (각 한계를 그것이 제한하는 대상 옆에 둔다. 갱신 조정자를 설명한 문단 끝에
> `운영 구현은 아직 없고 테스트용 가짜 구현만 있습니다`를 붙이는 식이다.)
실패 이유: 여덟 항목 모두 본문이 이미 설명한 것이다. 끝에 모아 놓으면 사람이 쓴 글이 아니라
**에이전트가 분석을 마치고 Findings를 정리한 출력**처럼 읽힌다. 문장은 자연스러운데 문서가 기계다.
한계는 그것이 제한하는 대상 바로 옆에 있을 때 독자에게 쓸모가 있다.
같은 이유로 아래도 하지 않는다.
- `다음에 열어볼 source 순서` — 글쓴이가 자기한테 남기는 작업 메모다. 독자는 묻지 않았다.
- `잘못 읽기 쉬운 지점` — AI 기술 문서에 반복해서 나오는 분류다. 잘못 읽기 쉬운 대목이 있으면
그 대목에서 바로 적는다.
- 확인한 사실을 빠짐없이 절로 승격하기. 코드를 읽으면 참인 관찰이 수십 개 나온다. **글의 중심
질문에 필요한 것만 넣고 나머지는 버린다.** 확인한 것을 다 넣고 싶은 마음이 가장 확실한 기계 신호다.
## 25. 독자에게 사고를 지시하지 않는다
잘못 고친 예:
> 먼저 결론을 구분해야 합니다.
> 여기서 typed label과 end-to-end 동작을 구분해야 합니다.
허용하는 예:
> `RedisCacheRegionAdapter`는 운영 빈으로 조립됩니다. 그런데 `CacheAsideExecutor`와 묶어 쓰는
> 운영 유스케이스는 찾지 못했습니다.
>
> `CacheAsideExecutor`까지 따라가면 동작이 달라집니다.
실패 이유: `구분해야 합니다`는 독자에게 사고 절차를 지시할 뿐 아무 사건도 말하지 않는다. 사람이
작업 기록을 쓰면 바로 사건으로 들어간다. 방향을 알려 주는 문장은 글 전체에 한둘이면 충분하고,
절마다 붙으면 자기 분석 과정을 중계하는 글이 된다.
## 제목 회귀 목록
| 피할 제목 | 사실을 적은 제목 |
|---|---|
| 같은 EAGER가 정반대 곡선을 그린다 | `EAGER` 연관 관계에서 발생한 추가 조회 |
| 쿼리 하나에 숨어 있던 비용 | 한 컬렉션을 `fetch join`했을 때 조회되는 행 수 |
| 페이지가 아닌 데이터셋에 비례한다 | `Feed Item` 수에 따라 늘어난 `Page` 추가 쿼리 |
| 비용은 사라지지 않고 이동한다 | 쿼리 수는 줄었지만 추가 조회는 남았다 |
| fetch join의 회계 항등식 | `fetch join` 적용 전후의 쿼리 수와 조회 행 수 |
| 기준선 구현 | 측정한 `loadFeed` 구현 |
| 각 쿼리는 빠른데 느리다 | 반복되는 하이라이트 조회 하나의 실행계획 |
| 두 지표를 같은 것으로 읽지 않는다 | 초기화 컬렉션 수와 `PreparedStatement` 수가 뜻하는 것 |