Για χρόνια, το PostgreSQL υπήρξε ένα από τα πιο κρίσιμα, αφανή συστήματα δεδομένων που υποστηρίζουν βασικά προϊόντα όπως το ChatGPT και το API της OpenAI. Καθώς η βάση χρηστών μας αυξάνεται ραγδαία, οι απαιτήσεις στις βάσεις δεδομένων μας αυξάνονται και αυτές εκθετικά. Τον τελευταίο χρόνο, το φορτίο μας στο PostgreSQL έχει αυξηθεί πάνω από 10 φορές και συνεχίζει να αυξάνεται γρήγορα.
Οι προσπάθειές μας να αναβαθμίσουμε την υποδομή παραγωγής μας για να υποστηρίξουμε αυτή την ανάπτυξη αποκάλυψαν μια νέα διαπίστωση: το PostgreSQL μπορεί να κλιμακωθεί ώστε να υποστηρίζει αξιόπιστα πολύ μεγαλύτερους φόρτους εργασίας με βάρος στην ανάγνωση από ό,τι πολλοί θεωρούσαν προηγουμένως δυνατό. Το σύστημα (που δημιουργήθηκε αρχικά από μια ομάδα επιστημόνων στο University of California στο Μπέρκλεϊ) μας έχει επιτρέψει να υποστηρίζουμε τεράστια παγκόσμια κυκλοφορία με μία κύρια παρουσία ευέλικτου διακομιστή Azure PostgreSQL(ανοίγει σε νέο παράθυρο) και σχεδόν 50 αντίγραφα ανάγνωσης κατανεμημένα σε πολλές περιοχές παγκοσμίως. Αυτή είναι η ιστορία του πώς κλιμακώσαμε το PostgreSQL στην OpenAI ώστε να υποστηρίζει εκατομμύρια ερωτήματα ανά δευτερόλεπτο για 800 εκατομμύρια χρήστες μέσω ενδελεχών βελτιστοποιήσεων και στιβαρής μηχανικής. Θα καλύψουμε επίσης τα βασικά συμπεράσματα που μάθαμε στην πορεία.
Μετά την κυκλοφορία του ChatGPT, η κυκλοφορία αυξήθηκε με πρωτοφανή ρυθμό. Για να το υποστηρίξουμε, υλοποιήσαμε γρήγορα εκτεταμένες βελτιστοποιήσεις τόσο στο επίπεδο της εφαρμογής όσο και στο επίπεδο της βάσης δεδομένων PostgreSQL, αυξήσαμε το μέγεθος της παρουσίας για κάθετη κλιμάκωση και προσθέσαμε περισσότερα αντίτυπα ανάγνωσης για οριζόντια κλιμάκωση. Αυτή η αρχιτεκτονική μας έχει εξυπηρετήσει καλά για μεγάλο χρονικό διάστημα. Με τις συνεχιζόμενες βελτιώσεις, εξακολουθεί να προσφέρει άφθονο περιθώριο για μελλοντική ανάπτυξη.
Μπορεί να ακούγεται περίεργο ότι μια αρχιτεκτονική με έναν μόνο κύριο κόμβο μπορεί να ανταποκριθεί στις απαιτήσεις της κλίμακας της OpenAI. Ωστόσο, το να λειτουργήσει αυτό στην πράξη δεν είναι απλό. Έχουμε παρατηρήσει αρκετά SEV που προκλήθηκαν από υπερφόρτωση του Postgres και συχνά ακολουθούν το ίδιο μοτίβο: ένα ανάντη πρόβλημα προκαλεί μια ξαφνική αύξηση στο φορτίο της βάσης δεδομένων, όπως εκτεταμένα cache misses (ανεπιτυχείς αναγνώσεις κρυφής μνήμης) από αποτυχία στο επίπεδο προσωρινής αποθήκευσης, μια αύξηση ακριβών multi-way joins που προκαλούν κορεσμό στη CPU, ή ένα write storm από την κυκλοφορία ενός νέου χαρακτηριστικού. Καθώς η χρήση των πόρων αυξάνεται, η λανθάνουσα καθυστέρηση των ερωτημάτων αυξάνεται και τα αιτήματα αρχίζουν να λήγουν λόγω χρονικού ορίου. Οι επαναλήψεις εντείνουν περαιτέρω το φορτίο, προκαλώντας έναν φαύλο κύκλο με τη δυνατότητα να υποβαθμίσουν το σύνολο των υπηρεσιών ChatGPT και API.
Αν και το PostgreSQL κλιμακώνεται καλά για τους φόρτους εργασίας μας με βάρος στην ανάγνωση, εξακολουθούμε να αντιμετωπίζουμε προκλήσεις κατά τη διάρκεια περιόδων με υψηλή κυκλοφορία εγγραφών. Αυτό οφείλεται σε μεγάλο βαθμό στην υλοποίηση του ελέγχου ταυτόχρονης πρόσβασης πολλαπλών εκδόσεων (MVCC) του PostgreSQL, που την καθιστά λιγότερο αποδοτική για φόρτους εργασίας με βάρος στην εγγραφή. Για παράδειγμα, όταν ένα ερώτημα ενημερώνει μια πλειάδα ή ακόμη και ένα μόνο πεδίο, αντιγράφεται ολόκληρη η γραμμή για να δημιουργηθεί μια νέα έκδοση. Υπό βαριά φορτία εγγραφών, αυτό έχει ως αποτέλεσμα σημαντική ενίσχυση εγγραφών. Αυξάνει επίσης την ενίσχυση αναγνώσεων, καθώς τα ερωτήματα πρέπει να σαρώνουν πολλαπλές εκδόσεις πλειάδων (νεκρές πλειάδες) για να ανακτήσουν την πιο πρόσφατη έκδοση. Το MVCC εισάγει πρόσθετες προκλήσεις, όπως η διόγκωση πινάκων και ευρετηρίων, ο αυξημένος φόρτος συντήρησης ευρετηρίων και η σύνθετη ρύθμιση του autovacuum. (Μπορείτε να βρείτε μια διεξοδική ανάλυση για αυτά τα ζητήματα στην ανάρτηση που έγραψα μαζί με τον καθηγητή Andy Pavlo του Carnegie Mellon University με τίτλο The Part of PostgreSQL We Hate the Most(ανοίγει σε νέο παράθυρο), που παρατίθεται με παραπομπή(ανοίγει σε νέο παράθυρο) στη σελίδα της Wikipedia για το PostgreSQL.)
Για να μετριάσουμε αυτούς τους περιορισμούς και να μειώσουμε την πίεση εγγραφής, έχουμε μετεγκαταστήσει και συνεχίζουμε να μετεγκαθιστούμε διαμεριζόμενα (π.χ. φόρτους εργασίας που μπορούν να διαμεριστούν οριζόντια), φόρτους εργασίας με βάρος στην εγγραφή σε διαμερισμένα συστήματα όπως το Azure Cosmos DB, βελτιστοποιώντας τη λογική της εφαρμογής ώστε να ελαχιστοποιούνται οι περιττές εγγραφές. Επίσης, δεν επιτρέπουμε πλέον την προσθήκη νέων πινάκων στην τρέχουσα εγκατάσταση PostgreSQL. Οι νέοι φόρτοι εργασίας προεπιλέγουν τα διαμερισμένα συστήματα.
Ακόμη και καθώς η υποδομή μας εξελίχθηκε, το PostgreSQL παρέμεινε χωρίς διαμέριση, με μία μόνο κύρια παρουσία να εξυπηρετεί όλες τις εγγραφές. Ο κύριος λόγος είναι ότι η διαμέριση των υφιστάμενων φόρτων εργασίας εφαρμογών θα ήταν εξαιρετικά περίπλοκη και χρονοβόρα, αφού θα απαιτούσε αλλαγές σε εκατοντάδες τελικά σημεία εφαρμογών και ενδεχομένως να χρειάζονταν μήνες ή ακόμη και χρόνια. Δεδομένου ότι οι φόρτοι εργασίας μας δίνουν βάρος κυρίως στην ανάγνωση και έχουμε εφαρμόσει εκτεταμένες βελτιστοποιήσεις, η τρέχουσα αρχιτεκτονική εξακολουθεί να παρέχει άφθονο περιθώριο για να υποστηρίξει τη συνεχιζόμενη αύξηση της κίνησης. Παρόλο που δεν αποκλείουμε τη διαμέριση του PostgreSQL στο μέλλον, δεν αποτελεί άμεση προτεραιότητα, δεδομένου του επαρκούς περιθωρίου που έχουμε για την τρέχουσα και μελλοντική ανάπτυξη.
Στις ακόλουθες ενότητες, θα εμβαθύνουμε στις προκλήσεις που αντιμετωπίσαμε και στις εκτεταμένες βελτιστοποιήσεις που εφαρμόσαμε για να τις αντιμετωπίσουμε και να αποτρέψουμε μελλοντικές διακοπές λειτουργίας, ωθώντας το PostgreSQL στα όριά του και κλιμακώνοντάς το σε εκατομμύρια ερωτήματα ανά δευτερόλεπτο (QPS).
Πρόκληση: Με μόνο έναν δημιουργό εγγραφών (writer), ένα σύστημα με έναν μόνο κύριο κόμβο (primary) δεν μπορεί να κλιμακώσει τις εγγραφές. Οι έντονες αιχμές εγγραφών μπορούν να υπερφορτώσουν γρήγορα τον κύριο κόμβο και να επηρεάσουν υπηρεσίες όπως το ChatGPT και το API μας.
Λύση: Ελαχιστοποιούμε το φορτίο στον κύριο κόμβο όσο το δυνατόν περισσότερο —τόσο τις αναγνώσεις όσο και τις εγγραφές— για να διασφαλίσουμε ότι έχει επαρκή δυναμικότητα για να διαχειρίζεται αιχμές εγγραφών. Η κίνηση ανάγνωσης εκφορτώνεται σε αντίγραφα όπου είναι δυνατόν. Ωστόσο, ορισμένα ερωτήματα ανάγνωσης πρέπει να παραμείνουν στον κύριο κόμβο επειδή αποτελούν μέρος συναλλαγών εγγραφής. Για αυτά, φροντίζουμε να διασφαλίσουμε ότι είναι αποδοτικά και να αποφεύγουμε τα αργά ερωτήματα. Για την κίνηση εγγραφών, έχουμε μεταφέρει διαμεριζόμενους φόρτους εργασίας με βάρος στην εγγραφή σε διαμερισμένα συστήματα όπως το Azure CosmosDB. Οι φόρτοι εργασίας που είναι πιο δύσκολο να διαμεριστούν αλλά εξακολουθούν να δημιουργούν μεγάλο όγκο εγγραφών χρειάζονται περισσότερο χρόνο για να μεταφερθούν, και αυτή η διαδικασία εξακολουθεί να βρίσκεται σε εξέλιξη. Επίσης, βελτιστοποιήσαμε δυναμικά τις εφαρμογές μας για να μειώσουμε το φορτίο εγγραφών. Για παράδειγμα, διορθώσαμε σφάλματα εφαρμογών που προκαλούσαν πλεονάζουσες εγγραφές και εισαγάγαμε καθυστερημένες εγγραφές, όπου ήταν κατάλληλο, για να εξομαλύνουμε τις αιχμές κίνησης. Επιπλέον, κατά τη συμπλήρωση εκ των υστέρων των πεδίων του πίνακα, επιβάλλουμε αυστηρά όρια ρυθμού για να αποτρέψουμε την υπερβολική πίεση εγγραφών.
Πρόκληση: Εντοπίσαμε αρκετά δαπανηρά ερωτήματα στο PostgreSQL. Στο παρελθόν, οι ξαφνικές αιχμές στον όγκο αυτών των ερωτημάτων κατανάλωναν μεγάλες ποσότητες CPU, επιβραδύνοντας τόσο το ChatGPT όσο και τα αιτήματα API.
Λύση: Μερικά ακριβά ερωτήματα, όπως αυτά που συνδέουν πολλούς πίνακες μεταξύ τους, μπορούν να προκαλέσουν σημαντική υποβάθμιση ή ακόμη και την κατάρρευση ολόκληρης της υπηρεσίας. Πρέπει να βελτιστοποιούμε συνεχώς τα ερωτήματα PostgreSQL, ώστε να διασφαλίζουμε ότι είναι αποδοτικά και να αποφεύγουμε συνηθισμένα αντι-πρότυπα της Επεξεργασίας Συναλλαγών Online (OLTP). Για παράδειγμα, κάποτε εντοπίσαμε ένα εξαιρετικά δαπανηρό ερώτημα που συνένωνε 12 πίνακες, όπου οι αιχμές σε αυτό το ερώτημα ήταν υπεύθυνες για προηγούμενα SEV υψηλής σοβαρότητας. Θα πρέπει να αποφεύγουμε τις σύνθετες ενώσεις πολλών πινάκων όποτε είναι δυνατόν. Αν οι ενώσεις είναι απαραίτητες, μάθαμε να εξετάζουμε το ενδεχόμενο να αναλύσουμε το ερώτημα και να μεταφέρουμε τη σύνθετη λογική ένωσης στο επίπεδο της εφαρμογής. Πολλά από αυτά τα προβληματικά ερωτήματα δημιουργούνται από πλαίσια Object-Relational Mapping (ORM), οπότε είναι σημαντικό να εξετάζεται προσεκτικά το SQL που παράγουν και να διασφαλίζεται ότι λειτουργεί όπως αναμένεται. Είναι επίσης σύνηθες να βρίσκονται αδρανή ερωτήματα μεγάλης διάρκειας στο PostgreSQL. Η ρύθμιση χρονικών ορίων όπως το idle_in_transaction_session_timeout είναι απαραίτητη για να αποτρέπεται ο αποκλεισμός του autovacuum.
Πρόκληση: Αν ένα αντίγραφο ανάγνωσης τεθεί εκτός λειτουργίας, η κυκλοφορία μπορεί ακόμα να δρομολογηθεί σε άλλα αντίγραφα. Ωστόσο, η στήριξη σε έναν μόνο δημιουργό εγγραφών σημαίνει ότι υπάρχει ένα μοναδικό σημείο αστοχίας —αν τεθεί εκτός λειτουργίας, επηρεάζεται ολόκληρη η υπηρεσία.
Λύση: Τα περισσότερα κρίσιμα αιτήματα αφορούν μόνο ερωτήματα ανάγνωσης. Για να μετριάσουμε το μοναδικό σημείο αστοχίας στον κύριο κόμβο, εκφορτώσαμε αυτές τις αναγνώσεις από τον δημιουργό εγγραφών σε αντίγραφα, διασφαλίζοντας ότι αυτά τα αιτήματα μπορούν να συνεχίσουν να εξυπηρετούνται ακόμη και αν ο κύριος κόμβος τεθεί εκτός λειτουργίας. Παρότι οι λειτουργίες εγγραφής θα εξακολουθούν να αποτυγχάνουν, ο αντίκτυπος μειώνεται. Δεν είναι πλέον SEV0, καθώς οι αναγνώσεις παραμένουν διαθέσιμες.
Για να μετριάσουμε τις αστοχίες του κύριου κόμβου, εκτελούμε τον κύριο κόμβο σε λειτουργία Υψηλής Διαθεσιμότητας (HA) με ένα hot standby, ένα συνεχώς συγχρονισμένο αντίγραφο που είναι πάντα έτοιμο να αναλάβει την εξυπηρέτηση της κυκλοφορίας. Εάν ο κύριος κόμβος πέσει ή χρειαστεί να τεθεί εκτός λειτουργίας για συντήρηση, μπορούμε να προωθήσουμε γρήγορα τον εφεδρικό για να ελαχιστοποιήσουμε τον χρόνο διακοπής. Η ομάδα Azure PostgreSQL έχει καταβάλει σημαντική προσπάθεια για να διασφαλίσει ότι αυτές οι αυτόματες μεταγωγές σε εφεδρεία παραμένουν ασφαλείς και αξιόπιστες ακόμη και υπό πολύ υψηλό φόρτο. Για να αντιμετωπίζουμε αστοχίες των αντιγράφων ανάγνωσης, αναπτύσσουμε πολλαπλά αντίγραφα σε κάθε περιοχή με επαρκές περιθώριο δυναμικότητας, διασφαλίζοντας ότι η αποτυχία ενός μόνο αντιγράφου δεν επιφέρει διακοπή σε επίπεδο περιοχής.
Πρόκληση: Συχνά συναντάμε καταστάσεις όπου ορισμένα αιτήματα καταναλώνουν δυσανάλογο αριθμό πόρων σε παρουσίες 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.
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.
Κάθε αντίγραφο ανάγνωσης έχει τη δική του ανάπτυξη Kubernetes που εκτελεί πολλαπλά pod PgBouncer. Εκτελούμε πολλαπλές αναπτύξεις Kubernetes πίσω από την ίδια Υπηρεσία Kubernetes, η οποία εξισορροπεί την κυκλοφορία μεταξύ των pod.
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.
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.
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.
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.
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.
Συντάκτης
Ευχαριστίες
Ιδιαίτερες ευχαριστίες στους Jon Lee, Sicheng Liu, Chaomin Yu και Chenglong Hao, που συνέβαλαν σε αυτήν την ανάρτηση, και σε ολόκληρη την ομάδα που βοήθησε στην κλιμάκωση του PostgreSQL. Θα θέλαμε επίσης να ευχαριστήσουμε την ομάδα Azure PostgreSQL για την ισχυρή συνεργασία τους.


