- JSON ซึ่งกลายเป็นมาตรฐานของเว็บ API นั้นอ่านง่ายและยืดหยุ่น แต่มีข้อจำกัดด้านประสิทธิภาพและความเสถียร
- Protobuf (Protocol Buffers) รับประกันโครงสร้างข้อมูลได้อย่างชัดเจนผ่าน การกำหนดชนิดข้อมูลที่เข้มงวด และ การสร้างโค้ดอัตโนมัติ
- ใช้ การซีเรียลไลซ์แบบไบนารี ทำให้ ลดขนาดข้อมูลและเพิ่มความเร็วในการส่งได้ มากกว่า JSON ราว 3 เท่า
- เซิร์ฟเวอร์และไคลเอนต์แชร์ สคีมา
.proto เดียวกัน จึงไม่ต้องกังวลเรื่องชนิดข้อมูลไม่ตรงกันหรือการตรวจสอบแบบแมนนวล
- แม้จะดีบักได้ยากกว่า แต่ในด้าน ประสิทธิภาพ การดูแลรักษา และประสิทธิผลในการพัฒนา Protobuf เหมาะกับ API ยุคใหม่มากกว่า
ความแพร่หลายและข้อจำกัดของ JSON
- JSON เป็น ฟอร์แมตข้อความที่มนุษย์อ่านได้ง่าย และสามารถตรวจดูข้อมูลได้ด้วย
console.log() แบบง่าย ๆ
- ด้วย การทำงานร่วมกับเว็บได้อย่างสมบูรณ์ จึงถูกใช้อย่างแพร่หลายใน JavaScript และเฟรมเวิร์กฝั่งแบ็กเอนด์จำนวนมาก
- มอบ ความยืดหยุ่น ในการเพิ่ม-ลบฟิลด์หรือเปลี่ยนชนิดข้อมูลได้อย่างอิสระ แต่ก็ทำให้มีโอกาสเกิดโครงสร้างไม่ตรงกันหรือข้อผิดพลาดได้
- มี ecosystem ของเครื่องมือที่สมบูรณ์ ทำให้จัดการได้ง่ายแม้มีเพียง text editor หรือ
curl
- อย่างไรก็ตาม แม้จะมีข้อดีเหล่านี้ ก็ยังมีทางเลือกที่ดีกว่าในด้าน ประสิทธิภาพและความปลอดภัยของชนิดข้อมูล
ภาพรวมของ Protobuf
- เป็น ฟอร์แมตการซีเรียลไลซ์แบบไบนารี ที่ Google พัฒนาขึ้นในปี 2001 และเปิดเผยสู่สาธารณะในปี 2008
- ถูกใช้อย่างแพร่หลายภายในระบบและในการ สื่อสารระหว่างไมโครเซอร์วิส
- มักมีความเข้าใจผิดว่า ต้องใช้ร่วมกับ gRPC เสมอ แต่จริง ๆ แล้ว Protobuf สามารถใช้กับ HTTP API ได้โดยอิสระ
- ในช่วงแรกเข้าถึงได้ยากเพราะ ฟอร์แมตไบนารีมองไม่เห็นได้โดยตรง แต่มีจุดแข็งมากในด้านประสิทธิภาพและความเสถียร
ระบบชนิดข้อมูลที่แข็งแรงและการสร้างโค้ด
ประสิทธิภาพของการซีเรียลไลซ์แบบไบนารี
- Protobuf ซีเรียลไลซ์เป็น ข้อมูลไบนารีแทนข้อความ จึง กะทัดรัดและรวดเร็วมาก
- เปรียบเทียบขนาดของข้อมูลเดียวกัน (ออบเจ็กต์
User):
- JSON: 86 ไบต์ (68 ไบต์เมื่อเอาช่องว่างออก)
- Protobuf: 30 ไบต์
- สาเหตุของประสิทธิภาพ:
- ใช้ varint encoding กับตัวเลข
- ใช้ แท็กตัวเลขแทนคีย์ข้อความ
- ตัดช่องว่างและไวยากรณ์ที่ไม่จำเป็นออก
- มี การปรับให้เหมาะกับฟิลด์แบบเลือกได้
- ผลลัพธ์คือช่วย ลดแบนด์วิดท์, เพิ่มความเร็วในการตอบสนอง, ประหยัดดาต้ามือถือ และ ปรับปรุงประสบการณ์ผู้ใช้
ตัวอย่าง Protobuf API บนพื้นฐาน Dart
- ใช้แพ็กเกจ
shelf เพื่อสร้าง HTTP server แบบง่าย และส่งคืนออบเจ็กต์ User ในรูปแบบ Protobuf
- แกนหลักของโค้ดฝั่งเซิร์ฟเวอร์:
- สร้างออบเจ็กต์
User() แล้วซีเรียลไลซ์ด้วย writeToBuffer()
- กำหนด response header เป็น
'content-type': 'application/protobuf'
- ฝั่งไคลเอนต์ใช้แพ็กเกจ
http และ user.pb.dart เพื่อ ดีโค้ดข้อมูล Protobuf ได้โดยตรง
- เนื่องจากเซิร์ฟเวอร์และไคลเอนต์แชร์สคีมา
.proto เดียวกัน จึง ไม่เกิดความไม่ตรงกันของโครงสร้างข้อมูล
- แนวทางเดียวกันนี้ยังใช้ได้เหมือนกันใน Go, Rust, Kotlin, Swift, C#, TypeScript เป็นต้น
ข้อดีที่ JSON ยังมีอยู่
- Protobuf ตีความความหมายได้ยากหากไม่มีสคีมา
- เพราะจะแสดงเป็นเพียงตัวระบุแบบตัวเลขแทนชื่อฟิลด์ จึง ไม่ง่ายสำหรับมนุษย์ในการอ่าน
- ตัวอย่างเปรียบเทียบ:
- JSON:
{ "id": 42, "name": "Alice" }
- Protobuf:
1: 42, 2: "Alice"
- ดังนั้น Protobuf จึง:
- ต้องมี เครื่องมือสำหรับดีโค้ดโดยเฉพาะ
- ต้องมี การจัดการสคีมาและเวอร์ชัน อย่างจริงจัง
- ถึงอย่างนั้น ข้อดีด้านประสิทธิภาพและความมีประสิทธิผลก็ยังมากกว่ามาก
บทสรุป
- Protobuf เป็นเทคโนโลยีการซีเรียลไลซ์ที่ สุกงอมและประสิทธิภาพสูง ซึ่ง ใช้งานกับ public API ได้อย่างเพียงพอ
- สามารถทำงานได้อย่างอิสระบน HTTP API ทั่วไป โดยไม่ต้องใช้ gRPC
- เป็นเครื่องมือที่ช่วยยกระดับทั้ง ประสิทธิภาพ ความทนทาน การลดข้อผิดพลาด และประสิทธิผลในการพัฒนา
- สำหรับโปรเจกต์รุ่นถัดไป Protobuf มีคุณค่ามากพอที่จะนำมาใช้
10 ความคิดเห็น
ความเห็นจาก Hacker News
JSON มักลงเอยด้วยการส่ง ข้อมูลที่กำกวมหรือไม่รับประกันความถูกต้อง ปัญหาเกิดได้หลายแบบ เช่น ฟิลด์หาย ชนิดข้อมูลผิด คีย์สะกดผิด หรือโครงสร้างที่ไม่ได้มีเอกสารกำกับ เคยมีบทความที่อ้างว่า Protobuf ทำให้เรื่องแบบนี้เป็นไปไม่ได้ เพราะนิยามโครงสร้างข้อความไว้อย่างชัดเจนด้วยไฟล์
.protoแต่จริง ๆ แล้วนี่คือการเข้าใจแนวคิดของ Protobuf ผิดไป ในproto3นั้น ไม่รองรับ required field ตั้งแต่แรก เอกสารทางการ (Protobuf Best Practices) ก็ระบุชัดว่า “required fields were harmful and removed” สุดท้ายแล้วฝั่งไคลเอนต์ของ Protobuf ก็ยังต้องเขียนแบบป้องกันตัวเองเหมือน JSON API อยู่ดีjson:"-"โปรเจ็กต์ของฉันดูได้ที่ GooeyJSON ที่ถูกบีบอัดแล้วใช้งานได้ดีพอสมควร และมี ต้นทุนการสื่อสารช่วงเริ่มต้นต่ำ แน่นอนว่าถ้าฟิลด์หายหรือชนิดข้อมูลเปลี่ยนก็มีปัญหา แต่คนที่พยายามออกแบบโครงสร้างที่ type ครบถ้วนสมบูรณ์และสร้างกระบวนการซิงก์เวอร์ชันนั้น ส่วนใหญ่มักล้มเหลว สุดท้ายแล้ว ทางเลือกที่มีต้นทุนฝั่งมนุษย์ต่ำกว่าจะชนะ เพราะงั้น JSON จะยังไม่หายไป จนกว่าจะมีตัวแทนที่มีต้นทุนการสื่อสารระหว่างคนต่ำกว่านี้
console.log()JSON ก็จะยังไม่ถูกแทนที่Protobuf ไม่ได้สมบูรณ์แบบ ถ้าเซิร์ฟเวอร์กับไคลเอนต์ถูก deploy คนละช่วงเวลาแล้ว เวอร์ชันของสเปกไม่ตรงกัน ความปลอดภัยก็พังได้ แม้จะบรรเทาได้ด้วยการห้าม reuse ID หรือการคัดลอก unknown-field แต่ระบบกระจายตัวนั้นซับซ้อนโดยธรรมชาติ ถึงอย่างนั้น
protobuf3ก็แก้ปัญหาหลายอย่างของprotobuf2ไปมาก แต่ก่อนแยกไม่ออกว่าค่า default ถูกตั้งไว้หรือฟิลด์นั้นหายไป ตอนนี้แก้ได้ด้วยการใช้ชนิดmessageในบทความบอกว่า “มีประสิทธิภาพสูงมาก” แต่ไม่ได้พูดถึง gzip เลย ข้อมูลข้อความส่วนใหญ่ทุกวันนี้ถูก บีบอัดอัตโนมัติก่อนส่งอยู่แล้ว เพราะงั้น Protobuf ควรถูกเปรียบเทียบกับ JSON ที่บีบอัดด้วย gzip
การสนับสนุนโปรโตคอลที่ดีกว่าเป็นเรื่องดี แต่ก็ยากจะบอกว่า Protobuf มาแทน JSON ได้ทั้งด้านประสิทธิภาพและการใช้งาน เพราะ Protobuf มีสคีมาที่เข้มงวดจนพลาดพื้นที่ที่ JSON ทำได้ดี ตรงกันข้าม CBOR อาจเหมาะกว่าในฐานะตัวแทนของ JSON เพราะยืดหยุ่นแบบ JSON แต่เข้ารหัสได้กระชับกว่า
ASN.1 ในปี 1984 ทำสิ่งที่ Protobuf ทำได้อยู่แล้ว และยืดหยุ่นกว่าด้วย ถ้าใช้การเข้ารหัสแบบ DER ก็ไม่ได้แย่อะไร ดูได้จาก ตัวอย่าง ASN.1 DER Protobuf นั้น ซับซ้อนเกินไปเมื่อเทียบกับสิ่งที่มันทำได้จริง
ฉันเคยสร้างระบบ production ทั้งชุดด้วย Protobuf และพบว่า การดูแลจัดการนั้นทรมานมาก ในเชิงเทคนิคมันดูดี แต่พอใช้จริง JSON เรียบง่ายกว่ามาก
Protobuf นั้นยอดเยี่ยม แต่ก็น่าเสียดายที่ไม่รองรับ zero-copy ฟอร์แมตอย่าง Cap’n Proto ช่วยตัดคอขวดเรื่อง serialize/deserialize ได้
ฉันเคยสร้างเซิร์ฟเวอร์ในโปรเจ็กต์ NodeJS ที่นิยาม API ทั้งหมดด้วย
.protoและ ตอบกลับเป็น proto หรือ JSON ตาม Content-Type ซึ่งมีโครงสร้างดีกว่า Swagger มาก เพียงแต่น่าเสียดายที่ Google ไม่ได้ให้ความสามารถนี้มาใน ไลบรารีทางการ และ gRPC ก็ใช้งานลำบากเพราะผูกกับ HTTP/2 อนึ่ง ฉันคิดว่า Text proto คือภาษาคอนฟิกแบบ static ที่ดีที่สุดฟอร์แมตไบนารีในฝันของฉันคือแบบ อิงสคีมา แต่ฝังสคีมาไว้ในตัวข้อความด้วย แบบนี้จะเปิดอ่านตรง ๆ ด้วยปลั๊กอิน vim ได้เลย เวลาจัดการอ็อบเจ็กต์เป็นล้านชิ้น การใส่สคีมาขนาด 1KB ลงในข้อความขนาด 2GB ไม่ใช่ภาระใหญ่โตอะไร
TypeSpec
https://typespec.io/
https://msgpack.org/ อันนี้เป็นอย่างไรบ้าง?
MessagePack ก็ดีเหมือนกัน
ผมคิดว่ามันขัดแย้งกันที่จะอ้างว่าฟอร์แมตที่ไม่มีแม้แต่ตัวถอดรหัสอย่างเป็นทางการไว้ใช้ดีบักนั้นมีความสมบูรณ์成熟
"ดีบักยาก แต่"
ไม่ผ่าน
เครื่องมือทุกอย่างก็เหมือนกัน ไม่มีอะไรที่ใช้ได้สารพัดอย่าง แต่ผมก็คิดว่า Protobuf เป็นเครื่องมือที่ดีมากพอครับ
โดยเฉพาะตอนที่เคยต้องส่งข้อมูลปริมาณมากและมีความถี่สูง (20 ครั้งต่อวินาที) ไปยังหลายภาษา/ไคลเอนต์ในสภาพแวดล้อมแบบ embedded ตอนนั้นใช้ nanopb ได้อย่างเรียบร้อยมากครับ
ถ้าเข้มงวดขนาดนั้น เดี๋ยวก็กลายเป็นส่งมาแบบ XML ใช่ไหมครับ 555
ถ้ากำหนดสคีมาด้วย dtd ไว้ แล้วฝั่ง parser ทำ caching ก็เหมือนจะได้ผลเท่ากับส่งสคีมาแค่ครั้งเดียวเหมือนกันนะ
=> ยังไงก็ต้องส่งสคีมาอย่างน้อย 1 ครั้งอยู่แล้วไม่ใช่หรือ? ต่อให้เป็น JSON ก็ไม่ได้แปลว่าไม่มีสคีมา เพียงแต่สคีมาถูกรวมอยู่ในข้อมูลแบบแฝง ๆ ดังนั้นจึงไม่ใช่ว่าไม่ได้ส่งสคีมาเสียทีเดียว กลับกัน มันยิ่งไม่มีประสิทธิภาพกว่าเพราะส่งสคีมาซ้ำในแต่ละรายการด้วยซ้ำ รูปแบบที่ "อิงสคีมาและในขณะเดียวกันก็มีสคีมารวมอยู่ในข้อความด้วย" ดูจะเป็นไอเดียที่ค่อนข้างดีเลยนะ