이번 파이프라인 작업과 무관하게 작업 트리에 남아 있던 것을 그대로 올린다. 사용자가 「전부 커밋」으로 정했고, 이번 작업과 섞이지 않게 커밋만 나눴다. 대부분은 clean-architecture-backend-template 의 그림 정본 재배치다 — final/assets/diagrams/<이름>/ 에 있던 것이 CLAUDE.md 가 적은 배치인 final/assets/<이름>/ 로 옮겨졌고 .techviz/<이름>/ 이 함께 들어왔다. 삽입 줄의 대부분(3.15M)이 그 .techviz context.json 이다. 그 밖에 ca-tmpl·document-haness 의 정리, .claude/agents/ 열한 개, writing-practitioner-guides 스킬, .playwright-mcp 세션 산출물, scripts/check-ssot-facts.py 와 그 시험이 들어 있다. 이 커밋의 내용은 내가 만든 것이 아니라 이전 세션이 남긴 것이고 검증하지 않았다. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
113 lines
5.2 KiB
Markdown
113 lines
5.2 KiB
Markdown
---
|
|
kind: CONCEPT
|
|
slug: deadline-propagation
|
|
title: 호출 예산에서 DB 로컬 타임아웃까지의 데드라인 전파
|
|
topic: transaction-deadline-and-pool
|
|
project: clean-architecture-backend-template
|
|
status: 게시 전
|
|
sourceRevision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916
|
|
basisVersion: Spring Boot 4.0.8 · PostgreSQL 16 · 리비전 21234e38
|
|
rootTreeNode: concept:deadline-propagation
|
|
evidenceCapturedOn: 2026-09-01
|
|
assets:
|
|
- key: deadline-propagation
|
|
file: ../../../final/evidence/rendered/deadline-propagation.svg
|
|
- key: deadline-propagation-diagram
|
|
file: ../../../final/assets/diagrams/deadline-propagation/deadline-propagation.svg
|
|
evidence:
|
|
- ../../../final/evidence/raw/deadline-propagation.txt
|
|
source:
|
|
- 원본 분석 절은 final/document.md#3-2 · final/document.md#a05 §3.3 이다.
|
|
---
|
|
|
|
# 호출 예산에서 DB 로컬 타임아웃까지의 데드라인 전파
|
|
|
|
호출자가 가진 시간 예산이 트랜잭션 타임아웃으로, 다시 데이터베이스의 로컬 타임아웃으로 좁혀진다. 각 단계가 앞 단계보다 작아야 상위 호출자가 포기한 뒤에도 하위가 계속 도는 상황이 생기지 않는다.
|
|
|
|
## 관계
|
|
|
|
- **데드라인은 호출 예산에서 시작해 세 단계로 좁힌다**
|
|
이 개념을 규칙으로 옮긴 것이다.
|
|
- **쓰기 트랜잭션에는 유한 타임아웃이 필수다**
|
|
이 전파의 마지막 단계가 없을 때의 문제를 다룬 규칙이다.
|
|
- **세션 스코프 설정은 풀로 돌아간 커넥션에 남는다**
|
|
로컬 타임아웃을 설정할 때의 함정이다.
|
|
|
|
## 본문
|
|
|
|
<!-- body:start -->
|
|
|
|
호출자의 남은 예산이 세 단계로 좁혀져 DB 세션 설정에 도달하는 구조의 설명이다.
|
|
|
|
## 마감이 좁혀지는 세 단계
|
|
|
|
:::evidence key="deadline-propagation-diagram" alt="호출 예산과 트랜잭션 마감과 DB 로컬 타임아웃이 위에서 아래로 쌓여 있고 오른쪽에 좁아지는 방향 화살표가 있다" caption="마감이 좁혀지는 세 단계" zoom="false"
|
|
:::
|
|
|
|
## 획득 전에 요구하는 것
|
|
|
|
`connectionTimeout + beginBudget + minimumActionWindow + completionMargin`을 요구하고, Spring의 초 단위 타임아웃이 1초 미만이면 시작하지 않는다. begin 이후에는 statement/lock/idle 셋을 각각 유도하고 하나라도 1ms 미만이면 거부한다.
|
|
|
|
## 예산이 좁혀지는 경로
|
|
|
|
:::evidence key="deadline-propagation" alt="분석 문서 final/document.md 에서 이 기록의 근거 절을 그대로 잘라낸 18줄. 코드베이스를 측정한 것이 아니라 원본 판정이 무엇을 적었는지를 보여 준다." caption="final/document.md 발췌 — 18줄" zoom="true"
|
|
:::
|
|
|
|
## SET 이 아니라 set_config 인 이유
|
|
|
|
둘이다 — `SET`은 파라미터 바인딩 전에 파싱되어 syntax error가 나고, 함수 호출은 값이 statement text에서 빠진다. 세 번째 인자 `true`가 transaction-local을 뜻한다.
|
|
|
|
## H2가 두 가지에서 다르다
|
|
|
|
세션 스코프이고 idle 가드가 없다. 그것이 H2의 성질이지 선택이 아니라는 점도 함께 적혀 있다.
|
|
|
|
:::note
|
|
|
|
실제 세션에서 SHOW statement_timeout으로 적용을 확인하지 않았다
|
|
|
|
:::
|
|
|
|
## 왜 전파해야 하는가
|
|
|
|
상위 호출자가 30 초 예산을 갖고 있는데 데이터베이스 쿼리에 타임아웃이 없으면, 호출자가 포기한 뒤에도 쿼리는 계속 돈다. 그 커넥션은 반납되지 않고 풀에서 빠져 있다.
|
|
|
|
부하가 걸리면 그 상태가 누적된다. 아무도 기다리지 않는 작업이 풀을 점유한다.
|
|
|
|
## 계산이 자기 타입을 갖는다
|
|
|
|
데드라인 계산기가 별도 타입이다. 획득 봉투를 포함하는 형태와 포함하지 않는 형태를 나눠 갖는다.
|
|
|
|
획득 봉투는 커넥션을 얻는 데 드는 시간이다. 그것을 예산에서 빼지 않으면, 커넥션을 기다리다가 남은 시간이 없는 채로 쿼리를 시작하게 된다.
|
|
|
|
## 데이터베이스마다 다른 설정기
|
|
|
|
로컬 타임아웃을 실제로 거는 방법은 데이터베이스마다 다르다. PostgreSQL 용 설정기와 H2 용 설정기가 따로 있다.
|
|
|
|
이 분리가 필요한 이유는 두 가지다. 설정 문법이 다르고, 세션 스코프 설정이 커넥션에 남는 방식도 다르다.
|
|
|
|
## 세 단계
|
|
|
|
```text
|
|
호출 예산 상위 호출자가 기다릴 수 있는 시간
|
|
↓ 획득 봉투를 뺀다
|
|
트랜잭션 타임아웃 스프링 트랜잭션 템플릿에 설정
|
|
↓ 여유를 남긴다
|
|
DB 로컬 타임아웃 데이터베이스가 스스로 끊는 시간
|
|
```
|
|
|
|
각 단계가 앞 단계보다 작다. 마지막이 가장 작아야 데이터베이스가 먼저 끊고, 그래야 애플리케이션이 그 실패를 분류할 기회를 갖는다.
|
|
|
|
:::note
|
|
|
|
순서가 반대가 되면 애플리케이션이 먼저 타임아웃되고 데이터베이스는 계속 돈다. 그 쿼리는 아무도 결과를 받지 않은 채 자원을 쓴다.
|
|
|
|
:::
|
|
|
|
## 템플릿을 미리 만드는 것과의 관계
|
|
|
|
트랜잭션 템플릿은 모드마다 미리 만들어져 있고 전부 같은 격리 수준에 고정되어 있다. 템플릿을 호출마다 고쳐 쓰면 경합이 생기기 때문이다.
|
|
|
|
그래서 데드라인은 템플릿의 필드가 아니라 실행 시점에 계산되어 전달된다.
|
|
|
|
<!-- body:end -->
|