API 응답시간 개선 - yi5oyu/writemd GitHub Wiki
단일 사용자 테스트 모니터링

개선 후

초기 데이터(JSON) 파일 읽기 시간
GET [302] - REDIRECTION | 832ms(평균) 1.34s(최고) 321ms(최저)
주요 기존 코드
// UserService
@Transactional
public Users saveUser(String githubId, String name, String htmlUrl, String avatarUrl, String principalName) {
Optional<Users> user = userRepository.findByGithubId(githubId);
if (user.isPresent()) {
Users existingUser = user.get();
boolean updated = false;
if (!Objects.equals(existingUser.getName(), name)) {
existingUser.setName(name);
updated = true;
}
if (!Objects.equals(existingUser.getAvatarUrl(), avatarUrl)) {
existingUser.setAvatarUrl(avatarUrl);
updated = true;
}
if (!Objects.equals(existingUser.getPrincipalName(), principalName)) {
existingUser.setPrincipalName(principalName);
updated = true;
}
return updated ? userRepository.save(existingUser) : existingUser;
}
// 새 유저 저장
Users newUser = Users.builder()
.githubId(githubId)
.name(name)
.htmlUrl(htmlUrl)
.avatarUrl(avatarUrl)
.principalName(principalName)
.build();
Folders myFolder = Folders.builder()
.users(newUser)
.title("내 템플릿")
.build();
Folders gitFolder = Folders.builder()
.users(newUser)
.title("깃 허브")
.build();
// JSON 파일에서 템플릿 데이터 로드
List<Map<String, String>> myTemplates;
List<Map<String, String>> gitTemplates;
try {
Resource myResource = new ClassPathResource("data/template.json");
myTemplates = objectMapper.readValue(myResource.getInputStream(),
new TypeReference<List<Map<String, String>>>() {
});
} catch (IOException e) {
myTemplates = Collections.emptyList();
}
for (Map<String, String> templateData : myTemplates) {
Templates template = Templates.builder().folders(myFolder)
.title(templateData.getOrDefault("title", ""))
.description(templateData.getOrDefault("description", ""))
.content(templateData.getOrDefault("content", ""))
.build();
myFolder.getTemplates().add(template);
}
try {
Resource resource = new ClassPathResource("data/git_template.json");
gitTemplates = objectMapper.readValue(resource.getInputStream(),
new TypeReference<List<Map<String, String>>>() {
});
} catch (IOException e) {
gitTemplates = Collections.emptyList();
}
for (Map<String, String> templateData : gitTemplates) {
Templates template = Templates.builder().folders(gitFolder)
.title(templateData.getOrDefault("title", ""))
.description(templateData.getOrDefault("description", ""))
.content(templateData.getOrDefault("content", ""))
.build();
gitFolder.getTemplates().add(template);
}
newUser.getFolders().add(myFolder);
newUser.getFolders().add(gitFolder);
return userRepository.save(newUser);
}
GET [302] - REDIRECTION | 348ms(평균) 403ms(최고) 227ms(최저)
평균 응답시간: 832ms -> 348ms
최고 응답시간: 1.34s -> 403ms
최저 응답시간: 321ms -> 227ms
- 초기 데이터(JSON) 파일 읽기 캐싱
- 트랜잭션 커밋 완료 후 유저 캐시 비동기 업데이트
// config/RedisConfig.cacheManager()
// 자동 캐싱설정
// cache/CacheInitializer
// 캐시 초기화, 초기 JSON 데이터 캐싱
// 유저 정보 비동기 redis 저장
@Async
public void updateUserCacheAsync(String githubId, Users user) {
Cache cache = cacheManager.getCache("user");
if (cache != null) {
cache.put(githubId, user);
}
}
// service/UserService.saveUser() 리팩토링
@Transactional
public Users saveUser(String githubId, String name, String htmlUrl, String avatarUrl, String principalName) {
Optional<Users> existingUser = userRepository.findByGithubId(githubId);
Users user = existingUser
.map(ckUser -> {
// 변경사항 체크 후 업데이트(기존 사용자)
if (!Objects.equals(ckUser.getName(), name)) {
ckUser.setName(name);
}
if (!Objects.equals(ckUser.getAvatarUrl(), avatarUrl)) {
ckUser.setAvatarUrl(avatarUrl);
}
if (!Objects.equals(ckUser.getPrincipalName(), principalName)) {
ckUser.setPrincipalName(principalName);
}
return ckUser;
})
.orElseGet(() -> {
// 새 유저 저장
return Users.builder()
.githubId(githubId)
.name(name)
.htmlUrl(htmlUrl)
.avatarUrl(avatarUrl)
.principalName(principalName)
.build();
});
Users savedUser = userRepository.save(user);
if (existingUser.isEmpty()) {
// JSON 파일에서 템플릿 데이터 로드
List<Map<String, String>> myTemplates = cachingDataService.getMyTemplates();
List<Map<String, String>> gitTemplates = cachingDataService.getGitTemplates();
Folders myFolder = Folders.builder()
.users(savedUser)
.title("내 템플릿")
.build();
Folders gitFolder = Folders.builder()
.users(savedUser)
.title("깃 허브")
.build();
for (Map<String, String> templateData : myTemplates) {
Templates template = Templates.builder().folders(myFolder)
.title(templateData.getOrDefault("title", ""))
.description(templateData.getOrDefault("description", ""))
.content(templateData.getOrDefault("content", "")).build();
myFolder.getTemplates().add(template);
}
for (Map<String, String> templateData : gitTemplates) {
Templates template = Templates.builder().folders(gitFolder)
.title(templateData.getOrDefault("title", ""))
.description(templateData.getOrDefault("description", ""))
.content(templateData.getOrDefault("content", "")).build();
gitFolder.getTemplates().add(template);
}
savedUser.getFolders().add(myFolder);
savedUser.getFolders().add(gitFolder);
savedUser = userRepository.save(savedUser);
}
final Users finalSavedUser = savedUser;
TransactionSynchronizationManager.registerSynchronization(
new TransactionSynchronization() {
@Override
public void afterCommit() {
cachingDataService.updateUserCacheAsync(githubId, finalSavedUser);
}
}
);
return savedUser;
}
사용자 조회 + 노트 생성 + 텍스트 엔티티 생성: 순차 처리, 불필요한 DB 조회 중복
POST [200] - /api/note/create/{userName} | 204ms(평균) 338ms(최고) 151ms(최저)
주요 기존 코드
// NoteService
public NoteDTO createNote(String userName, String noteName) {
Users user = userRepository.findByGithubId(userName)
.orElseThrow(() -> new RuntimeException("유저를 찾을 수 없습니다."));
Notes newNote = Notes.builder()
.users(user)
.noteName(noteName)
.build();
Notes savedNote = noteRepository.save(newNote);
Texts text = Texts.builder()
.notes(savedNote)
.markdownText("")
.build();
textRepository.save(text);
NoteDTO note = NoteDTO.builder()
.noteId(savedNote.getId())
.noteName(savedNote.getNoteName())
.createdAt(savedNote.getCreatedAt())
.updatedAt(savedNote.getUpdatedAt())
.build();
return note;
}
POST [200] - /api/note/create/{userName} | 134ms(평균) 295ms(최고) 85.7ms(최저)
평균 응답시간: 204ms -> 134ms
최고 응답시간: 338ms -> 295ms
최저 응답시간: 151ms -> 85.7ms
- 반복적인 사용자 조회 캐싱
- cascade 설정으로 한 번에 저장(DB 호출 감소)
- 노트와 텍스트 엔티티 생성을 하나의 트랜잭션으로 관리
// NoteService.createNote() 리팩토링
@Transactional
public NoteDTO createNote(String githubId, String noteName) {
Users user = cachingDataService.findUserByGithubId(githubId);
Notes newNote = Notes.builder()
.users(user)
.noteName(noteName)
.build();
Texts text = Texts.builder()
.notes(newNote)
.markdownText("")
.build();
newNote.setTexts(text);
Notes savedNote = noteRepository.save(newNote);
return NoteDTO.builder()
.noteId(savedNote.getId())
.noteName(savedNote.getNoteName())
.createdAt(savedNote.getCreatedAt())
.updatedAt(savedNote.getUpdatedAt())
.build();
}