Khi phỏng vấn vị trí Python Backend từ cấp độ Mid đến Senior, nhà tuyển dụng hiếm khi dừng lại ở cú pháp thông thường mà sẽ đào sâu vào cơ chế vận hành bên dưới (under the hood). Việc hiểu rõ cách Python quản lý bộ nhớ, xử lý concurrency và cách ứng dụng giao tiếp với web server/database chính là ranh giới phân biệt giữa một lập trình viên chỉ biết dùng framework và một kỹ sư backend có tư duy hệ thống vững chắc.
1. Concurrency: Bản chất của GIL, Threading, Multiprocessing và Asyncio
Global Interpreter Lock (GIL) là cơ chế mutex của CPython nhằm đảm bảo tính thread-safe khi quản lý bộ nhớ, ngăn không cho nhiều native thread thực thi Python bytecode cùng một thời điểm. Đây là câu hỏi kinh điển nhằm đánh giá xem ứng viên có hiểu đúng giới hạn và thế mạnh của Python khi xây dựng hệ thống chịu tải hay không.
Trong các hệ thống backend, phần lớn tác vụ là I/O-bound (gọi database, đọc ghi cache, gọi third-party API). Với tác vụ này, threading hoặc asyncio phát huy tối đa hiệu quả vì GIL sẽ được giải phóng trong lúc chờ I/O hoàn tất. Ngược lại, với tác vụ CPU-bound (xử lý ảnh, mã hóa dữ liệu, tính toán ma trận), việc dùng multi-threading không giúp tăng tốc mà thậm chí làm giảm performance do overhead của context switching; giải pháp bắt buộc là sử dụng multiprocessing hoặc đẩy sang task queue như Celery.
- GIL chỉ giới hạn việc thực thi bytecode trên nhiều core CPU, không bảo vệ race condition ở tầng logic ứng dụng (vẫn cần threading.Lock cho shared state).
- Asyncio sử dụng mô hình Cooperative Multitasking dựa trên Single-Threaded Event Loop, giảm thiểu memory overhead so với Thread Pool khi duy trì hàng chục nghìn kết nối đồng thời (WebSockets, Long Polling).
- Mô hình triển khai thực tế: Kết hợp Pre-fork Worker Model (Multiprocessing qua Gunicorn/Uvicorn) với Asyncio Event Loop bên trong mỗi worker để tận dụng tối đa multi-core CPU.
2. Quản lý bộ nhớ: Reference Counting, Generational GC và `__slots__`
CPython quản lý bộ nhớ chủ yếu bằng cơ chế Reference Counting: mỗi object duy trì một biến đếm số lượng tham chiếu trỏ tới nó. Ngay khi biến đếm về 0, bộ nhớ được giải phóng ngay lập tức. Tuy nhiên, điểm yếu chí mạng của Reference Counting là không thể tự xử lý chu kỳ tham chiếu (reference cycles - ví dụ Object A trỏ tới B và B trỏ ngược lại A).
Để giải quyết bài toán này, Python bổ sung bộ gom rác Generational Garbage Collector (GC) chia các object thành 3 thế hệ (Generation 0, 1, 2) dựa trên tuổi thọ. GC sẽ quét định kỳ để phát hiện và cô lập các chu kỳ tham chiếu không còn sử dụng. Trong backend quy mô lớn, việc tạo hàng triệu object ngắn hạn có thể kích hoạt GC liên tục, gây ra hiện tượng stop-the-world latency spikes.
- Memory Leak trong Python thường xuất phát từ: global variables giữ tham chiếu, listener/callback không được unregister, hoặc chu kỳ tham chiếu có định nghĩa hàm `__del__` (trước Python 3.4).
- Sử dụng `__slots__` trong class để vô hiệu hóa việc tạo dynamic `__dict__`, giúp tiết kiệm từ 30% đến 50% RAM khi phải khởi tạo hàng triệu instance của data models.
- Kỹ thuật tối ưu production: Tinh chỉnh ngưỡng GC (gc.set_threshold) hoặc tắt GC tự động trong quá trình fork process (như cách Instagram tối ưu Django) để tránh Copy-on-Write memory footprint.
3. Generator, Iterator và Lazy Evaluation trong xử lý dữ liệu lớn
Một lỗi phổ biến của lập trình viên là load toàn bộ dữ liệu từ database hoặc file log dung lượng hàng gigabyte vào một list bộ nhớ (Eager Evaluation), dẫn tới lỗi Out-of-Memory (OOM) làm crash server. Nhà tuyển dụng thường yêu cầu giải quyết bài toán stream processing để kiểm tra hiểu biết về Generator.
Generator cho phép thực hiện Lazy Evaluation (chỉ tính toán và sinh giá trị khi được yêu cầu thông qua từ khóa `yield`), duy trì state giữa các lần gọi mà chỉ chiếm dung lượng bộ nhớ cố định O(1) bất kể kích thước tập dữ liệu.
- Phân biệt rõ: Iterable (triển khai `__iter__`), Iterator (triển khai `__next__`), và Generator (cách đơn giản nhất để tạo Iterator thông qua function chứa `yield`).
- Ứng dụng thực tế: Xử lý Server-Sent Events (SSE), streaming response file CSV dung lượng lớn trong Django/FastAPI, và batch processing hàng triệu dòng log mà không làm tăng RAM usage.
4. Kiến trúc Web: Phân biệt WSGI vs ASGI và Request Lifecycle
Khi triển khai một ứng dụng Python backend lên production, câu hỏi kiến trúc nền tảng luôn là: Request từ client đi qua những thành phần nào trước khi chạm đến code của bạn? Hiểu rõ giao thức WSGI (Web Server Gateway Interface) và ASGI (Asynchronous Server Gateway Interface) là điều bắt buộc.
WSGI là tiêu chuẩn đồng bộ truyền thống (Synchronous), mỗi request chiếm dụng một worker thread hoặc process (tiêu biểu: Django truyền thống, Flask). ASGI ra đời để kế thừa và khắc phục giới hạn đó, hỗ trợ cả async/await, WebSockets, và HTTP/2 (tiêu biểu: FastAPI, Starlette, Django Channels). WSGI server không thể xử lý tốt hàng nghìn kết nối mở đồng thời nếu không tốn kém tài nguyên hệ thống.
- Request Flow chuẩn production: Client -> Nginx (Reverse Proxy, SSL Termination, Static Files) -> Unix Socket / TCP -> Gunicorn/Uvicorn (Process Manager & ASGI/WSGI Server) -> Application Framework (Router, Middleware, Controller).
- Tư duy chọn tech stack: Dùng WSGI + Gunicorn cho các dịch vụ CRUD truyền thống, tính toán nặng; Dùng ASGI + Uvicorn cho các microservice I/O-heavy, streaming API, WebSockets.
5. Cạm bẫy ORM và Quản lý Database Connection
ORM (Object-Relational Mapping) như SQLAlchemy hay Django ORM giúp tăng tốc độ phát triển nhưng dễ tạo ra các truy vấn SQL kém hiệu quả. Lỗi phổ biến nhất mà interviewer hay đưa vào bài test live coding là vấn đề N+1 Query: thực hiện 1 truy vấn lấy danh sách N bản ghi cha, sau đó lặp qua từng bản ghi để gửi thêm N truy vấn lấy dữ liệu con.
Bên cạnh đó, việc quản lý Database Connection Pooling là yếu tố sống còn cho backend scale. Tạo một database connection mới tốn rất nhiều chi phí (TCP handshake, SSL negotiation, authentication, process allocation phía DB). Nếu backend worker mở connection vô tội vạ, database server sẽ nhanh chóng chạm ngưỡng `max_connections` và từ chối phục vụ.
- Khắc phục N+1 Query: Sử dụng `select_related` (JOIN ở DB level cho quan hệ 1-1, Foreign Key) và `prefetch_related` (tách 2 query và gom nhóm bằng Python cho quan hệ M-N, 1-N).
- Chiến lược Connection Pool: Sử dụng PgBouncer đứng trước PostgreSQL hoặc cấu hình Pool size phù hợp với số lượng Worker Process trong framework; tránh việc mỗi ephemeral async task lại tự ý mở một connection riêng biệt.
- Cẩn trọng với Lazy Loading trong serializer: Luôn monitor các câu lệnh SQL thực tế chạy dưới database thông qua công cụ profiling hoặc APM (Datadog, New Relic) trên môi trường staging.
Ghi nhớ nhanh
- Xác định đúng tính chất tác vụ (I/O-bound hay CPU-bound) để chọn mô hình Asyncio, Multi-threading hoặc Multiprocessing phù hợp với cơ chế GIL.
- Nắm vững nguyên lý Reference Counting và Generational GC để phòng ngừa memory leak và tối ưu hóa latency cho production.
- Ứng dụng Generator và Lazy Evaluation khi xử lý tập dữ liệu lớn nhằm giữ mức tiêu thụ RAM ở ngưỡng O(1).
- Hiểu rõ Request Lifecycle từ Nginx qua WSGI/ASGI server tới framework để debug chính xác các vấn đề nghẽn cổ chai (bottleneck).
- Luôn kiểm soát triệt để câu lệnh SQL được ORM generate, xử lý dứt điểm bài toán N+1 và thiết lập Database Connection Pool chặt chẽ.