สุดยอดคู่มือประสิทธิภาพเครือข่ายบน Linux
(ntk148v.github.io)- การปรับจูนประสิทธิภาพเครือข่ายบน Linux คือการตีความเส้นทางที่แพ็กเก็ตเคลื่อนจาก บัฟเฟอร์ริงของ NIC, IRQ, NAPI, softIRQ, qdisc, บัฟเฟอร์ TCP ไปจนถึงซ็อกเก็ตของแอปพลิเคชัน โดยมองจากจุดคอขวดเป็นหลัก
- ในเส้นทางรับข้อมูล NIC จะเขียนแพ็กเก็ตลง RAM ด้วย DMA แล้วสร้าง HardIRQ จากนั้นไดรเวอร์จะกำหนดเวลาให้ NAPI ทำงาน เพื่อให้
NET_RX_SOFTIRQล้างบัฟเฟอร์ริง แล้วส่งต่อไปยังเลเยอร์ IP/TCP และบัฟเฟอร์รับของซ็อกเก็ต ethtool,/proc/net/softnet_stat,ss,netstat,sysctlคือจุดเริ่มต้นของการสังเกตและปรับแต่ง โดยแกนหลักได้แก่ interrupt coalescing, IRQ affinity, RSS/RPS/RFS/aRFS,netdev_budget,netdev_max_backlog,txqueuelen, บัฟเฟอร์ read/write ของ TCP- ไม่มีค่าตั้งเดียวที่ใช้ได้กับทุกระบบ การเพิ่มบัฟเฟอร์ริงอาจช่วยลดการดรอปได้แต่เพิ่ม latency และ interrupt coalescing อาจลดการใช้ CPU กับ HardIRQ ได้ แต่แลกมาด้วยต้นทุนด้าน latency
- การประมวลผลแพ็กเก็ตประสิทธิภาพสูงสามารถขยายไปใช้ตัวเลือกอย่าง
PACKET_MMAP, DPDK,PF_RING, XDP/AF_XDP ได้ แต่แต่ละแนวทาง ไม่ว่าจะเป็นการข้ามเคอร์เนล zero-copy หรือ fast path ภายในเคอร์เนล ล้วนมีข้อพึ่งพาฮาร์ดแวร์ การครอง CPU และข้อกำหนดเวอร์ชันเคอร์เนลที่ต่างกัน
เส้นทางรับข้อมูลของ Linux: จาก NIC ถึงซ็อกเก็ต
- อุปกรณ์เครือข่ายจะสร้าง IRQ เพื่อแจ้งว่ามีแพ็กเก็ตมาถึง และการแมป IRQ ของ Linux จะถูกเก็บไว้ใน
/proc/interrupts - IRQ handler ทำงานด้วยลำดับความสำคัญสูงมาก และบางกรณีจะป้องกันไม่ให้มีการสร้าง IRQ เพิ่ม ไดรเวอร์จึงเลื่อนงานที่ใช้เวลานานออกไปนอกบริบทของ IRQ
- การประมวลผลแบบเลื่อนเวลานี้ใช้ softIRQ และในการประมวลผลรับข้อมูลเครือข่าย จะมีการสร้างเธรดเคอร์เนล
ksoftirqd/<cpu-number>,softnet_data,poll_listสำหรับแต่ละ CPU net_dev_initลงทะเบียนNET_RX_SOFTIRQกับระบบ softIRQ และ handler ที่เกี่ยวข้องคือnet_rx_action-
การมาถึงของแพ็กเก็ตและการประมวลผล NAPI
- NIC บันทึกข้อมูลที่ได้รับจากเครือข่ายลงในบัฟเฟอร์ริงใน RAM ด้วย DMA
- NIC บางรุ่นเป็น multiqueue NIC ที่มีบัฟเฟอร์ริงหลายชุด
- เมื่อ NIC สร้าง HardIRQ แล้ว IRQ handler ของไดรเวอร์จะทำงาน
- ไดรเวอร์จะเคลียร์ IRQ ของ NIC และเรียก
napi_scheduleเพื่อเริ่มลูป poll ของ NAPI softIRQ napi_scheduleเพิ่มโครงสร้าง NAPI poll ของไดรเวอร์เข้าในpoll_listของ CPU ปัจจุบัน และตั้งค่า softIRQ pending bit- เมื่อ
ksoftirqdเรียก__do_softirqhandlernet_rx_actionของNET_RX_SOFTIRQที่อยู่ในสถานะ pending จะทำงาน
-
GRO และการเข้าสู่โปรโตคอลสแต็ก
net_rx_actionตรวจสอบ NAPI poll list และตรวจสอบbudgetกับเวลาที่ผ่านไป เพื่อไม่ให้ softIRQ ยึด CPU ไว้แต่ผู้เดียว- ฟังก์ชัน
pollของไดรเวอร์จะเก็บเกี่ยวแพ็กเก็ตจากบัฟเฟอร์ริงใน RAM - แพ็กเก็ตถูกส่งต่อไปยัง
napi_gro_receive - GRO(Generic Receive Offloading) เป็นเทคนิค offloading แบบซอฟต์แวร์ที่รวมแพ็กเก็ตขนาดเล็กกลับเป็นแพ็กเก็ตขนาดใหญ่ เพื่อลดจำนวนแพ็กเก็ตที่แอปพลิเคชันต้องประมวลผล
- หาก GRO ไม่พักแพ็กเก็ตไว้ แพ็กเก็ตจะเคลื่อนขึ้นไปยังโปรโตคอลสแต็กผ่าน
netif_receive_skb
-
การแยกเส้นทางตามการเปิดใช้ RPS
- กรณีที่ RPS ถูกปิดใช้งาน:
netif_receive_skbส่งข้อมูลต่อไปยัง__netif_receive_core__netif_receive_coreส่งข้อมูลต่อไปยัง tap และ handler ของเลเยอร์โปรโตคอลที่ลงทะเบียนไว้
- กรณีที่ RPS ถูกเปิดใช้งาน:
netif_receive_skbส่งข้อมูลต่อไปยังenqueue_to_backlog- แพ็กเก็ตเข้าสู่ input queue ของแต่ละ CPU
- โครงสร้าง NAPI ของ CPU ระยะไกลถูกเพิ่มเข้าใน
poll_listของ CPU นั้น และมีการคิว IPI เพื่อปลุกเธรด softIRQ ของ CPU ระยะไกล ksoftirqdของ CPU ระยะไกลใช้ฟังก์ชัน pollprocess_backlogเพื่อเก็บเกี่ยวแพ็กเก็ตจาก CPU input queue
- กรณีที่ RPS ถูกปิดใช้งาน:
-
IP, TCP และบัฟเฟอร์รับของซ็อกเก็ต
- แพ็กเก็ตถูกรับที่เลเยอร์ IPv4 ผ่าน
ip_rcvแล้วผ่าน netfilter และการปรับแต่ง routing ให้เหมาะสม - ข้อมูลที่มุ่งมายังระบบปัจจุบันจะถูกส่งต่อไปยังเลเยอร์โปรโตคอลระดับสูง เช่น UDP หรือ TCP
- ในเส้นทางรับของ TCP แพ็กเก็ตจะผ่าน
tcp_v4_rcv, TCP finite state machine และการค้นหาซ็อกเก็ต ก่อนเข้าสู่บัฟเฟอร์รับ - ขนาดบัฟเฟอร์รับเป็นไปตามกฎของ
tcp_rmem - หากเปิดใช้
tcp_moderate_rcvbufเคอร์เนลจะปรับบัฟเฟอร์รับโดยอัตโนมัติ tcp_rmemเก็บค่าต่ำสุด ค่าเริ่มต้น และค่าสูงสุดของบัฟเฟอร์รับของซ็อกเก็ต TCP- การใช้
SO_RCVBUFจะปิดการปรับบัฟเฟอร์รับอัตโนมัติของซ็อกเก็ตนั้น net.core.rmem_maxคือเพดานของขนาดบัฟเฟอร์รับ TCP และหน้าต่างที่ใหญ่ขึ้นสามารถทำให้ส่งข้อมูลได้มากขึ้นก่อนส่ง ACK ช่วยลด latency และเพิ่ม throughput
- แพ็กเก็ตถูกรับที่เลเยอร์ IPv4 ผ่าน
เส้นทางส่งข้อมูลของ Linux: จากแอปพลิเคชันถึง NIC
- เส้นทางส่งข้อมูลเรียบง่ายกว่าเส้นทางรับ แต่มี qdisc, TCP write buffer, DMA และ IRQ เข้ามาเกี่ยวข้อง
- เมื่อแอปพลิเคชันส่งข้อความด้วยการเรียกอย่าง
sendmsgเส้นทางส่งของ TCP จะจัดสรรskb_buff - แพ็กเก็ตเข้าสู่ write buffer ของซ็อกเก็ตตามขนาด
tcp_wmemtcp_wmemเก็บค่าต่ำสุด ค่าเริ่มต้น และค่าสูงสุดของบัฟเฟอร์ส่งของซ็อกเก็ต TCP- เคอร์เนลปรับขนาดบัฟเฟอร์ส่ง TCP แบบไดนามิกระหว่างค่าต่ำสุดและค่าสูงสุด
- การใช้
SO_SNDBUFจะปิดการปรับบัฟเฟอร์ส่งอัตโนมัติของซ็อกเก็ตนั้น net.core.wmem_maxคือเพดานของขนาดบัฟเฟอร์ส่ง TCP
- มีการสร้าง TCP header และ IP header จากนั้นผ่าน
LOCAL_OUT, routing,POST_ROUTING, fragmentation แล้วเรียกฟังก์ชันส่ง L2 ผ่านdev_queue_xmit - qdisc ฝั่งขาออกใช้ความยาว
txqueuelenและอัลกอริทึมdefault_qdisc - ไดรเวอร์ใส่แพ็กเก็ตลงใน TX ring buffer และทำ
NET_TX_SOFTIRQหลังจาก timeouttx-usecsหรือหลังtx-frames - NIC ดึงแพ็กเก็ตจาก RAM ด้วย DMA แล้วส่งออกไป และสร้าง HardIRQ หลังส่งเสร็จ
- ไดรเวอร์ประมวลผล IRQ นี้และกำหนดเวลาให้ระบบ NAPI poll เพื่อคืนหน่วยความจำ
เครื่องมือสังเกตการณ์และจุดตรวจสอบพื้นฐาน
-
/proc/net/softnet_stat- แต่ละบรรทัดของ
/proc/net/softnet_statแทนคอร์ CPU หนึ่งคอร์ โดยเริ่มจาก CPU0 - สถิติในแต่ละคอลัมน์แสดงเป็น เลขฐานสิบหก
- คอลัมน์ที่ 1 คือจำนวนเฟรมที่ interrupt handler ได้รับ
- คอลัมน์ที่ 2 คือจำนวนเฟรมที่ถูกดรอปเพราะเกิน
netdev_max_backlog - คอลัมน์ที่ 3 คือจำนวนครั้งที่
ksoftirqdใช้netdev_budgetหรือเวลา CPU หมด ทั้งที่ยังมีงานค้างให้ประมวลผล - คอลัมน์ที่เหลืออาจแตกต่างกันไปตามเวอร์ชัน Linux
- แต่ละบรรทัดของ
-
/proc/net/sockstatและss- ใน
/proc/net/sockstatให้ตรวจสอบฟิลด์mem - ค่านี้คำนวณจากผลรวมของ
sk_buff->truesizeของทุกซ็อกเก็ต ssเป็นเครื่องมือสำหรับ dump สถิติซ็อกเก็ต และสามารถแสดงข้อมูลที่คล้ายกับnetstatรวมถึงข้อมูล TCP และสถานะที่มากกว่าได้ss -tmใช้ตรวจสอบการใช้หน่วยความจำของ TCP socketrmem_alloc: หน่วยความจำที่จัดสรรให้แพ็กเก็ตขาเข้าrcv_buf: หน่วยความจำทั้งหมดที่สามารถจัดสรรให้แพ็กเก็ตขาเข้าได้wmem_alloc: หน่วยความจำที่ใช้กับแพ็กเก็ตขาออกที่ถูกส่งต่อไปยังเลเยอร์ 3 แล้วsnd_buf: หน่วยความจำทั้งหมดที่สามารถจัดสรรให้แพ็กเก็ตขาออกได้wmem_queued: หน่วยความจำที่จัดสรรให้แพ็กเก็ตขาออกที่ยังไม่ได้ถูกส่งต่อไปยังเลเยอร์ 3sock_drop: จำนวนแพ็กเก็ตที่ถูกดรอปก่อนถูก demultiplex ไปยังซ็อกเก็ต
- ใน
-
netstatและsysctlnetstatเป็นเครื่องมือบรรทัดคำสั่งที่แสดงการเชื่อมต่อเครือข่ายที่เปิดอยู่และสถิติของ protocol stack โดยดึงข้อมูลจากไฟล์ระบบ/proc/net//proc/net/dev: ข้อมูลอุปกรณ์/proc/net/tcp: ข้อมูล TCP socket/proc/net/unix: ข้อมูล Unix domain socketsysctlเป็นคำสั่งสำหรับเปลี่ยนการตั้งค่าระบบและเครือข่าย แทนการเขียนค่าลงในไฟล์ระบบ/procโดยตรงsysctl -w variable=valueใช้สำหรับการเปลี่ยนแปลงชั่วคราว ส่วนการเปลี่ยนแปลงถาวรให้แก้ไข/etc/sysctl.confแล้วใช้sysctl -pเพื่อปรับใช้
NIC ring buffer และการปรับ interrupt
-
NIC ring buffer
- RX ring buffer คือ FIFO circular buffer ขนาดคงที่ที่อยู่ใน RAM
- ตัว ring buffer เองไม่ได้เก็บข้อมูลแพ็กเก็ต แต่เก็บ descriptor ที่ชี้ไปยัง
skbซึ่งถูกนำเข้า RAM ด้วย DMA - หากพบการดรอปหรือ overrun สามารถเพิ่มขนาดคิวได้ แต่ผลข้างเคียงคือ latency อาจเพิ่มขึ้น
- ในหลายกรณี การเพิ่มขนาด receive buffer เพียงอย่างเดียวก็อาจป้องกัน packet drop ได้ และช่วยให้ kernel มีเวลามากขึ้นเล็กน้อยในการเคลียร์ buffer
- ตรวจสอบและเปลี่ยนค่าได้ด้วย
ethtool ethtool -g eth3: ตรวจสอบขนาด RX/TX ring ปัจจุบันและค่าสูงสุดethtool -G eth3 rx 8192 tx 8192: เพิ่ม RX/TX buffer เป็นค่าสูงสุด- มอนิเตอร์ด้วย
ethtool -S eth3และเคาน์เตอร์อย่างerr,drop,over,miss,timeout,reset,collis
-
การรวม interrupt ระดับฮาร์ดแวร์
- NIC สามารถสะสม packet reference ไว้ใน RX ring buffer จนถึงเงื่อนไข timeout
rx-usecsหรือrx-framesแล้วจึงสร้าง HardIRQ ได้ สิ่งนี้เรียกว่า Interrupt coalescence - หาก interrupt เร็วเกินไป kernel จะถูกขัดจังหวะงานที่กำลังรันอยู่บ่อยครั้ง ทำให้ประสิทธิภาพระบบแย่ลง
- หาก interrupt ช้าเกินไป NIC อาจเคลียร์ทราฟฟิกออกไม่เร็วพอ จนเกิดการเขียนทับและทราฟฟิกสูญหาย
- การปรับ interrupt coalescence อาจลดการใช้ CPU และ HardIRQ และเพิ่ม throughput ได้ แต่มีต้นทุนด้าน latency
- ตรวจสอบพารามิเตอร์ coalesce ด้วย
ethtool -c eth3และเปลี่ยนค่าได้ เช่นethtool -C eth3 adaptive-rx off rx-usecs 0 rx-frames 0 - adaptive mode จะให้การ์ดประเมินค่า coalescing แบบไดนามิกจากรูปแบบทราฟฟิกและรูปแบบการรับของ kernel
- NIC สามารถสะสม packet reference ไว้ใน RX ring buffer จนถึงเงื่อนไข timeout
IRQ affinity และการกระจายโหลดระหว่าง CPU
-
IRQ affinity
- IRQ มีคุณสมบัติ
smp_affinityที่กำหนดคอร์ CPU ซึ่งสามารถรัน ISR ของ IRQ นั้นได้ - หากจัด interrupt affinity และ application thread affinity ให้ตรงกับคอร์ CPU เฉพาะ อาจช่วยเพิ่มประสิทธิภาพแอปพลิเคชันได้จากการแชร์ cache line
- โดยค่าเริ่มต้นจะถูกควบคุมโดย daemon
irqbalance - ก่อนปรับด้วยตนเองต้องหยุด
irqbalance /proc/irq/<IRQ_NUMBER>/smp_affinityเก็บ bitmask เลขฐานสิบหกที่แทนคอร์ CPU- ในเซิร์ฟเวอร์ 4 คอร์ ค่าเริ่มต้น
fหมายถึงทุก CPU สามารถประมวลผล IRQ ได้ echo 1 > /proc/irq/32/smp_affinityทำให้ใช้เฉพาะ CPU0- ในระบบที่มีมากกว่า 32 คอร์ จะใช้ comma แยกกลุ่ม 32-bit
- IRQ affinity สามารถเพิ่มประสิทธิภาพได้เฉพาะในคอนฟิกที่เจาะจงมากและ workload ที่กำหนดไว้ล่วงหน้าเท่านั้น และอาจเป็น ดาบสองคม
- IRQ มีคุณสมบัติ
-
RSS
- หากรับแพ็กเก็ตบน NIC ความเร็วสูงด้วยคิวเดียวและ CPU เดียว คอร์หนึ่งอาจต้องรับผิดชอบการประมวลผลข้อมูลทั้งหมด ขณะที่คอร์อื่นว่างอยู่
- RSS(Receive-side scaling) เป็นเทคโนโลยีของ NIC ที่ให้ NIC กระจายทราฟฟิกไปยังคิวรับส่งหลายคิว
- NIC คำนวณแฮชจาก source/destination IP และ TCP/UDP source/destination port จัดแพ็กเก็ตของ flow เดียวกันไปยังคิวเดียว และกระจาย flow ให้สม่ำเสมอระหว่างคิวต่างๆ
- RSS ให้ข้อดีของการประมวลผลรับแบบขนานในสภาพแวดล้อม multiprocessor
- ตามเอกสาร Linux kernel ควรเปิดใช้ RSS เมื่อ latency สำคัญหรือการประมวลผล receive interrupt เป็นคอขวด และในเครือข่าย latency ต่ำ การตั้งค่าที่เหมาะสมคือจัดสรรคิวเท่ากับจำนวน CPU ของระบบ
-
RPS, RFS, aRFS
- RPS(Receive Packet Steering) ใกล้เคียงกับการทำ RSS ในซอฟต์แวร์
- ขณะที่ RSS เลือกคิวและ CPU ที่จะรัน hardware interrupt handler, RPS จะเลือก CPU ที่จะประมวลผลโปรโตคอลเหนือ interrupt handler
- ต้องมี
CONFIG_RPSและจะเปิดใช้งานเป็นค่าเริ่มต้นบน SMP - ตั้งค่าด้วย CPU bitmap ใน
/sys/class/net/<dev>/queues/rx-<n>/rps_cpus - หากมี RSS อาจไม่จำเป็น แต่จะมีประโยชน์เมื่อจำนวน CPU มากกว่าจำนวนคิว
- RFS(Receive Flow Steering) ขยาย RPS ไปจนถึง locality ของแอปพลิเคชัน
- RPS กระจายแพ็กเก็ตตาม flow แต่ไม่ได้พิจารณาว่าแอปพลิเคชันใน user space รันอยู่บน CPU ใด
- RFS รักษา
rps_sock_flow_tableซึ่งเป็นตาราง global flow-to-CPU - สามารถปรับขนาดตารางได้ด้วย
net.core.rps_sock_flow_entries - per-queue
rps_dev_flow_tableใช้เพื่อลดปัญหาลำดับผิดเมื่อ scheduler ย้ายแอปพลิเคชันไปยัง CPU ใหม่ แต่ยังมีแพ็กเก็ตค้างอยู่ - aRFS(Accelerated RFS) เป็นกลไกกระจายโหลดที่เร่งด้วยฮาร์ดแวร์สำหรับ RFS
- เนื่องจากส่งแพ็กเก็ตไปยัง CPU ที่อยู่ใกล้กับเธรดที่ใช้ข้อมูลโดยตรง จึงให้ประสิทธิภาพดีกว่า RFS ได้
- ต้องมี
ndo_rx_flow_steer,ntuplefiltering ของ NIC และCONFIG_RFS_ACCEL - mapping ระหว่าง CPU กับคิวจะถูกอนุมานอัตโนมัติจาก IRQ affinity ของแต่ละคิวรับ ดังนั้นจึงไม่ต้องตั้งค่าเพิ่มเติม
การปรับแต่ง softIRQ, qdisc และบัฟเฟอร์ TCP
-
งบประมาณ softIRQ
- รูทีน NAPI polling ถูกจำกัดด้วย
netdev_budget_usecs,netdev_budget,dev_weightเพื่อไม่ให้ softIRQ ยึดครอง CPU - ค่าเริ่มต้นของ
net.core.netdev_budgetคือ 300 ซึ่งหมายถึงการล้างข้อความ 300 รายการจาก NIC ก่อนที่กระบวนการ softIRQ จะออกจาก CPU net.core.netdev_budget_usecsคือจำนวนไมโครวินาทีสูงสุดของ NAPI polling cycle หนึ่งรอบnet.core.dev_weightคือจำนวนแพ็กเก็ตสูงสุดต่อ CPU ที่เคอร์เนลสามารถประมวลผลได้ใน NAPI interrupt- หากคอลัมน์อื่นนอกเหนือจากคอลัมน์ที่ 1 ใน
/proc/net/softnet_statเพิ่มขึ้น อาจจำเป็นต้องปรับ budget แต่การเพิ่มขึ้นเล็กน้อยอาจถือเป็นเรื่องปกติ
- รูทีน NAPI polling ถูกจำกัดด้วย
-
ingress qdisc และ
netdev_max_backlognetdev_max_backlogคือคิวภายในเคอร์เนลที่เก็บทราฟฟิกหลังจากได้รับจาก NIC และก่อนจะประมวลผลผ่าน protocol stack เช่น IP/TCP- แต่ละคอร์ของ CPU จะมี backlog queue หนึ่งคิว
- หากอินเทอร์เฟซรับแพ็กเก็ตได้เร็วกว่าความเร็วที่เคอร์เนลสามารถประมวลผลได้ คิวฝั่ง INPUT จะเต็มจนถึง
netdev_max_backlogและเมื่อเกินกว่านั้นจะถูกดรอป - ค่าเริ่มต้นคือ 1000 ซึ่งอาจไม่เพียงพอสำหรับอินเทอร์เฟซ 1Gbps หลายตัว หรืออินเทอร์เฟซ 10Gbps ตัวเดียว
- คอลัมน์ที่ 2 ของ
/proc/net/softnet_statคือเคาน์เตอร์ที่เพิ่มขึ้นจาก backlog queue overflow - การเปลี่ยนค่าทำได้ด้วย
sysctl -w net.core.netdev_max_backlog <value>
-
egress qdisc,
txqueuelen,default_qdisctxqueuelenตั้งค่าจำนวนแพ็กเก็ตที่อนุญาตใน kernel transmit queue ของอุปกรณ์อินเทอร์เฟซเครือข่าย- ค่าเริ่มต้นอาจเป็น 1000 ขึ้นอยู่กับไดรเวอร์ของอินเทอร์เฟซ
- เปลี่ยนได้ด้วย
ifconfig <interface> txqueuelen valueและตรวจสอบ RX/TX dropped ในip -s link default_qdiscคือ queuing discipline เริ่มต้นที่จะใช้กับอุปกรณ์เครือข่าย- สามารถระบุทางเลือกอื่นแทน
pfifo_fastได้ เช่นsfq,codel,fq_codel - ตรวจสอบตัวชี้วัดอย่าง dropped, overlimits, requeues ได้ใน
tc -s qdisc ls dev <interface>
-
TCP read/write buffer และคิวการเชื่อมต่อ
tcp_rmemและtcp_wmemกำหนดค่าต่ำสุด ค่าเริ่มต้น และค่าสูงสุดของบัฟเฟอร์รับ·ส่ง TCP ตามลำดับ- เปลี่ยนค่าได้ดังนี้
sysctl -w net.ipv4.tcp_rmem="min default max"sysctl -w net.ipv4.tcp_wmem="min default max"
- ตรวจสอบสถานะการใช้บัฟเฟอร์ได้ด้วย
/proc/net/sockstat - Accept queue และ SYN queue ได้รับผลจาก
net.core.somaxconnและnet.ipv4.tcp_max_syn_backlog net.core.somaxconnคือเพดานบนของพารามิเตอร์ backlog ของlisten()และหากเปลี่ยนค่านี้ แอปพลิเคชันก็ต้องเปลี่ยนเป็นค่าที่เข้ากันได้ด้วยnet.ipv4.tcp_syncookiesเปิดหรือปิด SYN cookie ซึ่งมีประโยชน์ในการป้องกันการโจมตี SYN floodnet.ipv4.tcp_congestion_controlตั้งค่าอัลกอริทึม congestion control ที่จะใช้กับการเชื่อมต่อใหม่
NUMA และประสิทธิภาพเครือข่าย
- NUMA(Non-uniform memory access) คือสถาปัตยกรรมหน่วยความจำที่โปรเซสเซอร์สามารถเข้าถึง local memory ได้เร็วกว่า non-local memory
- ในการประมวลผลเครือข่าย CPU ต้องเข้าถึงหน่วยความจำของ ring buffer ดังนั้น NUMA locality จึงอาจส่งผลต่อประสิทธิภาพเครือข่าย
- NUMA แบ่ง CPU, หน่วยความจำ และอุปกรณ์ออกเป็นหลาย node และทำงานเหมือนคอมพิวเตอร์ขนาดเล็กหลายเครื่องที่มี interconnect ความเร็วสูงและ OS ร่วมกัน
- ในระบบ NUMA เป้าหมายของการปรับแต่งคือการรวม interrupt ของอุปกรณ์นั้นไปยังคอร์ CPU ใน node เดียวกับที่อุปกรณ์สังกัดอยู่
- อย่างไรก็ตาม ระบบ NUMA อาจโต้ตอบกับแอปพลิเคชันแบบเรียลไทม์ได้ไม่ดี และทำให้เกิดความล่าช้าของเหตุการณ์ที่คาดไม่ถึง
- ตรวจสอบ NUMA node ได้ที่
/sys/devices/system/node/node* - ตรวจสอบ locality ของอุปกรณ์ได้ที่
/sys/class/net/<interface>/device/numa_node-1หมายถึงกรณีที่แพลตฟอร์มฮาร์ดแวร์ไม่ใช่ NUMA จริง หรือเคอร์เนลจำลอง NUMA ขึ้นมา หรืออุปกรณ์ไม่มี NUMA locality
- Linux kernel รองรับ NUMA ตั้งแต่เวอร์ชัน 2.5 และดิสโทรที่อิง RedHat และ Debian มี
numactl,numadให้ใช้งาน numadตรวจสอบ topology ของระบบและการใช้ทรัพยากร และพยายามจัดวางโปรเซสที่มีการใช้หน่วยความจำและโหลด CPU สูงพอไว้ใน NUMA locality ที่มีประสิทธิภาพ
ตัวเลือกสำหรับการประมวลผลแพ็กเก็ตที่เร็วขึ้น
-
AF_PACKETv4 และPACKET_MMAPAF_PACKET v4เป็นอินเทอร์เฟซแพ็กเก็ตความเร็วสูงของ Linux โดยตัด system call ออกจาก data path และโดยพื้นฐานใช้ copy-mode- เมื่อใช้
PACKET_ZEROCOPYจะสามารถใช้โหมด true zero-copy ที่แมป DMA packet buffer ไปยังพื้นที่ผู้ใช้ได้ - เส้นทางทั่วไปที่อ่านไฟล์แล้วส่งผ่าน socket ต้องมี context switch ระหว่าง user mode กับ kernel mode และต้อง copy ข้อมูลหลายครั้ง
- zero-copy ช่วยเพิ่มประสิทธิภาพด้วยการกำจัดการ copy ข้อมูลซ้ำซ้อน
PACKET_MMAPเป็น Linux API สำหรับการทำ packet sniffing ความเร็วสูง- ให้บริการ mmapped ring buffer ที่พื้นที่ผู้ใช้และเคอร์เนลใช้ร่วมกัน
- ลด system call และการ copy ระหว่างพื้นที่ผู้ใช้กับเคอร์เนลสำหรับแพ็กเก็ตที่รับและส่ง
-
DPDK
- DPDK(Data Plane Development Kit) คือไลบรารีพื้นที่ผู้ใช้และเฟรมเวิร์กไดรเวอร์สำหรับการประมวลผลแพ็กเก็ตความเร็วสูง
- เป้าหมายคือการรับส่งแพ็กเก็ตเครือข่ายระหว่าง NIC กับแอปพลิเคชันผู้ใช้ด้วย native speed
- มุ่งเป้าไปที่ NIC 10Gb หรือ 40Gb โดยความเร็วเป็นเกณฑ์สำคัญที่สุด
- ไม่ใช่ network stack แต่เน้นที่ packet forwarding
- เมื่อ DPDK ควบคุม NIC ทราฟฟิกทั้งหมดจะข้ามเคอร์เนล และ NIC นั้นจะมองไม่เห็นจากเคอร์เนล
- พอร์ตจะถูก unbind จากไดรเวอร์เคอร์เนล Linux และถูกจัดการด้วยไดรเวอร์อย่าง
vfio_pci,igb_uio,uio_pci_generic - หลังจากนั้น DPDK PMD จะรับผิดชอบการสื่อสารระหว่างแอปพลิเคชันกับ NIC
- DPDK ต้องตั้งค่า hugepages เพื่อจัดสรร chunk หน่วยความจำขนาดใหญ่
- องค์ประกอบหลัก:
- EAL: generic interface ที่ซ่อนความแตกต่างของสภาพแวดล้อม
librte_ring: API FIFO แบบ lockless สำหรับ multi-producer, multi-consumerlibrte_mempool: การจัดสรร pool ของอ็อบเจ็กต์หน่วยความจำlibrte_mbuf: การสร้างและจัดการ buffer ที่บรรจุแพ็กเก็ตเครือข่ายlibrte_timer: timer service สำหรับรันฟังก์ชันแบบอะซิงโครนัส- PMD: ไดรเวอร์ที่ให้ CPU poll NIC อย่างต่อเนื่องแทนการใช้ interrupt
- ข้อจำกัด:
- พึ่งพาฮาร์ดแวร์สูง
- ต้องจัดสรร CPU core เฉพาะเพื่อรัน PMD และจะใช้ CPU 100%
-
PF_RINGPF_RINGคือ Linux kernel module และ user-space framework ที่ประมวลผลแพ็กเก็ตด้วยความเร็วสูง และให้ API ที่สม่ำเสมอแก่แอปพลิเคชันประมวลผลแพ็กเก็ตPF_RINGใช้ Linux NAPI เพื่อ poll แพ็กเก็ตจาก NIC- NAPI copy แพ็กเก็ตจาก NIC ไปยัง
PF_RINGcircular buffer และแอปพลิเคชันพื้นที่ผู้ใช้อ่านแพ็กเก็ตจาก ring - โครงสร้างนี้มี poller สองตัวคือแอปพลิเคชันและ NAPI ทำให้ CPU cycle ถูกใช้ไปกับ polling
- ข้อดีคือสามารถกระจาย incoming packet ไปยังหลาย ring พร้อมกันได้
- ด้วยโครงสร้างแบบโมดูล จึงสามารถใช้องค์ประกอบเพิ่มเติมอย่าง ZC module, FPGA-based card module, Stack module, Timeline module, Sysdig module ได้
PF_RINGลดต้นทุนของ packet capture และ userland forwarding แต่มีข้อจำกัดด้านประสิทธิภาพสูงสุดเพราะเป็นโครงสร้างสอง actor ที่เคอร์เนล copy จาก NIC ไปยัง ring และ userland อ่านจาก ring มาประมวลผลPF_RINGตั้งแต่เวอร์ชัน 7.5 มีการรองรับAF_XDPadapter
-
XDP และ
AF_XDP- XDP(eXpress Data Path) คือการใช้งาน eBPF ที่ดักจับแพ็กเก็ตตั้งแต่ช่วงต้นใน data path ของเครือข่าย Linux
- XDP ประมวลผล RX packet page โดยตรงภายใน device driver RX function ก่อนการจัดสรร SKB
- eBPF คือ bytecode แบบ sandboxed ที่ผู้ใช้กำหนดเองและรันในเคอร์เนล
- ใช้ register 64-bit จำนวน 11 ตัว และ stack ขนาด 512-byte
- ด้วย LLVM backend สามารถคอมไพล์จาก C, Lua, Go, P4, Rust ฯลฯ ไปเป็น eBPF ได้
- มี in-kernel verifier และ JIT compiler พร้อมรองรับฟีเจอร์อย่าง map, tail call, helper
- กรณีใช้งาน XDP:
- pre-stack filtering เพื่อบรรเทา DoS
- forwarding และ load balancing
- เทคนิค batching อย่าง GRO
- flow sampling และ monitoring
- ULP processing
- XDP ไม่ใช่ kernel bypass แต่เป็น fast path ภายในเคอร์เนลสแต็ก และไม่ได้แทนที่ TCP/IP stack
- ไม่ต้องใช้ฮาร์ดแวร์เฉพาะ แต่มีข้อกำหนดอย่าง multi-queue NIC, TX/RX checksum offload, RSS, TSO
- ข้อดีของ XDP เมื่อเทียบกับ DPDK:
- เลือกใช้ busy polling หรือ interrupt driven networking ได้
- ไม่ต้องใช้ huge pages
- ไม่มีข้อกำหนดด้านฮาร์ดแวร์พิเศษ
- ไม่จำเป็นต้องมี CPU เฉพาะ
- ไม่จำเป็นต้องฉีดแพ็กเก็ตกลับเข้าเคอร์เนลจาก 3rd party userspace application
- ไม่จำเป็นต้องมี security model ใหม่สำหรับการเข้าถึงฮาร์ดแวร์เครือข่าย
- ไม่จำเป็นต้องใช้ 3rd party code/licensing
AF_XDPเป็น socket ประเภทใหม่ที่ถูกนำมาใช้ใน Linux 4.18- สามารถใช้ฟังก์ชันของเคอร์เนลเพื่อสร้างโครงสร้างคล้าย DPDK หรือ
AF_PACKETได้ โดยไม่ต้องข้ามเคอร์เนลทั้งหมด - XDP program สามารถ redirect frame ไปยัง memory buffer ในพื้นที่ผู้ใช้ด้วย eBPF ได้
- DMA transfer รองรับ zero-copy โดยใช้หน่วยความจำพื้นที่ผู้ใช้
- สามารถเพิ่มประสิทธิภาพได้ 3~20 เท่าเมื่อเทียบกับ
AF_PACKET - ข้อจำกัด:
- เป็นโปรเจกต์ที่ค่อนข้างใหม่
- การรองรับอย่างสมบูรณ์ต้องใช้ Linux kernel 5.4 ขึ้นไป
1 ความคิดเห็น
ความคิดเห็นจาก Hacker News
ถ้าได้เห็นข้อมูลนี้ตั้งแต่ไม่กี่สัปดาห์ก่อนคงมีประโยชน์มาก
ผมพยายามทำ การเข้ารหัสลิงก์ L2 ระหว่างดาต้าเซ็นเตอร์ เลยไปขอใบเสนอราคาจากหลายเจ้าเรื่องฮาร์ดแวร์ appliance แต่รู้สึกว่าแพงเกินไป เลยลองทำเอง
ผมตั้งค่าบนฮาร์ดแวร์ทั่วไปให้ขนส่ง Ethernet frame บน overlay network ของ WireGuard เพื่อให้ได้ 10Gbps และหลังจากทำงานอยู่ราวสิบวัน ก็ทำได้ในต้นทุนที่ถูกกว่าข้อเสนอที่ถูกที่สุดประมาณ 70% และถูกกว่าข้อเสนอที่แพงที่สุดประมาณ 95% แต่ก็ต้องอ่านเอกสารละเอียด ๆ และทดลองเองเยอะมาก
ผมอยากใช้เนื้อหาในบทความนี้ตรวจสอบว่าความเข้าใจของตัวเองถูกต้องไหม และมองเผิน ๆ ก็ดูน่าสนใจและครอบคลุมมาก
ถ้ามีพารามิเตอร์ที่ปรับได้เยอะขนาดนี้ ก็น่าจะคุ้มที่จะทำ ซอฟต์แวร์ autotuning
ดูเหมือนจะทำแนวทางคล้าย gradient descent ได้ โดยสุ่มเลือกพารามิเตอร์จาก whitelist แล้วขยับขึ้นหรือลงเล็กน้อยภายในช่วงที่อนุญาต วัดประสิทธิภาพสักพัก ถ้าแย่ลงก็ย้อนกลับ ถ้าดีขึ้นก็ปรับต่อ
น่าสนใจดี แต่ในฐานะวิศวกรซอฟต์แวร์ ผมแทบไม่มีโอกาสได้รันคำสั่งในบทความจริง ๆ
ระบบส่วนใหญ่รันอยู่ในคอนเทนเนอร์ลินุกซ์แบบย่อส่วนบางอย่าง, ไม่มีสิทธิ์เข้าถึงเชลล์บนระบบโปรดักชัน, และสภาพแวดล้อม dev หรือ QA ก็แตกต่างจากโปรดักชันมากเกินไปในแง่โหลดและอย่างอื่น ทำให้การจำลองบั๊กส่วนใหญ่ไม่ค่อยช่วยอะไร
สุดท้ายแล้วโอกาสที่จะได้ลองรันคำสั่งในบทความก็มีแค่ตอนจัดการเครื่องส่วนตัวเอง และคิดว่าน่าจะมีประโยชน์ถ้าทำงานเป็น platform engineer
อย่างหนึ่งที่ผมไม่ค่อยชอบใน Kubernetes คือโมเดลด้าน networking มันตั้งสมมติฐานว่ามี network card แค่ใบเดียว และแอปพลิเคชันก็ควรง่ายพอที่จะไม่ต้องรู้อะไรเกี่ยวกับชั้นล่างกว่านั้น
โมเดล networking ทั้งหมดดูเหมือนยังมีพื้นที่ให้ยกเครื่องครั้งใหญ่ในแบบยุค 2020s เพื่อให้เรียบง่ายขึ้นและดีขึ้น
ในบทความบอกว่า
net.core.wmem_maxคือค่าขีดบนของขนาด TCP send buffer และก็มีnet.ipv4.tcp_wmemด้วย เลยสงสัยสองอย่างnet.core.wmem_maxอย่างไรnet.core.wmem_maxก็เป็น ค่าสูงสุด ตามชื่อเลยส่วน
net.ipv4.tcp_wmemเป็นค่าแบบสามตัว คือค่าต่ำสุด ค่าเริ่มต้น และค่าสูงสุด โดยค่าสูงสุดที่ระบุตรงนี้จะเกินnet.core.wmem_maxไม่ได้TCP ควรเป็นโปรโตคอลเดียวกันไม่ว่าจะขนส่งผ่าน IPv4 หรือ IPv6
ตัวอย่าง: https://docs.redhat.com/en/documentation/red_hat_data_grid/7...
สิ่งที่ขาดไปนิดหน่อยตรงนี้คือการดีบักและการจูนสำหรับ throughput เกิน 100Gbps
เมื่อให้บริการ HTTP ในระดับนั้น คอขวดแรกมักเป็น memory bandwidth จนหลายครั้งต้องใช้ kTLS
เครื่องมืออย่าง AMD μProf มีประโยชน์มากสำหรับการดีบัก และการทำ continuous profiling ด้วย eBPF ก็ช่วยได้มากเช่นกันถ้าอยากเข้าใจให้ชัดว่าในเคอร์เนลและ user space กำลังเกิดอะไรขึ้นบ้าง
ดูเจ๋งทีเดียว ตลอดเส้นทางอาชีพของผมที่ผ่านมา เวลาอยากได้ประสิทธิภาพ มักเริ่มจาก kernel bypass ก่อนเสมอ