Decisions - ntphiep/zhesen-main GitHub Wiki

Decisions

Có 5 quyết định đã nghiên cứu. Mục 1 tới 3 và mục 5 đã chốt, gồm 2 về hạ tầng, 1 về bố cục trang chi tiết từ và 1 về tên miền; mục 4 về Cloudflare chờ owner quyết. Mỗi mục ghi kết luận, số đo dẫn tới kết luận đó, phần đã làm, và điều kiện phải xem lại.


1. Chuyển database sang AWS EC2, chạy Supabase self-hosted

Quyết định 2026-09-22. Trạng thái: đã làm, cutover 2026-09-23 (issue #57). Project Supabase Cloud cvltsyoweddhpkomuevz giữ nguyên, đóng băng, tới 2026-10-23 làm đường rollback.

Vấn đề là dung lượng. Gói Free của Supabase cho 500 MB; chạm trần thì database chuyển sang chế độ chỉ đọc cho tới khi xuống dưới 475 MB. Database đang ở 455 MB, trong đó dữ liệu từ điển khoảng 197 MB và hai index PGroonga chiếm 91 MB cố định. Mở rộng vốn tiếng Anh (issue #36) hay nạp thêm bất kỳ nguồn nào đều bị chặn. Triệu chứng thứ hai cùng gốc: gói Free cho 0,5 GB RAM, shared_buffers 224 MB không giữ nổi 455 MB, nên tám lượt tra tiếng Việt gửi đồng thời thì sáu lượt trả statement timeout (issue #7).

Phương án Dung lượng Search tiếng Trung Tháng Sửa code
Supabase Pro 8 GB giữ PGroonga 25 USD tiền mặt 0
EC2 Seoul chạy Supabase self-hosted tuỳ đĩa giữ PGroonga 23 tới 38 USD, credit trả 0
RDS Postgres tuỳ đĩa mất PGroonga, viết lại; vẫn cần một máy cho auth và PostgREST khoảng 40 USD 9 file
Aurora Serverless v2 tuỳ mất PGroonga từ 73 USD 9 file

Chọn EC2 vì ba điều. Credit AWS 163,70 USD trả tiền máy được khoảng bảy tháng, Pro thì không dùng credit được. App chỉ đọc NEXT_PUBLIC_SUPABASE_URL và anon key, nên chạy nguyên bộ Supabase bằng Docker trên EC2 là đổi một biến môi trường, không sửa code, giữ PGroonga, auth và RLS. Máy riêng cho phép cài thêm thứ khác về sau. Marketplace bị loại vì credit không trả; Aurora bị loại vì giá.

graph LR
  V["Vercel icn1 Seoul"] --> CF["CloudFront, HTTPS"]
  CF -->|"VPC origin"| E["EC2 Seoul: Envoy, GoTrue, PostgREST,<br/>Postgres 17.6 với PGroonga"]
  E -.-> S3["S3, pg_dump hằng ngày"]
Loading

Đã làm trước đó, giữ nguyên: nghiên cứu 2026-09-16 đo lex.search tốn 46,3 ms trong Postgres trong khi người dùng chờ 319 tới 582 ms, nguyên nhân là function Vercel ở Washington còn database ở Seoul. vercel.json đã pin function về icn1 Seoul. Kết luận "không chuyển database" hôm đó dựa trên dung lượng 383 MB còn chỗ; điều kiện xem lại là vượt 450 MB, đã tới.

Xem lại khi: credit hết, dự kiến khoảng tháng 4 năm 2027. Lúc đó so tiền mặt cho EC2 với Supabase Pro 25 USD. Cost Explorer ghi khoảng 38 USD/tháng trong 30 ngày tới 2026-10-08, nên credit có thể hết khoảng 2027-01-31 (suy luận); mục 4 so các phương án.


2. Chuyển file phát âm khỏi Wikimedia, nhưng chỉ sau phần ghi công tác giả

Nghiên cứu 2026-09-17, cập nhật 2026-09-26. Trạng thái: bước 1 đã làm, bước 2 và 3 chưa làm.

App đang hotlink thẳng vào upload.wikimedia.org. Chuyển sang S3 ap-northeast-2 đặt sau CloudFront nhanh hơn rõ rệt, nhưng nó biến zhesen từ bên nhúng thành bên phân phối lại, nên phải làm phần ghi công trước.

Phương án TTFB từ Việt Nam Chi phí ở mức tăng trưởng
Giữ Wikimedia (đang chạy) 167 ms khi nóng, 411 ms khi nguội 0 USD
S3 Seoul trực tiếp 277 ms 0,112 USD
S3 Seoul sau CloudFront (khuyến nghị) 32,3 ms khi cache hit 0,007 USD
Supabase Storage chưa đo được 0 USD, nhưng ăn vào hạn mức egress của từ điển

Chi phí không phân biệt được phương án nào: cả bốn đều dưới 0,12 USD một tháng. Chỉ có hai thứ quyết định.

  • Latency. CloudFront phục vụ từ PoP Hà Nội, nhanh hơn Wikimedia 379 ms khi file nguội. Trạng thái nguội mới là thường gặp: 20 trên 25 file đo được age = 0.
  • Giấy phép. 647 trên 699 file có AttributionRequired = true theo Commons API, nên phải ghi công trước khi phân phối lại. Cột lex.pronunciations.attribution có từ migration 0003 nhưng không chỗ nào đọc nó.

Bước 1 đã làm theo cách rẻ hơn dự tính. Sau khi nạp đủ tiếng Anh, số bản ghi tăng từ 699 lên 96.765, nên gọi Commons API cho từng file không còn hợp lý. Mọi chỗ phát bản ghi, gồm trang một từ, sổ tay và các trang luyện tập, có link "nguồn" tới trang file trên Wikimedia Commons, nơi ghi tác giả và giấy phép (issue #29). CC BY-SA 4.0 mục 3(a)(2) chấp nhận ghi công bằng một link tới nguồn chứa thông tin đó (legalcode).

Thứ tự làm, bước một là bắt buộc:

graph TD
  A["1. Link tới trang file trên Commons<br/>ở mỗi bản ghi: đã làm"] --> B["2. Copy file sang S3 ap-northeast-2<br/>dựng CloudFront distribution"]
  B --> C["3. Migration ghi lại audio_url<br/>đổi media-src trong next.config.ts"]
  A -.->|"nếu không làm bước 1"| X["Giữ nguyên Wikimedia"]
  style A fill:#fff3cd,stroke:#b8860b
  style X fill:#fde2e2,stroke:#c0392b
Loading

Xem lại khi: người dùng thật bắt đầu nhận HTTP 429 từ Wikimedia, lúc đó chuyển ngay và bỏ điều kiện làm bước 1 trước, vì khi đó nút phát âm đang hỏng chứ không phải chậm. Hoặc khi dự án bắt đầu tự sinh audio, vì lúc đó cần object store dù sao đi nữa.


3. Bố cục trang chi tiết từ

Quyết định 2026-09-26, cập nhật 2026-09-27. Trạng thái: đã làm. Bản vẽ ở Figma, frame A2 · Desktop 1440 và A2 · Phone 390; ba frame A, B, C phía trên là các hướng đã so trước khi chọn.

Trang cũ đặt 3 nghĩa đầu theo thứ tự từ điển gốc, nên nghĩa thứ tư của take là "bắt bóng" trong cricket. Ví dụ nằm tách khỏi nghĩa. Bốn khối từ liên quan lặp nhau: take up nằm cả ở "Cụm từ chứa từ này" lẫn "Từ cùng gốc". Ở 1440 px cột phải hết sớm và bỏ trống nửa dưới trang.

Vùng Nội dung
Đầu trang Từ, trình độ, phát âm, nút lưu, một dòng nối 5 nghĩa chính, nút nhảy tới từng từ loại, Ví dụ và Từ liên quan
Cột trái Nghĩa nhóm theo từ loại. Mỗi từ loại hiện 5 nghĩa hay dùng nhất, mỗi nghĩa kèm 1 ví dụ của chính nó. Phần còn lại mở ngay tại chỗ. Cuối cột là "Ví dụ khác", 3 câu
Cột phải Dạng từ trên một dòng, Từ liên quan chia tab và bỏ mục trùng, Ngôn ngữ khác, Ngữ pháp, Trợ lý
Điện thoại Một cột: đầu trang, nghĩa, dạng từ, ví dụ, từ liên quan, ngôn ngữ khác, ngữ pháp, trợ lý

Trang rộng 1296 px thay vì 1024 (issue #50, chỉ trang một từ). Trỏ chuột, focus hoặc chạm vào một từ thì trang của từ đó được tải trước, và link hiện chấm chờ trong lúc tải. Không dùng khung chờ loading.tsx: khung đó làm từ không tồn tại trả mã 200 thay vì 404.

Bảng trên mô tả bố cục mặc định, giờ gọi là "Tổng quan". Trang có thêm nút chọn ba bố cục: Tổng quan, Song ngữ (từ và nghĩa tiếng Việt song song, không chia cột trái phải) và Cổ điển (bảng và mục theo thứ tự từ điển giấy). Lựa chọn nhớ theo trình duyệt (components/lookup/WordLayouts.tsx). Một bố cục thứ tư, "Nhiều cột", mở từ liên quan thành cột riêng cạnh trang, bị bỏ theo yêu cầu owner vì thừa so với ba bố cục kia; trình duyệt nào còn nhớ lựa chọn đó rơi về Tổng quan.

Hai phần cần dữ liệu mới. "Nghĩa hay dùng nhất" đọc cột lex.senses.sense_frequency, trước nay để trống. "Ví dụ của chính nghĩa đó" đọc lex.examples.sense_id: câu từ Wiktionary đã có cột này, câu từ Tatoeba thì chưa. Trong 17.427 từ tiếng Anh trình độ A1 tới B2, 12.692 từ đã có ít nhất một ví dụ gắn nghĩa. AI router của project xếp nghĩa, gắn các câu Tatoeba vào nghĩa và viết nghĩa tiếng Việt còn thiếu. Azure Translator dịch những câu ví dụ trang hiện ra. Câu từ Cambridge chưa dùng tới khi kiểm tra xong licence. Từ chưa qua bước này vẫn hiện đúng: nghĩa theo thứ tự cũ, ví dụ gom vào "Ví dụ khác".

Xem lại khi: mở rộng bước dữ liệu ra C1, C2 hoặc sang tiếng Tây Ban Nha. Tiếng Tây Ban Nha hiện chưa có câu ví dụ nào.

4. Cloudflare và nơi đặt hạ tầng sau khi hết credit AWS

Nghiên cứu ngày 2026-10-13, đang chờ owner quyết. Chi tiết, script và số đo nằm ở file 1-cloudflare.md trong folder ~/W/zhesen-docs/2026-10-research/ trên máy của owner.

Không chuyển zhesen sang Cloudflare lúc này. Phần tốn tiền là backend: Postgres với PGroonga, GoTrue, PostgREST, 2 router AI và các batch job. Cloudflare không chạy được phần nào trong số đó. App đang miễn phí trên Vercel Hobby và AWS đang trả bằng credit, nên không sản phẩm Cloudflare nào làm hoá đơn hiện tại thấp hơn. Đo từ Việt Nam, cả 2 đều trả lời từ Hồng Kông hoặc Singapore, Cloudflare trong 135 tới 195 ms và Vercel trong 163 ms.

Có 3 dịch vụ miễn phí của Cloudflare đáng dùng khi có tên miền. DNS quản lý tên miền ở chế độ DNS-only, vì Vercel khuyên không đặt reverse proxy phía trước. Tunnel kèm Access đóng dashboard của 9router và OmniRoute. Turnstile chống bot ở trang đăng ký.

Trần ISR write của Hobby cạn sớm vì 2 lý do. Mỗi deployment có ISR cache riêng, tức cache trang đã render của Vercel, và CI deploy production 138 lần trong 30 ngày. Ở nhịp đó mỗi deploy chỉ còn chỗ render khoảng 241 trang trước khi cạn trần tháng. Một lượt crawl toàn bộ 213.089 URL trong sitemap tốn 4,3 tới 6,4 lần trần đó. Vì vậy gom deploy production còn 1 lần mỗi ngày, chặn crawler huấn luyện AI và đọc trang Usage trước khi nộp sitemap. Hobby cũng chỉ cho dùng phi thương mại, nên bán bất cứ thứ gì cần rời Hobby.

Phương án sau khi hết credit USD/tháng Giờ công Được bán 12 tháng, USD
Giữ Hobby và EC2 như hiện tại 38,00 0 không 342
Hobby và EC2 Savings Plan 1 năm 25,29 0,5 tới 1 không 235
Vercel Pro và EC2 Savings Plan 45,29 1 tới 2 có 483
Workers và EC2 Savings Plan 30,38 16 tới 40 có 659
Hobby và OVH VPS-2 8,50 20 tới 44 không 512
next start và backend trên 1 OVH VPS-2, sau proxy Cloudflare 8,50 30 tới 64 có 752
Như trên, trên Oracle Always Free A1 0 34 tới 74 có 720

Tổng 12 tháng tính giờ công 10 USD/giờ và giả định credit còn 3 tháng. Khi credit hết, thử Oracle Always Free A1 một lần; không được thì chọn OVH VPS-2 sau khi xác nhận kỳ thanh toán. EC2 Savings Plan chỉ hợp khi owner chấp nhận cam kết 12 tháng. Khi bán, chọn Vercel Pro nếu backend còn ở EC2, hoặc tự chạy next start nếu backend đã sang VPS. Rời AWS phải viết lại phần admin, gồm 9 file và 1.642 dòng gọi AWS.

Next.js đã lên 16.3.8 ngày 2026-10-14, sửa lỗ hổng critical chạy code từ xa qua class ImageResponse của next/og và 6 lỗ hổng khác. 16.3.8 cũng là bản thấp nhất mà OpenNext, adapter chạy Next.js trên Workers, nhận.

Xem lại khi owner đọc trang Billing, Credits của AWS, 6 tuần trước ngày credit hết, hoặc khi bắt đầu bán.

5. Tên miền

Chốt ngày 2026-10-13 (issue #89). Chi tiết và các bước nối vào Vercel nằm ở file 2-ten-mien.md trong cùng folder.

Owner đăng ký zhesen.net tại Mắt Bão ngày 2026-10-13, hạn tới 2027-10-13.

zhesen.net là tên chính, chạy HTTPS trên Vercel từ 2026-10-13. DNS đặt ở Cloudflare chế độ DNS-only. www.zhesen.net và zhesen-main.vercel.app chuyển 308 về zhesen.net, trừ đường dẫn /api của tên cũ mà SNS alert và script revalidate còn gọi (commit 7050fbc).

Owner mua thêm zhesen.xyz, zhesen.cloud và zhesen.io.vn cùng ngày. Cả 3 không có nội dung riêng: DNS đặt ở Cloudflare và Vercel chuyển 308 mọi host của chúng về zhesen.net.

Xem lại khi mở rộng ra ngoài Việt Nam, hoặc trước hạn gia hạn 2027-10-13.

⚠️ **GitHub.com Fallback** ⚠️