jpa 방식으로 saveAll 했다가 강제종료했다.
알고 보니 bulk insert로 더 괜찮게 할 수 있다는 것을 발견했다.
처음에는 과제의 의미를 제대로 이해하지 못해 이것저것 시도해보다가
init()으로 초기 데이터를 셋팅해주었다.
셋팅 이후에는 주석처리 해놓았다.
데이터 셋팅
@Component
@RequiredArgsConstructor
public class InitData {
JdbcTemplate jdbcTemplate;
@PostConstruct
@Transactional
public void init() {
final int BATCH_SIZE = 10_000;
List<User> userList = new ArrayList<>();
String sql = "INSERT INTO users (email, password, username, user_role) " +
"VALUES (?, ?, ?, ?)";
Faker faker = new Faker();
for(int i = 0; i<1_000_000;i++){
String fullName = faker.name().firstName();
String email = fullName.toLowerCase()+ i + "@gmail.com";
String password = "1234";
UserRole userRole = UserRole.valueOf("USER");
userList.add(new User(email,password,fullName,userRole));
}
jdbcTemplate.batchUpdate(sql,
userList,
BATCH_SIZE,
(PreparedStatement ps, User user) -> {
ps.setString(1, user.getEmail());
ps.setString(2, user.getPassword());
ps.setString(3,user.getUsername());
ps.setString(4, user.getUserRole().toString());
});
}
}
localhost:8080/users/all?username=william 으로 get 요청
보통 350 - 400ms 정도 나오는 것 같다
혹시나해서 페이지 사이즈를 키워봣는데(30으로, 디폴트값은 10) 엇비슷하게 나왔다.
public Page<UserResponse> getAllUser(int page, int size, String username) {
Pageable pageable = PageRequest.of(page - 1, size);
Page<UserResponse> users = userRepository.findAllByUsername(pageable,username);
return users;
}
@Query("SELECT new org.example.expert.domain.user.dto.response.UserResponse(u.id, u.email, u.username)"
+ " FROM User u WHERE (:username IS NULL OR u.username = :username) ORDER BY u.id ASC")
Page<UserResponse> findAllByUsername(Pageable pageable, String username);
처음에 짰던 코드
@Query("SELECT new org.example.expert.domain.user.dto.response.UserResponse(u.id, u.email, u.username)"
+ " FROM User u WHERE (:username IS NULL OR u.username = :username)"
+ " ORDER BY u.id ASC")
Page<UserResponse> findAllByUsername(Pageable pageable, String username);
편차가 있긴한데 350ms - 400ms 정도 나옴
두번째 짠 코드(no-offset만 적용)
@Query("SELECT new org.example.expert.domain.user.dto.response.UserResponse(u.id, u.email, u.username)"
+ " FROM User u"
+ " WHERE (:username IS NULL OR u.username = :username)"
+ " AND (:lastUserId IS NULL OR u.id < :lastUserId)"
+ " ORDER BY u.id ASC"
+ " LIMIT :size")
List<UserResponse> findAllByUsername(Long lastUserId, int size, String username);
사이즈 기본 5개 > 13ms
사이즈 300개 > 340ms
사이즈 700개 > 482ms
세번째 짠 코드(인덱싱 적용)
@Table(name = "users",
indexes = @Index(name = "idx_user_username"
, columnList = "username"))
이 이상 빨리나올 수는 없겠는데…
네번째 시도(인덱싱+nooffset)
아니 원래 이정도로 드라마틱하게 차이나나????
배수가 내가 들었던거랑 다른데 ㅠㅠ?….
닉네임 같은 사람의 표본이 너무 작다고 생각이 들었고 데이터베이스에서
세팅 되어있는 값 중에 가장 겹치는 닉네임이 뭔지 검색했다.
select username, count(username) from users group by username order by count(username)
로 데이터베이스에 쿼리를 날렸고 Scottie라는 이름이 619명 있다는 것을 확인했고 해당 이름으로 조회 테스트를 진행했다.
1. 인덱싱 없이
localhost:8080/users/all?username=Scottie
처음 조회 448ms - 두번째 조회 332ms -세번째 조회 350ms
2. 인덱싱 추가
localhost:8080/users/all?username=Scottie
처음 조회 - 32ms 두번째 조회6ms -세번째 조회 5ms