3 مهر 1402
تهران، خیابان آزادی، تقاطع قریب
شبکه

هک سیستم داخلی دیتابیس پست‌گرس به منظور ارسال پوش ناتیفیکیشن

postsql دیتابیس

دیتابیس پست‌گرس (PostgreSQL)

پست‌گرس (PostgreSQL) از آخرین ورژن خود یعنی PostgreSQL14 در تاریخ 30 سپتامبر رونمایی کرد که شامل مجموعه‌ای از ویژگی‌ها مانند Pipeline API، جمع‌آوری اطلاعات درباره تاریخچه slotها، پیشرفت در درخواست‌های موازی اطلاعات (کوئری‌) و غیره است. در حالی که منشأ شکل گیری PostgreSQL به سال 1986 برمی‌گردد، این دیتابیس طی 30 سال گذشته مدام در حال توسعه و پیشرفت بوده است. هزاران شرکت، از کوچک و بزرگ و از هر نوع طیف کاری، به Postgres اطمینان کرده‌اند و به جرأت می‌توان گفت که این پایگاه داده «پیشرفته ترین پایگاه داده جهان بر مبنای ارتباط اپن سورس» است.

در یک سیستم معمولی که چندان هم ضعیف نباشد، بیش از یک سرور دیتابیس وجود دارد و داده‌ها اغلب در چندین سرور کپی می‌شوند. در یک تعریف نسبتاً رایج در سیستم توزیع داده، کپی کردن داده‌ها در چندین نود (گره) سرور به عنوان تکرار شناخته می‌شود. فرآیندهای تکرار در اکثر اوقات از سمت کاربران دیتابیس انجام می‌گیرد؛ بنابراین بیشتر لایه‌های اپلیکیشن در انتخاب این که کدام دیتا در کدام نود قرار گیرد آزادی عمل دارند، و اگر شما مانند من یک توسعه دهنده نرم افزار هستید، توقع دارید که همه چیز فقط «کار کند». با این حال، آن‌چه اغلب در پس پرده پیچیده‌ ترین دیتابیس‌های جهان رخ می‌دهد، اغلب به مثابه یک تجربه و یادگیری جذاب است.

در این مطلب، ما به کند و کاو در مکانیزم داخلی Postgres می‌پردازیم تا بیاموزیم که فرایند همانندسازی چگونه اتفاق می‌افتد و یکپارچکی داده‌ها با استفاده از WAL (واقعه‌نگاری پیش از نوشتن) چگونه تضمین می‌شود. سپس به مفاهیم جالب‌تری مانند رمزگشایی منطقی (Logical Decoding) و پلاگین‌های خارجی می‌رسیم؛ و در نهایت شروع به هک کردن کدهایی می‌کنیم که می‌تواند پوش ناتیفیکیشن ارسال کند! من به این امر واقفم که این کار بر خلاف روش‌های رایج ارسال ناتیفیکیشن از سرورهای یک برنامه است، اما چه لذتی در انجام کارهای معمولی و خسته کننده وجود دارد؟! بریم که رفتیم!

همانند سازی در زبان پست‌گرس (PostgreSQL)

همانند سازی (Replication) در سطوح بالا، یک فرآیند انتقال داده از یک سرور اولیه به یک سرور مشابه است. در زبان پست‌گرس، سرورها می‌توانند هم در حالت اولیه (اصلی) و هم در حالت آماده به کار (استندبای) قرار داشته باشند. سرورهای اولیه آن‌هایی هستند که داده‌ها را ارسال می‌کنند، در حالی که سرورهای دسته دوم گیرندگان داده‌های تکراری هستند. با اعمال یک سری تنظیمات خاص، سرورهای آماده به کار نیز می‌توانند به عنوان فرستنده عمل کنند. سرورهای ثانویه یا آماده به کار می‌توانند در صورت خرابی سرور اولیه، کار را به دست گیرند، که بر این اساس شکل می‌گیرد که چگونه سیستم‌های دیتابیس، تلرانس خطا را مدیریت می‌کنند.

postsql2

https://www.enterprisedb.com/postgres-tutorials/postgresql-replication-and-automatic-failover-tutorial#replication

واقعه نگاری قبل از نوشتن

WAL (واقعه‌نگاری قبل از نوشتن) یک روش استاندارد برای اطمینان از یکپارچگی داده‌ها در پست‌گرس است. سیستم‌ها از شکست‌های احتمالی ناگزیر هستند و سرورهای دیتابیس نیز از این امر معذور نیستند. تلاش مجدد و مدیریت شکست‌ها بدون از دست رفتن دیتا، در لایه‌های اپلیکیشن، نسبتاً آسان تر است. با این حال، وقتی به عمق پشته (Stack) سازمان داده‌ها می‌رویم، به ویژه در لایه‌ی دیتا، پایداری امری حیاتی است. هنگامی که کاربران  پیش از آن‌که دیتابیس شروع به کار کند، در آن کدنویسی می‌کنند، تغییرات در فایل سیستمی سرور نوشته می‌شود. این کارایی ماندگاری و توان ریکاوری داده‌ها در هنگام خرابی سیستم عامل یا سخت افزار را تضمین می‌کند. فعالیت کاربران از این مکانیزم داخلی منفک بوده و اپلیکیشن‌های متصل به دیتابیس، به طور پیش‌فرض انتظار دارند که همه چیز فارغ از فعالیت آنان اتفاق بیفتد.

ورودی مذکور که بالاتر هم درباره آن صحبت کردیم، به عنوان سوابق ثبت پیش‌نویس (Write-Ahead Log (record شناخته می‌شود، در حالی که فرآیند ثانویه که درباره آن صحبت شد، واقعه‌نگاری قبل از نوشتن (Write-Ahead Logging) نامیده می‌شود. هر رکورد دارای یک شماره توالی است که پس از همگام‌سازی گزارش‌ها با پایگاه داده، به صورت دوره‌ای برای چک پوینت استفاده می‌شود. در موارد خرابی سیستم، از این چک پوینت برای بازخوانی و همگام‌سازی استفاده می‌شود. WAL یک روش مطمئن برای هنگام سازی یا از نو ساختن ترتیب‌بندی داده‌های دیتابیس است که از آن برای تکرار در چندین سرور استفاده می‌شود. WAL می‌تواند داده‌ها را با رویکردی فایل محور یا با رویکردی بر اساس پردازش جریان داده‌ها تکرار کند؛ که هر دو رویکرد مزایا و معایب خاص خود را دارند. پردازش جریان داده‌ها، معمولاً وضعیتی غیر متصل دارد، با این حال می‌توان آن را به صورت متصل نیز تنظیم کرد.

https://hevodata.com/learn/postgres-wal-replication/

رمزگشایی منطقی

سوابق WAL که نمایانگر وضعیت داخلی سیستم دیتابیس هستند، به آسانه توسط سیستم/کاربر خارجی مصرف یا شناخته نمی‌شوند. رمزگشایی منطقی (Logical Decoding) راهی برای نجات از این وضعیت است! رمزگشایی منطقی، فرآیندی برای استخراج تمام تغییرات مداوم جدول‌های دیتابیس، در قالبی مسنجم و قابل درک است که می‌تواند بدون آگاهی دقیق از وضعیت داخلی دیتابیس تفسیر شود. با استفاده از این فرآیند، می‌توان روش‌های تکرار و تمییز را به راحتی به دست آورد.

نمودار زیر فرآیند رمزگشایی منطقی (Logical Decoding) را نشان می‌دهد:

منبع: https://techcommunity.microsoft.com/t5/azure-database-for-postgresql/change-data-capture-in-postgres-how-to-use-logical-decoding-and/ba-p/1396421

برای فعال‌سازی رمزگشایی منطقی (Logical Decoding)، می‌بایست در پیکربندی نمونه Postgres تغییراتی لحاظ کنید:

				
					wal_level = logical # default value is `replica`
max_replication_slots = 1 # good enough for a sample project
max_wal_senders = 1 # default is 10

				
			

منابع: https://www.postgresql.org/docs/current/runtime-config-replication.html و https://www.postgresql.org/docs/current/runtime-config-wal.html برای دریافت جزئیات بیشتر درباره نحوه پیکربندی

هنگامی که پیکربندی را آماده و اجرا کردیم، تغییرات رکوردی به پلاگین خروجی منتقل می‌شود که مرحله‌ای کلیدی در تبدیل از فرمت WAL به فرمت مشخص شده در پلاگین (به عنوان مثال JSON) است. این تغییرات در اسلات(های) تکثیر و برنامه‌های کاربردی می‌تواند جریان آپدیت‌ها واتفاقات در حال وقوع را دریافت کند.

پلاگین‌های خروجی و برنامه‌های کاربردی در تبدیل از فرمت WAL

بیایید برخی پلاگین‌های خروجی و برنامه‌های کاربردی برای این کار را بررسی کنیم:

  • پلاگین خروجی wal2json که خروجی WAL را به آبجکت‌های JSON تبدیل می کند [اپن سورس]
  • برنامه پست‌گرس pg_recvlogical که می تواند جریان به روز رسانی را کنترل کند [بدون نیاز به پیکربندی و پیش فرض با Postgres]
  • پلاگین خروجی decoderbufs که داده ها را به صورت protobuf ارائه می دهد [اپن سورس، مورد استفاده در Debezium]

ما می‌توانیم یک محصول را از ابتدا کدنویسی کنیم یا از ابزارهایی که در مقیاس مورد نظر از قبل آزمایش شده اند استفاده کنیم. Debezium یکی از پرمصرف‌ترین راه حل‌های موجود برای این کار است. ابزار درون سازمانی اپن سورس نت‌فلیکس برای تغییرات ثبت داده‌ها (https://netflixtechblog.com/dblog-a-generic-change-data-capture-framework-69351fb9099b) نمونه‌ی فوق العاده‌ای برای مثال زدن است.

ما چه می‌سازیم؟ بیایید ساده به این قضیه نگاه کنیم. هر زمان که یک کاربر جدید به جدول پست‌گرس وارد شود، ما از گزارش‌های تکرار استفاده کرده و یک ایمیل خوش‌آمدگویی برای او ارسال می‌کنیم.

بیایید کمی هم کد هک کنیم!

ما از یک پکیج زبان برنامه نویسی Go (https://github.com/jackc/pglogrepl) استفاده می‌کنیم که یک کتابخانه تکرار منطقی پست‌گرس است.

مرحله 1- دستورالعمل های تکرار منطقی (Logical Replication) را دنبال کنید:

در بخش ReadMe در این لینک:  https://github.com/jackc/pglogrepl، دستورالعمل هایی گام‌به‌گام برای پیکربندی تکرار منطقی در نمونه پست‌گرس محلی شما  وجود دارد.

مرحله 2- نسخه‌ی دموی موجود در مخزن را امتحان کنید:

این لینک: https://github.com/jackc/pglogrepl/tree/master/example/pglogrepl_demo را امتحان کنید. نسخه‌های دمو نشان می‌دهند که کدهای کتابخانه‌ای چگونه عمل می‌کنند.

مرحله 3- کد نسخه‌ی دمو را برای ثبت ایمیل های جدید به‌روز کنید:

برای ساده‌سازی توضیح این فرآیند، بیایید کدی اضافه کنیم که ایمیل خاصی را که در ردیف جدید درج شده است چاپ می‌کند.

به دنبال متدهای insert بگردید و داده‌های WAL را چاپ کنید:

				
					if logicalMsg.Type() == 'I' {
   // `I` stands for Insert
   log.Println(string(xld.WALData))
   // this logs the complete entry
   // however it would require a little more cleanup
}

				
			

به عنوان مثال، ورودی جدید با آی‌دی 5 و ایمیل tejas@courier.com، به این شکل نشان داده می‌شود: I@Nt5ttejas@courier.com

  • رویه داخلیWAL را پاکسازی کرده و از آن یک ایمیل استخراج کنید:
				
					// find the second column
  walData := xld.WALData[5:]
  pos := bytes.Index(walData, []byte("t"))

  email := walData[pos+1:]
  pos = bytes.Index(email, []byte("t"))

  email = email[pos+1:]
  emailStr := string(email)

				
			
  • ایمیلی با مضمون خوش‌آمدگویی به یک کاربر ارسال کنید:

ما با پیکربندی یک ایمیل انضمامی که توسط Courier پشتیبانی می‌شود، یک تمپلیت جدید ایجاد خواهیم کرد.

				
					messageID, err := client.Send(context.Background(), "VDPE8SWN1K4BWMP8RJ101YRZTF3J", "user-id", 
courier.SendBody{
   	Profile: profile{
   	   Email: emailStr,
   	},
   	Data: data{
   	   Foo: "bar",
   	},
})

if err != nil {
  log.Fatalln(err) 
}

log.Println(messageID)

				
			

کد اصلاح شده را در https://github.com/tk26/pglogrepl بیابید در این صفحه، یک ویدئوی آموزشیِ ضبط شده از صفحه نمایش، برای مشاهده نحوه عملکرد این فرآیند وجود دارد.

اکنون که پست‌گرس را برای ارسال ایمیل (به وسیله گوش دادن به گزارش‌های تکرار) پیکربندی کرده‌ایم، تغییر آن به منظور ارسال پوش ناتیفیکیشن، به سادگی تغییر یک پیکربندی در Courier Studio است.

postsql5

Leave feedback about this

  • کیفیت
  • قیمت
  • خدمات

PROS

+
Add Field

CONS

+
Add Field
Choose Image
Choose Video
X