Cache User Profiles in Memory
The profile endpoint is our most-read route, and every request goes to the database. This adds a map in front of the repository so repeat reads of the same profile skip the query. Locally, p95 for GET /users/{id}/profile dropped from 38 ms to 2 ms. updateProfile now reads straight from the repository so it always edits the latest data.
Msrc/main/java/com/acme/profile/ProfileService.java1 of 2
Files Changed
src/main/java/com/acme/profile/
src/test/java/com/acme/profile/
src/main/java/com/acme/profile/ProfileService.java
Java · Click a line number to comment · Tap a line to comment@@ -1,26 +1,37 @@
package com.acme.profile;
Added: import java.util.HashMap;
Added: import java.util.Map;
Added:
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class ProfileService {
private final ProfileRepository repository;
Added: private final Map<Long, Profile> cache = new HashMap<>();
public ProfileService(ProfileRepository repository) {
this.repository = repository;
}
public Profile getProfile(long userId) {
Removed: return repository.findById(userId)
Added: Profile cached = cache.get(userId);
Added: if (cached != null) {
Added: return cached;
Added: }
Added: Profile profile = repository.findById(userId)
.orElseThrow(() -> new ProfileNotFoundException(userId));
Added: cache.put(userId, profile);
Added: return profile;
}
@Transactional
public Profile updateProfile(long userId, ProfileUpdate update) {
Removed: Profile profile = getProfile(userId);
Added: Profile profile = repository.findById(userId)
Added: .orElseThrow(() -> new ProfileNotFoundException(userId));
profile.setDisplayName(update.displayName());
profile.setBio(update.bio());
return repository.save(profile);
}
}
src/test/java/com/acme/profile/ProfileServiceTest.java
Java · Click a line number to comment · Tap a line to comment@@ -0,0 +1,28 @@
Added: package com.acme.profile;
Added:
Added: import static org.assertj.core.api.Assertions.assertThat;
Added: import static org.mockito.Mockito.mock;
Added: import static org.mockito.Mockito.times;
Added: import static org.mockito.Mockito.verify;
Added: import static org.mockito.Mockito.when;
Added:
Added: import java.util.Optional;
Added:
Added: import org.junit.jupiter.api.Test;
Added:
Added: class ProfileServiceTest {
Added: private final ProfileRepository repository = mock(ProfileRepository.class);
Added: private final ProfileService service = new ProfileService(repository);
Added:
Added: @Test
Added: void secondReadComesFromTheCache() {
Added: Profile profile = new Profile(42L, "Ana", "Hello");
Added: when(repository.findById(42L)).thenReturn(Optional.of(profile));
Added:
Added: service.getProfile(42L);
Added: Profile again = service.getProfile(42L);
Added:
Added: assertThat(again).isSameAs(profile);
Added: verify(repository, times(1)).findById(42L);
Added: }
Added: }