ข้ามไปยังเนื้อหาหลัก
OpenAI

22 มกราคม 2569

วิศวกรรม

การขยายสเกล PostgreSQL เพื่อรองรับผู้ใช้ ChatGPT จำนวน 800 ล้านคน

โดย Bohan Zhang สมาชิกทีมงานด้านเทคนิค

กำลังโหลด…

เป็นเวลาหลายปีที่ PostgreSQL เป็นหนึ่งในระบบข้อมูลเบื้องหลังที่สำคัญที่สุด ซึ่งขับเคลื่อนผลิตภัณฑ์หลักอย่าง ChatGPT และ OpenAI API เมื่อฐานผู้ใช้ของเราเติบโตอย่างรวดเร็ว ความต้องการต่อฐานข้อมูลของเราก็เพิ่มขึ้นอย่างทวีคูณเช่นกัน ในช่วงปีที่ผ่านมา ปริมาณโหลดของ PostgreSQL ของเราเพิ่มขึ้นมากกว่า 10 เท่า และยังคงเพิ่มขึ้นอย่างรวดเร็ว

ความพยายามของเราในการพัฒนาโครงสร้างพื้นฐานการผลิตเพื่อรองรับการเติบโตนี้ทำให้เราได้ข้อค้นพบใหม่: PostgreSQL สามารถขยายสเกลเพื่อรองรับเวิร์กโหลดที่เน้นการอ่านเป็นหลักซึ่งมีขนาดใหญ่กว่าที่หลายคนเคยคิดว่าเป็นไปได้อย่างน่าเชื่อถือ ระบบ (ซึ่งเริ่มต้นสร้างโดยทีมของนักวิทยาศาสตร์ที่ University of California, Berkeley) ทำให้เราสามารถรองรับปริมาณการใช้งานจำนวนมหาศาลทั่วโลกได้ด้วย อินสแตนซ์เซิร์ฟเวอร์ที่ยืดหยุ่น Azure PostgreSQL(เปิดในหน้าต่างใหม่) หลักเพียงอินสแตนซ์เดียว และ read replica เกือบ 50 รายการที่กระจายอยู่ในหลายภูมิภาคทั่วโลก นี่คือเรื่องราวเกี่ยวกับการที่เราได้ขยายขนาด PostgreSQL ที่ OpenAI เพื่อรองรับคำค้นนับล้านครั้งต่อวินาทีสำหรับผู้ใช้ 800 ล้านคน ผ่านการปรับแต่งอย่างเข้มงวดและวิศวกรรมที่แข็งแกร่ง นอกจากนี้ เรายังจะกล่าวถึงประเด็นสำคัญที่เราได้เรียนรู้ตลอดเส้นทาง

รอยร้าวในงานออกแบบเบื้องต้นของเรา

หลังจากการเปิดตัว ChatGPT ปริมาณการใช้งานเติบโตขึ้นอย่างรวดเร็วในอัตราที่ไม่เคยมีมาก่อน เพื่อรองรับสิ่งนี้ เราได้ดำเนินการเพิ่มประสิทธิภาพอย่างครอบคลุมอย่างรวดเร็วทั้งในระดับแอปพลิเคชันและระดับฐานข้อมูล PostgreSQL ขยายสเกลโดยการเพิ่มขนาดของอินสแตนซ์ และขยายออกโดยการเพิ่ม read replica สถาปัตยกรรมนี้ได้ให้บริการเราอย่างดีมาเป็นเวลานาน ด้วยการปรับปรุงอย่างต่อเนื่อง จึงยังคงมีศักยภาพเพียงพอสำหรับการเติบโตในอนาคต

อาจฟังดูน่าประหลาดใจที่สถาปัตยกรรมหลักเพียงแบบเดียวสามารถตอบสนองความต้องการของ OpenAI ได้ในระดับใหญ่ แต่การทำให้ใช้งานได้จริงนั้นไม่ใช่เรื่องง่าย เราพบเห็น SEV หลายครั้งที่เกิดจากการโอเวอร์โหลดของ Postgres และมักจะมีรูปแบบเดียวกัน คือ ปัญหาจากต้นทางทำให้ภาระงานของฐานข้อมูลเพิ่มขึ้นอย่างฉับพลัน เช่น การแคชพลาดเป็นวงกว้างจากความล้มเหลวของเลเยอร์แคช การเชื่อมต่อแบบหลายทางที่ใช้ทรัพยากรมากจนทำให้ CPU ทำงานหนัก หรือการเขียนข้อมูลจำนวนมากจากการเปิดตัวฟีเจอร์ใหม่ เมื่อการใช้ทรัพยากรเพิ่มขึ้น ความหน่วงของการสืบค้นก็เพิ่มขึ้น และคำขอเริ่มหมดเวลา การลองใหม่จะยิ่งเพิ่มภาระโหลดมากขึ้น กระตุ้นให้เกิดวงจรอุบาทว์ที่อาจทำให้บริการ ChatGPT และ API ทั้งหมดเสื่อมประสิทธิภาพลง

แผนภาพการขยายสเกลการโหลด

แม้ว่า PostgreSQL จะปรับขยายได้ดีสำหรับเวิร์กโหลดที่เน้นการอ่านของเรา แต่เรายังคงพบความท้าทายในช่วงที่มีปริมาณการเขียนสูง สาเหตุหลักมาจากการใช้งานการควบคุมการทำงานพร้อมกันแบบหลายเวอร์ชัน (MVCC) ของ PostgreSQL ซึ่งทำให้มีประสิทธิภาพน้อยลงสำหรับเวิร์กโหลดที่เน้นการเขียน ตัวอย่างเช่น เมื่อมีการอัปเดตทูเพิลหรือแม้แต่ฟิลด์เดียว ระบบจะคัดลอกทั้งแถวเพื่อสร้างเวอร์ชันใหม่ ภายใต้ภาระการเขียนที่หนัก สิ่งนี้ส่งผลให้เกิดการขยายการเขียนอย่างมีนัยสำคัญ นอกจากนี้ยังเพิ่มการขยายการอ่าน เนื่องจากการค้นหาต้องสแกนผ่านหลายเวอร์ชันทูเพิล (dead tuples) เพื่อดึงข้อมูลเวอร์ชันล่าสุด MVCC นำมาซึ่งความท้าทายเพิ่มเติม เช่น การขยายขนาดของตารางและดัชนี ค่าใช้จ่ายในการบำรุงรักษาดัชนีที่เพิ่มขึ้น และการปรับแต่ง autovacuum ที่ซับซ้อน (คุณสามารถอ่านบทความแบบเจาะลึกเกี่ยวกับประเด็นเหล่านี้ได้ในบล็อกที่เขียนร่วมกับศาสตราจารย์ Andy Pavlo จาก Carnegie Mellon University ชื่อ The Part of PostgreSQL We Hate the Most(เปิดในหน้าต่างใหม่) ซึ่งอ้างอิง(เปิดในหน้าต่างใหม่)ในหน้า Wikipedia ของ PostgreSQL)

การปรับขนาด PostgreSQL ให้รองรับ QPS หลายล้าน

เพื่อลดข้อจำกัดเหล่านี้และลดแรงกดดันในการเขียน เราจึงได้ย้ายไปใช้โครงสร้างข้อมูลแบบแบ่งส่วน (shardable) และยังคงดำเนินการย้ายต่อไป (เช่น เวิร์กโหลดที่สามารถแบ่งพาร์ทิชันในแนวนอนได้) เวิร์กโหลดที่เน้นการเขียนไปยังระบบแบบแบ่งส่วน เช่น Azure Cosmos DB โดยปรับตรรกะแอปพลิเคชันให้เหมาะสมเพื่อลดการเขียนที่ไม่จำเป็น นอกจากนี้ เราไม่อนุญาตให้เพิ่มตารางใหม่ลงในการนำไปใช้งาน PostgreSQL ปัจจุบันอีกต่อไป เวิร์กโหลดใหม่จะถูกตั้งค่าเริ่มต้นให้ใช้ระบบแบบแบ่งส่วน

แม้ว่าโครงสร้างพื้นฐานของเราจะพัฒนาไปแล้ว PostgreSQL ยังคงไม่มีการแบ่งส่วน โดยมีอินสแตนซ์หลักเพียงตัวเดียวที่รองรับการเขียนทั้งหมด เหตุผลหลักคือ การทำการแบ่งส่วนให้กับเวิร์กโหลดของแอปพลิเคชันที่มีอยู่จะมีความซับซ้อนสูงและใช้เวลานาน โดยต้องปรับเปลี่ยน endpoint ของแอปพลิเคชันหลายร้อยจุด และอาจใช้เวลาหลายเดือนหรือแม้กระทั่งหลายปี เนื่องจากเวิร์กโหลดของเราส่วนใหญ่เน้นการอ่าน และเราได้ดำเนินการปรับแต่งประสิทธิภาพอย่างครอบคลุมแล้ว สถาปัตยกรรมปัจจุบันยังคงมีพื้นที่เพียงพอที่จะรองรับการเติบโตของปริมาณการใช้งานอย่างต่อเนื่อง แม้ว่าเราจะไม่ได้ตัดความเป็นไปได้ในการทำการแบ่งส่วนให้กับ PostgreSQL ในอนาคต แต่ก็ไม่ใช่สิ่งที่มีความสำคัญในระยะใกล้ เนื่องจากเรามีทรัพยากรเพียงพอสำหรับการเติบโตทั้งในปัจจุบันและอนาคต

ในส่วนถัดไป เราจะเจาะลึกถึงความท้าทายที่เราเผชิญ และการเพิ่มประสิทธิภาพอย่างครอบคลุมที่เราได้นำมาใช้เพื่อแก้ไขปัญหาเหล่านั้นและป้องกันการหยุดชะงักในอนาคต โดยผลักดัน PostgreSQL ไปจนถึงขีดจำกัดและขยายขนาดให้รองรับคำขอได้ถึงระดับหลายล้านครั้งต่อวินาที (QPS)

การลดภาระการทำงานบนระบบหลัก

ความท้าทาย: ด้วยเครื่องมือเขียนเพียงเครื่องเดียว การตั้งค่าแบบหลักเดียวไม่สามารถขยายการเขียนได้ ปริมาณการเขียนข้อมูลจำนวนมากอย่างฉับพลันอาจทำให้เซิร์ฟเวอร์หลักทำงานหนักเกินไปและส่งผลกระทบต่อบริการต่างๆ เช่น ChatGPT และ API ของเรา

วิธีแก้ปัญหา: เราลดภาระการทำงานบนระบบหลักให้น้อยที่สุดเท่าที่จะเป็นไปได้ ทั้งการอ่านและการเขียน เพื่อให้มั่นใจว่าเซิร์ฟเวอร์มีกำลังการผลิตเพียงพอที่จะรองรับปริมาณการเขียนที่เพิ่มขึ้นอย่างฉับพลัน ปริมาณการอ่านจะถูกถ่ายโอนไปยัง replica เมื่อใดก็ตามที่เป็นไปได้ อย่างไรก็ตาม คำสั่งอ่านบางคำสั่งต้องยังคงอยู่บนโหนดหลักเพราะเป็นส่วนหนึ่งของธุรกรรมการเขียน สำหรับสิ่งเหล่านั้น เรามุ่งเน้นให้มั่นใจว่ามีประสิทธิภาพและหลีกเลี่ยงการสืบค้นที่ช้า สำหรับปริมาณการเขียน เราได้ย้ายเวิร์กโหลดที่สามารถแบ่งส่วนได้และมีการเขียนจำนวนมากไปยังระบบแบบแบ่งส่วน เช่น Azure CosmosDB เวิร์กโหลดที่แบ่งส่วนได้ยากกว่า แต่ยังคงสร้างปริมาณการเขียนสูง ใช้เวลานานกว่าในการย้ายข้อมูล และกระบวนการนั้นยังคงดำเนินอยู่ เรายังได้ปรับแต่งแอปพลิเคชันของเราอย่างจริงจังเพื่อลดภาระการเขียน ตัวอย่างเช่น เราได้แก้ไขบั๊กของแอปพลิเคชันที่ทำให้เกิดการเขียนซ้ำซ้อน และนำการเขียนแบบหน่วงเวลา (lazy writes) มาใช้ในกรณีที่เหมาะสม เพื่อทำให้การจราจรที่พุ่งสูงเป็นช่วงๆ ราบรื่นขึ้น นอกจากนี้ เมื่อเติมข้อมูลย้อนหลังลงในฟิลด์ของตาราง เราบังคับใช้อัตราขีดจำกัดอย่างเข้มงวดเพื่อป้องกันแรงกดดันจากการเขียนที่มากเกินไป

การเพิ่มประสิทธิภาพคิวรี

ความท้าทาย: เราระบุคำสั่งคิวรีที่มีค่าใช้จ่ายสูงหลายรายการใน PostgreSQL ในอดีต ปริมาณคิวรีเหล่านี้ที่พุ่งสูงขึ้นอย่างกะทันหันจะใช้ CPU จํานวนมาก โดยทำให้ทั้งคําขอ ChatGPT และ API ช้าลง

วิธีแก้ปัญหา: คิวรีที่มีค่าใช้จ่ายสูงเพียงไม่กี่รายการ เช่น คิวรีที่ทำการรวมหลายตารางเข้าด้วยกัน สามารถทำให้ประสิทธิภาพลดลงอย่างมาก หรือแม้กระทั่งทำให้บริการทั้งหมดล่มได้ เราจำเป็นต้องปรับปรุงคิวรี PostgreSQL อย่างต่อเนื่องเพื่อให้มั่นใจว่ามีประสิทธิภาพและหลีกเลี่ยงรูปแบบที่ไม่พึงประสงค์ที่พบบ่อยใน Online Transaction Processing (OLTP) ตัวอย่างเช่น ครั้งหนึ่งเราเคยระบุคำสั่งที่มีค่าใช้จ่ายสูงมากซึ่งเชื่อมโยงตาราง 12 ตาราง โดยที่การพุ่งขึ้นของคำสั่งนี้เป็นสาเหตุของ SEV ระดับความรุนแรงสูงในอดีต เราควรหลีกเลี่ยงการเชื่อมโยงหลายตารางที่ซับซ้อนเมื่อเป็นไปได้ หากจำเป็นต้องมีการเชื่อมต่อข้อมูล เราได้เรียนรู้ว่าควรพิจารณาแบ่งคิวรีออกเป็นส่วนๆ และย้ายตรรกะการเชื่อมต่อข้อมูลที่ซับซ้อนไปไว้ในเลเยอร์แอปพลิเคชันแทน คิวรีที่มีปัญหาจำนวนมากเหล่านี้ถูกสร้างขึ้นโดยเฟรมเวิร์ก Object-Relational Mapping (ORM) ดังนั้นจึงเป็นสิ่งสำคัญที่จะต้องตรวจสอบ SQL ที่สร้างขึ้นอย่างรอบคอบ และตรวจสอบให้แน่ใจว่าทำงานตามที่คาดไว้ นอกจากนี้ ยังพบได้บ่อยว่ามีคิวรีที่ไม่ได้ใช้งานและทำงานค้างอยู่นานใน PostgreSQL การกำหนดค่าหมดเวลา เช่น idle_in_transaction_session_timeout เป็นสิ่งสำคัญเพื่อป้องกันไม่ให้ค่าเหล่านี้ขัดขวางการทำงานของ autovacuum

การลดความเสี่ยงจากจุดล้มเหลวเพียงจุดเดียว

ความท้าทาย: หาก read replica ล่ม ปริมาณการใช้งานยังสามารถถูกกำหนดเส้นทางไปยัง replica อื่นๆ ได้ อย่างไรก็ตาม การพึ่งพาเครื่องมือเขียนเพียงเครื่องเดียวหมายถึงการมีจุดล้มเหลวเพียงจุดเดียว หากผู้เขียนนั้นไม่สามารถทำงานได้ บริการทั้งหมดจะได้รับผลกระทบ

วิธีแก้ปัญหา: คำขอที่สำคัญที่สุดส่วนใหญ่เกี่ยวข้องกับคิวรีแบบอ่านเท่านั้น เพื่อลดความเสี่ยงจากจุดอ่อนเพียงจุดเดียวในระบบหลัก เราจึงย้ายการอ่านข้อมูลเหล่านั้นจากเครื่องมือเขียนไปยัง replica เพื่อให้มั่นใจว่าคำขอเหล่านั้นจะยังคงให้บริการได้แม้ว่าระบบหลักจะล่มก็ตาม แม้ว่าการดำเนินการเขียนจะยังคงล้มเหลว แต่ผลกระทบลดลง ไม่ใช่ SEV0 อีกต่อไปเนื่องจากการอ่านยังคงใช้งานได้

เพื่อลดโอกาสที่เซิร์ฟเวอร์หลักจะล้มเหลว เราจึงใช้งานระบบหลักในโหมดความพร้อมใช้งานสูง (High-Availability: HA) โดยมีเซิร์ฟเวอร์สำรองแบบพร้อมใช้งาน (Hot Standby) ซึ่งเป็น replica ที่ซิงโครไนซ์อย่างต่อเนื่องและพร้อมที่จะรับช่วงต่อในการให้บริการทราฟฟิกได้ตลอดเวลา หากระบบหลักล่มหรือต้องนำออกจากระบบเพื่อการบำรุงรักษา เราสามารถเลื่อนระบบสำรองขึ้นมาเป็นระบบหลักได้อย่างรวดเร็วเพื่อลดเวลาหยุดทำงาน ทีม Azure PostgreSQL ได้ทำงานอย่างสำคัญเพื่อให้มั่นใจว่าการเฟลโอเวอร์เหล่านี้ยังคงปลอดภัยและเชื่อถือได้ แม้ในสภาวะที่มีปริมาณงานสูงมาก เพื่อจัดการกับความล้มเหลวของ read replica เราได้ปรับใช้ replica หลายตัวในแต่ละภูมิภาค โดยมีความจุสำรองเพียงพอ เพื่อให้มั่นใจว่าความล้มเหลวของ replica เพียงตัวเดียวจะไม่ทำให้เกิดการหยุดทำงานในระดับภูมิภาค

การแยกการดำเนินงานเวิร์กโหลด

ความท้าทาย: เรามักพบสถานการณ์ที่คำขอบางอย่างใช้ทรัพยากรในสัดส่วนที่ไม่สมเหตุสมผลบนอินสแตนซ์ PostgreSQL อาจทำให้ประสิทธิภาพของงานอื่นๆ ที่ทำงานบนอินสแตนซ์เดียวกันลดลง ตัวอย่างเช่น การเปิดตัวฟีเจอร์ใหม่อาจทำให้เกิดคิวรีที่ไม่มีประสิทธิภาพซึ่งใช้ CPU ของ PostgreSQL อย่างมาก ส่งผลให้คำขอสำหรับฟีเจอร์สำคัญอื่นๆ ช้าลง

Solution: To mitigate the “noisy neighbor” problem, we isolate workloads onto dedicated instances to ensure that sudden spikes in resource-intensive requests don’t impact other traffic. Specifically, we split requests into low-priority and high-priority tiers and route them to separate instances. This way, even if a low-priority workload becomes resource-intensive, it won’t degrade the performance of high-priority requests. We apply the same strategy across different products and services as well, so that activity from one product does not affect the performance or reliability of another.

Connection pooling

Challenge: Each instance has a maximum connection limit (5,000 in Azure PostgreSQL). It’s easy to run out of connections or accumulate too many idle ones. We’ve previously had incidents caused by connection storms that exhausted all available connections.

Solution: We deployed PgBouncer as a proxy layer to pool database connections. Running it in statement or transaction pooling mode allows us to efficiently reuse connections, greatly reducing the number of active client connections. This also cuts connection setup latency: in our benchmarks, the average connection time dropped from 50 milliseconds (ms) to 5 ms. Inter-region connections and requests can be expensive, so we co-locate the proxy, clients, and replicas in the same region to minimize network overhead and connection use time. Moreover, PgBouncer must be configured carefully. Settings like idle timeouts are critical to prevent connection exhaustion.

แผนภาพพร็อกซี postgreSQL

read replica แต่ละตัวจะมี Kubernetes Deployment ของตัวเอง ซึ่งรัน PgBouncer pod หลายตัว เราดำเนินการปรับใช้ Kubernetes หลายรายการภายใต้ Kubernetes Service เดียวกัน ซึ่งจะกระจายการรับส่งข้อมูลไปยังพ็อดต่างๆ

Caching

Challenge: A sudden spike in cache misses can trigger a surge of reads on the PostgreSQL database, saturating CPU and slowing user requests.

Solution: To reduce read pressure on PostgreSQL, we use a caching layer to serve most of the read traffic. However, when cache hit rates drop unexpectedly, the burst of cache misses can push a large volume of requests directly to PostgreSQL. This sudden increase in database reads consumes significant resources, slowing down the service. To prevent overload during cache-miss storms, we implement a cache locking (and leasing) mechanism so that only a single reader that misses on a particular key fetches the data from PostgreSQL. When multiple requests miss on the same cache key, only one request acquires the lock and proceeds to retrieve the data and repopulate the cache. All other requests wait for the cache to be updated rather than all hitting PostgreSQL at once. This significantly reduces redundant database reads and protects the system from cascading load spikes.

Scaling read replicas

Challenge: The primary streams Write Ahead Log (WAL) data to every read replica. As the number of replicas increases, the primary must ship WAL to more instances, increasing pressure on both network bandwidth and CPU. This causes higher and more unstable replica lag, which makes the system harder to scale reliably.

Solution: We operate nearly 50 read replicas across multiple geographic regions to minimize latency. However, with the current architecture, the primary must stream WAL to every replica. Although it currently scales well with very large instance types and high-network bandwidth, we can’t keep adding replicas indefinitely without eventually overloading the primary. To address this, we’re collaborating with the Azure PostgreSQL team on cascading replication(เปิดในหน้าต่างใหม่), where intermediate replicas relay WAL to downstream replicas. This approach allows us to scale to potentially over a hundred replicas without overwhelming the primary. However, it also introduces additional operational complexity, particularly around failover management. The feature is still in testing; we’ll ensure it’s robust and can fail over safely before rolling it out to production.

แผนภาพการจำลองแบบแบบคาสเคดของ postgreSQL

Rate limit

Challenge: A sudden traffic spike on specific endpoints, a surge of expensive queries, or a retry storm can quickly exhaust critical resources such as CPU, I/O, and connections, which causes widespread service degradation.

Solution: We implemented rate-limiting across multiple layers—application, connection pooler, proxy, and query—to prevent sudden traffic spikes from overwhelming database instances and triggering cascading failures. It’s also crucial to avoid overly short retry intervals, which can trigger retry storms. We also enhanced the ORM layer to support rate limiting and when necessary, fully block specific query digests. This targeted form of load shedding enables rapid recovery from sudden surges of expensive queries.

Schema Management

Challenge: Even a small schema change, such as altering a column type, can trigger a full table rewrite(เปิดในหน้าต่างใหม่). We therefore apply schema changes cautiously—limiting them to lightweight operations and avoiding any that rewrite entire tables.

Solution: Only lightweight schema changes are permitted, such as adding or removing certain columns that do not trigger a full table rewrite. We enforce a strict 5-second timeout on schema changes. Creating and dropping indexes concurrently is allowed. Schema changes are restricted to existing tables. If a new feature requires additional tables, they must be in alternative sharded systems such as Azure CosmosDB rather than PostgreSQL. When backfilling a table field, we apply strict rate limits to prevent write spikes. Although this process can sometimes take over a week, it ensures stability and avoids any production impact.

Results and the road ahead

This effort demonstrates that with the right design and optimizations, Azure PostgreSQL can be scaled to handle the largest production workloads. PostgreSQL handles millions of QPS for read-heavy workloads, powering OpenAI’s most critical products like ChatGPT and the API platform. We added nearly 50 read replicas, while keeping replication lag near zero, maintained low-latency reads across geo-distributed regions, and built sufficient capacity headroom to support future growth.

This scaling works while still minimizing latency and improving reliability. We consistently deliver low double-digit millisecond p99 client-side latency and five-nines availability in production. And over the past 12 months, we’ve had only one SEV-0 PostgreSQL incident (it occurred during the viral launch(เปิดในหน้าต่างใหม่) of ChatGPT ImageGen, when write traffic suddenly surged by more than 10x as over 100 million new users signed up within a week.)

While we’re happy with how far PostgreSQL has taken us, we continue to push its limits to ensure we have sufficient runway for future growth. We’ve already migrated the shardable write-heavy workloads to our sharded systems like CosmosDB. The remaining write-heavy workloads are more challenging to shard—we’re actively migrating those as well to further offload writes from the PostgreSQL primary. We’re also working with Azure to enable cascading replication so we can safely scale to significantly more read replicas.

Looking ahead, we’ll continue to explore additional approaches to further scale, including sharded PostgreSQL or alternative distributed systems, as our infrastructure demands continue to grow.

ผู้เขียน

Bohan Zhang

การรับทราบ

ขอขอบคุณเป็นพิเศษแก่ Jon Lee, Sicheng Liu, Chaomin Yu และ Chenglong Hao ที่มีส่วนร่วมในโพสต์นี้ และขอขอบคุณทีมทั้งหมดที่ช่วยขยาย PostgreSQL เราขอขอบคุณทีม Azure PostgreSQL ด้วยเช่นกันสำหรับความเป็นพันธมิตรที่แข็งแกร่ง