พื้นฐานที่ควรรู้ก่อนเรียน Big Data
Big Data ไม่ได้สร้างกฎของคอมพิวเตอร์ขึ้นมาใหม่ แต่ขยายปัญหาเดิมจนต้นทุนที่เคยมองข้ามกลายเป็นคอขวด บทนี้จึงไม่ได้ทบทวนทุกวิชาตั้งแต่ต้น แต่เลือกพื้นฐานที่ต้องหยิบกลับมาใช้เมื่อต้องจัดการข้อมูลขนาดใหญ่จริง ๆ
ถ้าพื้นฐานบางเรื่องยังไม่แน่น ไม่จำเป็นต้องหยุดอ่านทั้งหมด ให้ใช้บทนี้เป็นแผนที่ เมื่อบทต่อไปพูดถึง cache, hash, partition, Join, lock หรือ network cost แล้วรู้สึกว่าภาพไม่ชัด ค่อยย้อนกลับมายังส่วนที่เกี่ยวข้อง เป้าหมายคือเชื่อมความรู้ ไม่ใช่ทดสอบว่าใครจำรายวิชาเดิมได้ครบกว่าใคร
1สถาปัตยกรรมคอมพิวเตอร์: ทำไม Big Data ถึงแคร์เรื่อง Cache
เมื่อเราพูดว่า CPU เร็วขึ้น มักนึกถึงจำนวนคำสั่งที่ประมวลผลได้ต่อวินาที แต่ในงานข้อมูล CPU จำนวนมากไม่ได้ติดอยู่ที่การบวกหรือเปรียบเทียบ มันกำลังรอข้อมูลเดินทางมาถึงจาก cache, RAM, SSD, ดิสก์ หรือเครื่องอื่น
| ระดับ | จุดเด่น | สิ่งที่ต้องระวัง |
|---|---|---|
| Register / CPU Cache | ตอบสนองเร็วที่สุด เหมาะกับ working set ขนาดเล็กและการเข้าถึงซ้ำ | ความจุน้อย หากเข้าถึงข้อมูลกระจัดกระจาย cache line ที่โหลดมาอาจถูกใช้เพียงส่วนน้อย |
| RAM | เก็บโครงสร้างข้อมูลและผลระหว่างทางได้มากกว่า cache | Memory bandwidth มีเพดาน หลาย core อาจแย่งช่องทางเดียวกันจนเพิ่ม core แล้วไม่เร็วขึ้น |
| SSD / HDD | เก็บข้อมูลถาวรและมีความจุสูง | การอ่านแบบสุ่มมี overhead สูงกว่าการอ่านต่อเนื่อง โดยเฉพาะ HDD |
| Remote Memory / Storage | ช่วยแบ่งข้อมูลและขยายความจุออกไปหลายเครื่อง | ต้องจ่ายค่า serialization, network latency, bandwidth และความล้มเหลวระหว่างทาง |
Latency กับ Bandwidth เป็นคนละเรื่อง
Latency คือเวลารอก่อนข้อมูลเริ่มมาถึง ส่วน Bandwidth คือปริมาณข้อมูลที่ส่งได้ต่อหน่วยเวลา การส่งข้อมูลชิ้นเล็กจำนวนมากมักแพ้ latency ซ้ำ ๆ ขณะที่การอ่านข้อมูลก้อนใหญ่มักติด bandwidth
Spatial และ Temporal Locality
Spatial locality หมายถึงเมื่ออ่านข้อมูลตำแหน่งหนึ่ง เรามีแนวโน้มใช้ข้อมูลข้างเคียงต่อ การเก็บข้อมูลต่อเนื่องจึงใช้ cache line และการอ่าน block ได้คุ้มกว่า ส่วน temporal locality หมายถึงข้อมูลที่เพิ่งใช้มีโอกาสถูกใช้อีก การ cache จึงช่วยได้เมื่อการใช้ซ้ำเกิดก่อนข้อมูลถูกแทนที่
Array มักมี spatial locality ดีกว่าโครงสร้างที่แต่ละ node กระจายอยู่ทั่ว heap แต่ไม่ได้หมายความว่า array ชนะทุกงาน หากต้องแทรกกลางข้อมูลบ่อยหรือไม่รู้ขนาดล่วงหน้า โครงสร้างอื่นอาจเหมาะกว่า ประเด็นคือเลือก layout จาก access pattern ไม่ใช่เลือกจาก Big-O เพียงช่องเดียวในตาราง
Row-oriented กับ Column-oriented Layout
ถ้างานอ่านข้อมูลลูกค้าหนึ่งคนครบทุกคอลัมน์ การวางข้อมูลเป็นแถวช่วยให้ค่าที่ต้องใช้มาอยู่ใกล้กัน แต่ถ้างานหาค่าเฉลี่ยรายได้ของลูกค้าทุกคน การอ่านเฉพาะคอลัมน์รายได้ช่วยลดข้อมูลที่ต้องดึงอย่างมาก Columnar format เช่น Parquet จึงเหมาะกับ analytical workload และเปิดทางให้ compression กับ vectorized execution ทำงานได้ดี
Pipeline, Superscalar, และ Out-of-Order Execution
Pipeline แบ่งการทำคำสั่งเป็นช่วง เช่น fetch, decode และ execute เพื่อให้คำสั่งหลายตัวอยู่คนละช่วงพร้อมกัน Superscalar ทำให้ CPU ออกคำสั่งได้มากกว่าหนึ่งคำสั่งต่อรอบ และ Out-of-order execution อนุญาตให้คำสั่งที่พร้อมข้ามคำสั่งซึ่งกำลังรอข้อมูล ตราบใดที่ผลลัพธ์สุดท้ายยังเทียบเท่าลำดับเดิม
ความสามารถเหล่านี้ช่วยซ่อน latency ได้บางส่วน แต่มีขอบเขต หากคำสั่งทุกตัวรอ memory หรือมี dependency ต่อกันยาว CPU ก็ไม่มีงานอิสระพอให้สลับขึ้นมาทำ การออกแบบ data layout และ algorithm จึงยังสำคัญ แม้ฮาร์ดแวร์จะฉลาดขึ้นมาก
Vectorization และ Branch Prediction
SIMD instruction ประมวลผลค่าหลายตัวด้วยคำสั่งเดียว เหมาะกับข้อมูลชนิดเดียวกันที่วางต่อเนื่อง เช่น บวก vector หรือกรองคอลัมน์ แต่ถ้าแต่ละ record แยกไปคนละ branch CPU อาจทำนายทางผิดและต้องทิ้งงานใน pipeline ระบบ analytical สมัยใหม่จึงพยายามประมวลผลข้อมูลเป็น batch และใช้รูปแบบที่เอื้อต่อ vectorization
Immutability, Lazy Evaluation และ Cache เป็นคนละแนวคิด
RDD ของ Spark เป็น immutable และเก็บ lineage จึงสร้าง partition ที่หายขึ้นใหม่ได้ Transformation เป็น lazy ทำให้ Spark รวมงานเป็น stage ก่อนรัน ส่วน cache() เป็นการตัดสินใจเก็บผลไว้ใช้ซ้ำ ผู้เรียนควรแยกสามชั้นนี้ออกจากกัน เพื่ออธิบายได้ว่าความเร็วหรือการกู้คืนเกิดจากกลไกใด
แม้เป็นคนละแนวคิด แต่เชื่อมต่อกันเป็นสายเหตุผลเดียวได้
จุดเชื่อมสำคัญอยู่ที่ dependency หากงานหนึ่งแก้ข้อมูลก้อนเดิมตลอดเวลา ความหมายของข้อมูลจะขึ้นกับว่าใครเขียนก่อน ใครอ่านทีหลัง และอ่านระหว่างการเขียนหรือไม่ Dependency จึงซ่อนอยู่ในลำดับเวลาและ shared state ระบบต้องใช้ lock หรือกลไก concurrency control เพื่อป้องกันผลลัพธ์ที่ขัดแย้งกัน
เมื่อข้อมูลเป็น immutable การแปลงแต่ละครั้งสร้างผลลัพธ์เชิงตรรกะก้อนใหม่ ข้อมูลเดิมยังคงเดิม ความสัมพันธ์จึงเขียนออกมาเป็นสายได้ชัดเจน:
rawData
└── filter(valid)
└── map(toFeature)
└── groupByKey
└── aggregate
สายนี้บอกได้ว่าผลแต่ละก้อนมาจากไหน หากยังไม่มีใครต้องการผล ระบบไม่จำเป็นต้องรีบคำนวณ นี่คือจุดที่ Lazy Evaluation เข้ามาใช้ประโยชน์จาก dependency ที่ชัดเจน เมื่อมี Action ระบบจึงพิจารณาสายงานทั้งหมด จัด stage และเลือกว่าจะเริ่มคำนวณตรงไหน
ลด Write Lock ได้อย่างไร
หาก thread หรือ worker หลายตัวอ่านข้อมูล immutable ก้อนเดียวกัน ทุกคนรู้ว่าค่าจะไม่ถูกเปลี่ยนระหว่างอ่าน จึงไม่ต้องใช้ write lock เพื่อป้องกันข้อมูลก้อนนั้น และ reader ไม่ต้องรอ writer ที่กำลังแก้ค่าภายในก้อนเดียวกัน
อย่างไรก็ตาม Immutability ไม่ได้ทำให้ lock หายจากระบบทั้งหมด Scheduler ยังต้องจัดคิว Cache manager ยังต้องปรับ metadata และ storage ยังต้องประกาศว่าผลลัพธ์ชุดใหม่พร้อมใช้งานหรือไม่ จุดที่ลดลงคือการแย่งแก้ เนื้อหาของข้อมูลก้อนเดิม ไม่ใช่การประสานงานทุกชนิดในระบบ
ลด Dependency แบบใด
Immutability ไม่ได้ลบ dependency ทางคณิตศาสตร์ หาก C ต้องใช้ผลจาก B มันยังต้องรอ B เหมือนเดิม สิ่งที่ลดลงคือ write-after-read, read-after-write และ write-after-write dependency ที่เกิดจากหลายงานใช้ตำแหน่งเก็บข้อมูลร่วมกันโดยไม่จำเป็น
| Dependency | เมื่อแก้ข้อมูลร่วมกัน | เมื่อสร้างผลลัพธ์ใหม่ |
|---|---|---|
| Read after Write | Reader ต้องรู้ว่า writer เขียนเสร็จหรือยัง | ผลใหม่จะถูกส่งต่อเมื่อ transformation ที่สร้างมันเสร็จ |
| Write after Read | Writer อาจต้องรอ reader อ่านค่าเดิมให้เสร็จ | Writer สร้าง version ใหม่ Reader เดิมยังอ่าน version เก่าได้ |
| Write after Write | Writer หลายตัวแย่งตำแหน่งเดียวกัน ต้องกำหนดลำดับ | แต่ละงานเขียน output แยกกัน แล้วค่อยเลือกหรือรวมผลตามกฎ |
| True data dependency | ต้องรอผลก่อนหน้า | ยังต้องรอเหมือนเดิม เพราะเป็นข้อกำหนดของ algorithm |
การทำ dependency ให้ชัดช่วยทั้ง scheduling และ recovery ระบบรู้ว่างานใดทำพร้อมกันได้ งานใดต้องรอ และหาก partition หายต้องย้อนคำนวณจาก parent ใด
จาก Random Write ไปสู่การเขียนผลใหม่
ระบบข้อมูลขนาดใหญ่มักหลีกเลี่ยงการกระโดดไปแก้ record เดิมทีละตำแหน่ง เพราะ random write ทำให้ storage ต้องค้นตำแหน่ง ล็อก block หรือ page และรักษาความถูกต้องระหว่างการเขียนหลายงาน การสร้าง output ใหม่เปิดทางให้แต่ละ partition เขียนไฟล์ของตนแบบต่อเนื่อง แล้วประกาศผลทั้งชุดเมื่อเสร็จ
// แนวคิดแบบแก้ข้อมูลเดิม
for each record:
locate old position
lock page
update value
unlock page
// แนวคิดแบบ transformation
read old partition sequentially
compute new records
write new partition sequentially
นี่เป็นเหตุผลที่ HDFS เน้นการเขียนแบบ append และระบบ analytical จำนวนมากสร้างไฟล์หรือ version ใหม่แทนการแก้ byte เดิมโดยตรง แนวคิดเดียวกันพบใน copy-on-write table, immutable segment และ LSM-based storage แม้รายละเอียดภายในต่างกัน
Lazy Evaluation ทำอะไรได้เมื่อเห็นแผนทั้งหมด
ถ้าแต่ละ transformation รันทันที ระบบเห็นเพียงขั้นปัจจุบันและต้องสร้างผลระหว่างทางทุกครั้ง เมื่อชะลอไว้ก่อน ระบบมองเห็น DAG ทั้งสายแล้วอาจ:
- รวม
mapกับfilterที่ทำต่อเนื่องให้อยู่ใน stage เดียว - หลีกเลี่ยงการสร้าง collection ระหว่างทางที่ไม่มีใครใช้โดยตรง
- อ่านเฉพาะ partition ที่ Action ต้องการ
- วาง narrow transformation ต่อกันก่อนจุด Shuffle
- ใน DataFrame/SQL ดัน filter และการเลือกคอลัมน์ลงไปใกล้แหล่งข้อมูล
- ตัด branch ของแผนที่ไม่เกี่ยวกับผลลัพธ์
แต่ RDD optimizer ไม่ได้เข้าใจความหมายของคอลัมน์ลึกเท่า Catalyst ใน Spark SQL Lazy Evaluation ของ RDD ช่วยวาง execution DAG ส่วนการจัดรูป Relational Algebra ต้องอาศัย schema และ operator ระดับสูงในบทที่ 9
Cache อยู่ตรงไหนในความสัมพันธ์นี้
Lazy ไม่ได้หมายถึงจำผลลัพธ์ไว้ หากมี Action สองตัวใช้ RDD เดียวกัน Spark อาจคำนวณ lineage ซ้ำสองครั้ง จึงใช้ cache() หรือ persist() ระบุว่าผลตรงนี้ควรถูก materialize และเก็บไว้สำหรับการใช้ครั้งถัดไป
val cleaned = raw.filter(valid).map(normalize) cleaned.count() // Action แรก cleaned.saveAsTextFile() // Action ที่สอง // หาก cleaned ไม่ถูก cache สาย filter + map อาจถูกรันใหม่ // หาก cache ไว้ Action ที่สองอ่านผล cleaned ที่เก็บไว้ได้
จุด cache ที่ดีมักเป็นผลซึ่งสร้างแพง ใช้ซ้ำหลายครั้ง และมีขนาดพอเหมาะกับ storage level ที่เลือก จุด cache ที่ไม่ดีคือข้อมูลใช้ครั้งเดียว ใหญ่จนเบียดข้อมูลอื่น หรืออยู่ก่อน filter ทั้งที่สามารถลดขนาดได้มาก
Lineage ช่วยกู้คืนโดยไม่ต้องทำสำเนาผลระหว่างทางทุกก้อน
เพราะ dependency ชัด Spark สามารถคำนวณ partition ที่หายจาก parent ได้ ไม่จำเป็นต้อง replicate intermediate RDD ทุกตัวไว้หลายสำเนา วิธีนี้เหมาะเมื่อการคำนวณใหม่ถูกกว่าการเก็บสำเนา แต่ยังต้องมีต้นทางที่ทนทาน เช่นไฟล์บน HDFS หรือ object storage
ถ้า lineage ยาวมากหรือการคำนวณใหม่แพง อาจใช้ checkpoint ตัดสาย dependency แล้วบันทึกสถานะลง storage ที่ทนทาน Cache กับ checkpoint จึงต่างกัน: cache เน้นใช้ซ้ำและอาจสูญหายได้ ส่วน checkpoint เน้นสร้างจุดเริ่มกู้คืนใหม่
สิ่งที่ต้องแลก
| ประโยชน์ | ต้นทุนที่ตามมา |
|---|---|
| ลดการแย่งแก้ข้อมูลเดิม | ต้องสร้าง object, partition หรือ file version ใหม่ |
| Reader อ่าน version เดิมต่อได้ | ต้องจัดการหลาย version และลบข้อมูลที่หมดอายุ |
| เขียนต่อเนื่องและกระจายตาม partition ได้ | อาจเกิดไฟล์เล็กจำนวนมากและต้อง compaction ภายหลัง |
| ติดตาม lineage และคำนวณใหม่ได้ | Lineage ยาวทำให้ recovery แพง จึงอาจต้อง checkpoint |
| Cache ลดการคำนวณซ้ำ | ใช้ memory, serialization และ eviction อาจทำให้ผลถูกคำนวณใหม่ |
read → parse → filter → feature → train ซึ่งต้องฝึกโมเดลหลายค่า parameter ลองตอบว่า ขั้นใดควร immutable จุดใดได้ประโยชน์จาก lazy plan จุดใดควร cache และจุดใดควร checkpoint คำตอบไม่จำเป็นต้องเหมือนกัน เพราะต้นทุนสร้าง ขนาดข้อมูล และจำนวนครั้งที่ใช้ซ้ำต่างกันจาก Cache ไปสู่ Superlinear Speedup
เมื่อแบ่งข้อมูลให้หลายหน่วยประมวลผล working set ต่อหน่วยอาจเล็กลงจนพอดีกับ cache จำนวน cache miss จึงลดลงมากกว่าที่คาดจากการแบ่งงานเพียงอย่างเดียว บางกรณีจึงเห็น speedup มากกว่าจำนวน CPU สิ่งนี้เรียกว่า Superlinear Speedup และจะอธิบายเต็มในบทที่ 6
2โครงสร้างข้อมูลที่งาน Big Data ใช้บ่อย
โครงสร้างข้อมูลไม่ได้เป็นเพียงภาชนะ แต่กำหนดว่าจะเข้าถึงข้อมูลอย่างไร ข้อมูลวางต่อเนื่องหรือกระจัดกระจาย ต้องใช้หน่วยความจำเพิ่มเท่าใด และกระจายไปหลายเครื่องได้ง่ายเพียงใด Big-O บอกแนวโน้มของจำนวน operation แต่ไม่บอก locality, serialization cost หรือจำนวน byte ที่ต้องส่ง
| โครงสร้าง | ลักษณะ | การใช้งานใน Big Data |
|---|---|---|
| Array / Contiguous Buffer | เข้าถึงตามตำแหน่ง O(1) และใช้ cache line ได้คุ้มเมื่อสแกนต่อเนื่อง | เหมาะกับ vectorized processing, columnar data และงานที่อ่านเป็นช่วง |
| Linked Structure | แทรกหรือลบได้สะดวกเมื่อมี pointer ไปตำแหน่งนั้น แต่แต่ละ node มี overhead และ locality มักไม่ดี | ใช้ในโครงสร้างภายในบางชนิด แต่ไม่ใช่ตัวเลือกอัตโนมัติสำหรับ stream หรือ queue ขนาดใหญ่ |
| Hash Table | ค้นหาเฉลี่ย O(1) เมื่อ hash กระจายดีและ load factor เหมาะสม แต่กรณีแย่อาจช้าลง | Hash aggregation, cache, partitioning, hash join และ key-value lookup |
| Ordered Tree / LSM Tree | Tree รองรับ range query ตามลำดับ ส่วน LSM เน้นเขียนต่อเนื่องแล้ว merge ภายหลัง | Index, storage engine และฐานข้อมูลที่ต้องแลก write amplification กับ read/compaction cost |
| Graph Representation | Adjacency matrix ใช้พื้นที่ O(V²); adjacency list/edge list ใช้พื้นที่ตามจำนวน edge | กราฟ sparse มักใช้ adjacency list หรือ edge list และต้องเลือก partition จากรูปแบบการ traverse |
| Probabilistic Structure | ยอมให้คำตอบคลาดเคลื่อนที่ควบคุมได้ เพื่อใช้พื้นที่น้อยกว่าการเก็บสมาชิกทั้งหมด | Bloom Filter, Count-Min Sketch, HyperLogLog และงาน streaming |
Array กับ Linked List: Big-O เท่ากัน แต่เครื่องจริงอาจต่างกันมาก
การสแกน array และ linked list มีความซับซ้อน O(n) เหมือนกัน แต่ array วางข้อมูลต่อเนื่องจึงอ่านล่วงหน้าและใช้ cache line ได้ดี Linked list ต้องตาม pointer ไปยัง node ถัดไป ซึ่งอาจอยู่คนละตำแหน่งในหน่วยความจำ ความต่างนี้ไม่ปรากฏใน Big-O แต่ชัดเมื่อข้อมูลมีจำนวนมาก
การบอกว่า LinkedList เหมาะกับ queue เพราะแทรกหรือลบบ่อยจึงต้องระวัง Queue ที่สร้างด้วย circular array สามารถเพิ่มและนำข้อมูลออกหัวท้ายแบบ amortized O(1) เช่นกัน และมักใช้หน่วยความจำน้อยกว่า การเลือกต้องดูทั้ง operation, locality และ overhead ต่อ element
Hash Table: O(1) โดยเฉลี่ยไม่ได้แปลว่าไม่มีต้นทุน
Hash Table ต้องคำนวณ hash จัดการ collision และขยายตารางเมื่อแน่นเกินไป หาก key กระจายไม่ดี บาง bucket จะรับงานมากกว่าส่วนอื่น ในเครื่องเดียวทำให้ lookup ช้าลง ส่วนในระบบกระจายทำให้บาง partition หรือบาง worker กลายเป็น hotspot
hash(key) % p เลือกหนึ่งใน p partition เราคาดหวังให้ key กระจายใกล้เคียงกัน แต่ถ้างานส่วนใหญ่ถาม key เดียว ต่อให้จำนวน record กระจายเท่ากัน request ก็ยังไม่สมดุล Distribution ของข้อมูลกับ distribution ของ workload เป็นคนละเรื่องRange Partition กับ Hash Partition
| วิธี | ข้อดี | สิ่งที่ต้องแลก |
|---|---|---|
| Hash Partition | กระจาย point lookup และการเขียนได้ดีเมื่อ hash เหมาะสม | Range scan ต้องไปหลาย partition และรวมผลกลับมา |
| Range Partition | ข้อมูลที่ key ใกล้กันอยู่ด้วยกัน เหมาะกับ range query และ ordered scan | Sequential key อาจทำให้การเขียนไหลไป partition ล่าสุดจนเกิด hotspot |
ความต่างนี้จะกลับมาใน HBase กับ Cassandra บทที่ 7 HBase เรียงตาม row key และแบ่งเป็นช่วง ส่วน Cassandra ใช้ hash ของ partition key เลือก token การออกแบบ key จึงเป็นการออกแบบตำแหน่งและ workload ไปพร้อมกัน
Tree, LSM Tree และเหตุผลที่ฐานข้อมูลไม่ได้ใช้โครงสร้างเดียว
B-tree และโครงสร้างใกล้เคียงรักษาลำดับและช่วยค้นช่วงข้อมูลได้ดี แต่การเขียนแบบสุ่มจำนวนมากอาจต้องแก้หลายหน้า LSM Tree เปลี่ยนการเขียนให้ไหลต่อเนื่องเข้า memory table และไฟล์เรียงลำดับ แล้วค่อย merge ผ่าน compaction ภายหลัง จึงเขียนเร็วขึ้นแต่ย้ายต้นทุนไปยัง read amplification, write amplification และ compaction
ไม่มี storage engine ที่ “เร็วกว่า” โดยไม่บอก workload ระบบที่รับ log เขียนต่อเนื่องอาจเลือกต่างจากระบบบัญชีที่ต้องแก้ record และอ่านช่วง key บ่อย
Graph: จำนวน vertex เท่ากันไม่ได้แปลว่างานเท่ากัน
Adjacency matrix เหมาะเมื่อกราฟหนาแน่นหรือ operation ต้องเข้าถึงคู่ vertex จำนวนมาก แต่กราฟจริงจำนวนมากมี edge น้อยกว่า V² มาก Adjacency list และ edge list จึงประหยัดพื้นที่กว่า อย่างไรก็ตาม vertex บางตัวอาจมี degree สูงมาก การแบ่ง vertex เท่ากันจึงทำให้จำนวน edge ต่อ worker ต่างกันมาก
Bloom Filter และ Sketch: บางครั้งการไม่เก็บทุกอย่างคือคำตอบ
Bloom Filter ตอบว่า key “อาจมี” หรือ “ไม่มีแน่นอน” โดยใช้บิตและ hash หลายตัว มันอาจตอบว่ามีทั้งที่ไม่มีจริง แต่ไม่ควรตอบว่าไม่มีเมื่อเคยเพิ่ม key นั้นแล้ว คุณสมบัตินี้ช่วยหลีกเลี่ยงการเปิดไฟล์หรือส่ง request ที่รู้ล่วงหน้าว่าไม่พบ
Count-Min Sketch ประมาณความถี่โดยไม่เก็บตัวนับสำหรับทุก key ส่วน HyperLogLog ประมาณจำนวนสมาชิกไม่ซ้ำ โครงสร้างเหล่านี้ยอมเสียความแม่นยำเล็กน้อยเพื่อควบคุมหน่วยความจำ เป็นพื้นฐานสำคัญของ stream ซึ่งไม่สามารถเก็บประวัติทั้งหมดได้ตลอดไป
3อัลกอริทึมและ Complexity ที่ควรคุ้นเคย
Complexity ช่วยให้เรามองแนวโน้มเมื่อขนาดปัญหาโตขึ้น แต่ใน Big Data ตัวแปรสำคัญไม่ได้มีเพียง n เราต้องคิดถึงจำนวนเครื่อง p, ปริมาณข้อมูลที่ส่ง, จำนวนรอบที่ต้องประสานงาน, ขนาดหน่วยความจำ และรูปแบบการเข้าถึง storage ด้วย
แบ่งปัญหา แก้ส่วนย่อย แล้วรวมคำตอบ แต่ไม่ได้หมายความว่าทุกส่วนทำพร้อมกันได้ ต้องดู dependency และต้นทุนการรวมด้วย
การเรียงและจัดกลุ่มอยู่ใต้ SQL, MapReduce และการสร้าง index เมื่อข้อมูลเกิน RAM ต้องคิดถึง external sorting และ I/O หลายรอบ
BFS เดินกราฟทีละระดับ ใช้ O(V+E) สำหรับ adjacency-list graph และให้เส้นทางสั้นสุดเมื่อ edge ไม่มีน้ำหนักหรือมีน้ำหนักเท่ากัน
ยอมใช้ randomness หรือความคลาดเคลื่อนที่ควบคุมได้ เพื่อให้ใช้เวลาและพื้นที่น้อยลงเมื่อคำตอบ exact แพงเกินความจำเป็น
Big-O บอกอะไร และไม่บอกอะไร
O(n log n) บอกว่าอัตราการเติบโตสูงกว่า O(n) แต่ไม่บอก constant factor, cache behavior หรือจำนวน byte ที่อ่าน Algorithm O(n) ที่กระโดดอ่านข้อมูลจากเครือข่ายทีละรายการอาจช้ากว่า O(n log n) ที่ทำงานอยู่ใน RAM และอ่านต่อเนื่อง
Complexity จึงเป็นจุดเริ่มต้น ไม่ใช่ผล benchmark เราควรใช้มันตัดแนวทางที่ไม่สามารถโตได้ แล้วใช้ measurement ตรวจต้นทุนบนสถาปัตยกรรมจริง
Work, Span และ Cost ของการทำงานแบบขนาน
- Work: งานทั้งหมดที่ต้องทำ เทียบได้กับเวลาหากใช้หน่วยประมวลผลหนึ่งตัว
- Span: เส้นทาง dependency ที่ยาวที่สุด หรือเวลาต่ำสุดแม้มีหน่วยประมวลผลไม่จำกัด
- Cost: ทรัพยากรรวมที่ใช้ เช่น p × T(p) ซึ่งช่วยตรวจว่าความเร็วที่ได้แลกกับงานส่วนเกินมากเพียงใด
ถ้า Work สูงแต่ Span สั้น ปัญหามี parallelism มาก แต่ถ้า Span ยาวจากขั้นตอนที่ต้องทำเรียงกัน การเพิ่มเครื่องไม่สามารถข้าม dependency ได้ ในระบบกระจาย Span ยังรวม barrier และรอบการสื่อสารด้วย
Divide and Conquer ไม่เท่ากับ MapReduce เสมอไป
Divide and Conquer แบ่งปัญหาเป็นส่วนย่อยซึ่งอาจเรียกซ้ำ แล้วรวมคำตอบ ส่วน MapReduce กำหนดรูปการไหลของข้อมูลเป็น Map → Shuffle/Group → Reduce งานบางอย่างเข้ารูปนี้ได้ดี เช่น Word Count และ inverted index แต่ algorithm ที่ต้องแลกสถานะหลายรอบอาจต้องสร้าง MapReduce job ซ้ำจนมี overhead สูง
(sum, count) แล้วรวมสองค่านี้ก่อนหารตอนท้าย นี่คือตัวอย่างที่ representation ของผลย่อยกำหนดว่าการรวมแบบขนานถูกต้องหรือไม่Sorting เมื่อข้อมูลใหญ่กว่า RAM
ในหน่วยความจำ เราอาจใช้ Merge Sort หรือ Quick Sort ตามคุณสมบัติที่ต้องการ แต่เมื่อข้อมูลใหญ่กว่า RAM ต้องแบ่งข้อมูลเป็น run ที่เรียงแล้วเขียนลง storage จากนั้น merge หลาย run ในรอบถัดไป ต้นทุน I/O และจำนวน pass จึงสำคัญกว่า comparison อย่างเดียว
Distributed sort เพิ่มขั้น partition ข้อมูลตามช่วง key แล้ว Shuffle ไปยัง worker ที่รับผิดชอบ แต่ถ้า boundary เลือกไม่ดี บาง worker จะรับข้อมูลมากกว่าส่วนอื่น Sampling จึงถูกใช้ช่วยประมาณ distribution ก่อนแบ่งช่วง
BFS: “คำตอบแรกดีที่สุด” ต้องระบุเงื่อนไข
BFS สำรวจกราฟทีละระดับ จึงหาเส้นทางที่ใช้จำนวน edge น้อยที่สุดในกราฟไม่มีน้ำหนัก หรือกราฟที่ edge ทุกเส้นมีน้ำหนักเท่ากัน หาก edge มีน้ำหนักต่างกันต้องใช้ algorithm เช่น Dijkstra เมื่อ weight ไม่ติดลบ การกล่าวว่า BFS optimal โดยไม่ระบุเงื่อนไขจึงกว้างเกินไป
สำหรับ graph representation แบบ adjacency list BFS ใช้เวลา O(V+E) และพื้นที่ O(V) ในกรณีทั่วไป ส่วน O(bd) เป็นการอธิบาย tree search ด้วย branching factor b และความลึก d ซึ่งใช้คนละมุมวิเคราะห์ ควรเลือก notation ให้ตรงกับสิ่งที่กำลังนับ
Monte Carlo กับ Las Vegas
Monte Carlo algorithm ใช้เวลาหรือทรัพยากรที่ควบคุมได้ แต่ยอมให้คำตอบมีโอกาสผิดหรือคลาดเคลื่อน เช่น การประมาณค่าด้วย sampling ส่วน Las Vegas algorithm ให้คำตอบถูกต้องเมื่อจบ แต่เวลาที่ใช้ขึ้นกับ randomness เช่น randomized quicksort ในบางมุมมอง
คำว่า “ยิ่งสุ่มมากยิ่งแม่น” ใช้ได้กับบาง estimator แต่ไม่ใช่คำอธิบาย algorithm randomized ทุกชนิด สิ่งที่ต้องระบุคือ error bound, confidence และทรัพยากรที่เพิ่มตามจำนวน sample
NP-hard ไม่ได้หายไปเมื่อเพิ่มเครื่อง
Parallelism ช่วยสำรวจทางเลือกได้หลายส่วนพร้อมกัน แต่ไม่เปลี่ยนอัตราการเติบโตแบบ exponential ให้กลายเป็น polynomial โดยอัตโนมัติ หากขนาดปัญหาเพิ่มเล็กน้อย จำนวนทางเลือกอาจโตเร็วกว่าจำนวนเครื่องที่เพิ่มมาก
ทางปฏิบัติจึงใช้ heuristic, approximation, relaxation, sampling หรือจำกัดขอบเขตปัญหา เป้าหมายไม่จำเป็นต้องเป็นคำตอบดีที่สุดทางคณิตศาสตร์เสมอ แต่ต้องรู้ว่าคำตอบที่ได้มีข้อรับประกันอะไร และล้มเหลวในกรณีใด
Communication Complexity: บางครั้งนับ byte สำคัญกว่านับ operation
Algorithm สองตัวอาจทำ arithmetic ใกล้กัน แต่ตัวหนึ่งต้องส่งข้อมูลทั้งหมดข้ามเครื่องหลายรอบ ขณะที่อีกตัวรวมผลในเครื่องก่อนส่ง ใน Big Data การวิเคราะห์จึงควรถามเพิ่มว่า:
- ส่งข้อความกี่ครั้ง
- ข้อความรวมกี่ byte
- มี all-to-all Shuffle หรือไม่
- ต้องรอ barrier กี่รอบ
- ข้อมูลกระจายสมดุลหรือมี skew
4เครือข่าย ฐานข้อมูล และระบบปฏิบัติการ: สามเสาที่มองข้ามไม่ได้
Computer Networks
เครือข่ายไม่ได้เป็นสายต่อระหว่างเครื่องเฉย ๆ แต่เป็นทรัพยากรที่มี latency, bandwidth, topology, queue และโอกาสล้มเหลว การเรียกข้อมูลข้ามเครื่องต้องผ่าน serialization, protocol stack, switch และ software ปลายทาง จึงแพงและแปรปรวนกว่าการอ่านหน่วยความจำภายในเครื่องมาก
Data Locality: ส่ง code ไปหา data
เมื่อข้อมูลหนึ่ง block มีขนาดหลายร้อย MB แต่ code ที่ประมวลผลมีเพียงไม่กี่ KB การส่ง task ไปยังเครื่องที่มีข้อมูลอยู่แล้วคุ้มกว่าขน block กลับมาหาโปรแกรม HDFS กับ scheduler จึงร่วมกันพยายามทำ node-local execution ก่อน rack-local และหลีกเลี่ยง off-rack transfer เท่าที่ทำได้
หลักเดียวกันปรากฏใน predicate pushdown ซึ่งส่งเงื่อนไขกรองลงไปยัง data source และ projection pruning ซึ่งอ่านเฉพาะคอลัมน์ที่ใช้ เป้าหมายคือทำให้ข้อมูลเล็กลงก่อนผ่านเครือข่าย
Topology และ Oversubscription
การส่งข้อมูลระหว่าง worker ใน rack เดียวกันกับข้าม rack อาจใช้เส้นทางและแย่ง bandwidth ต่างกัน หากทุก worker Shuffle พร้อมกัน ลิงก์ส่วนกลางอาจเป็นคอขวดแม้ NIC ของแต่ละเครื่องยังไม่เต็ม Scheduler และ partitioner จึงต้องรู้ตำแหน่งของข้อมูล ไม่ใช่รู้เพียงจำนวนเครื่อง
Failure เป็นเหตุการณ์ปกติเมื่อระบบใหญ่ขึ้น
เครื่องหนึ่งมีโอกาสเสียไม่บ่อย แต่คลัสเตอร์ที่มีเครื่องจำนวนมากจะพบ disk error, process crash, packet loss และ slow node เป็นประจำ ระบบ Big Data จึงออกแบบ retry, replication, lineage และ speculative execution ไว้เป็นส่วนหนึ่งของการทำงาน ไม่ใช่เพิ่มภายหลังเมื่อเกิดเหตุ
เวลาในระบบกระจาย
Physical clock ของเครื่องต่าง ๆ ไม่ตรงกันสมบูรณ์และ drift ได้ การซิงโครไนซ์ลดความคลาดเคลื่อนแต่ไม่ทำให้เวลาทุกเครื่องเหมือนกันทุกขณะ ส่วน logical clock เช่น Lamport clock ใช้ช่วยจัดลำดับเหตุการณ์ตามความสัมพันธ์ happens-before ไม่ได้ใช้ตั้งนาฬิกาให้ตรงกัน
ความต่างนี้สำคัญต่อ log, event time และ conflict resolution หากใช้ timestamp จากคนละเครื่องตัดสินว่าเหตุการณ์ใดใหม่กว่าโดยไม่รู้ขอบเขต clock skew อาจเลือกข้อมูลผิด version ได้
Database Systems
ฐานข้อมูลสอนสามเรื่องสำคัญต่อ Big Data: วิธีแทนข้อมูล วิธีเข้าถึงข้อมูล และเงื่อนไขความถูกต้อง การเข้าสู่ NoSQL ไม่ได้ทำให้หลักเหล่านี้หายไป เพียงเปลี่ยน trade-off และย้ายความรับผิดชอบบางส่วนจากระบบไปยังผู้ออกแบบข้อมูล
| หัวข้อ | รายละเอียด |
|---|---|
| Indexing | Hash index เหมาะกับ equality lookup; ordered index เช่น B-tree รองรับ range query; spatial index รองรับข้อมูลหลายมิติ แต่ทุก index เพิ่มต้นทุนการเขียนและพื้นที่ |
| Join | Nested-loop, Hash Join, Sort-Merge Join และ Broadcast Join เหมาะกับข้อมูลและเงื่อนไขต่างกัน Optimizer ต้องประเมินขนาดผลระหว่างทาง |
| Transaction | กำหนดขอบเขตที่หลาย operation ต้องสำเร็จหรือย้อนกลับร่วมกัน พร้อมควบคุมผลของการทำงานพร้อมกัน |
| NoSQL | กลุ่มระบบที่เลือก data model, query และ consistency ต่างจาก relational database เพื่อรองรับ access pattern หรือการกระจายบางรูปแบบ ไม่ได้แปลว่า “ใช้ hash ทั้งหมด” หรือ “ไม่มี schema” เสมอไป |
Index คือสำเนาที่จัดระเบียบเพื่อคำถามบางชนิด
Index ทำให้การอ่านเร็วขึ้นเพราะไม่ต้องสแกนทุก record แต่ต้องแลกพื้นที่และต้นทุนอัปเดต เมื่อมี index จำนวนมาก ทุกการเขียนต้องแก้หลายโครงสร้าง ระบบ analytical ที่นำเข้าข้อมูลเป็นชุดอาจเลือก format, partition pruning และ metadata แทนการสร้าง index แบบ OLTP ทุกคอลัมน์
Join ไม่ได้มี complexity เดียว
การกล่าวว่า Join สองตารางใช้เวลาขนาดตารางคูณกันใช้ได้กับ nested-loop แบบพื้นฐาน แต่ระบบจริงมี Hash Join ที่โดยแนวคิดใช้ O(N+M) เมื่อเงื่อนไขและหน่วยความจำเหมาะสม และ Sort-Merge Join ซึ่งอาศัยการเรียงข้อมูล การเลือก algorithm ต้องดูชนิดเงื่อนไข ขนาดข้อมูล memory และการกระจาย key
Relational Algebra เปิดทางให้ Optimizer
Selection, Projection, Join และ Aggregation รับ relation แล้วคืน relation ใหม่ จึงประกอบและจัดรูปได้ เช่น ดัน filter ลงไปก่อน Join ตัดคอลัมน์ที่ไม่ใช้ออก และรวมผลบางส่วนก่อน Shuffle Spark SQL ใช้แนวคิดนี้สร้าง logical plan แล้วเลือก physical plan ในบทที่ 9
Schema-on-write กับ Schema-on-read
Schema-on-write ตรวจและจัดข้อมูลให้เข้ารูปก่อนบันทึก ทำให้ผู้ใช้ปลายทางเห็นความหมายสม่ำเสมอ แต่การนำเข้าต้องตกลง schema ล่วงหน้า Schema-on-read เก็บข้อมูลดิบยืดหยุ่นกว่า แล้วตีความตอนอ่าน แต่ภาระการทำความเข้าใจอาจกระจายไปยังผู้ใช้ทุกคน
การเลือกระหว่างสองแนวทางไม่ควรกลายเป็นศาสนา ระบบจริงมักมีหลายชั้น: raw zone เก็บข้อมูลใกล้ต้นทาง ส่วน curated table กำหนด schema และ quality สำหรับการใช้ร่วมกัน
Operating Systems: Locking และ Deadlock
Operating Systems จัดสรร CPU, memory, file, process และ I/O ซึ่งเป็นทรัพยากรเดียวกับที่ Big Data engine ต้องบริหาร หากไม่เข้าใจสิ่งเหล่านี้ เราอาจตีความอาการผิด เช่น คิดว่า algorithm ช้า ทั้งที่ระบบกำลัง swap หรือคิดว่าเพิ่ม thread แล้วเร็วขึ้น ทั้งที่ทุก thread แย่ง disk queue เดียวกัน
Process, Thread และ Context Switch
Process มี address space แยกกัน ส่วน thread ใน process เดียวกันใช้ memory ร่วมกัน การใช้หลาย thread ลดต้นทุนบางส่วนและแบ่งข้อมูลได้เร็ว แต่ shared state ทำให้ต้อง synchronize เมื่อ thread มากเกินไป context switch, cache contention และ lock contention อาจทำให้ throughput ลดลง
Virtual Memory และ Page Fault
Virtual memory ทำให้โปรแกรมมองเห็น address space ต่อเนื่อง แต่เมื่อ working set ใหญ่กว่า RAM ระบบอาจย้าย page ไป storage การเกิด page fault จำนวนมากทำให้ performance ตกอย่างรุนแรง โปรแกรมยังรันได้จึงดูเหมือนเพียง “ช้าลง” ทั้งที่กำลังใช้ดิสก์แทนหน่วยความจำ
นี่เป็นสาเหตุหนึ่งของ Superlinear Speedup: เมื่อแบ่งข้อมูลไปหลายเครื่อง working set ต่อเครื่องอาจกลับมาพอดีกับ RAM หรือ cache ทำให้ paging หรือ cache miss หายไปมาก
File, Buffer และ Page Cache
การอ่านไฟล์ไม่ได้แปลว่า byte เดินจากดิสก์ทุกครั้ง Operating System อาจเก็บ page ที่เพิ่งอ่านไว้ใน memory Benchmark รอบสองจึงเร็วกว่ารอบแรกเพราะ cache ไม่ใช่เพราะ algorithm ดีขึ้น การทดลองต้องบันทึกว่าเป็น cold cache หรือ warm cache และรวมเวลา I/O ส่วนใดไว้บ้าง
Locking และ Deadlock
Read lock อาจอนุญาตให้หลายงานอ่านพร้อมกัน ส่วน write lock ต้องกันการแก้ไขที่ขัดแย้ง หากงาน A ถือ lock X แล้วรอ Y ขณะที่งาน B ถือ Y แล้วรอ X จะเกิด deadlock
Wait-for graph ใช้แทนความสัมพันธ์ว่า transaction ใดรอใคร หากพบวงจรแปลว่ามี deadlock ภายใต้แบบจำลองนั้น วิธีจัดการมีทั้งกำหนดลำดับ lock, timeout, deadlock detection แล้ว abort บาง transaction หรือใช้ concurrency control แบบอื่น เช่น MVCC
Container ไม่ใช่ Virtual Machine ขนาดเล็กเฉย ๆ
Container แยก process ผ่าน namespace และจำกัดทรัพยากรผ่าน cgroup แต่ยังใช้ kernel ร่วมกับ host จึงเริ่มเร็วและเบากว่า VM โดยทั่วไป อย่างไรก็ตาม memory limit, CPU quota และ storage mount มีผลต่อ Spark worker และ database หากตั้งค่าไม่เข้าใจ อาการ out-of-memory หรือ throttling อาจถูกเข้าใจผิดว่าเป็น bug ของ application
5Parallel vs Concurrent Computing: คำสองคำที่มักถูกใช้ปนกัน
ระบบ web server บน CPU หนึ่ง core สามารถ concurrent ได้ด้วย event loop สลับจัดการ request ระหว่างรอ I/O แต่ยังไม่ parallel ในขณะใดขณะหนึ่ง หากมีหลาย core และ request หลายตัวกำลังใช้ CPU พร้อมกัน ระบบจึงเป็นทั้ง concurrent และ parallel
| รูปแบบ | รายละเอียด |
|---|---|
| SIMD / Vectorization | คำสั่งหนึ่งทำ operation เดียวกับข้อมูลหลายค่า เหมาะกับ loop ที่ข้อมูลเป็นชนิดเดียวกันและไม่มี branch แตกต่างมาก |
| Data Parallelism | แบ่งข้อมูลเป็น partition แล้วใช้ computation เดียวกันกับแต่ละส่วน Map และ Filter เข้ารูปนี้ได้ดี |
| Task Parallelism | งานหลายชนิดทำพร้อมกัน เช่น อ่านข้อมูล แปลงข้อมูล และเขียนผล แต่ต้องจัด dependency กับทรัพยากรร่วม |
| Pipeline Parallelism | แต่ละขั้นทำ record คนละชุดพร้อมกัน คล้ายสายพาน แต่ throughput ถูกจำกัดด้วยขั้นที่ช้าที่สุด |
| Shared Memory | Thread หลายตัวเข้าถึง address space เดียว สื่อสารง่ายแต่ต้องควบคุม race condition และ contention |
| Message Passing | แต่ละ process มี state ของตนและแลกข้อมูลผ่านข้อความ เหมาะกับหลายเครื่องแต่มี serialization และ network cost |
Parallel, Distributed และ Scalable ไม่ใช่คำเดียวกัน
| แนวคิด | คำถามหลัก | ตัวอย่างสิ่งที่กังวล |
|---|---|---|
| Parallel Computing | ทำอย่างไรให้งานเสร็จเร็วขึ้นด้วยหลายหน่วยประมวลผล | Decomposition, dependency, speedup, load balance |
| Distributed Systems | ทำอย่างไรให้หลายเครื่องร่วมกันทำงาน แม้สื่อสารช้าและล้มเหลวแยกกันได้ | Ordering, consistency, replication, partial failure |
| Scalable Systems | เมื่อ workload โต ระบบเพิ่มทรัพยากรแล้วรักษาคุณภาพและต้นทุนได้เพียงใด | Throughput, latency, bottleneck, elasticity, cost |
Spark job เป็นทั้ง parallel และ distributed ส่วนบริการ Spark SQL ที่รับ query หลายผู้ใช้ยังมี concurrency ด้วย การแยกคำช่วยให้รู้ว่าปัญหาที่เห็นเป็น race condition, network failure หรือ scalability limit
Race Condition และ Determinism
Race condition เกิดเมื่อผลลัพธ์ขึ้นกับลำดับเวลาของงานที่แข่งขันกัน เช่น thread สองตัวอ่านค่า counter เดิมแล้วเขียนค่าที่เพิ่มกลับไป ทำให้การเพิ่มหนึ่งครั้งหายไป ระบบกระจายยิ่งตรวจยากเพราะลำดับข้อความเปลี่ยนได้และการรันซ้ำอาจไม่เกิดอาการเดิม
Functional Programming และ immutability ลด shared mutable state แต่ไม่ได้กำจัดปัญหาทุกอย่าง Operation ที่รวมผลควรมีสมบัติที่เหมาะสม เช่น associativity เพื่อให้จัดกลุ่มใหม่แล้วคำตอบไม่เปลี่ยน หากลำดับมีผล ระบบต้องรักษาลำดับหรือยอมรับ nondeterminism อย่างชัดเจน
Load Balance กับ Straggler
แบ่ง record เท่ากันไม่รับประกันว่าเวลาทำงานเท่ากัน บาง record ใช้ computation มากกว่า บาง key มีข้อมูลมาก และบาง worker แชร์ทรัพยากรกับงานอื่น งานหนึ่ง stage มักเสร็จเมื่อ task ตัวสุดท้ายเสร็จ ดังนั้นหางของ distribution สำคัญกว่าค่าเฉลี่ย
Backpressure: เมื่อปลายทางรับไม่ทัน
ใน pipeline หรือ streaming หากผู้ผลิตส่งข้อมูลเร็วกว่าผู้บริโภคประมวลผล queue จะโตจนใช้ memory หมด Backpressure คือกลไกส่งสัญญาณให้ต้นทางชะลอ ลดอัตรา หรือเลือกทิ้งข้อมูลตามนโยบาย การมี concurrent task มากขึ้นไม่ช่วยถ้าขั้นปลายยังเป็นคอขวดเดิม
6ACID ในระบบกระจาย: ตัวอย่างธนาคาร
| คุณสมบัติ | ตัวอย่างระบบธนาคาร |
|---|---|
| Atomicity | การหักบัญชีต้นทางและเพิ่มบัญชีปลายทางต้องสำเร็จร่วมกัน หรือย้อนกลับทั้งคู่ ไม่ทิ้งสถานะครึ่งทางให้เห็น |
| Consistency | Transaction พาข้อมูลจากสถานะที่รักษากฎไปยังอีกสถานะที่ยังรักษากฎ เช่น ยอดรวมและข้อจำกัดบัญชีถูกต้อง คำนี้ไม่ใช่ข้อรับประกันว่าทุก replica เหมือนกันทันที |
| Isolation | ผลของ transaction ที่ทำพร้อมกันต้องสอดคล้องกับระดับ isolation ที่ระบบสัญญา ระดับอ่อนอาจยอมให้เห็นปรากฏการณ์บางอย่างเพื่อเพิ่ม concurrency |
| Durability | เมื่อระบบยืนยัน commit แล้ว ผลต้องรอดจากความล้มเหลวตาม fault model ที่กำหนด ผ่าน log, replication หรือ storage ที่ทนทาน |
ACID Consistency กับ Distributed Consistency ใช้คำเดียวกันแต่ถามคนละเรื่อง
ACID Consistency ถามว่ากฎหรือ invariant ของข้อมูลยังถูกต้องหรือไม่ เช่น จำนวนเงินไม่ถูกสร้างหรือหายจากการโอน ส่วน distributed consistency model ถามว่าการอ่านจากหลาย client หรือ replica เห็นลำดับและค่าตรงกันเพียงใด เช่น linearizability, causal consistency หรือ eventual consistency
Isolation มีหลายระดับ
การทำให้ทุก transaction เหมือนรันเรียงทีละรายการให้ความเข้าใจง่าย แต่ลด concurrency และอาจแพงเมื่อกระจายหลายเครื่อง Database จึงมี isolation level ต่างกัน ซึ่งอาจยอมให้เกิด dirty read, non-repeatable read, phantom หรือ anomaly อื่นตามแบบจำลอง
MVCC ช่วยให้ reader เห็น snapshot โดยไม่ขวาง writer ทุกครั้ง แต่ต้องเก็บหลาย version และจัดการ garbage collection ไม่มีวิธีที่ให้ concurrency สูง ไม่มี overhead และรับประกันเข้มที่สุดพร้อมกันโดยไม่แลกอะไร
Durability ต้องถามว่า “ทนต่อความล้มเหลวแบบไหน”
การเขียนลง memory ของ process ยังไม่ทน process crash การเขียนลง disk เครื่องเดียวอาจไม่ทน disk หรือเครื่องเสีย การทำสำเนาหลายเครื่องช่วยได้ แต่ถ้าทุกสำเนาอยู่ rack เดียวก็ยังเสี่ยงต่อเหตุระดับ rack คำว่า durable จึงต้องผูกกับ failure model และช่วงเวลาที่ระบบยืนยันกับ client
จาก Transaction เดียวไปสู่ Distributed Transaction
เมื่อ transaction แตะหลาย service หรือหลาย database การ commit ต้องประสานหลายฝ่าย Protocol อย่าง Two-Phase Commit ช่วยให้ผู้เข้าร่วมทุกฝ่ายตกลงร่วมกัน แต่หาก coordinator ล้มเหลว ผู้เข้าร่วมอาจต้องรอ การประสานสองช่วงจึงเพิ่มทั้ง latency และความเสี่ยงที่ระบบจะให้บริการต่อไม่ได้ ระบบบางแบบจึงเปลี่ยนไปใช้ Saga ซึ่งแบ่งงานเป็น local transaction และกำหนดงานชดเชยไว้ล่วงหน้า
เมื่อใดยอมให้ข้อมูลไม่ตรงกันชั่วคราว
จำนวนไลก์ ยอดผู้ชม หรือ cache ของหน้าเว็บอาจคลาดเคลื่อนชั่วคราวได้ หากค่าจะปรับเข้าหากันภายหลังและไม่กระทบสิทธิสำคัญ แต่ยอดเงิน สินค้าชิ้นสุดท้าย หรือสิทธิการเข้าถึงต้องกำหนดข้อรับประกันเข้มกว่า การตัดสินใจควรมาจากผลเสียของความผิดพลาด ไม่ใช่เลือก ACID หรือ BASE ตามกระแส
Eventual consistency ไม่ได้หมายถึง “ข้อมูลผิดได้” อย่างไม่มีขอบเขต ผู้ออกแบบต้องตอบว่าข้อมูลจะกลับมาตรงกันเมื่อใด ความขัดแย้งแก้อย่างไร ผู้ใช้อาจพบเหตุการณ์ผิดปกติแบบใด และงานส่วนใดต้องใช้ consistency ที่เข้มกว่าส่วนอื่น
Idempotency และ Retry
ระบบกระจายมัก retry เมื่อ timeout แต่ timeout ไม่บอกว่า server ไม่ได้ทำงาน อาจเป็นเพียง response หาย หากคำสั่ง “หักเงิน 100 บาท” ถูกทำซ้ำ ผลจะผิด จึงควรใช้ operation ที่ทำซ้ำแล้วผลเท่าเดิม หรือมี idempotency key ให้ระบบจำว่า request นี้เคยประมวลผลแล้ว
7แผนที่เชื่อมพื้นฐานไปยังบทต่อไป
| พื้นฐาน | จะกลับมาในหัวข้อใด | คำถามที่ควรติดตัวไป |
|---|---|---|
| Memory hierarchy และ locality | Datacenter, HDFS, Spark cache, Superlinear Speedup | ข้อมูลถูกอ่านจากระดับใด และ working set พอดีกับ memory หรือไม่ |
| Hash, range และ tree | Partitioning, HBase, Cassandra, Bloom Filter | โครงสร้างนี้รองรับ point lookup, range scan หรือ write pattern แบบใด |
| Work, span และ communication | MapReduce, Parallel BFS, PageRank, Scalable ML | งานใดทำพร้อมกันได้ และข้อมูลต้องเดินทางกี่รอบ |
| Relational Algebra และ Join | Spark SQL, Catalyst Optimizer, Data Integration | กรองหรือลดคอลัมน์ก่อน Shuffle ได้หรือไม่ และ Join แบบใดเหมาะ |
| Process, memory และ I/O | Docker Lab, worker, spill, persistence | ระบบติด CPU, memory, storage หรือ network กันแน่ |
| Concurrency และ failure | Streaming, distributed database, service deployment | เมื่อ retry หรือทำพร้อมกัน ผลลัพธ์ยังถูกต้องหรือไม่ |
| ACID และ consistency model | CAP, BASE, NoSQL, replication | กฎใดห้ามผิด ข้อมูลส่วนใดยอมไม่ตรงกันชั่วคราว และนานเพียงใด |
8สรุปบทและขั้นตอนถัดไป
พื้นฐานของ Big Data ไม่ใช่รายการคำศัพท์แยกส่วน แต่เป็นการมองระบบเดียวผ่านหลายระดับ:
- ระดับฮาร์ดแวร์ถามว่าข้อมูลอยู่ใกล้ CPU เพียงใด
- ระดับโครงสร้างข้อมูลถามว่าการวางข้อมูลเหมาะกับ access pattern หรือไม่
- ระดับ algorithm ถามว่างานโตอย่างไร แบ่งได้แค่ไหน และต้องสื่อสารเท่าใด
- ระดับเครือข่ายถามว่าข้อมูลเดินทางผ่าน topology ใดและล้มเหลวตรงไหนได้บ้าง
- ระดับฐานข้อมูลถามว่า query plan, transaction และ consistency รักษาความหมายอย่างไร
- ระดับระบบปฏิบัติการถามว่าทรัพยากรถูกแบ่งจริงอย่างไรและกำลังรออะไร
หากจำสูตร complexity ได้แต่ไม่เห็น data movement เราจะประเมินระบบกระจายต่ำเกินไป หากอ่าน dashboard ระบบเป็นแต่ไม่เข้าใจ correctness เราอาจเร่งระบบให้ตอบผิดเร็วขึ้น และหากรู้ชื่อเครื่องมือแต่ไม่รู้ workload เราอาจสร้าง cluster เพื่อแก้ปัญหาที่เครื่องเดียวทำได้ดีกว่า
บทที่ 3 จะขยายจากเครื่องหนึ่งเครื่องไปสู่ศูนย์ข้อมูล และอธิบายแนวคิด “The Datacenter is the Computer” ก่อนอ่านต่อ ลองเลือกงานหนึ่งแล้วระบุ working set, access pattern, จุดที่ข้อมูลข้ามเครื่อง และกฎที่ผลลัพธ์ห้ามละเมิด คำตอบสี่ข้อนี้จะกลายเป็นแกนสำหรับวิเคราะห์ทุกสถาปัตยกรรมต่อจากนี้
- อธิบายความต่างระหว่าง latency กับ bandwidth พร้อมยกตัวอย่างงานที่ติดข้อจำกัดแต่ละแบบ
- แยกความหมายของ immutability, lazy evaluation และ caching แล้วอธิบายว่า Spark ใช้แต่ละแนวคิดเพื่ออะไร
- อธิบายว่า Immutability ทำให้ dependency ชัดขึ้นและลด write contention ได้อย่างไร พร้อมระบุสิ่งที่ยังต้องใช้ lock หรือการประสานงานอยู่
- เปรียบเทียบ Cache กับ Checkpoint ในแง่จุดประสงค์ ความทนทาน และการกู้คืนเมื่อ partition หาย
- เปรียบเทียบ Parallel Computing กับ Concurrent Computing พร้อมยกตัวอย่างที่ Big Data ต้องใช้ทั้งสองแบบร่วมกัน
- อธิบาย ACID ทั้ง 4 ข้อผ่านตัวอย่างระบบธนาคาร และแยก ACID Consistency ออกจาก replica consistency
- อธิบายว่าทำไม Adjacency List จึงเหมาะกับกราฟข้อมูลขนาดใหญ่มากกว่า Adjacency Matrix
- เปรียบเทียบ Hash Partition กับ Range Partition และยกตัวอย่าง workload ที่เหมาะกับแต่ละแบบ
- อธิบายว่าทำไม Big-O อย่างเดียวไม่พอสำหรับเปรียบเทียบ algorithm บนระบบกระจาย
- เลือก Query หนึ่งคำสั่ง แล้วระบุ data structure, Join, network transfer และทรัพยากรของระบบปฏิบัติการที่อาจกลายเป็นคอขวด