Flask API รับมือ 50,000 คำขอต่อวินาทีได้อย่างไร

AI Dev Thai
AI Dev Thaiรีวิว AI · สอน Coding · หาเงินจาก Tech

Flask API รับมือ 50,000 คำขอต่อวินาทีได้อย่างไร

Flask API รับมือ 50,000 คำขอต่อวินาทีได้อย่างไร

Flask handles 50,000 requests before you notice lag — ประโยคนี้อาจฟังดูเกินจริงไปบ้างสำหรับมือใหม่ที่เพิ่งเริ่มต้นกับ Python และ Flask แต่เชื่อผมเถอะว่ามันเป็นไปได้ และบทความนี้จะเปิดเผยเคล็ดลับเบื้องหลังว่า Flask API ที่ดูเรียบง่ายของเรานั้นสามารถรองรับโหลดมหาศาลได้อย่างไร โดยเจาะลึกเข้าไปในกลไกการทำงานภายในตั้งแต่ต้นจนจบ การทำความเข้าใจพื้นฐานเหล่านี้จะช่วยให้คุณออกแบบและปรับแต่ง API ให้มีประสิทธิภาพสูงสุดไม่ว่าจะเป็นการสร้างโปรดักต์ใหม่หรือแม้แต่การลอง สร้างรายได้จากการขาย API ของคุณเอง

Key Facts ที่คนส่วนใหญ่ไม่รู้

  • Flask’s routing system uses Werkzeug’s Map class which compiles URL rules into a single regex tree, reducing lookup time from O(n) to O(log n) for 100+ endpoints
  • Instagram served 30 million users on Django before switching, but Pinterest still runs Flask APIs handling 250 billion pins with only 2.7ms average response time
  • Flask’s request context uses LocalStack and LocalProxy objects that leverage Python’s threading.local, allowing 1,000+ concurrent requests per worker without variable collision

ทำไมต้องเรียนรู้เรื่องนี้?

ในโลกที่ทุกอย่างเชื่อมโยงกันด้วย API การสร้าง API ที่เสถียรและรวดเร็วเป็นสิ่งสำคัญ ไม่ว่าคุณจะสร้างแอปพลิเคชันสำหรับโทรศัพท์มือถือ เว็บไซต์ หรือระบบหลังบ้าน การจัดการกับปริมาณคำขอที่สูงเป็นความท้าทายที่นักพัฒนาทุกคนต้องเจอ การรู้ว่า Flask ทำงานอย่างไรภายใต้ hood จะช่วยให้คุณ:

  • ปรับปรุงประสิทธิภาพ: เข้าใจจุดคอขวดและปรับแต่งโค้ดของคุณ
  • แก้ไขปัญหาได้ดีขึ้น: วิเคราะห์และแก้ไขข้อผิดพลาดที่เกิดขึ้นภายใต้โหลดสูง
  • ออกแบบระบบที่ยืดหยุ่น: วางแผนสถาปัตยกรรมที่สามารถขยายได้ในอนาคต
  • ประหยัดค่าใช้จ่าย: ใช้ทรัพยากรเซิร์ฟเวอร์ได้อย่างมีประสิทธิภาพสูงสุด

สิ่งที่ต้องเตรียม

ก่อนที่เราจะดำดิ่งลงไปในรายละเอียด ขอแนะนำให้คุณมีสิ่งต่อไปนี้

  1. Python 3.x: ติดตั้งในเครื่องของคุณ
  2. pip: ตัวจัดการแพ็คเกจของ Python (ปกติจะมาพร้อมกับ Python)
  3. ความเข้าใจพื้นฐานเกี่ยวกับ Flask: การสร้าง route, view function
  4. ความเข้าใจพื้นฐานเกี่ยวกับ HTTP: คำขอ (request), การตอบกลับ (response)
  5. Terminal หรือ Command Prompt: สำหรับรันคำสั่ง
  6. Editor ที่คุณถนัด: เช่น VS Code, PyCharm

ขั้นตอนโดยละเอียด: กลไกภายในของ Flask ในการจัดการคำขอ

1. Flask instantiation และการเตรียมตัวของ WSGI Application

ทุกอย่างเริ่มต้นที่การสร้างอินสแตนซ์ของ Flask application ด้วย app = Flask(__name__)
ขั้นตอนนี้ Flask instantiation creates a WSGI application object that registers itself with Werkzeug’s DispatcherMiddleware and initializes an empty url_map dictionary นี่คือหัวใจสำคัญเพราะ Flask ไม่ได้ทำงานด้วยตัวเอง แต่จะใช้ มาตรฐาน WSGI (Web Server Gateway Interface) เป็นตัวกลางในการสื่อสารกับ Web Server (เช่น Gunicorn, uWSGI) นอกจากนี้ยังสร้าง url_map ซึ่งเป็น dictionary เปล่าๆ สำหรับเก็บกฎการ routing.


import json
from flask import Flask, request, jsonify

# Step 1: Flask instantiation creates a WSGI application object
app = Flask(__name__)

# ในขั้นตอนนี้ app.url_map จะถูกสร้างขึ้นมาเป็น object ของ Map() จาก Werkzeug
# และว่างเปล่าในตอนเริ่มต้น
# print(app.url_map) # เพื่อดูโครงสร้างหลังสร้าง
# <Map []>
    

2. การลงทะเบียน Route ด้วย Decorator และ Rule Object

เมื่อคุณใช้ @app.route('/path') เพื่อกำหนด endpoint
The

@app.route

decorator captures the function reference and calls

add_url_rule()

, which converts the URL string into a Rule object using Werkzeug’s routing parser นั่นหมายความว่า decorator นี้ไม่ได้แค่ผูก URL กับฟังก์ชัน แต่เบื้องหลังมันเรียกใช้เมธอด add_url_rule() ของ Flask ซึ่งจะสร้างออบเจกต์ Rule จาก Werkzeug เพื่อเก็บข้อมูลเกี่ยวกับ URL, เมธอด HTTP ที่อนุญาต และอื่นๆ


# Step 2: @app.route decorator calls add_url_rule()
@app.route('/hello')
def hello_world():
    # การทำงานแบบง่ายๆ ที่ไม่ซับซ้อน
    return jsonify({"message": "Hello, Flask!"})

@app.route('/echo', methods=['POST'])
def echo_data():
    data = request.json
    return jsonify(data)

# ตอนนี้ app.url_map จะมี Rule object ที่สร้างจาก route ด้านบน
# print(app.url_map) # เพื่อดูการเปลี่ยนแปลง
# <Map ['/hello', '/echo']>
    

3. การคอมไพล์ Rule Objects เป็น Regex Tree

Rule objects ที่สร้างขึ้นจะถูกรวมเข้าด้วยกันอย่างชาญฉลาด
Rule objects compile into regex patterns and get inserted into the Map data structure as a directed acyclic graph for O(log n) matching performance นี่คือจุดที่ประสิทธิภาพของ Flask เริ่มฉายแสง เมื่อมี Rule เป็นร้อยหรือเป็นพัน Flask (ผ่าน Werkzeug) จะไม่ตรวจสอบ Rule ทีละตัว (ซึ่งจะเป็น O(n)) แต่จะรวมพวกมันเข้าเป็นโครงสร้างข้อมูลแบบ Directed Acyclic Graph (DAG) ที่ใช้ Regular Expression ในการจับคู่ ทำให้การค้นหา Rule สำหรับ URL ที่เข้ามาทำได้เร็วมากในระดับ O(log n) นี่ทำให้ Flask ไม่ช้าลงอย่างมีนัยสำคัญแม้จะมี endpoint จำนวนมาก อ่านบทความเกี่ยวกับ Coding เพิ่มเติม

Flask’s routing system uses Werkzeug’s Map class which compiles URL rules into a single regex tree, reducing lookup time from O(n) to O(log n) for 100+ endpoints.

4. Request Context และ Thread-Local Storage

เมื่อมีคำขอ HTTP เข้ามา Flask จะสร้าง “สภาพแวดล้อม” เฉพาะสำหรับคำขอนั้น
When a request arrives, Flask creates a RequestContext object that pushes itself onto the _request_ctx_stack using LocalStack’s thread-local storage mechanism สิ่งนี้สำคัญมากสำหรับการรองรับการทำงานพร้อมกันหลายๆ คำขอ (concurrent requests) Flask ใช้ threading.local ของ Python เพื่อให้แต่ละ thread (ที่ประมวลผลคำขอแต่ละรายการ) มี RequestContext และ Global objects (เช่น request, g) ของตัวเอง ไม่ปะปนกันระหว่างคำขอ ทำให้คุณสามารถเข้าถึง request.json หรือ request.args ได้อย่างปลอดภัยโดยไม่ต้องกังวลเรื่องการชนกันของข้อมูล

หากคุณอยากรู้ว่า Python จัดการกับสิ่งเหล่านี้ภายในได้อย่างไร แนะนำให้อ่าน Python ทำงานภายในอย่างไร


# Step 4: RequestContext creation and push to stack
# (ส่วนนี้เกิดขึ้นภายใน Flask โดยอัตโนมัติเมื่อมี request เข้ามา)
# เราไม่จำเป็นต้องเขียนโค้ดตรงนี้โดยตรง แต่เป็นกลไกที่เกิดขึ้น
# เมื่อมี request เข้ามา request object จะถูกสร้างขึ้นมาให้ใช้ได้
# ผ่าน LocalProxy ของ Flask
@app.route('/user/<int:user_id>')
def get_user(user_id):
    # 'request' object ใช้งานได้เพราะ RequestContext ถูก push เข้ามา
    # ถ้าไม่มี request context เราจะไม่สามารถเข้าถึง request ได้
    return jsonify({"user_id": user_id, "method": request.method})

    

5. การจับคู่ URL และการค้นหา View Function

เมื่อ RequestContext ถูกสร้างขึ้น Flask จะทำการจับคู่ URL ที่เข้ามา
The URL adapter’s match() method traverses the compiled rule tree, extracting path variables into a dictionary and identifying the target view function ตัวแปลง URL จะใช้ Regex Tree ที่เรากล่าวถึงในข้อ 3 เพื่อจับคู่ URL ที่เข้ามา เช่น /user/123 และดึงค่าตัวแปร user_id (คือ 123) ออกมา จากนั้นจึงระบุว่าควรเรียกใช้ view function ชื่ออะไร (ในที่นี้คือ get_user)

6. การประมวลผลก่อน Request (before_request hooks)

ก่อนที่ view function จะทำงาน Flask จะเรียกใช้ฟังก์ชัน “hook” บางตัว
Flask calls

preprocess_request()

which executes all registered

before_request

hooks in registration order, allowing middleware to modify request or abort early นี่เป็นจุดที่คุณสามารถใส่ logic ก่อนที่ view function หลักจะทำงาน เช่น การตรวจสอบโทเค็นการยืนยันตัวตน (authentication token), การตรวจสอบสิทธิ์ (authorization), การบันทึก log หรือแม้กระทั่งการเปลี่ยนเส้นทาง (redirect) โดยสามารถยกเลิก request ได้หากไม่ผ่านเงื่อนไข


# Step 6: before_request hooks
@app.before_request
def auth_check():
    # สมมติการตรวจสอบ token ง่ายๆ
    if 'X-API-Key' not in request.headers and request.path != '/hello':
        return jsonify({"error": "Unauthorized"}), 401
    
    # สามารถเพิ่ม logic ซับซ้อน เช่น ดึงข้อมูลจากฐานข้อมูล
    # หรือใช้ libraries สำหรับ Data Science บางตัว แต่ระวังเรื่อง performance
    # หากอยากรู้เรื่อง Python Libraries สำหรับ Data Science อ่านเพิ่มเติมได้ที่
    # Python Libraries สำหรับ Data Science

    # ตั้งค่าตัวแปร global สำหรับ request นี้
    # Flask's request context uses LocalStack and LocalProxy objects that leverage Python's threading.local
    # allowing 1,000+ concurrent requests per worker without variable collision
    from flask import g
    g.user_id = 123 # สมมติว่าดึงมาจาก token

@app.route('/secure')
def secure_data():
    from flask import g # เข้าถึง g ที่ตั้งค่าไว้ใน before_request
    return jsonify({"message": f"Welcome, user {g.user_id}", "access": "granted"})
    

7. การเรียกใช้ View Function และการสร้าง Response

เมื่อทุกอย่างผ่านการตรวจสอบ View function ที่ถูกจับคู่ไว้จะถูกเรียกใช้
The matched view function executes and returns data, which Flask wraps in a Response object by calling

make_response()

that sets default headers including Content-Type ฟังก์ชัน view ของคุณจะส่งคืนข้อมูลอะไรก็ได้ (เช่น JSON, HTML, string) Flask จะนำข้อมูลนั้นมาสร้างเป็นออบเจกต์ Response จริงๆ โดยอัตโนมัติ และกำหนดค่าเฮดเดอร์เริ่มต้น เช่น Content-Type: application/json หรือ text/html ให้เหมาะสม

8. การประมวลผลหลัง Request (after_request hooks) และการเคลียร์ Context

หลังจาก View function ทำงานเสร็จและ Response ถูกสร้างขึ้น Flask จะเรียกใช้ hook สุดท้าย
After response creation,

process_response()

runs all

after_request

handlers, then

LocalStack.pop()

removes the request context, cleaning up thread-local variables automatically นี่คือโอกาสสุดท้ายในการปรับแต่ง Response เช่น การเพิ่ม custom headers, การบีบอัดข้อมูล หรือการบันทึก log ของ Response และที่สำคัญคือ Flask จะทำการลบ RequestContext ออกจาก stack โดยอัตโนมัติ ซึ่งเป็นการทำความสะอาดทรัพยากร thread-local ที่ใช้สำหรับคำขอนั้นๆ เพื่อเตรียมพร้อมสำหรับคำขอถัดไป


# Step 8: after_request hooks and context cleanup
@app.after_request
def log_response(response):
    # สามารถแก้ไข response ได้ก่อนส่งกลับ
    response.headers["X-Powered-By"] = "AiDevThai Flask"
    # print(f"Response Status: {response.status}")
    return response

# (ส่วนนี้เกิดขึ้นภายใน Flask โดยอัตโนมัติเมื่อ request จบลง)
# RequestContext ถูก pop ออกจาก stack

# วิธี run server ที่เหมาะสำหรับการพัฒนาเท่านั้น
# Flask's development server uses Werkzeug's BaseWSGIServer which forks from Python's HTTPServer,
# limiting it to exactly 1 request at a time unlike Gunicorn's 4-12 worker default
if __name__ == '__main__':
    app.run(debug=True, port=5000)
    

ตัวอย่างโค้ดสมบูรณ์


import json
from flask import Flask, request, jsonify, g, make_response

app = Flask(__name__)

# ----------------------------------------------------------------------
# BEFORE REQUEST HOOKS
# ----------------------------------------------------------------------
@app.before_request
def auth_check():
"""
ตรวจสอบ API Key ก่อนประมวลผล request จริง
จำลองการตรวจสอบ key สำหรับแต่ละ request (ยกเว้น /hello)
"""
if request.path != '/hello' and request.path != '/' and 'X-API-Key' not in request.headers:
return jsonify({"error": "Unauthorized - Missing X-API-Key"}), 401

# หากมีการตรวจสอบ key จริงๆ จะดึง user_id จาก DB หรือ JWT
# แล้วเก็บไว้ใน g (thread-local global object)
if 'X-API-Key' in request.headers:
# สมมติฐาน: API Key 'test-key' มาจาก user_id 123
if request.headers['X-API-Key'] == 'test-key':
g.user_id = 123
else:
return jsonify({"error": "Unauthorized - Invalid X-API-Key"}), 401
else:
g.user_id = None # สำหรับ path ที่ไม่ต้องการการยืนยันตัวตน

# print(f"Before Request: User ID in g: {g.user_id}")

# ----------------------------------------------------------------------
# AFTER REQUEST HOOKS
# ----------------------------------------------------------------------
@app.after_request
def log_response(response):
"""
แก้ไข response

📬 ชอบบทความนี้?

สมัครรับบทความใหม่เข้าเมลทุกสัปดาห์ ฟรี ไม่สแปม

🎁

ปลั๊กอิน WordPress จากเรา: Exit Pop Pro

ป๊อปอัพ exit-intent ที่แจก PDF ฟรี แลกอีเมล — เก็บ subscriber เข้า WordPress ของคุณโดยตรง จ่ายครั้งเดียว $29 ไม่มีค่ารายเดือน ไม่ต้องง้อ SaaS

ดูรายละเอียด →
📺 YouTube📘 Facebook