وصال دانشور
وصال دانشور
خواندن ۷ دقیقه·۵ ماه پیش

رپلیکا پستگرس - قسمت دوم - تفاوت sync و async

سلام

امیدوارم پست قبلی براتون مفید بوده باشه.

اگر مطالعه نکردید از لینک زیر میتونید بهش سر بزنید.

https://virgool.io/@vessaldaneshvar/%D8%AA%D8%AC%D8%B1%D8%A8%D9%87-%D8%AC%D8%A7%D8%A8%D8%AC%D8%A7%DB%8C%DB%8C-%D9%BE%D8%B3%D8%AA%DA%AF%D8%B1%D8%B3-%D8%A8%D8%A7-%D8%B1%D9%BE%D9%84%DB%8C%DA%A9%D8%A7-%D9%82%D8%B3%D9%85%D8%AA-%D8%A7%D9%88%D9%84-ayuvddolvrb1



ai generated image
ai generated image


در ادامه پست قبلی توی این قسمت قراره چند تا مورد رو بررسی بکنیم.

  • چجوری از حالت async به حالت sync توی رپلیکا بریم
  • تنظیماتی که برای رپلیکا میشه اعمال کرد و مهم هستش رو بررسی بکنیم.




سوییچ از حالت async به sync

برای اینکه دستمون برای تست کردن حالت های مختلف باز باشه من یه رپلیکا دیگه هم اضافه کردم

در واقع الان دو تا سرور رپلیکا داریم و یه سرور پرایمری.

اگر دستور زیر رو اجرا بکنم

select * from pg_stat_replication;

این نتیجه رو برامون برمیگردونه.

pid | usesysid | usename | application_name | client_addr | client_hostname | client_port | backend_start | backend_xmin | state | sent_lsn | write_lsn | flush_lsn | replay_lsn | write_lag | flush_lag | replay_lag | sync_priority | sync_state | reply_time
-----+----------+--------------+------------------+-------------+-----------------+-------------+-------------------------------+--------------+-----------+-----------+-----------+-----------+------------+-----------------+-----------------+-----------------+---------------+------------+-------------------------------
34 | 16388 | replica_user | rep1 | 172.18.0.3 | | 60592 | 2024-06-06 16:50:55.104077+00 | | streaming | 0/F009500 | 0/F009500 | 0/F009500 | 0/F009500 | 00:00:00.000243 | 00:00:00.00132 | 00:00:00.001379 | 0 | async | 2024-06-06 16:51:10.16883+00
35 | 16388 | replica_user | rep2 | 172.18.0.4 | | 32934 | 2024-06-06 16:51:00.042541+00 | | streaming | 0/F009500 | 0/F009500 | 0/F009500 | 0/F009500 | 00:00:00.000245 | 00:00:00.001349 | 00:00:00.00141 | 0 | async | 2024-06-06 16:51:10.168877+00
(2 rows)

الان اینجا ما دو تا رپلیکا داریم. من برای هر کدوم اسم گذاشتم که هم بتونیم تفکیک بکنیم هم یه مثال از حالتی که میشه این ها رو اولویت بندی کرد داشته باشیم. اسم رپلیکا ها که با application_name مشخص شده rep1 و rep2 هستش.

برای نام گذاری هم کافیه توی تنظیمات رپلیکا ها داخل primary_conninfo یه فیلد به نام application_name اضافه بکنیم. اگر چیزی تنظیم نکنیم به صورت دیفالت اسم walreceiver نمایش داده میشه.

primary_conninfo = 'host=primary-psql port=5432 user=replica_user password=password application_name=rep1'

فیلد sync_state

همانطور که از جدول مشخصه مقدار هر دو رپلیکا برای این ستون async هستش
یعنی ابتدا ترنزاکشن ما کامیت میشه و به ما ریسپانس برمیگرده و بعدش لاگش برای رپلیکا ارسال میشه و مقادیر توی رپلیکا هم به روزرسانی میشن. توی این حالت ترنزاکشن ما منتظر رپلیکا نمیمونه و حتی اگر رپلیکا بره پایین یا خیلی latency داشته باشه مشکلی برای سرور پرایمری پیش نمیاد.

من برای اینکه خودم تست بکنم که در عمل اگر latency داشته باشیم چه اتفاقی میوفته نیاز به یه ابزار داشتم که بتونم این مورد رو تست کنم. به طور مثال یه delay چند ثانیه روی شبکه ایجاد بکنم که این به صورت واقعی تست بشه.

من با دستور زیر روی کل شبکه اون کانتینر میتونم یه تاخیر ایجاد بکنم. به هیچ وجه این دستور رو روی سرور های عملیاتی یا حتی سرور های تست اجرا نکنید. چون واقعا فاجعه افرینه. این فقط برای تست لوکال روی سیستم خودتون به درد میخوره.

من از ابزار tc برای اینکار استفاده کردم . به صورت پیشفرض روی ایمیج پستگرس ۱۶ نیستش و اول باید نصبش کنیم

apt update apt install iproute2
tc qdisc add dev eth0 root netem delay 3s

من این دستور رو روی رپلیکا اولم اجرا کردم. توی حالت async وقتی یک دستور insert روی سرور پرایمری اجرا میکنیم بدون مکث ریسپانس برمیگرده. برای دیدن میزان تایم هم میتونیم تایمینگ رو با دستور زیر توی شل پستگرس فعال بکنیم

> \timing Timing is on.

به طور مثال من یه جدول به نام تست ایجاد کردم که فقط یه ستون به نام ایدی داره و یه مقدار داخلش میریزم و تایمش رو محاسبه میکنم. نتیجه در حالت async :‌

insert into test values(1);
INSERT 0 1 Time: 2.015 ms

این نتیجه رو اینجا داشته باشید تا بعدا بهش برگردیم.

کانفیگ سرور پرایمری

برای سوییچ از حالت async به sync ما نیاز به تغییر دادن کانفیگ های رپلیکا نداریم و تمام تنظیمات داخل سرور پرایمری اعمال میشه.

راحت ترین حالت

اول از راحت ترین راه شروع میکنیم. توی این حالت صرفا یکی از سرور های رپلیکا sync میشه که اولویت بندی هم نداره کافیه داخل فایل postgresq.conf تنظیمات synchronous_standby_names رو ویرایش میکنیم. برای synchronous_standby_names مقدار * رو میزاریم.

#synchronous_standby_names = ''
synchronous_standby_names = '*'

پرایمری رو ریستارت میکنیم. الان باید یکی از دو رپلیکا به حالت سینک رفته باشه.

مجدد جدول pg_stat_replication رو فراخوانی میکنیم

pid | usesysid | usename | application_name | client_addr | client_hostname | client_port | backend_start | backend_xmin | state | sent_lsn | write_lsn | flush_lsn | replay_lsn | write_lag | flush_lag | replay_lag | sync_priority | sync_state | reply_time
-----+----------+--------------+------------------+-------------+-----------------+-------------+-------------------------------+--------------+-----------+-----------+-----------+-----------+------------+-----------------+-----------------+-----------------+---------------+------------+-------------------------------
39 | 16388 | replica_user | rep1 | 172.18.0.3 | | 58742 | 2024-06-06 17:58:29.023912+00 | | streaming | 0/F00BED0 | 0/F00BED0 | 0/F00BED0 | 0/F00BED0 | 00:00:00.000149 | 00:00:00.000149 | 00:00:00.000149 | 1 | potential | 2024-06-06 17:58:29.034197+00
40 | 16388 | replica_user | rep2 | 172.18.0.4 | | 35490 | 2024-06-06 17:58:29.024248+00 | | streaming | 0/F00BED0 | 0/F00BED0 | 0/F00BED0 | 0/F00BED0 | 00:00:00.000119 | 00:00:00.000119 | 00:00:00.000119 | 1 | sync | 2024-06-06 17:58:29.034154+00
(2 rows)

همانطور که از جدول بالا مشخصه مقدار sync_state تغییر کرد. تو این حالت الان برای رپلیکا دوم مقدار sync نوشته شده. یعنی بعد از این که ترنزاکشن ما کامیت میشه. لاگش باید به سرور رپلیکا ۲ بره و بعد تاییدش برای ما میاد. رپلیکا یک هم به صورت async اپدیت میشه ولی مقدارش potential هستش و اگر رپلیکا دوم خاموش شد ازش به عنوان رپلیکا سینک استفاده میشه.

یه نکته مهمی هم که در مورد این فرایند باید بدونید اینه که وقتی میگیم منتظر میمونه به این معنیه که اگر تا یک زمان طولانی نتونه بفرسته صرفا پندینگ میمونه و در عمل جدول اپدیت شده و رول بک نمیکنه (‌به صورت پیشفرض)

تعیین حداقل تعداد سینک رپلیکا

یه مقدار کانفیگی که داشتیم رو میتونیم پیچیده تر بکنیم. توی این حالت ما تعیین میکنیم حداقل چند رپلیکا باید سینک اپدیت بشه و متد و اولویت هاشون رو تعیین میکنیم.

synchronous_standby_names = 'FIRST 2 (rep1, rep2, rep3)'

توی مثال بالا منظور اینه که حداقل دو تا سینک رپلیکیشن باید داشته باشی. یعنی توی هر ترنزاکشن منتظر میمونیم دیتا رو به دو رپلیکا که توی لیست بالا هستن بفرستیم.

بر خلاف حالت قبل که اسم رپلیکا براش مهم نبود. توی این حالت باید دقیق مشخص بکنیم که اولویت رپلیکا ها به چه صورته و کدومشون سینک هستند. الان برای ما مثال ما هر دو رپلیکا سینک هست و اگر هر کدوم بره پایین منتظر میمونه بیاد بالا تا دیتا رو براش بفرسته.

خب بریم سراغ اون مبحث زمان بندی که بالا مطرح کردم. نتیجه توی حالتی که ما هیچ تاخیری روی شبکه ایجاد نکردیم :‌

postgres=# insert into test values(2);
INSERT 0 1 Time: 5.647 ms

نتیجه روی حالتی که روی rep1 تاخیر سه ثانیه گذاشتیم.

postgres=# insert into test values(3);
INSERT 0 1 Time: 3006.457 ms (00:03.006)

نتیجه روی حالتی که rep1 سه ثانیه تاخیر و rep2 چهار ثانیه تاخیر داره (درخواست همزمان میره و منتظر میمونه جفتشون اپدیت بشن که میشه ماکسیموم تاخیر بین رپلیکا ها)

postgres=# insert into test values(4);
INSERT 0 1 Time: 4006.963 ms (00:04.007)


وقتی از متد FIRST استفاده میکنیم داریم اولویت بندی میکنیم. یه متد دیگه هم وجود داره به نام ANY که همینکار رو انجام میده بدون اولویت بندی.

تو این حالت الان sync_priority برای rep1 یک هستش و rep2 دو هستش و توی حالت ANY جفتش یک هست.


تنظیم synchronous_commit

در ابتدای این مقاله اشاره کوچکی به اینکه وقتی رپلیکا در دسترس نباشه کردم و گفتم پرایمری منتظر میمونه که اطلاعات رو به رپلیکا در حالت سینک بفرسته و بعد ریسپانس روبه ما برگردونه و همچنین توی حالت پیشفرض رول بک نمیکنه اگر نتونه اطلاعات رو بفرسته.

حالا اینجا تنظیماتی داریم که به ما امکان مدیریت این حالات رو میده و میشه تنظیم کرد تحت چه شرایطی ترنزاکشن انجام بشه یا انجام نشه

مقادیری که میتوان به عنوان مقدار این فیلد ها ست کرد به صورت زیر است.

  • on (default)
  • remote_write
  • remote_apply
  • off
  • local

در حالت اول بعد از این که wal_log به رپلیکا فرستاده میشه و اون روی دیسک ذخیره میکنه ترنزاکشن ما هم تایید میشه. اگر بخوایم ریسک از بین رفتن یکپارچگی داده ها رو به جون بخریم میتونیم این رو بزاریم روی حالت off یا remote_apply . توی حالت remote_apply صرفا اگر دیتا ارسال بشه به رپلیکا (تو بافرش ذخیره کنه)‌ جواب به ما برمیگرده و نیازی نیست توی دیسک بنویسه

ممنون که همراه بودید.

در پست بعدی در مورد دو تا موضوع مهم صحبت میکنیم.

  • سناریویی که رپلیکا رو تبدیل به پرایمری میکنیم و دوباره میخوایم برگردیم به همون ترکیب قبلی
  • ایجاد لود بالانس برای اینکه درخواست ها بتونن توی این سه تا سرور پخش بشن
  • ایجاد ssl برای حفظ امنیت داده ها برای جابجایی بین چند دیتاسنتر


postgresasyncsync
دانشجوی ارشد بهینه سازی، گیک ، فعال در حوزه تحلیل داده ، عاشق دنیای کامپیوتر ها
شاید از این پست‌ها خوشتان بیاید