- Apache Fory Rust คือ เฟรมเวิร์กซีเรียลไลซ์ข้ามภาษา ที่มอบ ประสิทธิภาพการซีเรียลไลซ์ความเร็วสูงมาก และ การจัดการรีเฟอเรนซ์อัตโนมัติ
- สร้างบนพื้นฐานของ เทคโนโลยี zero-copy และ type safety ของ Rust พร้อมจัดการ circular references, trait objects และ schema evolution โดยอัตโนมัติ
- รองรับการแลกเปลี่ยนข้อมูลระหว่างภาษาอย่าง Rust, Python, Java, Go และอื่น ๆ โดยไม่ต้องมีไฟล์ IDL หรือการ generate code
- ผลการทดสอบ benchmark ระบุว่ามี ความเร็วในการประมวลผลมากกว่า JSON และ Protobuf กว่า 10–20 เท่า
- มีคุณค่าสูงสำหรับการใช้งานในสภาพแวดล้อมที่ต้องการสมรรถนะสูง เช่น microservices, data pipelines และระบบเรียลไทม์
ภาวะกลืนไม่เข้าคายไม่ออกของการซีเรียลไลซ์ และการมาของ Apache Fory Rust
- วิธีซีเรียลไลซ์แบบเดิมมีข้อจำกัดที่ต้องยอมเสียอย่างใดอย่างหนึ่งระหว่าง ความเร็ว ความยืดหยุ่น และความเข้ากันได้ข้ามภาษา
- ฟอร์แมตไบนารีที่ทำเองเร็วก็จริง แต่เปราะบางต่อการเปลี่ยน schema
- JSON/Protobuf ยืดหยุ่น แต่มี performance overhead มากกว่า 10 เท่า
- โซลูชันเดิม ๆ รองรับความสามารถเฉพาะภาษายังไม่ดีพอ
- Apache Fory Rust ให้ทั้งประสิทธิภาพและความยืดหยุ่นพร้อมกัน โดย ไม่จำเป็นต้องใช้ IDL หรือจัดการ schema แบบแมนนวล
คุณสมบัติหลัก
-
1. รองรับข้ามภาษาอย่างแท้จริง
- ใช้ binary protocol เดียวกันร่วมกันได้ใน Java, Python, C++, Go และภาษาอื่น ๆ
- ข้อมูลที่ซีเรียลไลซ์ใน Rust สามารถนำไป deserialize ใน Python ได้โดยตรง
- ไม่มีปัญหาเรื่องไฟล์ schema, การ generate code หรือ version mismatch ช่วยให้การแลกเปลี่ยนข้อมูลระหว่าง microservices หลายภาษาง่ายขึ้น
-
2. จัดการ circular/shared references อัตโนมัติ
- ติดตามและคงโครงสร้าง circular references ที่เฟรมเวิร์กส่วนใหญ่มักจัดการไม่ได้โดยอัตโนมัติ
- แม้อ้างอิงอ็อบเจ็กต์เดียวกันหลายครั้ง ก็จะ ซีเรียลไลซ์เพียงครั้งเดียว และคงเอกลักษณ์ของรีเฟอเรนซ์ไว้
- เหมาะกับ graph databases, ORM และโดเมนโมเดลที่ซับซ้อน
-
3. การซีเรียลไลซ์ trait objects
- รองรับ การซีเรียลไลซ์ trait objects เช่น
Box ของ Rust
- สามารถลงทะเบียน polymorphic types ได้ด้วยแมโคร
register_trait_type!
- รองรับหลายรูปแบบ เช่น
Box, Rc, Arc, dyn Any
- ทำให้สร้าง plugin systems, heterogeneous collections และสถาปัตยกรรมที่ขยายต่อได้
-
4. Schema evolution (compatible mode)
- อนุญาตให้เปลี่ยน schema ระหว่างเวอร์ชันของบริการได้ด้วย Compatible mode
- เพิ่ม ลบ เปลี่ยนลำดับฟิลด์ และแปลงเป็น option type ได้
- แต่ไม่รองรับการเปลี่ยนชนิดข้อมูล
- มีประโยชน์ต่อ การ deploy แบบไม่หยุดระบบ และ การพัฒนา microservices แบบอิสระ
พื้นฐานทางเทคนิค
-
การออกแบบโปรโตคอล
- โครงสร้าง:
| fory header | reference meta | type meta | value data |
- ใช้ จำนวนเต็มความยาวแปรผัน, metadata แบบบีบอัด, การติดตามรีเฟอเรนซ์ และ little-endian layout
- เพิ่มประสิทธิภาพด้วย การตัดความซ้ำซ้อนของ shared objects และ การบีบอัด type metadata
-
การ generate code ตอน compile time
- ใช้ การ generate code ด้วยแมโครแทน reflection เพื่อตัด runtime overhead
- แมโคร
#[derive(ForyObject)] จะสร้างฟังก์ชัน serialize/deserialize ให้อัตโนมัติ
- ช่วยให้ คง type safety, ลดขนาดไบนารี และรองรับ IDE autocomplete
-
องค์ประกอบสถาปัตยกรรม
fory/: API ระดับสูง
fory-core/: เอนจินซีเรียลไลซ์ (buffer I/O, การลงทะเบียนชนิดข้อมูล, การบีบอัด metadata ฯลฯ)
fory-derive/: คำจำกัดความ procedural macro
- โครงสร้างแบบ แยกโมดูล ช่วยให้ดูแลรักษาและขยายระบบได้ง่าย
ผลการทดสอบ Benchmark
- เร็วกว่า JSON และ Protobuf มากกว่า 10–20 เท่า
- ตัวอย่าง:
simple_struct(small) → Fory 35,729,598 TPS / JSON 10,167,045 / Protobuf 8,633,342
person(medium) → Fory 3,839,656 TPS / JSON 337,610 / Protobuf 369,031
- ในทุกเคสทดสอบ Fory ทำผลงานได้ดีที่สุด
สถานการณ์การใช้งาน
-
กรณีใช้งานที่เหมาะสม
- microservices หลายภาษา: แลกเปลี่ยนข้อมูลได้โดยไม่ต้องมีไฟล์ schema
- data pipelines ประสิทธิภาพสูง: ประมวลผลได้หลายล้านเรกคอร์ดต่อวินาที
- โดเมนโมเดลที่ซับซ้อน: รองรับโครงสร้างแบบ circular references และ polymorphism
- ระบบเรียลไทม์: latency ต่ำกว่า 1ms พร้อม zero-copy deserialization
-
ทางเลือกอื่นที่ควรพิจารณา
- หากต้องการฟอร์แมตที่มนุษย์อ่านง่าย → JSON/YAML
- หากต้องการฟอร์แมตสำหรับเก็บระยะยาว → Parquet
- หากเป็นโครงสร้างข้อมูลเรียบง่าย → serde + bincode
เริ่มต้นใช้งาน
-
การติดตั้ง
-
ตัวอย่างการซีเรียลไลซ์พื้นฐาน
- ลงทะเบียน struct ด้วย
#[derive(ForyObject)] แล้วใช้ serialize() / deserialize()
- ลงทะเบียน type ID เพื่อคงความสอดคล้องของข้อมูล
-
การซีเรียลไลซ์ข้ามภาษา
- เปิดใช้งานโหมดรองรับหลายภาษาด้วยการตั้งค่า
compatible(true).xlang(true)
- รองรับการลงทะเบียนตาม ID หรือชื่อ (
register_by_namespace, register_by_name)
ชนิดข้อมูลที่รองรับ
- ชนิดพื้นฐาน: bool, จำนวนเต็ม, จำนวนทศนิยม, String
- คอลเลกชัน: Vec, HashMap, BTreeMap, HashSet, Option
- สมาร์ตพอยน์เตอร์: Box, Rc, Arc, RcWeak, ArcWeak, RefCell, Mutex
- วันที่/เวลา: ชนิดข้อมูลของ chrono
- อ็อบเจ็กต์ที่ผู้ใช้กำหนดเอง: ForyObject, ForyRow
- trait objects: Box/Rc/Arc, Rc/Arc
โรดแมป
-
สิ่งที่มีใน v0.13
- static code generation, zero-copy Row format, การติดตาม circular references, การซีเรียลไลซ์ trait objects, schema compatible mode
-
ฟีเจอร์ที่กำลังจะมา
- การซีเรียลไลซ์รีเฟอเรนซ์ข้ามภาษา, การอัปเดต Row บางส่วน
ข้อควรพิจารณาสำหรับโปรดักชัน
- ความปลอดภัยของเธรด: หลังลงทะเบียนเสร็จแล้วสามารถแชร์ผ่าน
Arc ได้ (Send + Sync)
- การจัดการข้อผิดพลาด: อิง
Result และแยกข้อผิดพลาดอย่างชัดเจน เช่น ชนิดข้อมูลไม่ตรงกันหรือบัฟเฟอร์ไม่เพียงพอ
เอกสารและชุมชน
สรุป
- Apache Fory Rust คือ เฟรมเวิร์กซีเรียลไลซ์ยุคใหม่ที่ขจัดการต้องแลกกันระหว่างประสิทธิภาพ ความยืดหยุ่น และความเข้ากันได้ข้ามภาษา
- ด้วย ระบบอัตโนมัติบนพื้นฐานแมโคร, การรองรับ trait objects และ การจัดการ circular references จึงช่วยเพิ่มประสิทธิภาพการพัฒนาได้อย่างมาก
- พร้อมนำไปใช้ได้ทันทีใน microservices, data pipelines และระบบเรียลไทม์
2 ความคิดเห็น
ประสิทธิภาพแบบนี้เป็นไปได้จริงเหรอ?
ความคิดเห็นจาก Hacker News
อยากให้โฟกัสกับการพัฒนา tooling ของเทคโนโลยีที่มีอยู่แล้วอย่าง W3C EXI(Binary XML) มากกว่าการสร้างฟอร์แมตใหม่
แค่เร็วอย่างเดียวไม่พอ และฟอร์แมตที่ไม่มี ecosystem แบบ Aeron/SBE ก็ขยายการใช้งานได้ยาก ส่วน XML มี ecosystem นี้อยู่แล้ว
อีกทั้งยังไม่สามารถแสดง object graph ที่ซับซ้อนอย่าง shared reference หรือ circular reference ได้อย่างเป็นธรรมชาติ
ฟอร์แมต Fory ถูกออกแบบมาตั้งแต่ต้นเพื่อแก้ปัญหาเหล่านี้ พร้อมรองรับความเข้ากันได้ข้ามภาษาและการวิวัฒน์ของสคีมา
กล่าวคือควรออกแบบ encoding ก่อน แล้วค่อยขยายย้อนกลับไปยังภาษาและไคลเอนต์
สงสัยว่า benchmark ยุติธรรมหรือไม่
ดูจากโค้ดลิงก์ จะเห็นว่าในกรณีที่ไม่ใช่ Fory struct มีการรวมขั้นตอน แปลง to/from เข้าไปใน serialization ด้วย
กระบวนการแปลงนี้ทำให้เกิดการคัดลอกสตริงหรือการจัดสรรอาร์เรย์ใหม่
ในระบบจริง tonic มีบัฟเฟอร์ 8KB ให้ จึงน่าจะมีประสิทธิภาพกว่า Vec::default() แบบธรรมดา
บน CPU Xeon Gold 6136 มันดูเหมือนเร็วขึ้น 10 เท่า แต่ถ้าตัดการแปลง to/from และการคัดลอก Vec ออก แล้วจองบัฟเฟอร์ 8KB ไว้ล่วงหน้า ผลจริงจะอยู่ราว 3 เท่า
benchmark ควรถูกเขียนใหม่ในสไตล์ tower service/codec ที่ไม่มีโค้ดเฉพาะของ Fory ปะปนเลย
Fory ใช้ writer pool ระหว่างการทดสอบ
ดูโค้ดที่เกี่ยวข้อง
ในระยะยาว ถ้าต้องการรักษาความเข้ากันได้ข้ามภาษา คิดว่าจำเป็นต้องมี สัญญาที่ระบุไว้ชัดเจนบนพื้นฐาน IDL
แนวทางที่เริ่มจากภาษาแล้วค่อยทำ serialization แม้จะสะดวกในช่วงแรก แต่เมื่อเวลาผ่านไปจะเปราะบางต่อการเปลี่ยนแปลงของ runtime ของภาษา
โปรเจ็กต์ภาษาเดียวอาจคงความเรียบง่ายได้โดยไม่ต้องมี IDL แต่พอเกินสามภาษา IDL จะทำหน้าที่เป็น single source of truth
Apache Fory มีแผนจะเพิ่มการรองรับ IDL แบบเลือกใช้ได้ เพื่อให้ทีมเลือกแนวทางแบบยึดภาษานำหรือยึดสคีมานำตามสถานการณ์ได้
สงสัยว่าหากไม่มีสคีมา จะรักษา shared type ข้ามภาษาอย่างไร
ในภาษาแบบมี type จะอนุมานสคีมาจากการประกาศคลาส ส่วนภาษาแบบไม่มี type จะใช้การใส่ annotation ลงในโค้ดโดยตรง
ตัวอย่าง Python ดูได้ที่นี่
ดูบล็อกโพสต์ที่เกี่ยวข้อง
สงสัยว่าทำไมถึงควรใช้ Fory แทน ฟอร์แมตแบบไม่ต้อง deserialize อย่าง CapnProto หรือ Flatbuffers
ถ้าต้องการบีบอัดก็ใช้ zstd ได้
ถึงอย่างนั้น การรองรับหลายภาษาอย่างกว้างขวาง และความสะดวกในการใช้งานของ Fory ก็น่าประทับใจ
บน Python ก็ยังชอบ dill มากกว่า เพราะมัน serialize ได้แม้กระทั่ง code object
ลิงก์ dill
ดูโค้ด benchmark
ลิงก์ตัวอย่าง
pyfory มีอัตราการบีบอัดดีกว่า cloudpickle 3 เท่า และมี ฟีเจอร์ตรวจสอบความปลอดภัย เพื่อป้องกันการโจมตีจาก malicious deserialization
ลิงก์ benchmark เป็น 404 แต่เจอลิงก์ที่ถูกต้อง
น่าเสียดายที่เปลี่ยนชื่อจาก “Fury” เป็น “Fory”
Fury เป็นชื่อที่เหมาะกับเฟรมเวิร์ก serialization เร็ว ๆ มาก
โปรโตคอลไบนารี ส่วนใหญ่มุ่งลดขนาดข้อมูล
Protobuf ใช้การบีบอัดจำนวนเต็ม(varint, zigzag)
ถ้าเทียบแค่ TPS อย่างเดียว วิธี “do nothing” ที่ส่ง C struct ตรง ๆ ก็ย่อมชนะอยู่แล้ว
มีการแสดงตารางเปรียบเทียบกับชุดข้อมูลหลายแบบ
สงสัยว่าข้อจำกัด 4096 type ของ Fory เพียงพอหรือไม่
ดูโค้ดที่เกี่ยวข้อง
ที่ผ่านมายังแทบไม่เคยเห็นกรณีที่นิยาม protocol message เกิน 4096 แบบจริงจัง
ลิงก์ Rust benchmark ให้ข้อผิดพลาด 404
จากรากเอกสาร ก็หาไดเรกทอรี benchmark ไม่เจอ