Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
242 lines (143 sloc) 26.8 KB
description
อยู่ๆแอพที่ทำก็ช้าเป็นเต่าเฉยเลย เกิดจากอะไรและแก้ไงดี ?

👦 Bottlenecks of Software

😢 ปัญหา

เชื่อว่าหลายๆคนที่ได้เขียนโปรแกรมที่ใช้จริงมาซักระดับนึงน่าจะเคยมีประสบการณ์ว่า แอพที่เคยทำงานได้รวดเร็วดุจสายฟ้า แต่อยู่มาก็ดันช้าเป็นเต่าซะงั้น พยายามเปลี่ยนโค้ดจุดนั้นนู้นนี่ก็แล้ว จัดการ database ก็แล้ว (ทำทุกๆอย่างให้เธอแล้ว) แต่ก็ยังเป็นเต่าอยู่ดี หรือบางทีก็อาการดีขึ้น แต่ซักพักก็วนกลายร่างเป็นเต่าเช่นเคย เฮ่อ...เหนื่อยใจ ตอนนี้เลยได้แต่พึ่งเจ้าพ่อนั่งจุดธูปเช้าเย็นหวังว่ามันจะไม่ล่มก็พอใจละ

จากปัญหาที่ว่ามามันเกิดจากอะไรได้บ้าง แล้วเราจะแก้ยังไงดี ?

😄 วิธีแก้ปัญหา

ออกตัวไว้ก่อนเลยว่า ไม่สามารถบอกวิธีการแก้ปัญหาแบบรวบรัดได้ เพราะการแก้ปัญหาในเรื่องนี้ มันต้องไปนั่งวิเคราะห์เป็นเคสๆเลย เพราะ Software & Hardware Architecture ของแต่ละเคสมันไม่เหมือนกันยังไงล่ะ เลยไม่มียาเทพที่กินเม็ดเดียวแล้วหาย ดังนั้นในบทความนี้จะชี้แนะว่า จะวิเคราะห์ปัญหานี้ทำยัง และ มันเกิดจากอะไรได้บ้าง ยังไงละ

🤔 ทำไมแอพถึงช้า ?

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

  1. ดึงรายชื่อนักเรียนจาก database
  2. คำนวณเกรดของนักเรียนแต่ละคน
  3. แสดงผลออกทาง View

สมมุติว่าแอพของเรามันช้า สิ่งที่เราต้องไปไล่ดูคือ

หาว่ามันช้าจากการทำงานเรื่องอะไรบ้าง แล้วค่อยไปไล่ดู Pipeline ของมันต่อ

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

คอขวด หรือ Bottleneck ที่ซ่อนอยู่ภายใน Pipeline

{% hint style="info" %} การจัดการคอขวด
เป็นหลักพื้นฐานในการทำ Scalable เลย ซึ่งเป็นจุดชี้เป็นชี้ตายว่าตัวโปรแกรมของเราจะรับโหลดได้สูงสุดเท่าไหร่ เช่นจะรับ concurrent user เป็นแสนๆต่อวินาทีได้หรือเปล่า บลาๆ {% endhint %}

🤔 ทำไมต้องต้องไล่แก้คอขวดเพียงอย่างเดียว ?

หลายคนอาจจะสงสัยว่า ทำไมไม่ไปเขียนโค้ดให้มันทำ performance ดีๆ ไม่ก็จัดการเคลีย database จัด index, cleaning บลาๆ ไปเลยล่ะ จะไปไล่หาคอขวดมันทำไม?

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

จากภาพจะเห็นว่า ต่อให้เราทำ performance ที่จุดต่างๆที่เราคิดว่าควรทำหมดแล้ว แต่เราไม่ได้แก้ คอขวด สุดท้ายโดยรวมความเร็วที่ทำได้ทั้งหมดก็เท่ากับที่คอขวดทำได้อยู่ดีนั่นแหละ

🤔 อะไรบ้างที่ทำให้เกิดคอขวด ?

เยอะม๊วกกกกกกๆๆๆ แต่ขอลิสต์แค่ตัวหลักๆก่อนนะ ดูได้จากรายการด้านล่างเลย ซึ่งแค่เห็นก็ปวดตับละ

ชื่อ ความหมาย
OS

ตัว Operating System แต่ละตัวก็จะเก่งในงานไม่เหมือนกัน

การจัดการแต่ละเรื่องก็ไม่เท่ากัน

| Software | ตัวโปรแกรมต่างๆที่ติดตั้งไว้ในเซิฟเวอร์ก็มีผล เช่นลองติดตั้ง Antivirus เยอะๆเข้าไปดูจิ | | :--- | :--- |
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 แล้วผมเขียนโค้ดออกมาแบบนี้

var sum = 0;
for (int i = 1; i <= N; ++i)
{
    sum += i;
}

แล้วลองเปรียบเทียบกับผมเขียนโค้ดแบบนี้

var sum = N * (1 + N) / 2;

แม้ว่าโค้ดทั้ง 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 ของระบบ โดยเจ้าคอขวดมันมากับผองเพื่อนของมัน ดังนั้นถ้าเราจะเอามันออกเราต้องไปไล่ดูว่ามีจุดไหนที่เป็นขอควดแล้วไปไล่เก็บผองเพื่อนที่ทำให้ระบบเราช้า เพียงเท่านี้ระบบเราก็จะกลับมาเร็วขึ้นแล้ว

{% hint style="info" %} เกร็ดความรู้
ของทุกอย่างที่เรียนที่รู้มา อย่ายึดมั่นถือมั่นเมากาวตะบี้ตะบันบังคับให้มันเป็นไปตามที่ได้เรียนมา เพราะของทุกอย่างมีดีมีเสียเสมอ ดังนั้นให้เลือกทำให้เหมาะสมกับหน้างานที่เกิดขึ้นด้วย {% endhint %}

You can’t perform that action at this time.