Local Redis cache isolation¶
TL;DR¶
By default every web project's Web.config points the Redis cache at the
shared test cache (raklet-test-cache-ng.redis.cache.windows.net). Local
dev, the test environment, and CI all read and write the same keys on that
one instance. That causes cross-environment cache bleed — most visibly a
broken org logo on local pages.
Fix: run a local Redis and point your machine at it.
pwsh scripts/dev/setup-local-redis.ps1
Then recycle your app pool (or iisreset). No application code changes are
required, and nothing you do here is committed.
Why the logo breaks (the motivating bug)¶
SessionService.OrganisationInfo(permalink)
(Services/SessionService.cs)
caches public org info under the key "{permalink}-OrgInfoForPublicPages" for
12 hours:
model.LogoUrl = ConfigurationManager.AppSettings["StoragePath"] + organisation.LogoUrl;
CacheService.Set(cacheKey, model.ToJson(), TimeSpan.FromHours(12));
Two facts make this leak across environments:
- The cache key has no environment namespace — it is just
gercek-OrgInfoForPublicPages, identical in local, test, and CI. CacheServiceconnects to whateverRaklet-Cache-Connectionstringpoints at, and the committedWeb.configvalue is the shared test cache. So local and test share the same Redis database.
The cached JSON bakes in the database's organisation.LogoUrl value. The
key is purged on any org-settings change (WebJobs, CRM, and the V2 API all call
CacheService.Purge("{permalink}-OrgInfoForPublicPages")) — again, against the
shared Redis. So the sequence is:
- Something on test/CI touches a
gercek-permalinked org → purges the shared key. - The next request to hit that permalink — possibly from test/CI, not your local machine — re-populates the key from that environment's database.
- If test's row has an empty/default logo, or a logo blob that doesn't exist,
your local page now renders test's broken
<img src>until the next purge or the 12-hour TTL expires.
That is why the logo "breaks again" intermittently with no local change.
StoragePathis identical (rakletlocalfiles) across all configs, so the differentiator is purely the DB-sourcedLogoUrlcarried by whichever environment wrote the key last.
What the setup script does¶
scripts/dev/setup-local-redis.ps1:
- Checks whether a Redis server is already answering on
localhost:6379. - If not, installs Redis on Windows via
winget install Redis.Redis(registers an auto-starting Windows service on port 6379). Pass-SkipInstallto skip this and only write config. - For each local web project (
Application,Raklet.Backend,Raklet.Admin,Raklet.Api,Raklet.Login,Raklet.Crm), ensuresappSettings.local.configcontains aRaklet-Cache-Connectionstringoverride pointing atlocalhost:6379. It creates a minimal overlay file if none exists, injects the key if the file exists without it, and leaves it alone if the key is already present. - For each of the four WebJobs projects, writes the same override both at the
project root and directly into any existing
bin\Debug/bin\Releaseoutput folder next to the built.exe.config. WebJobs resolve theirappSettings.local.configoverlay relative to the build output, not the project root, so an already-built or currently-running WebJob only picks up the change once itsbin\<Configuration>copy is written and the process is restarted. Before writing, the script verifies thatApp.configloads the overlay and the project file copies it to the build output; it fails with a concrete wiring error instead of reporting successful isolation when either prerequisite is missing.
appSettings.local.config is gitignored, so your override is never committed.
The file= attribute on <appSettings> (Web.config for the web projects,
App.config for the WebJobs) makes it an overlay: this key overrides the
committed test-cache value; every other key falls through unchanged.
The override, by hand¶
If you prefer to do it manually, add this to each project's
appSettings.local.config (copy from appSettings.local.config.template):
<add key="Raklet-Cache-Connectionstring"
value="localhost:6379,abortConnect=False,connecttimeout=15000,synctimeout=15000" />
Three things differ from the committed test string — all required or it won't connect:
| Test cache | Local Redis | |
|---|---|---|
| Port | 6380 |
6379 |
| TLS | ssl=True |
(omitted) |
| Auth | password=... |
(none) |
Choosing a Redis server¶
| Option | Install | Notes |
|---|---|---|
| Redis on Windows (default) | winget install Redis.Redis |
One-liner, auto-starts as a service on 6379. Older build, but the cache only does GET/SET/EXPIRE, so it's fine for dev. |
| Memurai Developer | Manual MSI from https://www.memurai.com/get-memurai | Free for dev, current Redis 7 compatibility, polished Windows service. Not in winget. |
| WSL / Docker | apt install redis-server / docker run -p 6379:6379 redis |
Only worthwhile if you already run WSL or Docker; otherwise heavier than the above. |
Any of them listens on localhost:6379 with no TLS and no password, so the
same override string works for all.
Scope and a note for the future¶
This isolates the cache only; your local app still uses the shared dev
database and storage from Web.config unless you override those too. The logo
is served by the Application project, so isolating that one resolves the
immediate bug; the script covers the other web projects and the four WebJobs
for completeness.
A more durable, code-level fix would be to namespace cache keys per
environment (e.g. prefix every CacheService key with an Environment
appSetting) so a shared Redis can never collide. That's a broader change to
CacheService and out of scope here — tracked as a follow-up.