# Cache invalidation on metadata change When a PM saves a change in BACK — adds a column to a form, updates a permission, registers a new module — every running node has to drop its cached interpretation of the old metadata. xly does this through JMS, not by polling. ## The path ``` PM saves in BACK │ ▼ BACK controller writes the changed gds_* row │ ▼ Controller publishes a JMS "module changed" message │ ▼ Every node's xlyErpJmsConsumer receives it │ ▼ ConsumerChangeGdsModuleThread.run() clears the relevant Redis keys │ ▼ Next /business/getModelBysId call on any node re-reads the table and re-populates the cache with the new value ``` The handler is in `xlyErpJmsConsumer/src/main/java/com/xly/xlyerpjmsconsumer/thread/ConsumerChangeGdsModuleThread.java`. ## Why JMS, not poll-and-bust xly often runs across multiple nodes (xlyEntry, xlyApi, xlyInterface each on their own JVM, sometimes scaled horizontally). Polling for "has the metadata changed?" would either be slow (the change isn't visible until the next poll) or chatty (constant heartbeats). JMS fans out the invalidation to every node within milliseconds. xly uses both **ActiveMQ** and **RocketMQ** at different points; the metadata-change events typically run on the simpler of the two (usually ActiveMQ — confirm via `application*.yml`). ## Which keys get cleared The Redis cache holds: - Module metadata by `sId`. - Form metadata by `sId`. - Field-list slaves keyed by form `sId`. - Per-tenant overlay merges (a derived cache). - Permission rules per (module, role). The consumer thread receives the changed row's IDs and clears each cache key family that could plausibly include it. **Over-invalidating is the safe option here** — the cost of an extra DB read on the next request is far smaller than the cost of serving stale metadata. ## When you change metadata directly via SQL Inserts/updates done through MyBatis or BACK *trigger* the JMS event. Inserts/updates done by an engineer running raw `UPDATE gdsmodule SET ...` against the production DB do **not** trigger it. The cache will serve stale metadata until either: 1. The cache TTL expires (check the cache config for the actual TTL). 2. A bounce of the application servers. 3. A manual JMS message is sent (see `BusinessCleanRedisDataImpl` in `xlyBusinessService`). The third option is the supported workaround; option (2) is the brute-force fallback. ## Common bug: the cache is the bug When something looks like "I changed it but the page still shows the old value", check (in this order): 1. Did the change actually commit? (Confirm with `SELECT` against the DB.) 2. Is the JMS broker reachable from the BACK node? (If not, the invalidation event silently isn't published.) 3. Are all consumer nodes running? (If a node is paused, it'll serve stale metadata until restarted.) 4. Did the change happen via raw SQL? (Then no JMS event was published — manually trigger it.) The "five-table read" of [Slice 1](../../slices/01-hello-world.md) re-runs from the cache; understanding which layer is stale is the key to the bug.