توی در مورد batch select ها صحبت کردیم. اما واقعیت اینه نیاز سیستم ما فقط select ها نیستن که در بعضی شرایط نیاز به batch شدن دارن. تاثیر این روش در insert ها به مراتب بیشتر خواهد بود. بیاید یک سناریو رو با هم فرض کنیم و مقاله رو طبق اون جلو ببریم یک مثال ساده فرض کنید با دانش مقاله قبلی و این مقاله میخوایم یک سیستم کوتاه کننده لینک طراحی کنیم که بتونه لود بالا رو پشتیبانی کنه. برای این کار دو بخش رو باید در نظر بگیریم که روی دیتابیسمون لود میاره. توی یوز کیس میخوایم دیتاهایی که توی دیتابیس هست رو توی یک کش key / value بریزیم که بتونیم راحت تر scale کنیم و ریسپانس تایممون به کاربرایی که روی لینکامون کلیک کردن کمتر شه. این کار رو توی مقاله قبلی انجام دادیم توی بخش دوم که مربوط به این مقاله هست میخوایم روی یوز کیسی تمرکز کنیم که مربوط به کاربرایی هستن که میخوان لینک بسازن یا لینکاشون رو آپدیت کنن فرض کنید داریم یک سیستم شبیه توییتر طراحی میکنیم و میخوایم سرویس هش تگ ها رو پیاده سازی کنیم. فرض کنید در ثانیه هزار هشتگ درحال ساخته شدن هست. میخوایم طوری سیستممون رو طراحی کنیم که بتونه این عدد رو پشتیبانی کنه. فرض کنید طراحی دیتابیسمون اینطوره (من برای تست از پستگرس استفاده کردم) یک جدول به اسم hashtags داریم که دو ستون id و hashtag داره و id کلید اصلی ما هست و hashtag هم unique هست. راه حل ساده! به ازای هر درخواست یک INSERT ساده ترین راه حل که به ذهن میاد اینه به ازای هر درخواستی که برامون میاد یک INSERT توی دیتابیس انجام بدیم. بیاید با هم ببینیم چقدر این راه بده! من یک تابع توی Go نوشتم که ورودیش تعدادی هشتگ و کانکشن اتصال به دیتابیس هست. همون طوری که توی کد معلوم هست به ازای هر هشتگ یک کویری INSERT INTO hashtags ( hashtag ) VALUES ( $ ) اجرا میشود. بیاید اول تست کنیم اگه خواستیم فقط تا هشتگ با این روش بسازیم چقدر طول میکشه. کد تست اولمون اینطوره: اگه این کد رو اجرا کنیم خروجی ما میلی ثانیه خواهد بود (یا یه چیزی همین حدودا) عدد خیلی کمیه. حالا اگه جای تا خواستیم تا هشتگ جدید بسازیم چی؟ خروجی ما میشه میلی ثانیه! اگه بخوایم تا رو ذخیره کنیم چی؟ خروجیمون میشه میلی ثانیه. با توجه به اسکیلی که سیستم ما داره میتونه این عددا برای ما قابل قبول باشن. اما چون میدونیم قراره هزار ریکویست برثانیه بگیریم با توجه به عددا حس میکنیم ثانیه طول بکشه! اگه کد رو برای هزار هشتگ اجرا کنیم عددی حدود ثانیه میگیریم و این برای سیستم ما اصلا قابل قبول نخواهد بود. حالا میتونیم یک سری گیر به بنچ مارک من بدیم که قبول هم دارم درست هستن یک) ما تمام insert ها رو داریم توی یک ترد انجام میدیم و این باعث میشه هر کویری منتظر کویری بعدی بمونه تا تموم شه در صورتی که توی دنیای واقعی اینطور نیست. هر ریکویست کاربرا میره روی یک گوروتین و هیج وقت منتظر ریکویست کاربر دیگه ایی نمیمونیم که تموم شه و نوبت کویری ما برسه. دو) ما فقط از یک کانکشن به دیتابیس داریم استفاده میکنیم. در صورتی که برای بهبود پرفورمنس میتونیم چند تا کانکشن باز کنیم من اول از همه سراغ اینا نرفتم تا یکم مسیله رو باز تر کنم. برای الان بهتره واقعی تر کنیم بنچ مارک رو: برای حل مشکل اول من تک تک INSERT ها رو توی یک گوروتین جدا انجام میدم و در نهایت منتظر میمونم تا همشون انجام بشن برای حل مشکل دوم هم تنظیمات کانکشنمون رو توی Go اینطور تنظیم میکنم که کانکشن باز و IDLE بتونیم داشته باشیم. فقط برای اینکه مطمین شیم بیشتر از کانکشن باز نمیکنیم من یک queue با چنل ها ساختم که اجازه نمیده همزمان بیشتر از تا گوروتین INSERT بزنن. کد ما اینطور میشه: خب ببینیم خروجی چطور شد؟ توی رنج میلی ثانیه کدمون اجرا میشه که قابل قبوله. حالا اگه جای هزار تا خواستیم هزار تا insert کنیم چطور؟ حدود دو ثانیه. با استفاده از SELECT sum ( numbackends ) FROM pg _ stat _ database ; هم میتونیم تعداد کانکشن های باز رو ببینیم که بیشتر از تا نمیشه. اما چطور این عددا رو بهتر کنیم؟ راه حل دوم) چندین درخواست رو با هم بزنیم راه حل دوم ما مثل مقاله قبل اینه یک پنجره داشته باشیم و تمام درخواست های insert توی اون پنجره رو با هم بزنیم. مثلا میتونیم تمام درخواست ها رو توی مموری ذخیره کنیم و هر میلی ثانیه یک بار همشون رو با هم بزنیم. برای این کار از bulk insert استفاده میکنیم که احتمالا باهاش آشنایی دارید و خیلی ازشم استفاده کردید. با استفاده از کویری INSERT INTO hashtags ( hashtag ) VALUES ( $ ) ( $ ) ( $ ) و. میتونیم همزمان چندین هشتگ رو insert کنیم. ببینیم پرفورمنس این کد چطوره. برای هزار هشتگ با یک کانکشن تونستیم توی میلی ثانیه INSERT کنیم. تا همین الان بهبود خیلی خوبی دادیم به سیستم. حالا اگه بخوایم هزار تا بسازیم چی؟ حدود میلی ثانیه. همونطوری که می بینید عددا تا همین الان کلی بهبود پیدا کردن و نیازیم به باز کردن کانکشن جدید و ساختن گوروتین ها نبوده. حالا بیایم یکم عدالت رو رعایت کنیم: ) اینبار با تا کانکشن همزمان INSERT رو برای هزار هشتگ انجام بدیم. اگر کد رو اجرا کنیم می بینیم که هزار تا در حدود میلی ثانیه INSERT میشوند که به نسبت ثانیه پیشرفت زیادی هست. اما یک مسیله مهم رو تا الان در نظر نگرفتیم. اگر بین درخواست ها هشتگی بود که از قبل وجود داره چطور؟ فرض کنید طراحی سیستم ما طوریه که نمیدونیم یک هشتگ وجود خارجی داره یا نه. و انبوهی از درخواست ها سمت سیستم ما میاد که نمیدونیم کدوم هشتگ از قبل وجود داره و کدوم رو باید ذخیره کنیم. روش اول و کثیف اینطوره که کل درخواست ها رو بگیریم و یک کویری بزنیم ببینیم کدوم هشتگ ها وجود دارن و اونایی که وجود ندارن رو جدا کنیم و ذخیره کنیم. اول میلیون هشتگ برای واقعی تر کردن تست ریختم توی دیتابیس. من کد رو تا جایی که تونستم بهینه زدم. در مرحله اول تمام هشتگ ها رو با هم سعی میکنیم از دیتابیس بگیریم و میریزیمشون توی یک set . حالا یکی یکی چک میکنیم هشتگ هایی که توی ست نیستن رو توی یک اسلایس جدید میریزیم و میدیم همه رو با هم کد batch مون ذخیره کنه. کد رو برای هزار هشتگ ( تا جدید و قدیمی) اجرا کردم. فرایند پیدا کردن هشتگ های قدیمی میلی ثانیه طول کشید و فرایند ذخیره سازی نیست میلی ثانیه که در کل میشه میلی ثانیه. احتمالا الان براتون سواله خب چرا؟ از قابلیت های دیتابیس استفاده کنیم و کار خودمون رو راحت کنیم دیگه: ) منم باهاتون موافقم. استفاده از ON CONFLICT توی کویریمون میتونیم تعریف کنیم که شما بیا INSERT رو انجام بده حالا اگر یک هشتگی از قبل وجود داشت کاری نکن. برای این کار از ON CONFLICT استفاده میکنیم که خیلی راحت با اضافه کردن کد زیر به تابع Bulk insert مون میتونیم از همون تابع برای این منظور هم استفاده کنیم: شکل جدید تابعمون مثال قبل رو توی میلی ثانیه اجرا میشه که بهبود تقریبا برابری نسبت به حالت قبل و کد بسیار بسیار تمیز تر هست. حالا اگر خواستیم آپدیت کنیم چطور؟ فرض کنید اطلاعات کاربرها همراه هر ریکویست براتون ارسال میشه. میخواد اگر کاربر وجود داشت اون رو با اطلاعات جدید آپدیت کنید و اگر وجود نداشت اون رو بسازیم. با استفاده از ON CONFLICT به راحتی میتونیم همین کارو کنیم. بیایم اول یک ستون جدید به دیتابیس به اسم description اضافه کنیم: حالا میخوایم تعدادی هشتگ با description بسازیم. میخوایم اگر هشتگ از قبل وجود نداشت ذخیره شه و اگر وجود داشت ستون description (فقط) آپدیت بشه. برای این کار میتونیم از کویری زیر استفاده کنیم: برای هزار تا مثل مثال های قبل کد رو اجرا کردم و میلی ثانیه طول کشید. خب الان با ترکیب این مقاله و مقاله قبل به چی میرسیم؟ توی مقاله قبل من سعی ام بر روی این بود که با شو آف کردن قدرت گو: ) یکم در مورد batch کردن درخواست و کم کردن تاثیر شبکه برروی سیستم کیفیت سیستمون صحبت کنیم. با ترکیب این مقاله و مقاله قبلی میتونیم درخواست های SELECT , INSERT و UPDATE سیستممون رو Batch کنیم و فشار روی سیستم رو کمتر کنیم و کیفیتش رو بالا تر ببریم.