[see-realview] 1차 배포 문제점

Seokjun Moon·2024년 1월 16일
0

배포 후기

정말 많은 기대를 하고 배포를 했고, 처음엔 에러가 없는 줄 알았으나.... 역시 유저 플로우는 아무도 모른다고. 정말 많은 오류들이 발생했고, 또 수정할 내용들이 정말 많다.... 로그를 자세히 남기지 않아 정말 어려웠다. 로그가 얼마나 중요한 것이었는지 몸소 깨닫게 된 ... 1차 배포인 것 같다.

그럼 발생했던 문제를 살펴보자면 다음과 같다.

문제점

지나치게 많은 update 쿼리

redis에 저장된 캐싱 데이터를 db로 이관할 때, 지나치게 많은 update 쿼리가 전송된다. 아래와 같은 update쿼리가 수십개씩 발생하게 된다.

삽입할 때, bulk insert를 구현했던 코드는 아래와 같은데,

public void saveAll(List<ParsedImage> images) {
    String sql = String.format("""
            INSERT INTO `%s` (link, advertisement, count)
            VALUES (:link, :advertisement, :count)
            ON DUPLICATE KEY UPDATE link = :link, advertisement = :advertisement, count = count + :count
            """, PARSED_IMAGE_TABLE);

    SqlParameterSource[] parameterSources = images
            .stream()
            .map(BeanPropertySqlParameterSource::new)
            .toArray(SqlParameterSource[]::new);

    namedParameterJdbcTemplate.batchUpdate(sql, parameterSources);
}

테이블에서는 link에 unique 제약조건이 있기 때문에 중복 키 발생 시 업데이트하도록 구현했었다. 하지만 이게 문제인 것 같다. 중복이 발생하여 update 쿼리를 날려야 하는 경우에는 개별로 생성 후 전송하기 때문에 이런 문제가 발생한 것 같다.

그래서 redis에 저장할 때, DB에서 가져온 데이터인지 아니면 새로 생성된 데이터인지 구분하기 위한 구분자를 key에 추가하거나 value에 추가해야할 것 같다.

ObjectMapper 문제

null 값이 전달되는 경우가 있었다. 왜..지... 분명 null일 수는 없는데....아무튼 null값이 전달되는 버그가 생겼다.

여기서 또 로그의 중요성. redis에서 읽어온 데이터가 null이라는 건데, 어떤 키에 대해서 어떤 값인지를 모른다.... 에러가 발생하면 꼭 로그를 남겨야한다!! 에러처리를 한 모든 곳에서는 에러가 발생한 환경에 대한 로그를 다 남겨야겠다.

로그는 배포를 하는데 없을 수 없는 요소인 것 같다. 개발할 때는 디버깅이 있어서 많은 로그를 남길 필요를 못느꼈는데, 배포를 하고난 후 발생하는 에러들을 잡기 위해서는 정말 자세한 로그가 필요하다는 것을 몸소 ... 느낄 수 있었다.

인증서 문제

이미지 분석을 위해서는 다운 후 base64 인코딩을 해야 하는데, 이때 ssl인증서가 없어서 다운하지 못하는 일부 사진이 존재했다. 요청을 보내면 403에러가 발생해서 다운을 할 수 없었던 것. 이를 위해 이미지를 다운하는 WebClient에 대해서 옵션을 추가했다.

@Bean(name = "imageWebClient")
public WebClient imageWebClient() {
    try {
        SslContext context = SslContextBuilder.forClient().trustManager(InsecureTrustManagerFactory.INSTANCE).build();
        HttpClient httpClient = HttpClient.create().secure(provider -> provider.sslContext(context));

        return WebClient.builder()
                .defaultHeader("User-Agent", USER_AGENT)
                .clientConnector(new ReactorClientHttpConnector(httpClient))
                .exchangeStrategies(ExchangeStrategies.builder()
                        .codecs(clientCodecConfigurer -> clientCodecConfigurer.defaultCodecs().maxInMemorySize(100 * 1024 * 1024))
                        .build())
                .build();
    } catch (SSLException exception) {
        throw new ServerException(ExceptionStatus.IMAGE_PARSING_ERROR);
    }
}
profile
차근차근 천천히

0개의 댓글