id: PERFORMANCE title: PERFORMANCE
Performance
Caching Strategies
Redis Cache Configuration
Main Cache Component (common/config/main.php)
'cache' => [
'class' => 'yii\redis\Cache',
'redis' => [
'hostname' => 'redis',
'port' => 6379,
'database' => 0,
]
],
Cache Enable/Disable
// Enable cache only when not in CLI mode
defined('CACHE_ENABLE') || define('CACHE_ENABLE', php_sapi_name() !== 'cli');
Classifier Component Caching
'classifier' => [
'class' => common\components\classifier\ClassifierComponent::class,
'cachingDuration' => CACHE_ENABLE ? 300 : 0,
],
System State Component Caching
'system' => [
'class' => common\components\state\SystemStateComponent::class,
'cachingDuration' => CACHE_ENABLE ? 300 : 0,
],
API Response Caching
Base Cached Response Provider (api/providers/responses/BaseCachedResponseDataProvider.php)
abstract class BaseCachedResponseDataProvider implements IResponseDataProvider
{
public function getResponse(array $queryParams, callable $serialization): array
{
if (!$this->clientAccount->getTypeManager()->isSimpleCustomer()) {
return $this->getResponseDataProvider()->getResponse(
$queryParams,
$serialization
);
}
return Yii::$app->cache->getOrSet($this->getCacheKey($queryParams), fn() => $this->getResponseDataProvider()->getResponse(
$queryParams,
$serialization
), $this->getCacheDuration());
}
protected function getCacheDuration(): int
{
return 600; // 10 minutes
}
}
Content Page Caching (api/providers/responses/CachedContentPageResponseDataProvider.php)
protected function getCacheDuration(): int
{
return 600; // 10 minutes
}
Dynamic Filter Caching (api/providers/responses/CachedDynamicFilterResponseDataProvider.php)
protected function getCacheDuration(): int
{
return 600; // 10 minutes
}
Product Recommendation Caching
Base Cached Recommendation Provider (api/recommendations/BaseCachedProductRecommendationDataProvider.php)
abstract class BaseCachedProductRecommendationDataProvider implements IProductRecommendationDataProvider
{
public function getData(Product $product): array
{
if (!$this->clientAccount->getTypeManager()->isSimpleCustomer()) {
return $this->getProductRecommendationDataProvider()->getData($product);
}
return Yii::$app->cache->getOrSet($this->getCacheKey(), fn() => $this->getProductRecommendationDataProvider()->getData($product), $this->getCacheDuration());
}
protected function getCacheDuration(): int
{
return 600; // 10 minutes
}
}
Category Path Caching
Category Path Cache Storage (api/categories/CategoryPathCacheStorage.php)
public const CACHE_DURATION = 60 * 60 * 24; // 1 Day
public function getPath(string $categoryId): ?array
{
return Yii::$app->cache->getOrSet(
$this->getCacheKey($categoryId),
fn() => $this->getPathFromDatabase($categoryId),
self::CACHE_DURATION
);
}
Category Nav Code Path Storage (api/categories/CategoryNavCodePathStorage.php)
public const CACHE_DURATION = 60 * 60 * 24; // 1 Day
public function getPath(string $navCode): ?array
{
return Yii::$app->cache->getOrSet(
$this->getCacheKey($navCode),
fn() => $this->getPathFromDatabase($navCode),
self::CACHE_DURATION
);
}
User System Cache
System User Cache (console/models/User.php)
public const SYSTEM_USER_CACHE_DURATION = 600;
public static function getSystemUser(): ?User
{
return self::find()
->where(['id' => self::SYSTEM_USER_ID])
->cache(self::SYSTEM_USER_CACHE_DURATION)
->one();
}
Category Path Helper Caching
Category Path Helper (admin/helpers/CategoryPathHelper.php)
protected static $cacheDuration = 1800; // 30 minutes
public static function getPathMap(): array
{
if (self::$pathMap === null) {
self::$pathMap = self::getCacheHandler()->get(self::getCacheKey());
if (self::$pathMap === false) {
self::$pathMap = self::buildPathMap();
self::getCacheHandler()->set(self::getCacheKey(), self::$pathMap, self::$cacheDuration);
}
}
return self::$pathMap;
}
Redis Redirect Repository
Redis Redirect Storage (common/redirects/repositories/RedisRedirectRepository.php)
class RedisRedirectRepository implements RedirectRepositoryInterface
{
public function save(RedirectValue $redirect): bool
{
if ($redirect->urlsAreTheSame()) {
return false;
}
$value = $redirect->getToUrl();
if ($redirect->getReason() === LogRedirectReasonEnum::MANUAL_REDIRECT_SETUP) {
$value = $redirect->getHttpStatusCode() .
self::HTTP_STATUS_CODE_SEPARATOR .
$redirect->getToUrl();
}
$this->redis->set($redirect->getFromUrl(), $value);
return true;
}
public function get(string $from): ?RedirectValue
{
if(!empty($to = $this->redis->get($from))) {
if (count($parts = explode(self::HTTP_STATUS_CODE_SEPARATOR, $to, 1)) > 1) {
return new RedirectValue($from, $parts[1], null, (int) $parts[0]);
}
return new RedirectValue($from, $to);
}
return null;
}
}
Database Optimization
Query Optimization
Eager Loading
// Bad: N+1 query problem
$products = Product::find()->all();
foreach ($products as $product) {
echo $product->category->name; // Additional query for each product
}
// Good: Eager loading
$products = Product::find()->with('category')->all();
foreach ($products as $product) {
echo $product->category->name; // No additional queries
}
Batch Processing
// Process large datasets in chunks
Product::find()->batch(1000, function($products) {
foreach ($products as $product) {
// Process product
}
});
// Use generators for large datasets
function getProducts(): Generator
{
$query = Product::find();
foreach ($query->each() as $product) {
yield $product;
}
}
Query Caching
// Cache query results
$products = Product::find()
->cache(3600) // Cache for 1 hour
->byCategory($categoryId)
->all();
Elasticsearch Optimization
Product Search with Proper Counter (api/models/search/ProductElasticSearch.php)
public function search(): ProductsActiveDataProviderWithProperCounter
{
$dataProvider = new ProductsActiveDataProviderWithProperCounter([
'query' => $this->getProductsQuery(),
'pagination' => [
'pageSize' => $this->pageSize,
],
'sort' => $this->sort,
]);
// Cache total count for expensive queries
$totalCount = Yii::$app->cache->get($this->getTotalCountCacheKey());
if ($totalCount !== false) {
$dataProvider->setTotalCount((int) $totalCount);
} elseif ($dataProvider->getTotalCount() > ProductsActiveDataProviderWithProperCounter::MAX_ELASTIC_TOTAL_COUNT) {
Yii::$app->cache->set(
$this->getTotalCountCacheKey(),
$dataProvider->getTotalCount(),
$this->getTotalCountCacheDuration()
);
}
return $dataProvider;
}
protected function getTotalCountCacheDuration(): int
{
return 600; // 10 minutes
}
Shop Query Optimization
// Optimized shop retrieval
$shops = Shop::find()
->select(['id'])
->visible()
->active()
->where(['in', 'nav_code', $shopsNavCodes])
->column();
Database Indexing
Recommended Indexes
-- Product indexes
CREATE INDEX idx_product_name ON product(name);
CREATE INDEX idx_product_price ON product(price);
CREATE INDEX idx_product_category ON product(category_id);
CREATE INDEX idx_product_active ON product(is_active);
CREATE INDEX idx_product_created ON product(created);
-- Composite indexes for complex queries
CREATE INDEX idx_product_category_price ON product(category_id, price);
CREATE INDEX idx_product_active_created ON product(is_active, created);
-- Order indexes
CREATE INDEX idx_order_user ON `order`(user_id);
CREATE INDEX idx_order_status ON `order`(status);
CREATE INDEX idx_order_created ON `order`(created);
-- User indexes
CREATE INDEX idx_user_email ON user(email);
CREATE INDEX idx_user_active ON user(is_active);
Connection Pooling
Database Connection Configuration
'db' => [
'class' => 'yii\db\Connection',
'dsn' => 'mysql:host=mysql;dbname=rahvaraamat',
'username' => 'user',
'password' => 'password',
'charset' => 'utf8',
'tablePrefix' => 'rr_',
'enableLogging' => false,
'enableProfiling' => false,
'attributes' => [
PDO::ATTR_TIMEOUT => 60,
PDO::ATTR_PERSISTENT => false,
],
],
Asset Optimization
Asset Bundle Configuration
Admin Asset Bundle (admin/assets/AppAsset.php)
class AppAsset extends AssetBundle
{
public $basePath = '@webroot';
public $baseUrl = '@web';
public $css = [
'css/styles.css',
'css/styles_print.css',
'css/styles_screen.css',
];
public $js = [
'js/parseUri.js',
'js/thickbox.js',
'js/jquery.thickbox.alt.js',
'js/main.js'
];
public $depends = [
'yii\web\YiiAsset',
'yii\bootstrap\BootstrapAsset',
];
}
Asset Manager Configuration
'assetManager' => [
'class' => 'yii\web\AssetManager',
'bundles' => [
'yii\web\JqueryAsset' => [
'sourcePath' => null,
'js' => ['jquery.min.js'],
],
],
'appendTimestamp' => true,
'forceCopy' => YII_DEBUG,
],
Asset Compression
Asset Compression Command
# Compress assets
php yii asset/compress
# Or use Node.js build tools
npm install
npm run build
Asset Structure
web/assets/
├── css/ # Compiled CSS files
├── js/ # Compiled JavaScript files
├── images/ # Optimized images
└── fonts/ # Web fonts
admin/web/assets/
├── css/
├── js/
└── images/
api/web/assets/
├── css/
└── js/
Image Optimization
Image Processing
// Optimize image uploads
use yii\imagine\Image;
$image = Image::getImagine()->open($uploadedFile->tempName);
$image->resize($image->getSize()->widen(800))
->save($destinationPath, ['quality' => 85]);
File Storage Optimization
'fileStorage' => [
'class' => 'common\components\aws\Storage',
'buckets' => [
'rahvaraamat-staging-public' => ['acl' => 'public-read'],
'rahvaraamat-staging-private' => [],
// ... more buckets
]
],
Web Server Optimization
Nginx Configuration
# Gzip compression
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_proxied any;
gzip_comp_level 6;
gzip_types
text/plain
text/css
text/xml
text/javascript
application/json
application/javascript
application/xml+rss
application/atom+xml
image/svg+xml;
# Static file caching
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
Apache Configuration
# Enable compression
<IfModule mod_deflate.c>
AddOutputFilterByType DEFLATE text/plain
AddOutputFilterByType DEFLATE text/html
AddOutputFilterByType DEFLATE text/xml
AddOutputFilterByType DEFLATE text/css
AddOutputFilterByType DEFLATE application/xml
AddOutputFilterByType DEFLATE application/xhtml+xml
AddOutputFilterByType DEFLATE application/rss+xml
AddOutputFilterByType DEFLATE application/javascript
AddOutputFilterByType DEFLATE application/x-javascript
</IfModule>
# Cache static files
<IfModule mod_expires.c>
ExpiresActive on
ExpiresByType text/css "access plus 1 year"
ExpiresByType application/javascript "access plus 1 year"
ExpiresByType image/png "access plus 1 year"
ExpiresByType image/jpg "access plus 1 year"
ExpiresByType image/jpeg "access plus 1 year"
</IfModule>
PHP-FPM Optimization
PHP-FPM Configuration
[www]
user = www-data
group = www-data
listen = /run/php/php8.0-fpm.sock
listen.owner = www-data
listen.group = www-data
pm = dynamic
pm.max_children = 50
pm.start_servers = 5
pm.min_spare_servers = 5
pm.max_spare_servers = 35
pm.max_requests = 500
OPcache Configuration
[opcache]
opcache.enable=1
opcache.enable_cli=1
opcache.memory_consumption=128
opcache.interned_strings_buffer=8
opcache.max_accelerated_files=4000
opcache.revalidate_freq=2
opcache.fast_shutdown=1
Redis Optimization
Redis Configuration
maxmemory 512mb
maxmemory-policy allkeys-lru
save 900 1
save 300 10
save 60 10000
MySQL Optimization
MySQL Configuration
[mysqld]
innodb_buffer_pool_size = 1G
innodb_log_file_size = 256M
innodb_flush_log_at_trx_commit = 2
innodb_flush_method = O_DIRECT
query_cache_size = 128M
query_cache_type = 1
max_connections = 200
Performance Monitoring
Cache Hit Rate Monitoring
# Check Redis memory usage
redis-cli info memory
# Check cache hit rate
redis-cli info stats | grep keyspace_hits
Database Performance Monitoring
# Check slow queries
sudo tail -f /var/log/mysql/slow.log
# Check MySQL status
mysql -u root -p -e "SHOW STATUS LIKE 'Slow_queries';"
Application Performance Monitoring
# Check PHP-FPM status
sudo systemctl status php8.0-fpm
# Monitor memory usage
free -h
# Check disk usage
df -h