اثنا عشر بناءً

Postgres مضبوط بشكل صحيح لمثيل EPYC بسعة 64 غيغابايت

كل إعداد مهم على E-8، مع الحساب وراء كل رقم، الصفحات الكبيرة، وتجميع الاتصالات، وتشغيل pgbench يخبرك ما إذا كان أي من ذلك قد أثر.

ما الذي يبنيه هذا الدليل

مثيل Postgres واحد على E-8 يستخدم العتاد المقدم له: ثمانية أنوية Zen 4 مخصصة، و64 جيجابايت من DDR5 ECC المسجلة، وNVMe من الجيل الرابع مع حماية من فقدان الطاقة. خارج الصندوق، يتم تكوين Postgres ليعمل على حاسوب محمول من عام 2009. إذا تُرك على هذا الحال، سيستخدم حوالي جزء من مائة من ذاكرة هذا الجهاز ويتساءل لماذا هو بطيء.

كل رقم أدناه مشتق وليس منسوخًا. إذا كان مثيلك يحتوي على كمية مختلفة من الذاكرة، فإن الحسابات معروضة حتى تتمكن من إعادة حسابها. هذا مكتوب لعبء عمل يكون في الغالب معاملاتيًا مع بعض التقارير، وهو ما يمتلكه معظم الناس في الواقع بغض النظر عما يقولون.

قبل أن تبدأ

  • E-8 أو أكبر. أقل من 64 جيجابايت، لا تزال النسب صحيحة لكن الأرقام المطلقة ليست كذلك، وتتوقف الصفحات الضخمة عن كونها تستحق العناء.
  • ذاكرة ECC، التي يمتلكها خط EPYC ولا يمتلكها خط Ryzen. خطأ في الذاكرة داخل المخازن المشتركة يعني صفحة تالفة تُكتب مرة أخرى إلى القرص دون تحذير.
  • Debian 13، الذي يحزم PostgreSQL 17.

1. التثبيت والتهيئة الأولية

apt update && apt install -y postgresql postgresql-contrib numactl
systemctl stop postgresql
pg_dropcluster 17 main
pg_createcluster 17 main -- --data-checksums --encoding=UTF8 --locale=C.UTF-8

يجب اختيار مجاميع فحص البيانات (data checksums) في وقت التهيئة الأولية وتكلف بضعة بالمئة من الإنتاجية. وهي الفرق بين الإبلاغ عن صفحة تالفة وتسليم صفحة تالفة، لذا تحمل تكلفة الاثنين بالمئة.

2. الذاكرة

أربعة إعدادات، أربعة حسابات:

  • shared_buffers عند ربع الذاكرة: 16GB. كانت القيم الأكبر مثبطًا عليها سابقًا؛ على جهاز مخصص مع نواة حديثة، الربع هو نقطة البداية المجربة جيدًا والثلث قابل للدفاع عنه إذا كانت مجموعة العمل أكبر.
  • effective_cache_size عند ثلاثة أرباع الذاكرة: 48GB. هذا لا يخصص شيئًا؛ بل يخبر المخطط (planner) بكم من المرجح أن تكون النواة قد خزنت في الكاش، وإعداده منخفضًا جدًا يجعل المخطط يرفض عمليات فحص الفهرس التي يجب أن يختارها.
  • work_mem لكل عقدة فرز، وليس لكل اتصال: مع 200 اتصال وربما عمليتي فرز لكل منها، 32MB هو سقف قابل للدفاع عنه على جهاز بحجم 64 جيجابايت. ارفعه لكل جلسة لاستعلامات التقارير بدلاً من رفعه عالميًا.
  • maintenance_work_mem للفراغ وبناء الفهارس: 2GB، مع ترك autovacuum_work_mem ليرث ذلك.
shared_buffers = 16GB
effective_cache_size = 48GB
work_mem = 32MB
maintenance_work_mem = 2GB
huge_pages = try

3. الصفحات الضخمة

ستة عشر جيجابايت من المخازن المشتركة معينة في صفحات من أربعة كيلوبايت تعني أربعة ملايين إدخال جدول صفحات لكل عملية خلفية. صفحات بحجم ميغابايتين تقلل ذلك بعامل خمسمائة، والفائدة تظهر كاستهلاك أقل لوحدة المعالجة المركزية تحت التزامن وليس كرقم رئيسي.

systemctl start postgresql
sudo -u postgres psql -c "SHOW shared_memory_size_in_huge_pages;"

خذ الرقم الذي يظهر، وأضف عشرة بالمئة كمساحة أمان، واكتبه:

echo "vm.nr_hugepages = 8600" > /etc/sysctl.d/60-postgres.conf
echo "vm.overcommit_memory = 2" >> /etc/sysctl.d/60-postgres.conf
echo "vm.overcommit_ratio = 90" >> /etc/sysctl.d/60-postgres.conf
echo "vm.swappiness = 1" >> /etc/sysctl.d/60-postgres.conf
sysctl --system
echo never > /sys/kernel/mm/transparent_hugepage/enabled

صفحات ضخمة صريحة نعم، صفحات ضخمة شفافة لا. الثانية تجزئ الذاكرة في لحظات غير متوقعة وتنتج زيادات في زمن الاستجابة يلوم الناس عليها تخزينهم لأسابيع.

4. سجل الكتابة المسبقة ونقاط التحقق

إعدادات نقاط التحقق الافتراضية تفرض عملية مسح كل بضع ثوانٍ تحت الحمل، مما يحول NVMe إلى قائمة من عمليات الكتابة المتزامنة الصغيرة.

wal_level = replica
max_wal_size = 16GB
min_wal_size = 2GB
checkpoint_timeout = 15min
checkpoint_completion_target = 0.9
wal_compression = zstd
wal_buffers = 64MB
synchronous_commit = on
full_page_writes = on

اترك synchronous_commit مفعلاً. إيقاف تشغيله هو أكبر مكسب إنتاجي متاح ويعني الإقرار بمعاملات يمكن أن يفقدها فقدان الطاقة. أقراصنا تحتوي على مخازن كتابة مدعومة بالمكثفات لذا فإن fsync رخيص حقًا هنا؛ اشتر الإنتاجية من مكان شريف بدلاً من ذلك.

5. التخزين والمخطط

random_page_cost = 1.1
seq_page_cost = 1.0
effective_io_concurrency = 200
maintenance_io_concurrency = 200
default_statistics_target = 200
jit = off

random_page_cost عند القيمة أربعة هو رقم للأقراص الدوارة وهو أكثر خطأ إعداد شيوعًا في الممارسة العملية. على NVMe، تبلغ تكلفة القراءة العشوائية تقريبًا نفس تكلفة القراءة التسلسلية، وإخبار المخطط بخلاف ذلك يجعله يتجنب الفهارس على الجداول الكبيرة. تعطيل JIT لأنه لاستعلامات المعاملات القصيرة يقضي وقتًا في الترجمة أكثر من التنفيذ؛ فعّله لكل جلسة للاستعلامات التحليلية.

6. التوازي، والفراغ التلقائي، والاتصالات

max_connections = 200
max_worker_processes = 8
max_parallel_workers = 8
max_parallel_workers_per_gather = 4
max_parallel_maintenance_workers = 4

autovacuum_max_workers = 4
autovacuum_naptime = 15s
autovacuum_vacuum_scale_factor = 0.02
autovacuum_analyze_scale_factor = 0.01
autovacuum_vacuum_cost_limit = 3000

shared_preload_libraries = 'pg_stat_statements'
track_io_timing = on
log_min_duration_statement = 500ms
log_checkpoints = on
log_autovacuum_min_duration = 0

تطابق أعداد العمال ثمانية الأنوية المخصصة، لأنها مخصصة. على مضيف مفرط البيع ستكون هذه الأرقام كذبًا وستخفضها إلى أي جزء من نواة تم بيعه لك فعليًا؛ هذه ليست مشكلتك هنا.

عامل المقياس الافتراضي للفراغ التلقائي وهو 0.2 يعني أن جدولًا بمئة مليون صف ينتظر عشرين مليون صف ميت قبل حدوث أي شيء. اثنان بالمئة بدلاً من عشرين يبقي الفراغ يعمل كثيرًا وباختصار بدلاً من نادرًا وبشكل كارثي.

7. التجميع

مئتا عملية خلفية على ثمانية أنوية ليس توازيًا، بل قائمة انتظار مع عبء ذاكرة إضافي. ضع مجمعًا أمامه:

apt install -y pgbouncer
[databases]
app = host=/var/run/postgresql dbname=app

[pgbouncer]
listen_addr = 10.9.0.1
listen_port = 6432
auth_type = scram-sha-256
auth_file = /etc/pgbouncer/userlist.txt
pool_mode = transaction
default_pool_size = 32
max_client_conn = 2000
server_idle_timeout = 60

تجميع المعاملات مع اثنتين وثلاثين اتصال خادم يعطيك أربعة لكل نواة، وهو تقريبًا النقطة التي يتوقف عندها تحسن الإنتاجية على هذا السيليكون. التطبيقات التي تستخدم ميزات على مستوى الجلسة مثل الأقفال الاستشارية أو البيانات المعدة عبر المعاملات تحتاج إلى تجميع الجلسات بدلاً من ذلك، وستحصل على رقم أصغر.

systemctl restart postgresql pgbouncer

التحقق

الإعدادات أولاً. أي شيء لم يسري مفعوله سيكون واضحًا هنا بدلاً من أن يظهر بعد ثلاثة أسابيع:

sudo -u postgres psql -c "SELECT name, setting, unit FROM pg_settings WHERE name IN ('shared_buffers','effective_cache_size','work_mem','huge_pages','random_page_cost','max_wal_size');"
grep -E "HugePages_(Total|Free)" /proc/meminfo

HugePages_Free يجب أن يكون أقل من HugePages_Total بآلاف الصفحات، مما يعني أن Postgres قام بترقيمها فعلاً. القيم المتساوية تعني أنه تراجع إلى الصفحات الصغيرة وابتلع huge_pages = try الفشل بصمت.

ثم قس. قم ببناء مجموعة بيانات كبيرة بما يكفي لتكون مثيرة للاهتمام وصغيرة بما يكفي لتوجد في المخازن المشتركة:

sudo -u postgres createdb bench
sudo -u postgres pgbench -i -s 800 bench
sudo -u postgres pgbench -c 32 -j 8 -T 60 -S bench
sudo -u postgres pgbench -c 32 -j 8 -T 60 bench

على E-8، يصل تشغيل القراءة فقط إلى مئات الآلاف من المعاملات في الثانية، وتشغيل القراءة والكتابة إلى آلاف متوسطة. الأرقام الدقيقة تعتمد على الموقع ومجموعة البيانات، لذا فإن الإشارة المفيدة هي ترتيب المقدار: إذا أعاد اختبار SELECT فقط عشرات الآلاف بدلاً من مئات الآلاف، فإن shared_buffers لم يسري مفعوله أو أنك لا تزال تقيس عبر كاش بارد في التشغيل الأول.

أخيرًا، أكد تحميل إحصائيات الامتداد، لأنه ما ستستخدمه فعليًا كل أسبوع بعد ذلك:

sudo -u postgres psql bench -c "CREATE EXTENSION IF NOT EXISTS pg_stat_statements;"
sudo -u postgres psql bench -c "SELECT calls, round(mean_exec_time::numeric,2) AS ms, query FROM pg_stat_statements ORDER BY total_exec_time DESC LIMIT 5;"

بعد ذلك

فعّل أرشفة WAL إلى مثيل ثانٍ في مدينة أخرى قبل أن يحتوي هذا الدليل على أي شيء تفتقده. استعادة نسخة احتياطية أساسية لم تختبرها أبدًا ليست نسخة احتياطية، بل أمل، وصفحة المواقع موجودة جزئيًا بحيث لا يكون جهاز الاحتياطي في نفس المبنى الذي يوجد فيه الأساسي.

جاهز عندما تكون

اختر مدينة. اختر الحجم. ادفع بالعملة الرقمية.

لا نماذج عن هويتك، ولا انتظار لموافقة بشرية، ولا مكالمة للتحقق من أي شيء. بمجرد تصفية الفاتورة، تصل بيانات الدخول إلى بريدك الإلكتروني.