
به اشتراک بگذارید

به اشتراک بگذارید
ساعت ده شب کمپین شروع میشود، صفحه محصول باز نمیشود و درگاه پرداخت تایماوت میدهد؛ بهینهسازی ووکامرس روی سرور همینجا تعیین میکند که سفارش ثبت شود یا سبد خرید رها بماند. کار چهار لایه دارد: خط پایه، ظرفیت PHP-FPM و OPcache، کش آبجکت و کش صفحه با استثنای سبد خرید، و دیتابیس. به دسترسی root نیاز دارید.
| لایه | چه چیزی تنظیم میشود | ابزار سنجش |
|---|---|---|
| خط پایه | لاگ nginx و slowlog و لاگ کوئری کند | متغیر request_time |
| ظرفیت PHP | مقدار pm و pm.max_children و OPcache | وضعیت استخر FPM |
| لایه کش | کش آبجکت Redis و کش صفحه FastCGI | هدر X-Cache |
| دیتابیس | بافرپول InnoDB و جدول سفارش | لاگ کوئری کند |
💡 خلاصه کاربردی
ترافیک کمپین یعنی درخواست همزمان روی صفحههایی که ووکامرس اجازه کش شدنشان را نمیدهد؛ کار اصلی بالا بردن ظرفیت پردازش و بیرون نگهداشتن صفحه شخصی از کش است.
✅ سه عدد را قبل از تغییر ثبت کنید: زمان پاسخ nginx، پردازه فعال FPM و کوئری کند.
✅ هر تغییر را روی نسخه آزمایشی ببرید و با nginx -t تأیید کنید.
بهینهسازی فروشگاه معمولاً از دم دستترین کار شروع میشود: افزونه کش و چند تصویر کوچکشده. روز کمپین اما همان صفحههایی زیر بار میروند که افزونه کش حق کش کردنشان را ندارد. ترتیب این مقاله برعکس است: از اندازهگیری شروع میشود و هر بخش یک تنظیم دارد با یک راه دیدن اثرش در لاگ.
📌 آنچه در این مقاله میخوانید
◀️ سنجش خط پایه؛ فروشگاه زیر بار کمپین کجا میشکند
◀️ ظرفیت پردازش؛ تنظیم PHP-FPM و OPcache روی رم واقعی سرور
◀️ لایههای کش؛ آبجکت کش Redis و کش صفحه با استثنای سبد خرید
◀️ دیتابیس و روز کمپین؛ جدول سفارشها، کرون و اندازهگیری دوباره

خط پایه سه عدد است: زمان پاسخ nginx برای صفحه محصول و سبد خرید، پردازه فعال PHP-FPM در ساعت شلوغ، و شمار کوئری کند MariaDB. تا این سه عدد نباشند، هر تنظیمی حدس است.
مستندات رسمی nginx متغیر $request_time را «زمان پردازش درخواست بر حسب ثانیه با دقت میلیثانیه» تعریف میکند. $upstream_cache_status در مستندات ماژول upstream آمده و مقدار MISS، BYPASS، EXPIRED، STALE، UPDATING، REVALIDATED یا HIT میگیرد.
# در بستر http { }
log_format shop '$remote_addr [$time_local] "$request" $status '
'$request_time $upstream_cache_status';
access_log /var/log/nginx/shop.log shop;
بعد از یک روز معمولی همین دو ستون را گروهبندی کنید: فرمان اول کندترین آدرسها را با میانگین زمانشان میدهد و دومی درصد HIT را.
awk '{t[$6]+=$5; c[$6]++} END {for (u in t) printf "%.2f %d %s\n", t[u]/c[u], c[u], u}' \
/var/log/nginx/shop.log | sort -rn | head
awk '{print $NF}' /var/log/nginx/shop.log | sort | uniq -c
مستندات PHP دو تنظیم جدا دارد. request_slowlog_timeout زمانی را تعیین میکند که بعد از آن ردپای اجرای PHP در slowlog نوشته میشود، با پیشفرض 0 یعنی خاموش. request_terminate_timeout هم پیشفرض 0 دارد و با روشن شدن پردازه را میکشد. اول فقط slowlog را روشن کنید.
; Debian/Ubuntu: /etc/php/8.3/fpm/pool.d/www.conf (RHEL: /etc/php-fpm.d/www.conf)
slowlog = /var/log/php-fpm/www.slow.log
request_slowlog_timeout = 5s
pm.status_path = /fpm-status
هر ردیف slowlog یک backtrace دارد و تابع تکرارشده در بالای آن نام افزونه مقصر را لو میدهد. صفحه وضعیت تا یک location نداشته باشد پاسخ نمیدهد و احراز هویت هم ندارد؛ این مشاهده ما است، نه جمله سند PHP.
location = /fpm-status {
allow 127.0.0.1;
deny all;
include fastcgi_params;
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
}
اینجا یک تله هست. مرجع متغیرهای سیستمی MariaDB پیشفرض long_query_time را 10.000000 ثانیه ثبت کرده و slow_query_log هم پیشفرض خاموش است. کوئری سنگین فهرست سفارش زیر ده ثانیه است، پس با روشن کردن تنهای لاگ فایل خالی میمانَد و غلط نتیجه میگیرید که دیتابیس سالم است. آستانه را از یک ثانیه شروع کنید.
# /etc/mysql/mariadb.conf.d/60-slow.cnf
[mariadb]
slow_query_log = ON
long_query_time = 1
log_slow_verbosity = query_plan,explain
سمت کش آبجکت، نسبت موفقیت را از keyspace_hits و keyspace_misses بسازید: hits تقسیم بر مجموعشان. این دو و evicted_keys در INFO stats هستند و تعریفشان در مرجع دستور INFO مستندات Redis آمده، نه در صفحه مدیریت حافظه.
تست بار هم لازم است، اما روی نسخه آزمایشی و با درگاه در حالت تست؛ k6 و siege این کار را میکنند. برای سنجش از دید کاربر ایرانی، آموزش تست سرعت در سرور مجازی سمت شبکه را جدا گفته است. تا اینجا، بهینهسازی ووکامرس روی سرور فقط جمع کردن عدد بوده.
ظرفیت پردازش با یک عدد تعیین میشود: pm.max_children. مستندات PHP آن را سقف شمار درخواست همزمانی میداند که پاسخ داده میشود. در بهینهسازی ووکامرس روی سرور این عدد از رم آزاد و مصرف هر پردازه میآید، نه از اینترنت.
مستندات PHP سه مقدار برای pm میشناسد و این تنظیم اجباری است. در static تعداد پردازه ثابت و برابر pm.max_children است. در dynamic با pm.start_servers و دو تنظیم spare تغییر میکند. در ondemand پردازه فقط با رسیدن درخواست ساخته میشود؛ رم ساعت خالی آزاد میشود ولی هزینه ساخت در هر موج میماند. برای کمپین dynamic متعادلتر است.
رم کل را بردارید، سهم سیستمعامل و MariaDB و Redis را کنار بگذارید و باقیمانده را بر میانگین مصرف هر پردازه FPM تقسیم کنید. سهم دیتابیس و Redis را از ستون RSS بخوانید، نه از حدس.
# نام پردازه: Debian/Ubuntu php-fpm8.3 و RHEL فقط php-fpm
ps -o rss= -C php-fpm8.3 | awk '{s+=$1; n++} END {if (n) print s/n/1024, n}'
ps -o rss=,comm= -C mariadbd -C redis-server
صندوق فروشگاه تصویر دقیقی از این عدد است: هر پردازه FPM یک صندوق است که همزمان یک مشتری را جواب میدهد، و صندوق بیشتر از رم موجود سرور را به swap میاندازد. عدد این تقسیم محافظهکارانه است، چون RSS حافظه مشترک را دوباره میشمارد. پیشفرض pm.max_requests صفر است و مستندات PHP عدد دادن به آن را برای نشتی حافظه مفید میداند.
مستندات PHP پیشفرض opcache.memory_consumption را 128 مگابایت، opcache.max_accelerated_files را 10000 با بازه مجاز ۲۰۰ تا ۱۰۰۰۰۰۰ و opcache.interned_strings_buffer را هشت مگابایت ثبت کرده است. فروشگاهی با چند افزونه بزرگ از سقف ده هزار فایل عبور میکند؛ تعداد واقعی را از opcache_get_status() بگیرید، نه از شمارش خام فایلها.
php -r 'print_r(opcache_get_status()["opcache_statistics"]);'
; php.ini
opcache.memory_consumption = 256
opcache.max_accelerated_files = 20000
پیشفرض opcache.validate_timestamps عدد 1 و opcache.revalidate_freq دو ثانیه است. با خاموش کردنش، مستندات PHP پاک کردن دستی با opcache_reset() یا ریاستارت وبسرور را لازم میداند؛ برای فروشگاه پرخطر است.
🔶 یادداشت وبداده
اگر محاسبه بالا نشان داد رم کم است، در پنل سرور مجازی وبداده ارتقای منابع بدون تیکت انجام میشود. سرور مجازی ایران وبداده در دیتاسنتر تهران و روی شبکه رسپینا میزبانی میشود و تست ساعتی رایگان دارد، مناسب ساختن همان نسخه آزمایشی.

فروشگاه دو کش جدا لازم دارد. کش آبجکت نتیجه کوئری را در حافظه نگه میدارد و روی صفحه شخصی هم کار میکند. کش صفحه خروجی HTML آماده را برمیگرداند و فقط برای صفحه عمومی مجاز است. قاطی کردن این دو، منبع بیشتر خرابیهای کمپین است.
مستندات وردپرس میگوید کش آبجکت «بهصورت پیشفرض ناپایدار است» و داده تا نصب افزونه کش پایدار بین صفحهها نمیماند؛ روی سرور خودتان Redis این نقش را دارد. مستندات رسمی Redis میگوید مقدار صفر برای تنظیم maxmemory یعنی بدون محدودیت و پیشفرض سیستم ۶۴ بیتی؛ بدون سقف، Redis تا کشته شدن پردازه رشد میکند. همان سند allkeys-lru را پیشفرض خوبی میداند. سقف را از رم سرور بگیرید؛ عدد زیر نمونه است.
# /etc/redis/redis.conf
maxmemory 512mb
maxmemory-policy allkeys-lru
⚠️ کش را از نشست جدا کنید
سیاست allkeys-lru هر کلیدی را حذف میکند، نه فقط کلید کش. اگر همین Redis نشست کاربران یا صف Action Scheduler را هم نگه دارد، آن کلیدها بیصدا بیرون میافتند؛ برای کش آبجکت یک دیتابیس جدا بدهید. مستندات Redis هم میگوید سیاست volatile- بدون کلید منقضیشدنی مثل noeviction رفتار میکند.
مستندات nginx برای دستور fastcgi_cache_path میگوید داده در فایل ذخیره میشود و کلید از MD5 آدرس میآید؛ یک مگابایت از keys_zone حدود هشت هزار کلید نگه میدارد و inactive پیشفرض ده دقیقه است. fastcgi_cache_bypass شرط «از کش گرفته نشود» را میسازد و fastcgi_no_cache شرط «در کش ذخیره نشود»، پس هر دو لازماند.
مستندات رسمی ووکامرس در راهنمای تنظیم افزونههای کش صریح است: سبد خرید، تسویه حساب و حساب کاربری باید بیرون از کش بمانند، چون مخصوص همان مشتریاند. ?add-to-cart= و ?wc-api= در نمونههای همان سند رد میشوند و بخش عیبیابی آن مسیر /wc-api/ و نشانی logout و lost-password را هم استثنا میکند. پنج کوکی فهرستشده: woocommerce_cart_hash، woocommerce_items_in_cart، wp_woocommerce_session_، woocommerce_recently_viewed و store_notice[notice id]؛ و برای کش دیتابیس، _wc_session_.
دو مورد را خودمان اضافه میکنیم. اول wc-ajax که در سند ووکامرس نیامده، اما پاسخش قطعه سبد همان کاربر است و کش شدنش نشت سبد میسازد. دوم کوکی wordpress_logged_in و wp-postpass_، برای کاربر واردشده و کالای رمزدار.
⚠️ قبل از کپی کردن بلوک زیر این را بخوانید
اسلاگ صفحههای فروشگاه ایرانی تقریباً همیشه فارسی است، مثل /سبد-خرید/، و در آدرس percent-encoded میشود. قاعدهای که فقط cart و checkout لاتین را بگیرد هیچ تطبیقی ندارد: صفحه سبد کش میشود و سبد یک مشتری به بعدی میرسد. کوکی هم کافی نیست، چون بازدیدکننده تازه کوکی ندارد.
اسلاگ واقعی را با wp option get woocommerce_cart_page_id و wp post list --include=<id> --field=post_name بگیرید و هر دو شکل را در نقشه بگذارید.
fastcgi_cache_path /var/cache/nginx/shop levels=1:2 keys_zone=shop:20m
inactive=60m max_size=1g;
map $http_cookie $skip_cookie {
default 0;
"~*woocommerce_cart_hash|woocommerce_items_in_cart|wp_woocommerce_session_" 1;
"~*woocommerce_recently_viewed|store_notice|wordpress_logged_in|wp-postpass_" 1;
}
# اسلاگ واقعی خودتان؛ نمونه: /سبد-خرید/ /تسویه-حساب/ /حساب-کاربری/
map $request_uri $skip_uri {
default 0;
"~^/(cart|checkout|my-account|wc-api|logout|lost-password)(/|\?|$)" 1;
"~^/%D8%B3%D8%A8%D8%AF-%D8%AE%D8%B1%DB%8C%D8%AF(/|\?|$)" 1;
"~^/%D8%AA%D8%B3%D9%88%DB%8C%D9%87-%D8%AD%D8%B3%D8%A7%D8%A8(/|\?|$)" 1;
"~^/%D8%AD%D8%B3%D8%A7%D8%A8-%DA%A9%D8%A7%D8%B1%D8%A8%D8%B1%DB%8C(/|\?|$)" 1;
"~^/(wp-admin|wp-login\.php)" 1;
"~(\?|&)(add-to-cart|wc-api|wc-ajax)=" 1;
}
server {
set $skip_cache $skip_cookie;
if ($skip_uri = 1) { set $skip_cache 1; }
if ($request_method != GET) { set $skip_cache 1; }
add_header X-Cache $upstream_cache_status always;
location ~ \.php$ {
fastcgi_cache shop;
fastcgi_cache_key "$scheme$request_method$host$request_uri";
fastcgi_cache_valid 200 301 302 10m;
fastcgi_cache_lock on;
fastcgi_cache_use_stale updating error timeout;
fastcgi_cache_bypass $skip_cache;
fastcgi_no_cache $skip_cache;
include fastcgi_params;
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
}
}
بعد از اعمال، فایل را اعتبارسنجی کنید، کش را خالی کنید و دو آدرس را بسنجید: صفحه محصول در درخواست دوم HIT، سبد خرید BYPASS.
nginx -t && systemctl reload nginx
rm -rf /var/cache/nginx/shop/*
curl -sI https://example.ir/ | grep -i x-cache
curl -sI 'https://example.ir/سبد-خرید/' | grep -i x-cache
⚠️ دو خطای پرتکرار روز کمپین
اول، کش کردن admin-ajax.php؛ راهنمای افزایش سرعت پیشخوان وردپرس این سمت را جدا بررسی کرده است.
دوم، تکیه بر کوکی ووکامرس. مستندات nginx میگوید پاسخ دارای Set-Cookie کش نمیشود مگر با fastcgi_ignore_headers نادیده گرفته شود؛ ولی بازدیدکننده بیکوکی باز هم نسخه کششده را میبیند.
دو تنظیم دیگر بلوک بالا برای اوج کمپیناند. fastcgi_cache_lock با پیشفرض خاموش، اجازه میدهد فقط یک درخواست یک خانه کش را پر کند؛ سقف انتظار پنج ثانیه است. fastcgi_cache_use_stale با updating پاسخ کهنه را تحویل میدهد. و fastcgi_cache_valid با یک زمان تنها فقط پاسخ 200 و 301 و 302 را کش میکند. همینها بهینهسازی ووکامرس روی سرور را از موج همزمان درخواست جدا میکنند.

دیتابیس آخرین لایه است و دو کار دارد: خواندن کاتالوگ از حافظه به جای دیسک، و نوشتن سفارش بدون کند کردن بقیه. اولی به بافرپول InnoDB بند است، دومی به ساختار جدول سفارش.
مستندات MariaDB برای متغیر innodb_buffer_pool_size پیشفرض 134217728 بایت یعنی ۱۲۸ مگابایت را ثبت کرده و آن را مقدار اصلی تنظیم روی سرور InnoDB میداند. متغیر پویاست، ولی سقف بالاییاش در زمان راهاندازی تعیین میشود. اندازه هدف را از حجم داده بگیرید و کافی بودنش را از نسبت Innodb_buffer_pool_reads به read_requests بسنجید؛ کوچکتر یعنی خواندن کمتر از دیسک.
SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_read%';
متغیر innodb_flush_log_at_trx_commit وسوسهانگیز است؛ بهتر است دست نزنید. همان مستندات پیشفرض 1 را شرط سازگاری کامل ACID میداند: بعد از هر تراکنش، لاگ روی دیسک نوشته و flush میشود. مقدار 2 حدوداً هر ثانیه flush میکند، پس کرش سیستمعامل تراکنش آخرین ثانیه را میبرد. وقتی آن تراکنش سفارش پرداختشده باشد، معامله خوبی نیست. پیشفرض innodb_io_capacity هم 200 است.
مستندات رسمی ووکامرس این قابلیت را High-Performance Order Storage مینامد: داده سفارش از posts و postmeta به چهار جدول wc_orders، wc_order_addresses، wc_order_operational_data و wc_orders_meta میرود. سند نتیجه را «خواندن و نوشتن کمتر و جدولهای کمفشارتر» توصیف میکند، بیهیچ عدد بنچمارکی.
همان مستندات میگوید برای نصب جدید از نسخه ۸.۲ پیشفرض روشن است و فروشگاه قدیمی باید دستی فعالش کند: کلید قابلیت زیر Settings و Advanced و Features، و انتخاب جدول مرجع زیر همان Advanced و Custom data stores. تغییر جدول مرجع تا سفارشی در انتظار همگامسازی باشد مجاز نیست و همگامسازی با Action Scheduler انجام میشود؛ پس ترتیب این است: اول کرون، بعد همگامسازی، آخر جدول مرجع.
🔶 قبل از تغییر یک نقطه بازگشت بسازید
پنل سرور مجازی وبداده اسنپشات و بازگردانی دارد؛ قبل از تغییر بافرپول یا فعال کردن جدول سفارشها یکی بگیرید.
راهنمای رسمی افزونهنویسی وردپرس میگوید وردپرس «WP-Cron را روی هر بارگذاری صفحه اجرا میکند» و مستندات Action Scheduler هم صف کارهایش را با همان WP-Cron زمانبندی میکند. روز کمپین یعنی ایمیل سفارش و همگامسازی وسط اوج اجرا میشوند. راهحل سپردن اجرا به کرون سیستم است، با الگویی مثل */15 * * * * که همان راهنما نشان میدهد.
⚠️ ترتیب این دو کار را عوض نکنید
اگر DISABLE_WP_CRON را بگذارید و خط crontab را ننویسید، خطایی نمیبینید و در عوض ایمیل سفارش، همگامسازی HPOS و صف Action Scheduler بیصدا متوقف میشوند. اول crontab را بنویسید و یک دوره اجرا را ببینید.
# ۱) اول این خط در crontab کاربر سایت
*/15 * * * * cd /var/www/shop && wp cron event run --due-now --quiet
# بدون WP-CLI:
# */15 * * * * wget -q -O - 'https://example.ir/wp-cron.php?doing_wp_cron' >/dev/null 2>&1
// ۲) بعد در wp-config.php
define( 'DISABLE_WP_CRON', true );
| معیار | قبل | بعد |
|---|---|---|
| میانگین request_time صفحه محصول | … | … |
| درصد HIT در لاگ nginx | … | … |
| سطرهای slowlog در ساعت اوج | … | … |
| نسبت keyspace_hits به مجموع | … | … |
| بیشترین پردازه فعال FPM | … | … |
این جدول خالی مهمترین خروجی مقاله است. هیچ راهنمایی نمیتواند بگوید فروشگاه شما چند درصد سریعتر میشود؛ عدد به قالب و افزونهها و اندازه کاتالوگ بستگی دارد. بهینهسازی ووکامرس روی سرور وقتی کامل است که این جدول دو بار پر شود.
سوالهایی که فروشگاهداران پیش از کمپین میپرسند
برای صفحه عمومی تا حد زیادی بله. اما سبد خرید، تسویه حساب و حساب کاربری در مستندات ووکامرس باید بیرون از کش بمانند و همان صفحهها در کمپین زیر بار میروند.
قاعدههای آماده کش فقط cart و checkout لاتین را میگیرند. با اسلاگ فارسی تطبیق نمیکنند و صفحه سبد کش میشود؛ شکل percent-encoded اسلاگ خودتان را اضافه کنید.
عدد ثابتی نیست. رم آزاد را بر میانگین مصرف هر پردازه php-fpm تقسیم کنید و سهم سیستمعامل و دیتابیس و Redis را کنار بگذارید.
کش آبجکت نتیجه کوئری را در حافظه نگه میدارد و روی صفحه شخصی هم کار میکند. کش صفحه خروجی HTML آماده را برمیگرداند و فقط برای صفحه عمومی مجاز است.
پنج کوکی راهنمای ووکامرس: woocommerce_cart_hash، woocommerce_items_in_cart، wp_woocommerce_session_، woocommerce_recently_viewed و store_notice. کوکی wordpress_logged_in را هم اضافه کنید.
چون پیشفرض long_query_time در MariaDB ده ثانیه است و کوئری ووکامرس کمتر از آن طول میکشد. آستانه را روی یک ثانیه بگذارید.
اگر خط crontab را ننویسید بله: صف Action Scheduler و ایمیل سفارش بیصدا متوقف میشوند. اول کرون سیستمی را راه بیندازید، بعد ثابت DISABLE_WP_CRON را فعال کنید.
جمعبندی؛ از اندازهگیری تا روز کمپین بهینهسازی ووکامرس روی سرور با نصب یک افزونه تمام نمیشود. چهار لایه این مقاله هر کدام یک عدد قابل خواندن میدهند. اول لاگ nginx را با request_time و وضعیت کش فعال میکنید، slowlog استخر PHP-FPM را روشن میکنید و آستانه لاگ کوئری کند MariaDB را از پیشفرض ده ثانیه پایین میآورید. بعد ظرفیت پردازش را از رم واقعی سرور حساب میکنید: مدل مدیریت پردازه، سقف pm.max_children و حافظه OPcache متناسب با فایلهای کامپایلشده. مرحله سوم دو کش جداست؛ Redis با سقف حافظه و سیاست حذف مشخص، و کش صفحه FastCGI که اسلاگ واقعی سبد خرید شما در استثناهایش باشد، نه اسلاگ لاتین پیشفرض. مرحله چهارم دیتابیس است: بافرپول به اندازه داده، دست نزدن به flush لاگ و سپردن کرون به سیستمعامل، به همان ترتیب. آخرش جدول قبل و بعد را پر میکنید؛ عدد خودتان تنها چیزی است که روز کمپین به آن تکیه میکنید.
🚀 سرور را پیش از کمپین آماده کنید
سرور مجازی ایران وبداده تحویل خودکار و تست ساعتی رایگان دارد.
اگر به عدد عجیبی رسیدید، خروجی لاگ را در دیدگاهها بنویسید.