In a JMeter plan with an HTTP Cache Manager, why does the second iteration log fewer samples?
answer
- Count the rows, not the times
- Iteration two never left the machine
- A hit can produce no result
- One property names three modes
basics
~10 sJMeter's HTTP Cache Manager serves a still-fresh GET out of the thread's own cache without touching the network, and its default cached-resource mode, RETURN_NO_SAMPLE, records no result at all for that hit.
solid answer
~40 sThe Cache Manager keeps a per-thread cache — up to 5000 entries by default — and `Clear cache each iteration` is off on a newly added element, so iteration two starts holding everything iteration one collected. Before opening a connection, and only for `GET`, the HttpClient4 sampler asks the manager whether the entry is still fresh. If **Use Cache-Control/Expires header when processing GET requests** is ticked and the stored expiry is in the future, the sampler returns immediately. What it returns is decided by `cache_manager.cached_resource_mode`, whose default `RETURN_NO_SAMPLE` produces no `SampleResult` at all: no row in the results file, nothing in any listener. Iteration two is therefore both faster and thinner, and the aggregate you read afterwards covers only the samples that really went to the network.
code
xml · 4 lines<CacheManager guiclass="CacheManagerGui" testclass="CacheManager" testname="HTTP Cache Manager" enabled="true">
<boolProp name="clearEachIteration">false</boolProp>
<boolProp name="useExpires">true</boolProp>
</CacheManager>go deeper
Recall that the HTTP Cache Manager simulates a browser cache and that its cache belongs to one thread, not to the whole plan.
Explain the GET-only short-circuit and name the three values of cache_manager.cached_resource_mode and what each produces.
Diagnose a run whose sample count changed between iterations by checking the element before the application, and make hits visible.
Decide as policy whether team plans keep or clear the cache, so two people's results describe the same shape of traffic.
## What the cache holds after iteration one Each JMeter thread gets its **own** cache, held in a thread-local inside `CacheManager`, with room for 5000 entries by default — the **Max Number of elements in cache** field, stored as `maxSize`. After a sample whose response code is `2xx` or `304` and whose method is listed in `cacheable_methods` (default `GET`), the manager stores that URL's `Last-Modified`, `Etag`, `Cache-Control`/`Expires` and `Date`. A Cache Manager added through the GUI arrives with **Clear cache each iteration** unticked and **Use Cache-Control/Expires header when processing GET requests** ticked. So by default the cache survives the iteration boundary and freshness is honoured — which is exactly the combination that makes the second pass look different. ## Why the row disappears On the next sample for that URL the HttpClient4 implementation checks the cache **before** it opens a connection, and only when the method is `GET`: 1. if the stored entry's expiry is still in the future, the sampler returns without sending anything; 2. what it returns is chosen by the `cache_manager.cached_resource_mode` property; 3. the default, `RETURN_NO_SAMPLE`, returns nothing at all — there is no `SampleResult`, so no row in the JTL, no line in a listener, no contribution to any count. The static assets that dominated iteration one's sample count are simply absent from iteration two. Response times did not improve; the expensive samples were deleted from the denominator. ## The three modes | `cache_manager.cached_resource_mode` | What a cache hit produces | |---|---| | `RETURN_NO_SAMPLE` (default) | Nothing. The hit is invisible in every listener and in the results file | | `RETURN_200_CACHE` | A successful sample with code `200` and response message `(ex cache)`, overridable via `RETURN_200_CACHE.message` | | `RETURN_CUSTOM_STATUS` | A successful sample using `RETURN_CUSTOM_STATUS.code` and `RETURN_CUSTOM_STATUS.message`, both of which you must set yourself | ## The other half: conditional requests When the stored entry has no usable freshness — only `Last-Modified` or an `Etag` — the request does go out, but carrying `If-Modified-Since` and `If-None-Match`. A `304` comes back with an empty body. That sample **is** recorded, because a real round trip happened, but it is cheap and carries no content, which is enough to break a Response Assertion written against the body. The manual warns about exactly this. One trap for anyone grepping the docs: the Cache Manager section of the component reference calls the first header `If-Last-Modified`, while the code sends `If-Modified-Since`. Trust the wire. ## What to do about it - Decide **deliberately** whether `Clear cache each iteration` is on. Off models a returning visitor; on makes every iteration pay full price. - If you keep the cache, set `cache_manager.cached_resource_mode=RETURN_200_CACHE` so the hits become visible rows and the sample count stops moving between iterations for reasons nobody on the team can see. - Do not expect the cache to cover anything but `GET`. `cacheable_methods` controls what is stored, and the short-circuit inside the sampler is fixed to `GET` regardless. - Watch memory. `maxSize` is per thread, so 500 threads at the default is 2.5 million potential entries; the manual tells you to raise `-Xmx` to match if you increase it. - If you inherit a report where iteration counts differ between passes, check this element before you check the application.
- How do you tell a JMeter cache hit apart from a real network round trip in the results?By default you cannot, because the hit produces no sample. Set `cache_manager.cached_resource_mode=RETURN_200_CACHE` and each hit is logged with code 200 and the response message `(ex cache)`; or use `RETURN_CUSTOM_STATUS` with `RETURN_CUSTOM_STATUS.code` and `RETURN_CUSTOM_STATUS.message` to pick your own marker.
- Why can a JMeter HTTP Cache Manager make a Response Assertion fail on the second iteration?When the stored entry has only `Last-Modified` or `Etag`, the sampler sends a conditional request with `If-Modified-Since` and `If-None-Match`. The server answers `304` with an empty body, so an assertion looking for text in the response has nothing to match.
It is like a turnstile that only counts people who queued. Anyone already inside walks straight through uncounted, so the day's tally makes the building look quieter than it really was.
saying these in an interview costs you the question
- Says the cache is shared by all threads
- Claims a cache hit is always logged as a fast 200
- Thinks Clear cache each iteration is on by default
- Reads the improved average as a server-side speed-up
- Expects POST responses to be served from the cache