👦Bottlenecks of Software
อยู่ๆแอพที่ทำก็ช้าเป็นเต่าเฉยเลย เกิดจากอะไรและแก้ไงดี ?
😢 ปัญหา
เชื่อว่าหลายๆคนที่ได้เขียนโปรแกรมที่ใช้จริงมาซักระดับนึงน่าจะเคยมีประสบการณ์ว่า แอพที่เคยทำงานได้รวดเร็วดุจสายฟ้า แต่อยู่มาก็ดันช้าเป็นเต่าซะงั้น พยายามเปลี่ยนโค้ดจุดนั้นนู้นนี่ก็แล้ว จัดการ database ก็แล้ว (ทำทุกๆอย่างให้เธอแล้ว) แต่ก็ยังเป็นเต่าอยู่ดี หรือบางทีก็อาการดีขึ้น แต่ซักพักก็วนกลายร่างเป็นเต่าเช่นเคย เฮ่อ...เหนื่อยใจ ตอนนี้เลยได้แต่พึ่งเจ้าพ่อนั่งจุดธูปเช้าเย็นหวังว่ามันจะไม่ล่มก็พอใจละ
จากปัญหาที่ว่ามามันเกิดจากอะไรได้บ้าง แล้วเราจะแก้ยังไงดี ?
😄 วิธีแก้ปัญหา
ออกตัวไว้ก่อนเลยว่า ไม่สามารถบอกวิธีการแก้ปัญหาแบบรวบรัดได้ เพราะการแก้ปัญหาในเรื่องนี้ มันต้องไปนั่งวิเคราะห์เป็นเคสๆเลย เพราะ Software & Hardware Architecture ของแต่ละเคสมันไม่เหมือนกันยังไงล่ะ เลยไม่มียาเทพที่กินเม็ดเดียวแล้วหาย ดังนั้นในบทความนี้จะชี้แนะว่า จะวิเคราะห์ปัญหานี้ทำยัง และ มันเกิดจากอะไรได้บ้าง ยังไงละ
🤔 ทำไมแอพถึงช้า ?
ก่อนเฉลยผมอยากให้เข้าใจตรงกันก่อนว่า ตอนที่โปรแกรมมันของเราทำงานเรื่องอะไรก็ตาม มันจะประมวลผลเป็นรูปแบบที่เรียกว่า Pipeline หรือทำงานกันเป็นทอดๆนั่นเอง เช่น เราสั่งให้มันไปคำนวณให้หน่อยดิ๊ว่านักเรียนแต่ละคนได้เกรดอะไร ซึ่งภายใน pipeline นั้นก็จะไปทำงานประมาณนี้
ดึงรายชื่อนักเรียนจาก database
คำนวณเกรดของนักเรียนแต่ละคน
แสดงผลออกทาง View
สมมุติว่าแอพของเรามันช้า สิ่งที่เราต้องไปไล่ดูคือ
หาว่ามันช้าจากการทำงานเรื่องอะไรบ้าง แล้วค่อยไปไล่ดู Pipeline ของมันต่อ
จากที่ว่ามาทั้งหมด ประเด็นสำคัญที่สุดในการแก้ปัญหาเรื่องแอพหน่วงคือการหา คอขวด หรือ Bottleneck นั่นเอง ซึ่งเจ้า คอขวดนี้แหละมันจะซ่อนตัวอยู่ภายใน Pipeline ของเราอีกที
การจัดการคอขวด เป็นหลักพื้นฐานในการทำ Scalable เลย ซึ่งเป็นจุดชี้เป็นชี้ตายว่าตัวโปรแกรมของเราจะรับโหลดได้สูงสุดเท่าไหร่ เช่นจะรับ concurrent user เป็นแสนๆต่อวินาทีได้หรือเปล่า บลาๆ
🤔 ทำไมต้องต้องไล่แก้คอขวดเพียงอย่างเดียว ?
หลายคนอาจจะสงสัยว่า ทำไมไม่ไปเขียนโค้ดให้มันทำ performance ดีๆ ไม่ก็จัดการเคลีย database จัด index, cleaning บลาๆ ไปเลยล่ะ จะไปไล่หาคอขวดมันทำไม?
คำตอบคือ สมมุติว่าเราไล่แก้โค้ดให้มันเทพทุกตัวจริงๆ หรือจัดการ database จนลื่นหัวแตกจริงๆ แต่คอขวดมันยังมีอยู่ สุดท้ายโดยรวมกันก็ช้าอยู่ดีนั่นแหละ ไม่เชื่อลองดูภาพปลากรอบจิ
จากภาพจะเห็นว่า ต่อให้เราทำ performance ที่จุดต่างๆที่เราคิดว่าควรทำหมดแล้ว แต่เราไม่ได้แก้ คอขวด สุดท้ายโดยรวมความเร็วที่ทำได้ทั้งหมดก็เท่ากับที่คอขวดทำได้อยู่ดีนั่นแหละ
🤔 อะไรบ้างที่ทำให้เกิดคอขวด ?
เยอะม๊วกกกกกกๆๆๆ แต่ขอลิสต์แค่ตัวหลักๆก่อนนะ ดูได้จากรายการด้านล่างเลย ซึ่งแค่เห็นก็ปวดตับละ
ชื่อ | ความหมาย |
OS | ตัว Operating System แต่ละตัวก็จะเก่งในงานไม่เหมือนกัน การจัดการแต่ละเรื่องก็ไม่เท่ากัน |
Hardware | อุปกรณ์ต่างๆที่ใช้ เช่น ฮาร์ดดิส แบบ SSD ก็เห็นผลชัดแล้ว |
Environments | สภาพแวดล้อมของเซิฟเวอร์ก็มีผลนะ ร้อนไปเย็นไป หรือ มีแอพติดตั้งในเซิฟเวอร์นั้นเป็นร้อยตัวดูดิ |
Programming | ประสิทธิภาพของโค้ดที่เขียนให้กับโปรแกรมนั้นๆ |
Database | การออกแบบและเลือกใช้ฐานข้อมูล |
Network | การเชื่อมต่อต่างๆของเซิฟเวอร์ เช่นเน็ทกาก หรือแบ่ง subnet ไม่ดีใช้อุปกรณ์ไม่เหมาะสมไรงี้ |
Limitations | ขีดจำกัดทางสายเลือดบางประการ |
แก้คอขวด 1 เรื่อง ไม่ได้หมายความว่าปัญหาจะหายไปเพราะมันอยู่กันเป็นฝูง
🤔 คอขวดเต็มไปหมดแล้วเอาไง ?
ในบทความนี้ผมจะลงรายละเอียดเพียงแค่ 3 เรื่องเท่านั้น ไม่งั้นมันจะไม่ได้อยู่ในหมวดความรู้พื้นฐานสำหรับมือใหม่ละ ฮ่าๆ ซึ่งทั้ง 3 เรื่องที่ว่านั้นคือ Programmin
, Database
และ Limitations
ดังนั้นไปดูแต่ละเรื่องเลยว่ามันมีอะไรที่เกี่ยวกับมันบ้าง
🔥 Programming
ปัจจัยของการเขียนโค้ดแล้วทำให้เกิด คอขวด นั้นมีหลายอย่างเลย ซึ่งผมขอยกตัวอย่างเท่าที่นึกออกก่อนนะ
🔹 Algorithms and data structures
การเลือกใช้คำสั่งที่เหมาะสมนั้นมีผลสูงมากต่อ performance ของโปรแกรม ถ้าต้องทำในระยะยาว ดังนั้นเวลาที่เราจะทำ Refactor Code เพื่อทำ performance เราจะต้องเลือกใช้คำสั่งที่เหมาะสมกับงานนั้นๆด้วย
ตัวอย่าง สมมุติว่าผมต้องการให้โค้ดหาผลรวมตั้งแต่เลข 1 จนถึงเลขที่ผมใส่เข้าไป เช่น ผมใส่เลข 3 คำตอบคือ 6 เพราะเกิดจาก 1+2+3 แล้วผมเขียนโค้ดออกมาแบบนี้
แล้วลองเปรียบเทียบกับผมเขียนโค้ดแบบนี้
แม้ว่าโค้ดทั้ง 2 แบบจะให้คำตอบที่ถูกได้ทั้งคู่ก็ตาม แต่ BigO ของทั้ง 2 ตัวต่างกันคนละโลกเลย (ตัวที่ 2 เร็วกว่า) หรือลองคิดแบบนี้ดูก็ได้ว่า ถ้าเราเลือกใช้ของที่ไม่เหมาะสมกับงาน มันก็เหมือนกับการเอาค้อนไปเลื่อยไม้นั่นแหละ ซึ่งมันก็อาจจะเลื่อยได้(มั้ง) แต่มันก็ไม่ได้เร็วเท่าที่มันควรจะเป็นยังไงล่ะ
🔹 Strategies
การคิดวิธีการรับมือของงานแต่ละแบบก็มีผลสูงมากนะ ผมขอยกตัวอย่างให้เห็นภาพตามนี้
ตัวอย่าง 1 ถ้าเราไปดึงรายการสินค้าที่มีเป็นแสนๆรายการมาแสดงผลในหน้าเดียวมันจะเกิดอะไรขึ้น? แอพอาจจะเด้งเลยก็ได้ อีกทั้งเสียเวลาทั้ง server ทั้ง bandwidth ที่ไปดาวโหลดไฟล์มาอีกด้วย การแก้ปัญหา แทนที่จะโหลดมาตูมเดียวเราก็แบ่งโหลดก็ได้นิ หรือเราเรียกว่าการทำ Paging
ตัวอย่าง 2 ถ้าโค้ดมีการสร้าง database connection ใหม่เรื่อยๆโดยไม่คืน resource เลยสุดท้าย database ก็ตายแอพก็ตายตามกันไป การแก้ปัญหา เปิด connection แล้วปิดด้วย หรือใช้พวกที่มีการจัดการ connection pooling ที่เหมาะสม
ตัวอย่าง 3 การทำงานต่างๆ ถ้ามันมีเวลาในการทำงานที่สูงมากก็อาจจะเกิดปัญหาภายหลังได้ การแก้ปัญหา พยายามให้ Roundtrip มันสั้นเข้าไว้
🔹 Language limitations
ในภาษาแต่ละภาษามันจะมีขีดจำกัดทางสายเลือดของมันอยู่ ดังนั้นเราก็ควรจะต้องไปศึกษาว่ามันมีข้อจำกัดอะไรบ้าง และจะจัดการกับของพวกนั้นยังไง ยกตัวอย่างเช่น
Recursive function - บางภาษาการเขียน recursive function ไม่ใช่เรื่องดีนัก แต่ในขณะเดียวกันบางภาษาจะเก่งกับการทำ recursive function เอามากๆ
Structural & Unstructured - บางภาษาเวลาที่เราจะใช้มันต้องสร้างโครงสร้างให้มันก่อนถึงจะทำงานได้ เช่น Class แต่ในขณะเดียวกันบางภาษาสามารถเรียกใช้งานโดยไม่ต้องสร้างโครงสร้างให้มันก็ได้ เช่น Javascript
🔹 Build & Compile level
บางภาษาพอเขียนเสร็จปุ๊ปก็เอาไปใช้งานได้เลย เช่น php แต่ก็มีอีกหลายๆภาษาที่ต้องทำการ compile เสียก่อนถึงจะใช้งานได้ เช่น C# ซึ่งในการ build ก็มีหลายระดับ เช่น debug, release ไรพวกนี้ ซึ่งประสิทธิภาพของพวกนี้ก็ไม่เหมือนกันด้วย
🔥 Database
ปัจจัยที่เกิด คอขวด ของ database ก็มีหลายอย่างเช่นกัน ขอยกตัวอย่างเท่าที่นึกออกเช่นกัน (เพราะอันที่นึกไม่ออกก็ไม่รู้จะเขียนไร ฮา)
🔹 Poor design
การออกแบบที่ไม่เหมาะสมนี่เจอบ่อยฝุดๆ เช่นกำหนดขนาดที่ไม่เหมาะสม varchar, nvarchar หรือไปกำหนดของให้มันเป็น text ทั้งๆที่มันไม่ควรเป็น text เลวร้ายกว่านั้นคือไปใช้ char แทน boolean ไรงี้ (พูดเรื่องพวกนี้แล้วปวดใจ) และรวมถึงการตั้งชื่อหัวตารางที่ไม่เหมาะสมด้วย N1, N2, N3, N4 ... เฮ่อเหนื่อยใจ
🔹 Normalization
การทำ normalization มากจนเกินไปบางทีก็ไม่ดีนะจ๊ะ และอย่าเมากาวเอาแต่ยึดติดกับการทำ normalize ด้วย เพราะเป้าหมายของ database ไม่ใช่การทำ normalize แต่เป็นการเก็บและรักษาข้อมูลได้ต่างหาก ซึ่งในบางทีเราอาจจะไม่ต้องทำ normalize ถึงระดับ 3 ก็ได้ ขึ้นอยู่กับความเหมาะสมของหน้างานที่เจอ ว่าคนเอาไปใช้ในเคสนั้นๆเหมาะกับแบบไหน
🔹 Redundancy
ตรงตัวเลยจะเก็บข้อมูลซ้ำๆกันไปทำไม ? แต่ถ้ามันจำเป็นที่จะต้องทำก็ทำไปเถอะ ขึ้นกับความเหมาะสมของหน้างาน
🔹 Bad Referential Integrity
การเชื่อมตารางแบบต่างๆ เช่น 1-1, 1-M และ M-M ไรพวกนี้ก็มีผลกับ performance นะ ซึ่งยิ่งเยอะ performance ยิ่งตก
🔹 Not Taking Advantage of DB Engine Features
ตัว database แต่ละตัวมันก็มีความสามารถที่ติดมากับมันอยู่แล้วนะ ลองไปศึกษาใช้งานมันดูบ้างก็จะทำ performance กลับมาได้เยอะพอตัวเลย เช่น การใช้ Views, Indexes, Stored procedures, Constraints, Triggers อะไรเทือกนี้
🔹 No limitations on table or column name size
คือการที่เราปล่อยให้เขามาดึงข้อมูลได้โดยไม่ใส่ข้อจำกัดอะไรเลย เช่นดึงไปทีเดียวเป็นล้าน records เลย แล้วถ้าเขาส่งมาขอแบบนี้รัวๆ database มันจะไม่ล่มได้ไง? ดังนั้นจัดการกำหนด limit มันบ้างซะ
🔹 One table to hold all domain values
เคยเจอตารางมหาเทพไหม ที่มีทุกอย่างอยู่ในตารางนั้นเลย ข้อมูลลูกค้า ข้อมูลสินค้า ที่อยู่จัดส่ง บลาๆ จะบ้าตาย แค่คิดยินนี่ก็ปวดหัวที่จะไปเขียน query ด้วยละ นี่ยังไม่รวมว่าถ้าจะไป maintenance มันด้วยนี่ .... เอิ่ม
🔹 Lack of testing
ตัว database เราทำเทสครั้งสุดท้ายเมื่อไหร่ ? แล้วจะรู้ได้ไงว่าที่ออกแบบมามันออกแบบได้เหมาะสมกับงานมันแล้ว ? หรือรอให้มันขึ้น production แล้วให้ user จริงมาเทส ?
ตัวอย่างที่จะเกิดคอขวดที่ database
เขียน query ที่ทำให้มันรอนานมากๆ เช่น ดึงข้อมูลจากตาราง A แล้วเอาไป Join กับตาราง B แล้วก็ intersect กับตาราง C .... Z ไรงี้ อย่าทำ เสียทรัพยากรเครื่องอันมีค่าโดยใช่เหตุ
ออกแบบกฎที่อาจจะมีปัญหา เช่น A reference B และ B ก็ reference A ส่วนถ้าจะลบ A ต้องลบ B ด้วย และถ้าจะลบ B ก็ต้องลบ A ด้วยไรงี้
🔹 Limitation
ตัว 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 ให้ไหนๆก็ยาวละ
ตัว database ในโลกนี้ (ณ ตอนที่เขียนบทความนี้) มีทั้งหมด 2 ตระกูล หลักคือ
🔹 Relational database
ก็ที่เราใช้ๆกันมามีเส้นโยงยั้วเยี้ยนั่นแหละ ซึ่งของพวกนี้ต้องมีโครงสร้างก่อนถึงจะสามารถใช้งานได้ พูดง่ายๆต้องกำหนดก่อนว่าตารางนี้จะเก็บข้อมูลกี่ฟิล์อะไรบ้างและแต่ละฟิล์จะเป็นชนิดข้อมูลอะไร บลาๆ
🔹 Non-Relational database (NoSQL)
คือตรงข้ามกับตระกูลแรกเลย ซึ่งเจ้าตัวนี้จะเก็บข้อมูลได้ยืดหยุ่นและหลากหลายกว่า เพราะมันไม่ต้องประกาศโครงสร้างก่อนใช้งาน เช่น ตารางเดียวกันก็มีหลายโครงสร้างได้ และมันไม่ได้มีแค่แบบตารางเท่านั้น
ซึ่งเจ้าตระกูลนี้จะแตกย่อยออกเป็น 4 สายตามนี้เลย
ชื่อประเภท database | ตัวอย่าง product |
Graph | neo4J, OrientDB, Titan |
Key-Value store | Redis, Amazon DynamoDB |
Document database | MongoDB, Couchbase |
Column store | Apache HBase, Cassandra |
ตัวสีน้ำเงินสามารถกดไปลองเล่นได้นะ ส่วนรายละเอียดแต่ละตัวเป็นยังไง เดี๋ยวว่างๆจะมาเขียนให้ละกัน ลองติดตามดูได้จาก side menu ละกัน
🎯 บทสรุป
การที่โปรแกรมมันช้านั้นเพราะมันมี คอขวด ในระบบเกิดขึ้น ซึ่งมันจะแอบซ่อนอยู่ภายใน Pipeline ของระบบ โดยเจ้าคอขวดมันมากับผองเพื่อนของมัน ดังนั้นถ้าเราจะเอามันออกเราต้องไปไล่ดูว่ามีจุดไหนที่เป็นขอควดแล้วไปไล่เก็บผองเพื่อนที่ทำให้ระบบเราช้า เพียงเท่านี้ระบบเราก็จะกลับมาเร็วขึ้นแล้ว
เกร็ดความรู้ ของทุกอย่างที่เรียนที่รู้มา อย่ายึดมั่นถือมั่นเมากาวตะบี้ตะบันบังคับให้มันเป็นไปตามที่ได้เรียนมา เพราะของทุกอย่างมีดีมีเสียเสมอ ดังนั้นให้เลือกทำให้เหมาะสมกับหน้างานที่เกิดขึ้นด้วย
Last updated