พื้นฐานที่สำคัญที่สุดของฐานข้อมูล

🤔 ความรู้เบื้องต้นของฐานข้อมูลที่โปรแกรมเมอร์ 90% ไม่รู้

😢 ปัญหา

ฐานข้อมูลที่ใช้อยู่ดีๆก็แปลงร่างเป็นเต่า ทำอะไรกับมันก็ช้า จัด index เคลียข้อมูล ทำ replica ต่างๆนานๆก็ดีขึ้นมาหน่อย ซักพักก็กลับไปเป็นเต่าตัวเดิมเลย แก้ไงดี?

แนะนำให้อ่าน บทความนี้เป็นส่วนหนึ่งของบทความ 👦 Bottlenecks of Software หากเพื่อนๆสนใจอยากรู้สาเหตุว่าทำไมระบบถึงช้าลง หรือมันเกิดจากอะไรได้บ้าง ก็สามารถศึกษาได้จากบทความหลักได้เลยโดยการคลิกที่ชื่อบทความสีฟ้าๆนั่นแหละ

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

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

เรื่องตลกร้าย หลายคนที่ทำงานในวงการนี้แม้จะมีประสบการณ์ในการทำงานมาเป็น 10 ปี ออกแบบฐานข้อมูลมาได้เป็นอย่างดี รู้จัก database family ต่างๆ ก็อาจจะตกม้าตายเพราะแค่เรื่องพื้นฐานตัวนี้ก็ได้นะเชื่อไหม?

🤔 พื้นฐานที่สำคัญที่สุดคืออะไร ?

พื้นฐานที่สำคัญที่สุดในการทำงานกับฐานข้อมูลนั้นสามารถเขียนไว้ภายในรูปเดียวเท่านั้นแหละ ซึ่งถ้าเราเข้าใจเรื่องนี้เพียงแค่เรื่องเดียวแล้วล่ะก็ ของที่เหลือไม่ว่าจะเป็นการทำ indexing, backup, cleansing, design หรือแมวน้ำอะไรก็แล้วแต่ ก็จะเป็นเรื่องที่เอาไว้สนับสนุนเรื่องนี้ล้วนทั้งสิ้น ดังนั้นบทความนี้เราจะอธิบายและยกตัวอย่างให้เข้าใจว่า หัวใจหลักในการทำงานกับฐานข้อมูลคืออะไรเพียงเรื่องเดียวเท่านั้นครัช อย่ารอช้าดูรูปด้านล่างกันเบย

😫 นี่มันรูปบร้าไร ?

ผมขออธิบายเป็นแบบนี้ละกันว่า ไม่ว่าจะเป็นโปรแกรมบ้าบออะไรก็แล้วแต่ สุดท้ายมันก็จะมีแค่ ตัวแอพ ที่จะคอยเรียกใช้ ตัวฐานข้อมูล ใช่ป่ะ ดังนั้นมองรูปดีๆเราก็จะเห็นว่ามันแบ่งออกเป็น 2 ฝั่ง นั่นคือฝั่ง ตัวแอพ (App) กับฝั่ง ตัวฐานข้อมูล (DB) ยังไงล่ะ

ถัดมาเวลาที่เราอยากจะเอาข้อมูลมาโชว์ในแอพของเรา เช่น แสดงรายการสินค้าไรงี้ เราก็ต้องสั่งให้ ตัวแอพ ไปดึงข้อมูลจาก ตัวฐานข้อมูล ใช่ป่ะ? ซึ่งปรกติ ตัวแอพ กับ ตัวฐานข้อมูล มันจะเป็นคนละตัวกัน ดังนั้นพวกมันเลยต้องคุยกันผ่าน Network ยังไงล่ะ

ซึ่งตัวแอพมันก็จะบอกตัวฐานข้อมูลว่ามันอยากได้ข้อมูลอะไรบ้าง ดังนั้นตัวฐานข้อมูลก็จะต้องใช้ Computing power ไปไล่ดูดข้อมูลในตัวมันเอง ตามที่ตัวแอพขอมายังไงล่ะ

สีม่วงๆคือข้อมูลที่เก็บไว้ในฐานข้อมูล

หลังจากที่ตัวฐานข้อมูลได้ข้อมูลที่แอพขอมาละ มันก็จะทำการส่งข้อมูลพวกนั้นกลับไปให้ตัวแอพผ่านทาง Network ยังไงล่ะ ตามรูปด้านล่างเลย

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

ดังนั้นเราก็จะเห็นว่ามันมีข้อมูลทั้งหมด 3 แบบ

  1. สีม่วง (ขวาสุด) - คือข้อมูลดิบที่อยู่ในฐานข้อมูล

  2. สีน้ำเงิน (ตรงกลาง) - คือข้อมูลที่ส่งผ่าน network ระหว่างแอพกับฐานข้อมูล ซึ่งข้อมูลที่ส่งกลับจากฐานข้อมูลอาจจะเป็นตัวเดียวกับสีม่วง หรืออาจจะถูกดัดแปลงเป็นแบบอื่นก่อนส่งไปให้ก็ได้เหมือนกัน และข้อมูลที่รับส่งนี้ก็อาจจะเป็นการส่งจากแอพไปยังฐานข้อมูลได้เช่นกัน

  3. สีเขียว (ซ้ายสุด) - คือข้อมูลที่อยู่ในตัวแอพแล้ว ไม่มีความเกี่ยวข้องอะไรกับในฐานข้อมูลอีกต่อไป คิดง่ายๆว่ามันเป็นแค่ตัว copy เท่านั้นแหละ

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

😫 แล้วมันสำคัญยังไงแฟร๊ะ ?

ใจเย็นแล้วลองนั่งวิเคราะห์รูปให้ดีๆดิ แล้วลองตอบคำถามด้านล่างนี้ดูนะ

🔥 Database Query

🤔 จะเกิดอะไรขึ้นถ้าเราสั่งให้ตัวฐานข้อมูลไปดึงข้อมูลแบบที่มันซับซ้อนมากๆและกินเวลานานๆ 😱 ตัวดูดข้อมูลของ database ก็จะทำงานหนัก และถ้ามีคำขอแบบนี้เข้ามามากๆมันก็จะทำให้มันทำงานต่อไม่ได้ เพราะงานเก่ามันยังทำไม่เสร็จ งานใหม่เลยต้องรอคิวกันไปเรื่อยๆ ทำให้เกิดสิ่งที่เรียกว่า Database Overloaded นั่นเอง

🔥 Network Latency

🤔 จะเกิดอะไรขึ้นถ้าตัวแอพขอข้อมูลจากตัวฐานข้อมูลเป็นแสนๆรายการเลย? 😱 ข้อมูลเป็นแสนๆรายการก็จะต้องถูกส่งผ่าน network กลับมายังไงล่ะ ซึ่งถ้าข้อมูลยิ่งเยอะเท่าไหร่ ตัวแอพก็จะต้องรอนานมากขึ้นเท่านั้น เพราะมันต้องรอโหลดข้อมูลพวกนั้นจาก network อ่ะดิ

🔥 App Resource

🤔 จะเกิดอะไรขึ้นถ้าเราเอาข้อมูลปริมาณเยอะๆมาทำงานที่ฝั่งแอพ? 😱 ตัวแอพมีทรัพยากรที่ค่อนข้างจำกัด เช่น Computing Power หรือ RAM ที่น้อยอยู่แล้ว ถ้าต้องทำงานกับข้อมูลที่เยอะๆก็อาจจะกระตุก พัง และล่วงลับดับขันธ์ไปในที่สุดนั่นเอง และ การจัดการกับหน้าตา UX ก็จะแย่ด้วยอ่ะดิ

😭 แล้วแก้ไงอ่ะ ?

หลักในการแก้ปัญหาเรื่องนี้นั้นสุดแสนจะง่าย แค่ทำ 3 เรื่องด้านล่างก็พอละ

  1. ลดงานตัวดูด - อย่าสั่งอะไรที่มันซับซ้อนมากๆไปให้มันทำ และตัวฐานข้อมูลจริงๆมันก็มีงานที่มันถนัดและไม่ถนัดด้วยนะ ดังนั้นงานที่มันไม่ถนัดก็ส่งไปให้แอพจัดการต่อก็พอ

  2. ลดข้อมูลที่ส่งผ่าน network - ตัวนี้พูดยากเพราะรายละเอียดมันเยอะ แต่หลักงง่ายๆคือส่งไปแค่เท่าที่จำเป็นต้องใช้ก็พอ และอาจจะใช้เทคนิคต่างๆเข้ามาช่วยก็ได้ เช่น การทำ paging ก็เป็นตัวเลือกที่ดีเหมือนกัน

  3. ลดงานฝั่งแอพ - ตัวนี้ก็พูดยากอีกตัว แต่หลักง่ายๆคืออย่าให้มันต้องทำงานกับข้อมูลเยอะๆ เช่น ถ้าถามว่าเว็บเรามีหนังทั้งหมดกี่เรื่อง แทนที่จะส่งรายการหนังทั้งหมดมาที่ตัวแอพเพื่อให้มันไปนับเอง เราก็แค่ส่งจำนวนหนังในฐานข้อมูลไปให้แอพซะก็สิ้นเรื่อง

จากที่ว่ามาทั้ง 3 ข้อ เราก็จะได้ข้อสรุปว่า เราควรจะให้การทำงานมันออกมาตามภาพด้านล่างนี้ต่างหาก

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

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

เกร็ดความรู้ งานที่ฐานข้อมูลถนัดคือคณิตศาสตร์เบื้องต้นยังไงล่ะ เช่น การนับจำนวน การหาค่าสูงสุด หาค่าต่ำสุด มากกว่า น้อยกว่า ค่าเฉลี่ย ผลรวม บลาๆ ไรพวกนี้ ดังนั้นงานอะไรที่ใช้เรื่องพื้นฐานพวกนี้ทำได้ ก็อาจจะให้ทำใน database เลยแล้วค่อยส่งแค่ผลลัพท์กลับมาก็ได้ มันจะได้ไม่เปลือง Database computing power มากนัก ข้อมูลที่ส่งผ่าน network ก็น้อย ตัว client ก็ทำงานสบาย วินๆทั้งสองฝ่าย

😢 ขอตัวอย่างหน่อยได้ไหม ?

สมมุติว่าในฐานข้อมูลเก็บ ข้อมูลสินค้า ข้อมูลลูกค้า และ บันทึกการสั่งสื้อสินค้าไว้ละกัน ซึ่งพอเขียน diagram ก็จะออกมาแบบนี้ (การสั่งซื้อแต่ละครั้งสั่งสินค้าได้ 1 ชนิดเท่านั้น) ตามรูปด้านล่างเบย

🤔 ถ้าอยากรู้ว่าเรามีสินค้าทั้งหมดกี่ชิ้นจะทำไง ?

👎 สิ่งที่ไม่ควรทำ: ไปดึงข้อมูลสินค้าทั้งหมดมาจากฐานข้อมูล แล้วให้ client เป็นคนนับเองว่ามีกี่ชิ้น ตามโค้ดด้านล่าง

Query command
SELECT * FROM สินค้า;
โค้ดในฝั่ง client ตอนได้ผลลัพท์กลับมา
var totalProducts = allProducts.Count();

ถ้าใครคิดแบบนี้ให้กลับไปอ่านตั้งแต่ต้นใหม่นะ เพราะมันโหลดข้อมูลทุกอย่างเลย ฐานข้อมูลต้องไปดึงข้อมูลยั้วเยี๊ย แล้วต้องไล่ส่งข้อมูลทั้งหมดผ่าน network อีก แล้วเอาข้อมูลปริมาณมากๆไปยัดให้ client ด้วย ขอแสดงความยินดีด้วย ที่ผมพิมพ์มาทั้งหมดไม่มีความหมายเลย T-T

👍 สิ่งที่ควรทำ: ตัวฐานข้อมูลมันเก่งเรื่องคณิตศาสตร์พื้นฐานอยู่แล้ว ดังนั้นก็ให้มันนับ records ทั้งหมดมาซิ แล้วส่งแค่ตัวเลขมาให้ client ก็พอ เพราะมันแทบจะไม่โหลดใครเลย

Query command
SELECT COUNT(*)
FROM สินค้า
โค้ดในฝั่ง client ตอนได้ผลลัพท์กลับมา
var totalProducts = allProducts;

🤔 ถ้าอยากรู้ว่าสินค้าชิ้นนี้ถูกสั่งซื้อไปวันไหนบ้างจะทำไง ?

👎 สิ่งที่ไม่ควรทำ: ไปดึงข้อมูลคำสั่งซื้อทั้งหมดกลับมา แล้วค่อยหาคำสั่งซื้อของสินค้าชิ้นนั้น

Query command
SELECT * FROM คำสั่งซื้อ;
โค้ดในฝั่ง client ตอนได้ผลลัพท์กลับมา
var orders = allOrders.Where(it => it.รหัสสินค้า == "P01");

รหัส P01 คือรหัสสินค้าที่เราต้องการหา

มันไม่ดีเพราะเหตุผลเดียวกันกับข้อที่แล้วเลย ซ้ำร้ายคือมันทำให้ client ต้องเสียเวลามานั่งประมวลผล filter ต่ออีกด้วย

👍 สิ่งที่ควรทำ: ให้ตัวฐานข้อมูลมันเก่งเรื่องคณิตศาสตร์พื้นฐานอยู่แล้ว ดังนั้นก็ให้มันนับ records ทั้งหมดมาซิ แล้วส่งแค่ตัวเลขมาก็พอ แทบจะไม่โหลดอะไรใครเลย

Query command
SELECT * FROM คำสั่งซื้อ
WHERE รหัสสินค้า = 'P01';
โค้ดในฝั่ง client ตอนได้ผลลัพท์กลับมา
var orders = allOrdersFromProductP01;

เพิ่มเติม ในกรณีที่เราต้องการใช้แค่ วันที่สั่งซื้อเท่านั้น เราก็สามารถทำ projection แค่วันที่อย่างเดียวก็ได้ มันจะได้ไม่มีข้อมูลที่ไม่ได้ใช้ส่งผ่าน network ไปยังไงล่ะ

🤔 ถ้าอยากรู้ข้อมูลลูกค้าทั้งหมดในระบบละ? สมมุติว่ามีเป็นล้านๆคนเลยนะ

👎 สิ่งที่ไม่ควรทำ: ไปดึงข้อมูลลูกค้าทั้งระบบมา -*- ไม่ขอเขียนตัวอย่างนะ

👍 สิ่งที่ควรทำ: ดึงข้อมูลมาเป็นส่วนๆ หรือที่เราเรียกว่า Paging นั่นเอง และในการดึงข้อมูลควรจะต้องดูด้วยว่าเราจะเอาข้อมูลอะไรของลูกค้าไปใช้บ้าง เช่น ถ้าในหน้านั้นมันเอาไปโชว์แค่ ชื่อ กับ นามสกุล เท่านั้น เราก็ไม่ต้องส่งข้อมูลจากฐานข้อมูลทั้ง record ก็ได้ โดยเราสามารถทำ Projection ได้นั่นเอง

ข้อควรระวัง ในการทำงานบางอย่างที่ต้องอาศัยการทำงานจากฝั่ง service และฝั่ง client ให้เราดูด้วยว่ามันเป็นแบบไหน เช่นการทำ paging ถ้าเราทำที่ฝั่ง client เพียงอย่างเดียว แต่เซิฟเวอร์ก็ยังส่งข้อมูลมาเป็นล้านๆตัว ก็ไม่มีประโยชน์อยู่ดี เพราะงานฝั่ง client ส่วนใหญ่จะเป็นเรื่อง UX เท่านั้น

🤔 แนวคิดนี้ใช้กับอะไรได้บ้าง ?

ใช้ได้กับฐานข้อมูลทุกแบบเลย มีฐานข้อมูลตัวไหนไม่ทำงานแบบนี้บ้าง? (อ่อจะบอกว่า embedded database ซินะ คิดเหรอว่ามันจะไม่มี network cost? และเรื่องอื่นๆที่ว่ามา)

ข้อควรระวัง ในการทำเรื่องนี้ก็ขึ้นอยู่กับเทพเจ้าแต่ละองค์ เช่น องค์ SQL กับ องค์ MySQL หรือองค์ MongoDB, Redis, Neo4J หรือจะองค์ไหนก็แล้วแต่ มันก็จะมีวิธีบูชาเทพไม่เหมือนกัน ... ผมไม่ได้เมากาวอะไรอยู่นะ แต่จะสื่อว่า ตัว database แต่ละตัวมันก็จะมีวิธีการ Optimize หรือการจัดการไม่เหมือนกัน ดังนั้นอย่าตะบี้ตะบันใช้วิธีเดียวกับฐานข้อมูลทุกประเภทนะ ไปไล่อ่าน Best practices ของแต่ฐานข้อมูลที่เราจะใช้งานมันด้วยซะ ... เตือนแล้วนะ

🎯 บทสรุป

จากที่ร่ายยาวมาก็จะเห็นแล้วว่า หัวใจ ในการทำงานกับฐานข้อมูลจริงๆก็มีแค่เรื่องนี้เรื่องเดียวแหละ เพราะต่อให้เราไปเตรียมทำ perfect design, indexing, cleansing, read/write replica บ้าบออะไรก็ตาม สุดท้าย ถ้าจุดเริ่มต้นมันกาก มันก็จะเหมือนกับแต่งรถไว้สุดเทพ มีเครื่องสุดแจ่ม มีน้ำมันสำรองครบ แต่ดันใส่เบรคมือทิ้งไว้แล้วขับลากเกียร์ 1 ตลอดทาง มันก็เท่านั้นแหละ จะไปนั่ง optimize มันทำไม?

ดังนั้นสุดท้ายก็หาทางปรับพื้นฐานในการทำงานกับ database ให้มันออกมาเป็นภาพนี้ให้ได้มากที่สุดเอาละกันนะจุ๊ฟๆ