# Publish Instance Performance Tuning

A slow AEM site is rarely fixed by one clever setting. It's usually a handful of ordinary problems: a low cache hit ratio, a query that scans too much, a component doing work on every request, or oversized images. This post gives you a method for finding the real bottleneck, and a checklist of the fixes that matter most, for both AEM 6.5 and AEM as a Cloud Service.

## Start with Measurement, Not Guesses

Before changing anything, establish a baseline:

- **Response times** for key pages, measured from outside (browser developer tools, synthetic monitoring) and inside (request logs)
- **Dispatcher/CDN cache hit ratio**, because every hit is a request Publish never serves
- **Publish load:** CPU, memory, thread usage and request rate
- **Slow requests:** which URLs take the longest, and how often they're requested

Change **one thing at a time**, and re-measure. Without a baseline you can't tell whether a change helped, hurt or did nothing.

## The Performance Layers

Think of a request passing through layers, from cheapest to most expensive:

1. **Browser cache:** free
2. **CDN:** very cheap
3. **Dispatcher cache:** cheap
4. **Publish rendering:** expensive
5. **Repository and queries:** the most expensive part of Publish rendering

The strategy follows directly: **push as many requests as possible to the earlier layers**, and make whatever reaches Publish cheap.

## Layer 1: Maximize Cache Hits

This delivers the largest improvements, so do it first.

- **Check what isn't cached and why.** Common reasons are query strings, missing file extensions, `no-cache` headers, authenticated requests and cache rules that deny the path (see Part 1)
- **Use an allow-list for ignorable query parameters** such as `utm_*`, so marketing links still hit the cache
- **Set TTLs** with `Cache-Control` headers so content that changes without activation still expires correctly, and so the CDN can cache effectively
- **Tune** `/statfileslevel` so one activation doesn't flush more than it needs to
- **Keep personalization out of cached HTML.** Load user-specific fragments client-side, or use **Sling Dynamic Include** so the page can be cached while small dynamic pieces are fetched separately
- **Use versioned clientlibs** and long TTLs for static assets

A site whose pages are mostly served from Dispatcher and the CDN may need only a modest Publish tier, whereas one with a poor hit ratio can overwhelm Publish regardless of tuning.

## Layer 2: Make Publish Rendering Cheap

When a request does reach Publish, the cost is mostly your code.

### Keep Sling Models Light

- Do only what the page needs. Don't compute things you won't render
- Avoid **repository queries** inside components that render on every request
- Don't call external services synchronously during rendering. If you must, add timeouts and caching
- Reuse results within a request, rather than recomputing in several places
- Use `@Optional` and lazy getters for expensive values that are only sometimes used

### Cache Expensive Results

If a component needs data that's costly to compute and doesn't change often, cache it in an OSGi service with a sensible expiry, and invalidate it deliberately. Be careful that the cache doesn't grow without limit.

### Avoid Resource Leaks

A classic cause of gradual slowdown and memory pressure is **unclosed resource resolvers**:

```java
@Reference
private ResourceResolverFactory resolverFactory;

public String readTitle(String path) {
    Map<String, Object> auth = Map.of(ResourceResolverFactory.SUBSERVICE, "my-service");
    try (ResourceResolver resolver = resolverFactory.getServiceResourceResolver(auth)) {
        Resource resource = resolver.getResource(path);
        return resource != null ? resource.getValueMap().get("jcr:title", String.class) : null;
    } catch (LoginException e) {
        return null;
    }
}
```

Always open service resolvers in a **try-with-resources** block (or close them in `finally`), and use **service users** mapped through a subservice rather than shared admin sessions.

## Layer 3: Queries and Indexes

Slow queries are the most frequent cause of slow Publish pages and heavy CPU.

### How to Spot Them

- **Query Performance** (under Tools → Operations → Diagnosis on 6.5) lists recent and slow queries and lets you **explain** a query to see which index it uses
- Look for **traversal** warnings in the logs. A query that traverses the repository, rather than using an index, gets slower as content grows
- Check the **Index Manager** for the indexes in play

### How to Fix Them

- **Use an index.** Add or extend an Oak index definition so the query's properties and node types are covered. On Cloud Service, ship index definitions as code
- **Narrow the query.** Restrict it by path (`/content/mysite/...`), node type and properties, rather than searching the whole repository
- **Limit results.** Add limits and paging
- **Avoid wildcards at the start of a value** (`LIKE '%term'`), which can't use indexes effectively
- **Don't query at render time at all** if you can avoid it. Use a pre-computed property, a tag-based navigation structure or a cache

An example of a well-scoped query:

```sql
SELECT * FROM [cq:Page] AS p
WHERE ISDESCENDANTNODE(p, '/content/mysite/en/blog')
AND p.[jcr:content/cq:tags] = 'mysite:topic/aem'
```

Run it through the explain tool, and confirm it uses an index and not traversal.

## Layer 4: Frontend Weight

Backend tuning can't rescue a heavy page.

- **Images:** serve appropriately sized renditions, modern formats (such as WebP) and responsive `srcset` attributes. The Core Components Image component supports this
- **Lazy-load** images below the fold
- **Clientlibs:** minify and compress, combine where sensible, and load non-critical JavaScript with `defer` or `async`
- **Compression:** enable gzip or brotli at the CDN or web server
- **Fewer third-party scripts**, which are often the biggest slowdown after your own code

## Platform-Specific Tuning

### AEM 6.5 / AMS (you own the infrastructure)

- **JVM:** size the heap to the workload, choose a suitable garbage collector and monitor GC pauses rather than guessing
- **Repository maintenance:** run **revision cleanup** (online compaction) on a schedule so the segment store doesn't bloat, and plan **data store garbage collection** if you use a file or cloud data store
- **Index maintenance:** reindex only when needed, outside peak times
- **Instances:** add Publish instances behind the load balancer to raise capacity, and keep them identical
- **Logging:** keep log levels at INFO or above in production, since debug logging slows things down
- **Request timing:** the request log, and tools such as `rlog`, show how long each request took

### AEM as a Cloud Service

- **Scaling:** Publish scales automatically with load, so your priority is making each request cheap, because inefficient code simply costs more compute
- **No JVM or OS tuning:** that's Adobe's side of the line
- **Code quality gates:** pay attention to the Cloud Manager performance and quality checks, which flag some problems before they reach production
- **Dispatcher and CDN:** your configuration is the main lever, so test it on Dev and Stage
- **Observability:** forward logs to your logging platform and watch response times, errors and cache behavior
- **Load testing:** run it against **Stage** and not Production, and check Adobe's current policy and guidance for testing Cloud Service environments before you start

## Common Symptoms and Likely Causes

| Symptom | Likely cause | First thing to check |
| --- | --- | --- |
| Fast for some pages, slow for others | Uncached pages or heavy components | Dispatcher cache rules and response headers |
| Slow after every publish | Over-invalidation | `/statfileslevel` and `/invalidate` rules |
| CPU high on Publish | Queries without indexes | Query Performance and traversal logs |
| Slow gradually, fixed by restart | Resource or memory leak | Unclosed resolvers, unbounded caches |
| Slow only under load | Insufficient cache hit ratio or capacity | Hit ratio, then instance count |
| Fast server, slow in browser | Frontend weight | Page size, image sizes, third-party scripts |
| Timeouts on certain pages | Slow external calls in rendering | Component code calling remote services |

## A Tuning Checklist

1. Measure the baseline (response times, hit ratio, load)
2. Raise the **cache hit ratio** (rules, params, TTLs, `/statfileslevel`)
3. Move **personalization** out of cached HTML
4. Find and fix **slow queries** with Query Performance and indexes
5. Remove **work from rendering** (queries, remote calls, repeated computation)
6. Fix **resolver leaks** and unbounded in-memory caches
7. Reduce **frontend weight** (images, clientlibs, compression, third parties)
8. On 6.5, review **JVM, compaction and instance count**
9. **Re-measure**, and keep the results so you know what worked
10. Load-test **before** major launches, in a non-production environment

## Series Wrap-Up

That completes **AEM Architecture & Operations**. You should now be able to:

- Explain how the **Dispatcher cache** works and configure invalidation, including `/statfileslevel`, TTLs and query parameter rules
- Troubleshoot **replication** on both 6.5 (agents) and Cloud Service (content distribution)
- Describe the **differences between AEM 6.5 and AEM as a Cloud Service**, and plan a migration
- Find and fix **performance bottlenecks** on Publish, from caching to queries to frontend weight

The final series, **Advanced AEM & Career**, covers headless AEM with GraphQL, Edge Delivery Services, architect interview preparation and migration lessons.
