Python 2.7로 마이그레이션

이 페이지에서는 Python 애플리케이션을 Python 2.7 런타임으로 업그레이드하는 데 필요한 조치에 대해 설명합니다. 이 과정을 완료하면 애플리케이션은 멀티 스레딩, Jinja2 템플릿 엔진, 바이트코드 액세스 및 업로드, 새로 포함된 타사 라이브러리를 비롯한 Python 2.7 런타임의 여러 새로운 기능을 활용할 수 있습니다.

필수 조건 및 고려 사항

Python 2.7을 사용하려면 애플리케이션이 다음 요구사항을 충족해야 합니다.

  • 애플리케이션이 Django를 사용하는 경우 버전 1.2 이상을 사용해야 합니다. 업그레이드 관련 세부정보는 Django 문서를 참조하세요.
  • 동시 요청을 사용하려면 WSGI 사용에 설명된 대로 애플리케이션이 웹 서버 게이트웨이 인터페이스(WSGI) 스크립트 핸들러를 사용해야 합니다.

이러한 일반 필수 조건을 충족해야 할 뿐 아니라 일부 App Engine 기능 및 타사 라이브러리의 특정 버전을 사용해야 합니다. 애플리케이션에 포함하고 가져올 버전을 업데이트하고, 업그레이드 후에는 애플리케이션을 광범위하게 테스트합니다. 다음 목록에는 주요 호환성 문제를 파악하고 이를 해결하기 위한 추가 리소스가 나와 있습니다.

  • 성능: Python 2.7로 업그레이드한 후 애플리케이션 성능이 변경될 수 있습니다. 응답 지연 시간이 길어지면 프런트엔드 인스턴스 클래스를 늘리고 동시 요청을 사용 설정할 수 있습니다. 동시 요청을 사용하면 애플리케이션이 더 큰 인스턴스에서 더 낮은 인스턴스 비용으로 더 빠른 성능을 달성할 수 있습니다. 자세한 내용은 인스턴스의 요청 처리를 참조하세요.
  • Django: Django 1.2 이상을 Python 2.7과 함께 사용해야 합니다. 업그레이드에 대한 자세한 내용은 Django 출시 노트를 참조하세요.
  • PyCrypto: Crypto.Util.randpool은 지원 중단되었습니다. 대신 Crypto.Random을 대신 사용하시기 바랍니다. 자세한 내용은 RandomPool에 수행할 작업을 참조하세요.
  • webapp: Python 2.7에서 웹 앱 템플릿이 지원 중단됩니다. 대신 Django 템플릿을 직접 사용하거나 jinja2 또는 원하는 다른 템플릿 엔진을 사용할 수 있습니다.
  • WebOb: Python 2.7은 WebOb 버전 1.1을 지원합니다. 이 버전은 이전 버전(0.9)과 완벽하게 호환되지 않습니다. 애플리케이션이 WebOb를 사용하는 경우 광범위하게 테스트하여 업그레이드로 인해 발생하는 오류를 확인해야 합니다.
  • zipimport: Python 2.7은 zipimport를 지원하지 않지만 Python 2.7은 기본적으로 .zip 파일에서 가져올 수 있습니다.
  • simplejson: Python 2.7은 simplejson을 지원하지 않지만 Python 2.7에는 이 기능을 더 빨리 수행하는 표준 라이브러리 json 모듈이 포함되어 있습니다.

동시 요청 및 WSGI

애플리케이션의 설계 및 성능에 가장 많은 영향을 주는 Python 2.7 기능은 동시 요청을 처리할 수 있는 멀티스레드 애플리케이션에 대한 지원입니다. 동시 요청을 처리할 수 있게 되면 사용률이 개선되어 애플리케이션의 성능을 크게 향상시킬 수 있습니다. 특히 여러 CPU 코어를 활용하는 인스턴스 클래스를 많이 사용하는 애플리케이션의 경우 더욱 효과를 발휘합니다.

멀티스레딩을 사용하도록 설정하려면 애플리케이션이 이전 Python 런타임의 공용 게이트웨이 인터페이스(CGI) 기반 접근 방식을 웹 서버 게이트웨이 인터페이스(WSGI) 기반 접근 방식으로 전환해야 합니다. 이는 요청을 순차적으로 처리하도록 설계된 CGI 스크립트가 입력 및 출력 스트림에 액세스하기 위해 환경 변수에 의존하기 때문입니다.

요청 처리를 위한 WSGI 모델은 입력 및 출력 스트림에 대해 더 직접적인 액세스 권한을 애플리케이션에 제공(동시 요청 사용 설정)하지만, 여러 요청을 동시에 처리하는 경우 요청 핸들러의 논리가 로컬보다 큰 범위인 데이터(예: 애플리케이션 상태)를 사용하거나 이 데이터와 상호작용하면 경합 상태를 초래할 수 있습니다. 따라서 경합 상태를 처리하도록 방어적으로 코딩하여 새 WSGI 애플리케이션이 스레드 안전을 유지할 수 있도록 해야 합니다.

자세한 내용은 애플리케이션을 스레드에 안전하게 만들기를 참조하세요.

app.yaml 업데이트

Python 2.7에서는 app.yaml의 헤더에 특별한 runtime 구성 요소가 필요합니다. 참고로 threadsafe: [ true | false ] 요소가 Python 2.7 애플리케이션에 필요합니다. true인 경우 App Engine은 요청을 동시에 전송하고, false이면 요청을 순차적으로 전송합니다. 아래의 app.yaml 헤더는 동시 요청을 사용 설정합니다.

application: myapp
version: 1
runtime: python27
api_version: 1
threadsafe: true
...

WSGI 사용

Python 2.7 런타임을 사용하면 run_wsgi_app 어댑터를 사용하여 프로그램을 CGI 스크립트로 실행하는 대신 필요에 따라 웹 서버 게이트웨이 인터페이스(WSGI) 애플리케이션을 직접 실행할 수 있습니다. 이렇게 하려면 app.yaml의 CGI 핸들러(예: myapp.py)를 WSGI 애플리케이션 이름(예: myapp.app)으로 바꿉니다.

...
handlers:
- url: /.*
  script: myapp.app
...

또한 WSGI 애플리케이션 객체를 전역 범위로 이동해야 합니다.

import webapp2

class MainPage(webapp2.RequestHandler):
  def get(self):
    self.response.headers['Content-Type'] = 'text/plain'
    self.response.out.write('Hello, WebApp World!')

app = webapp2.WSGIApplication([('/', MainPage)])

""" Old code:
def main():
  run_wsgi_app(app)

if __name__ == '__main__':
  main()
"""

여전히 app.yaml에 CGI 스크립트 핸들러를 지정할 수 있지만 CGI 스크립트에서 처리하는 요청은 동시에 처리되지 않고 순차적으로 처리됩니다. 또한 CGI 스크립트와 WSGI 애플리케이션이 혼합된 app.yaml 파일은 가질 수 없으며 CGI 핸들러를 정의한 경우 threadsafetrue로 설정할 수 없습니다.

이전 Python 런타임의 몇 가지 규칙(예: main() 사용 및 __name__ == 'main' 확인)은 이제 지원이 중단됩니다. 이러한 조치는 이전 CGI 스크립트를 캐시된 상태로 유지하는 데 도움이 되지만, 이제 WSGI 애플리케이션을 직접 실행하므로 해당 조치는 더 이상 필요하지 않습니다.

앱 루트 디렉토리 사용

Python 2.5에서 CGI 스크립트는 현재 작업 디렉토리가 스크립트가 포함된 디렉토리로 설정된 상태로 실행되었습니다. Python 2.7에서는 이 부분이 변경되었습니다. WSGI에서 요청 핸들러 수명 초기의 현재 작업 디렉토리는 애플리케이션 루트 디렉토리입니다.

애플리케이션 코드를 검토하고 현재 작업 디렉토리가 애플리케이션 루트가 되도록 모든 핸들러가 작성되었는지 확인하세요.

라이브러리 구성

Python 2.7 런타임에는 타사 모듈이 포함되어 있습니다. 이 중 일부는 기본적으로 사용할 수 있으며 그 외에는 구성된 경우에만 사용할 수 있습니다. 사용할 버전을 지정할 수 있습니다.

libraries:
- name: PIL
  version: "1.1.7"
- name: webob
  version: "1.1.1"

애플리케이션이 최신 모듈 버전을 사용하도록 지정할 수 있습니다. 이는 아직 사용자가 없는 애플리케이션을 개발 중인 경우에 유용하며, 새로운 버전을 추적할 필요가 없습니다. 그러나 애플리케이션이 활발히 사용되는 경우 애플리케이션이 이전 버전과 호환되지 않는 새로운 라이브러리 버전을 사용할 수 있으므로 주의하시기 바랍니다. 최신 버전을 사용하려면 다음을 사용하세요.

libraries:
- name: PIL
  version: latest

지원되는 라이브러리 목록은 타사 라이브러리를 참조하세요.

애플리케이션을 스레드에 안전하게 만들기

각 핸들러가 해당 범위 내 변수와만 상호작용하는 경우에는 동시 요청을 간단하게 처리할 수 있습니다. 하지만 특정 핸들러가 리소스를 읽는 동안 다른 핸들러가 리소스를 수정하는 경우에는 문제가 복잡해집니다. 여러 요청이 동일한 데이터를 조작하고 서로 간섭하는 경우에도 애플리케이션이 예상대로 작동하도록 하는 것을 애플리케이션을 '스레드에 안전'하게 만든다고 합니다.

애플리케이션을 스레드에 안전한 상태가 되도록 설계할 때 기본 규칙은 가능한 자주 공유 리소스 사용(예: 상태 정보 또는 전역 변수)을 제한하는 것입니다. 그러나 일반적으로 공유 리소스 사용을 완전히 배제할 수 없으므로, 이런 경우를 위해 잠금 객체와 같은 동기화 메커니즘이 필요합니다.

Python 2.7에서는 Python의 스레딩 라이브러리에 액세스할 수 있으므로, 내부의 코드가 동시에 실행되는 대신 순차적으로 실행되도록 논리 블록에 잠금을 선언할 수 있습니다. 다음 코드를 살펴보세요.

class Configuration(ndb.Model):
  some_config_data = ndb.StringProperty()
  _config_cache = None
  _config_lock = threading.Lock()
  @classmethod
  def get_config(cls):
    with cls._config_lock:
      if not cls._config_cache:
        cls._config_cache = cls.get_by_id('config')
    return cls._config_cache

이 코드는 일부 전역 구성 변수의 캐시를 _config_cache라는 변수에 만드는 방법을 보여줍니다. 여기에서 이름이 _config_lock이라는 잠금 객체를 사용하면 기존 _config_cache에 대한 검사가 안정적으로 수행됩니다. 그렇게 하지 않으면 경쟁하는 모든 요청이 _config_cache가 비어 있는지 확인하게 되므로, 이 변수는 Datastore로 여러 번 이동하여 동일한 데이터로 동일한 변수를 여러 번 설정하는데 시간을 낭비할 수 있습니다.

잠금을 사용할 때는 신중해야 합니다. 잠금은 이 메소드를 실행하는 다른 스레드를 강제로 차단합니다. 이로 인해 성능에 병목이 발생할 수 있습니다.

애플리케이션을 webapp2로 업데이트

참고: webapp을 요청 핸들러로 사용하고 있지 않은 경우 이 부분을 건너뛸 수 있습니다.

Python 2.7 런타임에 포함되어 있는 웹 프레임워크가 webapp에서 webapp2로 업그레이드되었습니다. 무엇보다도 webapp2는 개선된 URI 라우팅, 예외 처리, 완전한 기능을 갖춘 응답 객체, 더 유연한 디스패치 메커니즘을 추가합니다.

webapp 템플릿은 현재 지원 중단되었습니다. 대신 Jinja2, Django, 원하는 템플릿 시스템(순수 Python으로 작성된 경우)을 사용할 수 있습니다.

App Engine에서 webapp2의 별칭은 webapp으로 지정되었으며 webapp2는 이전 버전과 호환됩니다. 하지만 업그레이드한 후에도 애플리케이션을 철저히 테스트하고, 이전 버전과의 호환성에 의존하기보다는 webapp2새로운 구문과 기능을 숙지해야 합니다.

업그레이드가 완료되었습니다.

애플리케이션을 업로드한 후에는 이전 버전과 호환되는지 확인하기 위해 광범위한 테스트를 수행해야 합니다. 문제가 발생하면 포럼을 확인하세요.