๐Ÿ“š TIL 41์ผ์ฐจ

temprmnยท2023๋…„ 7์›” 28์ผ
0
post-thumbnail

์›๋ณธ ํ’€๋ฆฌํ€˜์ŠคํŠธ: [Feature][Refactor] ์ž‘์„ฑ๋œ ๊ฒŒ์‹œ๊ธ€(post) Redis๋กœ ์บ์‹ฑํ•˜๊ธฐ

์ด์ „์—” ์—„์ฒญ๋‚œ ์‚ฝ์งˆ์„ ํ–ˆ์—ˆ๋Š”๋ฐ... ๊ฐ€๊นŒ์šด ๊ณณ์„ ๋ชป๋ณด๊ณ  cache-session ์„œ๋ฒ„๋ฅผ ๋ถ„๋ฆฌํ•  ์ƒ๊ฐ๋ถ€ํ„ฐ ํ–ˆ์—ˆ๋‹ค. (๊ฒฐ๋ก ์ ์œผ๋ก  ๋ถ„๋ฆฌํ•˜๋Š”๊ฒŒ ์žฅ๊ธฐ์ ์œผ๋กœ ๋งž๊ธด ํ•˜์ง€๋งŒ, ์ง€๊ธˆ์€ ํ”„๋กœ์ ํŠธ ๊ทœ๋ชจ๊ฐ€ ์ž‘๊ธฐ ๋•Œ๋ฌธ์— ํฐ ํ•„์š”๋Š” ์—†๋‹ค.) ์—ฌ๊ธฐ์„œ๋„ ์‹œ๊ฐ„์„ ๋งŽ์ด ์žก์•„ ๋จน๊ณ ...

๋ ˆ๋””์Šค๊ฐ€ ๋ญ”์ง€ ์ „ํ˜€!! ๊ฐ์ด ์˜ค์ง€ ์•Š์•„์„œ, ์ž๋ฃŒ์™€ ์˜ˆ์ œ๋งŒ 3์ผ์ •๋„ ์ฐพ์•„๋‹ค๋‹Œ ๊ฒƒ ๊ฐ™๋‹ค... ํž˜๋“ค์–ด... (ใ… ใ… )
ํ•˜์ง€๋งŒ ํ•˜๊ณ  ๋‚˜๋‹ˆ๊นŒ ๊ป์งˆ์„ ํ•˜๋‚˜ ๊นฌ ๋Š๋‚Œ... ๋ฟŒ๋“ฏํ–ˆ๋‹ค.

์ž‘์„ฑ๋œ ๊ฒŒ์‹œ๊ธ€(post) Redis๋กœ ์บ์‹ฑํ•˜๊ธฐ

  • ์ด์ „ ๋ชฉํ‘œ: ์ž‘์„ฑํ•œ ๊ธ€์„ ์บ์‹ฑํ•ด์•ผ๊ฒ ๋‹ค! (๊ธ€์ด ์ž‘์„ฑ๋  ๋•Œ๋งˆ๋‹ค)
  • ํ˜„์žฌ ๋ชฉํ‘œ: ์กฐํšŒํ•œ ๊ธ€์„ ์บ์‹ฑํ•ด์•ผ๊ฒ ๋‹ค!

1. Serialize๋ฅผ ์œ„ํ•œ ์˜์กด์„ฑ ์ถ”๊ฐ€

์•„๋ž˜ ํŠธ๋Ÿฌ๋ธ” ์ŠˆํŒ… 1๋ฒˆ ์ฐธ๊ณ 

build.gradle

dependency {
    // serialize
    implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.13.3'
}

2. RedisCacheConfig๋กœ ์กฐํšŒํ•œ ๊ธ€ ์บ์‹ฑํ•˜๊ธฐ

  1. RedisCacheConfig๋ฅผ ํ†ตํ•ด @Cacheable ์–ด๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ–ˆ์„ ๋•Œ, Spring์ด ์•„๋‹Œ Redis๊ฐ€ ์บ์‹ฑํ•  ์ˆ˜ ์žˆ๋„๋ก ์„ค์ •ํ•œ๋‹ค.
@Configuration
@EnableCaching
public class RedisCacheConfig {
    @Bean
    public RedisCacheManager redisCacheManager(RedisConnectionFactory cacheConnectionFactory) {
        GenericJackson2JsonRedisSerializer redisSerializer = getGenericJackson2JsonRedisSerializer(); // ๋ฐ”๋กœ ์•„๋ž˜ ๋ฌธ๋‹จ์—
        
        RedisCacheConfiguration configuration = RedisCacheConfiguration.defaultCacheConfig()
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
                    .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()))
                .entryTtl(Duration.ofMinutes(10)); // ์บ์‹œ ์ง€์† ์‹œ๊ฐ„

        return RedisCacheManager
                .RedisCacheManagerBuilder
                .fromConnectionFactory(cacheConnectionFactory)
                .cacheDefaults(configuration)
                .build();
    }
}
private GenericJackson2JsonRedisSerializer getGenericJackson2JsonRedisSerializer() {
    PolymorphicTypeValidator typeValidator = BasicPolymorphicTypeValidator
            .builder()
            .allowIfSubType(Object.class)
            .build();

    // ObjectMapper ๋ฅผ ์‚ฌ์šฉํ•ด์„œ LocalTimeDate๋ฅผ ์ง๋ ฌํ™”๋ฅผ ํ•˜๊ธฐ ์œ„ํ•ด ๋งคํ•‘
    ObjectMapper objectMapper = new ObjectMapper();
    objectMapper.registerModule(new JavaTimeModule());
    objectMapper.activateDefaultTyping(typeValidator, ObjectMapper.DefaultTyping.NON_FINAL);
    GenericJackson2JsonRedisSerializer redisSerializer = new GenericJackson2JsonRedisSerializer(objectMapper);

    return redisSerializer;
}
  1. SpringApplication์—๋„ @EnableCaching ์–ด๋…ธํ…Œ์ด์…˜์„ ์ถ”๊ฐ€ํ•ด์ฃผ์–ด์•ผ ํ•œ๋‹ค.
@EnableCaching // โ† โ˜…
public class KP3COutsourcingProjectApplication { ... }
  1. Service ํด๋ž˜์Šค ์ค‘, ์บ์‹ฑํ•  ๋ฉ”์„œ๋“œ์— @Cacheable ์–ด๋…ธํ…Œ์ด์…˜์„ ์ถ”๊ฐ€ํ•˜๊ณ , ๊ฐ’์„ ์„ค์ •ํ•ด์ค€๋‹ค.
  • value = ์ €์žฅํ•  value ์ด๋ฆ„
  • key = ์ €์žฅํ•  key ์ด๋ฆ„, #+{๋ณ€์ˆ˜๋ช…}์„ ์กฐํ•ฉํ•ด์„œ key ๊ฐ’์„ ๋ณ€์ˆ˜๋ช…์œผ๋กœ ์„ค์ •ํ•ด์ค„ ์ˆ˜ ์žˆ๋‹ค.
  • cacheManager = RedisCacheConfig์—์„œ ์ƒ์„ฑํ•œ ์บ์‹œ ๋งค๋‹ˆ์ € ๋ฉ”์„œ๋“œ
/* PostService.java */
    
// ๊ฒŒ์‹œ๊ธ€ 1๊ฐœ๋งŒ
@Cacheable(value = "post", key = "#postId", cacheManager = "redisCacheManager")
public PostResponseDto getPost(Long postId) {
    Post post = postRepository.findById(postId).orElseThrow(() ->
                new NullPointerException("์กด์žฌํ•˜์ง€ ์•Š๋Š” ๊ฒŒ์‹œ๊ธ€ ์ž…๋‹ˆ๋‹ค.")
    );
    return new PostResponseDto(post);
}
  • ์ด๋ ‡๊ฒŒํ•˜๋ฉด ๋ฐ˜ํ™˜ํ˜•์ธ PostResponseDto ํƒ€์ž…์œผ๋กœ ๋ฐ์ดํ„ฐ๊ฐ€ ๋‹ด๊ฒจ์ ธ Redis ์บ์‹œ ๋ฉ”๋ชจ๋ฆฌ์— ์ €์žฅ๋œ๋‹ค.

ํฌ์ŠคํŠธ๋งจ ํ…Œ์ŠคํŠธ

  1. ์ตœ์ดˆ ์‹คํ–‰: 135ms
    ์ตœ์ดˆ_135ms

  2. 2๋ฒˆ์งธ ์‹คํ–‰: 22ms
    ์ฐจํšŒ_22ms

  3. 3๋ฒˆ์งธ ์‹คํ–‰: 11ms
    3ํšŒ_11ms

Redis ๋ฉ”๋ชจ๋ฆฌ์— ์ €์žฅ๋œ ๋ชจ์Šต

image

ํŠธ๋Ÿฌ๋ธ” ์ŠˆํŒ…

1. LocalDateTime ํƒ€์ž… ์ง๋ ฌํ™” ๋ฌธ์ œ

Java 8 date/time type java.time.LocalDateTime not supported by default: add Module "com.fasterxml.jackson.datatype:jackson-datatype-jsr310" to enable handling...

  • ์œ„์™€ ๊ฐ™์ด LocalDateTime ํƒ€์ž…์„ ์ง€์›ํ•˜์ง€ ์•Š๋Š” ๋“ฏํ•œ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ๋‹ค. Jackson์€ ํ•ด๋‹น ํƒ€์ž…์˜ ์ง๋ ฌํ™”๋ฅผ ์ง€์›ํ•˜์ง€ ์•Š์œผ๋ฉฐ, ์ฒ˜๋ฆฌํ•˜๊ธฐ ์œ„ํ•ด com.fasterxml.jackson.datatype:jackson-datatype-jsr310 ์˜์กด์„ฑ์„ ์ถ”๊ฐ€ํ•˜๋ผ๋Š” ๋œป์ด๋‹ค.

1-2. ํ•ด๊ฒฐ

  • ์—๋Ÿฌ ๋ฉ”์„ธ์ง€์˜ ๊ถŒ์œ ๋Œ€๋กœ ์˜์กด์„ฑ์„ ์ถ”๊ฐ€.

build.gradle

dependency {
    // serialize
    implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.13.3'
}
  • ์ง๋ ฌํ™” ๋ฐ ์—ญ์ง๋ ฌํ™”, ํฌ๋งท ์„ค์ • ์–ด๋…ธํ…Œ์ด์…˜ ์ถ”๊ฐ€
/* TimeStamped.java */

@CreatedDate
@Column(updatable = false)
@Temporal(TemporalType.TIMESTAMP)
@JsonSerialize(using = LocalDateTimeSerializer.class) // ๏ผŠ ์ง๋ ฌํ™”
@JsonDeserialize(using = LocalDateTimeDeserializer.class) // ๏ผŠ ์—ญ์ง๋ ฌํ™”
@JsonFormat(shape= JsonFormat.Shape.STRING, pattern="yyyy-MM-dd HH:mm") // ๏ผŠ ์ €์žฅ ํฌ๋งท ์„ค์ •
private LocalDateTime createdAt;

์šฐ๋ ค๋˜๋Š” ์ ...: ์–ด๋…ธํ…Œ์ด์…˜์ด ๋„ˆ๋ฌด ๋งŽ์ด ๋‹ฌ๋ ค์„œ ๊ฐ€๋…์„ฑ์— ์ข‹์ง€ ์•Š๊ฑฐ๋‚˜ ์„ฑ๋Šฅ์ƒ์˜ ๋ฌธ์ œ๊ฐ€ ์ƒ๊ธฐ์ง„ ์•Š์„์ง€ ๊ฑฑ์ •์ด ๋˜๊ธด ํ•œ๋‹ค.

2. imageUrlList ํ˜ธ์ถœ ๋ฌธ์ œ

could not write json: failed to lazily initialize a collection: could not initialize proxy - no session

  • ์œ„์™€ ๊ฐ™์€ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด์„œ imageUrlList ํ•„๋“œ๋ถ€ํ„ฐ๋Š” ์ œ๋Œ€๋กœ ๋ฐ์ดํ„ฐ๊ฐ€ ์บ์‹ฑ๋˜์ง€ ์•Š์€ ๊ฒƒ ๊ฐ™์€ ๋ชจ์Šต์„ ๋ณด์˜€๋‹ค.
    getHomeFeed(user3)_์บ์‹ฑํƒ€์ž„๋ผ์ธํ˜ธ์ถœ_์˜ค๋ฅ˜

2-2. ํ•ด๊ฒฐ

  • ์ด๋Š” Redis์— ์ €์žฅ๋˜๋Š” PostResponseDto ๋‚ด๋ถ€, imageUrlList ํ•„๋“œ๊ฐ€ ์—”ํ‹ฐํ‹ฐ์˜ ์ •๋ณด๋ฅผ List ํ˜•ํƒœ๋กœ ๋‹ด๊ณ  ์žˆ์—ˆ๊ธฐ ๋•Œ๋ฌธ์ด์—ˆ๋‹ค. Redis์—๋Š” ์—”ํ‹ฐํ‹ฐ๋ฅผ ๊ทธ๋Œ€๋กœ ์ €์žฅํ•˜๋Š” ๊ฒƒ์ด ๋ถˆ๊ฐ€๋Šฅํ•˜๊ณ , ๊ฐ€๋Šฅํ•˜๋”๋ผ๋„ ๊ถŒ์žฅ์‚ฌํ•ญ์ด ์•„๋‹ˆ๊ธฐ ๋•Œ๋ฌธ์—, ์ €์žฅํ•˜๋Š” ๊ฒƒ์„ ์ง€์–‘ํ•ด์•ผํ•œ๋‹ค.

์ˆ˜์ • ์ „ PostResponseDto

@NoArgsConstructor
@AllArgsConstructor
public class PostResponseDto extends ApiResponseDto {
    private Long id;
    private String username;
    private String nickname;
    private String content;
    private LocalDateTime createdAt;
    private List<Post_Image> imageUrlList; // Post_Image ์—”ํ‹ฐํ‹ฐ ํด๋ž˜์Šค๋ฅผ List ํ˜•ํƒœ๋กœ ๋‹ด๊ณ  ์žˆ๋‹ค
    private List<PostResponseDto> children;

    /* ์ƒ์„ฑ์ž ์ž ์‹œ ์ƒ๋žต */
}

์ˆ˜์ • ํ›„ PostResponseDto

@NoArgsConstructor
@AllArgsConstructor
public class PostResponseDto extends ApiResponseDto {
    private Long id;
    private String username;
    private String nickname;
    private String content;
    private LocalDateTime createdAt;
    private List<PostImageDto> imageUrlList; // Post_Image ์—”ํ‹ฐํ‹ฐ ํด๋ž˜์Šค์˜ ์ •๋ณด๋ฅผ ๋‹ด์€ Dto ํด๋ž˜์Šค๋ฅผ ๋งŒ๋“ค์—ˆ๋‹ค
    private List<PostResponseDto> children;

    /* ์ƒ์„ฑ์ž ์ž ์‹œ ์ƒ๋žต */
}
  • PostImageDto๋ฅผ ์ƒ์„ฑํ•˜์—ฌ Post_Image ์—”ํ‹ฐํ‹ฐ ํด๋ž˜์Šค์˜ ์ •๋ณด๋ฅผ ๋‹ด์•˜๋‹ค.
@NoArgsConstructor
@AllArgsConstructor
public class PostImageDto {
    Long id;
    String imgUrl;

    public PostImageDto (Post_Image postImage) {
        this.id = postImage.getId();
        this.imgUrl = postImage.getImgUrl();
    }
}

Post_Image ๊ฐ’์„ PostImageDto๋กœ ์ €์žฅํ•  ๋•Œ๋Š” ์ด๋ ‡๊ฒŒ...

/* PostResponseDto.java */

public PostResponseDto(Post post) {
    // ๋‹ค๋ฅธ ํ•„๋“œ ์ƒ๋žต
    this.imageUrlList = post.getImagetList().stream().map(PostImageDto::new).toList();
}
profile
`ISFJ` T 49% F 51% /

0๊ฐœ์˜ ๋Œ“๊ธ€