การปรับจูนประสิทธิภาพให้ Logstash

Nutmos
KBTG Life
Published in
4 min readMay 26, 2020

มาต่อกันจากบทความตอนที่แล้ว เราได้อธิบายถึงประโยชน์ของ Logstash รวมทั้งวิธีการเขียน Pipeline อย่างถูกวิธี ซึ่งจะช่วยให้ Logstash ทำงานได้อย่างมีประสิทธิภาพมากขึ้น ในบทความนี้เราจะมาอธิบายวิธีการจูนประสิทธิภาพของ Logstash ให้เหมาะสมและมีประสิทธิภาพสูงสุดกันครับ

ขออธิบายคร่าวๆอีกครั้งครับ Logstash คือ อุปกรณ์ที่ใช้ในการประมวลผลข้อมูลก่อนส่งเข้า Elasticsearch ซึ่ง Logstash สามารถรับข้อมูลได้หลายช่องทาง ไม่ว่าจะเป็น Beats, Kafka, Redis, RabbitMQ และอื่นๆ ทำให้นอกจากจะนำมาเป็นส่วนประมวลผลของ ELK Stack แล้ว เรายังสามารถนำไปใช้เชื่อมต่อกับระบบอื่นๆ ได้ด้วย

สำหรับ Use Case หลักที่เราใช้งานจะเป็นการเก็บไฟล์ Log จากเครื่องปลายทางด้วย Filebeat และส่งเข้า Logstash เพื่อประมวลผล จากนั้นส่งเข้า Elasticsearch เพื่อเอาไว้เสิร์ชข้อมูลต่อไป ดังนั้นคำแนะนำจะเป็นสิ่งที่เราได้เรียนรู้จาก Use Case นี้ แต่สามารถนำไปปรับใช้กับอินพุต (Input) และเอาท์พุต (Output) รูปแบบอื่น ๆ ได้เช่นกันครับ

JVM Heap Size

เนื่องจาก Logstash (รวมถึง Elasticsearch) เขียนด้วยภาษา Java ดังนั้นตัวระบบจึงสามารถจูนทุกอย่างได้เหมือนโปรแกรม Java ทั่วไป ซึ่ง JVM Heap Size ที่เป็นการกำหนดเมมโมรีให้ประมวล Java ก็ถือเป็นข้อพื้นฐานและสำคัญที่สุด เพราะเมื่อพูดถึงการจูน Logstash ในส่วนที่เหลือ เกือบทั้งหมดจะต้องแลกมาด้วย Heap Size เสมอ

ภาพจาก Kibana Demo (demo.elastic.co)

การเปิด Monitoring บน Logstash จะช่วยให้เห็นข้อมูลเกี่ยวกับตัว Logstash ได้อย่างละเอียด และนำไปใช้พิจารณาปรับแต่งค่าต่างๆ ได้ดี อ่านเพิ่มเติมได้ที่ Elastic

วิธีดู Heap Size ว่าควรจะปรับเพิ่มหรือไม่ แนะนำให้ดูจากลักษณะการ GC ของ Java คือถ้า GC ในลักษณะฟันปลา และมีการ GC ค่อนข้างสม่ำเสมอ หมายความว่า Heap ยังเหลือพอให้ใช้งาน โดย Heap ที่ใช้งานสูงสุดก่อนจะ GC (ซึ่งก็คือจุดสูงสุดก่อนจะตกลงมา) ไม่ควรเกิน 75% ของ Heap Size สูงสุดที่กำหนดไว้

การจูน Heap Size จะขึ้นอยู่กับทรัพยากรที่ตัวเครื่องมีด้วย ดังนั้นถ้ามีงานที่ต้องใช้ Logstash ประมวลผลจำนวนมาก เราอาจจะต้องแยกเครื่องให้มีแต่ Logstash เพื่อประมวลผล Log โดยเฉพาะเลย

ค่า Heap Size ของ Logstash เป็นค่า Argument ของ Java ดังนั้นเราสามารถเซ็ทค่านี้ได้จากไฟล์ jvm.options โดยมีตัวที่สำคัญอยู่ 2 ตัว คือ -Xms หมายถึงค่า Heap Size ต่ำสุด และ -Xmx หมายถึงค่า Heap Size สูงสุด ยกตัวอย่างเช่น

-Xms1g -Xmx4g

หมายความว่า Logstash จะจอง Heap Size เริ่มต้นที่ 1GB และมีเพดานสูงสุดที่ 4GB

หมายเหตุ: Heap Size ไม่ใช่แรมทั้งหมดที่ JVM จะใช้งาน (เช่น กำหนด Heap ไว้ 1GB แต่ JVM อาจจะใช้แรมจริง ๆ ถึง 1.5GB ได้) ดังนั้นจึงควรเซ็ท Heap Size ให้เหลือแรมบนเครื่องไว้ใช้งานอย่างอื่นด้วย

Workers

โดยปกติแล้ว Logstash จะมีหน่วยย่อยที่เอาไว้ใช้ทำงานภายในเรียกว่า Worker (หรือ Thread) โดยค่าเริ่มต้นของ Worker จะเท่ากับจำนวนซีพียูคอร์ (CPU Core) ของเครื่องที่ใช้รัน Logstash

Logstash ออกแบบมาให้ Worker ทำงานแยกกัน ซึ่งจุดที่ทำให้ Worker เสียเวลาคือส่วนอินพุตที่จะต้องรอข้อมูล และส่วนเอาท์พุตที่จะต้องรอการตอบกลับจากปลายทาง ถ้าเรามี Worker จำนวนมาก ก็เปรียบเสมือนมีคนทำงานมาก หากเรามี Worker บางตัวที่กำลังรออินพุตหรือเอาท์พุต Worker ตัวอื่นที่ยังทำงานต่อไปได้

ค่า Worker นี้สัมพันธ์กับ JVM Heap และซีพียูโดยตรง แม้ว่า Worker จะมีค่าเริ่มต้นเท่ากับจำนวนซีพียูคอร์ แต่สามารถปรับเพิ่มค่าได้ถ้าเห็นว่า JVM Heap และซีพียูยังว่างอยู่ แต่ถ้า Worker เยอะเกินไปก็อาจทำให้ Logstash กินซีพียูและ JVM Heap จนหมดได้ หรือถ้าไม่ได้มีอีเว้นท์เยอะมาก การเพิ่ม Worker ก็ไร้ประโยชน์เพราะจะมี Worker ที่ว่างงานเยอะเกินไป

ค่า Worker สามารถเซ็ทจากไฟล์ logstash.yml โดยกำหนดค่า pipeline.workers หรือเซ็ทผ่าน Command Line โดยใส่ -w หรือ --pipeline.workers

Batch Size + Batch Delay

เบื้องหลัง Worker ของ Logstash คือตัวระบบจะรอเก็บอีเว้นท์จำนวนหนึ่งเป็นระยะเวลาหนึ่ง จากนั้นจึงจะประมวลผลและส่งเข้า Elasticsearch ทีเดียว ซึ่งค่าจำนวนอีเว้นท์และระยะเวลาที่รอจะถูกกำหนดผ่าน Batch Size และ Batch Delay ตามลำดับ

Batch Size คือปริมาณอีเว้นท์ที่แต่ละ Worker ของ Logstash จะรับงาน เช่น ถ้ากำหนด Batch Size มีค่าเป็น 5,000 หมายความว่า Worker ของ Logstash จะรับงานสูงสุด 5,000 อีเว้นท์ จากนั้นจะเริ่มทำงานประมวลผลตาม Pipeline ที่กำหนดไว้

ส่วน Batch Delay เป็นค่าเวลาที่ Worker จะรอรับอีเว้นท์หลังจากอีเว้นท์สุดท้ายเข้ามา หน่วยเป็นมิลลิวินาที เช่น ถ้ากำหนด Batch Delay มีค่าเป็น 50 เมื่อรับอีเว้นท์สุดท้ายเข้ามาแล้ว หากผ่านไปแล้ว 50 วินาทีแล้วไม่มีอีเว้นท์ใหม่เข้ามาอีก Logstash จะสั่งให้ Worker ตัวนั้นทำงานทันที (แต่ถ้ามีอีเว้นท์เข้ามา จะเริ่มนับเวลารอใหม่)

สรุปโดยรวม Batch Size + Batch Delay คือค่าระยะเวลารออีเว้นท์สูงสุดก่อนที่จะเริ่มประมวลผล จะเท่ากับ Batch Size คูณกับ Batch Delay เช่น ถ้ากำหนด Batch Size เป็น 5,000 และ Batch Delay เป็น 50 หมายความว่าระยะเวลารออีเว้นท์สูงสุดจะอยู่ที่ 5,000 x 50 = 250,000 มิลลิวินาที หรือ 4 นาที 10 วินาทีจึงจะเริ่มประมวลผล

ขอย้ำว่าด้านบนนี้เป็นระยะเวลาการรอสูงสุด ในความเป็นจริงแล้วระบบที่มีอีเว้นท์เข้ามาตลอดเวลาไม่มีหยุดพัก มักจะมีอีเว้นท์มาเติมเรื่อย ๆ อยู่แล้ว ดังนั้นเราจึงเน้นการปรับค่า Batch Size มากกว่า

Batch Size จะมีผลไปจนถึงส่วนเอาท์พุต เพราะ Logstash จะส่งอีเว้นท์ออกเอาท์พุต Elasticsearch ทั้ง Batch Size เป็น Bulk Request เพียง 1 Request (เพราะ Logstash จะส่ง Batch Size ทั้งหมดออกเอาท์พุตเป็น 1 Logical Unit) ดังนั้นถ้ารวมอีเว้นท์เป็น Batch Size ขนาดใหญ่ก็จะช่วยลด Overhead ที่จะต้องรอในการส่งออกเอาท์พุตได้

ภาพจาก Wikimedia

ถ้ามอง Logstash เป็นสายการผลิตในโรงงาน Batch Size ก็เปรียบเสมือน “กล่อง” ที่เอาไว้ใส่อีเว้นท์และส่งให้ Worker เอาไปทำงาน โดยให้ 1 กล่องต่อ 1 Worker เมื่อ Worker ทำงานจนส่งออกเอาท์พุตเสร็จแล้วค่อยมารับกล่องใหม่

ดังนั้นการเพิ่ม Batch Size จึงเปรียบเสมือนเพิ่มขนาดกล่อง ซึ่งจะช่วยประหยัดเวลาเพราะเราไม่ต้องเดินไปรับกล่องใหม่ (อินพุต) หรือเดินไปส่งกล่องที่ทำงานเสร็จแล้ว (เอาท์พุต) บ่อย ๆ หมายความว่าเมื่อขนาดอีเว้นท์ต่อ Worker เยอะขึ้น จะช่วยลด Overhead ที่เกิดขึ้นจากการประมวลผลโดยรวมของ Pipeline ได้

เมื่อมีข้อดีก็ต้องมีข้อแลกเปลี่ยน คือค่า Batch Size สัมพันธ์กับ JVM Heap โดยตรง ถ้าใส่ Batch Size น้อยไป Logstash ก็จะใช้ Heap ได้ไม่เต็มที่ แต่ถ้าใส่ Batch Size มากไป Java ก็จะ GC บ่อย ทำให้ประสิทธิภาพของ Logstash ตกลงได้ หนักสุดคือใช้ Heap หมดจนเกิด Out of Memory Exception ทำให้ Logstash หยุดทำงานได้เลย

หรือในกรณีที่โหลดน้อยมากๆ Batch Size สูงๆก็อาจไม่มีผลอะไรเลยเนื่องจากระยะเวลารออาจจะนานจนถึงค่า Batch Delay เสียก่อนที่อีเว้นท์จะเข้ามาจนเต็ม Batch Size

ค่า Batch Size สามารถเซ็ทจากไฟล์ logstash.yml โดยกำหนดค่า pipeline.batch.size หรือเซ็ทผ่าน Command Line โดยใส่ -b หรือ --pipeline.batch.size ส่วน Batch Delay สามารถเซ็ทจากไฟล์ logstash.yml โดยกำหนดค่า pipeline.batch.delay หรือเซ็ทผ่าน Command Line โดยใส่ -u หรือ --pipeline.batch.delay

Separate Instances

เมื่อเราพบว่า Logstash ต้องรับโหลดเพิ่มขึ้นและ JVM ของ Logstash เริ่มใหญ่ สิ่งที่ควรจะทำก็คือการแยก Logstash ออกมาให้รันมากกว่า 1 โปรเซส โดยทุกโปรเซสจะรัน Pipeline ที่ทำงานเดียวกัน

การแยกรัน Logstash มากกว่า 1 โปรเซส จะป้องกันกรณีที่ Logstash เกิดปัญหาไม่สามารถรันได้ เพราะเราจะยังมีอีกตัวที่คอยทำงานทดแทนได้อยู่ และถ้าต้องการ HA คือตัวระบบยังคงสามารถทำงานได้ต่อเนื่อง เราควรจะแยกเครื่องรัน Logstash อย่างน้อย 2 เครื่องนั่นเอง

Multiple Pipelines

ข้อสุดท้ายคือการทำ Multiple Pipelines ข้อนี้จะตรงข้ามกับ Separate Instances คือ Separate Instances เป็นการแยก Logstash ออกมาหลายๆ ตัวเพื่อรัน Pipeline ที่ทำงานเดียวกัน แต่ Multiple Pipelines จะทำตรงข้าม คือรวม Pipeline ที่โหลดน้อยๆ เข้ามาเป็น Logstash ตัวเดียว

การรัน Multiple Pipelines คือการนำ Pipeline ของ Logstash ที่รันแยกกันมารันภายใต้ JVM เดียว เพื่อรวมงานของ Logstash ที่มีโหลดน้อยๆ เข้าด้วยกัน ลด Overhead ที่เกิดขึ้นจากการที่ Logstash ต้องสร้าง JVM หลายๆ JVM เพื่อรัน Pipeline ที่มีโหลดเข้ามาน้อยๆ หลาย ๆ ตัว

วิธีรัน Multiple Pipeline คือเราจะต้องกำหนด Pipeline ลงในไฟล์ pipelines.yml (ดูได้จากเว็บไซต์ Elastic) ซึ่งเราสามารถตั้งค่า Worker และ Batch Size แยกตามแต่ละ Pipeline ได้

สรุป

โดยรวมแล้ว ​Logstash มีวิธีปรับจูนประสิทธิภาพในหลายแง่มุม ซึ่งโดยปกติแล้ว Pipeline ของ Logstash ก็มีไว้ใช้ประมวลผลข้อมูลหลากหลายรูปแบบ ดังนั้น เราควรพิจารณาลักษณะการทำงานของ Logstash ด้วย เพื่อนำไปพิจารณาปรับปรุงให้ Logstash ทำงานได้เต็มประสิทธิภาพต่อไป

ข้อมูลส่วนหนึ่งจาก Elastic โดยเพิ่มเติมและเรียบเรียงจากประสบการณ์ตรงของทีม งานครับ

--

--