این صفحه، یکی از شاخههای خوشهٔ «دیتابیس و پرفورمنس» است و موضوعش یک جدول است که کمتر سراغش میروند: wp_options و ردیفهایی که علامت autoload دارند. دلیل انتخاب این زاویه، یک تجربهٔ تکراری است: سایتی کند است، صاحب سایت افزونهها را یکییکی خاموش میکند، هاست را ارتقا میدهد، و عدد تکان نمیخورد؛ چون هزینه در کد امروز نیست، در ردیفهایی است که سال پیش نوشته شدهاند و هیچکس دیگر آنها را نمیخواند.
نکتهای که در بیشتر راهنماها غایب است، همین است: «کوچککردن جدول» یک هدف نیست. هدف، کمکردن بایتهایی است که در هر درخواست از پایگاه داده به حافظهٔ PHP منتقل و تجزیه میشوند. تفاوت این دو هدف، گاهی یک سایت سالم است و گاهی یک صفحهٔ سفید بعد از یک «پاکسازی».
خودکارخوان یعنی چه؛ پیش از اجرای کد تو
وقتی درخواستی به وردپرس میرسد، پیش از آنکه فایل قالب یا هیچ افزونهای اجرا شود، یک کوئری روی جدول گزینهها اجرا میشود و تمام ردیفهایی را که در ستون autoload مقدار فعال دارند، یکجا برمیدارد. این خواندن شرطی نیست: به مسیر صفحه ربط ندارد، به اندازهٔ نوشته ربط ندارد، به اینکه مخاطب وارد شده یا نه ربط ندارد. صفحهٔ خطای 404 همان بار داده را میخواند و بخش مدیریت هم.
کلمهٔ «خواندن» را دو بخش کن، چون هزینه در هر دو است. بخش یک، انتقال بایتها از موتور پایگاه داده به حافظهٔ فرآیند PHP است. اگر مجموع این بایتها چند مگابایت شود، همان حجم باید از اتصال بگذرد و در یک آرایهٔ بزرگ جا بگیرد؛ این کار در کوئریهای سادهٔ SELECT زمان میبرد و بعد هم در کپیکردن رشتهها در حافظه. بخش دو، گشودن قالب سریال است. وردپرس در نسخههای تازهتر همان هنگام بارگذاری، مقدارها را از قالب سریال بیرون میآورد و در نسخههای قدیمیتر این کار را در نخستین تماس به get_option انجام میداد. برای تو فرقی نمیکند: در دو حالت، همان بایتها باید تجزیه شوند.
به همین دلیل، autoload یک معامله است، نه یک کلید. گزینهای که خودکارخوان است، در هر درخواست با بقیه میآید و بار دوم خواندنش رایگان تمام میشود. گزینهای که نیست، در هر درخواستی که واقعاً لازم شود، یک کوئری جدا میگیرد. معامله برای گزینهٔ کوچک و پراستفاده بهنفع حالت اول است و برای گزینهٔ بزرگ و کماستفاده بهنفع حالت دوم. سایتهای کندِ این دسته، دقیقاً همانجا اشتباه کردهاند که معامله را برای گزینههایی بستهاند که دیگر هیچجا خوانده نمیشوند.
هزینهٔ دوم را هم دستکم نگیر: حافظه. هر بایتی که به آرایهٔ گزینهها میآید، تا پایان همان درخواست آنجا میماند و از سقف memory_limit کم میکند. در نتیجه اولین نشانهٔ یک بار خودکارخوانِ سنگین، همیشه کندی نیست؛ گاهی خطای تمامشدن حافظه در صفحهٔ مدیریت است، بدون هیچ تغییر تازه در سایت.
چرا حذف افزونه، بار را پایین نمیآورد
سه عمل را از هم جدا کن، چون فقط یکی از آنها میتواند ردیفی را پاک کند. غیرفعالکردن افزونه، فقط خطِ فعالسازی را در فایل افزونهها تغییر میدهد و به جدول گزینهها دست نمیزند. حذف افزونه از پیشخوان، فایلها را از روی دیسک میبرد و اگر افزونه فایل حذفِ اختصاصی نداشته باشد، هیچ ردیفی را پاک نمیکند. تنها کاری که ردیفها را میبرد، کدی است که خودِ افزونه در همان فایل حذف نوشته باشد؛ و واقعیت این است که بخش بزرگی از افزونهها یا آن فایل را ندارند، یا در آن فقط گزینههایی را پاک میکنند که در نسخهٔ اول داشتهاند و گزینههای اضافهشده در نسخههای بعدی را فراموش میکنند.
یک تفصیل دیگر هم هست که مقصر اصلی است: امضای تابع افزودن گزینه، حالت پیشفرض علامت را روی فعال میگیرد. یعنی هر افزونهای که گزینهاش را با فراخوانی ساده ساخته، از روز اول خودکارخوان بوده است؛ نه از سر بیدقتی، بلکه چون پیشفرضِ API همین بود. بعضی افزونهها هم آگاهانه فعال میگذارند، چون گزینهشان در هر صفحه لازم است؛ مثل تنظیمات ظاهری یک صفحهساز. پس «خودکارخوان بودن» بهخودیخود خطا نیست؛ خطا، ماندنِ آن برای دادهای است که دیگر مصرفکنندهاش وجود ندارد.
نتیجه: ردیفهای یتیم، هزینهٔ دائمی دارند. هر بار که افزونهای عوض میشود، یک لایه به بار هر درخواست اضافه میشود و صاحب سایت هیچوقت امضا نمیکند. در شبکهٔ چندسایتی، این وضعیت بدتر است: برای هر زیرسایت یک نسخه از همان ردیف میماند و پاککردن، باید در هر سایت جدا انجام شود.
اندازهگیری صادقانه؛ سه کوئری که عدد میدهند
قبل از هر تصمیم، سه عدد بگیر. اول، جمع حجم به تفکیک حالت علامت. دوم، توزیع حجم در سطلها. سوم، فهرست متهمهای نخست با سرآغاز مقدار. سه کوئری زیر همین را میدهند و هیچکدام چیزی را تغییر نمیدهند:
SELECT autoload,
COUNT(*) AS row_count,
SUM(LENGTH(option_value)) AS payload_bytes,
ROUND(SUM(LENGTH(option_value)) / 1048576, 2) AS payload_mb
FROM wp_options
GROUP BY autoload
ORDER BY payload_bytes DESC;
SELECT CASE
WHEN LENGTH(option_value) < 1024 THEN '01_under_1kb'
WHEN LENGTH(option_value) < 16384 THEN '02_1_16kb'
WHEN LENGTH(option_value) < 65536 THEN '03_16_64kb'
WHEN LENGTH(option_value) < 262144 THEN '04_64_256kb'
ELSE '05_over_256kb'
END AS bucket,
COUNT(*) AS row_count,
SUM(LENGTH(option_value)) AS bucket_bytes
FROM wp_options
WHERE autoload IN ('yes', 'on', 'auto')
GROUP BY bucket
ORDER BY bucket;
SELECT option_id,
option_name,
autoload,
LENGTH(option_value) AS bytes,
ROUND(LENGTH(option_value) / 1024, 1) AS kb,
LEFT(option_value, 120) AS head
FROM wp_options
WHERE autoload IN ('yes', 'on', 'auto')
ORDER BY bytes DESC
LIMIT 25;چند نکتهٔ خواندن این خروجی، که بدون آنها عدد گمراهکننده است. تابع LENGTH شمارش را بایتبایت انجام میدهد و CHAR_LENGTH نویسهبهنویسه؛ برای کار ما LENGTH درست است، چون قالب سریالِ PHP هم طولها را بر حسب بایت مینویسد و متن فارسی در کولیشن چندبایتی، هر نویسه را دو بایت میگیرد. اگر با CHAR_LENGTH اندازه بگیری، حجم واقعی رشتههای فارسیدار را کمتر نشان میدهد.
کوئری سوم، فقط فهرست بزرگها نیست؛ ستون آخرش، ابزار تشخیص است. سرآغاز مقدار را نگاه کن: اگر با حرف a: شروع شود، آرایه است؛ اگر با O: شروع شود، شیء سریالشده است و به کلاسی وابسته است که باید هنوز وجود داشته باشد؛ و اگر با s: شروع شود ولی ادامهاش یک بلوک سریال باشد، وارد حالت دونکدی شدهایم. همانجا میفهمی کدام ردیفها قابل دستکاریاند و کدامها اصلاً نباید لمس شوند.
توزیع را ببین، نه فقط جمع
جمع حجم، تصمیم نمیدهد؛ توزیع تصمیم میدهد. دو سایت میتوانند هر دو چند مگابایت بار خودکارخوان داشته باشند و دو درمان متفاوت بخواهند. اگر حجم در چند ردیفِ بزرگ متمرکز باشد، کار روی همان چند ردیف، بیشترِ هزینه را میبرد. اگر در هزاران ردیفِ کوچک پخش باشد، حذف تکتکها بیفایده است و باید الگوی تولیدکنندهشان را پیدا کنی: معمولاً یک افزونه که برای هر بازدید یا هر ورودی، یک ردیف مستقل و خودکارخوان میسازد.
ردیفهای کوچک هم بیهزینه نیستند. هزینهٔ هر ردیف دو قسم است: بایتهای خودش و سربارِ هر ردیف در ساخت آرایه و در کوئری. پس اگر شمار ردیف از چند دههزار گذشت، حتی با اندازههای کوچک، باز هم وقت میبازی. برای همین هر دو ستون در کوئری اول لازماند: شمار و حجم.
علامت تشخیص الگوی دوم را ساده میکنم: اگر در فهرست متهمها، نامهاشان پیشوند مشترک دارند و شمارشان هر روز بیشتر میشود، با دادهٔ روبهرشد طرفی، نه با زبالهٔ گذشته. درمان زبالهٔ روبهرشد، پاککردن نیست؛ تنظیم چرخهٔ حذف خودکار است، وگرنه هفتهٔ بعد همان عدد اینجاست.
مقادیر علامت در نصبهای مختلف یکی نیست
بیشتر نوشتهها فرض میکنند ستون autoload فقط دو مقدار دارد: فعال و غیرفعال. این فرض روی هستههای قدیمی درست بود و روی نصبهای امروزی، نه. در نسخههای تازهٔ وردپرس برای مدیریت همین مشکل، حالتهایی بهجز دو مقدار کلاسیک اضافه شده است: yes و no از قدیم، و کنارشان on و off و auto برای گزینههای بزرگ و قابلتغییر. معنای auto به نسخه بستگی دارد و هسته خودش تصمیم میگیرد که این ردیف در بارِ اولیه بیاید یا نه؛ پس ارزشش را دستی و با حدس عوض نکن.
روی اینها، ردیفهایی را هم اضافه کن که ابزارهای مهاجرت، اسکریپتهای میزبان، و افزونههای بهینهسازی ساختهاند: برخی 0 و 1 مینویسند، برخی false، برخی رشتهٔ خالی، و برخی حتی فاصله. نتیجهٔ عملیاش این است که کوئریِ فقط-yes روی بعضی نصبها حجم واقعی را کم نشان میدهد و روی بعضی، ردیفهای فعال را ناپدید میکند. قبل از هر قضاوتی، خودِ نصب را بشمار:
SELECT autoload,
COUNT(*) AS row_count,
SUM(LENGTH(option_value)) AS payload_bytes
FROM wp_options
GROUP BY autoload
ORDER BY payload_bytes DESC;دو دامِ ریز در همین شمارش هست. اول، کولیشن: اگر مقایسهٔ متن در جدول بیتوجه به بزرگی و کوچکی حرف باشد، YES و yes برای کوئری یکیاند؛ پس خروجی را با چشم چک کن، نه فقط با عدد. دوم، ستون از نوع رشتهای است که فاصلهٔ انتهایی در مقایسه نادیده گرفته میشود؛ یعنی مقداری که با یک فاصله ذخیره شده، با مقدار بدون فاصله برابر دانسته میشود. هر دو حالت، «تفاوتی که در نوشتهها نیست» را توضیح میدهند. قانون ساده: هر ادعایی دربارهٔ علامتها را با GROUP BY روی پایگاه خودت بسنج؛ هیچ مقالهای، حتی همین صفحه، آمار نصب تو را نمیداند.
قالب سریال و چرا طولها بخشی از متناند
وردپرس بیشتر مقدارهای پیچیده را در قالب سریالِ PHP ذخیره میکند. در این قالب، هر بخش پیش از مقدارش طول میگیرد: نوع، سپس اندازه. رشته با s: میآید و بعد طول بایتی آن رشته؛ آرایه با a: و شمار عضوها؛ و شیء با O: و نام کلاس. یعنی متن و طول، هر دو در یک رشتهٔ ساده نوشتهاند و هیچ جداکنندهای بین «محتوا» و «ساختار» نیست.
اینجا دو پیام عملی هست. پیام اول: طولها بایتاند، نه نویسه. اگر متن را در یک اتصال با کولیشن دیگری بخوانی و بنویسی، یا اگر ابزاری جایگزینی را نویسهای انجام دهد و طول را بایتی بفهمد، شمارشها با متن نمیخوانند. پیام دوم و مهمتر: تغییر حتی یک نویسه درون متن، بدون بهروزرسانی طولها، کل ساختار را بیاعتبار میکند. تابع گشودن سریال، اولین جای ناهمخوانی که ببیند، بیهیچ توضیحی شکست میخورد و نتیجه، مقدار false است. وردپرس در مقابل false چه میکند؟ رشتهٔ خام را برمیگرداند، چون فرضش این است که مقدار ساده بوده. یعنی خطا بیصدا از کوئری به کد منتقل میشود و تبدیل میگردد به «چیزی که آرایه نیست».
وقتی کدی که انتظار آرایه دارد رشته بگیرد، رفتارها متنوع و همه بد هستند: ویجتی که هیچ چیزی چاپ نمیکند، هشدارِ عضو غیرتعریفشده در لاگ، صفحهای که نیمهکاره میماند، و در بدترین حالت: ذخیرهٔ مجدد همان گزینه با مقدار خالی. آن حالت آخر، همانجا داده را برای همیشه از بین میبرد، بیهیچ پیام خطا روی صفحه.
مقداری که سریالشده به نظر میرسد ولی باز نمیشود
حالت دونکدی، قهرمانِ پنهان این ماجراست و زیاد دیده میشود. در این حالت، مقداری که باید یک آرایهٔ سریالشده باشد، داخل یک رشتهٔ سریالشده دیگر پیچیده شده است: بیرونی s: است و درونش، متنی که شکل a: دارد. ظاهرش در جدول گمراهکننده است؛ با چشم نگاه میکنی و میگویی «اینکه سالم است». اما get_option فقط یک لایه را باز میکند، چون طراحیاش همین است: اگر بیرون سریال بود، یکی بازش کن. لایهٔ دوم برای وردپرس یک رشتهٔ معمولی است، نه دادهٔ ساختاریافته.
دو راه ورود به این حالت را بارها دیدهام. راه اول: رشتهای که از قبل سریال بود را به مسیر ذخیرهسازی گزینه دادهایم؛ مثل زمانی که خروجی یک کوئری دستی را با update_option مینویسیم. راه دوم: ابزاری که «پاکسازی کاراکتر» یا «تبدیل کدگذاری» انجام میدهد و ناخواسته لایهای اضافه یا کم کرده است. نشانهٔ تشخیص در همان ستون head کوئری سوم است: اگر مقدار با s: شروع شد و بیست نویسهٔ بعدش a: یا O: بود، لایهٔ اضافی داری. مقایسهٔ دقیق پیش و پس از یک بازنویسیِ نادرست، ایناست:
EXPECTED a:2:{s:5:"color";s:7:"#224466";s:5:"count";i:3;}
gettype( get_option( 'x' ) ) => "array"
DOUBLE s:61:"a:2:{s:5:\"color\";s:7:\"#224466\";s:5:\"count\";i:3;}"
gettype( get_option( 'x' ) ) => "string"
BROKEN a:2:{s:5:"color";s:7:"#112233";s:5:"count";i:3;}
unserialize( <raw> ) => false
gettype( get_option( 'x' ) ) => "string" (raw text, not array)حالت DOUBLE در جدول وسوسهکنندهتر است، چون هیچ چیزی خراب نشده به نظر میرسد: طولها با متن میخوانند و متن داخلش هم دستنخورده است. خرابی فقط وقتی معلوم میشود که کد، رشته را آرایه فرض کند. تشخیص سالم بودن همیشه با ظاهر نیست؛ تشخیص با نوع دادهای است که در PHP بیرون میآید.
جستوجو و جایگزینی روی ستون مقدار؛ هشدار اصلی این مقاله
بدترین حرکتی که روی این جدول زده میشود، یک دستور است: بازنویسی متن سراسری روی ستون مقدار، معمولاً برای عوضکردن یک دامنهٔ قدیمی یا یک رشتهٔ تکراری. این دستور در ظاهر منطقی است و در عمل، همان کاری است که سایت را از پا درمیآورد، چون طولهای داخل ساختار را نمیشناسد و تغییرشان نمیدهد. متن عوض میشود، طول ثابت میماند، و گشودن سریال در اولین ناهمخوانی میشکند.
روی همین سناریو، دو پیامد روی هم مینشینند. اگر بلوک سریالشده آرایه بوده باشد، شکست گشودن، رشتهٔ خام را برمیگرداند و همان مسیرِ خرابیِ بیصدای بالاتر را میرویم. اما اگر درون مقدار، شیء سریالشده باشد، وضعیت خطرناکتر است: کلاس آن شیء متعلق به افزونهای است که حذف شده. PHP در این حالت شیئی از کلاس جانشین میسازد که نامش __PHP_Incomplete_Class است؛ یعنی بدنهٔ داده میآید ولی رفتار نیست. هر کدی که بعداً روی آن مقدار، فراخوانی روش یا دسترسی به ویژگی انجام دهد، با خطای کشنده میایستد. بدتر: همان شیءِ ناتمام در آرایهٔ گزینهها مینشیند و در هر درخواست دوباره ساخته میشود؛ یعنی خرابی، تصادفی و دیررس نیست، بلکه ثابت است.
نکتهٔ اصلی که میخواهم بگویم همین است: درمان، «حذف ردیفهای بزرگ» نیست. درمان، «فهمیدن کدام ردیف امن است». ردیف بزرگ و سادهٔ متنی، کاندید خوب است. ردیف بزرگ و سریالشده، با شیء و کلاس و ارجاع، کاندید بد است. و ردیفی که کلاسش دیگر وجود ندارد، کاندید حذف است، نه بازنویسی؛ چون بازنویسی فقط جسد را خوشکل میکند. اگر مجبور به جایگزینی متن شدی، از ابزاری استفاده کن که قالب سریال را میشناسد و همانجا طولها را دوباره میسازد، نه از دستور خامِ جایگزینی روی ستون. جایگزینی روی پایگاهِ کپیشده در محیط آزمایش، و بعد مقایسهٔ نوع داده، نه مقایسهٔ طول رشته.
بعد از نوشتن، نوع داده را اثبات کن
«یک ردیف بهروز شد» هیچ چیز ثابت نمیکند. این جملهای است که موفقیت کوئری را با سلامت داده اشتباه میگیرد، و دقیقاً همانجا فاجعه شروع میشود که بازنویسی با موفقیت اجرا شده و سایت تا چند ساعت بالا میماند. چون ردیف مربوط به صفحهای است که امروز کسی باز نکرده. برای اثبات، باید از همان سمتی نگاه کنی که مصرفکننده نگاه میکند: از درون PHP، با یاریرسان خط فرمان یا یک اسکریپت موقت:
$name = 'my_plugin_settings';
$raw = $wpdb->get_var(
$wpdb->prepare( "SELECT option_value FROM {$wpdb->options} WHERE option_name = %s", $name )
);
$value = get_option( $name, null );
fwrite( STDERR, sprintf(
"name=%s raw_head=%s php_type=%s php_count=%s class=%s\n",
$name,
substr( (string) $raw, 0, 16 ),
gettype( $value ),
is_array( $value ) ? count( $value ) : '-',
is_object( $value ) ? get_class( $value ) : '-'
) );سه سطر از این خروجی، سه تشخیص میدهد. raw_head که با s: شروع شود و بعدش a: بیاید، لایهٔ اضافی را نشان میدهد. php_type که string باشد در حالی که قبلاً array بوده، یعنی همان بازنویسی ساختار را شکسته است. class که __PHP_Incomplete_Class برگرداند، یعنی ردیف باید حذف شود، نه اصلاح؛ نگهداشتنش یعنی هر روز یک قدم به خطای کشنده نزدیکتر.
دامِ ظریفِ همین بخش را جدا بگویم: اگر مقدارِ سریالشدهٔ دستی را به مسیرِ بهروزرسانیِ گزینه بدهی، همان مسیر یک لایهٔ دیگر روی آن میپیچد. دستور مینویسد، کوئری موفق است، جدول هم سالم به نظر میرسد، و فردا همان داده، رشته است نه آرایه. پس بعد از نوشتن، دوباره از همان API بخوان و نوع را با مقدار قبل از تغییر مقایسه کن؛ اگر نوع عوض شده، تغییرت ناموفق بوده هرچه کوئری بگوید. و برای اطمینان از خودِ سنجش: پنج بار عدد زمان پاسخ را بگیر و میانهاش را با میانهٔ قبل مقایسه کن، چون تغییر چند صد کیلوبایت در حافظه، گاهی فقط با تکرار اندازهگیری قابل دیدن است.
حذف ردیف یا خاموشکردن علامتش؛ دو کار متفاوت
این دو عمل را بهجای هم استفاده میکنند و باید جدا شوند. خاموشکردن علامت، داده را نگه میدارد و فقط از بارِ هر درخواست بیرونش میکند؛ هزینهٔ جابهجایی: هر فراخوانی واقعی، یک کوئری اضافه میگیرد. حذف ردیف، داده را از بین میبرد؛ هزینهٔ جابهجایی: فقط برگرداندن از پشتیبان. بازگشتپذیری هم فرق دارد: یک کوئری برای خاموشکردن، یک کوئری برای روشنکردن؛ اما حذف، هر وقت اشتباه باشد، یعنی بازگردانی.
کدام کِی درست است؟ اگر مطمئنی افزونه حذف شده و برنمیگردد، و اگر کوئریهای سرآغاز مقدار نشان میدهد مصرفکنندهای ندارد: حذف، با پشتیبانِ آزمودهشده. اگر افزونه موقتاً غیرفعال است یا ممکن است برگردد: خاموشکردن علامت، نه حذف؛ چون با برگشتن افزونه، دادهاش هست و تنظیم کاربر از دست نمیرود. اگر داده لازم است ولی کم و در صفحههای محدود: خاموشکردن علامت، و بعد اندازهگیری؛ اگر شمار کوئریها بالا رفت، به حالت اول برگرد. اگر داده بزرگ است و افزونه هنوز زنده است و آن را میخواهد: نه حذف، نه خاموشی؛ مشکل از طراحی آن افزونه است و با تنظیمات کاهش حجم یا جایگزینیاش حل میشود، نه با جراحی جدول.
و یک قاعده که بحثناپذیر است: هر تغییر گروهی، روی این جدول، فقط با پشتیبانِ آزمودهشده و فقط اول روی محیط آزمایش. آزمودهشده یعنی یک بار بازگردانده باشی و سایت بالا آمده باشد، نه اینکه فایل پشتیبان در پوشه باشد. تغییر گروهی یعنی یک بستهٔ کوچک، با فهرست مکتوب نام گزینهها؛ تا اگر خراب شد، بدانی چه را برگردانی.
نوع ردیف، چه کاری میکند، اقدام امن
| نوع ردیف | چه کاری میکند | اقدام امن | | — | — | — | | ردیف موقت با مهلت انقضا | نتیجهٔ موقت یک محاسبه را نگه میدارد و تاریخ پایان دارد | معمولاً حذف امن، ولی اگر شمارشان زیاد است علت تولید را ببند | | ردیف تنظیم افزونهای که نیست | تنظیمی که هیچ کدی نمیخواند، در هر درخواست بار میشود | حذف، بعد از یکبار اطمینان از نبودِ مصرفکننده | | ردیف تنظیم قالب | چیدمان و رنگ را نگه میدارد و قالب به آن وابسته است | دست نزن؛ اگر بزرگ است، علت رشد را پیدا کن | | ردیف ابزارک و منو | ترتیب و محتوای بخشهای ظاهر را نگه میدارد | دست نزن؛ فقط اندازهگیری | | ردیف رویداد زمانبندیشده | صف کارهای پسزمینه؛ میتواند متورم و قفلشده شود | پاککردنش یعنی کشتن زمانبندیها؛ فقط رویداد مانده را اصلاح کن | | ردیف گزارش و رویداد | افزونهای کل دفتر رویدادها را در یک گزینه مینویسد | چرخهٔ چرخش لاگ از تنظیمات افزونه، نه با حذف دستیِ نصفه | | ردیف سریالشده با کلاسِ رفته | کلاسش با افزونه حذف شده و خودِ خواندنش خطای کشنده میسازد | حذف نامزد است؛ بازنویسی نه | | ردیف دونکدی | یک لایهٔ رشته روی دادهٔ ساختاریافته | بازسازی از منبع، نه بازنویسی طولها با دست |
چه کارهایی کمک نمیکند
کش شیء، بار اول را حذف نمیکند. اگر کش پایا فعال باشد، آرایهٔ گزینهها از کش خوانده میشود و هزینهٔ کوئری و تجزیه برای همان بازهٔ معتبر حذف میشود. اما دو سوراخ دارد. یک: بعد از هر پاکشدن کش، بالاآمدن سرویس، یا تغییر نسخهٔ کش، اولین بازدیدکننده کل هزینه را میپردازد؛ دقیقاً همان چیزی که روی موبایل و در ساعت اوج، به چشم میآید. دو: اگر کش پایا نباشد، کشی وجود ندارد که چیزی را نگه دارد و هر درخواست همان خواندن را از اول انجام میدهد؛ نصب افزونهٔ کش شیء روی هاستی که پشتیبانیاش ندارد، فقط یک فایل اضافه است.
کش صفحه، هزینه را پنهان میکند نه برداشتن. برای بازدیدکنندهٔ ناشناس که پاسخ کششده میگیرد، اصلاً به PHP نمیرسیم، پس ردیفهای خودکارخوان هم خوانده نمیشوند. ولی هر مسیری که کش نمیشود همان هزینه را میپردازد: کاربر واردشده، سبد خرید و پرداخت، تماسهای رابط برنامهنویسی، بخش مدیریت، کارهای زمانبندیشده، و ابزارهای خط فرمان. اگر عددِ صفحهٔ کششده خوب و عددِ صفحهٔ کشرد شده بد است، کش درست کار میکند و مشکل همان بارِ خودکارخوان است.
بهینهسازی جدول، فضا میگیرد نه زمان. بازسازی فضای آزادِ ردیفهای پاکشده، اندازهٔ فایل دیسک را کم میکند و در بکاپ اثر دارد؛ سرعت بارِ خودکارخوان را نه، چون کوئری بر حسب محتوای ردیفهای فعال است نه بر حسب سوراخهای فایل.
بالا بردن سقف حافظه، علامت را ساکت میکند. خطای حافظه برطرف میشود و همان بایتها همانجا میمانند و همانقدر زمان میبرند.
حذف همهٔ ردیفهای بزرگ، یک نسخهٔ واحد نیست. از همان جدول بالا: بعضی بزرگها، منبع حیات قالباند.
توالی امن برای یک پاکسازی
- پیش از هر کاری، زمان پاسخ صفحهٔ کشردشده را پنج بار بگیر و میانه را بنویس. عدد مبدأ بدون این کار، فقط یک حس است.
- سه کوئری اندازهگیری را اجرا کن و خروجی را در یک فایل نگه دار؛ این فایل، مبنای مقایسهٔ هفتهٔ بعد است.
- فهرست 25 ردیف نخست را با چشم بخوان و هر کدام را در یکی از دستههای جدول بالا بگذار: امن، مشکوک، خطرناک.
- پشتیبان بگیر و بازگردانیاش را روی محیط آزمایش تست کن. همین، نه نسخهٔ پشتیبان، شرط شروع کار است.
- یک ردیف را بردار، یا علامتش را خاموش کن. یک تغییر، یک عدد.
- نوع دادهٔ هر گزینهٔ همخانوادهاش را در
PHPچک کن، و سه صفحهٔ اصلی را باز و مرور کن: خانه، نوشتهٔ تکی، بخش مدیریت.
- اندازهگیری را دوباره بگیر؛ اگر تکان نخورد، آن ردیف مقصر نبود و فرضیهٔ بعدی را امتحان کن.
- هر چه کردی را بنویس: تاریخ، نام گزینه، عمل، مقدار قبل، مقدار بعد. این دفترچه، تنها چیزی است که شش ماه بعد، وقتی عدد بدتر شد، کارت را نجات میدهد.
جمعبندی
ردیفهای خودکارخوان، تنها هزینهای هستند که در هر درخواست، پیش از کد تو و بیآنکه لازم باشند، پرداخت میشوند؛ و تنها هزینهای که حذف افزونه، خودبهخود پاکشان نمیکند. درمان از سه عدد شروع میشود: جمع حجم به تفکیک حالت علامت، توزیع حجم در سطلها، و فهرست متهمها با سرآغاز مقدار. از آنجا، تفکیک اصلی شروع میشود: کدام ردیف متن ساده است و کدام ساختار سریالی با طول و کلاس.
هشدارِ اصلیِ مقاله را دوبار بخوان: بازنویسی متن سراسری روی ستون مقدار، ساختار را میشکند، نتیجهاش در کوئری موفق به نظر میرسد، و اگر کلاس غایب باشد همان مقدار به __PHP_Incomplete_Class تبدیل میشود و سایت را میخواباند. اثبات هر تغییر، نوع داده در درون PHP است، نه تعداد ردیفهای بهروزشده. و هیچکس، نه یک نوشتهٔ وبلاگی و نه همین صفحه، نمیداند نصب تو از کدام علامتها استفاده میکند؛ GROUP BY بزن و خودت نگاه کن.
اعضای این خوشه، هر کدام یک طرف دیگر از همین جدول را کامل میکنند: تفاوت موتورهای پایگاه داده و اینکه چرا کوئریِ یکسان روی دو موتور دو زمان میگیرد، تورم دیتابیس و روش امن پاکسازیاش، و ردیابی کوئریهای کند تا بفهمی آیا مشکل از حجم ردیف است یا از طرحبندی کوئری.