Gevent로 Python 어플리케이션 성능 개선하기
F-Lab : 상위 1% 개발자들의 멘토링
📌 글 작성
F-Lab 백엔드 멘토 Jacob
팀 문화와 엔지니어 생산성에 관심이 많은 실리콘밸리 백엔드 개발자
Python 은 다양한 영역에서 사용되는 언어이지만 다른 언어에 비해 느리다는 인식이 퍼져 있습니다. 그 이유로는 아래와 같은 특징들을 들 수 있습니다.
- 인터프리터 언어이기 때문에 런타임에 bytecode를 생성합니다. 컴파일러 언어에 비해서 런타임에 동일한 행위를 하기 위해서 처리해야 하는 일이 더 많습니다.
- 가비지 콜렉터의 존재로 인해 CPU 사이클을 소모하게 됩니다. 또 가비지 콜렉터는 언제 실행될지 알기 어렵습니다.
- 동적 타이핑으로 인해서 Python 인터프리터가 namespace lookup을 하는데 더 많은 노력이 들어갑니다.
위와 같은 이유 때문에 Python으로 작성한 프로그램은 C 나 C++로 짜여진 동일한 행위를 하는 프로그램에 비해서 10배, 심지어는 100배가량도 느릴 수 있습니다. 하지만 그것이 Python 이 충분히 빠르지 않다는 의미는 아닙니다.
Python 이 느리고 Python 프로그램을 프로덕션 환경에서 사용하기 위해서는 많은 최적화를 필요로 할 것이라고 생각하셨다면 이 블로그 글을 일독하시는 걸 권해드립니다. 이 글은 파이썬의 성능상의 제약에도 불구하고 그것이 서비스의 주요 병목이 될 확률은 몹시 적다고 말하고 있습니다. 아래는 제가 좋아하는 Coding Horror 블로그의 글 The Infinite Space Between Words에서 발췌한 표입니다.
1 CPU 사이클에 필요한 시간을 1초라고 가정했을 때 메인 메모리에 접근하는데 필요한 시간은 6분 남짓, 디스크에 접근하는데 필요한 시간은 일에서 월 단위로 늘어납니다. 네트워크를 통해 자원에 접근하는 것은 심지어 더 오래 걸립니다. 앞서 가정한 CPU 사이클당 1초가 걸린다는 가정을 기반으로 계산하면 샌프란시스코에서 뉴욕에 있는 데이터센터에 접근하는 데 필요한 시간은 4년이나 걸립니다. 한 번의 네트워크 왕복은 2억 5천만 건의 CPU cycle에 맞먹습니다.
그러면 우리가 작성하는 Python 프로그램을 빠르게 할 수 있는 가장 쉬운 방법은 무엇일까요? 디스크/네트워크에 읽고 쓰는 행위에 드는 시간을 줄이면 프로그램이 명백하게 빨라질 것 같습니다. 여기에서 MDN에서 발췌한 비동기 프로그래밍에 대한 설명을 확인하도록 하겠습니다.
비동기 프로그래밍은 작업이 완료될 때까지 기다리지 않고 잠재적으로 오래 실행되는 작업을 시작하여 해당 작업이 실행되는 동안에도 다른 이벤트에 응답할 수 있게 하는 기술입니다. 작업이 완료되면 프로그램이 결과를 제공합니다.
디스크/네트워크에 읽고 쓰는 동안 Python 인터프리터가 기다리지 않고 다른 동작을 할 수 있다면 디스크/네트워크에 읽고 쓰는 행위에 드는 시간을 줄일 수 있습니다. 파이썬은 비동기 프로그래밍을 위한 여러가지 라이브러리를 제공합니다. 그중 오늘 소개해 드릴 라이브러리는 Gevent 입니다.
Gevent는 코루틴 기반의 Python 네트워킹 라이브러리입니다. Gevent는 libev 또는 libuv 이벤트 루프 위에서 동기적인 API를 제공하기 위해서 Greenlet을 사용합니다.
위의 설명에서 나온 핵심 단어들을 전부 이해하지 못해도 괜찮습니다. 핵심은 Gevent가 어떤 일을 하는지 이해하는 것입니다. Gevent는 우리가 일상적으로 사용하는 동기적인 API를 제공하고 내부 구현체가 요청들을 비동기적으로 처리합니다. 다시 말해서 Gevent 라이브러리에서 제공되는 socket() 과 같은 함수들을 이용하면 Python 인터프리터가 네트워크 요청을 보내고 기다릴 필요 없이 다음 명령을 실행할 수 있게 됩니다.
import gevent
import socket
def arbitrary_socket_call():
_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
_socket.connect(('www.google.com', 80))
_socket.send(b"GET / HTTP/1.1\r\nHost:www.google.com\r\n\r\n")
response = _socket.recv(4096)
print(response)
def run_many_socket_calls():
for _ in 1000:
arbitrary_socket_call()
# gevent.socket 을 사용함으로써 동일한 결과를 내는 코드가 더 빠르게 실행된다
# gevent.socket 은 blocking I/O가 발생하면 그것을 기다리는 동안 다른 작업을 처리할 수 있게 해준다.
from gevent import socket
def arbitrary_socket_call():
_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
_socket.connect(('www.google.com', 80))
_socket.send(b"GET / HTTP/1.1\r\nHost:www.google.com\r\n\r\n")
response = _socket.recv(4096)
print(response)
def run_many_socket_calls():
jobs = [gevent.spawn(arbitrary_socket_call) _ for 1000]
gevent.joinall(jobs)
그렇다면 Gevent를 쓰지 않는 서드 파티 라이브러리들의 경우에는 성능 향상을 누리기 어려울까요? Gevent는 monkey patch라는 기능을 통해서 Python 표준 라이브러리들을 Gevent 라이브러리가 제공하는 구현체로 대체 할 수 있습니다. 이 기능을 이용하면 여러분이 사용하는 서드 파티 라이브러리의 코드도 성능 향상이 가능합니다.
import gevent
from gevent import monkey
monkey.patch_all() # 이 함수 호출 이후로 import 되는 모듈에서 gevent가 지원하는 I/O 관련 함수는 모두 gevent 모듈의 비동기 함수를 사용하게 된다
import requests
TARGET_URL_LIST = [
'www.google.com',
'www.youtube.com',
'www.facebook.com',
'www.twitter.com',
'www.instagram.com',
'www.wikipedia.com',
'www.yahoo.com',
'www.live.com',
'www.reddit.com',
'www.netflix.com',
'www.linkedin.com',
'www.office.com',
'www.bing.com',
'www.quora.com',
'www.ebay.com',
]
def send_request(url: str):
content = requests.get(url).content
print(content)
def run_concurrent():
jobs = [gevent.spawn(send_request, url) for url in TARGET_URL_LIST]
gevent.joinall(jobs)
물론 Gevent도 만능이 아닙니다. 우선 위에서 언급한 monkey patch는 순수하게 Python으로 작성된 코드에 한해서 지원됩니다. 만약 서드 파티 모듈이 다른 언어로 작성되었다면 성능 향상을 누리기 위해서는 Gevent를 지원하는지 확인하는 것이 필요합니다.
또 Gevent 라이브러리를 사용하는 경우 thread-safe 한 코드 작성이 필요합니다. 하지만 그럼에도 불구하고 Gevent를 현업에서 도입하는 것은 만족스러운 경험이었습니다.
저의 경우에는 Python으로 작성된 레거시 백엔드 어플리케이션에서 스케일링 문제가 발생해서 그것을 해결하는 과정에서 Gevent를 도입한 경험이 있는데 어플리케이션 서버의 성능을 10배까지 끌어올릴 수 있었고 클라우드 비용을 유의미하게 절감하는 성과를 낼 수 있었습니다.
동시에 Gevent가 제공하는 monkey patch를 이용해서 코드 변경을 최소화하고 함께 일하는 엔지니어들이 새로운 기술 도입 과정에서 들이는 노력도 많이 아낄 수 있었습니다. 여러분도 Gevent , 또는 다른 비동기 프로그래밍 라이브러리를 이용해서 Python 어플리케이션 성능을 개선해 보시기 바랍니다.
이 컨텐츠는 F-Lab의 고유 자산으로 상업적인 목적의 복사 및 배포를 금합니다.