ทำความเข้าใจ MongoDB index อย่างง่ายด้วยรูปภาพ

ทำความเข้าใจ MongoDB index อย่างง่ายด้วยรูปภาพ
16/06/20   |   15.9k

Introduction

ปัญหาอย่างหนึ่งของ developer ที่มักจะเจอก็คือ
ไม่เข้าใจว่า index นั้นทำงานอย่างไร, ทำอะไรได้บ้าง, แต่ละแบบแตกต่างกันอย่างไร
ในบทความนี้จะเป็นการเปรียบเทียบการทำงานของ index โดยอาศัยภาพประกอบเพื่อให้เข้าใจมากยิ่งขึ้นนั่นเอง

Cautions

MongoDB นั้นไม่ได้ทำงานเหมือนเรื่องที่จะเล่า
แต่เพื่อให้เข้าใจง่ายที่สุด เลยต้องตัดความซับซ้อนออกไป
ผู้อ่านควรศึกษาและทำความเข้าใจเรื่องของ index ต่อหลังจากอ่านจบแล้วด้วยครับ

Adam กับ Eve ที่เป็นตัวละครหลัก


เราจะจำลองสถานการณ์ของ Adam กับ Eve เล่นเกมตอบคำถามกัน
โดย Adam จะถามว่า X อยู่ในกล่องตำแหน่งที่เท่าไหร่ (1, 2, 3, ..., n)

โดยกฏของเกมนี้คือ

  1. Adam จะถามคำถาม 2 รอบ
  2. Eve จะต้องตอบคำถามครั้งที่ 2 ให้ได้ โดยที่ใช้เวลาไม่เกิน 1 วินาที
  3. ในการมองแต่ละครั้ง Eve จะใช้เวลามากกว่า 1 วินาทีเสมอ
  4. Eve ต้องเริ่มมองจากซ้ายสุดก่อนเสมอ
  5. ถ้า Eve ใช้เวลาในการตอบคำถามที่ 2 เกิน 1 วินาที Eve จะเป็นฝ่ายแพ้


note:
ถ้าสมมติว่า adam ถามเป็น query
การที่ eve มองก็เท่ากับ scanning
โดยที่แต่ละวัตถุคือ document และตำแหน่งของวัตถุก็คือตำแหน่งของ document
และวัตถุ X ที่เป็นคำตอบคือ item ที่อยู่ในกล่อง (document)

1. Without Indexes


Adam ถาม Eve วงกลมอยู่ตำแหน่งที่เท่าไหร่

Adam ถามครั้งแรกว่า "วงกลมอยู่ลำดับที่เท่าไหร่?"
Eve จึงเริ่มมองหาจากซ้ายไปขวา ซึ่งก็คือสี่เหลี่ยม, สามเหลี่ยม, หัวใจ, ดาว, วงกลม
Eve จึงตอบออกไปว่า วงกลมอยู่อันดับที่ 5

db.collection.find( { "shape": "circle" } )


Adam จึงถามอีกครั้งว่า "วงกลมอยู่ลำดับที่เท่าไหร่?"
ซึ่งในรอบนี้ Eve ต้องตอบคำถามโดยที่ใช้เวลาไม่เกิน 1 วินาที
Eve ก็เริ่มมองจากซ้ายไปขวาใหม่ ซึ่งก็คือสี่เหลี่ยม, สามเหลี่ยม, หัวใจ, ดาว, วงกลม
หลังจากใช้เวลาอยู่พอสมควร Eve ถึงจะตอบได้ว่าอยู่อันดับที่ 5

รอบนี้ Eve จึงได้แพ้ไป เพราะไม่สามารถที่จะตอบได้เร็วกว่า 1 วินาทีนั่นเอง

reminder: การมองแต่ละครั้งใช้เวลามากกว่า 1 วินาที

2. With Indexes

Adam ถามคำถามแรก ซึ่งเป็นคำถามเดิมว่า "วงกลมอยู่ลำดับที่เท่าไหร่?"
Eve จึงเริ่มมองหาจากซ้ายไปขวา ซึ่งก็คือสี่เหลี่ยม, สามเหลี่ยม, หัวใจ, ดาว, วงกลม
Eve จึงตอบออกไปว่า วงกลมอยู่อันดับที่ 5...

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

db.collection.createIndex( { shape: 1 } )

Eve จึงเริ่มคิดเริ่มคิดที่จะเอาชนะด้วยการจำซะเลยว่าอะไร อยู่ตำแหน่งไหน

Eve จำว่าวงกลมอยู่ตำแหน่งที่ 5

Adam ถามด้วยคำถามที่สองว่า "วงกลม อยู่ในกล่องที่เท่าไหร่?"
ซึ่งก็เป็นไปตามคาด! Eve ซึ่งมีคำตอบอยู่ในใจที่ท่องจำไว้แล้ว จึงสามารถตอบออกไปได้ทันทีว่า 5!

รอบนี้ชัยชนะตกเป็นของ Eve เพราะว่าสามารถตอบได้ด้วยเวลาน้อยอันน้อยนิด
ซึ่งการที่ Eve จำเอาไว้ในใจก็คือการทำ Index นั่นเอง

Adam ชักไม่แน่ใจ ที่อีฟตอบมานั่นมั่วหรือปล่าว ทำไมเร็วจัง?
Adam จึงลองถามใหม่แบบไม่สนแพ้ชนะว่า "หัวใจ อยู่ในกล่องอันดับที่เท่าไหร่?

Eve จำได้แค่วงกลม เลยต้องมองหาใหม่

Eve นึกดูแล้วจำไม่ได้ว่าหัวใจอยู่ในกล่องลำดับที่เท่าไหร่ จึงต้องเริ่มมองหาใหม่อีกครั้งถึงจะตอบได้

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

Adam สังเกตุเห็นว่ารอบนี้ Eve ใช้เวลานานกว่า 1 วินาที
Adam จึงจับทางได้แล้วว่า Eve ใช้วิธีจำเอาเลยตอบได้ไว จึงได้คิดจะหาทางเอาชนะ Eve

รูปจำลอง index ที่เกิดขึ้น

ถ้ารอบหน้าเขาถามหาหัวใจหรือวงกลมอีก Eve ก็จะสามารถตอบได้ไวแน่นอนเพราะ Eve จำได้แล้ว!
เขาต้องเลี่ยงที่จะถามหาวงกลมหรือหัวใจ...


3. Covered query

Eve ตอบไม่รู้ เพราะเท่าที่จำได้ไม่มีสามเหลี่ยม

Adam จึงเริ่มถามคำถามที่สองทันทีว่า "สี่เหลี่ยม อยู่ในกล่องลำดับที่เท่าไหร่?"
ซึ่งรอบนี้ Eve ต้องตอบให้ได้ภายในเวลาน้อยกว่า 1 วินาทีเพื่อที่จะชนะ
Eve ที่จำได้แค่วงกลมกับหัวใจ จึงตอบออกไปทันทีว่า ไม่รู้!

เทคนิคที่ Eve ใช้ในการตอบให้เร็วขึ้นคือ Covered query นั่นเอง
เป็นการโฟกัสเฉพาะสิ่งที่อยู่ในความจำเท่านั้น ไม่สนใจอย่างอื่นนอกจากนี้

Eve บอกว่ากติกาคือตอบเร็ว ไม่ได้ห้ามตอบว่าไม่รู้สักหน่อย
Adam จึงยอมรับความพ่ายแพ้ในรอบนี้ไปแบบไม่เต็มใจนัก

note:
ในทางปฎิบัติคนที่จะใช้งาน covered query คือคนสั่ง query (Adam) ไม่ใช่ทางคนรับ (Eve)
ตัวอย่างนี้ทำขึ้นเพื่อให้เข้าใจ concept ของ covered query
ที่ search space จะเป็นสิ่งที่ทำ index ไว้เท่านั้น
db.collection.find( { "shape": "triangle" }, { "shape": 1, _id: 0 } )

4. Compound Indexes

ในโจทย์ที่ผ่านมาค่อนข้างจะง่าย เนื่องจาก document มีมิติเดียว (รูปทรง)
Adam จึงเพิ่มความท้าทายของโจทย์ขึ้นไปอีกขั้น โดยการเพิ่มสี
และรอบนี้ Eve ต้องห้ามตอบว่าไม่รู้

รูปจำลองของใหม่กับเก่า


Adam จึงเริ่มต้นด้วยคำถามแรก "หัวใจสีเหลือง อยู่ลำดับที่เท่าไหร่?"
Eve ก็เริ่มมองหาหัวใจสีเหลืองจนพบว่าอยู่ตำแหน่งที่ 4 และ 7

Adam จึงถามคำถามที่สอง ซึ่งเช่นเคย Eve ต้องตอบโดยใช้เวลาน้อยกว่า 1 วินาที
แต่รอบนี้ Eve ใช้เทคนิคเดิมคือจำว่าหัวใจอยู่ตำแหน่งไหนบ้าง ซึ่ง Eve จำได้ว่า 4 กับ 7
แต่ Eve ดันจำไม่ได้ว่า 4 กับ 7 อันไหนเป็นสีเหลืองบ้าง
ทำให้ Eve ต้องไปมองลำดับที่ 4 กับ 7 ใหม่ ซึ่งใช้เวลาเกิน 1 วินาทีในการตอบ

ทำให้รอบนี้ Eve แพ้ไปนั่นเอง

Eve จำด้วยว่าสีเหลืองอยู่ตำแหน่งอะไรบ้าง

Eve รู้ตัวดีว่าถ้าจำแค่รูปทรงคงตอบไม่ทัน 1 วินาทีแน่นอน
Eve ต้องปรับเปลี่ยนเทคนิคใหม่เป็นจำรูปทรงและสีด้วย!

db.collection2.createIndex( { shape: 1, color: 1 })

Adam เริ่มถามคำถามแรกกับ Eve ว่า "วงกลมสีดำอยู่อันดับที่เท่าไหร่?"
Eve จึงเริ่มมองหาพร้อมกับจำไปด้วยว่า วงกลมสีดำอยู่อันดับที่เท่าไหร่บ้าง

Adam ถามคำถามเดิมซ้ำ เพราะชนะด้วยวิธีนี้มาแล้ว
แต่รอบนี้ Eve จำได้แล้วว่า วงกลมสีดำอยู่ตำแหน่งไหน จึงสามารถตอบได้ทันที!

ท้ายที่สุด Eve ก็ชนะไป

Common mistakes

การทำ index intersection ไม่เหมือนกับการทำ compound index

ทำให้ความเร็วที่ได้ไม่เท่ากัน เช่น

db.collection2.createIndex( { shape: 1 })
db.collection2.createIndex( { color: 1 })

ตัวอย่างข้างต้นคือ index ของ shape กับ color แยกกัน
แต่เวลา query ใช้ 2 fields นี้ร่วมกัน

db.collection2.find( { "shape": "circle", "color": "black" } )


การทำแบบนี้จะทำให้ Eve มีความจำอยู่ 2 ชุด และต้องนำความจำ 2 ชุดนี้มาหาจุดที่ร่วมกัน (intersect)
พูดให้ง่ายที่สุดคือมีทำงาน 2 รอบ รอบแรกหาสีดำอยู่ตำแหน่งไหนบ้าง รอบสองหาว่าวงกลมอยู่ตำแหน่งไหนบ้าง

Compound index จะถูกใช้ ต้อง query เฉพาะ field ที่ทำไว้เท่านั้น

หากมีการร้องขอ field ที่ไม่ได้อยู่ใน compound index, การ query จะเกิดขึ้นโดยไม่ใช้ index

db.collection2.find( { "shape": "circle", "color": "black", "letter": "A" } )

ใช้ Index type ตามเหมาะสม

index type มีอยู่หลากหลายประเภท เช่น 1, -1 เราควรเลือกใช้ตามความเหมาะสมของ use-case
เช่น 1 จะทำการ sorting index แบบ ascending และ -1 เป็นแบบ descending

db.collection2.createIndex( { shape: 1 })


db.collection2.createIndex( { shape: -1 })


Final word

สุดท้ายนี้ผู้เขียนยังคงเน้นย้ำอยู่เรื่องเดิมว่า MongoDB ไม่ได้ทำทุกอย่างเหมือนที่เล่ามา
อยากให้ผู้อ่านทุกคนทำความเข้าใจเพิ่มเติมด้วยครับ
หากมีข้อสงสัย, ข้อผิดพลาด สามารถบอกได้ในทันทีครับ

ขอบคุณครับ








ติดตามข่าวสารและเรื่องราวดีๆ ทาง Email