POC workspace cho mô hình Odoo ecommerce high-traffic với:
- sản phẩm có biến thể (ví dụ size, màu)
- giá và visibility theo nhóm người dùng
- edge cache / local CDN trong một
docker-compose.yml - Redis làm lớp cache dễ chuyển sang production
Odoo là một nền tảng ERP mã nguồn mở phổ biến với module ecommerce tích hợp, nhưng kiến trúc monolith và cách xử lý cache mặc định của nó có thể gặp khó khăn khi phục vụ các cửa hàng trực tuyến có lưu lượng truy cập cao và yêu cầu cá nhân hóa. Vấn đề này xuất phát từ các nguyên nhân cốt lõi sau, liên quan đến cách Odoo xử lý rendering, cache và logic kinh doanh:
Odoo tính toán và render lại toàn bộ mã HTML (QWeb) cho mỗi HTTP request. Dưới áp lực traffic lớn, điều này làm cạn kiệt tài nguyên CPU (worker pool) nhanh chóng và đẩy độ trễ TTFB lên rất cao.
Cụ thể, luồng xử lý một request /shop đi qua:
- Controller
website_salegọirequest.render()→ delegate sangResponseobject (http.py#L1767) - Khi response được finalize,
Response.render()gọiir.ui.view._render_template()(http.py#L1392) - QWeb engine compile template thành Python function (bước này được cache qua
@ormcache—base/models/ir_qweb.py#L617) - Tuy nhiên, việc thực thi function đó vẫn chạy fresh mỗi request với context riêng (
ir_qweb.py#L605:render_template(irQweb, values)) - Không có bất kỳ HTTP-level output cache nào (không ETag, không
Cache-Controlcho rendered HTML)
Kết luận: Template compilation được cache, nhưng template execution và HTML output thì không. Mỗi request tạo ra một chuỗi
''.join(rendering)mới hoàn toàn.
Tính toán giá (Pricelist) nhiều cấp độ, kiểm tra tồn kho realtime và quản lý biến thể tạo ra chi phí truy vấn SQL đắt đỏ. Khi traffic tăng đột biến (flash-sale), dễ gây khóa dòng (Row-level Locking) làm quá tải cơ sở dữ liệu.
Pricelist — độ phức tạp O(n × m):
- Controller
/shopload mặc định 20 sản phẩm/trang (ppg = website.shop_ppg or 20) - Gọi
products._get_sales_prices(website)→ triggerpricelist._compute_price_rule()(product_pricelist.py#L160) - Hàm này duyệt qua mỗi sản phẩm × mỗi pricelist rule để tìm rule phù hợp (
_is_applicable_for()), kèm thêm truy vấn fiscal position và tax mapping
Stock — 3 query GROUP BY trên mỗi batch:
free_qtylà computed field, gọi_compute_quantities_dict()(stock/models/product.py#L125) thực hiện 3 câu_read_group()lênstock.quantvàstock.move- Nếu
free_qtyđược truy xuất từng sản phẩm riêng lẻ (không batched), mỗi sản phẩm sinh riêng 3 query
Row-level Locking khi ghi tồn kho:
stock.quant._update_available_quantity()sử dụngSELECT ... FOR NO KEY UPDATE SKIP LOCKED(stock_quant.py#L1088) để serialize các giao dịch trừ kho đồng thời — đảm bảo tính nhất quán nhưng đánh đổi throughput dưới tải cao
Rất khó áp dụng Full-page cache ở mức Edge/Nginx vì HTML được render chứa dữ liệu theo từng user session. Nếu cache bừa bãi, user B sẽ thấy giỏ hàng hoặc giá của user A.
Bằng chứng từ source code Odoo 18:
- Cart badge: Template
website_sale.header_cart_linkđọcrequest.session['website_sale_cart_quantity']và render trực tiếp vào HTML. Chính Odoo đã đánh dấu đoạn này bằngt-nocache="The number of products is dynamic, this rendering cannot be cached."(website_sale/views/templates.xml#L3) - User avatar & name: Template
portal.frontend_layoutnhúnguser_id.namevàimage_data_uri(user_id.avatar_256)trực tiếp vào thẻ<img>và<span>của header (portal/views/portal_templates.xml#L110) - Pricelist per session: Controller
/shoplưuwebsite_sale_current_pl(pricelist ID) vàorequest.sessionvà dùng nó để tính giá — mỗi user/nhóm user có thể nhận pricelist khác nhau (website_sale/controllers/main.py#L291) - Permission flags: Với user đã đăng nhập,
website_templates.xmlnhúng các data attribute riêng (data-can-publish,data-editable-in-backend) vào thẻ<html>, khiến HTML khác biệt theo quyền
Giỏ hàng e-commerce là stateful session. Mặc định Odoo ghi session xuống filesystem, khi lượng truy cập lên hàng chục nghìn, IOPS ổ cứng sẽ trở thành nút thắt vật lý.
- Odoo sử dụng class
FilesystemSessionStorekế thừa từ Werkzeug (http.py#L897) - Session files được phân tán vào 4096 thư mục con (hash 2 ký tự đầu của
sid) tại đường dẫn{data_dir}/sessions/(tools/config.py#L800) - Property
session_stoređược khởi tạo duy nhất một lần và luôn trỏ đến filesystem (http.py#L2303) - Không có backend Redis/Memcached built-in — cần module bên ngoài để thay đổi session store
Repo hiện đang ở giai đoạn planning-first. Chưa có scaffold ứng dụng hoặc docker-compose.yml; trước mắt repo tập trung vào quyết định kiến trúc và kế hoạch triển khai POC. see docs/poc-plan.md để biết chi tiết.
docs/poc-plan.md— kế hoạch triển khai POC, stack service, cache strategy, sync workflow, và danh sách module custom đề xuất.docs/benchmark-seeding.md— cách seed nhanh 100–200+ sản phẩm benchmark có variant và ảnh để load test / stress test storefront.docs/k6-native-vs-edge.md— kịch bản k6 để benchmark tuần tự native Odoo origin so với edge storefront trong cùng stack..github/copilot-instructions.md— hướng dẫn ngắn cho agent khi làm việc trong repo này.
- Giữ
website_sale+ QWeb cho page shell. - Cache ở lớp edge cho HTML public an toàn và static assets.
- Tách giá, tồn kho, cart badge, và visibility theo persona sang batch JSON API.
- Dùng Redis cho cache key/tag/invalidation.
- Dùng worker/job bất đồng bộ để purge và warm cache.
- Native Odoo:
http://localhost:8069 - Edge HTTP:
http://shop.local.test:8080 - Edge HTTPS + HTTP/2:
https://shop.local.test:8443 - CDN HTTPS + HTTP/2:
https://cdn.local.test:8443
OpenResty sẽ tự sinh self-signed certificate local vào docker/openresty/certs/ nếu chưa có sẵn. Đây là HTTPS dev-only để kiểm tra browser waterfall, Lighthouse và HTTP/2; k6 nội bộ vẫn có thể tiếp tục dùng cổng HTTP hiện tại.