βΒ Β 60x Speed Improvement
- Β Β β Version
- Β Β ποΈ Database
- Β Β π API
- Β Β π Performance
- Β Β N+1 Query
- Β Β Bulk Query
- Β Β Benchmark
- Β Β π Package
- JavaΒ :Β Β 11 β 17
- Spring BootΒ :Β Β 2.7.8 β 3.2.7
- SwaggerΒ :Β Β Springfox β Springdoc
- AWS CloudWatchΒ :Β Β Agent β Logs
- login_idΒ Β βΒ Β email
- first_passwordΒ Β βΒ Β password
- usernameΒ Β βΒ Β nickname
- λ μ§λ₯Ό λ¬Έμμ΄λ‘ DBμ μ μ₯ν κ²½μ°, μΆν μ λ ¬ μ μλμ μ νμ± μΈ‘λ©΄μμ λΆλ¦¬ν¨.
- LocalDateTimeμΌλ‘ DBμ μ μ₯ ν, μλ΅ μ μνλ ν¬λ§·μ λ¬Έμμ΄λ‘ λ³ννλ λ°©μμ μ±ν.
- modified_date (VARCHAR)Β Β βΒ Β modified_time (DATETIME)
- is_friend : 0Β ,Β Β is_wait : 1Β Β βΒ Β friendship_state (SEND)
- is_friend : 1Β ,Β Β is_wait : 0Β Β βΒ Β friendship_state (FRIEND)
- κΈ°μ‘΄ id κ°μ μ§μ μ μ₯νλ λ°©μμ, μΆν μ‘°ν μ μΆκ°μ μΈ μΏΌλ¦¬ λ° λ©μλ νΈμΆμ λλ°ν¨.
- Friendship ν μ΄λΈμ User ν μ΄λΈμ λ λ² μ°κ΄κ΄κ³ 맀ννμ¬, senderUserλ μ°κ²°νλ λ°©μμ μ±ν.
- sender_user_id (Long)Β Β βΒ Β sender_user_id (User)
- JWT Access Tokenλ§ μ΄μ© μ, 6μκ°μ 짧μ λ‘κ·ΈμΈ μ μ§μκ°μ κ°μ§λ©° 보μμ μ·¨μ½ν¨.
- Access Token λ§λ£ μ, Refresh TokenμΌλ‘ μ¬λ°κΈ λ°μ 2μ£Όλμ λ‘κ·ΈμΈ μ μ§κ° κ°λ₯νλ©° 보μμ΄ κ°νλ¨.
- Access TokenΒ Β βΒ Β Access Token + Refresh Token ν¨κ» μ΄μ©.Β Β (FE : Axios Interceptor μ μ©)
Before | After |
---|---|
-Β Β λΆνμνκ² λ§μ API νΈμΆλ‘ μ±λ₯ μ ν λ°μ -Β Β μ¬μ©μμκ² userIdκ° μμ£Ό λ ΈμΆλμ΄ λ³΄μμ± μ ν |
-Β Β RestFul URI λ° API κ°μ λ¨μΆμΌλ‘ μ±λ₯ ν₯μ -Β Β Security Context μ λ³΄λ‘ userIdλ₯Ό λ체νμ¬ λ³΄μμ± ν₯μ |
Β CodeΒ :Β Open!
// < Before - JPA 쿼리 λ©μλ (Lazy μ‘°ν) >
Optional<User> findById(Long userId); // User
// < After - Fetch Join λ©μλ (Eager μ‘°ν) >
@Query("SELECT u FROM User u " + // User
"LEFT JOIN FETCH u.userMemoList uml " + // + User.userMemoList
"LEFT JOIN FETCH uml.memo m " + // + User.userMemoList.memo
"LEFT JOIN FETCH m.userMemoList umll " + // + User.userMemoList.memo.userMemoList
"LEFT JOIN FETCH umll.user " + // + User.userMemoList.memo.userMemoList.user
"WHERE u.id = :userId")
Optional<User> findByIdToDeepUserWithEager(@Param("userId") Long userId);
@Transactional(readOnly = true)
@Override
public List<MemoDto.MemoPageResponse> findMemos(String filter, String search) { // λ©λͺ¨ λͺ©λ‘ μ‘°ν,μ λ ¬,κ²μ λ‘μ§
if(filter != null && search != null) throw new Exception400.MemoBadRequest("μλͺ»λ 쿼리νλΌλ―Έν°λ‘ APIλ₯Ό μμ²νμμ΅λλ€.");
Predicate<Memo> memoPredicate = (filter != null) ? filterMemos(filter) : searchMemos(search);
Long loginUserId = SecurityUtil.getCurrentMemberId();
// < Before - JPA 쿼리 λ©μλ (Lazy μ‘°ν) > N+1 λ¬Έμ O
User user = userRepository.findById(loginUserId).orElseThrow(() -> new Exception404.NoSuchUser(String.format("userId = %d", loginUserId)));
// < After - Fetch Join λ©μλ (Eager μ‘°ν) > N+1 λ¬Έμ X
User user = userRepository.findByIdToDeepUserWithEager(loginUserId).orElseThrow(() -> new Exception404.NoSuchUser(String.format("userId = %d", loginUserId)));
List<MemoDto.MemoPageResponse> memoPageResponseDtoList = user.getUserMemoList().stream()
.map(UserMemo::getMemo) // User.userMemoList (N+1 쿼리 λ°μ)
.filter(memoPredicate) // User.userMemoList.memo (N+1 쿼리 λ°μ)
.sorted(Comparator.comparing(Memo::getModifiedTime, Comparator.reverseOrder())
.thenComparing(Memo::getId, Comparator.reverseOrder()))
.map(MemoDto.MemoPageResponse::new) // User.userMemoList.memo.userMemoList & User.userMemoList.memo.userMemoList.user (λ΄λΆμμ N+1 쿼리 λ°μ)
.collect(Collectors.toList());
return memoPageResponseDtoList;
}
Β CodeΒ :Β Open!
// < Before - JPA saveAll >
void saveAll(List<UserMemo> userMemoList);
// < Before - JPA deleteAll >
void deleteAll(List<Memo> memoList); // deleteAllInBatch()λ ORμ μ μ±λ₯ μ νμ μ€λ²ν€λμ κ°λ₯μ±μΌλ‘ μ¬μ©νμ§ μμμ.
// < After - JDBC Batch Insert >
public void batchInsert(List<UserMemo> userMemoList) {
String sql = "INSERT INTO user_memo (user_id, memo_id) VALUES (?, ?)";
for (int i=0; i<userMemoList.size(); i+=BATCH_SIZE) { // 'BATCH_SIZE = 1000' λ°°μΉ ν¬κΈ° μ€μ (λ©λͺ¨λ¦¬ μ€λ²ν€λ λ°©μ§)
List<UserMemo> batchList = userMemoList.subList(i, Math.min(i+BATCH_SIZE, userMemoList.size()));
jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() {
@Override
public void setValues(PreparedStatement ps, int i) throws SQLException {
UserMemo userMemo = batchList.get(i);
ps.setLong(1, userMemo.getUser().getId());
ps.setLong(2, userMemo.getMemo().getId());
}
@Override
public int getBatchSize() {
return batchList.size();
}
});
}
}
// < After - JDBC Batch Delete >
public void batchDelete(List<Memo> memoList) {
for (int i=0; i<memoList.size(); i+=BATCH_SIZE) {
List<Long> batchList = memoList.subList(i, Math.min(i+BATCH_SIZE, memoList.size()))
.stream()
.map(Memo::getId)
.collect(Collectors.toList());
String sql = String.format("DELETE FROM memo WHERE memo_id IN (%s)", // ORμ μ΄ μλ INμ μ¬μ©.
batchList.stream()
.map(String::valueOf)
.collect(Collectors.joining(",")));
jdbcTemplate.update(sql);
}
}
< Before > < After >
----------------------------------------------------------------------------------------------
: :
βββ config βββ config
β βββ JwtSecurityConfig.java β βββ SecurityConfig.java
β βββ SwaggerConfig.java β βββ SwaggerConfig.java
β βββ WebSecurityConfig.java βββ controller
βββ controller β βββ AuthController.java
β βββ AuthController.java β βββ FriendshipController.java
β βββ FriendshipController.java β βββ MemoController.java
β βββ MemoController.java β βββ TestController.java
β βββ TestController.java β βββ UserController.java
β βββ UserController.java βββ domain
βββ domain β βββ Friendship.java
β βββ DefaultFriendshipEntity.java β βββ Memo.java
β βββ DefaultMemoEntity.java β βββ User.java
β βββ friendship β βββ common
β β βββ Friendship.java β β βββ BaseEntity.java
β β βββ FriendshipJpaRepository.java β βββ enums
β βββ memo β β βββ Authority.java
β β βββ Memo.java β β βββ FriendshipState.java
β β βββ MemoJpaRepository.java β βββ mapping
β βββ user β βββ UserMemo.java
β β βββ Authority.java βββ dto
β β βββ User.java β βββ AuthDto.java
β β βββ UserJpaRepository.java β βββ FriendshipDto.java
β βββ userandmemo β βββ MemoDto.java
β βββ UserAndMemo.java β βββ UserDto.java
β βββ UserAndMemoJpaRepository.java βββ jwt
βββ dto β βββ CustomUserDetailsService.java
β βββ friendship β βββ JwtFilter.java
β β βββ FriendshipRequestDto.java β βββ TokenProvider.java
β β βββ FriendshipResponseDto.java β βββ handler
β β βββ FriendshipSendRequestDto.java β βββ JwtAccessDeniedHandler.java
β β βββ FriendshipSendResponseDto.java β βββ JwtAuthenticationEntryPoint.java
β β βββ FriendshipUpdateRequestDto.java β βββ JwtExceptionFilter.java
β βββ memo βββ repository
β β βββ MemoInviteResponseDto.java β βββ FriendshipBatchRepository.java
β β βββ MemoResponseDto.java β βββ FriendshipRepository.java
β β βββ MemoSaveRequestDto.java β βββ MemoBatchRepository.java
β β βββ MemoSaveResponseDto.java β βββ MemoRepository.java
β β βββ MemoUpdateRequestDto.java β βββ UserMemoBatchRepository.java
β β βββ MemoUpdateStarRequestDto.java β βββ UserMemoRepository.java
β βββ token β βββ UserRepository.java
β β βββ TokenDto.java βββ response
β βββ user β βββ GlobalExceptionHandler.java
β β βββ UserIdResponseDto.java β βββ ResponseCode.java
β β βββ UserLoginRequestDto.java β βββ ResponseData.java
β β βββ UserRequestDto.java β βββ exception
β β βββ UserRequestDtos.java β β βββ CustomException.java
β β βββ UserResponseDto.java β β βββ Exception400.java
β β βββ UserSignupRequestDto.java β β βββ Exception404.java
β β βββ UserUpdateNameRequestDto.java β β βββ Exception500.java
β β βββ UserUpdatePwRequestDto.java β βββ responseitem
β βββ userandmemo β βββ MessageItem.java
β βββ UserAndMemoRequestDto.java β βββ StatusItem.java
β βββ UserAndMemoResponseDto.java βββ service
βββ jwt β βββ AuthService.java
β βββ JwtAccessDeniedHandler.java β βββ FriendshipService.java
β βββ JwtAuthenticationEntryPoint.java β βββ MemoService.java
β βββ JwtFilter.java β βββ UserMemoService.java
β βββ TokenProvider.java β βββ UserService.java
βββ response β βββ impl
β βββ GlobalExceptionHandler.java β βββ AuthServiceImpl.java
β βββ ResponseCode.java β βββ FriendshipServiceImpl.java
β βββ ResponseData.java β βββ MemoServiceImpl.java
β βββ exception β βββ UserMemoServiceImpl.java
β β βββ FriendshipBadRequestException.java β βββ UserServiceImpl.java
β β βββ FriendshipDuplicateException.java βββ util
β β βββ LoginIdDuplicateException.java βββ SecurityUtil.java
β β βββ MemoSortBadRequestException.java βββ TimeConverter.java
β β βββ NoSuchFriendshipException.java
β β βββ NoSuchMemoException.java
β β βββ NoSuchUserException.java
β β βββ UserAndMemoDuplicateException.java
β βββ responseitem
β βββ MessageItem.java
β βββ StatusItem.java
βββ service
β βββ FriendshipService.java
β βββ MemoService.java
β βββ UserAndMemoService.java
β βββ UserService.java
β βββ auth
β β βββ AuthService.java
β β βββ CustomUserDetailsService.java
β βββ logic
β βββ FriendshipServiceLogic.java
β βββ MemoServiceLogic.java
β βββ UserAndMemoServiceLogic.java
β βββ UserServiceLogic.java
βββ util
βββ SecurityUtil.java