WAL في PostgreSQL: 4. سجل الإعداد

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

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

مستويات السجل


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

أدنى


يتم تعيين الحد الأدنى لمستوى ممكن من خلال القيمة wal_level = الحد الأدنى ويضمن فقط الانتعاش بعد الفشل. لتوفير مساحة ، لا يتم تسجيل العمليات المتعلقة بمعالجة البيانات الجماعية (مثل CREATE TABLE AS SELECT أو CREATE INDEX). بدلاً من ذلك ، تتم كتابة البيانات اللازمة على الفور على القرص ، ويتم إضافة كائن جديد إلى دليل النظام ويصبح مرئيًا عند الالتزام بالمعاملة. في حالة حدوث عطل أثناء العملية ، تظل البيانات المسجلة بالفعل غير مرئية ولا تنتهك التناسق. في حالة حدوث الفشل بعد اكتمال العملية ، فإن كل ما يلزم قد انتقل بالفعل إلى القرص ولا يحتاج إلى تسجيل.

لنرى. أولاً ، قم بتعيين المستوى المطلوب (لهذا ستحتاج أيضًا إلى تغيير معلمة أخرى - max_wal_senders ).

=> ALTER SYSTEM SET wal_level = minimal; => ALTER SYSTEM SET max_wal_senders = 0; 

 student$ sudo pg_ctlcluster 11 main restart 

لاحظ أن تغيير المستوى يتطلب إعادة تشغيل الخادم.

تذكر الموضع الحالي في السجل:

 => SELECT pg_current_wal_insert_lsn(); 
  pg_current_wal_insert_lsn --------------------------- 0/353927BC (1 row) 

الآن لنقم بإنشاء الجدول (CREATE TABLE AS SELECT) وكتابة الموضع في السجل مرة أخرى. لا يهم مقدار البيانات المحددة بواسطة بيان SELECT في هذه الحالة ، لذلك سنقتصر على سطر واحد.

 => CREATE TABLE wallevel AS SELECT 1 AS n; => SELECT pg_current_wal_insert_lsn(); 
  pg_current_wal_insert_lsn --------------------------- 0/353A7DFC (1 row) 

باستخدام الأداة المساعدة المألوفة pg_waldump ، دعونا نلقي نظرة على إدخالات السجل.

 postgres$ /usr/lib/postgresql/11/bin/pg_waldump -p /var/lib/postgresql/11/main/pg_wal -s 0/353927BC -e 0/353A7DFC 

بعض التفاصيل ، بالطبع ، قد تختلف من إطلاق إلى آخر ، ولكن في هذه الحالة ، هذا ما حدث. يشير إدخال مدير Heap2 إلى التنظيف ، وهو عبارة عن تنظيف في الصفحة لأحد الجداول في كتالوج النظام (يمكن تمييز كائنات النظام بسهولة بالعين المجردة بواسطة الرقم "القصير" فيما يتعلق):

 rmgr: Heap2 len (rec/tot): 59/ 7587, tx: 0, lsn: 0/353927BC, prev 0/35392788, desc: CLEAN remxid 101126, blkref #0: rel 1663/16386/1247 blk 8 FPW 

ثم هناك سجل حول الحصول على OID التالي للجدول الذي سنقوم بإنشائه:

 rmgr: XLOG len (rec/tot): 30/ 30, tx: 0, lsn: 0/35394574, prev 0/353927BC, desc: NEXTOID 82295 

الآن إنشاء الفعلي للجدول:

 rmgr: Storage len (rec/tot): 42/ 42, tx: 0, lsn: 0/35394594, prev 0/35394574, desc: CREATE base/16386/74103 

ومع ذلك ، لم يتم تسجيل إدخال البيانات في جدول. ثم هناك العديد من الإدخالات حول إدراج صفوف في جداول وفهارس مختلفة - يسجل هذا PostgreSQL الجدول الذي تم إنشاؤه في دليل النظام (أعطيها في شكل مختصر):

 rmgr: Heap len (rec/tot): 203/ 203, tx: 101127, lsn: 0/353945C0, prev 0/35394594, desc: INSERT off 71, blkref #0: rel 1663/16386/1247 blk 8 rmgr: Btree len (rec/tot): 53/ 685, tx: 101127, lsn: 0/3539468C, prev 0/353945C0, desc: INSERT_LEAF off 37, blkref #0: rel 1663/16386/2703 blk 2 FPW ... rmgr: Btree len (rec/tot): 53/ 2393, tx: 101127, lsn: 0/353A747C, prev 0/353A6788, desc: INSERT_LEAF off 10, blkref #0: rel 1664/0/1233 blk 1 FPW 

وأخيراً ، تثبيت المعاملة:

 rmgr: Transaction len (rec/tot): 34/ 34, tx: 101127, lsn: 0/353A7DD8, prev 0/353A747C, desc: COMMIT 2019-07-23 18:59:34.923124 MSK 

طبق الاصل


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

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

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

ثانياً ، يجب أن تكون قادرًا على إنشاء لقطات بيانات ، ولهذا ، كما نذكر ، هناك حاجة إلى معلومات حول المعاملات الجارية. في حالة النسخة المتماثلة ، نحن لا نتحدث فقط عن المعاملات المحلية ، ولكن أيضًا عن المعاملات على الخادم الرئيسي. الطريقة الوحيدة لنقل هذه المعلومات هي كتابتها بشكل دوري إلى السجل (يحدث هذا كل 15 ثانية).

يتم تعيين مستوى السجل ، الذي يضمن القدرة على الاسترداد من نسخة احتياطية وإمكانية النسخ المتماثل المادي ، بواسطة القيمة wal_level = النسخة المتماثلة . (قبل الإصدار 9.6 ، كان هناك مستويان منفصلان للأرشيف و hot_byby ، ولكن بعد ذلك تم دمجهما في واحد مشترك واحد.)

بدءًا من PostgreSQL 10 ، يتم تعيين هذا المستوى افتراضيًا (وقبل ذلك كان الحد الأدنى). لذلك ، فقط إعادة تعيين المعلمات إلى القيم الافتراضية:

 => ALTER SYSTEM RESET wal_level; => ALTER SYSTEM RESET max_wal_senders; 

 student$ sudo pg_ctlcluster 11 main restart 

نقوم بحذف الجدول ونكرر نفس تسلسل الإجراءات تمامًا كما حدث في المرة الأخيرة:

 => DROP TABLE wallevel; => SELECT pg_current_wal_insert_lsn(); 
  pg_current_wal_insert_lsn --------------------------- 0/353AF21C (1 row) 
 => CREATE TABLE wallevel AS SELECT 1 AS n; => SELECT pg_current_wal_insert_lsn(); 
  pg_current_wal_insert_lsn --------------------------- 0/353BE51C (1 row) 

تحقق الآن من إدخالات دفتر اليومية.

 postgres$ /usr/lib/postgresql/11/bin/pg_waldump -p /var/lib/postgresql/11/main/pg_wal -s 0/353AF21C -e 0/353BE51C 

تنظيف ، الحصول على OID ، إنشاء جدول والتسجيل في دليل النظام - في الوقت الحالي ، كل شيء كما كان:

 rmgr: Heap2 len (rec/tot): 58/ 58, tx: 0, lsn: 0/353AF21C, prev 0/353AF044, desc: CLEAN remxid 101128, blkref #0: rel 1663/16386/1247 blk 8 rmgr: XLOG len (rec/tot): 30/ 30, tx: 0, lsn: 0/353AF258, prev 0/353AF21C, desc: NEXTOID 82298 rmgr: Storage len (rec/tot): 42/ 42, tx: 0, lsn: 0/353AF278, prev 0/353AF258, desc: CREATE base/16386/74106 rmgr: Heap len (rec/tot): 203/ 203, tx: 101129, lsn: 0/353AF2A4, prev 0/353AF278, desc: INSERT off 73, blkref #0: rel 1663/16386/1247 blk 8 rmgr: Btree len (rec/tot): 53/ 717, tx: 101129, lsn: 0/353AF370, prev 0/353AF2A4, … rmgr: Btree len (rec/tot): 53/ 2413, tx: 101129, lsn: 0/353BD954, prev 0/353BCC44, desc: INSERT_LEAF off 10, blkref #0: rel 1664/0/1233 blk 1 FPW 

لكن شيئا جديدا. سجل القفل الحصري المتعلق بمدير الاستعداد - في هذه الحالة ، يقوم بحظر رقم المعاملة (لماذا هو مطلوب ، سنتحدث بالتفصيل في سلسلة المقالات التالية):

 rmgr: Standby len (rec/tot): 42/ 42, tx: 101129, lsn: 0/353BE2D8, prev 0/353BD954, desc: LOCK xid 101129 db 16386 rel 74106 

وهذا سجل حول إدراج صفوف في جدولنا (قارن رقم الملف بالنسب الموضحة أعلاه في سجل CREATE):

 rmgr: Heap len (rec/tot): 59/ 59, tx: 101129, lsn: 0/353BE304, prev 0/353BE2D8, desc: INSERT+INIT off 1, blkref #0: rel 1663/16386/74106 blk 0 

سجل الالتزام:

 rmgr: Transaction len (rec/tot): 421/ 421, tx: 101129, lsn: 0/353BE340, prev 0/353BE304, desc: COMMIT 2019-07-23 18:59:37.870333 MSK; inval msgs: catcache 74 catcache 73 catcache 74 catcache 73 catcache 50 catcache 49 catcache 7 catcache 6 catcache 7 catcache 6 catcache 7 catcache 6 catcache 7 catcache 6 catcache 7 catcache 6 catcache 7 catcache 6 catcache 7 catcache 6 snapshot 2608 relcache 74106 snapshot 1214 

ويشير سجل آخر ، والذي يحدث بشكل دوري وغير مرتبط بالمعاملة المكتملة ، إلى مدير الاستعداد ويقدم تقارير عن المعاملات الجارية حاليًا:

 rmgr: Standby len (rec/tot): 50/ 50, tx: 0, lsn: 0/353BE4E8, prev 0/353BE340, desc: RUNNING_XACTS nextXid 101130 latestCompletedXid 101129 oldestRunningXid 101130 

منطقي


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

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

الآن لن ندخل في تفاصيل عملية النسخ الاحتياطي والتكرار - هذا موضوع كبير لسلسلة منفصلة من المقالات.

موثوقية السجل


من الواضح أن آلية عمل دفتر اليومية يجب أن تكون موثوقة وتوفر ضمانات لإمكانية الاسترداد في أي حالات (لا تتعلق ، بالطبع ، بالأضرار التي لحقت بشركة البيانات). تتأثر الموثوقية بالعديد من العوامل ، والتي سننظر في تخزينها في ذاكرة التخزين المؤقت وفساد البيانات و atomicity التسجيلات.

التخزين المؤقت


هناك العديد من ذاكرات التخزين المؤقت على مسار البيانات إلى تخزين غير متغير (مثل محرك الأقراص الثابتة).

عندما يطلب برنامج (أي ، ولكن في حالتنا PostgreSQL) من نظام التشغيل كتابة شيء ما على القرص ، فإن نظام التشغيل ينقل البيانات إلى ذاكرة التخزين المؤقت الخاصة به في ذاكرة الوصول العشوائي. يحدث التسجيل الفعلي بشكل غير متزامن ، اعتمادًا على إعدادات برنامج جدولة I / O لنظام التشغيل.

عندما يقرر نظام التشغيل كتابة البيانات ، فإنها تقع في ذاكرة التخزين المؤقت لمحرك الأقراص (القرص الصلب). يمكن أن تؤدي إلكترونيات Drive أيضًا إلى تأخير التسجيل ، على سبيل المثال ، جمع البيانات في مجموعات تكون أكثر ربحية للتسجيل في نفس الوقت. وإذا تم استخدام وحدة تحكم RAID ، يظهر مستوى آخر من التخزين المؤقت بين نظام التشغيل ومحرك الأقراص.

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

يوفر نظام التشغيل الأدوات التي يجب أن تضمن الكتابة الفورية للبيانات إلى ذاكرة غير متقلبة. هناك العديد من الخيارات ، ولكنها تنقسم إلى خيارين رئيسيين: إما أن يتم إعطاء أمر التزامن بعد التسجيل (fsync ، fdatasync) ، أو عند فتح ملف (أو الكتابة إليه) ، يتم الإشارة إلى علامة خاصة للمزامنة أو حتى التسجيل المباشر ، وتجاوز ذاكرة التخزين المؤقت لنظام التشغيل.

بالنسبة للسجل ، تسمح لك الأداة المساعدة pg_test_fsync باختيار الطريقة الأنسب لنظام تشغيل معين ونظام ملفات محدد ، ويتم تثبيته في معلمة تكوين wal_sync_method . تتم مزامنة الملفات العادية دائمًا باستخدام fsync.

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

تحتوي الوثائق على العديد من التفاصيل حول هذا الموضوع.

في أي حال ، تكون المزامنة مكلفة ولا تحدث أكثر من اللازم (سنعود إلى هذه المشكلة أقل قليلاً عندما نتحدث عن الأداء).

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

تلف البيانات


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

من أجل اكتشاف المشكلة في الوقت المناسب ، يتم توفير إدخالات دفتر اليومية دائما مع الاختباري.

يمكن أيضًا حماية صفحات البيانات من خلال الاختبارات. في الوقت الحالي ، لا يمكن القيام بذلك إلا عند تهيئة الكتلة ، ولكن في PostgreSQL 12 سيكون من الممكن تشغيلها وإيقافها باستخدام الأداة المساعدة pg_checksums (على الرغم من عدم انتقالها بعد ، ولكن فقط عند إيقاف الخادم).

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

يقلل ، ولكن لا يلغي.
أولاً ، يتم التحقق من المجموع الاختباري فقط عند الوصول إلى الصفحة - وبالتالي ، قد لا يتم ملاحظة الضرر حتى يتم الوصول إلى كافة النسخ الاحتياطية. هذا هو السبب في pg_probackup بالتحقق من المجموع الاختباري لجميع صفحات الكتلة أثناء النسخ الاحتياطي.
ثانياً ، تعتبر الصفحة المملوءة بالأصفار صحيحة - إذا كان نظام الملفات عن طريق الخطأ "يلغي" الملف ، فقد يمر هذا دون أن يلاحظه أحد.
ثالثًا ، تحمي الاختبارية الطبقة الرئيسية فقط من ملفات البيانات. الطبقات المتبقية والملفات الأخرى (على سبيل المثال ، حالات المعاملات XACT) ليست محمية من قبل أي شيء.
للأسف.

دعونا نرى كيف يعمل. أولاً ، تأكد من تمكين عمليات المجموع الاختباري (ضع في اعتبارك أن هذا ليس هو الحال عند تثبيت حزمة على أنظمة تشبه دبيان):

 => SHOW data_checksums; 
  data_checksums ---------------- on (1 row) 

المعلمة data_checksums للقراءة فقط.

هنا هو الملف الذي يوجد به جدولنا:

 => SELECT pg_relation_filepath('wallevel'); 
  pg_relation_filepath ---------------------- base/16386/24890 (1 row) 

أوقف الخادم وقم بتغيير بضعة بايت في صفحة الصفر ، على سبيل المثال ، احذف إدخال السجل الأخير من رأس LSN.

 student$ sudo pg_ctlcluster 11 main stop 

 postgres$ dd if=/dev/zero of=/var/lib/postgresql/11/main/base/16386/24890 oflag=dsync conv=notrunc bs=1 count=8 
 8+0 records in 8+0 records out 8 bytes copied, 0,0083022 s, 1,0 kB/s 

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

نبدأ الآن الخادم ونحاول قراءة الجدول.

 student$ sudo pg_ctlcluster 11 main start 

 => SELECT * FROM wallevel; 
 WARNING: page verification failed, calculated checksum 23222 but expected 50884 ERROR: invalid page in block 0 of relation base/16386/24890 

ولكن ماذا لو لم يمكن استعادة البيانات من النسخة الاحتياطية؟ تسمح لك المعلمة ignore_checksum_failure بمحاولة قراءة الجدول ، بشكل طبيعي مع خطر الحصول على بيانات مشوهة.

 => SET ignore_checksum_failure = on; => SELECT * FROM wallevel; 
 WARNING: page verification failed, calculated checksum 23222 but expected 50884 n --- 1 (1 row) 

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

وأكثر شيء واحد. عند تشغيل المجموع الاختباري ، تتم كتابة أجزاء من المطالبات إلى السجل ( درسناها سابقًا) ، نظرًا لأن التغيير في أي شيء ، حتى غير أساسي ، يؤدي أيضًا إلى تغيير في المجموع الاختباري. عند إيقاف تشغيل المجموع الاختباري ، تكون المعلمة wal_log_hints مسؤولة عن كتابة بت تلميح في السجل .

يتم دائمًا تسجيل التغييرات في تلميحات البتات كصورة صفحة كاملة (FPI ، صورة صفحة كاملة) ، مما يزيد من حجم السجل بالترتيب. في هذه الحالة ، من المنطقي تمكين ضغط الصور الكاملة باستخدام المعلمة wal_compression (ظهرت هذه المعلمة في الإصدار 9.5). أدناه نحن ننظر إلى أرقام محددة.

سجل الذرية


وأخيرا ، هناك مشكلة ذرية السجل. تستغرق صفحة قاعدة البيانات 8 كيلو بايت على الأقل (يمكن أن تكون 16 أو 32 كيلو بايت) ، وعلى مستوى منخفض ، يحدث التسجيل في كتل تكون عادة أصغر (عادة 512 بايت أو 4 كيلوبايت). لذلك ، في حالة انقطاع التيار الكهربائي ، قد يتم تسجيل صفحة البيانات جزئيًا. من الواضح أنه أثناء الاسترداد ، لا معنى لتطبيق إدخالات دفتر اليومية العادية على هذه الصفحة.

للحماية ، يسمح لك PostgreSQL بالكتابة إلى الصورة كاملة للصفحة عند تغييرها لأول مرة بعد بداية نقطة التفتيش (يتم تسجيل نفس الصورة عند تغيير بت تلميح الأدوات). تتحكم المعلمة full_page_writes في ذلك ، ويتم تمكينها افتراضيًا.

في حالة مواجهة صورة صفحة أثناء الاستعادة في سجل ما ، تتم كتابتها بشكل غير مشروط (بدون التحقق من LSN) إلى القرص: هناك ثقة أكبر بها ، لأنه ، مثل أي سجل سجل ، محمي بواسطة المجموع الاختباري. ويتم بالفعل تطبيق إدخالات دفتر اليومية العادية على هذه الصورة الصحيحة المضمونة.

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

من أجل الشعور بالتغيير في حجم السجل بطريقة ما ، سنقوم بإجراء تجربة بسيطة باستخدام الأداة المساعدة pgbench. لنستعد:

 student$ pgbench -i test 
 dropping old tables... creating tables... generating data... 100000 of 100000 tuples (100%) done (elapsed 0.15 s, remaining 0.00 s) vacuuming... creating primary keys... done. 

يتم تمكين الخيار full_page_writes :

 => SHOW full_page_writes; 
  full_page_writes ------------------ on (1 row) 

قم بتشغيل نقطة الإيقاف وتشغيل الاختبار لمدة 30 ثانية على الفور.

 => CHECKPOINT; => SELECT pg_current_wal_insert_lsn(); 
  pg_current_wal_insert_lsn --------------------------- 0/38E04A08 (1 row) 

 student$ pgbench -T 30 test 
 starting vacuum...end. transaction type: <builtin: TPC-B (sort of)> scaling factor: 1 query mode: simple number of clients: 1 number of threads: 1 duration: 30 s number of transactions actually processed: 26851 latency average = 1.117 ms tps = 895.006720 (including connections establishing) tps = 895.095229 (excluding connections establishing) 

 => SELECT pg_current_wal_insert_lsn(); 
  pg_current_wal_insert_lsn --------------------------- 0/3A69C478 (1 row) 

حجم السجل:

 => SELECT pg_size_pretty('0/3A69C478'::pg_lsn - '0/38E04A08'::pg_lsn); 
  pg_size_pretty ---------------- 25 MB (1 row) 

قم الآن بإيقاف تشغيل المعلمة full_page_writes:

 => ALTER SYSTEM SET full_page_writes = off; => SELECT pg_reload_conf(); 

وكرر التجربة.

 => CHECKPOINT; => SELECT pg_current_wal_insert_lsn(); 
  pg_current_wal_insert_lsn --------------------------- 0/3A69C530 (1 row) 

 student$ pgbench -T 30 test 
 starting vacuum...end. transaction type: <builtin: TPC-B (sort of)> scaling factor: 1 query mode: simple number of clients: 1 number of threads: 1 duration: 30 s number of transactions actually processed: 27234 latency average = 1.102 ms tps = 907.783080 (including connections establishing) tps = 907.895326 (excluding connections establishing) 

 => SELECT pg_current_wal_insert_lsn(); 
  pg_current_wal_insert_lsn --------------------------- 0/3BE87658 (1 row) 

حجم السجل:

 => SELECT pg_size_pretty('0/3BE87658'::pg_lsn - '0/3A69C530'::pg_lsn); 
  pg_size_pretty ---------------- 24 MB (1 row) 

نعم ، لقد انخفض الحجم ، ولكن ليس على الإطلاق بنفس الأهمية التي قد يتوقعها المرء.

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

 postgres$ /usr/lib/postgresql/11/bin/pg_waldump --stats -p /var/lib/postgresql/11/main/pg_wal -s 0/3A69C530 -e 0/3BE87658 
 Type N (%) Record size (%) FPI size (%) ---- - --- ----------- --- -------- --- XLOG 1721 ( 1,03) 84329 ( 0,77) 13916104 (100,00) Transaction 27235 ( 16,32) 926070 ( 8,46) 0 ( 0,00) Storage 1 ( 0,00) 42 ( 0,00) 0 ( 0,00) CLOG 1 ( 0,00) 30 ( 0,00) 0 ( 0,00) Standby 4 ( 0,00) 240 ( 0,00) 0 ( 0,00) Heap2 27522 ( 16,49) 1726352 ( 15,76) 0 ( 0,00) Heap 109691 ( 65,71) 8169121 ( 74,59) 0 ( 0,00) Btree 756 ( 0,45) 45380 ( 0,41) 0 ( 0,00) -------- -------- -------- Total 166931 10951564 [44,04%] 13916104 [55,96%] 

من أجل الاكتناز ، أزلت الصفوف الصفرية من الجدول. انتبه إلى الخط الكلي (الإجمالي) وقارن حجم الصور الكاملة (حجم FPI) مع حجم السجلات العادية (حجم السجل).

لا يمكن تعطيل المعلمة full_page_writes إلا إذا كان نظام الملفات والأجهزة المستخدمة لوحدهما يضمنان التسجيل الذري. ولكن ، كما نرى ، لا يوجد سبب كبير لهذا (على افتراض أن المجموع الاختباري مدرج).

الآن دعونا نرى كيف يساعد الضغط.

 => ALTER SYSTEM SET full_page_writes = on; => ALTER SYSTEM SET wal_compression = on; => SELECT pg_reload_conf(); 

كرر نفس التجربة.

 => CHECKPOINT; => SELECT pg_current_wal_insert_lsn(); 
  pg_current_wal_insert_lsn --------------------------- 0/3BE87710 (1 row) 

 student$ pgbench -T 30 test 
 starting vacuum...end. transaction type: <builtin: TPC-B (sort of)> scaling factor: 1 query mode: simple number of clients: 1 number of threads: 1 duration: 30 s number of transactions actually processed: 26833 latency average = 1.118 ms tps = 894.405027 (including connections establishing) tps = 894.516845 (excluding connections establishing) 

 => SELECT pg_current_wal_insert_lsn(); 
  pg_current_wal_insert_lsn --------------------------- 0/3CBD3EA8 (1 row) 

حجم السجل:

 => SELECT pg_size_pretty('0/3CBD3EA8'::pg_lsn - '0/3BE87710'::pg_lsn); 
  pg_size_pretty ---------------- 13 MB (1 row) 

الخلاصة: في ظل وجود عدد كبير من صور الصفحة كاملة (نظرًا للاختبارات أو full_page_writes ، أي دائمًا تقريبًا) ، من المرجح استخدام الضغط على الرغم من حقيقة أن هذا يحمّل المعالج.

إنتاجية


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

لذلك ، يكون من المفيد عادةً وضع السجل على قرص فعلي منفصل (أو صفيف قرص) مثبت على نظام ملفات الخادم. بدلاً من دليل $ PGDATA / pg_wal ، تحتاج إلى إنشاء رابط رمزي إلى الدليل المطابق.

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

يتم التسجيل في أحد وضعين:

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

يتم تحديد الوضع المتزامن بواسطة المعلمة synchronous_commit ويتم تمكينه افتراضيًا.

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

المعلمة الافتراضية هي conf_siblings = 5 ، و conf_delay = 0 ، لذلك لا يوجد انتظار فعلي. تغيير commit_delay هو مفيد فقط في الأنظمة التي تنفيذ عدد كبير من OLTP-المعاملات قصيرة.

ثم تقوم العملية بمسح السجل إلى القرص إلى LSN المطلوبة (أو أكثر قليلاً إذا تمت إضافة إدخالات جديدة أثناء وقت الانتظار). بعد ذلك ، تعتبر الصفقة مكتملة.

مع التسجيل المتزامن ، يتم ضمان المتانة (الحرف D في اختصار ACID) - إذا تم تنفيذ المعاملة ، فكل إدخالات دفتر اليومية الخاصة بها موجودة بالفعل على القرص ولن يتم فقدها. الجانب الآخر هو أن التسجيل المتزامن يزيد من زمن الاستجابة (الأمر COMMIT لا يُرجع التحكم حتى ينتهي التزامن) ويقلل من أداء النظام.

يمكن الحصول على كتابة غير متزامنة عن طريق تعيين التزامن = إيقاف (أو محلي).

أثناء التسجيل غير المتزامن ، تقوم عملية كاتب السجل بإعادة تعيين إدخالات دفتر اليومية ، مع تبديل دورات الانتظار (التي يتم تعيينها بواسطة المعلمةwal_writer_delay = 200 مللي ثانية بشكل افتراضي).

عند الاستيقاظ بعد الانتظار التالي ، تتحقق العملية من ظهور صفحات WAL الكاملة من آخر مرة. في حالة وجودها ، تتجاهل العملية الصفحة الحالية غير المعبئة وتسجيل السجلات المملوءة بالكامل فقط. (صحيح ، أنها ليست دائمًا في كل مرة: يتوقف التسجيل ، ويصل إلى نهاية ذاكرة التخزين المؤقت ، ويستمر من بداية ذاكرة التخزين المؤقت في المرة التالية.)

إذا لم تكن صفحة واحدة ممتلئة ، فستقوم العملية بكتابة الصفحة الحالية (غير كاملة) للمجلة - لسبب وجيه استيقظت؟

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

يكون التسجيل غير المتزامن أكثر فعالية من التسجيل المتزامن - لا تنتظر تغييرات الالتزام التسجيل. ومع ذلك ، تتناقص الموثوقية: قد تختفي البيانات الملتقطة في حالة حدوث عطل في حالة انقضاء أقل من 3 × وقت wal_writer_delay بين الالتزام والفشل (والذي ، بشكل افتراضي ، يزيد قليلاً عن نصف ثانية).

يبقى الاختيار الصعب - الكفاءة أو الموثوقية - مع مسؤول النظام.

يرجى ملاحظة: على عكس إيقاف المزامنة ( fsync = off) ، لا يؤدي الوضع غير المتزامن إلى استحالة الاسترداد. في حالة حدوث عطل ، سيستمر النظام في استعادة حالة متناسقة ، ولكن ربما تكون بعض أحدث المعاملات غائبة فيه.

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

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

للحصول على فكرة عما يعطيه الالتزام غير المتزامن ، نحاول تكرار اختبار pgbench في هذا الوضع.

 => ALTER SYSTEM SET synchronous_commit = off; => SELECT pg_reload_conf(); 

 student$ pgbench -T 30 test 
 starting vacuum...end. transaction type: <builtin: TPC-B (sort of)> scaling factor: 1 query mode: simple number of clients: 1 number of threads: 1 duration: 30 s number of transactions actually processed: 45439 latency average = 0.660 ms tps = 1514.561710 (including connections establishing) tps = 1514.710558 (excluding connections establishing) 

باستخدام التزامن المتزامن ، تلقينا حوالي 900 معاملة في الثانية (tps) ، مع التزام غير متزامن - 1500. بالطبع ، في نظام حقيقي تحت الحمل الحقيقي ، ستكون النسبة مختلفة ، لكن من الواضح أن التأثير يمكن أن يكون مهمًا للغاية في المعاملات القصيرة.

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

وبعد ذلك ، سيكون لدينا مغامرات مثيرة في عالم الأقفال ، لكن هذه قصة أخرى.

Source: https://habr.com/ru/post/ar461523/


All Articles