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

Ảnh chụp đoạn mã nền tối TLS trên 10000 kết nối RAM CPU bắt tay ticket cứu gì với Go 1.23 crypto tls TLS 1.3 server dist-lab 9450 plaintext hoặc TLS stats 9451 client go-lab ECDSA P-256 và RSA-2048, ba khoản chi khác nhau của TLS gồm giữ kết nối mỗi tls.Conn giữ trạng thái record layer khoá bộ đệm đọc ghi cộng vào kết nối plaintext hỏi 10000 kết nối TLS idle tốn thêm bao nhiêu RAM so với 10000 plaintext, bắt tay mỗi kết nối mới trao đổi khoá X25519 cộng Kyber768 một chữ ký bằng khoá của chứng chỉ dẫn xuất khoá hỏi một lõi chịu bao nhiêu bắt tay mỗi giây RSA-2048 đắt hơn ECDSA P-256 bao nhiêu Kyber tốn bao nhiêu, resumption session ticket client mang PSK server không ký không gửi Certificate vẫn ECDHE để có forward secrecy hỏi ticket cắt bao nhiêu CPU server mỗi bắt tay với ECDSA và với RSA, cách đo A hold client mở 10000 kết nối không gửi gì stats server goroutines heap RSS plaintext rồi TLS, B hs server GOMAXPROCS bằng 1 8 hoặc 16 worker lặp connect bắt tay gửi 1 byte server đóng đếm 8 s đo bắt tay mỗi giây CPU server utime cộng stime chia số bắt tay bằng micro giây CPU mỗi bắt tay p50 phía client plaintext mốc ECDSA full ECDSA cộng ticket RSA-2048 full RSA-2048 cộng ticket, C ECDSA full server 4 lõi 16 worker scale theo lõi D GODEBUG tlskyber bằng 0 hai phía Kyber tốn gì, server đóng trước sau 1 byte q để TIME_WAIT nằm phía server không thì client cạn 28232 cổng sau khoảng 15 s

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%

Ảnh chụp output thật nền tối cộng 24,5 KB mỗi kết nối 4350 bắt tay mỗi giây mỗi lõi RSA nhân 4,4 đắt với server dist-lab client go-lab 10000 kết nối idle bắt tay đo 8 s mỗi lượt server GOMAXPROCS bằng 1 trừ mục C, A 10000 kết nối idle server 10 lõi gồm plaintext mở xong 3,0 s goroutines 10004 heap 8,7 MB heapSys 12,5 MB RSS 42,4 MB client RSS 16 MB CPU server 0,28 s, TLS 1.3 ECDSA 10,5 s 10004 77,8 MB 187,4 MB 287,9 MB 122 MB 3,71 s, TLS thêm 245 MB cho 10000 kết nối xấp xỉ 24,5 KB mỗi kết nối heap chỉ cộng 7 KB phần còn lại là bộ đệm record và rác bắt tay, 3,71 s CPU cho 10000 bắt tay bằng 371 micro giây mỗi cái trên 10 lõi tranh nhau client cũng trả 106 MB cho phía nó, B bắt tay mỗi giây server 1 lõi 8 worker 8 s gồm plaintext TCP connect 27114 mỗi giây 21 micro giây 0,04 ms mốc accept cộng goroutine cộng đóng, TLS ECDSA P-256 full 4350 212 micro giây 1,77 ms bằng 1 chữ ký ECDSA cộng X25519 cộng Kyber768, TLS ECDSA cộng ticket 4786 193 micro giây 1,99 ms ticket chỉ cắt 9 phần trăm chữ ký ECDSA vốn rẻ, TLS RSA-2048 full 1060 941 micro giây 4,74 ms chữ ký RSA xấp xỉ 730 micro giây bằng 78 phần trăm chi phí, TLS RSA-2048 cộng ticket 4808 192 micro giây 2,00 ms ticket cắt 80 phần trăm về bằng ECDSA, có ticket thì chứng chỉ RSA hay ECDSA không còn khác nhau 192 vs 193 micro giây phần còn lại là trao đổi khoá, C scale theo lõi D Kyber tốn gì gồm ECDSA full 1 lõi 16 worker 5328 mỗi giây 184 micro giây CPU server 7,83 s trên 8 s bằng bão hoà 1 lõi 8 worker 4350 chưa đầy, ECDSA full 4 lõi 16 worker 6055 mỗi giây 354 micro giây CPU server 17,2 s trên 8 s bằng 2,1 lõi client 16 worker là nút thắt không phải server, GODEBUG tlskyber bằng 0 1 lõi 8 worker 6575 mỗi giây 139 micro giây Kyber768 tốn khoảng 73 micro giây mỗi bắt tay 35 phần trăm của ECDSA full resumed không Kyber 131 micro giây, nói thẳng aarch64 1 vCPU ảo hoá số tuyệt đối của bạn sẽ khác tỉ lệ ECDSA RSA 4,4 lần và ticket 9 phần trăm vs 80 phần trăm thì bền hơn 24,5 KB mỗi kết nối là Go crypto tls 1.23 với bộ đệm mặc định cỡ bộ đệm record không chỉnh được như gorilla

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ề

  1. 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.
  2. 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.
  3. 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=0 chỉ khi đo thấy cần.

Nguồn

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.