← بازگشت به صفحه اصلی

Cron-Based Cache System for Cryptocurrency Prices

نسخه: 1.0.0
تاریخ: ژانویه 2026
وضعیت: 🟢 فعال و عملیاتی


📋 خلاصه تغییرات

این مستند شامل تغییرات اساسی در سیستم caching و بروزرسانی قیمت‌های ارز دیجیتال است که برای بهبود عملکرد و حذف race conditions پیاده‌سازی شده است.

مشکلات قبلی:

راه‌حل‌های جدید:


🏗️ معماری جدید

1. Batch Update System

┌─────────────────────────────────────────────────────────────┐
│                    Action Scheduler                          │
├─────────────────────────────────────────────────────────────┤
│  Batch 1 (00s) → Batch 2 (06s) → ... → Batch 10 (54s)       │
│     ↓              ↓                        ↓                │
│  ~10 coins     ~10 coins               ~10 coins             │
│     ↓              ↓                        ↓                │
│  Update individual symbol cache files                        │
│     ↓              ↓                        ↓                │
│  Update central all-symbols.json.gz                          │
└─────────────────────────────────────────────────────────────┘

2. Cache Architecture

wp-content/cache-api/
├── all-symbols.json.gz      # Central cache (updated every 5 min by cron)
├── symbols/
│   ├── BTC.json.gz          # Individual symbol cache
│   ├── ETH.json.gz
│   ├── USDT.json.gz
│   └── ...
└── coingecko-symbol.json.gz # External data

📁 فایل‌های تغییر یافته

1. ApiController.php

تغییرات اصلی:

کد کلیدی - Atomic File Write:

// قبلاً (مشکل race condition):
file_put_contents($cache_file, gzencode(json_encode($data), 9));

// جدید (atomic و safe):
atomic_file_put_contents($cache_file, gzencode(json_encode($data), 9));

2. CronController.php

تغییرات اصلی:

کد کلیدی - Action Scheduler Configuration:

// Allow concurrent batches
add_filter('action_scheduler_queue_runner_concurrent_batches', function () {
    return 5; // 5 concurrent batches
});

// Increase batch size
add_filter('action_scheduler_queue_runner_batch_size', function () {
    return 50;
});

// Reduce queue check interval
add_filter('action_scheduler_run_schedule', function ($interval) {
    return 10; // Check every 10 seconds
});

3. helpers.php

تابع جدید - atomic_file_put_contents():

/**
 * Write content to file atomically to prevent race conditions
 * 
 * This function writes to a temporary file first, then renames it to the
 * target file. This ensures that readers never see a partial/empty file.
 */
function atomic_file_put_contents($file_path, $content)
{
    $dir = dirname($file_path);
    
    if (!file_exists($dir)) {
        wp_mkdir_p($dir);
    }
    
    // Create temp file in same directory (ensures same filesystem for rename)
    $temp_file = $dir . '/.tmp_' . basename($file_path) . '_' . uniqid();
    
    // Write to temp file
    $result = file_put_contents($temp_file, $content, LOCK_EX);
    
    if ($result === false) {
        @unlink($temp_file);
        return false;
    }
    
    // Atomic rename - this is the key to preventing race conditions
    if (!rename($temp_file, $file_path)) {
        @unlink($temp_file);
        return false;
    }
    
    return true;
}

4. MarketDataController.php

تغییرات اصلی:

کد کلیدی:

public function getProcessedMarketData()
{
    $headerMenu = $this->getHeaderMenu();
    $listCoins = $this->getCoinsData();

    if (empty($listCoins)) {
        return $this->getEmptyData($headerMenu, $listCoins);
    }

    // Get all positive growth coins sorted by change24Hours
    $allPositiveGrowth = $this->getAllPositiveGrowth($listCoins);

    return [
        'positiveGrowth' => array_slice($allPositiveGrowth, 0, 4),
        'negativeGrowth' => $this->getTopNegativeGrowth($listCoins),
        'popularCRRS' => $this->getPopularCoins($listCoins),
        'mostExpensive' => $this->getMostExpensiveFromPositive($allPositiveGrowth),
        'topGrowth' => $this->getTopGrowth($listCoins),
        'headerMenu' => $headerMenu,
        'listCoins' => $listCoins,
    ];
}

🎨 Template های جدید

قیمت USDT, TRX, BTC, ETH را از PHP می‌خواند و Server-Side Render می‌کند.

مسیر: templates/coins/popular-coin.php

ویژگی‌ها:

<?php
// Get popular coins from processMarketData
if (isset($processMarketData['popularCRRS']) && !empty($processMarketData['popularCRRS'])) {
    $popularCoins = $processMarketData['popularCRRS'];
}

// If popularCRRS is missing some coins, get them from listCoins
if (isset($processMarketData['listCoins']) && !empty($processMarketData['listCoins'])) {
    $popularSymbols = ['USDT', 'TRX', 'BTC', 'ETH'];
    // ... fill missing coins
}
?>

<section class="swiper currencies-status-box">
    <?php foreach ($orderedSymbols as $symbol) : ?>
        <!-- Render coin with price and change -->
    <?php endforeach; ?>
</section>

2. growth-coin.php (آپدیت)

تغییرات:

// Initialize variables
$topGrowth = [];
$negativeGrowth = [];
$mostExpensive = [];
$headerMenu = [];

// Use positiveGrowth for "Most Profit Today"
if (isset($processMarketData['positiveGrowth'])) {
    $topGrowth = $processMarketData['positiveGrowth'];
}

// mostExpensive from positive coins only
if (isset($processMarketData['positiveGrowth'])) {
    $positiveCoins = $processMarketData['positiveGrowth'];
    usort($positiveCoins, fn($a, $b) => ($b['sellPrice'] ?? 0) <=> ($a['sellPrice'] ?? 0));
    $mostExpensive = array_slice($positiveCoins, 0, 4);
}

📄 View های آپدیت شده

1. home.php

قبلاً:

<!-- 150+ lines of hardcoded HTML for USDT, TRX, BTC, ETH -->
<section class="swiper currencies-status-box">
    <div class="swiper-slide crr-box" data-symbol="USDT">
        <!-- Empty price, filled by JS -->
        <p class="price"></p>
        <span class="change"></span>
    </div>
    ...
</section>

<?php get_template_part('templates/coins/growth', 'coin', ['processMarketData' => processMarketData()]); ?>

جدید:

<?php
// Get market data once and reuse
$marketData = processMarketData();

get_template_part('templates/coins/popular', 'coin', ['processMarketData' => $marketData]);
get_template_part('templates/coins/growth', 'coin', ['processMarketData' => $marketData]);
?>

2. coin.php (Archive)

همان تغییرات home.php - استفاده از template جدید و یکبار فراخوانی processMarketData()


🔧 تغییرات JavaScript

app.js

تغییرات:

function processAndPopulateData(data) {
    const { positiveGrowth, negativeGrowth, popularCRRS, mostExpensive } = processMarketData(data);

    // Now handled by PHP (popular-coin.php)
    // populateSectionCrr("#currencies-status-items", popularCRRS);

    populateSIngleMomentValue("#moment-value-of-coins-items", popularCRRS);
    // ...
}

📊 جدول زمانی Cron Jobs

Job Interval Description
xpay_batch_update_0 60s Update batch 0 (first ~10 coins)
xpay_batch_update_1 60s + 6s Update batch 1
xpay_batch_update_9 60s + 54s Update batch 9 (last ~10 coins)
xpay_update_all_symbols 300s Update central cache

✅ مزایا

Performance

Reliability

Maintainability


🐛 مشکلات رفع شده

مشکل علت راه‌حل
دیتا خالی هنگام رفرش Race condition در file write atomic_file_put_contents()
“بیشترین سود” با مقدار منفی استفاده از topGrowth بجای positiveGrowth فیلتر فقط مثبت‌ها
“برترین بازار” با کوین منفی ترکیب مثبت و منفی در getMostExpensive فقط از مثبت‌ها
BTC در لیست نیست array_filter ایندکس‌ها را حفظ می‌کند array_values() برای reindex
لودر نمایش داده می‌شود JS منتظر AJAX است SSR بدون لودر
Action Scheduler callback error ثبت callback بعد از schedule انتقال به constructor

🧪 تست

تست Atomic Write:

# چک کنید که فایل‌های temp وجود ندارند
docker exec wp-app ls -la /var/www/html/wp-content/cache-api/
# نباید فایلی با پیشوند .tmp_ وجود داشته باشد

تست Cache Content:

# چک کنید که cache خالی نیست
docker exec wp-app php -r \
  'echo count(json_decode(gzdecode(file_get_contents("/var/www/html/wp-content/cache-api/all-symbols.json.gz")), true)["value"]);'
# باید عدد > 0 برگرداند

در browser، صفحه اصلی یا /coin/ را باز کنید و با View Source چک کنید که قیمت‌ها در HTML هستند (نه خالی).


📚 فایل‌های مرتبط


🔗 مستندات مرتبط