Transparent caching: why your codebase should not know a cache exists
A cache your application code can see is a cache your team must maintain. We explain why reads should come from memory without the codebase ever knowing, and what that changes.
Open any mature backend and search for the word "cache". You will find it in the data access layer, in a handful of controllers, in a background job that warms keys on deploy, and in a comment that says "do not remove, see incident". The cache started as one helper class. Years later it is a second data model that nobody fully owns.
That spread is not carelessness. It is what happens when the application is responsible for both asking for data and deciding where the data lives. We think those two jobs should be separated, and that the application should only ever do the first one.
What transparent actually means
A transparent cache is one the application cannot see. The code asks for a row by its key, or asks for a list of rows matching some fields, and gets an answer. Whether that answer came from memory or from disk is not the application's concern, and there is no way for the code to tell.
In practice this means the codebase contains no cache client, no time-to-live settings (the timers that decide how long a cached value is trusted before it is thrown away), and no invalidation logic (the code that removes or refreshes a cached value after something changes). Those three things are where most cache bugs live, and removing them from the application removes the bugs with them.
One API, two paths
redfly does this by putting a single API between the application and the database. The API offers six operations: Get, Insert, Update, Delete, GetRows and GetTotalRowCount. They are available over REST (ordinary web calls) and gRPC (a compact, fast way for services to call each other). That is the entire surface the application sees.
Behind the API, reads and writes take different paths. Reads are served from Redis, an in-memory data store that answers in about a millisecond. Writes go straight to the database, which remains the source of truth. The application does not choose the path; it simply calls Get or Update and the API routes the call.
Who keeps the cache honest
The obvious question is how Redis stays correct if nobody in the application is updating it. The answer is a separate sync service that watches the database for changes and pushes them into Redis. On SQL Server it uses change tracking, read on a short interval. On PostgreSQL it uses logical replication, and on MongoDB it uses change streams; both of those are streamed as they happen.
Because the sync service watches the database rather than the application, it does not matter where a write came from. Rows changed by the API, by a nightly batch job or by an administrator at a console all reach Redis the same way. Hand-written caches usually miss the last two, which is why stale data so often appears after a "harmless" script runs.
Why every hand-written cache layer rots
We have watched this pattern for two decades, and the decay follows the same steps each time.
- A developer adds a cache in front of one slow query. It works, and it is small.
- Other developers copy the pattern for their own queries, each choosing their own key format and expiry timer.
- A new write path appears, often a bulk import or a fix script, and it does not know about the cache. The first stale-data bug ships.
- Someone adds invalidation calls after every write they can find. They miss some. The invalidation code itself now has bugs.
- The team stops trusting the cache, shortens every timer, and the database load creeps back up. The cache is still in the codebase, but it is barely doing anything.
At every step the cache is a little more entangled with the product code, and a little harder to remove. The rot is not a sign of a weak team. It is the natural result of putting cache policy in the same place as business logic.
A cache the application can see is a cache the application will eventually break.
What the application gains
When the cache is invisible, a few things change for the engineers who work on the product. Code reviews no longer include the question "did you remember to invalidate". Test suites do not need a fake cache to be set up and torn down. A new engineer can read the data access code and understand it in an afternoon, because it looks like ordinary data access.
One engineering leader who reviewed a system built this way said they understood how the code worked but could not find where the caching was done. That is the intended result.
The honest limits
Transparent does not mean magic. The cache is only as fresh as the sync service keeps it, so there is a short window after a write in which a read may return the previous value. For most application reads, such as permission checks, lookups and profile loads, that window is fine. For the rare read that must see its own write instantly, the design conversation has to happen up front.
Fallback is part of the contract
A transparent cache also has to be transparent when it fails. If Redis is unreachable, the redfly API serves reads from the database instead, automatically. The application sees slower responses, not errors, and no code in the application has to switch modes.
redfly sits between an application and its SQL Server, MongoDB or PostgreSQL database, serving reads from a Redis cache it keeps in sync and passing writes straight through. The codebase never sees the cache, which is the point.