Skip to content
Back to skills

Performance Optimization

ASecurity

Drupal performance optimization guide covering Redis, caching, database optimization, and asset management.

  • 4 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added May 26, 2026
developmentgophpbashsqlnodeapidatabasebackendperformance

Works with

  • cli
  • api

Security analysis

A100/100

Scanned May 27, 2026

npx -y skills add olegiv/claude-code-support-tools --skill performance-optimization --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Performance Optimization?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Performance Optimization
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/olegiv-performance-optimization/badge)](https://www.skillsdirectory.com/skills/olegiv-performance-optimization)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: performance-optimization
description: Drupal performance optimization guide covering Redis, caching, database optimization, and asset management.
---

# Performance Optimization Skill

Comprehensive guide for optimizing Drupal performance.

## Stack Overview

| Component | Technology | Purpose |
|-----------|------------|---------|
| Database | PostgreSQL or MySQL/MariaDB | Primary data store |
| Cache | Redis (or Memcached) | Object/session cache |
| Search | Apache Solr (or Elasticsearch) | Full-text search |
| Web Server | Apache or Nginx | HTTP serving |
| PHP | 8.2+ | Application runtime |

## Quick Diagnostics

```bash
# Overall health
./vendor/bin/drush core:status

# Cache status
./vendor/bin/drush cache:list

# Redis stats
redis-cli INFO memory | grep used_memory_human
redis-cli INFO stats | grep -E "keyspace_hits|keyspace_misses"

# Database size (PostgreSQL)
./vendor/bin/drush sql:query "SELECT pg_size_pretty(pg_database_size(current_database()))"

# Database size (MySQL)
./vendor/bin/drush sql:query "SELECT table_schema AS db, ROUND(SUM(data_length + index_length) / 1024 / 1024, 2) AS 'Size (MB)' FROM information_schema.tables WHERE table_schema = DATABASE() GROUP BY table_schema"
```

---

## 1. Redis Caching

### Configuration Location
`sites/default/settings.redis.php` (or inline in `settings.php`)

### Recommended Settings

```php
// Enable Redis
$settings['redis.connection']['interface'] = 'PhpRedis';
$settings['redis.connection']['host'] = '127.0.0.1';
$settings['redis.connection']['port'] = '6379';

// Cache bins to use Redis
$settings['cache']['default'] = 'cache.backend.redis';

// High-value cache bins
$settings['cache']['bins']['render'] = 'cache.backend.redis';
$settings['cache']['bins']['dynamic_page_cache'] = 'cache.backend.redis';
$settings['cache']['bins']['page'] = 'cache.backend.redis';
$settings['cache']['bins']['bootstrap'] = 'cache.backend.redis';
$settings['cache']['bins']['config'] = 'cache.backend.redis';
$settings['cache']['bins']['discovery'] = 'cache.backend.redis';

// Keep some bins in database (rarely accessed)
$settings['cache']['bins']['migrate'] = 'cache.backend.database';
```

### Redis Memory Optimization

```bash
# Check memory
redis-cli INFO memory

# Set max memory (in redis.conf)
# maxmemory 512mb
# maxmemory-policy allkeys-lru

# Monitor real-time
redis-cli MONITOR

# Clear Redis cache
redis-cli FLUSHDB
```

### Hit Rate Analysis

```bash
redis-cli INFO stats | grep -E "keyspace_hits|keyspace_misses"
# Calculate: hits / (hits + misses) * 100 = hit rate %
# Target: > 90%
```

---

## 2. Drupal Caching Layers

### Cache Hierarchy

1. **Page Cache** - Full page for anonymous users
2. **Dynamic Page Cache** - Partial pages for authenticated users
3. **Render Cache** - Individual render elements
4. **Internal Cache** - Discovery, config, bootstrap

### Views Caching

```yaml
# In views configuration
display:
  cache:
    type: tag  # Invalidates on content change
    # OR
    type: time
    options:
      results_lifespan: 3600   # 1 hour
      output_lifespan: 3600

# For static content
display:
  cache:
    type: time
    options:
      results_lifespan: 86400  # 24 hours
      output_lifespan: 86400
```

### Block Caching

```php
// In Block plugin
public function getCacheMaxAge() {
  return 3600; // 1 hour
}

public function getCacheContexts() {
  return ['user.roles', 'url.path'];
}

public function getCacheTags() {
  return ['node_list:article'];
}
```

### BigPipe

```bash
# Enable BigPipe for better perceived performance
./vendor/bin/drush en big_pipe -y
```

---

## 3. Database Optimization

### PostgreSQL Tuning

```sql
-- Check slow queries (requires pg_stat_statements)
SELECT query, calls, total_time, mean_time
FROM pg_stat_statements
ORDER BY total_time DESC
LIMIT 10;

-- Table sizes
SELECT relname, pg_size_pretty(pg_total_relation_size(relid))
FROM pg_catalog.pg_statio_user_tables
ORDER BY pg_total_relation_size(relid) DESC
LIMIT 20;

-- Index usage
SELECT indexrelname, idx_scan, idx_tup_read, idx_tup_fetch
FROM pg_stat_user_indexes
ORDER BY idx_scan DESC;

-- Unused indexes
SELECT indexrelname, idx_scan
FROM pg_stat_user_indexes
WHERE idx_scan = 0;

-- Vacuum and analyze
VACUUM ANALYZE;
```

### MySQL/MariaDB Tuning

```sql
-- Check slow queries (enable slow_query_log in my.cnf)
-- slow_query_log = 1
-- long_query_time = 2

-- Table sizes
SELECT table_name,
       ROUND(((data_length + index_length) / 1024 / 1024), 2) AS 'Size (MB)'
FROM information_schema.tables
WHERE table_schema = DATABASE()
ORDER BY (data_length + index_length) DESC
LIMIT 20;

-- Index usage
SELECT * FROM sys.schema_unused_indexes
WHERE object_schema = DATABASE();

-- Check for missing indexes
SELECT * FROM sys.statements_with_full_table_scans
WHERE db = DATABASE()
ORDER BY no_index_used_count DESC
LIMIT 10;

-- Optimize tables
OPTIMIZE TABLE cache_default, cache_render, cache_page;
```

### Query Optimization

```php
// BAD: N+1 queries
foreach ($nids as $nid) {
  $node = Node::load($nid);
}

// GOOD: Batch loading
$nodes = Node::loadMultiple($nids);

// BAD: Load all fields
$nodes = $storage->loadMultiple($nids);

// GOOD: Only load needed data
$query = $storage->getQuery()
  ->condition('type', 'article')
  ->condition('status', 1)
  ->range(0, 50);
$nids = $query->execute();
```

### Entity Query Best Practices

```php
// Use accessCheck appropriately
$query = \Drupal::entityQuery('node')
  ->accessCheck(TRUE)  // Required in Drupal 10+
  ->condition('type', 'article')
  ->condition('status', 1)
  ->sort('created', 'DESC')
  ->range(0, 10);

// Add caching tags
$nodes = Node::loadMultiple($query->execute());
$build['#cache']['tags'] = Cache::mergeTags(
  $build['#cache']['tags'] ?? [],
  ['node_list:article']
);
```

---

## 4. Asset Optimization

### CSS/JS Aggregation

```php
// settings.php (production)
$config['system.performance']['css']['preprocess'] = TRUE;
$config['system.performance']['js']['preprocess'] = TRUE;
```

### Image Optimization

```yaml
# responsive_image.style.*.yml
responsive_image_style:
  id: wide
  breakpoints:
    - media: '(min-width: 1200px)'
      image_style: wide_desktop
    - media: '(min-width: 768px)'
      image_style: wide_tablet
    - media: ''
      image_style: wide_mobile
```

### Lazy Loading

```twig
{# In templates #}
<img loading="lazy" src="{{ image_url }}" alt="{{ alt }}">

{# Or via preprocess #}
$variables['attributes']['loading'] = 'lazy';
```

---

## 5. Common Performance Issues

### Issue: Slow Views

**Diagnosis:**
```bash
./vendor/bin/drush views:analyze
```

**Solutions:**
1. Add caching to view display
2. Reduce fields to minimum needed
3. Add appropriate filters
4. Use pager or limit results
5. Consider using Search API instead

### Issue: Heavy Preprocess Functions

**Diagnosis:**
```bash
grep -r "entityTypeManager\|database" themes/custom/*/\*.theme
```

**Solutions:**
1. Move logic to services
2. Cache expensive computations
3. Use lazy builders

### Issue: Too Many Modules

**Diagnosis:**
```bash
./vendor/bin/drush pm:list --status=enabled | wc -l
```

**Solutions:**
1. Disable unused modules
2. Combine functionality
3. Use lazy loading where possible

### Issue: Large Sessions

**Diagnosis:**
```bash
redis-cli --scan --pattern '*session*' | head -10
```

**Solutions:**
1. Reduce session data
2. Clean old sessions
3. Use Redis for sessions

---

## 6. Monitoring & Profiling

### Built-in Tools

```bash
# Enable performance logging
./vendor/bin/drush config:set system.logging error_level verbose -y

# Check recent slow pages
./vendor/bin/drush watchdog:show --severity=warning
```

### Database Query Logging

```php
// settings.local.php (development only)
$settings['container_yamls'][] = DRUPAL_ROOT . '/sites/development.services.yml';
$config['system.logging']['error_level'] = 'verbose';
```

### Memory Profiling

```bash
# PHP memory
php -i | grep memory_limit

# Drupal memory usage
./vendor/bin/drush php:eval "echo 'Memory: ' . round(memory_get_peak_usage() / 1024 / 1024, 2) . 'MB';"
```

---

## 7. Performance Checklist

### Before Deployment

- [ ] CSS/JS aggregation enabled
- [ ] Page cache enabled for anonymous
- [ ] Views have appropriate caching
- [ ] Images use responsive styles
- [ ] Redis (or Memcached) configured and working
- [ ] No development modules enabled
- [ ] Error logging set to production level

### Regular Maintenance

- [ ] Clear stale cache entries
- [ ] Prune old revisions
- [ ] Clean watchdog logs
- [ ] Run database maintenance (VACUUM for PostgreSQL, OPTIMIZE for MySQL)
- [ ] Check Redis memory usage
- [ ] Review slow query log

### Commands

```bash
# Full cache clear
./vendor/bin/drush cr

# Rebuild only specific cache
./vendor/bin/drush cache:rebuild router

# Database maintenance (PostgreSQL)
./vendor/bin/drush sql:query "VACUUM ANALYZE"

# Database maintenance (MySQL)
# Run OPTIMIZE TABLE on large cache/log tables via drush sql:cli

# Clear old logs
./vendor/bin/drush watchdog:delete all
```

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.