• ClickHouse เปิดตัวชนิด JSON ใหม่ที่นำค่าตามพาธ JSON ไปวางไว้ใน ที่เก็บข้อมูลแบบคอลัมน์จริง เพื่อหลีกเลี่ยงคอขวดจากการใส่เอกสาร JSON เป็นสตริงแล้วต้อง parse ทุกครั้ง
  • แกนหลักของการใช้งานคือชนิด Variant และ Dynamic โดยแม้พาธ JSON เดียวกันจะมีชนิดต่างกัน เช่น จำนวนเต็ม สตริง หรืออาร์เรย์ ก็จะไม่ฝืนรวมให้เป็นชนิดร่วมต่ำสุด
  • จำกัดจำนวนซับคอลัมน์และจำนวนไฟล์แยกตามชนิดด้วยค่าเริ่มต้น max_dynamic_paths ที่ 1024 และ max_dynamic_types ที่ 32 เพื่อควบคุมการเพิ่มขึ้นของ file descriptor และต้นทุนการ merge
  • สามารถปรับวิธีจัดเก็บรายพาธได้ด้วย type hint, SKIP, SKIP REGEXP และอ่านค่าด้วย ไวยากรณ์ซับคอลัมน์ เช่น C.a.b
  • ชนิดใหม่นี้มีเป้าหมายเพื่อแทนที่ Object('json') ที่ถูก deprecated แล้ว และยังมีการปรับปรุงใน roadmap สำหรับการใช้พาธคีย์ JSON ใน primary key หรือ data-skipping index

ความท้าทายในการปรับ JSON ให้เข้ากับที่เก็บข้อมูลแบบคอลัมน์

  • JSON ถูกใช้เป็นรูปแบบร่วมสำหรับจัดการข้อมูลกึ่งมีโครงสร้างและไม่มีโครงสร้างใน log, observability, การสตรีมข้อมูลแบบเรียลไทม์, ที่เก็บข้อมูลของแอปมือถือ และ pipeline ด้าน machine learning
  • ClickHouse เป็น ฐานข้อมูลแบบคอลัมน์อย่างแท้จริง โดยจัดเก็บตารางเป็นชุดไฟล์ข้อมูลคอลัมน์บนดิสก์ เพื่อทำ compression รวมถึง filter และ aggregation แบบ vectorized
  • เพื่อให้ JSON ได้ประสิทธิภาพแบบเดียวกัน ไม่ควรเก็บเอกสารไว้ในคอลัมน์สตริงแล้วค่อย parse ภายหลัง แต่ควรเก็บค่าของ พาธ JSON ที่ไม่ซ้ำกันแต่ละพาธ เหมือนเป็นคอลัมน์

ข้อจำกัดสี่อย่างที่ชนิด JSON ใหม่จัดการ

  • การจัดเก็บแบบคอลัมน์รายพาธ

    • ค่าตามพาธ JSON ก็ควรถูกบีบอัด และนำไป filter/aggregate แบบ vectorized ได้เหมือนคอลัมน์ทั่วไป เช่น คอลัมน์ชนิดตัวเลข
  • ชนิดข้อมูลที่เปลี่ยนแบบไดนามิก

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

    • หากสร้างไฟล์คอลัมน์ใหม่สำหรับทุกพาธ JSON ใหม่ จำนวนไฟล์บนดิสก์จะเพิ่มขึ้นอย่างรวดเร็วในข้อมูลที่มีคีย์ไม่ซ้ำกันจำนวนมาก
    • file descriptor ใช้หน่วยความจำ และเมื่อมีไฟล์ที่ต้องประมวลผลมากขึ้น ก็ส่งผลต่อประสิทธิภาพการ merge ด้วย
  • การจัดเก็บคีย์แบบ sparse ให้แน่น

    • เมื่อมีคีย์ JSON ที่ไม่ซ้ำกันแต่ปรากฏแบบ sparse จำนวนมาก ไม่ควรบันทึก NULL หรือค่าเริ่มต้นซ้ำในทุกแถวที่ไม่มีค่า
    • ต้อง จัดเก็บแบบหนาแน่น เฉพาะค่าที่มีจริง จึงจะขยายไปสู่การวิเคราะห์ระดับ PB ได้

ชนิด Variant: ฐานที่ไม่ฝืนรวมชนิดข้อมูล

  • ชนิดข้อมูล Variant เป็นฟีเจอร์อิสระที่ใช้ได้แยกจาก JSON และสามารถจัดเก็บและอ่านค่าชนิดข้อมูลต่างกันภายในคอลัมน์เดียวของตาราง
  • คอลัมน์ ClickHouse เดิมมีชนิดตายตัว และค่าที่ insert ต้องเป็นชนิดนั้น หรือถูกแปลงโดยนัย
    • คอลัมน์ Nullable ใช้ ไฟล์ NULL mask นอกเหนือจากไฟล์ค่า
    • Array เก็บขนาดอาร์เรย์ไว้ในไฟล์แยก แล้วใช้สิ่งนี้คำนวณ offset
  • คอลัมน์ Variant เก็บค่าที่มีชนิดจริงเหมือนกันไว้ใน ซับคอลัมน์ แยกตามชนิด
    • ตัวอย่าง: ค่า Int64 ทั้งหมดถูกเก็บใน C.Int64.bin และค่า String ทั้งหมดถูกเก็บใน C.String.bin
  • แต่ละแถวใช้ชนิดใดจะถูกติดตามด้วยคอลัมน์ discriminator แบบ UInt8
    • ค่า discriminator คือดัชนีของรายการชื่อชนิดที่เรียงลำดับแล้ว
    • discriminator 255 เป็นค่าที่สงวนไว้สำหรับ NULL
    • ด้วยการออกแบบนี้ Variant จึงมีชนิดจริงได้สูงสุด 255 ชนิด
  • ไฟล์ข้อมูลแยกตามชนิดเป็นโครงสร้าง จัดเก็บแบบหนาแน่น ที่เก็บเฉพาะแถวที่มีค่า
    • ไฟล์แยกตามชนิดไม่เก็บค่า NULL
    • ใช้คอลัมน์ offset แบบ UInt64 ในหน่วยความจำ เพื่อหาตำแหน่งแถวในไฟล์ชนิดจริงจากแถว discriminator
    • offset นี้ไม่ได้เก็บบนดิสก์ และสามารถสร้างขึ้นทันทีจากไฟล์คอลัมน์ discriminator ได้
  • Variant รองรับการซ้อนกันได้ตามอำเภอใจ
    • ลำดับชนิดของ Variant(T1, T2) และ Variant(T2, T1) มีความหมายเหมือนกัน
    • สามารถใส่ Variant ซ้อนอยู่ภายใน Variant ได้อีก
  • อ่านค่าของชนิดที่ซ้อนอยู่เฉพาะชนิดได้ด้วยการต่อชื่อชนิดเหมือนเป็นซับคอลัมน์
    • ตัวอย่าง: C.Int64

ชนิด Dynamic: จัดเก็บได้แม้ไม่รู้รายการชนิดล่วงหน้า

  • ชนิด Dynamic เป็นฟีเจอร์อิสระที่สร้างบน Variant และใช้ได้นอกบริบท JSON เช่นกัน
  • Dynamic เพิ่มความสามารถสองอย่างให้ Variant
    • เก็บค่าของ ชนิดใดก็ได้ ในคอลัมน์เดียว โดยไม่ต้องระบุรายการชนิดล่วงหน้า
    • จำกัดจำนวนชนิดที่จะเก็บเป็นไฟล์ข้อมูลคอลัมน์แยกได้
  • โครงสร้างภายในเหมือน Variant แต่มีไฟล์ C.dynamic_structure.bin เพิ่มเข้ามา
    • ไฟล์นี้มีรายการชนิดที่ถูกเก็บเป็นซับคอลัมน์ และสถิติขนาดไฟล์ข้อมูลคอลัมน์แยกตามชนิด
    • metadata นี้ใช้สำหรับการอ่านซับคอลัมน์และการ merge data part
  • Dynamic(max_types=N) จำกัดจำนวนชนิดที่จะเก็บเป็นไฟล์แยก
    • 0 <= N < 255
    • ค่าเริ่มต้นคือ 32
  • เมื่อถึงขีดจำกัด ค่าชนิดอื่นที่เหลือจะถูกเก็บในไฟล์คอลัมน์เดียว เช่น C.SharedVariant.bin
    • ชนิดของไฟล์นี้คือ String
    • แต่ละแถวมีค่าสตริงในรูปแบบ <binary_encoded_data_type><binary_value>
    • สามารถเก็บค่าหลายชนิดไว้ในไฟล์คอลัมน์เดียวแล้วอ่านกลับได้
  • Dynamic ก็สามารถใช้ชื่อชนิดเป็นซับคอลัมน์เพื่ออ่านค่าชนิดเฉพาะได้เหมือน Variant
    • ตัวอย่าง: C.Int64

การประกาศชนิด JSON และโครงสร้างการจัดเก็บ

  • ชนิด JSON ใหม่ใช้เก็บ object JSON ที่มีโครงสร้างใดก็ได้ และทำให้สามารถอ่านค่า JSON แต่ละค่าด้วยซับคอลัมน์ตามพาธ
  • การประกาศชนิดสามารถมีพารามิเตอร์และ hint แบบเลือกได้
<column_name> JSON(
    max_dynamic_paths=N,
    max_dynamic_types=M,
    some.path TypeName,
    SKIP path.to.skip,
    SKIP REGEXP 'paths_regexp')
  • max_dynamic_paths
    • ค่าเริ่มต้นคือ 1024
    • กำหนดจำนวนพาธคีย์ JSON ที่จะเก็บเป็นซับคอลัมน์แยก
    • พาธที่เกินขีดจำกัดจะถูกเก็บรวมกันในซับคอลัมน์เดียวที่มีโครงสร้างพิเศษ
  • max_dynamic_types
    • ค่าเริ่มต้นคือ 32
    • ช่วงค่าคือ 0 ถึง 254
    • กำหนดจำนวนชนิดข้อมูลที่จะเก็บเป็นไฟล์ข้อมูลคอลัมน์แยกภายในคอลัมน์พาธคีย์ JSON หนึ่งพาธ
    • ชนิดใหม่ที่เกินขีดจำกัดจะถูกเก็บรวมกันในไฟล์ข้อมูลคอลัมน์เดียวที่มีโครงสร้างพิเศษ
  • some.path TypeName
    • เป็น type hint สำหรับพาธ JSON เฉพาะ
    • พาธนั้นจะถูกเก็บเป็นซับคอลัมน์ของชนิดที่ระบุเสมอ เพื่อให้รับประกันประสิทธิภาพ
  • SKIP path.to.skip
    • ข้ามพาธ JSON เฉพาะระหว่างการ parse
    • พาธนั้นจะไม่ถูกเก็บในคอลัมน์ JSON
    • หากพาธที่ระบุเป็น object JSON ที่ซ้อนกัน object ที่ซ้อนอยู่ทั้งหมดจะถูกข้าม
  • SKIP REGEXP 'path_regexp'
    • ข้ามพาธที่ตรงกับ regular expression ระหว่างการ parse JSON
    • พาธที่ตรงกันจะไม่ถูกเก็บในคอลัมน์ JSON

วิธีอ่านพาธ JSON เหมือนคอลัมน์

  • ค่า leaf path ที่ไม่ซ้ำกันแต่ละพาธของคอลัมน์ JSON จะถูกเก็บบนดิสก์ด้วยหนึ่งในสองวิธี
    • พาธที่มี type hint จะถูกเก็บเป็นไฟล์ข้อมูลคอลัมน์ทั่วไป
    • พาธที่ชนิดอาจเปลี่ยนแบบไดนามิกจะถูกเก็บเป็น ซับคอลัมน์ Dynamic
  • ชนิด JSON ใช้ไฟล์พิเศษชื่อ object_structure
    • มี metadata สำหรับพาธแบบไดนามิก
    • มีสถิติของค่าที่ไม่ใช่ null สำหรับแต่ละพาธแบบไดนามิก
    • ใช้สำหรับการอ่านซับคอลัมน์และการ merge data part
  • การเพิ่มขึ้นของไฟล์คอลัมน์ถูกควบคุมด้วยขีดจำกัดสองชั้น
    • max_dynamic_types จำกัดจำนวนชนิดที่จะเก็บเป็นไฟล์แยกภายในพาธคีย์ JSON หนึ่งพาธ
    • max_dynamic_paths จำกัดจำนวนพาธคีย์ JSON ที่จะเก็บเป็นซับคอลัมน์แยก
  • พาธ JSON แบบไดนามิกเพิ่มเติมที่เกินขีดจำกัด max_dynamic_paths จะถูกเก็บเป็น shared data
    • ตัวอย่างไฟล์คือ C.object_shared_data.size0.bin, C.object_shared_data.paths.bin, C.object_shared_data.values.bin
    • object_shared_data.values มีชนิดเป็น String
    • แต่ละ entry มีโครงสร้าง <binary_encoded_data_type><binary_value>
  • สำหรับ shared data ก็มีการเก็บสถิติเพิ่มเติมใน object_structure.bin เช่นกัน
    • ปัจจุบันจะเก็บสถิติค่าที่ไม่ใช่ null สำหรับพาธ 10000 พาธแรก ในบรรดาพาธที่เก็บอยู่ในคอลัมน์ shared data

ไวยากรณ์พาธ JSON และ object ที่ซ้อนกัน

  • ชนิด JSON สามารถอ่านค่า leaf ของแต่ละพาธเป็นซับคอลัมน์ตามชื่อพาธได้
    • ตัวอย่าง: C.a.b
  • ค่าของพาธที่ไม่ได้ระบุ type hint จะมีชนิดเป็น Dynamic เสมอ
    • ตัวอย่าง: ชนิดของ C.a.d คือ Dynamic
  • ซับคอลัมน์ของชนิดย่อยใน Dynamic อ่านด้วยไวยากรณ์ JSON พิเศษ
    • ตัวอย่าง: C.a.d.:Int64
  • object JSON ที่ซ้อนกันสามารถอ่านเหมือนซับคอลัมน์ชนิด JSON ด้วยไวยากรณ์ JSON_column.^some.path
    • ตัวอย่าง: C.^a
  • ปัจจุบันไวยากรณ์ dot จะไม่อ่าน object ที่ซ้อนกันด้วยเหตุผลด้านประสิทธิภาพ
    • โครงสร้างการจัดเก็บปัจจุบันมีประสิทธิภาพสำหรับการอ่านค่าลิเทอรัลรายพาธ
    • หากต้องการอ่าน object ย่อยทั้งก้อนตามพาธ จะต้องอ่านข้อมูลมากขึ้นและอาจช้าลง
    • การคืนค่า object ต้องใช้ไวยากรณ์ .^
    • ClickHouse วางแผนจะรวมไวยากรณ์ . สองแบบเข้าด้วยกัน

การ serialize discriminator แบบ compact

  • พาธ JSON แบบไดนามิกจำนวนมากอาจมีชนิดค่าที่ส่วนใหญ่เหมือนกัน
  • หากมีพาธ JSON ที่ไม่ซ้ำกันแต่ sparse จำนวนมาก ไฟล์ discriminator ของแต่ละพาธจะมีค่า 255 หรือ NULL เป็นหลัก
  • ไฟล์ลักษณะนี้บีบอัดได้ดี แต่หากค่าของทุกแถวเหมือนกัน ก็ยังมีข้อมูลซ้ำจำนวนมากอยู่ดี
  • ClickHouse จึง implement compact format สำหรับการ serialize discriminator
    • โดยทั่วไป แทนที่จะเขียนค่า discriminator แบบ UInt8 ทั้งหมด หาก discriminator ของ granule เป้าหมายเหมือนกันทั้งหมด จะ serialize เพียง 3 ค่า
    • ตัวระบุ compact granule format
    • ตัวระบุจำนวนค่าของ granule นั้น
    • ค่า discriminator
  • การ optimize นี้ควบคุมด้วยการตั้งค่า MergeTree use_compact_variant_discriminators_serialization
    • ค่าเริ่มต้นคือเปิดใช้งาน
    • จึงอาจมีกรณีที่เก็บเพียง 3 ค่า แทน 8192 ค่า ตาม index granularity ทั่วไป

สถานะการปล่อยและขั้นตอนถัดไป

  • ชนิด JSON ใหม่ถูกออกแบบมาเพื่อแทนที่ชนิด Object('json') ที่ถูก deprecated แล้ว
  • การ implement นี้มีให้ใช้งานเป็นฟีเจอร์ experimental สำหรับการทดสอบใน ClickHouse 24.08 release
  • JSON roadmap มีการปรับปรุงสำหรับการใช้พาธคีย์ JSON ภายใน primary key ของตารางหรือ data-skipping index
  • ส่วนประกอบอย่าง Variant และ Dynamic จะเป็นฐานสำหรับรองรับชนิดกึ่งมีโครงสร้างเพิ่มเติม เช่น XML และ YAML นอกเหนือจาก JSON
  • ผู้ใช้ ClickHouse Cloud ที่ต้องการทดสอบชนิดข้อมูล JSON ใหม่ควรติดต่อ ทีมสนับสนุน ClickHouse เพื่อขอสิทธิ์เข้าถึง private preview

ยังไม่มีความคิดเห็น

ยังไม่มีความคิดเห็น