본문 바로가기
Back-end/Spring boot

코인 모의투자 프로젝트(5) - 퍼포먼스 향상 - 2

by 악어코딩 2024. 4. 28.

지난 포스팅에서 Insert를 JDBC를 이용하여 bulk Insert를 진행한 결과 2배의 성능 향상을 이끌어낼 수 있었다.

좀 더 쿼리를 튜닝해서 할 수 있는 최대한의 퍼포먼스를 내보려고 한다!

 

@Transactional
public void executeAsk(Trade trade) {
    List<Order> orders = orderRepository.findBidOrders(trade.getCode(), trade.getTradePrice());
    List<Execution> executions = new ArrayList<>();
    for (Order order : orders) {
        BigDecimal executeAmount = orderService.updateOrder(trade, order);
        Execution execution = createExecution(trade, order, executeAmount);
        executions.add(execution);
        User user = order.getUser();
        userService.updateUserCash(user, trade.getTradePrice().multiply(executeAmount));
        sseService.sendExecution(execution);
    }
    executionRepository.bulkInsert(executions);
    System.out.println("real : " + trade.getSequentialId());
}

 

처음으로 보이는 문제는 Update 쿼리이다.

 

JPA의 더티 체킹(dirty checking)으로 인해서 Transaction 종료 시 Entity의 값을 비교해서 Update쿼리를 날린다.

보면 date_time, code, gubun 등은 바뀌지 않는대도 set에 들어간 것을 확인할 수 있다.

이를 제거하기 위해서 @DynamicUpdate를 사용하도록 하겠다.

 

@Getter
@AllArgsConstructor(access = AccessLevel.PRIVATE)
@NoArgsConstructor
@Builder
@ToString
@DynamicUpdate
@Entity
public class Asset {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private Long userId;
    private String code;
    @Setter
    private BigDecimal averagePrice;
    @Setter
    private BigDecimal amount;
}

@Getter
@NoArgsConstructor
@AllArgsConstructor(access = AccessLevel.PRIVATE)
@Builder
@ToString
@DynamicUpdate
@Entity(name = "orders")
public class Order {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    @ManyToOne(optional = false)
    @JoinColumn(name = "user_id")
    private User user;
    private String code;
    @Enumerated(EnumType.STRING)
    private Gubun gubun;
    private BigDecimal price;
    @Setter
    private BigDecimal amount;
    private BigDecimal prePrice;
    private LocalDateTime dateTime;

    public OrderResponse toResponse() {
        OrderResponse orderResponse = new OrderResponse();
        BeanUtils.copyProperties(this, orderResponse);
        return orderResponse;
    }
}

@Getter
@NoArgsConstructor
@AllArgsConstructor(access = AccessLevel.PRIVATE)
@Builder
@DynamicUpdate
@Entity(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    @Setter
    private String nickname;
    @Setter
    private String role;
    @Setter
    private String profile;
    private Long providerId;
    @Setter
    private BigDecimal cash;

    public UserResponse toResponse() {
        UserResponse userResponse = new UserResponse();
        BeanUtils.copyProperties(this, userResponse);
        return userResponse;
    }

    public UserDto toDto() {
        UserDto userDto = new UserDto();
        BeanUtils.copyProperties(this, userDto);
        return userDto;
    }
}

 

사실 기존에 Double 자료형이었던 것들을 전부 BigDecimal로 바꿨다. 코인도 소수점 하나하나가 중요하기 때문에, 오차를 없애기 위해 설정했다. (바꾸느라 굉장히 힘들었다... 테스트코드도 전부 수정했다... 다행히도 ObjectMapper와 BeanUtils가 BigDecimal도 지원해줘서 이 부분을 수정할 필요가 없었다 ㅎㅎ)

 

아무튼 Entity에 @DynamicUpdate를 넣고 다시 실행시키면 이런 쿼리를 발견할 수 있다.

 

쿼리가 짧아졌으니, 처리할 수 있는 데이터 양도 늘었을 것이란 기대를 가지고 퍼포먼스 테스트를 다시 해봤지만.. 별로 나아진게 없었다.

 

Select 쿼리를 깎아서 DTO로 값을 받는 방법도 있겠지만 크게 성능이 나아질 것으로 보이지 않아서 후에 기회가 된다면 이어서 테스트를 진행해야겠다.