사전 SQLAlchemy
구현체

SQLAlchemy

gabury1고친 사람 github-actions[bot]

SQLAlchemy 는 파이썬으로 짠 프로그램이 데이터베이스와 이야기하게 해 주는 라이브러리입니다. 파이썬 쪽에서 적은 조회와 저장을 질의문으로 바꿔 보냅니다. 돌아온 행은 다시 파이썬 객체로 돌려줍니다. 데이터베이스로 가는 연결을 미리 열어 두고 돌려 쓰는 일도 함께 맡습니다.

쉽고 빠른 이해

SQLAlchemy 는 파이썬 코드와 데이터베이스 사이에 서서 양쪽 말을 옮겨 주는 라이브러리입니다. session.get(User, 1) 이라고 적으면 사용자 한 행을 꺼내는 질의문을 만들어 보냅니다. 그 결과는 User 객체 하나로 돌려줍니다.

이게 없으면 질의문을 문자열로 손수 짜야 합니다. 돌아온 튜플도 필드 하나씩 꺼내 객체에 옮겨 담아야 합니다. 테이블이 스무 개면 그 옮겨 담는 코드가 스무 벌 생깁니다.

어떻게 도나:

  1. 데이터베이스 한 곳마다 엔진을 하나 만들어 둡니다. 연결을 모아 두는 풀이 엔진 안에 있습니다
  2. 객체를 다루는 동안에는 세션이 바뀐 내용을 메모리에 모아 둡니다
  3. 커밋할 때 모아 둔 것을 질의문으로 바꿔 한꺼번에 내보냅니다

대가도 있습니다. 질의문이 눈에 안 보여서, 점 하나 찍었을 뿐인데 질의가 수백 번 나가는 일이 생깁니다. 무엇이 나갔는지 보려면 로그를 켜서 따로 봐야 합니다.

상세

SQLAlchemy 는 파이썬 프로그램과 데이터베이스 사이에 서는 라이브러리입니다. 프로그램이 SQL(Structured Query Language, 구조화 질의 언어) 문자열을 손수 만들어 드라이버에 넘기는 대신, SQLAlchemy 가 그 일을 대신합니다. 드라이버는 특정 데이터베이스와 통신하는 파이썬 패키지입니다. SQLAlchemy 는 그 위에 얹힙니다.

계층이 둘입니다. 아래 계층은 Core 라고 부릅니다. 테이블과 컬럼을 파이썬 값으로 다룹니다. 그 값을 이어 붙여 질의문을 조립해 줍니다. 위 계층은 ORM(Object-Relational Mapping, 객체 관계 매핑)입니다.

ORM 은 테이블 하나에 파이썬 클래스 하나를 짝지어 둡니다. 그 테이블의 한 행이 그 클래스의 객체 하나가 됩니다. 아래 예제에 나오는 User 가 그렇게 짝지어 둔 클래스입니다. 클래스와 테이블을 이렇게 짝지어 선언하는 것을 선언적 매핑이라고 부릅니다.

ORM 이 필요한 까닭은 양쪽의 모양이 다르기 때문입니다. 프로그램 안의 데이터는 서로를 가리키는 객체 덩어리입니다. 데이터베이스 안의 데이터는 평평한 표입니다. 이 둘을 오가는 코드를 직접 짜면 테이블마다 같은 모양이 되풀이됩니다. 컬럼을 하나 더할 때마다 여러 곳을 같이 고쳐야 합니다.

질의 하나가 나갈 때 프로그램과 데이터베이스 사이에는 네 계층이 섭니다. 질의는 세션에서 출발해 엔진이 쥔 풀의 연결을 타고 드라이버로 나갑니다.

계층 누가 갖나
세션 SQLAlchemy
엔진 SQLAlchemy
커넥션 풀 엔진 안
드라이버 바깥 패키지

이 네 계층과 앞의 두 계층은 같은 것을 다른 축으로 가른 것입니다. 세션은 ORM 계층에 있습니다. 엔진과 그 안의 풀은 Core 계층에 있습니다. 드라이버는 둘 다의 바깥입니다.

flowchart TD
    A["애플리케이션 코드"]
    subgraph S["SQLAlchemy"]
        subgraph O["ORM 계층"]
            B["세션"]
        end
        subgraph C["Core 계층"]
            subgraph E["엔진"]
                P["커넥션 풀"]
            end
        end
    end
    A --> B
    B --> P
    P --> V["데이터베이스 드라이버"]
    V --> W["데이터베이스"]

드라이버는 데이터베이스 종류마다 따로 있습니다. SQLAlchemy 가 만들어 갖는 것이 아니라 바깥에서 가져다 쓰는 패키지입니다. 어느 드라이버를 쓸지는 엔진에 적은 접속 주소가 정합니다. 질의문을 실제로 보내고 결과를 받아 오는 것은 이 드라이버입니다.

엔진과 커넥션 풀

엔진은 데이터베이스 한 곳을 가리키려고 두는 물건입니다. 접속 주소와 쓸 드라이버를 쥡니다. 커넥션 풀도 엔진 안에 있습니다. 프로그램 하나에 엔진 하나를 만들어 두고 계속 씁니다.

커넥션 풀은 열어 둔 연결을 모아 놓고 돌려 쓰는 방식입니다. 연결을 새로 여는 일에는 통신 규약을 맞추고 사용자를 확인하는 왕복이 붙습니다. 질의 하나마다 이 왕복을 치르면 질의 자체보다 준비에 시간이 더 듭니다.

그래서 SQLAlchemy 는 질의가 끝나도 연결을 닫지 않습니다. 풀에 돌려놓고 다음 질의가 다시 꺼내 씁니다. 풀이 비었고 더 열 수 있는 상한도 찼으면, 연결을 달라고 한 쪽은 누군가 반납할 때까지 기다립니다.

풀에서 꺼낸 연결에는 앞사람의 흔적이 남아 있을 수 있습니다. 끝내지 않은 트랜잭션 같은 것입니다. SQLAlchemy 는 연결을 반납받는 시점에 이것을 되돌리는 동작을 걸어 둡니다.

세션과 작업 단위

세션은 ORM 계층에서 한 묶음의 작업을 담는 그릇입니다. 세션이 읽어 온 객체와 세션에 새로 넣은 객체를 메모리에 쥐고 있습니다. 그러다 정해진 때에 한꺼번에 데이터베이스로 내보냅니다.

객체의 값을 바꿔도 질의가 바로 나가지 않는 이유가 이것입니다. 세션은 어느 객체의 어느 값이 바뀌었는지를 기억해 두었다가 커밋 할 때 필요한 문장만 만들어 보냅니다. 이렇게 모았다가 한 번에 내보내는 방식을 작업 단위라고 부릅니다.

Python
user = session.get(User, 1)  # 조회 질의 1회
user.name = "은수"           # 안 나간다
session.commit()             # 갱신 질의 1회

코드는 세 줄입니다. 데이터베이스로 무언가 나가는 것은 첫 줄과 마지막 줄뿐입니다. 가운데 줄은 세션의 메모리만 건드립니다.

세션은 읽어 온 객체를 기본 키로 기억해 둡니다. 같은 기본 키를 이 세션에서 다시 읽으면 데이터베이스에 또 묻지 않고 쥐고 있던 객체를 돌려줍니다. 그래서 한 세션 안에서는 같은 행이 언제나 같은 객체 하나로 보입니다.

지연 로딩과 늘어나는 질의

ORM 은 테이블 사이의 관계를 파이썬 속성으로 보여줍니다. 글 목록을 따로 조회하지 않고 user.posts 처럼 점 하나로 꺼내 쓸 수 있습니다.

기본 동작은 지연 로딩입니다. 그 속성을 건드리는 때에야 질의를 보낸다는 뜻입니다. 안 쓸 관계까지 미리 읽어 오지 않으니 처음 조회가 짧게 끝납니다.

대가는 반복문에서 드러납니다. 사용자 백 명을 읽고 각각의 글 목록을 꺼내면 조회 한 번에 백 번이 더 붙습니다. 이것이 N+1 문제입니다. SQLAlchemy 에는 관계를 처음부터 함께 읽어 오는 방식도 있습니다. 그 관계를 쓸 것을 안다면 조회할 때 같이 가져오라고 적습니다.

asyncio 와 그린렛

파이썬의 asyncio는 한 스레드 안에서 여러 일을 번갈아 돌리는 방식입니다. 번갈아 돌릴 차례를 정하는 것이 이벤트 루프입니다. 기다리는 동안 다른 일로 넘어가려면 기다리는 지점마다 await 이 붙어 있어야 합니다.

SQLAlchemy 의 본체는 await 없이 도는 코드로 짜여 있습니다. 쌓아 온 ORM 동작을 비동기 판으로 다시 쓰면 같은 로직이 두 벌이 됩니다.

그래서 SQLAlchemy 는 비동기 확장을 따로 둡니다. 확장은 그린렛으로 두 세계를 잇습니다. 그린렛은 스레드가 아니라 한 스레드 안에서 서로 넘겨 가며 도는 실행 흐름입니다.

비동기 확장은 동기처럼 생긴 코드를 그린렛 안에서 돌립니다. 드라이버가 통신을 기다려야 하는 때가 되면 이벤트 루프로 흐름을 넘깁니다. 루프가 응답을 받아 오면 다시 그린렛으로 돌아옵니다.

sequenceDiagram
    participant 루프 as 이벤트 루프
    participant 그린렛
    participant 드라이버
    루프->>그린렛: 동기처럼 생긴 코드를 시작
    그린렛->>드라이버: 질의를 보낸다
    드라이버-->>그린렛: 응답을 기다려야 한다
    그린렛-->>루프: 흐름을 넘긴다
    Note over 루프: 그동안 다른 일을 돌린다
    루프->>그린렛: 응답이 왔다. 이어서 돈다

그래서 비동기로 쓰더라도 설치 목록에 그린렛이 따라 들어옵니다. 쓰는 쪽 코드는 await 을 붙여 부릅니다. 넘기고 돌아오는 일은 확장이 감춥니다.

맡지 않는 일

SQLAlchemy 는 데이터베이스가 아닙니다. 연결을 쥐고 질의문을 만들어 보낼 뿐입니다. 데이터를 저장하고 인덱스로 찾는 일은 데이터베이스가 합니다.

테이블 구조를 바꾸는 일도 받지 않습니다. 컬럼을 더하거나 이름을 바꾸는 스키마 변경은 Alembic 이라는 별도 도구가 맡습니다.

질의를 대신 다듬어 주지도 않습니다. 어떤 문장이 나갈지는 사람이 적은 대로 정해집니다. 나간 문장을 데이터베이스가 어떤 계획으로 처리할지는 데이터베이스가 정합니다.

그래서 쓰는 쪽도 갈립니다.

어떤 일 무엇을 쓰나
테이블 몇 개를 만들고 고치고 지우기 ORM
문장을 다듬어야 하는 집계 조회 Core
그보다 더 다듬어야 하면 질의문 직접

두 계층은 한 프로그램 안에서 섞어 쓸 수 있습니다.

관련 항목

SQLAlchemy 가 사이에 서는 양쪽

파이썬 · 데이터베이스 · SQL · PostgreSQL · MySQL · SQLite

SQLAlchemy 를 이루는 구성 요소

엔진 · 세션 · 커넥션 풀 · 작업 단위 · 아이덴티티 맵 · 지연 로딩 · 즉시 로딩 · 선언적 매핑

SQLAlchemy 가 비동기로 돌 때 기대는 실행 방식

그린렛 · asyncio · 이벤트 루프 · 코루틴 · 논블로킹 입출력

SQLAlchemy 를 쓰다 만나는 오류와 장애

N+1 문제 · 커넥션 누수 · 풀 고갈 · 끊긴 커넥션 · 데드락

SQLAlchemy 와 같은 역할을 두고 겨루는 라이브러리

Django ORM · Hibernate · Prisma · MyBatis · Peewee · JPA

SQLAlchemy 가 맡지 않는 스키마 변경을 받는 도구

Alembic · 마이그레이션 · 스키마 · DDL

SQLAlchemy 가 기대는 데이터베이스 개념

트랜잭션 · 커밋 · 롤백 · 격리 수준 · 기본 키 · 외래 키 · 인덱스

다른 이름: SQL알케미 · 에스큐엘알케미