Phần 1 đọc một bắt tay. Phần này nhân nó lên mười nghìn, vì câu hỏi thực tế của người vận hành không phải "TLS tốn bao nhiêu" mà là "bật TLS trên server đang giữ 10.000 kết nối WebSocket thì cần thêm bao nhiêu RAM, và khi 10.000 client cùng kết nối lại sau một lần deploy thì CPU chịu được không". Hai khoản đó khác nhau về bản chất: một là trạng thái giữ lâu cho mỗi kết nối, một là công việc một lần khi kết nối mở. Bài này đo cả hai với Go crypto/tls, so chứng chỉ ECDSA P-256 với RSA-2048, đo session ticket cắt được gì cho từng loại, và nhân tiện trả lời câu hỏi treo từ phần 1: Kyber trong ClientHello 1.474 byte tốn bao nhiêu CPU.
Ba khoản chi khác nhau của TLS

Hình 1: Ba khoản chi của TLS, cách đo từng khoản, và chi tiết để client không cạn cổng khi đo bắt tay.
TLS có ba khoản chi tách bạch. Giữ kết nối: mỗi tls.Conn giữ trạng thái record layer, khoá đối xứng, bộ đệm đọc và ghi, cộng thêm vào những gì một kết nối plaintext đã tốn. Bắt tay: mỗi kết nối mới cần một lần trao đổi khoá, trong Go 1.23 là X25519 cộng Kyber768, một chữ ký bằng khoá riêng của chứng chỉ, và dẫn xuất khoá; chữ ký là phần phụ thuộc loại chứng chỉ, ECDSA P-256 hay RSA-2048. Resumption: với session ticket, client mang pre-shared key, server không ký và không gửi Certificate, nhưng vẫn trao đổi khoá ECDHE để có forward secrecy. Vì vậy ticket chỉ cắt được phần chữ ký, và phần đó to hay nhỏ tuỳ loại chứng chỉ.
Cách đo. Mục A: client mở 10.000 kết nối và không gửi gì, server báo goroutine, heap, RSS, trước với plaintext rồi với TLS. Mục B: server chạy GOMAXPROCS=1, client 8 hoặc 16 worker lặp connect, bắt tay, gửi một byte, chờ server đóng, trong 8 giây; đo bắt tay mỗi giây, CPU server chia cho số bắt tay ra µs CPU mỗi bắt tay, và p50 phía client. Năm biến thể: plaintext làm mốc, ECDSA full, ECDSA cộng ticket, RSA-2048 full, RSA-2048 cộng ticket. Mục C: ECDSA full trên 4 lõi. Mục D: tắt Kyber ở cả hai phía bằng GODEBUG=tlskyber=0. Một chi tiết kỹ thuật quyết định phép đo có chạy được không: server phải đóng trước, sau khi nhận một byte, để TIME_WAIT nằm phía server; nếu client đóng trước, nó cạn 28.232 cổng ephemeral sau khoảng 15 giây như phần 6 của sê-ri WebSocket đã đo.
Đo thật: 24,5 KB, 4.350, 1.060, 9% và 80%

Hình 2: RAM cho 10.000 kết nối plaintext so với TLS, bắt tay mỗi giây trên một lõi theo loại chứng chỉ và ticket, scale theo lõi, và chi phí của Kyber.
| phép đo | kết quả |
|---|---|
| 10.000 kết nối idle, plaintext | RSS 42,4 MB, heap 8,7 MB |
| 10.000 kết nối idle, TLS 1.3 ECDSA | RSS 287,9 MB, heap 77,8 MB, tức +24,5 KB mỗi kết nối |
| TCP connect trần, 1 lõi | 27.114/s, 21 µs CPU |
| TLS ECDSA full / + ticket, 1 lõi | 4.350/s, 212 µs / 4.786/s, 193 µs |
| TLS RSA-2048 full / + ticket, 1 lõi | 1.060/s, 941 µs / 4.808/s, 192 µs |
| ECDSA full, Kyber tắt, 1 lõi | 6.575/s, 139 µs |
| ECDSA full, 1 lõi, 16 worker | 5.328/s, 184 µs, CPU 7,83 s trong 8 s |
| ECDSA full, 4 lõi, 16 worker | 6.055/s, client là nút thắt |
TLS thêm 24,5 KB cho mỗi kết nối idle, và phần lớn không nằm trong heap. 10.000 kết nối plaintext: 42,4 MB RSS, 8,7 MB heap, mỗi kết nối gần như chỉ là một goroutine và một fd. Cùng 10.000 kết nối qua TLS 1.3: 287,9 MB RSS, 77,8 MB heap, 187,4 MB heap đã xin từ hệ điều hành. Khoản chênh 245 MB là 24,5 KB mỗi kết nối, nhưng heap chỉ tăng 7 KB mỗi kết nối; phần còn lại là bộ đệm record của crypto/tls, thứ không chỉnh cỡ được như bộ đệm của gorilla, cộng với rác do 10.000 bắt tay để lại mà GC chưa trả về. Cộng với 27 KB của một kết nối WebSocket idle ở phần 6, một kết nối WebSocket qua TLS là khoảng 50 KB, 500 MB cho 10.000. Client cũng trả: 122 MB thay vì 16 MB cho 10.000 socket của nó.
Một lõi làm 4.350 bắt tay ECDSA mỗi giây, so với 27.114 TCP connect trần. TCP accept, tạo goroutine, đọc một byte, đóng: 21 µs CPU, 27.114 lần mỗi giây. Thêm bắt tay TLS 1.3 với chứng chỉ ECDSA P-256: 212 µs CPU, 4.350 lần mỗi giây với 8 worker, p50 phía client 1,77 ms. Với 16 worker, một lõi bão hoà ở 5.328 mỗi giây và 184 µs, CPU server 7,83 giây trong 8 giây. Nghĩa là nếu 10.000 client cùng kết nối lại sau deploy, một lõi mất khoảng 2 giây chỉ để bắt tay, và trong 2 giây đó nó không làm gì khác. Mục C cho một bài học về đo: 4 lõi chỉ đạt 6.055 mỗi giây vì server chỉ dùng 2,1 lõi, nút thắt chuyển sang 16 worker của client đang tự làm 16 bắt tay song song với p50 2,62 ms; muốn đo server nhiều lõi phải có client mạnh hơn server.
RSA-2048 đắt gấp 4,4 lần ECDSA, và chữ ký chiếm 78% chi phí. Cùng server một lõi, đổi chứng chỉ sang RSA-2048: 1.060 bắt tay mỗi giây, 941 µs CPU mỗi cái, p50 client 4,74 ms. Hiệu 941 trừ 212 là khoảng 730 µs cho một chữ ký RSA-2048, so với vài chục µs của ECDSA P-256. Đây là lý do mọi CA lớn và CDN đều khuyến khích ECDSA: cùng mức an toàn, bắt tay rẻ hơn 4 lần và chứng chỉ nhỏ hơn. Nếu buộc phải giữ RSA vì client cũ, có thể cấu hình cả hai chứng chỉ và để Go chọn theo client; tls.Config nhận nhiều Certificates và tự khớp theo thuật toán client hỗ trợ.
Session ticket cắt 9% với ECDSA nhưng 80% với RSA, và xoá luôn khác biệt giữa hai loại. ECDSA cộng ticket: 193 µs thay vì 212, tiết kiệm 19 µs, đúng bằng chữ ký ECDSA rẻ mà nó bỏ qua. RSA cộng ticket: 192 µs thay vì 941, tiết kiệm 80%. Có ticket, hai loại chứng chỉ cho cùng 192 đến 193 µs, vì phần còn lại là trao đổi khoá ECDHE giống nhau. Hệ quả thực tế: nếu đã dùng ECDSA, session ticket gần như không có lợi về CPU server và chỉ còn lợi về byte, 539 byte mỗi bắt tay như phần 1 đo; nếu dùng RSA, ticket là khác biệt giữa 1.000 và 4.800 bắt tay mỗi giây mỗi lõi.
Kyber768 chiếm 73 µs, tức 35% của một bắt tay ECDSA. Tắt trao đổi khoá hậu lượng tử ở cả hai phía bằng GODEBUG=tlskyber=0: 6.575 bắt tay mỗi giây, 139 µs CPU, so với 4.350 và 212 µs khi bật. Resumed không Kyber còn 131 µs. Vậy trong 212 µs của một bắt tay ECDSA đầy đủ với Go 1.23: khoảng 73 µs là Kyber, 19 µs là chữ ký ECDSA, phần còn lại là X25519, dẫn xuất khoá, AEAD và chi phí Go. Phần 1 thấy Kyber làm ClientHello to gấp 5; phần này thấy nó làm bắt tay đắt thêm một phần ba. Đó là giá của việc chống "thu hoạch hôm nay, giải mã sau này", và Go bật mặc định vì cho rằng giá đó đáng.
Đánh đổi cần cân nhắc
Dùng ECDSA P-256, và khi đó đừng kỳ vọng nhiều ở session ticket về CPU. ECDSA đưa chi phí bắt tay về vùng mà ticket chỉ cắt thêm 9%; ticket vẫn đáng bật vì byte và vì client di động quay lại thường xuyên, nhưng nó không phải phao cứu sinh. Với RSA kế thừa, ticket là bắt buộc nếu có bão kết nối lại.
Tính RAM TLS theo 25 KB mỗi kết nối, và nhớ client cũng trả. 10.000 kết nối là 250 MB thêm, 100.000 là 2,5 GB, trên cả server lẫn bất kỳ proxy hay gateway nào kết thúc TLS. Go không cho chỉnh bộ đệm record; nếu RAM là ràng buộc chính, cách giảm là kết thúc TLS ở tầng khác, chủ đề của phần 6, hoặc giảm số kết nối bằng HTTP/2.
Phạm vi đo. aarch64 ảo hoá, một vCPU cho mục B; số tuyệt đối sẽ khác trên x86 với AES-NI và trên máy thật, nhưng tỉ lệ ECDSA so với RSA và phần trăm ticket cắt được thì ổn định hơn nhiều. Chưa đo: TLS 1.2 với RSA key exchange không forward secrecy, OCSP stapling làm Certificate flight to hơn, và chi phí mã hoá dữ liệu sau bắt tay, chủ đề của phần 4.
Ba ý mang về
- TLS idle là 24,5 KB mỗi kết nối, phần lớn ngoài heap: 288 MB so với 42 MB cho 10.000 kết nối, cộng với 27 KB của WebSocket là 50 KB mỗi kết nối. Tính trước, không đợi OOM.
- Một lõi: 4.350 bắt tay ECDSA hay 1.060 RSA mỗi giây: chữ ký RSA-2048 chiếm 78% chi phí. Chọn ECDSA, và nếu kẹt RSA thì bật session ticket, thứ cắt 80% với RSA nhưng chỉ 9% với ECDSA.
- Kyber là một phần ba chi phí bắt tay: 73 trong 212 µs. Go bật mặc định; biết con số để quyết định có ý thức, và tắt bằng
GODEBUG=tlskyber=0chỉ khi đo thấy cần.
Nguồn
- Go
crypto/tls—Config.Certificates,ClientSessionCache,SessionTicketsDisabled: https://pkg.go.dev/crypto/tls#Config - Go 1.23 Release Notes — X25519Kyber768Draft00 và
GODEBUG=tlskyber=0: https://go.dev/doc/go1.23#cryptotlspkgcryptotls - RFC 8446 — TLS 1.3, mục 2.2 Resumption and Pre-Shared Key và 4.6.1 New Session Ticket: https://www.rfc-editor.org/rfc/rfc8446#section-2.2
- Cloudflare — ECDSA: The digital signature algorithm of a better internet: https://blog.cloudflare.com/ecdsa-the-digital-signature-algorithm-of-a-better-internet/
- Cloudflare — The state of the post-quantum Internet (chi phí Kyber trong TLS): https://blog.cloudflare.com/pq-2024/
- Linux
proc(5)—/proc/self/statusVmRSS,/proc/self/statutime/stime: https://man7.org/linux/man-pages/man5/proc.5.html
Phần sau rời khỏi hiệu năng để tới nơi TLS thật sự hay gãy trong vận hành: chứng chỉ và chuỗi tin cậy. Tự dựng CA, cấp chứng chỉ, rồi làm hỏng từng thứ một, sai SAN, hết hạn, lệch giờ, thiếu intermediate, để xem client Go và curl báo lỗi gì và verify tốn bao nhiêu.