비동기 지원¶
Django는 ASGI에서 실행중인 경우, 완전히 비동기적인 요청 스택과 함께 비동기식(“async”) 뷰 작성을 지원합니다. 비동기적뷰는 WSGI에서 계속 작동하지만 성능에 패널티가 있고, 효율적인 장기 실행 요청이 없습니다.
우리는 계속해서 ORM과 Django의 다른 부분에 대한 비동기 지원을 위해 노력하고 있습니다. 이 사항들을 향후 릴리스에서 볼 수 있습니다. 이제 Django의 동기식 부분과 상호작용하는 sync_to_async 어댑터를 이용할 수 있습니다. 통합할 수 있는 비동기식 네이티브 Python 라이브러리의 전체 범위도 있습니다.
비동기 뷰¶
Any view can be declared async by making the callable part of it return a
coroutine - commonly, this is done using async def
. For a function-based
view, this means declaring the whole view using async def
. For a
class-based view, this means declaring the HTTP method handlers, such as
get()
and post()
as async def
(not its __init__()
, or
as_view()
).
참고
Django는 view가 비동기식인지 아닌지를 테스트하기 위해 “asyncio.iscoroutinefunction” 을 사용합니다. 코루틴을 반환하는 본인만의 메소드를 구현했다면, view의 “_is_coroutine” 속성을 “asyncio.coroutines._is_coroutine” 로 설정했는지 확인해서 이 함수가 “True”를 반환합니다.
WSGI 서버에서, 비동기 view는 자체 일회성 이벤트 루프에서 실행된다. 문제 없이 비동기 동시성 HTTP 요청과 같은 기능을 사용할 수 있으나 비동기식 스택의 장점은 얻을 수 없다.
주요 이점은 수백개의 연결을 Python 스레드를 이용하지 않고 서비스를 할 수 있는 것이다. 이는 느린 스트리밍, 긴 폴링과 다른 흥미로운 응답 유형을 사용할 수 있도록 한다.
이를 이용하고 싶다면, :doc:`ASGI를 이용하여 Django를 배포해야한다.
경고
“no synchronous middleware”가 사이트에 로드하게 한다면 완전한 비동기식 요청 스택의 이점만 가질 수 있다. 동기식 미들웨어의 조각이 있다면, django는 동기식 환경을 안전하게 모방하기 위해서 요청마다 스레드를 사용해야한다.
Middleware can be built to support both sync and async contexts. Some of Django’s middleware is built like
this, but not all. To see what middleware Django has to adapt for, you can
turn on debug logging for the django.request
logger and look for log
messages about “Asynchronous handler adapted for middleware …”.
ASGI와 WSGI 모드에서 모두, 연속적으로보다 코드를 동시에 실행하여 비동기식 지원을 안전하게 사용할 수 있다. 외부 API나 데이터 저장소를 다룰 때 특히 유용하다
If you want to call a part of Django that is still synchronous, you will need
to wrap it in a sync_to_async()
call. For example:
from asgiref.sync import sync_to_async
results = await sync_to_async(sync_function, thread_sensitive=True)(pk=123)
If you accidentally try to call a part of Django that is synchronous-only from an async view, you will trigger Django’s asynchronous safety protection to protect your data from corruption.
Queries & the ORM¶
With some exceptions, Django can run ORM queries asynchronously as well:
async for author in Author.objects.filter(name__startswith="A"):
book = await author.books.afirst()
Detailed notes can be found in Asynchronous queries, but in short:
- All
QuerySet
methods that cause an SQL query to occur have ana
-prefixed asynchronous variant. async for
is supported on all QuerySets (including the output ofvalues()
andvalues_list()
.)
Transactions do not yet work in async mode. If you have a piece of code that
needs transactions behavior, we recommend you write that piece as a single
synchronous function and call it using sync_to_async()
.
성능¶
뷰가(예를 들어, WSGI에서의 비동기식 뷰, ASGI에서의 전통적 동기식 뷰) 맞지 않은 모드를 실행할 때, django는 코드가 실행되도록 하기 위해서 다른 호출 방식을 모방해야한다. 컨텍스트 스위치는 1 밀리초 전후의 작은 성능 상의 패널티를 유발한다.
이는 미들웨어에서도 TRUE이다. django는 동기식과 비동기식 사이의 컨텍스트 스위칭 횟수를 최소화하도록 시도하다. 만약 ASGI 서버에서 모든 미들웨어와 뷰는 동기식이나, 스택에 미들웨어를 넣기 전에, 스위치는 한 번만 발생할 것이다.
그러나, 동기식 미들웨어를 ASGI 서버와 비동기식 뷰 사이에 넣는다면, 미들웨어의 동기식 모드를 스위치할 것이고 뷰의 비동기식 모드로 돌아갈 것이다. django는 미들웨어 예외 전파를 막기 위해 동기식 스레드를 열린 상태로 유지할 것이다. 처음에는 인지되지 않을 것이나, 요청마다의 한 스레드의 패널티를 더하면 비동기식 성능의 이점을 제거한다.
ASGI와 WSGI 코드에 어떤 영향을 끼치는지 자체 성능 테스팅을 해야한다. 일부 케이스에서는 요청 핸들링 코드는 여전히 비동기식으로 실행되고 있기 때문에 심지어 순전히 ASGI 서버에서의 동기식 코드베이스이나 성능이 향상될 수 있다. 일반적인 경우, 비동기식 코드가 프로젝트에 있는 경우에만 ASGI 모드를 쓸 수 있게 하다.
비동기 안전성¶
-
DJANGO_ALLOW_ASYNC_UNSAFE
¶
코루틴을 인식하지 않는 전역 상태를 가지고 있기 때문에, django의 특정 키 부분은 비동기식 환경에서 안전하게 작동할 수 없다. django의 이러한 부분은 “async-unsafe”로 분류되고, 비동기식 환경에서 실행되지 않도록 보호된다. ORM은 주요 예제이지만, 이러한 방식으로 보호되는 다른 부분들도 있다.
“running event loop”가 있는 스레드에서 이러한 부분을 실행하고자 하면, SynchronousOnlyOperation
error가 발생한다. 이 오류를 발생하기 위해서 비동기식 함수 내부에 직접적으로 있을 필요가 없다. 동기식 함수에서 동기식 함수를 직접적으로 호출하면, :func:`sync_to_async`을 사용하지 않고도 오류가 발생할 수 있다. 비동기식 코드로 선언되었음에도 불구하고, 활성 이벤트 루프가 있는 스레드 안에서 코드는 여전히 실행중이기 때문이다.
오류가 발생하면, 비동식 컨텍스트에서 문제가 있는 코드를 호출하지 않도록 코드를 수정해야 한다. 대신에, 자체적인 비동기식의 안전하지 않은 함수와 동기식 함수와 통신하는 코드를 작성하고, :func:`asgiref.sync.sync_to_async`(또는 자체 스레드에서 동기식 코드를 실행하는 다른 방법)을 이용하여 호출한다.
비동기식 컨텍스트는 django 코드를 실행하는 환경에 의해 부과될 수 있다. 예를 들어, Jupyter notebooks and IPython interactive shells 모두 비동기식 API와 상호작용을 더 쉽게 할 수 있도록 투명하게 활성 이벤트 루프를 제공한다.
IPython shell을 이용해, 실행함으로써 이 이벤트 루프를 쓸 수 없게 하다.
%autoawait off
IPython 프롬프트에서 명령. SynchronousOnlyOperation
오류 발생 없이 동기식 코드를 실행할 수 있게 할 것이지만, “await” 비동기식 API를 쓸 수 없을 것이다. 이벤트 루프를 다시 켜려면, 실행하다:
%autoawait on
If you’re in an environment other than IPython (or you can’t turn off
autoawait
in IPython for some reason), you are certain there is no chance
of your code being run concurrently, and you absolutely need to run your sync
code from an async context, then you can disable the warning by setting the
DJANGO_ALLOW_ASYNC_UNSAFE
environment variable to any value.
경고
이 옵션을 쓸 수 있게 하고 안전하지 않은 비동기식 django의 부분으로 동시 접속이 있다면, 데이터 손실이나 오염을 입을 수 있다. 매우 주의 해야하고 프로덕션 환경에서 사용하지 않는다.
파이썬 내에서 수행해야한다면, ``os.environ``으로 수행한다.:
import os
os.environ["DJANGO_ALLOW_ASYNC_UNSAFE"] = "true"
비동기식 어댑터 함수¶
비동기식 컨텍스트에서의 동기식 코드를 호출할 때, 또는 그 반대의 경우일 때, 호출 방식을 어댑트하는 것이 필요하다. 이를 위해 asgiref.sync
모듈에 async_to_sync()
and sync_to_async()
두 개의 어댑터 함수가 있다. 호환성을 유지하면서 호출 방식을 전환하는 데에 사용한다.
어댑터 함수들은 django에서 널리 사용된다. asgiref`_패키지 자체는 django 프로젝트의 부분이고, ``pip` 로 django를 설치할 때 종속으로서 자동으로 설치된다.
async_to_sync()
¶
-
async_to_sync
(async_function, force_new_loop=False)¶
비동기식 함수를 사용하고, 이를 감싼 동기식 함수를 반환하다. 직접적인 래퍼나 데코레이터 모두 쓰일 수 있다.:
from asgiref.sync import async_to_sync
async def get_data(...):
...
sync_get_data = async_to_sync(get_data)
@async_to_sync
async def get_other_data(...):
...
이벤트 루프가 존재한다면, 비동기식 함수는 현재의 스레드에 대한 이벤트 루프에서 실행된다. 현재의 이벤트 루프가 없다면, 새로운 이벤트 루프가 단일 비동기식 호출에 대해서만 스핀업 하고, 완료하면 다시 종료된다. 두 상황 모두, 비동기식 함수는 호출 코드에 대한 다른 스레드에서 실행될 것이다.
Threadlocals와 contextvars 값은 양 방향의 경계를 넘어 유지된다.
async_to_sync()
는 파이썬의 표준 라이브러리에 있는 함수 asyncio.run`의 본질적으로 더 강한 버전이다. threadlocal 수행이 되는 것 뿐만 아니라, 또한 그 아래에 래퍼가 쓰일 때 :func:`sync_to_async`의 ``thread_sensitive`()
모드를 쓸 수 있게 하다.
sync_to_async()
¶
동기식 함수를 사용하고, 이를 감싼 비동기식 함수를 반환하다. 직접적인 래퍼나 데코레이터 모두 쓰일 수 있다.:
from asgiref.sync import sync_to_async
async_function = sync_to_async(sync_function, thread_sensitive=False)
async_function = sync_to_async(sensitive_sync_function, thread_sensitive=True)
@sync_to_async
def sync_function(...):
...
Threadlocals와 contextvars 값은 양 방향의 경계를 넘어 유지된다.
동기식 함수는 메인 스레드에서 모두 실행된다고 가정하여 쓰이는 경향이 있어서, :func:`sync_to_async`는 두 개의 스레딩 모드를 가진다.
thread_sensitive=True
(디폴트): 동기식 함수는 다른 모든``thread_sensitive`` 함수처럼 같은 스레드에서 실행할 것이다. 만일, 메인 스레드가 동기식이고async_to_sync()
래퍼를 이용하면, 이것이 메인 스레드일 것이다.thread_sensitive=False
: 동기식 함수는 호출이 한 번 완료하면 종료되는 완전 새로운 스레드에서 실행될 것이다.
경고
asgiref
버전 3.3.0은 thread_sensitive
매개 변수의 디폴트 값을 “True”로 바꿨다. 이는 더 안전한 디폴트이고, django와 상호작용하는 많은 경우에서 올바른 값이지만, 이전 버전에서 ``asgiref``를 업데이트한다면 ``sync_to_async()``의 용도를 평가해야한다.
Thread-sensitive 모드는 특별하고, 같은 스레드의 모든 함수를 실행하기 위해 많은 작업을 수행한다. 그럼에도 불구하고, 메인 스레드에서 올바르게 실행하기 위해 스택에서 async_to_sync`의 *용례를 필요로 하다*. ``asyncio.run()`()
또는 유사한 것을 사용한다면, 단일의, 공유되는 스레드의 thread-sensitive 함수를 실행하는 것으로 돌아갈 것이지만, 이는 메인 스레드는 아닐 것이다.
django에서 필요한 이유는 많은 라이브러리들 중 특히 데이터베이스 어댑터들은, 라이브러리들이 생성된 같은 스레드에 액세스하는 것을 필요로 하기 때문이다. 또한 기존의 많은 django 코드는 같은 스레드에서 모두 실행하는 것을 가정하고, 예를 들어, 미들웨어는 뷰에서 이후에 사용할 것을 위한 것을 요청에 추가한다
이 코드의 잠재적인 호환성 문제를 내놓는 것 대신에, 존재하는 모든 django 동기식 코드가 같은 스레드에서 실행하고 비동기식 모드와 완전히 호환이 될 수 있도록 이 모드를 추가하기로 선택하였다. 동기식 코드는 항상 호출하는 비동기식 코드와 다른 스레드에 있으므로, 원시 데이터베이스 핸들이나 다른 thread-sensitive 참조를 전달하지 않아야 한다.
In practice this restriction means that you should not pass features of the
database connection
object when calling sync_to_async()
. Doing so will
trigger the thread safety checks:
# DJANGO_SETTINGS_MODULE=settings.py python -m asyncio
>>> import asyncio
>>> from asgiref.sync import sync_to_async
>>> from django.db import connection
>>> # In an async context so you cannot use the database directly:
>>> connection.cursor()
...
django.core.exceptions.SynchronousOnlyOperation: You cannot call this from
an async context - use a thread or sync_to_async.
>>> # Nor can you pass resolved connection attributes across threads:
>>> await sync_to_async(connection.cursor)()
...
django.db.utils.DatabaseError: DatabaseWrapper objects created in a thread
can only be used in that same thread. The object with alias 'default' was
created in thread id 4371465600 and this is thread id 6131478528.
Rather, you should encapsulate all database access within a helper function
that can be called with sync_to_async()
without relying on the connection
object in the calling code.