Aphipu Nongbualang
ConvoLab
Published in
3 min readFeb 21, 2019

--

HOW-TO: เปลี่ยน Type Mapping อย่างไรใน Elasticsearch ให้ไม่กระทบ Production

สวัสดีครับผู้อ่านทุกท่าน วันนี้ผมจะมาแชร์ปัญหาหนึ่งที่เคยเจอ จากการใช้ Elasticsearch ครับ โดยปัญหาที่ผมเจอนั้นมี Error หน้าตาแบบนี้ครับ

Error จาก Kibana

บางคนอาจจะรู้แล้วว่าสาเหตุของปัญหาคืออะไร แต่บางคนอาจจะยังไม่รู้ เรามาลองดูกันครับว่า ผมทำอะไรลงไปถึงเกิด Error นี้

สมมติว่าก่อนหน้านี้ผมต้องการจะเก็บข้อมูล type user ลงใน index ชื่อ my_index โดย document นั้น มี 2 field คือ field name กับ field foo ผมสามารถทำได้ง่ายๆ โดย

create new document type user id 1

แต่แล้วอยู่มาวันหนึ่ง มีความต้องการบางอย่าง ที่จะเปลี่ยนค่าใน field foo จากที่เคยเก็บค่า 0.5 ไว้ ไปเป็นเก็บค่า boolean true แทน ผมจึง

update existing document type user id1

ซึ่งผลลัพธ์ที่ได้ก็คือ Error ด้านบนสุดครับ

ถึงตรงนี้หลายๆคนคงรู้แล้วว่าสาเหตุเกิดจาก การแก้ไขข้อมูล document ไม่ตรงกับ mapping ของ type user นั่นเอง เพราะตอนแรกที่เรา create document type user id1 นั้น Elasticsearch ได้สร้าง mapping ไว้สำหรับ type นั้นไว้แล้วเป็นดังนี้

mapping ของ my_index ที่บอก type ของ user.foo เป็น float

อธิบายเพิ่มเติมสำหรับตัว mapping ที่จริงมันคือ schema definition ซึ่งจะใช้ในการ search document นั่นเอง

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

แล้วเราสามารถเปลี่ยน mapping type ได้ไหม คำตอบคือเราสามารถทำได้แค่การ re-index เท่านั้น ไม่สามารถเปลี่ยน mapping ได้

ซึ่งการ re-index เป็นการ copy document ทั้งหมดจาก index หนึ่งมายังอีก index หนึ่ง

ก่อนอื่นเลยเราสร้าง index ใหม่มาชื่อ my_index_v1 โดยการ PUT mapping ให้กับมันตั้งแต่เริ่ม โดยหยิบ mapping อันเก่ามาแล้ว แก้type ของ foo เป็น boolean

mapping ของ my_index_v1

จากนั้นทำการ re-index เพื่อ ย้ายข้อมูล document ทั้งหมด ไม่ว่า type message, bot หรือ user โดยใช้คำสั่ง

re-index my_index to my_index_v1

ขั้นตอนนี้อาจจะเจอ warning เนื่องจาก user.foo ที่ copy มาเป็น float แต่เราสามารถ update หลังจากนั้นได้ ปกติ

update user id 1 ที่ re-index มาจาก my_index ด้วย field foo type ใหม่(boolean)ที่ต้องการ

จากนั้นเราจึง ลบ my_index ทิ้ง, สร้างใหม่ด้วย mapping ชุดใหม่ แล้ว re-index จาก my_index_v1 กลับไปทับ my_index ที่สร้างมาใหม่ เพื่อที่เราจะได้ใช้ ชื่อ my_index เหมือนเดิม

แต่การทำแบบนี้มีข้อเสีย คือ หลังจากนี้ทุกครั้งที่เราจะre-index เราจะต้องมีช่วงเวลาที่ไม่สามารถเข้าถึง my_index ได้เป็นเวลาสั้นๆๆ

ดังนั้นเราจึงขอนำเสนอการใช้ alias เพื่อให้การ re-index ของเราเป็นไปได้อย่างราบลื่นไม่กระทบ production

หลังจากเราสร้าง my_index_v1 ด้วย mapping และ update ข้อมูลแบบที่เราต้องการ เสร็จเรียบร้อบแล้ว ก่อนอื่นให้เราลบ index เก่า (my_index) ทิ้งเพราะเราจะใช้ชื่อของมันเป็น alias

จากนั้นเราจึงสร้าง alias ของ index my_index_v1 ด้วยชื่อ my_index ด้วยคำสั่ง

การตั้งชื่อเล่น alias ให้กับ index

เป็นอันเสร็จพิธี หลังจากนี้ เราสามารถเรียกใช้ชื่อ my_index แทน my_index_v1 ได้เลยโดยที่เราไม่ต้องกันการเข้าถึง my_index และ application ไม่ต้องรับรู้ถึงการเปลี่ยนแปลงด้วย

แล้วถ้าเราต้องการเปลี่ยน type อีกล่ะ? เราก็สร้าง my_index_v2 ด้วย mapping ใหม่, re-index, update docs. แล้วจึงแก้ไข alias อีกครั้งด้วยคำสั่ง

เพื่อลบ symbolic link ที่เคยชี้ไป my_index_v1 มาชี้ที่ my_index_v2 แทนนั่นเอง

ตรงนี้ผมก็ขอจบการเปลี่ยน mapping ด้วยวิธีการ re-index แบบใช้ alias ไว้แต่เพียงเท่านี้นะครับ อาจจะมีวิธีที่ดีกว่านี้หรือ มีจุดที่สามารถปรับให้ดีขึ้นได้ก็อยากให้ช่วยแชร์ช่วยแนะนำกันได้เลยครับ ขอบคุณครับ

--

--