Caching is the most reliably effective performance tool available and the one most likely to introduce a bug nobody can reproduce. Both facts come from the same property: a cache makes the system serve data that was true at some point rather than data that is true now. Whether that is fine or catastrophic depends entirely on the data, and that judgement is the whole discipline.
The question to answer before adding a cache is how stale this data may be before someone is harmed. Product descriptions can be minutes old. A user's permissions cannot - a revoked administrator who remains an administrator for five minutes because of a cache is a security incident. Account balances, stock levels and anything a person will act on financially need either very short lifetimes or explicit invalidation. Write the tolerance down as a number; teams that skip this step end up choosing lifetimes by feel and discovering the wrong answer through a customer complaint.
The other question is why the thing is slow. A cache in front of a query that is slow because it is missing an index has not fixed anything - it has hidden a fault that will resurface the moment the cache is cold, which is precisely when you are least able to handle it, during a restart or a deploy or after an outage. Fix the underlying query first, then cache what remains slow because it is genuinely expensive to compute.
Invalidation is where the bugs live. Time-based expiry is simple and predictable and always serves some stale data. Event-based invalidation is correct and requires every write path to remember to invalidate, which one of them eventually will not - usually the batch job or the admin tool that nobody thought of as a write path. The pragmatic combination is event-based invalidation with a time-based backstop, so a missed invalidation is a temporary wrongness rather than a permanent one.
Two failure modes are worth designing against explicitly. A stampede, where a popular key expires and a thousand simultaneous requests all miss and all hit the database at once - solved by allowing one request to recompute while others briefly serve stale. And cache dependency, where the system has quietly become unable to survive without the cache; if your database cannot serve a cold start, the cache is not an optimisation, it is a single point of failure you have not acknowledged. Test that path deliberately.
Finally, make cache state visible. Any response served from cache should be identifiable as such in your logs or headers, and hit rates should be on a dashboard. The debugging session that goes worst is the one where an engineer spends three hours proving the code is correct before anyone thinks to ask whether the answer came from cache at all.