내게 가장 익숙한 언어는 Python이고, 백엔드 엔지니어인 내게 가장 익숙한 웹 프레임워크는 Flask다. 처음 백엔드를 시작하고 썼던 게 JavaVert.x였는데, Flask는 비교적 훨씬 간단하게 웹 API 서버를 개발할 수 있었다. Flask를 처음 마주쳤을 땐 소프트웨어 쪽으론 아는 게 정말 없었기에, '간단함'은 Flask라는 프레임워크에 빠져들기에 충분한 이유였던 것 같다.

사실 내가 처음 Flask를 시작했을 땐 Flask의 메이저 버전이 0대였고, 0.11, 0.12같은 걸 stable 버전으로 사용했었다. 동시에 Flask의 인기가 꽤 많아지기 시작했고, 2018년 4월 27일 새벽에 Flask 1.0이 릴리즈되었다. Flask는 마이크로 프레임워크기에, 정석적으로 정해져 있는 틀이 딱히 없어서 구조나 모범 사례들에 대해 자주 고민하게 됐었다. 근데 지금 생각해 보면, 무언가에 대해 그토록 깊게 고민해봤던 적이 없던 것 같다. 그래서 Flask는 내게 정말 고마운 프레임워크다.

Flask

마이크로 프레임워크, 멀티 스레드 형태의 요청 처리 방식을 사용하는 웹 프레임워크이다. Python으로 개발되었으며, WSGI(CGI의 Python 구현체) 툴킷으로 Werkzeug를, 템플릿 엔진으로 Jinja2를 사용한다. Flask는 The Pallets Projects라는 팀에서 개발하고 있는데, Flask가 의존하고 있는 Werkzeug와 Jinja2 또한 동일한 팀에서 개발되고 있다.(The Pallets Project는 clickitsdangerous라는 라이브러리로도 유명하다.)

Flask는 비슷한 느낌의 이름을 가진 bottle이라는 마이크로 프레임워크를 겨냥하여 만들어졌고, 따라서 API도 비슷한 점이 많다.

마이크로 프레임워크 답게 간단한 웹 서버REST API 서버를 빠르게 개발하기 좋고, 나만의 어플리케이션 구조를 만들어나가는 재미도 쏠쏠하다. AWS Lambda + AWS API Gateway 위에 서버리스 어플리케이션의 배포를 돕는 zappa도 Django, Pyramid, Bottle과 함께 Flask를 지원한다. 풀 스택 프레임워크인 Django와 함께, 가장 인기가 많은 파이썬 웹 프레임워크가 아닐까 싶다.

다양한 스타일의 라우팅 방식 지원, context와 설정 데이터 관리, 테스트 등에서 프레임워크가 정말 많은 부분에 대해 깊게 고민해 주었다는 것을 느낄 수 있고, 공식 문서와 코드가 매우 잘 정리되어 있다. '파이썬을 여행하는 히치하이커를 위한 안내서'라는 책에서는, '훌륭한 코드를 읽기' 챕터에서 Flask를 예로 들어 설명하고 있을 정도다. Lightweight, Minimal, Open source, Documentation, Easy to use, Well designed 정도가 Flask를 사용하는 이유다.

GET '/' -> 'Hello World'

Flask를 이용해, 3000번 포트에서 동작하며 '/'에 대해 GET 요청을 하면 'Hello World'를 반환하는 간단한 웹 서버를 만들어 보도록 하자. 먼저 pip를 통해 Flask를 설치해야 한다. 가상 환경이 필요하다면 별도로 준비하도록 하자.

$ pip3 install flask

setup.py에 의해 Flask와 함께 의존성 라이브러리인 Werkzeug, Jinja2, itsdangerous, click이 동시에 설치된다. 그리고 아래는 위에서 이야기한 요구사항을 맞춘 Flask 어플리케이션이다.

1번 라인에서는 flask 패키지에서 Flask 클래스를 import했다. __init__.py가 import를 중계해 주기에, 실제로 잡히는 경로는 flask/app.py 모듈의 Flask 클래스다.

3번 라인에서는 Flask 객체를 생성하고 있다. import_name이라는 인자에 Flask 어플리케이션 패키지의 이름을 전달하며, 일반적으로 __name__을 사용한다.

5~7번 라인은 API의 정의에 대한 부분이다. Flask에서 지원하는 라우팅 방식 중 가장 기본형은, 위와 같이 route 데코레이터함수를 이용하는 것이다. 첫 번째 위치 인자로 전달되는 문자열은 해당 API의 URL rule이 되고, 키워드 인자로 HTTP 메소드 등 추가적인 옵션을 붙일 수 있다. 별도로 메소드를 명시하지 않으면 GET에 대해서만 동작하며 다른 메소드로 접근 시 405 Method Not Allowed가 response된다.

Flask에선 위처럼 API의 로직을 처리하는 함수를 view function이라고 부른다. view function의 이름은 식별자로 사용되기에 unique해야 하며, return 문에서 반환된 값은 Flask에서 Response라는 클래스의 인스턴스로 wrapping하여 응답으로 전해진다. 위의 경우, 'Hello World'라는 문자열을 return했으므로 response data는 'Hello World'가 되고, Flask에서 content type과 status code를 각각 기본값인 text/plain, 200으로 설정하여 response한다.

10번 라인은 Flask 어플리케이션을 실행하는 구문이다. 여기에도 debug 모드 등 몇가지 옵션을 추가할 수 있다.


결론적으로, 우리는 10줄 남짓의 코드를 통해 '/'에 대한 GET 요청을 처리할 수 있는 HTTP 서버를 구현했다. python 또는 python3 명령을 통해 모듈을 실행하고, localhost:3000에 접속하면 'Hello World'가 보일 것이다.

Django와 같은 풀 스택 프레임워크에 비해, 준비 과정이 간단하고 '파일 하나'로 천천히 시작할 수 있다. 사람마다 다르겠지만, 굳이 지금 필요없는 건 보이지조차 않아서, 내겐 차근차근 배워가기에 정말 좋았다.

'Python 계열 > Flask' 카테고리의 다른 글

POST '/' -> 'Hello World'  (0) 2018.09.20

SELECT 절에는 statement가 들어갈 수 있다. 단지 테이블의 특정 컬럼만이 아니라, 어떤 리터럴 값이나 을 포함할 수 있다는 것이다. 아래는 이미 우리에게 익숙한 SELECT절의 예다.

그리고 SELECT는 아래처럼 사용할 수도 있다.

한 row에, 'Hello', 20, 9라는 결과가 출력될 것이다.

응용

스키마가 동일한 두 테이블을 결합하기 위해, UNION을 사용하곤 한다.

한 row에 대해서, 레코드가 어느 테이블 소속인지를 알고 싶은 경우, literal select를 사용한다.

row마다, 레코드의 소속을 나타내는 'app' 또는 'web'이 origin이라는 이름으로 항상 포함될 것이다.

'데이터베이스 > SQL' 카테고리의 다른 글

집계 함수와 조건식을 함께 사용하기(SELECT FROM SELECT)  (0) 2019.02.12
GROUP BY  (0) 2019.02.12

ProxySQL은 MySQL 호환 데이터베이스(MySQL, Persona, MariaDB 등)를 backend로 두고, 외부의 connection을 받아 쿼리를 중계해 주는 쿼리 라우팅(Query routing)을 핵심으로 하는 MySQL Proxy Database다. 설정되어 있는 backend들에 주기적으로 health check를 보내고, 이들 중 하나가 죽으면 auto failover해주는 등, 로드 밸런싱과 유사하다. 이 외에도 쿼리 캐싱, 다운타임 없는 설정 변경 등을 지원한다. Amazon RDS 등에서 이야기하는 Master/Slave 구조로 인해 데이터베이스가 많은 수로 read replication되어 있는 상태에서 이들에게 효율적으로 커넥션을 분배해 주거나, 외부에서 많은 양의 커넥션이 들어오는 경우 효과적으로 connection을 pooling하기 위해 자주 사용된다.

구성

ProxySQL은 Runtime, Memory, Disk, Config file의 4가지 계층으로 이루어져 있다. 어느 곳에서 영감을 받았는진 모르겠지만, 처음 봤을 땐 꽤 생소하다.

Runtime

ProxySQL로 들어오는 요청을 처리하는 스레드의 메모리 내 데이터 구조를 표현한다. 시스템 수준에서 디버깅을 하는 정도가 아니라면 비교적 신경쓸 일이 적다.

Memory

main이라고도 부른다. MySQL 호환 인터페이스를 사용할 수 있는, in-memory로 동작하는 데이터베이스를 나타낸다. 쉽게 말하면 여기서 설정 값이 상주하고 있고, MySQL 클라이언트로 쿼리 가능하다는 의미다. Memory 내에는 대표적으로 아래 4개의 테이블이 존재한다.

  • mysql_servers : ProxySQL이 실제로 중계할 서버들의 목록을 관리한다. 중계 대상 서버들을 백엔드 서버라고도 부른다.
  • mysql_users : ProxySQL이 관리하는 사용자의 자격 증명 목록이다. '얘는 이 데이터베이스에 read, 얘는 이 데이터베이스에 read/write 권한, ..' 같은 설정들을 다룬다.
  • mysql_query_rules : 백엔드 서버로 트래픽이 라우팅될 때 평가되는 쿼리 규칙의 목록이다. 이 테이블을 사용하면, 'LIMIT 없는 쿼리는 사용하지 못한다' 와 같은 동작이 가능하다.
  • global_variables : 프록시가 사용하도록 구성된 전역 변수의 목록이며, MySQL connection timeout이나, connect retry 횟수와 같은 메타데이터들을 다룬다.

Disk

ProxySQL 자체 데이터베이스가 실제로 동작하는 곳의 이름이 memory인 이유는, 실제로 in-memory 형태의 SQLite3 데이터베이스가 상주하고 있기 때문이다. 따라서 영속성이 보장되지 않기 때문에, memory 내 구성을 유지하기 위한 계층으로서 Disk가 사용된다.

Config file

Memory에서 돌아가고 있는 데이터베이스에 설정 값들을 dump시킬 수 있는, 전형적인 설정 파일이다.

production level의 MySQL Proxy로 ProxySQL을 가장 많이 사용하고 있는 것 같다. 설치 과정은 ProxySQL Github의 wiki에 잘 나와 있다. 백엔드 인프라의 고도화 단계엔 정말 들어가는 게 많은 것 같다.

'배경지식' 카테고리의 다른 글

직렬화와 JSON  (0) 2019.02.12
ORM  (0) 2019.02.12
HTTP 메소드  (0) 2019.02.12
StatsD  (0) 2019.02.11
HTTP 헤더  (0) 2018.11.02

문서화도 다 끝났으니 로직을 코드에 옮기기만 하면 된다. 그러나 아직 어떤 언어를 쓸지/의존성을 어떻게 관리할지/어떤 데이터베이스를 어디서 운영할지와 같은 것들이 정해지지 않아서 바로 개발에 착수하기는 어렵다. 이번에는 어플리케이션 기술스택(프로그래밍 언어와 프레임워크)을 결정하고, /GET 요청 시 'Hello World'text/plain으로 반환하는 서버를 작성해 보자.

도입 이유

웹 어플리케이션 서버를 작성할 수 있는 언어는 수없이 많고, 프레임워크도 그렇다. 그만큼 조직의 현재 상황과 미래, 서비스의 형태, 요구사항, 일정 등에 맞춰서 매우 신중하게 결정해야 한다. '요즘 다 파이썬 쓰니까 파이썬', 'Node.js는 한글 문서가 잘 돼있으니까', '장고가 쓰는 사람도 많고 쉬우니까'같은 트렌드 차원의 이유로 어플리케이션 기술스택을 가볍게 결정하고 나면, 조직에게 매우 큰 기술 부채가 되어 돌아올 수도 있다. 기술 선택은 팀과 비즈니스를 고려해야 한다. 오늘은 두 의사결정 모두 하나의 요구사항 집합으로 정리된다.

배경과 요구사항

  • 조직에게 익숙하거나, 러닝커브가 감당할 수 있는 수준인가? - 생산성이 보장되며 유지보수에 큰 문제가 없는가?
  • 조직이 납득할 수 있는가?
  • 너무 마이너한 기술스택은 아닌가? - 라이브러리나 예제는 결국 그 언어/프레임워크의 사용자가 만들기 마련인데, 인기가 없으면 커뮤니티의 사이즈가 작고, 직접 만들어야 하는 도구의 범위가 넓어질 수 있다.(memcached 클라이언트, StatsD 클라이언트, PostgreSQL ORM 라이브러리 등등) 그리고 커뮤니티 파워가 약하면 먼 미래를 보장하기 어려울 수 있다.
  • 안정적인 응답 속도에 rps(requests per second)를 잘 뽑아주거나, TTL/LRU Cache, 경량 스레드, 비동기 프로그래밍 지원처럼 성능 최적화를 위한 도구가 잘 준비되어 있는가? - 비용 최적화를 위해 매우 중요하다.

쭉 써놓고 나니, 대충 인기 많은 스택이라면 이 조건에 대부분 부합하지 않을까 싶다. 데이터 티어나 모니터링 티어의 인프라를 결정할 땐 조금 더 자유도 높게 선택지를 둘 수 있을텐데, 이번 의사결정은 어쩔 수 없이 인기를 좀 따르게 될 것 같으니 양해 바란다.

의사결정 - 프로그래밍 언어

선택지

WAS 개발에 있어서 주류 언어로 꼽히고 + 필자(조직)가 조금이나마 경험해본 언어들을 선택지로 두자.

  • Python
  • Scala
  • Java
  • Go
  • Node.js

의사결정

Python을 선택하겠다. 그 이유는,

  • 조직(필자)에게 가장 익숙하다.
  • 커뮤니티 파워가 강하다.
  • 언어가 강력하다. 대부분 언어들이 어디 하나씩은 맘에 안드는 부분이 있는데, Python은 이런저런 면에서 평타 이상 쳐준다.
  • unpacking, comprehension, 비교 연산자 chaining과 같은 syntactic sugar나 arrow같은 drop-in replacement 라이브러리들이 생산성을 잘 커버해 준다.
  • 라이브러리 풀이 꽤 크고, 의존성 관리와 가상 환경 지원도 꽤 쓸만하다.
  • 공식 코드 컨벤션 가이드라인PEP8 덕분에 코드 퀄리티 유지도 괜찮게 할 수 있다.
  • Scala액터 모델 기반의 짱 좋은 동시성을 가지고 있고, 동시성 모델 자체가 Immutability에 기반되어 있어서 동기화에 관한 삽질이 비교적 적다. 그러나 조직 내에 Scala를 잘 다루는 개발자가 없어서 생산성을 보장할 수 없다.
  • Java는 비즈니스 관점에서 인력풀이 풍부하고 레퍼런스가 많지만, 필자의 기준으론 JSON 리터럴이 없어서 꽤 불편하다. Go의 [string]interface 타입 map이나, Scala의 List/Map 타입, Kotlin의 hashMapOf 함수처럼 JSON 리터럴을 우회할만한 방법도 딱히 없다. Map 객체를 만들어 put문을 발라두거나 별도로 VO(Value Object)를 정의하고 JSON string을 VO 객체로 역직렬화/VO로 만든 객체를 JSON string으로 직렬화하는 식으로 사용한다. VO를 사용하는 방식은 꽤 직관적이지만, WAS의 로직과 데이터 모델이 복잡한 게 아니기 때문에 JSON 리터럴 표현식이 언어 차원에서 지원되는 게 좋을 듯 싶었다. 게다가 나중에 VO를 쓰는 방식으로 스타일이 바뀌더라도, 파이썬이라고 못하는 것도 아니고 말이다.
  • Go는 웬만하면 성능 정말 잘 뽑아주고, 사용자 층도 세계적으로 꽤 많아지는 추세지만, 이게 생각보다 생산성이 좋지 않고 의존성 관리가 마음아프다. 그래도 트래픽 처리 비용을 낮추려고 시도하게 된다면, 고성능 파이썬 웹 프레임워크들과 Go를 벤치마킹해 볼 것 같다. 내가 Go를 많이 안 해봐서 삽질하는 게 아닐까 하는 믿음이 있다(아직은).
  • Node.js는 JavaScript 런타임인데, 자바스크립트라는 언어가 생각보다 잘 쓰기 정말 어렵고, 굳이 러닝커브를 감수하고 Node.js를 선택하는 것에 당장의 메리트가 별로 없다.

결론은 조직에게 익숙한 Python을 선택하게 됐다. 물론 조직에게 아무리 익숙한 기술이더라도, 그 기술이 의사결정의 기준에 부합하지 못한다면 보류시키는 게 맞다.

만약 백엔드 개발자가 여럿 있고 각각에게 익숙한 기술스택끼리의 교집합이 없거나 적은 상황이라면, 서비스와 개발 조직의 요구사항(예산, 처리량, 개발 일정 등)을 기반으로 서로를 설득하는 시간을 갖자.

의사결정 - 웹 프레임워크

선택지

써볼만한 프레임워크는 이정도가 있다.

의사결정

Sanic을 사용하겠다. 그 이유는,

  • Flask마이크로 프레임워크이기에, 자유도가 매우 높다. 원하는 설계 방향대로 프레임워크를 유연하게 써먹을 수 있다. 그러나 멀티스레드 모델의 원천적인 한계(컨텍스트 스위칭) + Python의 GIL 구조로 인해 여러 스레드가 시분할해서 동작하기 때문인지, 성능이 조금 아쉽기도 하고 요청 객체가 의존성 주입 형태가 아니라 global 객체로 제공되어서 자칫하면 안티패턴 코드를 작성하게 된다. Flask를 가장 많이 써봤지만, 얘를 열심히 튜닝하기 전에 대체제를 찾아보고 싶었다.
  • Sanicuvloop: Blazing fast Python networking이라는 글에서 영감을 받아, Flask-like한 APIasync 개념을 넣고 uvloop로 asyncio 이벤트 루프를 튜닝한 비동기 웹 프레임워크다. 벤치마크 상황에 따라 다르지만, 동일 조건에서 웬만하면 Sanic이 Flask보다 2~4배정도 처리량이 많다. 그리고 요청/응답 객체가 의존성 주입 형태로 제공되고, 미들웨어 개념이 잘 잡혀 있다.
  • Vibora도 Flask-like API에 뭔가 이것저것 붙여서 퍼포먼스를 높이는, Sanic과 비슷한 컨셉을 가지고 있다. 그리고 실제로 정말 빠르다(Flask를 기준으로, Sanic이 2~4배, Vibora가 5~6배 정도). 그러나 레퍼런스가 너무 없다. Sanic은 공식 문서라도 잘 작성되어 있는데, Vibora의 문서는 '이게 다인가?' 싶다.
  • Japronto는 내가 아는 파이썬 웹 프레임워크 중 rps가 가장 높다(웹 프레임워크 전체에 대한 벤치마크에서도 거의 선두권). 그런데 뭐 프레임워크가 딱히 제공해주는 게 별로 없다. 라우팅 메소드 하나랑 요청 객체, 응답 헬퍼 몇 개 정도. 2017년 2월에 미들웨어 관련 이슈가 올라왔지만 '계획이 있다'고만 하고 아직도 open되어 있다. 그리고 Vibora보다 크게 빠르지도 않고, 사용 사례가 많지 않다.
  • Falcon은 사용 사례도 꽤 있고 + 성능도 Japronto, Vibora와 함께 파이썬 웹 프레임워크 중에서 선두권 + 문서도 잘 만들어져 있지만 프레임워크 디자인 자체가 꽤 새로운 느낌이라 러닝커브를 감당하기 힘들 수 있을 듯 싶었다.
  • aiohttp는 생각보다 퍼포먼스가 안나온다. Sanic이나 aiohttp나 async/await 기반인데, 굳이 aiohttp를 선택할 이유가 없다.
  • Tornado는 이름만 보면 엄청 빨라 보이는데, 파이썬의 주류 웹 프레임워크들 중에 퍼포먼스가 가장 안나온다. 퍼포먼스를 포기하고서라도 쓸만한 이유도 없다. 생각보다 커뮤니티 파워도 약하다.
  • DjangoDjangoRestFramework가 있긴 하지만, Django에 익숙한 조직이 아니므로 제외했다. DjangoRestFramework를 쓰는 후배한테 물어봤는데, 이것저것 챙겨주는 게 많아서 익숙해지기 시작하면 생산성이 아주 괜찮다고 하니 구미가 당긴다면 배워봐도 좋을 것 같다.

작업 - Hello World 서버 작성

이슈를 생성하고, /GET 요청 시 'Hello World'text/plain으로 반환하는 서버를 작성해 보자. 서버 작성 직후의 스냅샷

지금까지

  • 버전 관리 시스템으로 Git을 사용하기 시작했다.
  • Git 웹호스팅 서비스로 GitHub를 사용하기 시작했다.
  • GitHub Issues와 Projects로 이슈 트래킹을 시작했다.
  • 개발 프로세스와 브랜칭 모델을 정립했다.
  • HTTP API 아키텍처 기반으로 API 스펙을 디자인하기로 했다.
  • JSON을 직렬화 포맷으로 결정했다.
  • Authorization 헤더로 인증 정보를 명시하기로 했다.
  • 인증 스키마에 JWT 기반의 Bearer를 사용하기로 했다.
  • API 스펙을 정의했다.
  • GitBook으로 API를 문서화했다.
  • Python + Sanic 조합으로 WAS를 개발하기로 했다.


집계 함수와 조건(LIMIT, WHERE 등)을 함께 사용하고 싶을 때가 있다. '소득'을 다루는 테이블에서 '상위 1만 명의 소득의 평균'을 집계하거나, '집값'을 다루는 테이블에서 '대전광역시에서 가장 비싼 원룸' 등을 집계하는 일이다. 일차원적으로 생각하면 '굉장히 쉽게 쿼리할 수 있지 않을까' 싶겠지만, 결과를 보면 갸우뚱하게 된다.

아무튼 결과는 나오지만, 사실 이는 원하는 결과가 아닐 가능성이 높다. 위 쿼리는 'tbl_people_incomes에서 AVG(income)을 계산한 결과를 income으로 내림차순 정렬한 후 LIMIT(10000)'을 의미한다. 이미 SELECT AVG(income) FROM tbl_people_incomes로 인해 만들어진 하나의 row를 정렬하고 잘라내는, 효과 없는 쿼리다. 집계 함수와 조건식을 함께 사용하려면, FROM의 뒷부분을 써먹어야 한다.

SELECT FROM SELECT절에는 nested(내부) SELECT를 소괄호로 감싸고 aliasing해줘야 한다. MySQL이 아닌 다른 SQL 엔진에서도 동일하게 적용되는지는 모르겠다.

'데이터베이스 > SQL' 카테고리의 다른 글

Literal SELECT  (0) 2019.02.12
GROUP BY  (0) 2019.02.12

이번엔 의존성 관리 도구를 결정하자. pip, npm, yarn, gem, maven, gradle 등과 같은 의존성 관리/빌드 도구를 써본 적 없다면 이해하기 어려울 수 있으니 의존성 관리 도구(Dependency Manager)라는 글을 읽어보자. 프로젝트 한두번 하면서 의존성 관리를 해 본 경험이 있다면 더욱 좋다.

도입 이유

소프트웨어 어플리케이션 개발에는 대부분 라이브러리가 필요하다. 언어 차원에서 지원되는(built-in) 것일 수도 있고, 우리가 사용하기로 한 Sanic처럼 외부 라이브러리 저장소에 의존하는 것일 수도 있다. 프로젝트가 어떤 외부 라이브러리를 사용하고 있는지를 별도로 관리하는 것의존성 관리라고 한다. 프로젝트가 사용하고 있는 외부 라이브러리들을 남들이 알도록 하는 것이 기본적인 목표인데, 이런 의존성 관리가 중요한 이유는 다음과 같다.

  • 먼저, 언어 차원에서 지원되는 라이브러리는 이미 다들 설치되어 있으니 따로 관리할 필요가 없다.
  • 백엔드 팀에서 함께하는 타 개발자의 입장에서 생각해 보자. 의존성이 제대로 설치되어 있지 않으면 어플리케이션을 실행해볼 수 없다. 혼자 개발하는 입장이라면 그냥 필요한 라이브러리들을 컴퓨터에 미리 설치해 두기만 하면 되겠지만, 협업이 힘들어지는 것이 문제다.
  • 오픈 소스로 관리되는 라이브러리의 소스 코드를 그냥 그대로 붙여넣거나, 따로 무슨무슨 라이브러리 쓴다고 문서화해둬도 되기야 하겠지만, 의존성의 의존성과 같은 재귀 의존 현상라이브러리 각각의 버전 업데이트 대응을 별도의 도구 없이 해결하기엔 너무 낭비다. 그 예로 Sanic은 httptools, websockets, multidict 등의 외부 의존성을 가지고 있다. 그리고 라이브러리 한 20개 쓰고 있는 상황에서, 버전 맞춰서 하나하나 설치하는 것도 귀찮은 일이다.
  • 배포 과정에서 필요하다. 예를 들어 테스트와 같은 배포 전처리를 위해 어플리케이션의 의존성들을 모두 다운로드해야 하고, 빌드/패키징 등과 같이 컴파일이 들어가는 과정에서 항상 필요하다.

이름은 거창하지만 의존성 관리 도구들이 자체적으로 많은 부분을 도와주기에, 우리에게 보여지는 겉모습 자체는 별 거 없다. 대부분 어떤 라이브러리의 무슨 버전을 쓸 지 목록화시킨 파일로서 의존성을 관리한다. 아래는 차례대로 Pythonrequirements.txt, JavaScriptyarn, RubyGemfile 예제다.

의사결정

선택지

  • pip와 requirements.txt
  • pipenv

의사결정

pipenv를 선택하겠다. 그 이유는,

  • Python은 일반적으로 가상 환경(virtual environment)을 통해 독립된 개발 환경을 구성하게 되는데, pipenv는 가상 환경 구성의존성 관리한번에 해결해주기 때문이다. JavaScript를 해봤다면, npm을 떠올리면 된다.

준비

pipenv란 무엇인가Pipenv으로 Python 프로젝트 관리하기라는 글로 pipenv에 입문하자.

작업

pipenv를 통해 프로젝트 디렉토리에 pipenv를 초기화하고, 가상 환경에 들어가 Sanic을 설치한 후 lock하자.

lock 직후의 스냅샷

boto3는 워낙 기능이 방대하다 보니 대부분의 경우에는 gist 등에서 예제를 찾아보게 되는데, boto3.resource를 사용하는 예제도 있고, boto3.client를 사용하는 예제도 있었다. 둘을 비교해 보자.

Client

  • low-level 인터페이스
  • service description에 의해 만들어짐
  • botocore 수준의 client를 공개(botocore는 AWS CLI와 boto3의 기초가 되는 라이브러리)
  • AWS API와 1:1 매핑됨
  • 메소드가 스네이크 케이스로 정의되어 있음

가끔가다 보면 botocore의 API를 찾아봐야 하는 경우가 생길 때도 있다. boto3.client는 AWS API와 1:1 매핑된다는 게 꽤 큰 메리트였던 것 같다.

Resource

  • high-level, 객체지향적 인터페이스
  • resource description에 의해 만들어짐
  • 식별자(identifier)와 속성(attribute)을 사용
  • 자원에 대한 조작 위주

'자원에 대한 조작 위주'라는 게 정확히 무슨 의미인지는 잘 모르겠지만, s3.Bcuket과 같은 구문을 의미하는 것이 아닐까 싶다. 당연하게도, boto3.resourceboto3.client를 wrapping한 high-level 인터페이스지만 boto3.client의 모든 기능을 wrapping하진 않으므로 때때로 boto3.clientboto3.resource.meta.client를 사용하여 작업해야 하는 경우가 생길 수 있다.

로직 수준의 직관성코드 수준의 직관성의 차이가 아닐까 싶다. 나는 boto3를 회사에서나 가끔 쓰는 정도였어서, 어떤 게 더 나았다거나 하는 주관적인 입장도 사실은 없다. 사용량 기준으로는 boto3.client가 더 많았던 것 같고, boto3를 써야 하는 작업이 있다면 boto3.client로 먼저 방법을 찾아보지 않을까 싶다. 그것보다 boto3는 네이밍 컨벤션이 참 특이한 것 같다.

이번엔 Compute Engine을 결정하자. 우리가 구현한 서버를 실행할 위치를 결정하는 것이다. 아래 3가지에 대해 의사결정이 필요하다.

  • 어떤 컴퓨팅 파워를 이용할 것인지(출처)
  • 만약 외부 서비스를 이용하기로 했다면, 어떤 서비스를 사용할 것인지
  • 해당 서비스에서 제공하는 컴퓨팅 엔진들 중 어떤 것을 사용할 것인지 등

오늘 하게 될 이야기는 AWS와 배경지식이 부족하면 조금 어려울 수도 있다. 읽다가 어렵다면 이고잉님의 AWS 강의로 흐름을 선회해도 좋을 것 같다. 컨테이너, dockerize, nginx, IAM 등등 이해되지 않는 부분이 있을 수 있을텐데, 큼지막한 흐름이 대충 이해가 된다면 머리아프게 다 이해하려 하지 말고 그냥 다음 내용으로 넘어가도 괜찮다. 이 컨텐츠는 텀을 길게 두고 두세번 정도 읽는 게 좋다고 생각한다. 일단 쭉 읽어보기 바란다.

도입 이유

필자는 초등학교 고학년(2012년) 때 마인크래프트 서버를 운영했었다. 내가 쓰던 데스크탑의 IPv4 주소에 oa.to 도메인을 묶어 두고, craftbukkit으로 서버를 직접 돌리는 방식이었다. 서버를 돌리기 위해 24시간동안 계속 컴퓨터를 켜두는 건 컴퓨터의 수명에 문제가 있지 않을까 걱정도 되고, 부모님 눈치도 보였기에 서버를 켜고 끄는 시간을 정해 공지해 두었었다. 3년만에 당시 운영하던 카페에 들어가보니, 켜지는 시간은 오전 8시 10분이었고 꺼지는 시간은 오후 11시였다. 등교하기 전에 켜두고, 잠들기 전에 껐나보다.

아무튼 결국 서비스 사용자의 트래픽을 처리하기 위해선 24시간 쉬지 않고 돌아가는 컴퓨팅 파워가 필요하다. 그게 PC던, 서버용 하드웨어를 구입해 구축한 홈서버던, 임대료를 지불해 호스팅받은 서버던, 클라우드 서비스던 말이다. 뭘 쓰던 '당장 되게 만드는 것'에는 문제가 없을 테지만, 여러 질문을 던져보며 가장 좋은 방향으로 의사결정 해보자. 백엔드 조직에 휴먼 리소스가 그리 많지 않은 상황이니, 되도록이면 서버의 컴퓨팅 자원같은 잡다한 부분을 직접 운영하는 일을 최대한 적게 만드려고 한다. 개발자는 어플리케이션 코드를 제공하기만 하면 되도록 말이다.

의사결정 - 컴퓨팅 파워의 출처

배경과 요구사항

  • 우리는 돈이 없다. 비용이 최소한으로 발생하는 방법을 선택해야 한다.
  • 그렇다고 불안정한 컴퓨팅 파워를 쓰진 말아야 한다.
  • 인프라 엔지니어링에 익숙하지 않기에, 인프라 관리에 있어서 휴먼 리소스가 되도록 적게 소비되어야 한다.

선택지

  • PC
  • 서버용 하드웨어를 구입해 홈서버 운영
  • 서버호스팅
  • 클라우드 플랫폼

의사결정

클라우드 플랫폼을 선택하겠다. 그 이유는,

  • PC나 별도의 하드웨어를 구입해 돌리는 것은 초기 투자 비용이 너무 비싸고, 인프라 엔지니어링을 잘할 수 있는 조직이 아니라면 운영도 어렵다. 클라우드 플랫폼은 쓰기 좋게 준비된 컴퓨팅 자원원하는 리소스 수준에 맞게, 원하는 만큼만 사용할 수 있다.
  • 클라우드 형태로 제공되는 인프라는 일반적으로 'pay as you go' 컨셉을 가지고 있다. 사용하는 만큼만 비용을 지불하는 것이다. 만약 하드웨어 스케일이 너무 크다거나 머신이 필요없어 졌다면, 그냥 버튼 클릭 몇 번으로 더 작은 하드웨어 사이즈를 가진 인스턴스 타입으로 변경하거나, 없애버릴 수 있다. 물리적인 서버가 남아서 '이걸 또 어디다 쓰지'하는 고민을 하지 않아도 된다.
  • 인프라 관리에 대해 수많은 부분이 이미 준비되어 있다. 오버워치같이 꽤 큰 조직들마저 클라우드 플랫폼을 사용하는 이유가 이건데, 서버 시스템 엔지니어 수 명이 비싼 서버실을 대신 운영해주는 수준이기 때문이다. 수 분만에 리눅스 서버를 띄우고, 서버를 다중화하고, 트래픽을 분산시키고, 로그 메트릭을 적재적소에 전송하고, 도메인을 달고, 스토리지를 자동으로 백업하고, 인바운드/아웃바운드 접근 제어를 수정하고, 트래픽 상태에 따라 서버를 늘리고 줄이는 등의 설정이 클릭 몇 번으로 가능하다. 물론 아무것도 모르는 사람이 마음껏 설정할 수 있을 정돈 아니지만, 뭐 하나 세팅하려고 라우터에 접속해서 커맨드 치고있는 것보다 훨씬 쉽고 빠르며 정확하다.
  • SLA(Service-Level Aggrement, 서비스 수준 협약서)를 통해 인프라의 가동 시간을 보장받는다. 부득이하게 SLA에 명시된 만큼의 서비스 수준을 보장받지 못한다면, 페이백이나 크레딧 등을 통해 돈으로 보상받을 수 있다. 백엔드 조직이 서버를 직접 운영한다고 치면, 클라우드 플랫폼 만큼의 높은 가동 시간을 보장할 수 없을 것이라고 판단했다.
  • 인프라의 신속한 배치, 쉬운 자동화, 유연한 용량(확장성), 실패에 대한 대비(신뢰성), 규모의 경제가 주는 비용적 혜택, 전 세계에 어플리케이션을 쉽게 배포하고, 높은 수준의 보안과 품질 표준이 준수되어 있는 등 클라우드를 사용함으로써 얻을 수 있는 것이 수없이 많다.

클라우드 플랫폼이 얼마나 편하며 비용 걱정이 줄어드는지는 한 번 써보면 알 것이다. 몇몇은 '클라우드에 너무 의존하지 말라'고들 하지만, 필자는 라우터 세팅같이 짜치는 일에 돈과 인생을 낭비하고 싶지 않다. 클라우드 컴퓨팅에 대해 조금 더 알고 싶다면 AWS의 '클라우드 컴퓨팅이란?' 문서를 읽어보자.

의사결정 - 클라우드 플랫폼 결정

배경과 요구사항

  • 우리는 돈이 없다. 무료로 제공되는 범위가 클수록 좋다.
  • 조직은 현재 AWS(Amazon Web Service)에 익숙하다.

선택지

  • AWS(Amazon Web Service)
  • GCP(Google Cloud Platform)
  • Microsoft Azure

의사결정

AWS(Amazon Web Service)를 선택하겠다. 그 이유는,

  • 조직이 AWS에 가장 익숙하다.
  • AWS는 이미 클라우드 플랫폼 시장의 50% 이상을 점유하고 있으며, 수많은 대형 클라이언트들의 신뢰를 받고 있다.
  • AWS에 새로 회원가입하고 나면, 12개월간 주요 서비스를 일정 한도 내에서 무료로 이용할 수 있는 free tier를 제공해주는 건 덤이다. Google Cloud Platform과 Azure도 비슷하지만, AWS가 가장 후하게 무료 플랜을 제공해준다.

의사결정 - 컴퓨팅 엔진 결정

배경과 요구사항

  • 프로젝트 시작 단계이므로 트래픽이 적고, 얼마나 늘어날지 예측 불가능하다.
  • 인프라를 직접 관리하는 범위가 적을수록 좋다.(개발자들은 코드를 제공하기만 되게)
  • Python으로 개발된 어플리케이션을 배포하는 데에 문제가 없어야 한다.

선택지

  • EC2(Elastic Compute Cloud)
  • Beanstalk
  • ECS(Elastic Container Service)
  • ECS+Fargate
  • Lightsail
  • Lambda

의사결정

Lambda를 선택하고, 추후에 ECS+Fargate로 마이그레이션을 진행하겠다. 그 이유는,

  • Lambda는 월마다 100만 개의 요청까지를 무료로 제공해준다. 정확히는 100만 초만큼의 함수 실행 시간을 무료로 제공해주는 것인데, 서비스가 인기몰이를 하기 전까진 이걸로 버텨볼만 하다. API 사용량이 많아지면 ECS+Fargate로 옮기는 것을 고려해보려 한다.
  • Lambda엔 서버가 존재하긴 하지만, 서버 관리의 주체는 우리가 아니고 Amazon이다(serverless). 이는 Amazon에서 관리하는 뛰어난 가용성을 가진 인프라에서 동작하기 때문에, 코드에 문제가 있는 것이 아니라면 API가 죽을 일이 거의 없다는 것이다. 알아서 여러 가용 영역(데이터 센터)에 걸쳐서 컴퓨팅 파워를 유지시키고, 우리가 따로 설정하지 않더라도 트래픽에 따라 서버를 축소/확장시키는 auto scaling도 기본으로 제공받는다. 우리는 컴퓨팅 파워를 직접 운영할 필요 없이, 코드를 제공하기만 하면 된다. 트래픽이 적고, 얼마나 늘어날지 예측 불가능한 현재 배경에 가장 알맞다.
  • 서비스가 커지면 과금 문제 때문에 lambda를 계속 유지하기가 어려울 것이다. lambda는 코드(함수)의 실행 시간에 비례하여 가격을 책정하는데, 일정 수준이 넘어가면 해당 트래픽의 처리기로 컴퓨팅 인프라를 직/간접적으로 따로 운영하는 것이 웬만하면 훨씬 싸다. 그 때를 생각해 추후 마이그레이션 대상으로 ECS+Fargate를 선택했다. 그 이유는 아래에서 설명한다.
  • EC2는 모던한 컴퓨팅 파워 서비스인데, 이게 EC2만 달랑 쓰면 배포 파이프라인을 수립하기가 번거롭다. 예를 들어 2대 이상의 인스턴스에 동일한 웹 어플리케이션을 배포하려면, 하나 잡아서 배포해 두고 이미지를 그대로 떠서 복사시켜줘야 하는데, 구성도 귀찮고 오류에 대한 리스크도 크며 느리다. 차라리 EC2를 쓸거라면 Beanstalk이나 ECS같이 EC2를 기반으로 돌아가는 추상화 계층을 두어 이들의 도움을 받는 것이 좋을 것이라는 판단이다.
  • Beanstalk은 웹 어플리케이션을 간편하게 배포하고 조정할 수 있는 서비스다. heroku같은 PaaS를 생각하면 되는데, 세팅 조금만 하면 GitHub같은 데에 hook 걸어서 배포 자동으로 알아서 해주고 트래픽 변동이 생기면 서버 사이즈 scaling도 자동으로 해준다. 서버의 관리를 클라우드 제공자에게 위임하는 서버리스 형태를 유지하면서, lambda의 실행 시간에 비례한 과금 정책을 회피할 수 있게 된다. 위에서도 말했듯 EC2를 기반으로 돌아가는 추상적인 layer라고 볼 수 있는데, 추후 웹 어플리케이션의 런타임을 옮기는 입장에서 생각해 보면, 어차피 EC2 위에 뭔가 올려서 쓴다고 쳤을 때 그냥 조금 더 공부해서 ECS 쓰는 게 더 낫다는 판단이다.
  • ECS는 docker 컨테이너 orchestration 서비스다. 컨테이너식 어플리케이션은, 경량 가상화된 OS 위에서 어플리케이션을 실행하게 되므로 환경에 구애받지 않고 어플리케이션을 배포, 확장하며 빌드 과정을 파일로 표준화시킬 수 있는 여지가 생긴다. 배포 자동화던 scaling 자동화던 Beanstalk이나 ECS나 고만고만 하니까, 컨테이너식 어플리케이션의 메리트를 가져오자는 생각이다. 컨테이너식 어플리케이션은 관리의 입장에서 orchestration이 필요한데, kubernetes나 Docker swarm을 직접 운영하지 않더라도 ECS가 대신 해준다. 컨테이너와 docker가 어렵다면 초보를 위한 도커 안내서 시리즈나 AWS의 Docker란 무엇입니까?, 도커를 이용한 웹서비스 무중단 배포하기의 초반부 정도를 읽어 보자. 왜 백엔드 엔지니어들이 다 도커도커 거리는지의 이유를 대충 알게 될 것이다.
  • Fargate는 옵션에서 제거할 수 있는 부분이라 따로 선택지를 두었다. ECS는 EC2 인스턴스 위에 올릴 수도 있는데, 결국 이것도 컴퓨팅 자원을 따로 운영해야 하기에 관리 포인트가 될 수밖에 없다. Fargate는 컨테이너에 최적화된 별도의 컴퓨팅 엔진을 제공하고, 이를 직접 관리해 주므로 서버리스 컨테이너 엔진이라고 이야기할 수 있다. ECS와 Fargate를 묶어서 쓰게 되면, 서버리스 형태를 유지하며 컨테이너 기반 어플리케이션의 장점을 함께 가져가기에 좋을 것이라고 생각했다. 원래 Fargate가 EC2보다 한 2배 정도 비쌌어서 고민하고 있었는데, 최근에 요금 인하가 되어서 걱정이 좀 사라졌다.
  • Lightsail은 어플리케이션을 손쉽게 배포하고 관리하는 데에 필요한 필수적인 인프라(컴퓨팅, 스토리지, 데이터베이스, DNS 등)들이 자체적으로 포함되어 있는 플랫폼이다. 간단한 어플리케이션을 빠르게 셋업하는 데에 초점이 맞추어져 있는데, Lambda를 운영하는 것과 비교했을 때 그리 큰 메리트가 있어 보이지 않는다.

여기에 더해 terraform같은 Infrastructure as Code(IaC) 도구나, EKS(Elastic Kubernetes Service)를 통해 kubernetes 인프라를 직접 운영하는 간지나는 모습도 욕심은 나지만 조직 상황을 감안해서 보류했다. kubernetes 등으로 컨테이너 클러스터의 orchestration을 직접 수행하는 것은 비즈니스적 요구사항에 매우 유연하게 대응할 수 있겠지만, 조직이 크지 않은 상황에서 클러스터 자체 운영은 자칫하면 IaC와 kubernetes에 익숙하지 않은 현재 조직에게 휴먼 리소스의 낭비가 될 수 있으므로 ECS의 도움을 받으려 한다. 

작업 - Hello World 서버 배포하기

Amazon Lambda는 '호출 기반으로 코드를 실행해주는 서비스'라고 요약할 수 있다. 호출을 수행하는 주체, 즉 event source는 대표적으로 CloudWatch, API Gateway가 있는데, 그 예를 들면,

  1. CloudWatch의 호출 : 특정 EC2 인스턴스에서 CPU 사용량이 80%가 넘어가면, 설정해 둔 lambda 함수를 호출해 조직 내 슬랙의 특정 채널에 경고 메시지를 보낸다. 이러한 설정을 'CloudWatch 이벤트 트리거'라고 부른다.
  2. API Gateway의 호출 : 외부의 요청이 들어오면, 설정해 둔 lambda 함수를 호출해 결과를 얻고 응답을 반환한다. 마치 웹 어플리케이션 서버를 호출하는 웹 서버(nginx 등)처럼 말이다.

우리는 API Gateway를 사용하는 후자의 방식을 사용할테고, AWS Lambda를 이용해서 HTTP API 만들기라는 outsider님의 포스트를 천천히 읽어보면 대충 무슨 말인지 알 것이다. 그리고 이 글을 읽어 보았다면, 우리는 Lambda에 코드를 배포하기 위해 아래의 과정을 거쳐야 한다는 것도 깨달았을 것이다.

  1. 소스 코드를 zip으로 패키징S3라는 object storage 서비스에 업로드한다.
  2. 소스 코드를 Lambda에 올려 새로운 버전의 코드가 lambda를 구성하도록 만든다.
  3. 만약 이게 최초 배포라면, API Gateway를 세팅해 준다. 특정 엔드포인트에 반응하여 lambda 함수를 호출하도록 말이다. CloudFormation같이 Amazon 자체에서 제공하는 Infrastructure as Code 서비스를 써야할 수도 있을 것이다.

물론 이걸 손으로 직접 클릭해가며 하진 않을테고 CLI에서 커맨드를 입력하는 방식을 사용하게 될텐데, 그렇게 생각하더라도 여간 귀찮은 일이 아닐 수 없다. update같은 커맨드를 입력하면 알아서 패키징하고, S3에 올리고, lambda를 세팅하고, API Gateway를 세팅해주는 도구가 있다면 매우 편할 것이다. Python 판에서는 zappa라는 라이브러리가 이런 문제를 해결하는 데에 압도적인 인지도를 가지고 있고, 사실상 다른 선택지가 없기도 하다.

aws configure

먼저 zappa는 AWS 리소스에 대한 접근 권한이 필요하므로, 권한을 부여해 주어야 한다. AWS 콘솔에 로그인하고(계정이 없으면 만들자.), IAM이라는 서비스의 콘솔에 들어가 zappa를 위한 IAM 사용자를 만들자. 본인 계정의 AWS 콘솔에 접근할 수 있는 권한을 가진 또 다른 하위 사용자를 만드는 것이다. AWS 계정의 IAM 사용자 생성 가이드를 보며 따라하면 된다. 주의해야 할 것은,

  1. 액세스 권한 유형은 프로그래밍 방식 액세스(또는 Programmatic access)를 선택하자.
  2. 권한 설정은 '기존 정책 직접 연결'을 선택해 AdministratorAccess를 부여하도록 하자. 필요한 권한만 연결하고, 그룹을 만드는 것이 여러모로 좋지만, 이건 따로 챕터를 진행할 것이다.

사용자를 생성하고 나면 Access Key IDSecret Access Key를 제공받을 수 있을텐데, CLI 터미널(cmd, terminal 등)을 켜고 아래의 순서에 따라 설정을 진행하자. AWS CLI 구성 가이드와 동일한 내용이다.

  1. pip install awscli 커맨드를 실행해 AWS CLI 툴을 설치하자. 일부러 pipenv 대신 pip를 사용했다.
  2. aws configure 커맨드를 실행한 후 Access Key ID와 Secret Access Key를 붙여넣자. Default region name에는 원하는 지역을 AWS region 규칙에 따라 입력하자. 나는 서울 region을 사용할 것이므로, ap-northeast-2를 입력했다.

zappa init

pipenv install zappa 커맨드로 의존성 목록에 zappa를 추가함과 함께 패키지를 본인의 가상 환경에 설치하고 프로젝트 루트 디렉토리에서 zappa init 커맨드를 입력해 zappa 설정을 초기화하자. environment, bucket name같은 것들은 적당히 잘 입력하거나 default로 설정되도록 그냥 엔터만 누르면 되고, 'Where is your app's function?'에 대한 답만 잘 입력하자. app 객체가 있는 경로를 입력하면 되는데, 현재 Hello World 서버 기준으론 run.py 모듈에 객체 이름이 app이므로 run.app이라 입력할 것이다. 필자의 경우 pipenv install 시 Python 3.7.1 버전으로 초기화되었는데, zappa가 Python 2.7과 3.6만을 지원해서 pipenv install --python 3.6 커맨드로 pipenv를 다시 초기화하고 진행했다. 성공하면 zappa_settings.json이라는 파일이 새로 생길 것이다. pipenv install zappa, zappa init 직후의 스냅샷

이제 배포 준비가 완료되었지만, 문제는 Sanic 어플리케이션을 zappa로 업로드하는 과정에 오류가 많다는 것이다. zappa에서는 마이크로 프레임워크로 Flask를 공식 지원하고 있고, Sanic은 Flask-like하게 만들어져 있으므로 Hello World 서버를 Flask 기반으로 변경하자. Pipfile에도 반영하고, 코드에도 반영해야 한다. 변경 직후의 스냅샷

이제 정말 배포만 남았다. zappa deploy를 입력하고 조금 기다리면 https://abc.execute-api.us-east-1.amazonaws.com/...같은 형태의 URL이 콘솔에 보여질 것이다.

그대로 복사해서 브라우저로 접속해 봤을 때, 'Hello World'가 뜨면 성공이다. 사람들이 그렇게나 말하던 서버리스 어플리케이션을 배포한 것이다.


engine은 일반적으로 sqlalchemy.engine.create_engine 함수에 의해 생성되는, SQLAlchemy에서 사용되는 lowest level의 객체다. engine은 어플리케이션이 데이터베이스와 통신할 때마다 사용할 수 있는 connection pool을 유지한다. 쿼리를 위해 사용하는 engine.executeengine.connect(close_with_result=True)를 수행해 Connection 객체를 얻고, conn.execute를 호출하는 편리한 메소드다. ORM을 사용하지 않아서, sqlalchemy.text 함수로 객체에 바인딩되지 않은 straight SQL 쿼리를 수행하는 경우 이처럼 engine과 connection을 사용하는 것이 적절한 방법이다.

어플리케이션이 SQLAlchemy ORM을 사용한다면, 객체에 바인딩된 쿼리를 위해서 Session 객체를 사용해야 한다. 이는 session.add(), session.rollback(), session.commit(), session.close()를 통해 트랜잭션을 단일 작업 단위로 관리하기 좋고, 이러한 특징을 통해 Python의 Context Manager 패턴을 사용하기에도 좋다.

session을 생성해 yield하고, 정상적인 경우 commit, 오류가 발생하면 rollback, 어느 상황이든 close하도록 만든다. 쿼리가 필요한 부분을 with-as 블럭으로 감싸기만 하면, global한 트랜잭션 관리는 context manager가 모두 수행해 준다. SQLAlchemy 문서에서는 위와 같은 구현을 scoped session이라고 이야기하고 있다. 따라서 raw string SQL을 사용하는 경우 engine이나 connection, ORM을 사용하는 경우 session과 scoped session 패턴을 사용하는 것이 적합하다고 생각한다. session 객체에서도 물론 execute 메소드가 지원되기 때문에, 어플리케이션 전체에 걸쳐 그냥 session만 사용하는 practice도 꽤 있었던 것 같다.

앞으로 블로그의 SQLAlchemy 카테고리에 올라올 포스트들의 예제는, scoped session이 적용되어 있다(session_scope 함수가 이미 존재한다)는 것을 가정하고 작성될 예정이다.

SQL에는 스키마가 동일한 두 테이블을 합쳐 쿼리하는 UNION과, SELECT 절에 리터럴한 expression을 두는 Literal Select라는 개념이 존재한다.

SQLAlchemy에서는 Query 객체의 union(*q)union_all(*q)로 각각 UNION/UNION ALL을, sqlalchemy.sql.expression.literal_column 함수를 통해 literal select를 표현할 수 있다. 위에서 예를 든 union 쿼리를 SQLAlchemy로 표현해 보자.


+ Recent posts