본문 바로가기

About/Flask

[Python] Greenlet과 Gevent(WSGI 성능 향상)

WSGI 표준

파이썬 웹 커뮤니티는 웹 서버 게이트웨이 인터페이스(WSGI, Web Server Gateway Interface)라는 표준을 만들었습니다. WSGI는 CGI(Common Gateway Interface)의 영향을 받았으며, 파이썬 어플리케이션이 HTTP 요청을 쉽게 처리할 수 있게 해줍니다. 이 표준에 따라 uwsgimod_wsgi 같은 WSGI 확장을 사용하여 아파치, nginx 등의 웹 서버에서 실행할 수 있습니다.

WSGI, Web Server Gateway Interface

하지만 WSGI의 가장 큰 문제는 동기 방식 이라는 것입니다. 웹 서버에 요청이 들어올 때마다 함수가 호출되고 함수가 종료될 때 응답을 반환합니다. 즉 함수가 한 번 호출되면 응답을 반환할 때 까지 블록됩니다.

따라서 마이크로서비스에서 WSGI를 적용하면 연동된 서비스가 응답을 보내줄 때 까지 항상 대기해야합니다. 동시에 여러 개의 요청이 어플리케이션에 들어오면 WSGI 서버는 스레드 풀을 사용하여 여러 개의 동시 요청을 다룹니다. 하지만 수천개의 동시요청을 다루기는 힘들며 풀이 꽉 차게되면 클라이언트 요청은 블록되게 됩니다.

이러한 단점 때문에 Twisted나 Tornado 같은 비WSGI 프레임워크와 자바스크립트의 Node.js를 많이 사용합니다. 이들은 완전한 비동기 방식이기 때문입니다.

한편 한 개의 요청을 스레드 한 개에서 처리한다는 WSGI 표준의 제약을 따른다면 동기 프레임워크로
마이크로서비스를 구축하는 것은 충분히 가능하며, 좋은 방법입니다. 또한 동기식 웹 어플리케이션의 성능을 향상시키기 위한 기술로 GreenletGevent가 있습니다.


Greenlet

Greenlet 프로젝트는 Stackless 프로젝트에 기반을 둔 패키지 입니다. 특히 CPython 구현인 greenlets를 제공합니다. (Stackless 프로젝트에 대하여 다음에 다룰 예정입니다.)


https://github.com/python-greenlet/greenlet

python-greenlet/greenlet

Lightweight in-process concurrent programming. Contribute to python-greenlet/greenlet development by creating an account on GitHub.

github.com

Greenlet의 pesudo-threads는 실제 스레드와 다르게 매우 작은 비용으로 인스턴스를 생성하여 파이썬 함수를 호출합니다. 이 함수 내에서 switch를 사용하여 다른 함수를 조절할 수 있습니다.

다음은 Greenlet의 예제 코드 입니다.

from greenlet import greenlet

def test1(x, y):
    z = gr2.switch(x+y)
    print(z)
    
def test2(u):
    print(u)
    gr1.switch(42)
    
gr1 = greenlet(test1)
gr2 = greenlet(test2)

gr1.switch("hello", "world")
실행 결과

이 코드에서 두 개의 greenlet은 switch를 사용하여 명시적으로 전환됩니다.
helloworld는 print(u)에서, 42는 print(z)에서 실행됩니다.

WSGI 표준에 기반을 둔 마이크로서비스를 만들기 위해 greenlet을 사용하면 I/O 요청처럼 요청이 블록되는 경우 다른 쪽으로 switch하여 여러 개의 동시 요청을 받게할 수 있습니다.

하지만 greenlet에서 스위칭은 명시적이여야 하므로, 코드가 지저분해지고 이해하기 어렵습니다.
이런 면에서는 Gevent가 유용합니다.

Gevent 프로젝트는 Greenlet 기반으로 만들어졌으며 greenlet 간의 암시적이고 자동화된 스위칭 방법을 제공합니다.


Gevent

Gevent는 greenlet을 사용하여 자동으로 멈추고 소켓에서 데이터 사용이 가능해지면 실행을 재개하는 소켓 모듈의 협업 버전을 제공합니다. 또한 monkey patch라는 표준 라이브러리 소켓을 Gevent 버전으로 자동 대체하는 기능이 있습니다.

다음 코드를 추가하면 표준 동기 코드가 소켓을 사용할 때 마다 비동기가 됩니다.

from gevent import monkey
monkey.patch_all()

예시 코드는 다음과 같습니다.

from gevent import monkey
monkey.patch_all()

def application(environ, start_response):
    headers = [('Content-type', 'application/json')]
    start_response('200 OK', headers)
    # 소켓 작업 수행
    return result

Gevent가 잘 동작하기 위해서는 모든 기본 코드가 Gevent와 잘 호환되어야 합니다.
특히 C-Expension을 사용하거나 Gevent가 패치한 일부 기능을 우회하는 경우에는 다른 패키지 일부가 계속 차단돼 예상치 못한 결과가 발생할 수 있습니다.

대부분의 경우에는 잘 동작한다고 합니다. Gevent와 잘 동작하는 프로젝트들은 green이라고 불립니다. (어떤 라이브러리가 잘 동작하지 않으면 커뮤니티에서 제작자에게 green이 되도록 요청한다고 합니다.)


Python에서의 동기식 웹 표준인 WSGI와 WSGI의 단점을 보완하며 성능을 향상시키는 greenlet과 Gevent에 대하여 알아보았습니다.
마이크로서비스 아키텍처를 설계할 때 이러한 웹 표준을 잘 알고 있는 것이 중요할 것 같습니다.


참조