# SWS Caching

> Advanced Topic - Caching Responses - Read Carefully!

---

LLMS index: [llms.txt](/llms.txt)

---

<div class="alert alert-warning" role="alert"><div class="h4 alert-heading" role="heading">Read this section before turning caching on</div>


SWS response caching is provided by the PeopleSoft `RESTListeningConnector` servlet — SWS does not implement its own cache, it only opts an endpoint in. The cache has several non-obvious behaviors that can leak data between callers or serve stale data indefinitely. Read the companion <a href="https://ib.books.cedarhillsgroup.com/rest/restcaching/">PeopleSoft REST Caching chapter in the Integration Broker book</a> for the underlying mechanics; this page covers how it applies specifically to SWS.
</div>


## How SWS uses the PeopleTools REST cache

When you set **Minutes to Cache Response** to a non-zero value on an SWS configuration row, the handler adds a `Cache-Control: max-age=<N*60>` header to the response. The PeopleTools `RESTListeningConnector` servlet then stores the full response in memory and replays it for subsequent matching requests until the TTL expires. The cache lives in the PIA web-server JVM, not in PeopleSoft application-server memory — restarting PIA empties it; restarting the app server does not.

No code change is required to opt in. Set a non-zero value on the configuration row, save, and the very next request from that endpoint will populate the cache.

## What gets cached, and what the cache key actually is

The cache key is the **full request URL** — that is, path *and* query string — combined with the `Accept` header. Two requests are treated as cache hits only when all three match exactly.

|                Component                 | Part of cache key? |                                                                Notes                                                                 |
| ---------------------------------------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------ |
| URL path (e.g. `/acmecorp/people/PS`)    | yes                | Different path = different cache entry. `{emplid}` path parameters create one entry per distinct value.                              |
| Query string (e.g. `?rowLimit=50`)       | yes                | `?rowLimit=10` and `?rowLimit=20` are independent cache entries. So is the absence of any query string.                              |
| `Accept` header                          | yes                | JSON, XML, and CSV responses are cached independently. Three different `Accept` values populate three cache entries.                 |
| `Authorization` header (basic-auth user) | **no**             | **This is the central caching gotcha.** Two different callers hitting the same URL get the same cached response — see below.         |
| Request body (POST endpoints)            | no                 | The REST cache is GET-oriented. `CHG_SWS_PSOFTQL` (a POST endpoint) effectively does not benefit from this cache for variable input. |

## Choosing a TTL

The right cache duration is the longest interval during which a stale response is still acceptable to the caller. Some patterns we've seen work well:

|                 Endpoint pattern                 |      Recommended TTL       |                                               Why                                                |
| ------------------------------------------------ | -------------------------- | ------------------------------------------------------------------------------------------------ |
| XLAT / setup lookups (subjects, campuses)        | 60 minutes (or 1440 / 24h) | These change on a release cadence — minutes-old data is fine.                                    |
| Reference tables editable only by power users    | 5–30 minutes               | Long enough to absorb burst traffic, short enough that an admin's edit shows up within the hour. |
| Reporting / dashboard rollups                    | 5–15 minutes               | Dashboards typically refresh on this cadence anyway; caching aligns with the refresh budget.     |
| Real-time-ish data (current term enrollments)    | 1–2 minutes                | Use only when the source data changes slower than the TTL.                                       |
| Per-user data (uses `%OPERATOR` / `%EMPLOYEEID`) | **0 (disabled)**           | Caching across users leaks data. See the security alert below.                                   |
| Cheap queries (<100ms server time)               | **0 (disabled)**           | The cache itself adds overhead. If the query isn't slow, don't cache.                            |

There is no upper limit enforced by SWS. Practical ceiling: anything beyond a few hours starts feeling like a static asset, at which point you should consider regenerating the data into a separate table refreshed by a scheduled job rather than caching an unbounded query.

## No manual cache invalidation

There is no SWS or PeopleSoft UI to flush the REST cache. Your options when cached data is wrong are:

1. **Wait out the TTL.** The cache entry expires naturally and the next request re-runs the SQL.
2. **Bounce PIA.** Restarting the PeopleSoft web server tier empties the JVM cache. This is heavy-handed but effective.
3. **Change the URL.** Adding a cache-busting query-string parameter (`?_v=2`) creates a new cache entry rather than replacing the old one — works as a per-caller workaround when you can change the integration partner's URL.

Choose your TTL with the assumption that you cannot intervene mid-window.

## Security: the cross-user data-leak risk

The cache key does **not** include the caller's OPRID. Two callers hitting the same URL within the TTL window receive **the same response**, even when:

- They were authenticated as different OPRIDs.
- They belong to different permission lists.
- The underlying SQL uses `%OPERATOR` or `%EMPLOYEEID` and would otherwise return different rows per user.

This means that an endpoint where caller A hits `/student/grades` and gets caller A's grades will, if cached, return caller A's grades to caller B's next call as well.

<div class="alert alert-danger" role="alert"><div class="h4 alert-heading" role="heading">Hard rule</div>


Never enable caching on an SWS configuration whose SQL contains `%OPERATOR`, `%EMPLOYEEID`, or any other per-caller binding — including SQL that filters by the caller's row-level-security setid. The handler does not warn you about this; nothing in the UI prevents it. The first cross-user request after the cache populates will return the wrong person's data.
</div>


Safe caching candidates: endpoints whose response depends only on the URL, query string, and database state — never on which OPRID is calling.

## Detecting whether a response was served from cache

There is no SWS-emitted "cache hit" header. Inspect these signals instead:

- **`meta.responseDTTM`** in JSON/XML responses (or `x-requestTime` for CSV) is the timestamp the response was *originally* assembled. On a cache hit it stays at the populate-time value, not the current wall clock. If two calls 30 seconds apart return identical `responseDTTM` strings, the second was a cache hit.
- **`meta.psftTransactionId`** is also frozen at populate time. Identical transaction IDs across calls = same cached entry.
- Wall-clock latency drops sharply on a hit — typically from hundreds of milliseconds (real SQL execution) to single-digit milliseconds (servlet returning cached bytes).

Combine the three: if `responseDTTM` and `psftTransactionId` match across two calls and latency is sub-10ms, you're seeing a cache hit.

## See also

- [Caching limitations](https://sws.books.cedarhillsgroup.com/docs/meta/limitations/) — same restrictions listed alongside other product boundaries.
- [PeopleSoft REST Caching — Integration Broker book](https://ib.books.cedarhillsgroup.com/rest/restcaching/) — the underlying servlet mechanics.
