본문 바로가기
C.W.K.
Stream
Lesson 10 of 10 · published

웹 앱의 트랜잭션 경계

~14 min · transactions, patterns

Level 0스키마 새싹
0 XP0/86 lessons0/10 achievements
0/120 XP to next level120 XP to go0% complete

트랜잭션은 정합성의 단위야

웹 요청의 데이터베이스 변경은 함께 확정되어야 하는 범위만 한 트랜잭션으로 묶어. 오래 열린 트랜잭션은 잠금을 붙잡고 죽은 행 버전을 쌓으며 VACUUM을 방해해.

운영 장애를 막는 5가지 규칙

  1. 가능한 늦게 열고 빨리 확정해. 열린 동안 외부 HTTP 호출을 하지 마.
  2. 사용자가 폼을 채우는 동안 트랜잭션을 잡아 두지 마.
  3. 구체적인 이유가 없다면 READ COMMITTED에서 시작해.
  4. REPEATABLE READ나 SERIALIZABLE에서는 직렬화 실패를 backoff와 함께 재시도해.
  5. 여러 왕복에 걸친 SELECT FOR UPDATE를 피하고 가능하면 한 문장이나 저장 프로시저로 줄여.

프레임워크의 경계 도구를 써

요청을 트랜잭션으로 감싸는 공식 방식을 이용하고, 직접 만든 BEGIN/COMMIT 경로가 연결을 누수하지 않는지 확인해. 이메일과 업로드 같은 외부 작업은 트랜잭션을 열기 전이나 확정한 뒤에 실행해.

Code

FastAPI와 SQLAlchemy의 트랜잭션 틀·python
from fastapi import Depends
from sqlalchemy.orm import Session

def get_db():
    db = SessionLocal()
    try:
        yield db
        db.commit()
    except Exception:
        db.rollback()
        raise
    finally:
        db.close()

@app.post("/orders")
def create_order(payload: OrderIn, db: Session = Depends(get_db)):
    order = Order(...)
    db.add(order)
    db.flush()  # 자동 생성된 id
    for item in payload.items:
        db.add(OrderItem(order_id=order.id, ...))
    return order
이거 하지 마·python
# 외부 HTTP 호출 가로질러 트랜잭션 잡음 — deadlock 시한폭탄
def risky(db):
    with db.transaction():
        order = db.execute("INSERT INTO orders ... RETURNING id").fetchone()
        # ❌ 열린 트랜잭션 안 외부 API 호출
        ext = httpx.post("https://payments.example.com/charge", json={...})
        db.execute("UPDATE orders SET payment_id = %s WHERE id = %s",
                   (ext.json()["id"], order["id"]))

External links

Exercise

프로젝트의 경로 하나를 골라 트랜잭션이 어디서 시작하고 언제 COMMIT하며 그 안에서 무엇을 실행하는지 점검해. 외부 네트워크 호출이 트랜잭션 안에 있다면 밖으로 옮기도록 다시 설계해.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.