# Django Async Views และ ASGI ในปี 2026: ประสิทธิภาพและคำถามสัมภาษณ์ > เจาะลึก Django async view และ ASGI ในปี 2026: กลไกเบื้องหลัง, เซิร์ฟเวอร์ที่ควรดีพลอย, async ORM กับกับดัก SynchronousOnlyOperation พร้อมคำถามสัมภาษณ์ - Published: 2026-07-03 - Updated: 2026-07-07 - Author: SharpSkill - Tags: django, async, asgi, python, performance - Reading time: 10 min --- Django async view ช่วยให้ worker เพียงตัวเดียวรองรับคำขอที่ผูกกับงาน I/O พร้อมกันได้หลายร้อยคำขอโดยไม่ต้องจัดสรร thread ให้แต่ละการเชื่อมต่อ แต่จะได้ผลก็ต่อเมื่อ path ของโค้ดยังคงเป็นแบบ asynchronous ตั้งแต่ตัว view ลงไปจนถึงทุกการเรียกผ่านเครือข่าย นับตั้งแต่ Django 3.1 เปิดตัว view แบบ `async def` และ Django 4.1 เพิ่มอินเทอร์เฟซ ORM แบบ async เฟรมเวิร์กนี้ก็กลายเป็นแพลตฟอร์ม ASGI ที่ใช้งานได้จริง ทว่าการดีพลอยบน production ส่วนใหญ่ยังคงรันภายใต้ WSGI และปล่อยศักยภาพด้าน concurrency นั้นทิ้งไว้โดยไม่ได้ใช้ บทวิเคราะห์เชิงลึกนี้อธิบายพฤติกรรมของ async view ภายใต้ ASGI, เซิร์ฟเวอร์ที่ควรเลือกใช้ในปี 2026, กับดักของ ORM ที่ทำให้เกิด `SynchronousOnlyOperation` และคำถามสัมภาษณ์ที่แยกวิศวกรผู้เคยส่งมอบ async Django จริงออกจากผู้ที่เพียงแค่อ่านผ่านตา > **เมื่อ Async ช่วยได้จริง** > > async view จะคุ้มค่าเมื่อคำขอใช้เวลาส่วนใหญ่ไปกับการรอ I/O ที่มันไม่ได้ควบคุมเอง เช่น HTTP API ของบุคคลที่สาม, บริการปลายทางที่ช้า หรือการตอบสนองแบบสตรีมที่เปิดค้างยาว สำหรับงานที่ผูกกับ CPU หรือคิวรีฐานข้อมูลภายในเครื่องที่รวดเร็ว, synchronous view ที่หนุนด้วย Gunicorn worker จำนวนมากขึ้นมักจะเร็วกว่าและเข้าใจได้ง่ายกว่ามาก ## วิธีทำงานของ Django Async View ภายใต้ ASGI WSGI นั้นเป็นแบบ synchronous โดยพื้นฐาน แต่ละคำขอที่กำลังประมวลผลจะครอบครอง thread หรือ process ของ worker หนึ่งตัวตั้งแต่ต้นจนจบ ดังนั้นคำขอที่ช้า 50 คำขอจึงต้องใช้ worker 50 ตัว ส่วน ASGI แทนที่โมเดลนั้นด้วย event loop ที่สลับสับเปลี่ยนหลายคำขอบน worker เพียงตัวเดียว โดยพัก coroutine ไว้เมื่อใดก็ตามที่มันรอ I/O และปลุกกลับมาทำงานต่อเมื่อข้อมูลมาถึง [เอกสารทางการเรื่อง async ของ Django](https://docs.djangoproject.com/en/5.2/topics/async/) อธิบายอะแดปเตอร์สองตัวที่ทำให้การอยู่ร่วมกันนี้เป็นไปได้ ภายใต้ ASGI, Django จะรัน view แบบ `async def` บน loop โดยตรง และผลัก view แบบ synchronous เข้าไปยัง threadpool ด้วย `sync_to_async` ส่วนภายใต้ WSGI จะห่อ async view ด้วย `async_to_sync` แล้วรันจนเสร็จ ทำให้ประโยชน์ด้าน concurrency ทั้งหมดหายไป ผลลัพธ์ในทางปฏิบัติจึงตรงไปตรงมา view แบบ `async def` ที่ถูกดีพลอยบนเซิร์ฟเวอร์ WSGI จะทำงานเหมือน synchronous view ที่ช้ากว่าเดิม concurrency จะปรากฏขึ้นก็ต่อเมื่ออยู่บนเซิร์ฟเวอร์ ASGI เท่านั้น async view ขั้นต่ำที่เรียก external API มีหน้าตาแทบไม่ต่างจาก view แบบ sync คู่กัน โดยจุดที่ต่างกันกระจุกตัวอยู่ที่ `await` ```python # views.py import httpx from django.http import JsonResponse async def dashboard(request): # ภายใต้ ASGI, coroutine นี้จะรันอยู่บน event loop ดังนั้น worker จึง # ว่างพอที่จะให้บริการคำขออื่นในระหว่างที่ httpx รอผลการรับส่งข้อมูลผ่านเครือข่าย async with httpx.AsyncClient(timeout=5.0) as client: response = await client.get("https://api.example.com/metrics") return JsonResponse(response.json()) ``` สิ่งที่ได้ตรงนี้ไม่ใช่ว่าคำขอเดียวเสร็จเร็วขึ้น เพราะยังใช้เวลาตามนาฬิกาเท่าเดิม สิ่งที่ได้คือ worker ไม่ถูกบล็อกในระหว่าง `await` ดังนั้น throughput ภายใต้โหลดที่เข้ามาพร้อมกันจึงสูงขึ้นในขณะที่จำนวน process ยังคงเท่าเดิม โมเดลเดียวกันนี้ยังปลดล็อกรูปแบบการตอบสนองที่ทำได้ยากภายใต้ WSGI นับตั้งแต่ Django 4.2, `StreamingHttpResponse` รองรับ async generator ดังนั้น view จึงสามารถ yield ข้อมูลเป็นก้อน ๆ ตามที่แหล่งต้นทางผลิตออกมา ซึ่งเหมาะกับ Server-Sent Events และการสตรีม token จาก LLM ผ่านพร็อกซีที่การเชื่อมต่อเปิดค้างไว้หลายวินาที ส่วนโปรโตคอลแบบสองทิศทางที่เชื่อมต่อค้างไว้อย่าง WebSockets ยังคงต้องผ่าน Django Channels แทน HTTP view ธรรมดา แต่ทั้งสองอย่างตั้งอยู่บนรากฐาน ASGI เดียวกัน ดังนั้นโปรเจกต์ที่นำ async view มาใช้ก็ได้จ่ายต้นทุนด้านโครงสร้างพื้นฐานสำหรับฟีเจอร์แบบเรียลไทม์ในอนาคตไว้แล้ว ## การเลือกเซิร์ฟเวอร์ ASGI สำหรับ Django ในปี 2026 Django มาพร้อมออบเจกต์แอปพลิเคชัน ASGI ในไฟล์ `asgi.py` แต่มันไม่รันตัวเอง ต้องมีเซิร์ฟเวอร์ ASGI เฉพาะทางเป็นตัวขับ event loop [ข้อกำหนดของ ASGI](https://asgi.readthedocs.io/en/latest/) นิยามอินเทอร์เฟซที่เซิร์ฟเวอร์เหล่านี้ทุกตัวนำไปใช้ นั่นคือเหตุผลที่ทำให้มันสลับใช้แทนกันได้ในระดับโปรโตคอล และต่างกันหลัก ๆ ที่ประสิทธิภาพและขอบเขตของโปรโตคอลที่รองรับ | เซิร์ฟเวอร์ | พัฒนาด้วย | เหมาะกับ | |--------|----------------|----------| | Uvicorn | Python (uvloop) | ASGI ทั่วไป, HTTP/1.1, ตัวเลือกเริ่มต้นที่นิยม | | Daphne | Python | Django Channels, WebSockets, HTTP/2 | | Granian | Rust | throughput ดิบสูงสุด, HTTP/1 และ HTTP/2 | | Hypercorn | Python | HTTP/2 และ HTTP/3 (QUIC) | สำหรับการดีพลอยส่วนใหญ่ในปี 2026, [Uvicorn](https://www.uvicorn.org/) ที่รันเป็น worker class ของ Gunicorn ยังคงเป็นตัวเลือกที่เชื่อถือได้ Gunicorn ทำหน้าที่ดูแลวงจรชีวิตของ process, รีสตาร์ต worker ที่ล่ม และจัดการ signal ขณะที่ Uvicorn ขับ event loop อยู่ภายในแต่ละตัว มีรายละเอียดหนึ่งของการย้ายเวอร์ชันที่มักทำให้การอัปเกรดสะดุด worker class ของ Gunicorn ถูกย้ายออกจากแกนหลักของ Uvicorn ไปยังแพ็กเกจ `uvicorn-worker` แยกต่างหากตั้งแต่ Uvicorn 0.30 เป็นต้นไป ทำให้ import path เปลี่ยนไป ```bash # ติดตั้งแพ็กเกจ worker ก่อน: pip install uvicorn-worker gunicorn myproject.asgi:application \ --workers 4 \ --worker-class uvicorn_worker.UvicornWorker \ --bind 0.0.0.0:8000 ``` ทีมที่ต้องการ throughput สูงสุดหันไปใช้ Granian มากขึ้นเรื่อย ๆ ซึ่งเป็นเซิร์ฟเวอร์ที่เขียนด้วย Rust และตัดภาระการแยกวิเคราะห์ HTTP ฝั่ง Python ออก ไม่ว่าจะเลือกตัวใด เรื่องราวของการดีพลอยคือเหตุผลที่ async จะปรากฏใน production ก็ต่อเมื่อมีการตั้งค่าอย่างตั้งใจเท่านั้น การเซ็ตอัปเฉพาะของ ASGI สะท้อนความรอบคอบด้านปฏิบัติการเช่นเดียวกับที่ครอบคลุมไว้ในโมดูล[คำถามสัมภาษณ์เรื่องการดีพลอย Django](/technologies/django/interview-questions/django-deployment) ## Async ORM กับกับดัก SynchronousOnlyOperation Django 4.1 เพิ่มเมธอดคิวรีแบบ asynchronous ทั่วทั้ง ORM ได้แก่ `aget`, `acreate`, `asave`, `adelete`, `acount`, `aexists`, `aaggregate` และการวนซ้ำด้วย `async for` เมธอดเหล่านี้ให้พื้นผิวที่เป็นมิตรกับ async แต่มีข้อควรระวังสำคัญที่ยังคงอยู่มาจนถึงปี 2026 ชั้นฐานข้อมูลที่อยู่ข้างใต้ไม่ได้เป็น asynchronous โดยกำเนิด แม้จะใช้ psycopg3, Django ก็ยังรันคิวรีใน threadpool แล้วเปิดเผยออกมาผ่าน wrapper แบบ async ดังนั้น async ORM จึงช่วยปรับปรุงความสะดวกในการใช้งานและหลีกเลี่ยงการบล็อก loop โดยไม่ได้มอบ database I/O แบบ async ที่แท้จริงจากต้นทางถึงปลายทาง รูปแบบความผิดพลาดที่พบบ่อยที่สุดคือการเรียกเมธอด ORM แบบ synchronous โดยตรงภายใน async view Django จะตรวจพบบริบท async และปฏิเสธ โดยยกข้อยกเว้น `SynchronousOnlyOperation` ขึ้นมาเพื่อป้องกันไม่ให้การเรียกที่บล็อกทำให้ event loop ค้างสำหรับทุกคำขออื่นที่ใช้ worker ตัวนั้นร่วมกัน > **SynchronousOnlyOperation คือฟีเจอร์** > > ข้อยกเว้นนี้คือราวกันตก ไม่ใช่บั๊ก การหันไปใช้ `DJANGO_ALLOW_ASYNC_UNSAFE=true` เพื่อปิดเสียงมันคือการนำพฤติกรรมการบล็อกแบบเดียวกับที่ async ตั้งใจจะกำจัดกลับเข้ามาอีกครั้ง วิธีแก้ที่ถูกต้องคือใช้เมธอด ORM แบบ async (`aget` แทน `get`) หรือครอบด้วย `sync_to_async` อย่างชัดเจนรอบโค้ดที่ไม่มีคู่แบบ async async view จะอ่านได้สะอาดตาเมื่อนำเมธอดคิวรีแบบ async มาแทนที่คู่แฝดแบบ synchronous รูปแบบด้านล่างนับจำนวนแถวและสตรีมสิบระเบียนแรกออกมาโดยไม่ต้องออกจาก event loop เลย ```python # views.py from django.http import JsonResponse from .models import Article async def latest_articles(request): # acount() และการวนซ้ำแบบ async คือ wrapper ที่ปลอดภัยสำหรับ async ครอบ ORM ไว้ count = await Article.objects.acount() titles = [] async for article in Article.objects.order_by("-created_at")[:10]: titles.append(article.title) return JsonResponse({"count": count, "titles": titles}) ``` โค้ดบางส่วนไม่มีคู่แบบ async เช่น SDK ของบุคคลที่สามที่ให้เฉพาะการเรียกแบบ sync หรือห่วงโซ่การไล่ผ่าน ORM ที่ซับซ้อนซึ่งไม่คุ้มที่จะเขียนใหม่ การครอบมันด้วย `sync_to_async` จาก[ไลบรารี asgiref](https://github.com/django/asgiref) จะย้ายงานไปยัง thread และทำให้ loop ยังตอบสนองได้ อาร์กิวเมนต์ `thread_sensitive=True` คือค่าเริ่มต้นที่ปลอดภัยเพราะมันส่งทุกการเรียกที่ถูกครอบไปยัง thread ตัวเดียวของ executor ที่ใช้ร่วมกัน ซึ่งรักษาสถานะของ connection และ transaction ของฐานข้อมูลที่จะพังหากการเรียกกระจัดกระจายไปตาม thread ต่าง ๆ ตามอำเภอใจ ```python # views.py from asgiref.sync import sync_to_async from .services import build_report # ฟังก์ชันแบบ synchronous async def report_view(request): # thread_sensitive=True ทำให้การเรียกที่ถูกครอบไว้อยู่บน thread เดียวกันที่ใช้ร่วมกัน # ช่วยรักษาสถานะของ connection และ transaction ไว้เมื่อข้ามขอบเขต report = await sync_to_async(build_report, thread_sensitive=True)(request.user.id) return JsonResponse(report) ``` ## ประสิทธิภาพของ Django Async: เมื่อ Concurrency เป็นฝ่ายชนะ ประสิทธิภาพของ Django async เป็นเรื่องของการทำ I/O ให้ทับซ้อนกัน ไม่ใช่ความเร็วดิบ ชัยชนะที่ชัดที่สุดมาจากการกระจายการเรียกผ่านเครือข่ายที่เป็นอิสระต่อกันออกไปด้วย `asyncio.gather` การเรียกภายนอกสามครั้งครั้งละ 200ms มีต้นทุนราว 600ms หากทำเรียงตามลำดับ แต่จะเสร็จภายในราว 200ms เมื่อทำพร้อมกัน เพราะ await ทับซ้อนกันบน loop เดียวกัน ```python # views.py import asyncio import httpx from django.http import JsonResponse async def aggregate(request): async with httpx.AsyncClient(timeout=5.0) as client: # การเรียกที่เป็นอิสระต่อกันสามครั้งรันพร้อมกันแทนที่จะทำทีละครั้ง weather, prices, news = await asyncio.gather( client.get("https://api.example.com/weather"), client.get("https://api.example.com/prices"), client.get("https://api.example.com/news"), ) return JsonResponse({ "weather": weather.json(), "prices": prices.json(), "news": news.json(), }) ``` กรณีตรงกันข้ามก็สำคัญไม่แพ้กัน view ที่รันคิวรีภายในเครื่องเพียงคิวรีเดียวที่เร็วจะไม่ได้อะไรจาก async และมักจะเสียเปรียบด้วยซ้ำ เพราะทุกคำขอตอนนี้ต้องจ่ายค่าการจัดคิวของ event loop บวกกับการกระโดดเข้า threadpool ไปยังไดรเวอร์ฐานข้อมูลแบบ sync นอกจากนี้ async ยังเก็บภาษีที่แยบยลกว่านั้นทุกครั้งที่ข้ามขอบเขต sync/async การผสม middleware แบบ synchronous กับ asynchronous บังคับให้ Django สลับไปมาระหว่าง loop กับ thread ในการเปลี่ยนผ่านแต่ละครั้ง และคำขอที่ข้ามเส้นนั้นหลายครั้งต่อการตอบสนองหนึ่งครั้งก็สะสม latency จริงขึ้นมา การทำให้ stack ของ middleware เป็น async ล้วนหรือ sync ล้วนจะกำจัดการสลับเหล่านั้นออกไป การตัดสินใจจึงลดทอนลงมาที่รูปร่างของภาระงานมากกว่าความชอบในไวยากรณ์สมัยใหม่ | ลักษณะงาน | ค่าเริ่มต้นที่ดีกว่า | |----------|----------------| | เรียก external API ที่ช้าหลายครั้งต่อคำขอ | async view ด้วย `asyncio.gather` | | การสตรีมที่เปิดค้างยาวหรือ Server-Sent Events | async view ด้วย async generator | | คิวรีฐานข้อมูลเดียวที่เร็ว, ใช้ CPU น้อย | sync view, เพิ่ม Gunicorn worker | | การเรนเดอร์หรือการคำนวณที่ผูกกับ CPU | sync view, ย้ายงานไปยัง task queue | สำหรับงานที่รันยาวนานจริง ๆ, async view เป็นเครื่องมือที่ผิดฝาผิดตัวโดยสิ้นเชิง การถือคำขอเปิดค้างไว้เพื่อทำงานหลายนาทีคือการสิ้นเปลืองการเชื่อมต่อไม่ว่าจะใช้โมเดล concurrency แบบใด งานเช่นนั้นควรอยู่ใน background worker ซึ่งเป็นการแลกเปลี่ยนที่สำรวจไว้ในคู่มือเรื่อง[งาน async ด้วย Celery ใน Django](/blog/django/django-celery-async-tasks-interview-2026) การนำไปใช้ควรเป็นไปตามการวัดผลมากกว่าสัญชาตญาณ วิธีที่ซื่อตรงในการพิสูจน์ความคุ้มค่าของ async คือการทำ load-test กับ endpoint นั้นโดยเฉพาะด้วยเครื่องมืออย่าง Locust หรือ k6 ภายใต้ concurrency ที่สมจริง โดยเทียบเวอร์ชัน synchronous บน worker จำนวน N ตัวกับเวอร์ชัน async บนฮาร์ดแวร์เดียวกัน endpoint ที่ถูกครอบงำด้วยการเรียกภายนอกครั้งเดียวที่มี latency สูงจะแสดงช่องว่างที่กว้างในทางที่เอื้อต่อ async ส่วน endpoint ที่ถูกครอบงำด้วยคิวรีที่เร็วมักจะแสดงว่า async ตามหลังอยู่เล็กน้อยเมื่อนับการกระโดดเข้า threadpool เข้าไปด้วย การทำ profiling คำขอก่อนยังเผยให้เห็นว่าคอขวดเป็น I/O จริงหรือไม่ เพราะ async ไม่ได้ช่วยอะไรเลยกับ view ที่ช้าเพราะคิวรีที่ไม่มี index ซึ่งเป็นปัญหาที่กล่าวถึงในบทแนะนำเรื่อง[การปรับแต่งคิวรี Django ORM](/blog/django/django-orm-optimizing-queries) ## คำถามสัมภาษณ์ Django Async สำหรับปี 2026 การสัมภาษณ์เรื่อง Django async จะตรวจสอบว่าผู้สมัครเข้าใจโมเดลนี้จริงหรือเพียงจำ keyword ได้ คำถามด้านล่างสะท้อนสิ่งที่การสัมภาษณ์ระดับ senior ถามจริงในปี 2026 โดยแต่ละข้อจับคู่กับคำตอบที่วิศวกรผู้มีประสบการณ์จะให้ **อะไรเปลี่ยนไปเมื่อ async view รันภายใต้ WSGI แทน ASGI?** ภายใต้ WSGI, Django จะห่อ coroutine ด้วย `async_to_sync` แล้วรันจนเสร็จบน thread ของคำขอ มันจึงทำงานเหมือน synchronous view ที่ช้ากว่าโดยไม่มีประโยชน์ด้าน concurrency เลย มีเพียงเซิร์ฟเวอร์ ASGI ที่รัน event loop เท่านั้นที่ทำให้ worker ให้บริการคำขออื่นได้ในระหว่าง `await` **เหตุใด `Article.objects.get(pk=1)` จึงยกข้อยกเว้น `SynchronousOnlyOperation` ภายใน async view?** การเรียก ORM แบบ synchronous จะบล็อก event loop ทำให้ทุกคำขออื่นบน worker ตัวนั้นค้าง Django จึงตรวจพบบริบท async และปฏิเสธ วิธีแก้คือเมธอดแบบ async อย่าง `await Article.objects.aget(pk=1)` หรือ `sync_to_async` สำหรับโค้ดที่ไม่มีคู่แบบ async **Async ORM ของ Django ไม่บล็อกจริงหรือไม่?** ไม่จริง เมธอดแบบ async เป็น wrapper เพื่อความสะดวก คิวรีที่อยู่ข้างใต้ยังรันใน threadpool เพราะไดรเวอร์ฐานข้อมูลไม่ได้ผสานรวมสำหรับการทำงานแบบ async โดยกำเนิดตั้งแต่ต้นจนจบ ประโยชน์คือการทำให้ loop ว่างอยู่ ไม่ใช่ database I/O แบบขนานที่ระดับไดรเวอร์ **ควรเลือก async view แทน task queue อย่าง Celery เมื่อใด?** async view เหมาะกับ concurrency ของ I/O ที่ผูกกับคำขอซึ่งต้องทำให้เสร็จก่อนการตอบสนอง เช่น การรวบรวมข้อมูลจากหลาย API ส่วนงานที่มีอายุยืนกว่าคำขอ, ต้องมีการลองใหม่ หรือรันเป็นเวลาหลายนาที ควรอยู่ใน Celery หรือ background task ของ Django เพราะการถือการเชื่อมต่อ HTTP เปิดค้างไว้เพื่องานนั้นคือการสิ้นเปลืองทรัพยากรเซิร์ฟเวอร์ **ต้นทุนของการผสม middleware แบบ sync กับ async คืออะไร?** การเปลี่ยนผ่านแต่ละครั้งระหว่างองค์ประกอบแบบ sync กับ async บังคับให้ Django กระโดดไปมาระหว่าง event loop กับ thread ผ่าน `sync_to_async` หรือ `async_to_sync` คำขอที่ข้ามขอบเขตนั้นซ้ำ ๆ จะจ่าย overhead ที่สะสมขึ้น ดังนั้น stack ของ middleware ที่เป็นแบบเดียวกันทั้งหมดจึงทำงานได้ดีกว่าแบบที่สลับกันไปมา ## บทสรุป - async view มอบ concurrency เฉพาะบนเซิร์ฟเวอร์ ASGI เท่านั้น view เดียวกันภายใต้ WSGI จะรันแบบ synchronous ผ่าน `async_to_sync` - ดีพลอย Uvicorn ผ่านแพ็กเกจ `uvicorn-worker` ภายใต้ Gunicorn หรือใช้ Granian เพื่อ throughput สูงสุด และถือว่าการตั้งค่า ASGI เป็นขั้นตอนการดีพลอยที่ต้องทำอย่างชัดเจน - หันมาใช้ async view เมื่อคำขอต้องรอการเรียก external I/O ที่ช้าหลายครั้ง และคงให้ endpoint ที่คิวรีเร็วและผูกกับ CPU เป็นแบบ synchronous พร้อมเพิ่มจำนวน worker - ใช้ `aget`, `acreate` และ `async for` ภายใน async view และจำไว้ว่า ORM ยังรันคิวรีใน threadpool แทนที่จะเป็น async โดยกำเนิด - ถือว่า `SynchronousOnlyOperation` เป็นราวกันตก แก้ด้วยเมธอดแบบ async หรือ `sync_to_async(thread_sensitive=True)` อย่าปิดการตรวจสอบเด็ดขาด - คงให้ stack ของ middleware เป็น sync ล้วนหรือ async ล้วนเพื่อเลี่ยงการจ่ายค่าปรับจากการสลับ thread ทุกครั้งที่ข้ามขอบเขต --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/th/blog/django/django-async-views-asgi-2026