👦Bottlenecks of Software
อยู่ๆแอพที่ทำก็ช้าเป็นเต่าเฉยเลย เกิดจากอะไรและแก้ไงดี ?
Last updated
อยู่ๆแอพที่ทำก็ช้าเป็นเต่าเฉยเลย เกิดจากอะไรและแก้ไงดี ?
Last updated
เชื่อว่าหลายๆคนที่ได้เขียนโปรแกรมที่ใช้จริงมาซักระดับนึงน่าจะเคยมีประสบการณ์ว่า แอพที่เคยทำงานได้รวดเร็วดุจสายฟ้า แต่อยู่มาก็ดันช้าเป็นเต่าซะงั้น พยายามเปลี่ยนโค้ดจุดนั้นนู้นนี่ก็แล้ว จัดการ database ก็แล้ว (ทำทุกๆอย่างให้เธอแล้ว) แต่ก็ยังเป็นเต่าอยู่ดี หรือบางทีก็อาการดีขึ้น แต่ซักพักก็วนกลายร่างเป็นเต่าเช่นเคย เฮ่อ...เหนื่อยใจ ตอนนี้เลยได้แต่พึ่งเจ้าพ่อนั่งจุดธูปเช้าเย็นหวังว่ามันจะไม่ล่มก็พอใจละ
จากปัญหาที่ว่ามามันเกิดจากอะไรได้บ้าง แล้วเราจะแก้ยังไงดี ?
ออกตัวไว้ก่อนเลยว่า ไม่สามารถบอกวิธีการแก้ปัญหาแบบรวบรัดได้ เพราะการแก้ปัญหาในเรื่องนี้ มันต้องไปนั่งวิเคราะห์เป็นเคสๆเลย เพราะ Software & Hardware Architecture ของแต่ละเคสมันไม่เหมือนกันยังไงล่ะ เลยไม่มียาเทพที่กินเม็ดเดียวแล้วหาย ดังนั้นในบทความนี้จะชี้แนะว่า จะวิเคราะห์ปัญหานี้ทำยัง และ มันเกิดจากอะไรได้บ้าง ยังไงละ
ก่อนเฉลยผมอยากให้เข้าใจตรงกันก่อนว่า ตอนที่โปรแกรมมันของเราทำงานเรื่องอะไรก็ตาม มันจะประมวลผลเป็นรูปแบบที่เรียกว่า Pipeline หรือทำงานกันเป็นทอดๆนั่นเอง เช่น เราสั่งให้มันไปคำนวณให้หน่อยดิ๊ว่านักเรียนแต่ละคนได้เกรดอะไร ซึ่งภายใน pipeline นั้นก็จะไปทำงานประมาณนี้
ดึงรายชื่อนักเรียนจาก database
คำนวณเกรดของนักเรียนแต่ละคน
แสดงผลออกทาง View
สมมุติว่าแอพของเรามันช้า สิ่งที่เราต้องไปไล่ดูคือ
หาว่ามันช้าจากการทำงานเรื่องอะไรบ้าง แล้วค่อยไปไล่ดู Pipeline ของมันต่อ
จากที่ว่ามาทั้งหมด ประเด็นสำคัญที่สุดในการแก้ปัญหาเรื่องแอพหน่วงคือการหา คอขวด หรือ Bottleneck นั่นเอง ซึ่งเจ้า คอขวดนี้แหละมันจะซ่อนตัวอยู่ภายใน Pipeline ของเราอีกที
การจัดการคอขวด เป็นหลักพื้นฐานในการทำ Scalable เลย ซึ่งเป็นจุดชี้เป็นชี้ตายว่าตัวโปรแกรมของเราจะรับโหลดได้สูงสุดเท่าไหร่ เช่นจะรับ concurrent user เป็นแสนๆต่อวินาทีได้หรือเปล่า บลาๆ
หลายคนอาจจะสงสัยว่า ทำไมไม่ไปเขียนโค้ดให้มันทำ performance ดีๆ ไม่ก็จัดการเคลีย database จัด index, cleaning บลาๆ ไปเลยล่ะ จะไปไล่หาคอขวดมันทำไม?
คำตอบคือ สมมุติว่าเราไล่แก้โค้ดให้มันเทพทุกตัวจริงๆ หรือจัดการ database จนลื่นหัวแตกจริงๆ แต่คอขวดมันยังมีอยู่ สุดท้ายโดยรวมกันก็ช้าอยู่ดีนั่นแหละ ไม่เชื่อลองดูภาพปลากรอบจิ
จากภาพจะเห็นว่า ต่อให้เราทำ performance ที่จุดต่างๆที่เราคิดว่าควรทำหมดแล้ว แต่เราไม่ได้แก้ คอขวด สุดท้ายโดยรวมความเร็วที่ทำได้ทั้งหมดก็เท่ากับที่คอขวดทำได้อยู่ดีนั่นแหละ
เยอะม๊วกกกกกกๆๆๆ แต่ขอลิสต์แค่ตัวหลักๆก่อนนะ ดูได้จากรายการด้านล่างเลย ซึ่งแค่เห็นก็ปวดตับละ
แก้คอขวด 1 เรื่อง ไม่ได้หมายความว่าปัญหาจะหายไปเพราะมันอยู่กันเป็นฝูง
ในบทความนี้ผมจะลงรายละเอียดเพียงแค่ 3 เรื่องเท่านั้น ไม่งั้นมันจะไม่ได้อยู่ในหมวดความรู้พื้นฐานสำหรับมือใหม่ละ ฮ่าๆ ซึ่งทั้ง 3 เรื่องที่ว่านั้นคือ Programmin
, Database
และ Limitations
ดังนั้นไปดูแต่ละเรื่องเลยว่ามันมีอะไรที่เกี่ยวกับมันบ้าง
ปัจจัยของการเขียนโค้ดแล้วทำให้เกิด คอขวด นั้นมีหลายอย่างเลย ซึ่งผมขอยกตัวอย่างเท่าที่นึกออกก่อนนะ
การเลือกใช้คำสั่งที่เหมาะสมนั้นมีผลสูงมากต่อ performance ของโปรแกรม ถ้าต้องทำในระยะยาว ดังนั้นเวลาที่เราจะทำ Refactor Code เพื่อทำ performance เราจะต้องเลือกใช้คำสั่งที่เหมาะสมกับงานนั้นๆด้วย
ตัวอย่าง สมมุติว่าผมต้องการให้โค้ดหาผลรวมตั้งแต่เลข 1 จนถึงเลขที่ผมใส่เข้าไป เช่น ผมใส่เลข 3 คำตอบคือ 6 เพราะเกิดจาก 1+2+3 แล้วผมเขียนโค้ดออกมาแบบนี้
แล้วลองเปรียบเทียบกับผมเขียนโค้ดแบบนี้
แม้ว่าโค้ดทั้ง 2 แบบจะให้คำตอบที่ถูกได้ทั้งคู่ก็ตาม แต่ BigO ของทั้ง 2 ตัวต่างกันคนละโลกเลย (ตัวที่ 2 เร็วกว่า) หรือลองคิดแบบนี้ดูก็ได้ว่า ถ้าเราเลือกใช้ของที่ไม่เหมาะสมกับงาน มันก็เหมือนกับการเอาค้อนไปเลื่อยไม้นั่นแหละ ซึ่งมันก็อาจจะเลื่อยได้(มั้ง) แต่มันก็ไม่ได้เร็วเท่าที่มันควรจะเป็นยังไงล่ะ
การคิดวิธีการรับมือของงานแต่ละแบบก็มีผลสูงมากนะ ผมขอยกตัวอย่างให้เห็นภาพตามนี้
ตัวอย่าง 1 ถ้าเราไปดึงรายการสินค้าที่มีเป็นแสนๆรายการมาแสดงผลในหน้าเดียวมันจะเกิดอะไรขึ้น? แอพอาจจะเด้งเลยก็ได้ อีกทั้งเสียเวลาทั้ง server ทั้ง bandwidth ที่ไปดาวโหลดไฟล์มาอีกด้วย การแก้ปัญหา แทนที่จะโหลดมาตูมเดียวเราก็แบ่งโหลดก็ได้นิ หรือเราเรียกว่าการทำ Paging
ตัวอย่าง 2 ถ้าโค้ดมีการสร้าง database connection ใหม่เรื่อยๆโดยไม่คืน resource เลยสุดท้าย database ก็ตายแอพก็ตายตามกันไป การแก้ปัญหา เปิด connection แล้วปิดด้วย หรือใช้พวกที่มีการจัดการ connection pooling ที่เหมาะสม
ตัวอย่าง 3 การทำงานต่างๆ ถ้ามันมีเวลาในการทำงานที่สูงมากก็อาจจะเกิดปัญหาภายหลังได้ การแก้ปัญหา พยายามให้ Roundtrip มันสั้นเข้าไว้
ในภาษาแต่ละภาษามันจะมีขีดจำกัดทางสายเลือดของมันอยู่ ดังนั้นเราก็ควรจะต้องไปศึกษาว่ามันมีข้อจำกัดอะไรบ้าง และจะจัดการกับของพวกนั้นยังไง ยกตัวอย่างเช่น
Recursive function - บางภาษาการเขียน recursive function ไม่ใช่เรื่องดีนัก แต่ในขณะเดียวกันบางภาษาจะเก่งกับการทำ recursive function เอามากๆ
Structural & Unstructured - บางภาษาเวลาที่เราจะใช้มันต้องสร้างโครงสร้างให้มันก่อนถึงจะทำงานได้ เช่น Class แต่ในขณะเดียวกันบางภาษาสามารถเรียกใช้งานโดยไม่ต้องสร้างโครงสร้างให้มันก็ได้ เช่น Javascript
บางภาษาพอเขียนเสร็จปุ๊ปก็เอาไปใช้งานได้เลย เช่น php แต่ก็มีอีกหลายๆภาษาที่ต้องทำการ compile เสียก่อนถึงจะใช้งานได้ เช่น C# ซึ่งในการ build ก็มีหลายระดับ เช่น debug, release ไรพวกนี้ ซึ่งประสิทธิภาพของพวกนี้ก็ไม่เหมือนกันด้วย
ปัจจัยที่เกิด คอขวด ของ database ก็มีหลายอย่างเช่นกัน ขอยกตัวอย่างเท่าที่นึกออกเช่นกัน (เพราะอันที่นึกไม่ออกก็ไม่รู้จะเขียนไร ฮา)
การออกแบบที่ไม่เหมาะสมนี่เจอบ่อยฝุดๆ เช่นกำหนดขนาดที่ไม่เหมาะสม varchar, nvarchar หรือไปกำหนดของให้มันเป็น text ทั้งๆที่มันไม่ควรเป็น text เลวร้ายกว่านั้นคือไปใช้ char แทน boolean ไรงี้ (พูดเรื่องพวกนี้แล้วปวดใจ) และรวมถึงการตั้งชื่อหัวตารางที่ไม่เหมาะสมด้วย N1, N2, N3, N4 ... เฮ่อเหนื่อยใจ
การทำ normalization มากจนเกินไปบางทีก็ไม่ดีนะจ๊ะ และอย่าเมากาวเอาแต่ยึดติดกับการทำ normalize ด้วย เพราะเป้าหมายของ database ไม่ใช่การทำ normalize แต่เป็นการเก็บและรักษาข้อมูลได้ต่างหาก ซึ่งในบางทีเราอาจจะไม่ต้องทำ normalize ถึงระดับ 3 ก็ได้ ขึ้นอยู่กับความเหมาะสมของหน้างานที่เจอ ว่าคนเอาไปใช้ในเคสนั้นๆเหมาะกับแบบไหน
ตรงตัวเลยจะเก็บข้อมูลซ้ำๆกันไปทำไม ? แต่ถ้ามันจำเป็นที่จะต้องทำก็ทำไปเถอะ ขึ้นกับความเหมาะสมของหน้างาน
การเชื่อมตารางแบบต่างๆ เช่น 1-1, 1-M และ M-M ไรพวกนี้ก็มีผลกับ performance นะ ซึ่งยิ่งเยอะ performance ยิ่งตก
ตัว database แต่ละตัวมันก็มีความสามารถที่ติดมากับมันอยู่แล้วนะ ลองไปศึกษาใช้งานมันดูบ้างก็จะทำ performance กลับมาได้เยอะพอตัวเลย เช่น การใช้ Views, Indexes, Stored procedures, Constraints, Triggers อะไรเทือกนี้
คือการที่เราปล่อยให้เขามาดึงข้อมูลได้โดยไม่ใส่ข้อจำกัดอะไรเลย เช่นดึงไปทีเดียวเป็นล้าน records เลย แล้วถ้าเขาส่งมาขอแบบนี้รัวๆ database มันจะไม่ล่มได้ไง? ดังนั้นจัดการกำหนด limit มันบ้างซะ
เคยเจอตารางมหาเทพไหม ที่มีทุกอย่างอยู่ในตารางนั้นเลย ข้อมูลลูกค้า ข้อมูลสินค้า ที่อยู่จัดส่ง บลาๆ จะบ้าตาย แค่คิดยินนี่ก็ปวดหัวที่จะไปเขียน query ด้วยละ นี่ยังไม่รวมว่าถ้าจะไป maintenance มันด้วยนี่ .... เอิ่ม
ตัว database เราทำเทสครั้งสุดท้ายเมื่อไหร่ ? แล้วจะรู้ได้ไงว่าที่ออกแบบมามันออกแบบได้เหมาะสมกับงานมันแล้ว ? หรือรอให้มันขึ้น production แล้วให้ user จริงมาเทส ?
ตัวอย่างที่จะเกิดคอขวดที่ database
เขียน query ที่ทำให้มันรอนานมากๆ เช่น ดึงข้อมูลจากตาราง A แล้วเอาไป Join กับตาราง B แล้วก็ intersect กับตาราง C .... Z ไรงี้ อย่าทำ เสียทรัพยากรเครื่องอันมีค่าโดยใช่เหตุ
ออกแบบกฎที่อาจจะมีปัญหา เช่น A reference B และ B ก็ reference A ส่วนถ้าจะลบ A ต้องลบ B ด้วย และถ้าจะลบ B ก็ต้องลบ A ด้วยไรงี้
ตัว database เองก็มีขีดจำกัดทางสายเลือดของมันเหมือนกัน เช่น database บางตัวทำงานกับกับข้อมูลที่ไม่เกินระดับ GB เท่านั้น หรือบางตัวต้องออกแบบโครงสร้างก่อนถึงจะใช้งานได้ แต่บางตัวไม่ต้องมีโครงสร้างเลย บลาๆ ดังนั้นไปศึกษาให้ดีเสียก่อนที่จะเลือกใช้ database
เอาง่ายๆนะแค่ทำตาม Design guideline & Best practices ของภาษาหรือ database ที่เราใช้เพียงแค่นี้ก็ช่วยได้เยอะแล้ว เพราะ 90% ของ Developer และ DBA บ้านเราไม่ยอมไปอ่านของพวกนี้กัน แล้วก็มาจับงานจริงเลยทำให้ คอขวด กระจายเต็มไปหมดเบย (ขอบคุณครับผมจะได้มีงานไปบรรยายเรื่องพวกนี้หากินต่อ ผมคิดในใจนะไม่ได้พูดออกมา ฮี่ๆ) ซึ่งในพวก guideline พวกนั้นก็จะแนะนำไว้หลายเรื่องเลย เช่น เรื่องของ database
Caching
Hot & Cold data
Vertical & Horizontal Scaling
Federation - Splitting into multiple DBs based on function
Sharding - Splitting one dataset up across multiple hosts
Moving some functionality to other types of DBs
ตัว database ในโลกนี้ (ณ ตอนที่เขียนบทความนี้) มีทั้งหมด 2 ตระกูล หลักคือ
ก็ที่เราใช้ๆกันมามีเส้นโยงยั้วเยี้ยนั่นแหละ ซึ่งของพวกนี้ต้องมีโครงสร้างก่อนถึงจะสามารถใช้งานได้ พูดง่ายๆต้องกำหนดก่อนว่าตารางนี้จะเก็บข้อมูลกี่ฟิล์อะไรบ้างและแต่ละฟิล์จะเป็นชนิดข้อมูลอะไร บลาๆ
คือตรงข้ามกับตระกูลแรกเลย ซึ่งเจ้าตัวนี้จะเก็บข้อมูลได้ยืดหยุ่นและหลากหลายกว่า เพราะมันไม่ต้องประกาศโครงสร้างก่อนใช้งาน เช่น ตารางเดียวกันก็มีหลายโครงสร้างได้ และมันไม่ได้มีแค่แบบตารางเท่านั้น
ซึ่งเจ้าตระกูลนี้จะแตกย่อยออกเป็น 4 สายตามนี้เลย
ตัวสีน้ำเงินสามารถกดไปลองเล่นได้นะ ส่วนรายละเอียดแต่ละตัวเป็นยังไง เดี๋ยวว่างๆจะมาเขียนให้ละกัน ลองติดตามดูได้จาก side menu ละกัน
การที่โปรแกรมมันช้านั้นเพราะมันมี คอขวด ในระบบเกิดขึ้น ซึ่งมันจะแอบซ่อนอยู่ภายใน Pipeline ของระบบ โดยเจ้าคอขวดมันมากับผองเพื่อนของมัน ดังนั้นถ้าเราจะเอามันออกเราต้องไปไล่ดูว่ามีจุดไหนที่เป็นขอควดแล้วไปไล่เก็บผองเพื่อนที่ทำให้ระบบเราช้า เพียงเท่านี้ระบบเราก็จะกลับมาเร็วขึ้นแล้ว
เกร็ดความรู้ ของทุกอย่างที่เรียนที่รู้มา อย่ายึดมั่นถือมั่นเมากาวตะบี้ตะบันบังคับให้มันเป็นไปตามที่ได้เรียนมา เพราะของทุกอย่างมีดีมีเสียเสมอ ดังนั้นให้เลือกทำให้เหมาะสมกับหน้างานที่เกิดขึ้นด้วย
ชื่อ
ความหมาย
OS
ตัว Operating System แต่ละตัวก็จะเก่งในงานไม่เหมือนกัน
การจัดการแต่ละเรื่องก็ไม่เท่ากัน
Hardware
อุปกรณ์ต่างๆที่ใช้ เช่น ฮาร์ดดิส แบบ SSD ก็เห็นผลชัดแล้ว
Environments
สภาพแวดล้อมของเซิฟเวอร์ก็มีผลนะ ร้อนไปเย็นไป หรือ มีแอพติดตั้งในเซิฟเวอร์นั้นเป็นร้อยตัวดูดิ
Programming
ประสิทธิภาพของโค้ดที่เขียนให้กับโปรแกรมนั้นๆ
Database
การออกแบบและเลือกใช้ฐานข้อมูล
Network
การเชื่อมต่อต่างๆของเซิฟเวอร์ เช่นเน็ทกาก หรือแบ่ง subnet ไม่ดีใช้อุปกรณ์ไม่เหมาะสมไรงี้
Limitations
ขีดจำกัดทางสายเลือดบางประการ
ชื่อประเภท database
ตัวอย่าง product
Graph
neo4J, OrientDB, Titan
Key-Value store
Redis, Amazon DynamoDB
Document database
MongoDB, Couchbase
Column store
Apache HBase, Cassandra