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 λΉμ©)μ΄ λ°μ
π κ°λ
-
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λ₯Ό νμ©νμ¬ μΊμ±νλ©΄ μ‘°ν μλλ₯Ό κ·Ήλνν μ μμ