لاراول 13.38 و خداحافظی با دادههای کهنهٔ replica
خلاصهٔ کاملتر
تیم لاراول نسخهٔ 13.38.0 رو منتشر کرده. اصلیترین قابلیتش middleware جدید PreventStaleDatabaseReads هست، برای اپهایی که یه connection برای نوشتن دارن و یه سری replica (یعنی کپیهای فقطخواندنی دیتابیس) برای خوندن. replica ممکنه چند لحظه از سرور اصلی عقب باشه. نتیجهش اینه که کاربر یه چیزی رو ذخیره میکنه، صفحه رو رفرش میکنه و هنوز دادهٔ قدیمی رو میبینه.
روش کارش سادهست: وقتی یه درخواست روی connection پیشفرض چیزی مینویسه، middleware یه timestamp تو session ذخیره میکنه. تا دو ثانیه بعدش، همهٔ خوندنهای همون کاربر از سرور اصلی انجام میشه و بعد دوباره برمیگرده سراغ replicaها. میشه مدت رو هم برای یه گروه route بیشتر کرد:
Route::middleware(PreventStaleDatabaseReads::for(10))->group(function () {
Route::get('/posts/{post}', [PostController::class, 'show']);
Route::put('/posts/{post}', [PostController::class, 'update']);
});چندتا محدودیت داره: به session نیاز داره، فقط connection پیشفرض رو دنبال میکنه و فقط برای همون کاربری کار میکنه که نوشته. یعنی کاربرهای دیگه هنوز ممکنه دادهٔ قدیمیتر رو از replica بخونن.
متد جدید DB::usingWritePdo() هم اضافه شده. متد قبلی onWriteConnection() فقط روی همون کوئریای اثر داشت که باهاش شروع میشد، پس مثلاً relation ای که از نتیجهش لود میشد باز از replica خونده میشد. حالا هر خوندنی که داخل callback این متد باشه به write connection میره و بعدش تنظیم قبلی برمیگرده، حتی اگه callback خطا بده:
$posts = DB::usingWritePdo(fn () => User::query()
->where('email', $email)
->first()
?->posts()
->get()
);بقیهٔ تغییرها هم به درد میخورن. attribute #[CountCrashesAsExceptions] که قبلاً فقط روی jobها کار میکرد، حالا روی listenerها، mailableها و notificationهای صفی هم جواب میده. قبلاً اگه worker مثلاً به خاطر کمبود حافظه کرش میکرد، با $tries = 0 تلاش دوباره بیانتها ادامه پیدا میکرد. یه باگ هم درست شده که توش #[Connection] مدل فرزند نادیده گرفته میشد و کوئری بیصدا میرفت سراغ connection والد.
برای jobهای یکتایی که با Schedule::job() زمانبندی میشن، اگه ارسال به صف شکست بخوره قفل حالا آزاد میشه. قبلاً با uniqueFor پیشفرض یعنی 0 قفل هیچوقت منقضی نمیشد و scheduler اون job رو برای همیشه رد میکرد. آخر سر هم lazy() و lazyById() تیکهٔ قبلی رو قبل از گرفتن تیکهٔ بعدی از حافظه پاک میکنن، پس اوج مصرف حافظه تقریباً نصف میشه.
نکات کلیدی:
- PreventStaleDatabaseReads بهطور پیشفرض تا ۲ ثانیه بعد از نوشتن، خوندنهای همون کاربر رو به سرور اصلی میفرسته
- این middleware به session نیاز داره و فقط connection پیشفرض رو پوشش میده
- DB::usingWritePdo() برخلاف onWriteConnection() روی relationهای لودشده هم اثر داره
- #[CountCrashesAsExceptions] حالا روی listener، mailable و notification صفی هم کار میکنه
- lazy() و lazyById() دیگه دو تیکه رو همزمان تو حافظه نگه نمیدارن




