Home > Backend Development > πŸ“š[Backend Development] νŽ˜μ΄μ§• 방식.

πŸ“š[Backend Development] νŽ˜μ΄μ§• 방식.
Backend Ddevelopment

β€œπŸ“š[Backend Development] νŽ˜μ΄μ§• 방식.”

πŸ“ Intro.

  • λŒ€κ·œλͺ¨ λ°μ΄ν„°μ—μ„œ κ²Œμ‹œκΈ€ λͺ©λ‘μ„ μ‘°νšŒν•  λ•Œ 효율적인 νŽ˜μ΄μ§•(Pagination) μ²˜λ¦¬λŠ” μ„±λŠ₯ μ΅œμ ν™”μ˜ ν•΅μ‹¬μž…λ‹ˆλ‹€.
  • 데이터가 λ§Žμ•„μ§ˆμˆ˜λ‘ OFFSET λ°©μ‹μ˜ μ„±λŠ₯ μ €ν•˜κ°€ λ°œμƒν•˜λ―€λ‘œ, Keyset Pagination(Seek 방식) λ“±μ˜ μ΅œμ ν™” 기법을 μ μš©ν•˜λŠ” 것이 μ€‘μš”ν•©λ‹ˆλ‹€.

βœ…1️⃣ νŽ˜μ΄μ§• 방식 비ꡐ.

  • λŒ€κ·œλͺ¨ 데이터λ₯Ό μ‘°νšŒν•  λ•Œ μ‚¬μš©ν•  수 μžˆλŠ” λŒ€ν‘œμ μΈ νŽ˜μ΄μ§• 방법은 λ‹€μŒκ³Ό κ°™μŠ΅λ‹ˆλ‹€.
방식 μž₯점 단점 적용 예제
OFFSET + LIMIT κ°„λ‹¨ν•œ κ΅¬ν˜„, SQL ν‘œμ€€ 지원 OFFSET이 컀질수둝 μ„±λŠ₯ μ €ν•˜ λΈ”λ‘œκ·Έ, κ²Œμ‹œνŒ
Keyset Pagination (Seek 방식) μ„±λŠ₯ μ΅œμ ν™”, 인덱슀 효율적 μ‚¬μš© νŠΉμ • 컬럼(μ •λ ¬ κΈ°μ€€)이 ν•„μš” λ‰΄μŠ€ ν”Όλ“œ, νƒ€μž„λΌμΈ
Cursor 기반 νŽ˜μ΄μ§• μœ μ € λ§žμΆ€ν˜• 데이터 μ΅œμ ν™” κ΅¬ν˜„μ΄ 볡작 페이슀뢁, μΈμŠ€νƒ€κ·Έλž¨
Redis 캐싱 ν™œμš© 쑰회 속도 ν–₯상 데이터 동기화 문제 λ°œμƒ κ°€λŠ₯ 인기 κ²Œμ‹œκΈ€ λͺ©λ‘

βœ…2️⃣ 기본적인 OFFSET + LIMIT 방식

πŸ“Œ κ°œλ…

  • OFFSET을 μ‚¬μš©ν•˜μ—¬ νŠΉμ • μœ„μΉ˜λΆ€ν„° LIMIT 개수만큼 데이터λ₯Ό μ‘°νšŒν•˜λŠ” 방식
  • 일반적인 κ²Œμ‹œνŒμ—μ„œ 많이 μ‚¬μš©ν•˜μ§€λ§Œ, λŒ€κ·œλͺ¨ λ°μ΄ν„°μ—μ„œλŠ” OFFSET 값이 컀질수둝 μ„±λŠ₯이 μ €ν•˜λ¨

πŸ“ SQL 예제

SELECT * FROM posts ORDER BY created_at DESC LIMIT 10 OFFSET 10000;
  • OFFSET 10000은 μ•žμ˜ 10,000개 행을 μŠ€μΊ”ν•œ ν›„ 버리고, κ·Έ λ‹€μŒ 10개만 κ°€μ Έμ˜΄.
  • 데이터가 λ§Žμ•„μ§ˆμˆ˜λ‘ μ„±λŠ₯이 κΈ‰κ²©νžˆ μ €ν•˜λ¨ β†’ β€œνŽ˜μ΄μ§•μ΄ κΉŠμ–΄μ§ˆμˆ˜λ‘ λŠλ €μ§€λŠ” 문제 λ°œμƒβ€

πŸ“ Java (Spring Data JPA) 예제

Pageable pageable = PageRequest.of(page, 10, Sort.by(Sort.Direction.DESC, "createdAt"));
Page<Post> posts - postRepository.findAll(pageable);

❌ 문제점

  • OFFSET이 크면 λΆˆν•„μš”ν•œ 데이터 κ²€μƒ‰μœΌλ‘œ 인해 μ„±λŠ₯ μ €ν•˜ λ°œμƒ
  • μΈλ±μŠ€κ°€ μžˆμ–΄λ„ 데이터λ₯Ό 읽고 λ²„λ¦¬λŠ” λΉ„μš©(I/O λΉ„μš©)이 λ°œμƒ

βœ…3️⃣ Keyset Pagination (Seek 방식)

πŸ“Œ κ°œλ…

  • OFFSET을 μ‚¬μš©ν•˜μ§€ μ•Šκ³ , λ§ˆμ§€λ§‰ μ‘°νšŒν•œ κ²Œμ‹œκΈ€μ˜ ID(λ˜λŠ” created_at)을 κΈ°μ€€μœΌλ‘œ λ‹€μŒ 데이터λ₯Ό κ°€μ Έμ˜€λŠ” 방식
  • β€œλ§ˆμ§€λ§‰ 쑰회된 λ°μ΄ν„°μ˜ ν‚€λ₯Ό κΈ°μ–΅ν•˜κ³ , κ·Έ 이후 데이터λ₯Ό μ‘°νšŒβ€

πŸ“ SQL 예제

SELECT * FROM posts
WHERE created_at < '2024-03-11 12:00:00'
ORDER BY created_at DESC
LIMIT 10;
  • created_at을 κΈ°μ€€μœΌλ‘œ 이전 νŽ˜μ΄μ§€μ˜ λ§ˆμ§€λ§‰ κ²Œμ‹œκΈ€ μ‹œκ°„λ³΄λ‹€ μž‘μ€ λ°μ΄ν„°λ§Œ 쑰회
  • 데이터 양이 λ§Žμ•„λ„ OFFSET이 μ—†μœΌλ―€λ‘œ λΉ λ₯΄κ²Œ 쑰회 κ°€λŠ₯

πŸ“ Java (Spring Data JPA) 예제

public List<Post> getNextPage(LocalDateTime lastCreatedAt, int limit) {
    return postRepository.findByCreatedAtBeforeOrderByCreatedAtDesc(lastCreatedAt, PageRequest.of(0, limit));
}
  • lastCreatedAt을 κΈ°μ€€μœΌλ‘œ λ‹€μŒ κ²Œμ‹œκΈ€μ„ 쑰회
  • LIMIT을 μ μš©ν•˜μ—¬ νŽ˜μ΄μ§• 처리

βœ… μž₯점.

  • βœ… OFFSET이 μ—†μœΌλ―€λ‘œ 속도가 빠름
  • βœ… 인덱슀λ₯Ό ν™œμš©ν•˜μ—¬ λΉ λ₯Έ 검색 κ°€λŠ₯
  • βœ… 데이터가 λ§Žμ•„λ„ μ„±λŠ₯이 μΌμ •ν•˜κ²Œ μœ μ§€λ¨

❌ 단점.

  • ❌ created_at λ˜λŠ” idκ°€ λ°˜λ“œμ‹œ μžˆμ–΄μ•Ό 함
  • ❌ ORDER BYλ₯Ό μœ„ν•œ μ μ ˆν•œ 인덱슀 μ„€μ • ν•„μš”

βœ…4️⃣ Cursor 기반 νŽ˜μ΄μ§•.

πŸ“Œ κ°œλ…

  • 페이슀뢁, μΈμŠ€νƒ€κ·Έλž¨ 같은 SNSμ—μ„œ μ‚¬μš©ν•˜λŠ” 방식
  • 이전 νŽ˜μ΄μ§€μ˜ λ§ˆμ§€λ§‰ ID(λ˜λŠ” created_at)λ₯Ό ν΄λΌμ΄μ–ΈνŠΈμ—μ„œ μ €μž₯ν•˜κ³ , 이λ₯Ό μ΄μš©ν•΄ λ‹€μŒ 데이터λ₯Ό 쑰회
  • Keyset Paginationκ³Ό μœ μ‚¬ν•˜μ§€λ§Œ, APIμ—μ„œ cursor 값을 λ°˜ν™˜ν•˜κ³  이λ₯Ό μ‚¬μš©

πŸ“ SQL 예제.

SELECT * FROM posts
WHERE id > 1050
ORDER BY id ASC
LIMIT 10;
  • idκ°€ 1050보닀 큰 κ²Œμ‹œκΈ€μ„ κ°€μ Έμ˜΄
  • ORDER BY id ASCλ₯Ό μ‚¬μš©ν•˜μ—¬ μ˜€λ¦„μ°¨μˆœ μ •λ ¬

πŸ“ Java (Spring Data JPA) 예제

public List<Post> getNextPosts(Long lastPostId, int limit) {
    return postRepository.findByIdGreaterThanOrderByIdAsc(lastPostId, PageRequest.of(0, limit));
}
  • ν΄λΌμ΄μ–ΈνŠΈμ—μ„œ 이전 νŽ˜μ΄μ§€μ˜ λ§ˆμ§€λ§‰ id 값을 μ €μž₯ν•˜κ³  이λ₯Ό μ΄μš©ν•˜μ—¬ λ‹€μŒ 데이터λ₯Ό 쑰회

βœ… μž₯점

  • βœ… Keyset Paginationκ³Ό λ§ˆμ°¬κ°€μ§€λ‘œ OFFSET 없이 μ„±λŠ₯ μ΅œμ ν™”
  • βœ… 페이슀뢁, μΈμŠ€νƒ€κ·Έλž¨ 같은 λ¬΄ν•œ 슀크둀(Scroll) UI에 적합
  • βœ… API μ‘λ‹΅μ—μ„œ cursor(λ§ˆμ§€λ§‰ id) 값을 ν¬ν•¨ν•˜μ—¬ λ‹€μŒ μš”μ²­μ— μ‚¬μš© κ°€λŠ₯

❌ 단점

  • ❌ ν΄λΌμ΄μ–ΈνŠΈμ—μ„œ cursor(λ§ˆμ§€λ§‰ id) 값을 μ €μž₯ν•΄μ•Ό 함
  • ❌ νŠΉμ • ν•„λ“œ(id, created_at) κΈ°μ€€μœΌλ‘œ μ •λ ¬ν•΄μ•Ό ν•˜λ―€λ‘œ λ³΅μž‘ν•œ 정렬이 어렀움.

βœ…5️⃣ Redisλ₯Ό ν™œμš©ν•œ 캐싱 νŽ˜μ΄μ§•

πŸ“Œ κ°œλ…

  • 인기 κ²Œμ‹œκΈ€, μ‘°νšŒμˆ˜κ°€ λ§Žμ€ λ°μ΄ν„°λŠ” DBμ—μ„œ 직접 μ‘°νšŒν•˜μ§€ μ•Šκ³  Redis에 μ €μž₯ν•˜μ—¬ λΉ λ₯΄κ²Œ 제곡
  • 일정 μ‹œκ°„λ§ˆλ‹€(예: 5λΆ„) 인기 κ²Œμ‹œκΈ€ λͺ©λ‘μ„ μ—…λ°μ΄νŠΈ
  • ZSET(Sorted Set)을 μ‚¬μš©ν•˜μ—¬ μ •λ ¬λœ 데이터λ₯Ό λΉ λ₯΄κ²Œ 쑰회

πŸ“ Java(Spring + Redis) 예제

@Service
public class PostCacheService {
    private static final String CACHE_KEY = "latest_posts";

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @Autowired
    private PostRepository postRepository;

    public List<Post> getLatestPosts() {
        List<Post> cachedPosts = (List<Post>) redisTemplate.opsForValue().get(CACHE_KEY);
        if (cachedPosts != null) {
            return cachedPosts;
        }

        // μΊμ‹œκ°€ μ—†μœΌλ©΄ DBμ—μ„œ 쑰회 ν›„ Redis에 μ €μž₯
        List<Post> posts = postRepository.findTop10ByOrderByCreatedAtDesc();
        redisTemplate.opsForValue().set(CACHE_KEY, posts, Duration.ofSeconds(60)); // 60초 캐싱
        return posts;
    }
}

βœ… μž₯점

  • βœ… 인기 κ²Œμ‹œκΈ€μ„ λΉ λ₯΄κ²Œ 쑰회 κ°€λŠ₯
  • βœ… DB λΆ€ν•˜λ₯Ό 쀄이고, 응닡 속도 ν–₯상

❌ 단점

  • ❌ μ‹€μ‹œκ°„ μ΅œμ‹  데이터가 ν•„μš”ν•  경우 μΊμ‹œ 동기화 λ¬Έμ œκ°€ λ°œμƒ

βœ…6️⃣ 졜적의 νŽ˜μ΄μ§• 방식 선택

νŽ˜μ΄μ§• 방식 μ„±λŠ₯ μž₯점 단점 μ‚¬μš© 사둀
OFFSET + LIMIT ❌ 느림 κ°„λ‹¨ν•œ κ΅¬ν˜„ 데이터 λ§Žμ•„μ§€λ©΄ μ„±λŠ₯ μ €ν•˜ 기본적인 νŽ˜μ΄μ§€λ„€μ΄μ…˜
Keyset Pagination βœ… 빠름 인덱슀 μ΅œμ ν™”, μ„±λŠ₯ 일정 νŠΉμ • μ •λ ¬ ν•„λ“œ ν•„μš” λ‰΄μŠ€ ν”Όλ“œ, λΈ”λ‘œκ·Έ
Cursor 기반 βœ… 빠름 SNS, λ¬΄ν•œ 슀크둀 졜적 ν΄λΌμ΄μ–ΈνŠΈμ—μ„œ cursor μ €μž₯ ν•„μš” μΈμŠ€νƒ€κ·Έλž¨, 페이슀뢁
Redis 캐싱 πŸš€ 맀우 빠름 인기 κ²Œμ‹œκΈ€ λΉ λ₯Έ 쑰회 μ‹€μ‹œκ°„ 데이터 반영 어렀움 인기 κ²Œμ‹œκΈ€, λž­ν‚Ή

βœ…7️⃣ κ²°λ‘ 

  • βœ… λŒ€κ·œλͺ¨ λ°μ΄ν„°μ—μ„œ OFFSET 방식은 λΉ„νš¨μœ¨μ 
  • βœ… Keyset Pagination(Seek 방식)이 μ„±λŠ₯ μ΅œμ ν™”μ— 유리
  • βœ… Cursor 방식은 SNSλ‚˜ λ¬΄ν•œ μŠ€ν¬λ‘€μ— 적합
  • βœ… Redisλ₯Ό ν™œμš©ν•˜μ—¬ μΊμ‹±ν•˜λ©΄ 쑰회 속도λ₯Ό κ·ΉλŒ€ν™”ν•  수 있음