- Hatchet ने 2 साल के प्रोडक्शन अनुभव में झेली समस्याओं के आधार पर, शुरुआती schema·query design से लेकर high-volume writes और table migration तक चरणबद्ध operational principles को व्यवस्थित किया है
- तेज़ reads के लिए index और
ORDER BY को मेल में रखना चाहिए, लेकिन query planner statistics और cost के आधार पर sequential scan चुन सकता है, इसलिए EXPLAIN ANALYZE से estimates और actual execution की तुलना करनी चाहिए
- write performance और stability छोटे transactions, केवल ज़रूरी rows को lock करना,
CREATE INDEX CONCURRENTLY, और connection pooling पर निर्भर करती है; batch processing ने Hatchet के माप में throughput को लगभग 10 गुना बढ़ाया
- high-frequency write environments में default autovacuum settings dead tuples और transaction IDs को समय पर reclaim नहीं कर पातीं, और transaction ID wraparound आने पर भारी downtime हो सकता है
- scale बढ़ने पर
FOR UPDATE SKIP LOCKED आधारित job queue, partitioning, trigger और batch backfill का उपयोग करें, लेकिन ORM abstraction के बाहर SQL को सीधे नियंत्रित कर पाना ज़रूरी है
लक्षित पाठक और ORM की सीमाएँ
- यह गाइड उन developers के लिए है जो SQL, row, table, index की बुनियादी अवधारणाएँ जानते हैं और production Postgres समस्याओं से निपटना चाहते हैं
- Postgres manual व्यापक है, लेकिन outage जैसी स्थिति में उसे जल्दी संदर्भित करना कठिन होता है, इसलिए Hatchet के 2 साल के operational अनुभव को केंद्र में रखकर इसे संक्षेपित किया गया है
- ORM इस्तेमाल करने पर भी ये सिद्धांत लागू होते हैं, लेकिन scale बढ़ने पर abstraction layer से बाहर निकलकर सीधे SQL लिखना पड़ता है ताकि कई optimizations संभव हो सकें
- Prisma TypedSQL जैसी सुविधाओं से ORM और direct SQL साथ में इस्तेमाल किए जा सकते हैं
- Go-आधारित Hatchet इसी तरह के व्यवहार के लिए
sqlc का उपयोग करता है
- Claude से query लिखवाने वाले environment में supabase/agent-skills की सिफारिश की गई है
बदलना मुश्किल schema design
- deploy के बाद schema change सबसे कठिन होता है, इसलिए table और primary key का draft बनाने के बाद application को चाहिए queries लिखते हुए design को बार-बार सुधारना चाहिए
- design प्रक्रिया में इन सवालों से table के उपयोग का आकलन किया जाता है
- read और write में किसकी frequency ज़्यादा है
- read करते समय सबसे अधिक उपयोग होने वाले filters कौन से हैं
- सबसे अधिक update होने वाले columns कौन से हैं
- database normalization के 1NF·2NF·3NF लागू किए जा सकते हैं, लेकिन कभी-कभी normal forms query efficiency या तेज़ development के लिए ज़रूरी usability से टकरा जाते हैं
- कुछ स्थितियों में data को
jsonb column में रखना ज़्यादा सरल होता है
- schema design पर लागू किए गए thumb rules इस प्रकार हैं
- primary key के लिए identity column वाला auto-increment integer या Postgres built-in UUID इस्तेमाल करें
- identity columns
bigserial से थोड़ा तेज़ होते हैं
- time के लिए हमेशा
timestamptz का उपयोग करें
- हर table में primary key रखें
- consistency और accuracy महत्वपूर्ण होने वाली low-volume tables में cascade delete सहित foreign keys का उपयोग करें, लेकिन high-volume environments में सावधानी रखें
read queries और indexes
- तेज़
SELECT को समझने के लिए एक सरल मॉडल यह है कि Postgres index से किसी row को तेज़ी से ढूंढता है, या sequential scan (seq scan) से table की सभी rows पढ़ता है
- तेज़ single-row lookup के लिए ये संरचनाएँ उपयोग होती हैं
- explicit index
- unique constraint, जो index का एक विशेष रूप है
- primary key, जिसे Postgres अपने-आप index करता है
- default index
btree होता है, और इसे ऐसे समझा जा सकता है जैसे query के लिए optimized data रखने वाली एक अलग table हो
- row lookup time लगभग
log(n) होता है, जहाँ n table की row count है
- अगर index उपयोग नहीं हो सकता तो sequential scan चलता है, लेकिन modern databases rows को memory में बहुत तेज़ी से लाती हैं, इसलिए 20,000 rows से कम वाली tables में यह लगभग तुरंत पूरा हो सकता है
join और composite index
- inner join के target में सामान्यतः primary key का उपयोग होना चाहिए; नहीं तो schema design या normalization में समस्या हो सकती है
ON clause को भी WHERE clause की तरह समझना चाहिए, और join conditions पर उचित index होने चाहिए
- बड़ी tables की list queries अक्सर application में सबसे पहले धीमी पड़ने वाली queries बनती हैं
- अगर organization और creation time को साथ filter और sort करना है, तो composite index इस्तेमाल किया जा सकता है
CREATE INDEX CONCURRENTLY idx_documents_org_created
ON documents (organization_id, created_at DESC);
- complex queries में thumb rule यह है कि
ORDER BY column को index के अंत में रखें और sort direction भी मेल में रखें
- Postgres
btree को दोनों दिशाओं में scan कर सकता है, इसलिए single column में DESC बेअर्थ हो सकता है, लेकिन composite index में इसे मेल में रखना बेहतर है
- descending index के detailed behavior के लिए संबंधित सामग्री देखें
writes, locks, और migrations
- सफल writes की पहली शर्त है transactions को छोटा रखना
- कोई विशेष कारण न हो तो transaction के बीच external service को query न करें
- दूसरी शर्त है केवल ज़रूरी rows को lock करना
- किसी row को update करने पर transaction commit होने तक उस row पर lock रहता है
- system load बढ़ने पर lock का असर और स्पष्ट दिखता है
- किसी मौजूदा बड़ी table पर सामान्य
CREATE INDEX चलाने से table lock हो जाती है और insert/update रुक जाते हैं, इसलिए हमेशा CREATE INDEX CONCURRENTLY का उपयोग करें
- अच्छी schema migration क्षमता iteration की speed बढ़ाती है और uptime बेहतर करती है
- जहाँ तक संभव हो column delete या removal से बचें और additive changes करें
- जहाँ संभव हो transaction के अंदर execute करें ताकि rollback और partial apply की स्थिति संभाली जा सके
- और उन्नत तरीके के रूप में expand and contract migration का उपयोग किया जा सकता है
- migration में पहले यह जाँचना चाहिए कि क्या वह सभी writes को block करेगी
CONCURRENTLY के बिना index creation सभी writes रोककर downtime ला सकता है
ALTER TABLE operations की दोबारा समीक्षा करनी चाहिए, और बड़ी tables में check constraint जोड़ना भी writes block कर सकता है
- check constraint को
NOT VALID के साथ जोड़ने पर यह block टाला जा सकता है
connection management
- हर query और transaction database connection का उपयोग करता है, और connections CPU तथा memory की दृष्टि से महंगे होते हैं, इसलिए उन्हें लंबे समय तक बनाए रखना चाहिए
- बार-बार connections बनाना और हटाना resources की बर्बादी है
- एक साथ बहुत सारे नए connections बनने वाली connection storm, Postgres internal locks से जुड़ी कठिन debugging समस्याएँ पैदा कर सकती है
- पहले external connection pooler
pgbouncer पर विचार करें; अगर वह उपलब्ध न हो तो in-memory connection pool विकल्प हो सकता है
- Hatchet
pgxpool for Go का उपयोग करता है क्योंकि वह यह मान नहीं सकता कि users का database external pooler इस्तेमाल कर रहा होगा
query planner और statistics
- बहुत सारे joins वाली या कई join methods मिलाने वाली complex queries केवल index जोड़ देने से हल नहीं होतीं
- index का अपना overhead भी होता है, इसलिए उन्हें असीमित रूप से नहीं जोड़ना चाहिए
- query planner SQL को internal database operations में बदलता है और तय करता है कि index इस्तेमाल होगा या नहीं, लेकिन सीमित जानकारी के कारण वह हमेशा optimal plan नहीं चुन पाता
- planner जिस जानकारी का उपयोग करता है वह table statistics है, जिसे
pg_stats में देखा जा सकता है
SELECT *
FROM pg_stats
WHERE tablename = 'mytable';
- statistics
ANALYZE के समय इकट्ठी होती हैं और autovacuum चलने पर भी update होती हैं
- autovacuum frequency बढ़ाने से query statistics भी अधिक up-to-date रहती हैं
- query के गलत तरीके से चलने का एक सामान्य कारण analysis frequency का कम होना है
- query को sequential scan हुआ या नहीं, इस सरल दृष्टिकोण से देखने पर micro-optimization के कारण planner की unpredictability बढ़ाने से बचा जा सकता है
- primary key और index-केंद्रित lookups planner के लिए plan चुनना आसान बनाते हैं
execution plan analysis और sequential scan
- कुछ providers, जैसे Google CloudSQL, query sampling करके slow queries store करते हैं, लेकिन हर service यह सुविधा नहीं देती
EXPLAIN ANALYZE query को वास्तव में चलाता है और table statistics के आधार पर expected rows तथा actual scanned rows की तुलना करता है
- production में यह query सचमुच चलती है, इसलिए सावधानी ज़रूरी है
- execution के बिना केवल plan देखना हो तो
ANALYZE हटाकर EXPLAIN इस्तेमाल करें
- detailed plan को JSON में save करके explain.dalibo.com पर visualize किया जा सकता है
psql -XqAt -f explain.sql -d $DATABASE_URL > analyze.json
- अगर statistics और indexes सही होने के बावजूद sequential scan हो रहा है, तो planner ने संभवतः sequential scan की cost को कम आंका होगा
- index वास्तविक table data वाले heap से अलग store होता है, इसलिए index से मिली कई rows को heap से फिर पढ़ने की लागत लगती है
- अगर query को बड़े स्तर पर restructure नहीं किया जा सकता, तो sequential scan स्वीकार करना या partitioning पर विचार करना चाहिए
bulk writes और batch processing
- हर query में database round-trip time, application connection pool से connection लेने का समय, और Postgres processing time जैसा overhead होता है
- Postgres internal locks भी high-throughput environments में bottleneck बन सकते हैं
- एक query में कई rows को बाँधने से ये लागतें कम की जा सकती हैं
- सबसे सरल तरीका है implicit transaction के रूप में कई queries को server पर एक साथ भेजना
- Go में
pgx का SendBatch इस्तेमाल किया जा सकता है
- Hatchet में batch processing से throughput लगभग 10 गुना बढ़ा, और insert optimization पर अतिरिक्त जानकारी तेज़ Postgres inserts गाइड में है
autovacuum और transaction ID wraparound
- autovacuum dead tuples की सफाई और transaction ID management संभालता है, और high-frequency write environments में इसकी settings को adjust करना पड़ सकता है
- tuple file system में stored row का एक version होता है
- किसी row को update या delete करने पर भी पुराने versions तब तक बने रहते हैं जब तक उससे पहले शुरू हुए सभी transactions commit या rollback न हो जाएँ
- वह version जिसे अब कोई transaction पढ़ नहीं सकता, dead tuple कहलाता है
- अगर write speed बहुत अधिक हो, तो autovacuum dead tuples बनने की speed के साथ नहीं चल पाता और database की हालत तेज़ी से बिगड़ सकती है
pg_stat_activity में active processes देखते समय अगर autovacuum query लगभग 1 घंटे या उससे अधिक समय से चल रही हो, तो settings बदलने पर विचार करें
- autovacuum reclaim करने से पहले अगर सभी transaction IDs समाप्त हो जाएँ, तो transaction ID wraparound होता है और भारी downtime आ सकती है
table और index bloat
- Postgres disk के 8KB pages में rows store करता है, और अगर मौजूदा page में नई row नहीं आ सकती तो नया page बनाया जाता है
- dead tuples reclaim होने के बाद page आंशिक रूप से खाली रह जाए तो table bloat हो सकता है, जिससे disk usage बहुत बढ़ जाती है
- सबसे अच्छा बचाव यह है कि bloat होने से पहले autovacuum को tune किया जाए
- जो tables पहले से bloated हों, उनमें
pg_repack जैसी extension इस्तेमाल की जा सकती है
- built-in
VACUUM FULL लगभग कभी अच्छा विकल्प नहीं होता
- Postgres 19 में concurrent table repacking के लिए
REPACK...CONCURRENTLY आने की उम्मीद है, लेकिन Hatchet ने अभी इसका परीक्षण नहीं किया है
- index bloat भी table bloat का एक विशेष रूप है, और उचित autovacuum settings से इसे कम किया जा सकता है
- जो indexes पहले से bloated हों, उनके लिए built-in command
REINDEX INDEX CONCURRENTLY उपयोग की जा सकती है
FOR UPDATE SKIP LOCKED आधारित concurrent processing
FOR UPDATE SKIP LOCKED चुनी हुई rows को वर्तमान transaction के लिए reserve करता है, जबकि दूसरी queries को बाधित नहीं करता
- Hatchet इसे job queue में उपयोग करता है, जहाँ एक query में waiting tasks को lock करके उनका status
RUNNING किया जा सकता है
WITH eligible_tasks AS (
SELECT *
FROM tasks
WHERE status = 'QUEUED'
ORDER BY id ASC
FOR UPDATE SKIP LOCKED
LIMIT 100
)
UPDATE tasks
SET status = 'RUNNING'
FROM eligible_tasks
WHERE tasks.id = eligible_tasks.id
RETURNING tasks.*;
- यह उन स्थितियों में भी उपयोगी है जहाँ स्वतंत्र rows को एक साथ update करना हो या कई application instances किसी object की lease manage कर रहे हों
- Hatchet इसका उपयोग कई engines में tenant lease बाँटने के लिए करता है
partitioning
- Postgres की built-in partitioning table को timestamp या hash जैसे row values के आधार पर विभाजित करती है
- time-series data और Hatchet के historical task data में इसके ये लाभ हैं
- हर partition पर autovacuum स्वतंत्र रूप से चल सकता है, जिससे table की autovacuum processing capacity बढ़ती है
- पुराने data को row-by-row delete करने के बजाय partition table को drop करके लगभग तुरंत हटाया जा सकता है
- अगर planning stage में Postgres अनावश्यक partitions को हटा नहीं पाता, तो read queries में overhead आ सकता है
- हाल की Postgres releases में partition pruning बेहतर हुई है
- Hatchet का operational अनुभव Postgres partitioning लेख में संकलित है
बड़ी tables के बीच data move करना
- यहाँ large table migration का अर्थ schema change नहीं, बल्कि एक table से दूसरी table में large-scale data move करना है
- बहुत बड़ी table को एक single transaction में copy करने पर घंटों लग सकते हैं
- लंबे transactions autovacuum के सामान्य कामकाज को रोकते हैं और dead tuple bloat बढ़ाते हैं
- अगर पुरानी table में लगातार writes होती रहें, तो नई table में वह data प्रतिबिंबित नहीं होगा
- Hatchet transaction के बाहर बड़े batch backfill चलाता है, और migration शुरू होने के बाद आने वाली नई writes को Postgres triggers से नई table में copy करता है
- primary key के unique constraint का उपयोग duplicate writes रोकने के लिए किया जाता है
1 टिप्पणियां
Hacker News की राय
अगर यह production database है, तो सबसे पहले backup और recovery plan बनाना चाहिए। High availability शुरुआत में वैकल्पिक हो सकती है, लेकिन survival guide में backup और recovery का न होना अजीब लगता है
PostgreSQL backup के लिए क्या आज भी Barman(https://pgbarman.org/) का काफी इस्तेमाल होता है, यह जानने की उत्सुकता है
pg_dump_allचलाकर उसेzstdसे compress करने के बाद S3 या FTP जैसी जगह पर copy कर देना काफी होता है। डेटा बड़ा होने पर full backup का समय और लागत बोझ बनते हैं, लेकिन यह सरल तरीका भी काफी लंबे समय तक काम कर सकता हैAWS पर कई TB आकार के MongoDB को EBS snapshots से backup करके तेज incremental backup और recovery लागू की गई थी। Point-in-time recovery नहीं मिलती, लेकिन इसे घंटों के अंतराल पर बार-बार लिया जा सकता है, इसलिए PostgreSQL-specific tools के साथ चलाने लायक एक सहायक रणनीति है
कुछ बातें और जोड़ी जा सकती हैं। सामान्य UUIDv4 की जगह UUIDv7 इस्तेमाल करें, और deadlock से बचने के लिए सिर्फ lock की जाने वाली rows की संख्या ही नहीं, बल्कि सभी queries में lock order को
id ASCजैसे deterministic तरीके से एकसमान रखना चाहिएEXPLAIN (GENERIC_PLAN)इस्तेमाल करने पर parameter placeholders को बनाए रखते हुए query copy की जा सकती है, और PostgreSQL वास्तविक values न जानता हो तब का optimization plan भी देखा जा सकता है। खाली या छोटी tables मेंSET enable_seqscan = offसे index इस्तेमाल होने की संभावना जांची जा सकती हैसबके द्वारा default में इस्तेमाल होने वाले B-tree indexes भारी होते हैं और आसानी से bloated हो सकते हैं, इसलिए अगर sorting या range search के बिना केवल simple lookups करने हैं, तो hash index पर भी विचार किया जा सकता है। Unique hash index नहीं बनाया जा सकता, लेकिन hash exclusion constraint से मिलता-जुलता असर लाया जा सकता है, और multi-column unique index इसमें supported नहीं है
GIN·GiST indexes भी सीख लेना अच्छा है। MySQL users के लिए यह चौंकाने वाला हो सकता है, लेकिन full-text search में बदले बिना भी साधारण
LIKE '%foo%'query को तेज किया जा सकता हैORDER BYन हो, बल्कि table lock order अलग होने पर भी होता है। अगर एक transactiontable_a,table_bक्रम में lock करे और दूसरा transaction उल्टे क्रम में, तो हर table के भीतरORDER BYऔरFOR UPDATEइस्तेमाल करने पर भी deadlock हो सकता हैसिद्धांत में यह साफ है, लेकिन व्यवहार में debugging बहुत कठिन हो जाती है क्योंकि globally यह समझना पड़ता है कि हर write किन tables को छू रही है। किसी खास extension में मैं यह झेल चुका हूँ। JSONB key-value lookup के लिए GIN आज़मा रहा हूँ, और performance improvement बहुत बड़ा था;
ANDऔरORके बीच performance का अंतर भी काफी थायह सलाह भी अच्छी है, लेकिन जिन startup के साथ काम किया, उनमें scalability से पहले ही संगठनात्मक समस्याएँ सामने आ गईं। ORM न इस्तेमाल करना, अर्थपूर्ण fields की जगह sequentially increasing primary keys का उपयोग करना, और JSONB को सिर्फ़ जब सच में ज़रूरत हो तब सीमित रूप से इस्तेमाल करना बेहतर है
source data को insert-only append-only रखना चाहिए और उसे modify या delete नहीं करना चाहिए। performance और सुविधा के लिए denormalized सहायक tables बदली जा सकती हैं, लेकिन उन्हें source of truth नहीं बनाना चाहिए
connection pool का उपयोग करें, लेकिन connections की संख्या पर ध्यान दें; अगर कोई समस्या नहीं है, तो शायद PgBouncer तक की ज़रूरत न पड़े। स्पष्ट कारण न हो तो explicit transactions से बचें, और उन्हें खोलकर RPC जैसे लंबे काम न चलाएँ;
SERIALIZABLEभी लगभग कभी इस्तेमाल न करना बेहतर हैअगर
SELECT FOR UPDATEजैसी explicit locking की ज़रूरत पड़ रही है, तो संभव है कि design गलत हो।type intvalue के आधार पर एक table की rows को कई अर्थ देने जैसा type system फिर से न बनाएँ, और self-referencingnode·edgetables से graph database की नकल भी न करें। ज़्यादातर चीज़ें सामान्य normalized tables से हल हो सकती हैंproject के शुरुआती चरण में database schema पर ज़रूरत से ज़्यादा सोचने और जल्दबाज़ी में optimization करने के बजाय मैं product development पर समय देना पसंद करूँगा
SELECT FOR UPDATEको कई जगह उपयोगी पाया है; समस्या क्या है, यह जानना चाहता हूँ। यह भी जानना है कि append-only source of truth इस्तेमाल करने पर क्या ऐसी locking की ज़रूरत नहीं रहतीइसके उलट, traditional mutable relational tables को source of truth रखकर triggers से change log लिखने का तरीका कैसा रहेगा, यह जानना चाहता हूँ
cascade delete पसंद नहीं है। ज़्यादातर developers database की बजाय Python, Node, Go जैसी application layer में रहते हैं, इसलिए table A की row हटाने पर table B का data भी गायब हो जाना जादू जैसा लग सकता है। गलत configuration होने पर यह और ख़तरनाक है, इसलिए long-term maintenance के लिए explicit delete statements बेहतर हैं; foreign keys सही तरह इस्तेमाल करने भर से भी consistency रखी जा सकती है
बड़े tables की migration के pitfalls और workarounds सही हैं, लेकिन pg-osc जैसे tools पहले से मौजूद हैं। यह इतना सरल होना चाहिए कि एक command चलाएँ और फिर 24 घंटे तक data copy होते समय तनाव में नज़र रखें
application और database deployment को शुरुआत से अलग करना चाहिए। schema और application changes को पूरी तरह एक साथ transaction में deploy नहीं किया जा सकता, इसलिए production में जाने के बाद नई columns को nullable बनाना या default value देना, और table/column names न बदलना जैसे backward-compatible schema changes ही करने की आदत ज़रूरी है
schema management strategy भी जल्दी तय करनी चाहिए। ऐसा deployment process टालना चाहिए जिसमें senior developer अपने कंप्यूटर से production DB पर DDL manually चलाए; चाहें तो Liquibase या Flyway जैसे परिचित tools इस्तेमाल कर सकते हैं
query planner औसत स्थिति को optimize करता है, लेकिन application के लिए कभी-कभी worst case को optimize करना ज़्यादा उपयोगी होता है। औसत user के लिए rows कम थीं और एक खास index से 10ms में result आ जाता था, लेकिन heavy users के लिए वही query parameters के अनुसार 1 सेकंड से ज़्यादा लेती थी
ज़्यादा complex query लिखकर अलग index path force किया; average performance थोड़ी धीमी हुई, लेकिन worst case भी 100ms से कम हो गया। कंपनी के लिए average में 10ms बचाने से timeout रोकना कहीं ज़्यादा महत्वपूर्ण था
SKIP LOCKEDउस interactive transaction-based work queue में उपयोगी है जहाँ application काम करते समय transaction खुला रखकर rows lock करती है। high-performance applications में ऐसे transactions से ही बचना चाहिए और row को तुरंतpendingमें update कर देना चाहिए, इसलिएSKIP LOCKEDकी ज़रूरत नहीं पड़तीscale बढ़ने के साथ database memory में रखी जाने वाली state कम करनी चाहिए, और interactive transactions भी ऐसी ही state हैं। distributed environment में idempotency, atomicity से ज़्यादा फ़ायदेमंद है
long-running transactions database की स्थिति को नुकसान पहुँचा सकती हैं, इसलिए इन्हें सिर्फ़ मज़बूत कारण होने पर ही इस्तेमाल करना चाहिए।
idle_in_transaction_session_timeoutसेट करके idle transactions को लंबे समय तक locks या tuples पकड़े रखने से रोकना चाहिए, और migrations मेंlock_timeoutसेट करना चाहिए ताकि एक DDL पूरे system को न रोक देstatement_timeoutभी सेट करना चाहिए ताकि एक महँगी query पूरे system को पंगु न बना देstartup के शुरुआती दौर में PostgreSQL चलाने के अनुभव से लगा कि यह लेख monitoring और alerts पर पर्याप्त ज़ोर नहीं देता। PostgreSQL में कुछ मुख्य failure types हैं जिनसे हर हाल में बचना चाहिए, और alerts से जोखिम जल्दी पकड़ा जा सकता है
AWS अगर email भेजे कि transaction ID wraparound नज़दीक है, तो startup में, खासकर Boxing Day जैसे दिन, यह आसानी से छूट सकता है। AWS जिन signals को देखता है, उन्हें email नहीं बल्कि pager से जोड़ना चाहिए
connection pool implementations में एक बड़ा, कम-ज्ञात अंतर होता है। ज़्यादातर application connection pools FIFO से low latency और connection availability optimize करते हैं, लेकिन connections को लगातार warm रखते हैं, इसलिए अनावश्यक connections कम करना मुश्किल होता है
PgBouncer और कुछ external poolers LIFO का उपयोग करते हैं, जिससे PostgreSQL तक पहुँचने वाले connections की संख्या और throughput optimize होते हैं। सबसे हाल का connection पहले reuse करने पर बचे हुए connections स्वाभाविक रूप से ठंडे होकर बंद हो जाते हैं
नए applications के लिए FIFO काफ़ी है, लेकिन scale बढ़ने पर PgBouncer जैसे tools से सैकड़ों connections को लगभग 90% तक घटाना बेहतर है। PostgreSQL की प्रति-connection process बनाने वाली संरचना में connections कम हों तो वह बेहतर काम करता है
बहुत ही खास स्थितियों में application memory में join करके अच्छे परिणाम मिले। कई बार database round trip कम करने की कोशिश में
JOIN,UNION,CASEसे उलझी हुई एक single query बना दी जाती हैइसके बजाय कई simple queries को स्वतंत्र रूप से चलाकर, फिर results को iterate करते हुए map से संबंधित rows को जोड़ें, तो round trip और iteration cost बढ़ने पर भी query plan ज़्यादा predictable हो सकता है और उल्टा यह अधिक फायदेमंद हो सकता है। इसे केवल सीमित रूप से इस्तेमाल करना चाहिए; सिर्फ इसलिए कि कुछ ORM अंदरूनी तौर पर ऐसा करते हैं, इसे बिना शर्त recommend नहीं किया जाता
लेकिन selective inner join मूल data से बहुत छोटा result बनाता है, इसलिए सभी records लाकर local में intersection और filtering करना कहीं ज़्यादा महंगा पड़ता है। Index join में query planner index का उपयोग करके अंधाधुंध table scan, sort, और filtering से बच सकता है