Microservices typically run multiple instances in parallel. When a cache is required to improve performance for certain use cases, a simple in-memory cache offers significant gains without the need for additional infrastructure, such as a Redis instance.
Cache invalidation is a major downside of having an individual in-memory cache for each microservice instance. When cached data changes, all instances must invalidate or update their caches. Otherwise, an instance may continue using outdated data.
With a shared external cache, such as Redis, all microservice instances use the same source of truth. In contrast, each microservice needs a notification to synchronize its individual cache when data is updated. The challenge is how to notify all microservices without introducing another infrastructure service.
Idea
Given that the microservices already use a Postgres database, its built-in Listen/Notify (aka Pub/Sub) feature can be used to synchronize all instances.
When the cached data is also stored in Postgres, this approach makes even more sense. With a trigger in place, Postgres automatically sends notifications to all listening services when data changes. Any microservice instance—or even an external Postgres connection—can change Postgres records, and all services will evict their affected in-memory caches without any additional steps.
Example Code
Two components are required to get this to work. On one hand, Postgres must send notifications using pg_notify. Ideally, change notifications are sent automatically on any insert, update, or delete on particular tables.
On the other hand, each microservice instance must listen for notifications on the same channel.
Listen for Notifications in Spring
The code omits some checks and logging for brevity.
1const val NOTIFICATIONS_CHANNEL = "change_notifications"
2const val NOTIFICATION_POLL_TIMEOUT = 1000
3
4@Component
5class PostgresNotificationListener(
6 private val dataSource: DataSource,
7 private val notificationParser: PostgresNotificationParser,
8 private val eventPublisher: ApplicationEventPublisher,
9) {
10
11 @Async
12 @EventListener(ApplicationReadyEvent::class)
13 fun listenForNotifications() {
14 dataSource.connection.use { connection ->
15 val pgConnection = connection.unwrap(PGConnection::class.java)
16
17 connection.createStatement().use {
18 it.execute("LISTEN $NOTIFICATIONS_CHANNEL")
19 }
20
21 while (!Thread.currentThread().isInterrupted) {
22 pgConnection.getNotifications(NOTIFICATION_POLL_TIMEOUT)
23 ?.mapNotNull(notificationParser::mapFromParameter)
24 ?.forEach(eventPublisher::publishEvent)
25 }
26 }
27 }
28}
PostgresNotificationParser is a class that takes the notification message payload, parses it, and maps it to an internal application event. That event is then published within the Spring ApplicationContext. In this example, such an event could be of type SettingsChanged.
Since the listener waits for notifications in a loop, it should run asynchronously. The example above uses @Async, which requires async execution to be enabled in the Spring application.
Spring Cache Eviction
1@Service
2class SettingsService {
3 [...]
4
5 @CacheEvict(cacheNames = [SETTINGS_CACHE_NAME], allEntries = true)
6 @EventListener
7 internal fun onSettingsChanged(event: SettingsChanged) {
8 logger.info("Received SettingsChanged. Performing cache eviction.")
9 }
10}
Postgres Change Detection and Notifications
To publish change notifications automatically on inserts, updates, or deletes in a particular table, a function must be created that can be called from a trigger. The trigger associates the function with a single table and specifies which changes will cause it to execute.
1CREATE OR REPLACE FUNCTION notify_on_change()
2 RETURNS TRIGGER AS $$
3BEGIN
4 PERFORM pg_notify('change_notifications', TG_TABLE_SCHEMA || '/' || TG_TABLE_NAME);
5 RETURN NULL;
6END;
7$$ LANGUAGE plpgsql;
8
9CREATE TRIGGER settings_notification_trigger
10 AFTER INSERT OR UPDATE OR DELETE ON settings
11 FOR EACH ROW
12EXECUTE FUNCTION notify_on_change();
Notifications can also be sent programmatically, by running a simple query like this. This approach is not limited to events originating in Postgres. Each microservice instance could trigger such an event at any time.
1SELECT pg_notify('change_notifications', '[message-payload]');
This idea was inspired by You Don’t Need Redis, Postgres Already Has Pub/Sub.
Disclaimer: AI helped me phrase this post. The idea and content are entirely my own.