feat: 문서 구조 변경 및 tech-visual 스킬 추가
This commit is contained in:
@@ -0,0 +1,182 @@
|
||||
# 글의 뼈대 — 관찰 기록
|
||||
|
||||
> **이 문서는 채워 넣을 틀이 아니다.** 아래 다섯 편이 실제로 어떤 순서를 썼는지 적어 둔 것이다.
|
||||
> 여기 있는 칸을 전부 채우려 들면 문서마다 같은 목차가 나오고, 문장은 한국어인데 글은
|
||||
> 보고서 생성기 출력처럼 읽힌다. 실제로 그렇게 됐고, 그래서 이 경고를 맨 앞에 둔다.
|
||||
>
|
||||
> 쓰는 법: 어떤 수가 있는지 보고, **이 문서에 필요한 것만 source가 정하게 한다.**
|
||||
> 자료가 말하지 않는 칸은 비우고, 자료가 채울 수 있어도 이 글에 필요 없으면 역시 비운다.
|
||||
|
||||
# 글의 뼈대
|
||||
|
||||
우아한형제들 기술블로그 5편의 목차와 도입·마무리를 그대로 읽고 정리한 것이다. 낱낱의 문장이 아니라
|
||||
**글 전체가 어떤 순서로 서는지**를 담는다. 인용은 아래 글에서 가져왔고, 브라우저로 페이지를 직접 열어
|
||||
옮겼다.
|
||||
|
||||
- [사람도 AI도 놓친 번역 누락, ESLint 플러그인을 만들어 해결하기](https://techblog.woowahan.com/26388/) — 이하 `26388`
|
||||
- [WMS 재고 이관을 위한 분산 락 사용기](https://techblog.woowahan.com/17416/) — `17416`
|
||||
- [로그 및 SQL 진입점 정보 추가 여정](https://techblog.woowahan.com/13429/) — `13429`
|
||||
- [검색 성능 개선을 위한 Elasticsearch 인덱스 구조와 쿼리 최적화](https://techblog.woowahan.com/20161/) — `20161`
|
||||
- [나 4년 차 서버개발자, 배달의민족의 지리 체계를 뒤흔들다](https://techblog.woowahan.com/11238/) — `11238`
|
||||
|
||||
이 다섯 편은 `scripts/style_profile.mjs`가 쓰는 기준선이기도 하다. 원문을 다시 받으려면
|
||||
`node scripts/fetch_reference.mjs <디렉터리>`를 쓴다. 사이트가 curl과 리더 프록시를 403으로 막으므로
|
||||
실제 브라우저가 필요하고, `playwright-core`가 있어야 한다. 받은 뒤
|
||||
`node scripts/style_profile.mjs --baseline <디렉터리>/*.md`로 값을 다시 잰다.
|
||||
|
||||
---
|
||||
|
||||
## 1. 다섯 편이 공유하는 순서
|
||||
|
||||
| 자리 | 26388 | 17416 | 13429 | 20161 | 11238 |
|
||||
|---|---|---|---|---|---|
|
||||
| 왜 이 글인가 | 여는 글 | (첫 문단) | (첫 문단) | (첫 문단) | 포스팅 목적 |
|
||||
| **말 뜻 정하기** | @lib/i18n과 세 가지 컨벤션 | **WMS란?** | **진입점이 뭐죠?** · **MDC를 아시나요?** | 성능개선을 돕는 도구 | **공간 (Spatial) 데이터 타입이란?** |
|
||||
| 무슨 일이 있었나 | 험난한 컨벤션 준수의 길 | 할당과 취소를 동시에 요청한다면? | 근데 왜 진입점 정보가 남아야 해요? | 현상 | 프로젝트 배경 |
|
||||
| 왜 그랬나 | 사람과 AI 검수의 한계 | 동시성 이슈 원인 | — | 문제 원인 분석 및 해결 | 방향성 검토 |
|
||||
| 어떻게 했나 | 린트로 위반 탐지하고 AI로 교정하기 | 1 단계: 분산 락 추가하기 | 기본 작업 · 추가 작업 | 해결 방안 | 개발 |
|
||||
| 결과 | 린트 플러그인의 성과 | (단계마다 문제점) | — | 개선 결과 | 결과 · 검증 |
|
||||
| 닫기 | 맺는 글 | 마무리 | 마무리 | 맺으며 | 회고 |
|
||||
|
||||
**용어를 정하는 자리가 항상 문제보다 앞에 있다.** 다섯 편 예외가 없다. 독자가 모르는 말을 안고
|
||||
문제 설명을 따라가게 두지 않는다.
|
||||
|
||||
---
|
||||
|
||||
## 2. 첫 문단은 세 가지를 한다
|
||||
|
||||
`17416`의 도입은 세 문장이고, 셋이 각각 다른 일을 한다.
|
||||
|
||||
> "WMS 재고 이관 과정에서 발생한 동시성 이슈를 분산 락(Distributed Lock)을 사용해 해결한 경험을 공유하는 글입니다. 본 글은 분산 락에 대해 알고 있는 분들을 대상으로 작성되었습니다. 제가 경험한 내용들이 여러분들의 비즈니스에 도움이 되는 글이 되길 바랍니다."
|
||||
|
||||
1. **이 글이 무엇인가** — `<무엇>에서 <무슨 일>을 <어떻게> 한 <경험/과정>을 공유하는 글입니다`
|
||||
2. **누가 읽는 글이고, 무엇을 알고 있어야 하는가**
|
||||
3. 바람 한 줄
|
||||
|
||||
`26388`도 같은 자리에 같은 문장을 둔다.
|
||||
|
||||
> "이 글은 다국어 라이브러리를 사용하거나, 팀의 까다로운 컨벤션 유지를 위해 AI 및 린트를 활용하는 개발자를 대상으로 합니다. 린트 플러그인을 구현한 경험이 없어도 쉽게 읽을 수 있게 정리했습니다."
|
||||
|
||||
`11238`은 독자를 둘로 나눠서 각각에게 읽는 법을 준다.
|
||||
|
||||
> "취업 준비 중인 분들이라면 프로젝트 과정을 간접적으로 경험해 보시면 좋겠고, 현업에 계신 분들이라면 속한 부서에서 진행하는 방법과 차이를 비교해 보면서 읽으시면 좋겠습니다."
|
||||
|
||||
**독자와 선수 지식의 바를 도입에서 못 박는다.** 이 문장이 있으면 본문에서 어디까지 풀어 써야 하는지가
|
||||
정해진다. 없으면 글 전체가 흔들린다.
|
||||
|
||||
---
|
||||
|
||||
## 3. 도입 끝에 차례를 알린다
|
||||
|
||||
> `17416` — "본 글에서는 WMS 재고를 이관하는 과정에서 마주친 동시성 문제에 대해 살펴보고, 어떤 방법으로 동시성 이슈를 해결해 나갔는지에 대해 공유합니다."
|
||||
|
||||
> `11238` — "프로젝트는 다음 순서대로 소개해 보겠습니다. — 프로젝트 배경 / 방향성 검토 / 개발 / 검증 / 회고"
|
||||
|
||||
> `26388` — "먼저 문제의 출발점이 된 @lib/i18n 라이브러리와 컨벤션부터 살펴보겠습니다."
|
||||
|
||||
한 줄이든 목록이든, **읽는 사람이 지금 어디쯤인지 알 수 있게 한다.**
|
||||
|
||||
---
|
||||
|
||||
## 4. 용어 절의 생김새
|
||||
|
||||
제목부터 묻는 형태다.
|
||||
|
||||
| 제목 | 글 |
|
||||
|---|---|
|
||||
| `WMS란?` | 17416 |
|
||||
| `진입점이 뭐죠?` | 13429 |
|
||||
| `MDC를 아시나요?` | 13429 |
|
||||
| `공간 (Spatial) 데이터 타입이란?` | 11238 |
|
||||
| `근데 왜 진입점 정보가 남아야 해요?` | 13429 |
|
||||
|
||||
안에서 하는 일은 셋이다.
|
||||
|
||||
1. **한 문장 정의** — `WMS(Warehouse Management System, 창고 관리 시스템)는 물류센터에서 반복되는 수기 작업을 시스템화하여 안정적으로 운영될 수 있도록 합니다.`
|
||||
2. **이 글에서 쓸 말을 직접 정함** — `편의상 "화면에 렌더링되어 사용자에게 노출되는 문자열"을 "문구"라고 하겠습니다.` · `이들을 모두 묶어서 "번역 API"라고 표현하겠습니다.`
|
||||
3. **주변 관계를 한 문단으로** — `WMS 재고들은 중앙물류기지라고 불리는 DC(Distribution Center)로 입고되며, DC에 입고된 상품들은 지역 거점 센터인 PPC(Picking Packing Center)로 재고가 이관됩니다.`
|
||||
|
||||
약어는 나오는 자리에서 전부 편다. `WMS(Warehouse Management System, 창고 관리 시스템)`,
|
||||
`DC(Distribution Center)`, `PPC(Picking Packing Center)`, `MDC(Mapped Diagnostic Context)`,
|
||||
`AST(Abstract Syntax Tree)`, `분산 락(Distributed Lock)`, `보간(Interpolation)`.
|
||||
|
||||
---
|
||||
|
||||
## 5. 사례 하나를 다루는 작은 틀
|
||||
|
||||
`20161`은 같은 세 칸을 다섯 번 반복한다.
|
||||
|
||||
```
|
||||
현상 → 문제 원인 분석 및 해결 → 개선 결과
|
||||
```
|
||||
|
||||
`17416`은 단계마다 자기 문제를 달고 간다.
|
||||
|
||||
```
|
||||
1 단계: 분산 락 추가하기 → 해결방법 → 문제점
|
||||
2 단계: 분산 락 대기하기 → 해결방법 → 문제점
|
||||
3 단계: 분산 락과 상태 키 함께 사용하기 → 해결방법
|
||||
```
|
||||
|
||||
**고친 방법마다 남은 문제를 같이 적는다.** 마지막 단계에 와서야 `문제점`이 없다. 처음부터 정답을
|
||||
내놓지 않고, 왜 다음 단계가 필요했는지를 앞 단계의 `문제점`이 만든다.
|
||||
|
||||
---
|
||||
|
||||
## 6. 독자를 데리고 다니는 문장
|
||||
|
||||
이 글들은 독자가 무엇을 궁금해할지 알고 미리 처리한다. 내 스킬이 가장 크게 놓쳤던 부분이다.
|
||||
|
||||
| 하는 일 | 문장 |
|
||||
|---|---|
|
||||
| 곁길 막기 | "여기서 번역 API의 내부 동작이 궁금할 수 있겠지만 딴 길로 새지 맙시다." |
|
||||
| 나중으로 미루기 | "그리고 유형별로는 문구의 포맷 차이가 있는데, 나중에 살펴보겠습니다." |
|
||||
| 초점 잡기 | "중요한 것은 아래 세 가지 컨벤션을 반드시 지켜야 한다는 점입니다." |
|
||||
| 다음 칸 예고 | "이번에는 그렇게 탄생한 규칙들을 깊게 파보겠습니다." |
|
||||
| 수치로 넘어가기 | "숫자 없이 복잡한 글만으로는 효능이 마음에 와닿지 않는 듯하니, 제작한 플러그인의 규칙이 적발한 컨벤션 위반 개수를 확인해봅시다." |
|
||||
| 범위 좁히기 | "전체적인 내용은 기술적인 내용보다는 ~ 전체 과정을 소개하는 데 집중했습니다." |
|
||||
|
||||
**`중요한 것은 ~입니다`는 금지어가 아니다.** 여기서는 앞에 늘어놓은 것 중 무엇을 들고 갈지 고르는
|
||||
말이고, 자료에 없는 결론을 만드는 말이 아니다. 금지되는 쓰임과 구분해야 한다.
|
||||
|
||||
- 쓴다 — 이 글 안에서 독자의 눈을 어디로 보낼지 정할 때
|
||||
- 안 쓴다 — 측정값 옆에 붙여 자료가 증명하지 않은 해석을 결론처럼 얹을 때
|
||||
|
||||
---
|
||||
|
||||
## 7. 마무리 절
|
||||
|
||||
`26388`의 맺는 글은 네 걸음이다.
|
||||
|
||||
> "지금까지 커머스 웹프론트에서 다국어 지원을 위해 도입한 @lib/i18n의 컨벤션 준수 이슈와 그 해결 과정을 살펴봤습니다. 사람은 실수를 하고 AI는 확률론적이다 보니 컨벤션 위반의 미탐과 오탐이 빈번했기 때문에, 결정론적인 린트 규칙을 구현해 탐지하고 교정은 자연어에 능숙한 AI에게 맡기는 하이브리드 접근을 택했습니다. 그 결과 다량의 번역 누락과 오역을 방지하고 코드 복잡도도 낮췄습니다."
|
||||
>
|
||||
> "AI는 요술램프가 아닙니다. ... 결정론과 확률론의 경계를 구분하고 적재적소에 일을 맡기는 안목이 더욱 중요해지고 있습니다. 이 글이 비슷한 고민을 하시는 분들에게 도움이 되면 좋겠습니다."
|
||||
|
||||
1. `지금까지 ~를 살펴봤습니다` — 다룬 범위를 되짚는다
|
||||
2. `[원인]이다 보니 [문제]했기 때문에, [해결]을 택했습니다` — 한 문장으로 압축한 줄거리
|
||||
3. `그 결과 ~` — 성과
|
||||
4. 글쓴이 자신의 생각 + 독자에게 건네는 인사
|
||||
|
||||
**4번은 마무리 절에만 온다.** 본문 문단 끝마다 붙는 교훈과는 다른 것이다. 그리고 이것은 **글쓴이가
|
||||
실제로 가진 생각**이라 자료에 있을 때만 옮긴다. 없으면 1~3만 쓰고 끝낸다.
|
||||
|
||||
---
|
||||
|
||||
## 8. 이 저장소에 적용할 때
|
||||
|
||||
TechLog 기록은 서로 링크로 이어지고, 우아한형제들 글보다 짧다. 그래도 위 뼈대에서 **빼면 안 되는
|
||||
자리**가 있다.
|
||||
|
||||
| 자리 | 필수 여부 |
|
||||
|---|---|
|
||||
| 이 기록이 무엇을 다루는지 한 문장 | 필수 |
|
||||
| 독자와 선수 지식의 바 | 필수 |
|
||||
| 처음 쓰는 말의 정의 (본문 안, 첫 사용 앞) | 필수 |
|
||||
| 무슨 일이 있었나 · 왜 그랬나 | 필수 |
|
||||
| 어떻게 했나 · 결과 | 자료에 있으면 필수 |
|
||||
| 단계마다 남은 문제 | 자료에 있으면 필수 |
|
||||
| 차례 예고 | 절이 셋 이상이면 |
|
||||
| 글쓴이의 생각 | 자료에 있을 때만 |
|
||||
|
||||
없는 자리를 지어내지 않는다. **자료에 없으면 그 칸은 비운다.** 이 문서는 무엇을 채울 수 있는지를
|
||||
말할 뿐, 채울 내용을 만들어도 된다는 뜻이 아니다.
|
||||
+317
@@ -0,0 +1,317 @@
|
||||
# 한국 기술 블로그 문장 규범
|
||||
|
||||
한국 대기업 기술 블로그가 실제로 쓰는 문장을 모아 정리한 것이다. 인용문은 아래 글에서 가져왔다.
|
||||
|
||||
- 우아한형제들 — [번역 누락을 막는 ESLint 플러그인](https://techblog.woowahan.com/26388/)
|
||||
- 우아한형제들 — [분산 락으로 재고 이관 동시성 해결](https://techblog.woowahan.com/17416/)
|
||||
- 우아한형제들 — [Elasticsearch 인덱스 구조와 쿼리 최적화](https://techblog.woowahan.com/20161/)
|
||||
- 우아한형제들 — [배달의민족 지리 체계 개선](https://techblog.woowahan.com/11238/)
|
||||
- 우아한형제들 — [로그 및 SQL 진입점 정보 추가 여정](https://techblog.woowahan.com/13429/)
|
||||
- 토스 — [브라우저에서 번들링하기](https://toss.tech/article/engineering-note-6)
|
||||
|
||||
**인용문 출처에 관한 한계.** 원 사이트가 자동 수집을 막고 있어, 아래 인용문은 페이지를 그대로 내려받지
|
||||
않고 추출 도구를 거쳐 옮겼다. 문장의 어투·어미·낱말 선택을 보기에는 충분하지만 **한 글자까지 원문과
|
||||
같다고 보장하지 못한다.** 이 파일의 인용문을 문서에 직접 인용으로 옮기지 말고, 필요하면 원문 링크에서
|
||||
직접 확인한 뒤 옮긴다. 이 파일의 쓰임은 문체 관찰이다.
|
||||
|
||||
**문장 틀을 베끼라는 뜻이 아니다.** 같은 표현을 반복해서 쓰면 그것이 또 하나의 기계 문체가 된다.
|
||||
여기서 가져갈 것은 *어떤 자리에 어떤 품사와 어떤 동사를 쓰는가*이고, 버릴 것은 문장 자체다.
|
||||
|
||||
---
|
||||
|
||||
## 1. 쓸 말은 쓰기 전에 정의한다
|
||||
|
||||
기술 블로그는 처음 쓰는 말을 그 자리에서 한 문장으로 풀고 시작한다. 정의는 그 말이 **무엇인지**와
|
||||
**무엇을 하는지**를 말하지, 이 글에서 어떤 역할을 맡는지를 말하지 않는다.
|
||||
|
||||
> "'진입점'은 사용자 요청의 시작점을 의미합니다. 애플리케이션 또는 시스템에서 사용자 요청이 최초 진입되는 지점이 바로 진입점 입니다."
|
||||
|
||||
> "MDC(Mapped Diagnostic Context)는 자바 로깅 프레임워크(slf4j 등)에서 지원하는, 현재 실행중인 쓰레드 단위에 메타 정보를 넣고 관리하는 공간입니다."
|
||||
|
||||
> "WMS(Warehouse Management System, 창고 관리 시스템)는 물류센터에서 반복되는 수기 작업을 시스템화하여 안정적으로 운영될 수 있도록 합니다."
|
||||
|
||||
> "화면에 렌더링되어 사용자에게 노출되는 문자열을 '문구'라고 하겠습니다."
|
||||
|
||||
> "할당이란? 동일한 상품이 물류 센터 내 여러 로케이션(위치)에 흩어져 있는 경우, 작업자가 출고할 상품을 선점하는 작업이 필요한데 이 작업을 할당이라고 합니다."
|
||||
|
||||
> "Payload는 특정 term에 추가로 저장할 수 있는 메타데이터를 의미합니다."
|
||||
|
||||
> "샌드박스는 브라우저에서 바로 연동 흐름을 체험하고, 테스트 연동을 해볼 수 있는 개발자 도구예요."
|
||||
|
||||
정의에 쓰는 서술어는 좁다. **`~를 의미합니다` · `~입니다` · `~하는 공간입니다` · `~하는 설정입니다`
|
||||
· `~라고 하겠습니다` · `~를 X라고 합니다`.**
|
||||
|
||||
두 가지 습관을 같이 본다.
|
||||
|
||||
- **약어는 처음 나올 때 편다.** `MDC(Mapped Diagnostic Context)`, `WMS(Warehouse Management System, 창고 관리 시스템)`, `AST(Abstract Syntax Tree)`.
|
||||
- **정의한 뒤 한 번 더 구체적으로 바꿔 말한다.** 진입점 예시가 그렇다. 첫 문장은 사전적으로,
|
||||
두 번째 문장은 이 시스템에서 어디를 가리키는지로 다시 말한다.
|
||||
|
||||
정의를 넣는 자리는 **그 말을 처음 쓰기 직전**이다. 글 끝의 용어집이나 각주가 아니다.
|
||||
|
||||
---
|
||||
|
||||
## 2. 원인과 결과는 한 문장 안에서 잇는다
|
||||
|
||||
한국어 기술 문장은 이유를 연결어미로 문장 안에 넣는다. 사실 하나마다 문장을 끊지 않는다.
|
||||
|
||||
> "행정동은 변경이 잦기 때문에, 실시간으로 반영하지 않으면 내부에서 관리하는 행정동과 실제 행정동이 달라 배달팁이 실제 '동' 기준으로 부과되지 못하는 문제가 발생합니다."
|
||||
|
||||
> "폴리곤 데이터는 실시간으로 변경되는 데이터가 아니기 때문에 메모리에 올려놓고 사용해도 무방했습니다."
|
||||
|
||||
> "카테고리ID 필드는 숫자이기 때문에 integer로 색인을 하였는데, 정확하게 일치하는 값을 찾아내는 용도로만 쓰고 있기 때문에 keyword로 타입을 변경했습니다."
|
||||
|
||||
> "높은 집중력이 요구되는 작업에서 사람은 실수 덩어리이고 LLM은 확률적이다 보니 판단력이 다소 아쉬웠습니다."
|
||||
|
||||
> "물론 린트가 자동 교정까지 해주면 가장 이상적이겠지만 자연어를 기계적으로 교정하기는 어렵다 보니, 역할을 나눠서 린트가 탐지하고 AI가 교정하는 하이브리드 접근법으로 가겠습니다."
|
||||
|
||||
자주 쓰는 이음말: **`~기 때문에` · `~다 보니` · `~어서` · `~(으)므로` · `~는데` · `~니` · `~면`.**
|
||||
|
||||
문장 길이는 대체로 40–120자다. 한 문장에 사실 하나만 담으라는 규칙을 기계적으로 적용하면
|
||||
"A였다. B였다. 그래서 C였다."처럼 끊기는데, 이렇게 쓰는 한국 기술 블로그는 없다.
|
||||
**끊는 기준은 사실의 개수가 아니라 주어가 바뀌는 지점이다.** 주어가 같고 이유·조건으로 이어지면
|
||||
한 문장에 둔다. 주어가 바뀌면 끊는다.
|
||||
|
||||
---
|
||||
|
||||
## 3. 수치는 동사로 말한다
|
||||
|
||||
> "색인 문서의 양이 약 3배 증가했습니다."
|
||||
> "검색 및 리스팅 API 호출 수는 약 1.5배 증가했습니다."
|
||||
> "p99.9와 p99.99의 응답 속도가 20% 개선되었습니다."
|
||||
> "aggregation 수행 속도가 2배 이상 향상되었습니다."
|
||||
> "응답시간 0.7초 이상 슬로우쿼리가 모두 제거되었습니다."
|
||||
> "기존에는 약 4시간이 소요되었고, 개선 후에는 약 1분이 소요되었습니다."
|
||||
> "성능을 약 150배 향상 할 수 있었습니다."
|
||||
> "파일에서 컬럼을 읽어서 저장하기에 INSERT에 비해 약 20배 정도까지 빠를 수 있습니다."
|
||||
|
||||
틀은 단순하다. **`<잰 것>이/가 <수치만큼> <동사>했습니다`**. 앞뒤 비교는 `기존에는 ~, 개선 후에는 ~`로 둔다.
|
||||
`약`, `이상`, `정도까지`로 정밀도를 솔직하게 낮춘다.
|
||||
|
||||
### 증가 관계를 말하는 법
|
||||
|
||||
두 값이 같이 늘어난다는 말을 억지로 만들지 않는다. 실제로 쓰는 말은 이렇다.
|
||||
|
||||
| 관계 | 쓰는 표현 |
|
||||
|---|---|
|
||||
| 같은 수만큼 | `아이템이 100개면 쿼리도 100번 나갔습니다` · `N과 같은 수로 늘었습니다` |
|
||||
| 비례 | `N에 비례해 늘었습니다` · `N이 커진 만큼 늘었습니다` |
|
||||
| 배수 | `약 3배 증가했습니다` · `50배까지 벌어졌습니다` |
|
||||
| 안 변함 | `N을 바꿔도 2개로 그대로였습니다` · `페이지 크기에 고정되었습니다` |
|
||||
| 단위로 증가 | `배치 크기마다 한 번씩 늘었습니다` |
|
||||
|
||||
`~를 따라 늘었다`, `~를 따라갔다`, `~를 좇았다`는 쓰지 않는다. 한국어에서 `따라가다`의 목적어는
|
||||
사람·길·기준 같은 것이지 개수가 아니다. **`조회 수는 아이템 수 100을 따라갔다`는 한국어 문장이
|
||||
아니다.** 무엇이 몇이면 무엇이 몇이었는지를 그대로 적으면 된다.
|
||||
|
||||
---
|
||||
|
||||
## 4. 문맥에 따른 동사 선택
|
||||
|
||||
같은 뜻이라도 자리마다 쓰는 동사가 다르다. 아래는 관찰한 글에서 실제로 쓰인 동사다.
|
||||
|
||||
| 무엇을 말할 때 | 쓰는 동사 |
|
||||
|---|---|
|
||||
| 쿼리·요청이 실행됨 | 나갔습니다 · 실행되었습니다 · 호출했습니다 |
|
||||
| 수가 늘어남 | 늘었습니다 · 증가했습니다 · 벌어졌습니다 · 부풀었습니다 |
|
||||
| 수가 줄어듦 | 줄었습니다 · 감소했습니다 · 제거되었습니다 |
|
||||
| 빨라짐·좋아짐 | 개선되었습니다 · 향상되었습니다 · 빨라졌습니다 |
|
||||
| 시간이 걸림 | 소요되었습니다 · 걸렸습니다 |
|
||||
| 문제가 나타남 | 발생합니다 · 생겼습니다 · 드러났습니다 · 초래했습니다 |
|
||||
| 문제가 사라짐 | 해소되었습니다 · 사라졌습니다 · 막았습니다 |
|
||||
| 설정을 바꿈 | 변경했습니다 · 조정했습니다 · 분기했습니다 |
|
||||
| 기능을 넣음 | 적용했습니다 · 도입했습니다 · 추가했습니다 |
|
||||
| 재보고 확인함 | 측정했습니다 · 확인했습니다 · 살펴보겠습니다 · 파보겠습니다 |
|
||||
| 코드가 훑음 | 순회합니다 · 탐색합니다 · 마주합니다 |
|
||||
| 코드가 찾아냄 | 찾아냅니다 · 잡습니다 · 탐지합니다 |
|
||||
| 코드가 판정함 | 판단합니다 · 허용합니다 · 제한합니다 · 위반으로 잡습니다 |
|
||||
| 코드가 저장·전달함 | 넣고 관리합니다 · 삽입합니다 · 표시합니다 · 전달합니다 |
|
||||
| 원인을 지목함 | ~ 때문입니다 · ~에서 비롯되었습니다 |
|
||||
| 판단을 밝힘 | ~라고 판단했습니다 · ~해도 무방했습니다 · 도입하기 무리였습니다 · 한계가 있었습니다 |
|
||||
|
||||
코드 동작을 설명하는 문장의 예시다.
|
||||
|
||||
> "린터의 원리는 AST 노드를 순회하면서 설정된 규칙 기반으로 패턴을 찾아내는 것입니다."
|
||||
> "노드에 진입·퇴장하는 이벤트마다 스택에 삽입·회수할 플래그들을 정의합니다."
|
||||
> "계속 탐색하다 보면 어느새 말단에서 세 가지 타입의 문자열에 각각 상응하는 노드를 마주합니다."
|
||||
> "내부에 JSX 텍스트만 있고 엘리먼트나 컴포넌트가 없는 `<Trans>` 컴포넌트를 위반으로 잡을 뿐입니다."
|
||||
> "이 규칙은 함수 파라미터의 기본값으로 문자열 리터럴이 들어가는 것을 허용하는데, 프로젝트에서는 최종적으로 이런 기본값이 노출될 수도 있으니 제한해야 합니다."
|
||||
|
||||
코드는 `~합니다` 현재형으로 쓴다. 측정과 겪은 일은 `~했습니다` 과거형으로 쓴다. 둘을 섞지 않는다.
|
||||
|
||||
---
|
||||
|
||||
## 5. 조사
|
||||
|
||||
| 자리 | 조사 | 예 |
|
||||
|---|---|---|
|
||||
| 잰 대상 | `이/가` | `응답 속도가 20% 개선되었습니다` |
|
||||
| 앞뒤 대비 | `은/는` | `기존에는 4시간, 개선 후에는 1분` |
|
||||
| 바뀐 결과 상태 | `(으)로` | `keyword로 타입을 변경` · `1분이 소요` |
|
||||
| 비교 기준 | `에 비해` · `보다` | `INSERT에 비해 약 20배` |
|
||||
| 비례 기준 | `에 비례해` · `만큼` | `N에 비례해` · `N이 커진 만큼` |
|
||||
| 출처·주체 | `로부터` · `에서` | `DC 관리자로부터 문의가 들어왔습니다` |
|
||||
| 용도 한정 | `용도로만` | `일치하는 값을 찾는 용도로만 쓰고 있기 때문에` |
|
||||
|
||||
- `~를 따라`를 개수 증가에 붙이지 않는다. (3절)
|
||||
- `의`를 세 번 이상 잇지 않는다. `조회 수의 증가 형태의 비교`는 `조회 수가 어떻게 늘었는지`로 푼다.
|
||||
- 명사를 `~에 대한`으로 잇지 말고 동사로 푼다. `쿼리 수에 대한 측정` → `쿼리 수를 측정했습니다`.
|
||||
|
||||
---
|
||||
|
||||
## 6. 명사: 역할 이름이 아니라 물건 이름
|
||||
|
||||
기술 블로그는 대상을 그 대상의 이름으로 부른다. 논증에서 맡은 역할로 부르지 않는다.
|
||||
|
||||
| 쓰지 않는 말 | 쓰는 말 |
|
||||
|---|---|
|
||||
| 기준선 / 비교 대상 | 처음 만든 `loadFeed` 구현 · 이 코드를 그대로 두고 잰 값 |
|
||||
| 최소한의 선 / 마지노선 | 반드시 지켜야 하는 조건은 `<조건>`입니다 |
|
||||
| 관계 (막연한) | `FeedItem`과 `Highlight`의 `@OneToMany` 매핑 · `user_id` 외래 키 |
|
||||
| 위반 | 어떤 요구를 어떻게 어겼는지 |
|
||||
| 핵심 / 본질 / 실체 | 실제로 일어난 일 |
|
||||
| 구조적 문제 | 어떤 코드가 어떤 조건에서 무엇을 하는지 |
|
||||
| 증가 형태 / 비용 | 쿼리 수 · 조회 행 수 · 응답 시간 |
|
||||
| ~는 비교 대상이 아니다 | 두 값은 세는 것이 다릅니다. A는 `<A가 세는 것>`, B는 `<B가 세는 것>`입니다 |
|
||||
|
||||
`관계`는 JPA `연관 관계`처럼 이름의 일부일 때만 쓴다. 무엇과 무엇이 어떻게 연결되는지를
|
||||
`관계`라는 낱말로 덮으면 독자는 어느 매핑인지 알 수 없다.
|
||||
|
||||
---
|
||||
|
||||
## 6.5 라틴 문자는 식별자에만
|
||||
|
||||
관찰한 다섯 편은 자리잡은 외래어를 모두 한글로 적는다. 라틴 문자로 남는 것은 실제 식별자와 제품명뿐이다.
|
||||
|
||||
| 한글로 적는다 | 라틴으로 둔다 |
|
||||
|---|---|
|
||||
| 쿼리 · 캐시 · 인덱스 · 라이브러리 · 컴포넌트 · 플러그인 · 스레드 · 클래스 · 메서드 · 필드 · 테스트 · 세션 · 토큰 · 커넥션 · 타임아웃 · 어댑터 · 인스턴스 · 클라이언트 | `CacheAsideExecutor` · `getLoadCount()` · `min-replicas-to-write 1` · `application.yml` · `@ManyToOne` · `GETDEL` |
|
||||
| 자격 증명 · 상한 · 소유자 · 원본 · 응답 · 경고 · 계정 · 묶음 · 갈래 · 상태 · 설정 · 키 | Redis · Nginx · Hibernate · Spring · Keycloak · PostgreSQL |
|
||||
|
||||
실측: 우아한형제들은 문장당 맨몸 영문 낱말이 **1.4개**, 글자 중 한글이 **58%**다.
|
||||
같은 자리에서 이 저장소 문서는 **4.4개 / 34%**였다. 영어 낱말을 조사로 이어 붙인 문장이
|
||||
"AI가 정리한 기술 보고서"처럼 읽히는 가장 큰 이유다.
|
||||
|
||||
---
|
||||
|
||||
## 7. 주어
|
||||
|
||||
- **결정과 행동은 사람이 주어다.** `저는 ~하기로 했습니다`, `역할을 나눠서 ~로 가겠습니다`,
|
||||
`거의 전부 AI에게 맡겼습니다`.
|
||||
- **결과와 현상은 잰 대상이 주어이고 서술어는 피동이다.** `응답 속도가 개선되었습니다`,
|
||||
`슬로우쿼리가 모두 제거되었습니다`, `약 40시간 이상이 걸릴 것으로 예측이 되었습니다`.
|
||||
- **코드를 설명할 때는 코드 요소가 주어다.** `린터의 원리는 ~`, `이 규칙은 ~를 허용하는데`,
|
||||
`@rollup/browser는 파일 시스템이 아닌 메모리상의 데이터를 다뤄요`.
|
||||
- 주어를 생략해도 앞 문장에서 분명하면 생략한다. 문단마다 주어를 다시 세우지 않는다.
|
||||
|
||||
---
|
||||
|
||||
## 8. 문제는 사건으로 쓴다
|
||||
|
||||
> "DC 관리자로부터 취소된 이관요청서에 재고가 할당되어있다는 문의가 들어왔습니다."
|
||||
> "동시성 이슈의 원인은 취소 작업에는 분산 락이 걸려 있지 않기 때문입니다."
|
||||
> "데이터를 추출하고 보니, 이 배치를 통해 정확한 매핑 데이터를 추출하기에는 한계가 있었습니다."
|
||||
> "최초에 해당 배치를 개발하고 성능 측정을 해보았을 때, 운영환경의 데이터 기준 약 40시간 이상이 걸릴 것으로 예측이 되었습니다."
|
||||
> "그래서 기존 코드에서 많은 한글 문구들이 탐지되지 않아 번역이 누락되었고, 내부 개발용 코드의 한글 문자열이 잘못 잡히는 문제도 있었습니다."
|
||||
|
||||
`문제가 있었습니다`로 끝내지 않는다. **누가 무엇을 겪었는지, 어떤 조건에서 무엇이 어긋났는지**를
|
||||
적는다. 원인은 `원인은 ~ 때문입니다`로 한 번에 지목한다.
|
||||
|
||||
---
|
||||
|
||||
## 9. 선택과 권고
|
||||
|
||||
> "처음에는 이상적인 린트 플러그인을 섭외하여 공수를 절감하려 했지만 눈앞의 생태계는 상당히 척박했습니다."
|
||||
> "옵션 조절로도 해결이 어려운 문제가 다수 있어서 도입하기 무리였습니다."
|
||||
> "결국 컨벤션들을 충족시키는 커스텀 린트 규칙들과 이들을 포함하는 플러그인을 직접 개발하기로 했습니다."
|
||||
> "단일 term일 경우 match_phrase 쿼리가 아니라 match 쿼리로도 요구사항을 만족할 수 있기 때문에 분석된 term에 따라 쿼리를 변경하도록 쿼리를 분기했습니다."
|
||||
> "폴리곤 데이터는 실시간으로 변경되는 데이터가 아니기 때문에 메모리에 올려놓고 사용해도 무방했습니다."
|
||||
|
||||
순서가 일정하다. **먼저 해보려던 것 → 안 된 이유 → 그래서 고른 것 → 고른 이유.**
|
||||
대안을 `대안으로는 A, B가 있다`처럼 목록으로 늘어놓지 않고, 실제로 검토했다가 접은 것만 이유와 함께 쓴다.
|
||||
|
||||
권고할 때 쓰는 말: `~해야 합니다` · `~하는 편이 낫습니다` · `가급적 ~를 씁니다` · `~해도 무방했습니다`
|
||||
· `도입하기 무리였습니다`.
|
||||
|
||||
---
|
||||
|
||||
## 9.5 설명의 순서
|
||||
|
||||
용어 하나를 설명하는 대목은 대체로 같은 순서로 흘러간다.
|
||||
|
||||
1. **정의** — 그 말이 무엇이고 무엇을 하는지 (`화면에 렌더링되어 사용자에게 노출되는 문자열을 '문구'라고 하겠습니다`)
|
||||
2. **그래서 무슨 일이 벌어지는가** — 그 말이 실제 코드·운영에서 어떻게 쓰이는지
|
||||
3. **거기서 생기는 문제** — 어떤 조건에서 무엇이 어긋나는지 (`판단력이 다소 아쉬웠습니다`, `미탐과 오탐이 생겨 ~ 하락을 초래했습니다`)
|
||||
4. **그래서 무엇으로 대신하는가** — 대안과 고른 이유 (`역할을 나눠서 린트가 탐지하고 AI가 교정하는 하이브리드 접근법으로 가겠습니다`)
|
||||
|
||||
읽는 사람은 이 순서대로 알게 된다. **정의를 뒤로 미루면 2번과 3번을 읽는 동안 무슨 말인지 모른 채
|
||||
따라가야 한다.** 문제를 먼저 던지고 정의를 나중에 붙이는 구성은 극적이지만, 기술 문서에서는 독자가
|
||||
같은 문단을 두 번 읽게 만든다.
|
||||
|
||||
TechLog 기록은 서로 링크로 이어지는 관계형 문서라 분량이 짧을 수 있다. 그렇더라도 **핵심 주장과,
|
||||
그 주장을 이해하는 데 필요한 선수 지식은 그 기록 안에 있어야 한다.** 다른 기록으로 넘겨도 되는 것은
|
||||
더 깊은 배경이지, 이 문장을 읽는 데 당장 필요한 정의가 아니다.
|
||||
|
||||
4번은 자료에 근거가 있을 때만 쓴다. 대안을 검토한 적이 없으면 3번에서 멈춘다.
|
||||
|
||||
---
|
||||
|
||||
## 10. 소제목
|
||||
|
||||
| 형태 | 예 |
|
||||
|---|---|
|
||||
| 질문형 | `진입점이 뭐죠?` · `근데 왜 진입점 정보가 남아야 해요?` · `MDC를 아시나요?` |
|
||||
| 행동형 | `1 단계: 분산 락 추가하기` · `할당과 취소가 동시에 처리되는 것을 막아보자` · `브라우저에서 번들링하기` |
|
||||
| 대상형 | `공간데이터 및 GeoJSON` · `@lib/i18n과 세 가지 컨벤션` · `세 가지 린트 규칙과 위반 탐지 과정` |
|
||||
| 한계·상태형 | `사람과 AI 검수의 한계` · `험난한 컨벤션 준수의 길` · `남은 과제들` |
|
||||
|
||||
셋 다 **이 절에서 다루는 대상이나 하려는 일**을 이름으로 말한다. 대비를 만들거나
|
||||
수수께끼를 내지 않는다. `같은 EAGER가 정반대 곡선을 그린다` 같은 제목은 이 목록에 없다.
|
||||
|
||||
---
|
||||
|
||||
## 11. 절 첫 문장 — 예고는 되고 되풀이는 안 된다
|
||||
|
||||
앞으로 무엇을 어떤 각도에서 볼지 알려 주는 문장은 실제로 쓴다.
|
||||
|
||||
> "이번에는 그렇게 탄생한 규칙들을 깊게 파보겠습니다."
|
||||
> "그러므로 각 규칙이 위반·허용 패턴을 정의하는 방식과, 특정 노드의 진입·퇴장 이벤트에서 패턴을 찾아내고 처리하는 로직을 중심으로 살펴보겠습니다."
|
||||
|
||||
이 문장은 **읽는 각도**라는 새 정보를 준다. 반면 아래 같은 문장은 뒤 문장이 이미 하는 말이라 지운다.
|
||||
|
||||
- 제목이 `반복되는 하이라이트 조회 하나의 실행계획`인데 첫 문장이 `반복되는 하이라이트 조회 하나를 실행계획으로 확인했다`
|
||||
- 설명을 시작하기 전에 붙이는 `이 코드는 반복문이 없는 상황이다` / `여기서는 조회가 여러 번 일어나는 경우를 다룬다`
|
||||
- 관찰을 적고 나서 붙이는 `이 관찰은 두 가지를 보여준다`
|
||||
|
||||
**판별법: 그 문장을 지웠을 때 독자가 잃는 정보가 있는가.** 없으면 지운다.
|
||||
|
||||
---
|
||||
|
||||
## 12. 가져오지 않는 것
|
||||
|
||||
관찰한 글에는 이런 문장도 많다.
|
||||
|
||||
> "AI를 향한 무한한 숭배심은 던져버렸습니다."
|
||||
> "더 깐깐한 컨벤션 경찰이 필요합니다."
|
||||
> "쉬운 길은 없었습니다."
|
||||
> "어느정도 개발이 많이 진행된 상태에서 이런 상황이 닥치면 의욕이 상실되기도 하고, 대상 없는 원망이 생기기도 합니다."
|
||||
|
||||
**이 활력은 필자가 실제로 겪은 일에서 나온다. 자료에 없으면 만들지 않는다.**
|
||||
감정, 실패담, 비유, 1인칭 서술을 문체를 살리려고 지어내면 이 저장소의 작업 규칙을 어긴다.
|
||||
|
||||
자료 없이도 가져올 수 있는 것은 따로 있다. **평범한 동사, 구체적인 명사, 문장 안에서 이어지는 이유,
|
||||
정의를 먼저 두는 순서**다. 문장을 사람처럼 만드는 것은 감탄사가 아니라 이 네 가지다.
|
||||
|
||||
---
|
||||
|
||||
## 13. 소리 내어 읽기 검사
|
||||
|
||||
고친 문장마다 묻는다. **한국어를 쓰는 개발자가 동료에게 이 말을 이대로 하는가.**
|
||||
|
||||
- `조회 수는 아이템 수 100을 따라갔다` → 아무도 이렇게 말하지 않는다. → `아이템이 100개면 조회도 100번 나갔습니다`
|
||||
- `채워진 목록 수가 반환 아이템 수와 정확히 같았다` → 말하지 않는다. → `아이템 하나당 목록을 한 번씩 채웠습니다`
|
||||
- `이 값은 비교 대상이 아니다` → 말하지 않는다. → `두 값은 세는 것이 다릅니다`
|
||||
- `최소한의 선을 지켰다` → 말하지 않는다. → `<지킨 조건>은 지켰습니다`
|
||||
|
||||
정확한데 아무도 그렇게 말하지 않는 문장은 고쳐야 할 문장이다. 정확성은 낱말을 비틀어서가 아니라
|
||||
조건을 한 문장 더 적어서 지킨다.
|
||||
@@ -0,0 +1,469 @@
|
||||
# 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` 수가 뜻하는 것 |
|
||||
Reference in New Issue
Block a user