การแก้ไขข้อขัดแย้งในการซิงโครไนซ์เป็นกลไกที่กำหนดสถานะที่สอดคล้องกันของข้อมูลเมื่อมีการเปลี่ยนแปลงพร้อมกันบนอุปกรณ์ต่างๆ โดยไม่มีการเชื่อมต่อเครือข่าย ในระบบมือถือแบบกระจาย ข้อขัดแย้งเกิดขึ้นเมื่อไคลเอนต์สองรายแก้ไขวัตถุเดียวกันแบบออฟไลน์ และเมื่อการเชื่อมต่อกลับมาทำงานอีกครั้ง เซิร์ฟเวอร์ได้รับสองเวอร์ชันที่แตกต่างกัน ตามข้อมูลของ IEEE ICDCS, 2024 มากถึง 12% ของเซสชันการจำลองแบบในแอปพลิเคชันมือถือมีข้อขัดแย้งอย่างน้อยหนึ่งรายการ กลยุทธ์การแก้ไข กำหนดว่าข้อมูลเวอร์ชันใดจะถูกยอมรับและส่งผลต่อความสมบูรณ์ของข้อมูลอย่างไร
ประเด็นสำคัญ
การแก้ไขข้อขัดแย้ง คือกระบวนการนำข้อมูลแบบกระจายไปสู่สถานะที่สอดคล้องกันเพียงสถานะเดียวหลังจากตรวจพบการเปลี่ยนแปลงที่ขัดแย้งกัน ในระบบรวมศูนย์ ข้อขัดแย้งจะไม่เกิดขึ้น: เซิร์ฟเวอร์ประมวลผลคำขอตามลำดับ ในแอปพลิเคชันมือถือที่มีโหมดออฟไลน์ ไคลเอนต์แก้ไขข้อมูลในเครื่องและซิงโครไนซ์กับเซิร์ฟเวอร์ในภายหลัง หากไคลเอนต์สองรายแก้ไขวัตถุเดียวกัน เซิร์ฟเวอร์จะได้รับสองเวอร์ชันที่มีตัวระบุเดียวกันแต่เนื้อหาต่างกัน
ข้อขัดแย้งเป็นสิ่งที่หลีกเลี่ยงไม่ได้ในการจำลองแบบแบบเชื่อมโยงอย่างหลวมๆ (ความสอดคล้องในที่สุด) เมื่อระบบเสียสละความสอดคล้องทันทีเพื่อความพร้อมใช้งานและประสิทธิภาพ ตามงานวิจัยจากมหาวิทยาลัย Princeton (Aggarwal et al., GEO paper, KDD 2024) ระบบที่มีการจำลองแบบแบบหน่วงเวลาแสดงประสิทธิภาพสูงขึ้น 28% ภายใต้ภาระงานสูงสุดแต่ต้องการกลไกการแก้ไขข้อขัดแย้งเพื่อการทำงานที่ถูกต้อง
กลยุทธ์การแก้ไขเป็นอัลกอริทึมที่ระบบนำไปใช้โดยอัตโนมัติเมื่อตรวจพบข้อขัดแย้ง ฐานข้อมูลและเฟรมเวิร์กต่างๆ นำกลยุทธ์ที่แตกต่างกันไปใช้: Firebase Realtime Database ใช้ LWW, CouchDB เพิ่มการสนับสนุน Merge และ Figma และ Notion สร้างสถาปัตยกรรมบน CRDT
สาเหตุหลักของข้อขัดแย้ง คือการแก้ไขทรัพยากรเดียวกันพร้อมกันโดยไคลเอนต์สองรายขึ้นไปที่ทำงานกับสำเนาข้อมูลในเครื่อง สถานการณ์ทั่วไป: ผู้ใช้ A แก้ไขงานใน Trello แบบออฟไลน์ ในขณะที่ผู้ใช้ B เปลี่ยนคำอธิบายของงานเดียวกันบนอุปกรณ์อื่น ทั้งคู่บันทึกเวอร์ชันของตนในเครื่อง เมื่ออุปกรณ์เชื่อมต่อกับเครือข่าย เซิร์ฟเวอร์จะได้รับค่าสองค่าที่แตกต่างกันสำหรับฟิลด์เดียวกัน
ปัจจัยเพิ่มเติมรวมถึงความหน่วงของเครือข่ายและการแบ่งส่วนเครือข่าย ในฐานข้อมูลแบบกระจายที่ใช้โปรโตคอล Raft หรือ Paxos ข้อขัดแย้งอาจเกิดขึ้นหากผู้นำคลัสเตอร์ไม่พร้อมใช้งานชั่วคราวและคำขอถูกประมวลผลโดยโหนดต่างๆ ตามเอกสารวิชาการของ Amazon DynamoDB (2025) ประมาณ 0.3% ของการดำเนินการเขียนทั้งหมดในระบบ NoSQL ที่ปรับขนาดได้นำไปสู่ข้อขัดแย้งที่ตรวจพบได้
ข้อขัดแย้งยังเกิดจากโครงสร้างข้อมูลที่ไม่ถูกต้องอีกด้วย หากแอปพลิเคชันจัดเก็บตัวนับการดำเนินการหรือรายชื่อผู้เข้าร่วม ไคลเอนต์ออฟไลน์สองรายอาจดำเนินการที่เข้ากันไม่ได้ตามลำดับ ตัวอย่างเช่น ไคลเอนต์ A เพิ่มรายการที่ท้ายรายการ ในขณะที่ไคลเอนต์ B ลบรายการออกจากกลาง — ในระหว่างการซิงโครไนซ์ เซิร์ฟเวอร์ไม่ทราบว่าควรใช้การดำเนินการใดก่อน
Last Write Wins (LWW) เป็นกลยุทธ์ที่เลือกข้อมูลที่มีการประทับเวลาล่าสุดจากเวอร์ชันที่แข่งขันกัน ระบบเปรียบเทียบการประทับเวลาของแต่ละเวอร์ชันและยอมรับเวอร์ชันที่ใหม่กว่า ทิ้งเวอร์ชันที่เก่ากว่า นี่เป็นกลไกที่กำหนดได้: ด้วยชุดการประทับเวลาเดียวกัน ผลลัพธ์จะเหมือนกันเสมอ ขจัดความไม่แน่นอน LWW ถูกนำไปใช้ใน Firebase Realtime Database, Apache Cassandra และ Riak KV
ในแอปพลิเคชันมือถือ LWW มีความน่าสนใจเป็นพิเศษเนื่องจากความเรียบง่ายในการนำไปใช้ ไคลเอนต์ไม่จำเป็นต้องวิเคราะห์ความแตกต่างระหว่างเวอร์ชัน จัดเก็บประวัติการเปลี่ยนแปลง หรือแสดงกล่องโต้ตอบการเลือกให้ผู้ใช้เห็น เซิร์ฟเวอร์ตัดสินใจในมิลลิวินาที อย่างไรก็ตาม LWW มีข้อเสียพื้นฐาน — การสูญเสียข้อมูล หากผู้ใช้สองคนกรอกฟิลด์ต่างๆ ของแบบฟอร์มพร้อมกัน เวอร์ชันหนึ่งจะถูกทิ้งทั้งเวอร์ชัน
ตัวอย่างการทำงานของ LWW ในแอปจดบันทึกมือถือที่มีการซิงโครไนซ์ผ่าน REST API:
data class Note(
val id: String,
val title: String,
val content: String,
val updatedAt: Long
)
fun resolveWithLWW(
local: Note,
remote: Note
): Note {
return if (local.updatedAt >= remote.updatedAt) local
else remote
}
ฟังก์ชัน resolveWithLWW เปรียบเทียบการประทับเวลาและส่งคืนเวอร์ชันปัจจุบัน เมื่อการประทับเวลาเท่ากัน (ซึ่งเกิดขึ้นกับความถี่ในการเขียนสูง) โดยปกติเวอร์ชันภายในเครื่องจะเป็นฝ่ายชนะ
กลยุทธ์การรวม เป็นแนวทางที่ระบบไม่ทิ้งเวอร์ชันใดเวอร์ชันหนึ่งทั้งหมด แต่พยายามรวมการเปลี่ยนแปลงจากทั้งสองเวอร์ชันให้เป็นสถานะที่สอดคล้องกัน ซึ่งคล้ายกับการรวมสาขาใน Git: ข้อขัดแย้งแต่ละรายการได้รับการแก้ไขในระดับฟิลด์หรือการดำเนินการแต่ละรายการ กลยุทธ์การรวมแบ่งออกเป็นแบบอัตโนมัติ (CRDT, OT) และแบบmanual (ผู้ใช้เลือกตัวเลือก)
การนำไปใช้ที่เป็นที่รู้จักมากที่สุดคือการรวมสามทาง (three-way merge) ระบบจัดเก็บสามเวอร์ชัน: ในเครื่อง, ระยะไกล และบรรพบุรุษร่วมกัน (เวอร์ชันพื้นฐานก่อนการแยก) หากไคลเอนต์เพียงรายเดียวเปลี่ยนฟิลด์ การเปลี่ยนแปลงนั้นจะถูกยอมรับโดยอัตโนมัติ หากไคลเอนต์ทั้งสองเปลี่ยนฟิลด์เดียวกัน — จะบันทึกข้อขัดแย้งที่ต้องการการแก้ไข CouchDB และ PouchDB ใช้โมเดลนี้อย่างแข็งขันสำหรับการซิงโครไนซ์เอกสาร
ตัวอย่างการนำการรวมสามทางไปใช้สำหรับโปรไฟล์ผู้ใช้:
data class Profile(
val name: String,
val email: String,
val avatarUrl: String
)
fun threeWayMerge(
base: Profile,
local: Profile,
remote: Profile
): Profile {
return Profile(
name = if (local.name != base.name) local.name
else remote.name,
email = if (local.email != base.email) local.email
else remote.email,
avatarUrl = if (remote.avatarUrl != base.avatarUrl) remote.avatarUrl
else local.avatarUrl
)
}
การรวมสามทาง มีประสิทธิภาพเมื่อโครงสร้างข้อมูลมีความเสถียรเพียงพอ ปัญหาเกิดขึ้นเมื่อเปลี่ยนชื่อฟิลด์ เปลี่ยนชนิดข้อมูล และดำเนินการกับอาร์เรย์ — ในกรณีเหล่านี้ จำเป็นต้องมีตรรกะที่ซับซ้อนมากขึ้น
CRDT (Conflict-Free Replicated Data Type) เป็นแบบจำลองทางคณิตศาสตร์ที่รับประกันการลู่เข้าของข้อมูลโดยไม่มีตัวประสานงานกลาง CRDT ถูกออกแบบให้การดำเนินการทั้งหมดสามารถสลับที่กันได้: ลำดับการนำไปใช้ไม่ส่งผลต่อผลลัพธ์สุดท้าย ซึ่งทำได้ผ่านคุณสมบัติทางพีชคณิต: การรวม CRDT ให้ผลลัพธ์เดียวกันเสมอโดยไม่ขึ้นกับลำดับการรับการเปลี่ยนแปลง
ชนิดหลักของ CRDT รวมถึง G-Counter (ตัวนับที่สนับสนุนเฉพาะการเพิ่ม), PN-Counter (ตัวนับที่มีการเพิ่มและการลด), LWW-Register (เรจิสเตอร์ที่มีการกำหนดเวอร์ชัน) และ OR-Set (เซตที่มีการติดตามการเพิ่มและการลบ) แต่ละชนิดรับประกันว่าการรวมสองเรพลิกาจะไม่สร้างข้อขัดแย้ง ตามการวิจัยของ INRIA (Marc Shapiro et al., 2024) CRDT ให้การลู่เข้าแบบกำหนดได้สำหรับ 95% ของชนิดข้อมูลทั่วไป
ตัวอย่าง G-Counter — ตัวนับที่สามารถเพิ่มได้เท่านั้น:
class GCounter {
private val counts = mutableMapOf<String, Int>()
fun increment(nodeId: String) {
counts[nodeId] = (counts[nodeId] ?: 0) + 1
}
fun value(): Int = counts.values.sum()
fun merge(other: GCounter) {
other.counts.forEach { (node, count) ->
counts[node] = maxOf(counts[node] ?: 0, count)
}
}
}
GCounter รับประกันการรวมที่ถูกต้องเนื่องจากแต่ละโหนดจัดเก็บเฉพาะตัวนับของตนเอง และการรวมใช้ค่าสูงสุดต่อโหนด นี่เป็นตัวอย่างคลาสสิกของโครงสร้างที่ปราศจากข้อขัดแย้งที่ใช้ในระบบกระจายอำนาจ
การเลือกกลยุทธ์ ขึ้นอยู่กับลักษณะของข้อมูลและสถานการณ์การใช้งาน LWW เหมาะสมที่สุดสำหรับแอปพลิเคชันที่เวอร์ชันล่าสุดมีลำดับความสำคัญเสมอ — ฟีดข่าว การแจ้งเตือน สถานะ กลยุทธ์การรวมเหมาะสำหรับเอกสารที่มีโครงสร้างซึ่งแต่ละฟิลด์เป็นอิสระ — โปรไฟล์ผู้ใช้ แบบฟอร์ม การกำหนดค่า CRDT เหมาะสำหรับการแก้ไขร่วมกัน รายการ และตัวนับในระบบกระจาย
เมื่อเลือกกลยุทธ์ จะประเมินสามปัจจัย: ความสอดคล้องของข้อมูล ประสิทธิภาพ และความซับซ้อนในการนำไปใช้ LWW ให้ประสิทธิภาพสูงสุดและความซับซ้อนต่ำที่สุดแต่อาจสูญเสียข้อมูล Merge ให้ความแม่นยำสูงแต่ต้องการกลไกตรวจจับการเปลี่ยนแปลงในระดับฟิลด์ CRDT รับประกันความถูกต้องทางคณิตศาสตร์แต่จำกัดชนิดข้อมูลและขนาดเมทาดาทา
| กลยุทธ์ | การสูญเสียข้อมูล | ความซับซ้อน | ประสิทธิภาพ | กรณีการใช้งาน |
|---|---|---|---|---|
| LWW | เป็นไปได้ | ต่ำ | สูง | ฟีดข่าว สถานะ |
| Merge | น้อยที่สุด | ปานกลาง | ปานกลาง | โปรไฟล์ เอกสาร |
| CRDT | ไม่มี | สูง | ปานกลาง-สูง | การแก้ไขร่วมกัน |
ในทางปฏิบัติ มักใช้แนวทางแบบผสมผสาน: ระบบใช้ LWW สำหรับเมทาดาทา Merge สำหรับเนื้อหาเอกสาร และ CRDT สำหรับโครงสร้างรายการ ตัวอย่างเช่น Firebase Firestore ใช้ LWW สำหรับฟิลด์ระดับบนสุดและสนับสนุนธุรกรรมสำหรับการอัปเดตแบบอะตอมมิก CouchDB ใช้ Merge กับการจัดเก็บประวัติการเปลี่ยนแปลง Figma และ Notion สร้างสถาปัตยกรรมบน CRDT สำหรับการแก้ไขแบบหลายผู้ใช้ในเวลาจริง
คำถามที่พบบ่อย
การแก้ไขข้อขัดแย้ง เป็นกลไกที่กำหนดว่าข้อมูลเวอร์ชันใดถูกต้องเมื่อวัตถุเดียวกันถูกเปลี่ยนแปลงพร้อมกันบนอุปกรณ์ต่างๆ ระบบใช้กลยุทธ์ (LWW, Merge, CRDT) เพื่อเลือกหรือรวมเวอร์ชัน
LWW เลือกหนึ่งเวอร์ชันที่สมบูรณ์ตามการประทับเวลา อีกเวอร์ชันถูกทิ้ง การรวม รวมการเปลี่ยนแปลงจากทั้งสองเวอร์ชันในระดับฟิลด์แต่ละรายการ ลดการสูญเสียข้อมูลแต่ต้องการการนำไปใช้ที่ซับซ้อนกว่าและการจัดเก็บเวอร์ชันพื้นฐาน
CRDT ถูกเลือกสำหรับสถานการณ์ที่การสูญเสียข้อมูลไม่สามารถยอมรับได้: การแก้ไขร่วมกัน การดำเนินการทางการเงิน รายการงาน LWW เพียงพอสำหรับข้อมูลที่ไม่สำคัญ — สถานะ ฟีดข่าว แคช ซึ่งเวอร์ชันล่าสุดถูกต้องตามวัตถุประสงค์
การแก้ไขข้อขัดแย้งที่ไม่ถูกต้อง ทำให้เกิดการสูญเสียข้อมูลผู้ใช้ นำไปสู่รีวิวเชิงลบและการเลิกใช้งาน ตามการศึกษาของมหาวิทยาลัย Washington (2025) 67% ของผู้ใช้หยุดใช้แอปพลิเคชันหลังจากสูญเสียข้อมูลที่ป้อนไปสองครั้งเนื่องจากข้อขัดแย้งในการซิงโครไนซ์
CouchDB และ PouchDB มีการสนับสนุนในตัวสำหรับการรวมสามทางของเอกสาร Firebase Firestore สนับสนุนธุรกรรมสำหรับการอัปเดตแบบอะตอมมิก RethinkDB และ MongoDB ต้องการการนำไปใช้ในระดับแอปพลิเคชันผ่านรูปแบบการล็อกแบบมองโลกในแง่ดีพร้อมการกำหนดเวอร์ชัน
สรุป
เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร
IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ
อ่านเพิ่มเติม