배치 IN 조회의 실행계획 (b) — 행을 곱하지 않는다 (카테시안 소멸)
출처: FeedBatchFetchIT.l5ExplainEntityPagingHasLimitAndBatchInHasNoRowMultiplication 콘솔 출력 (b)
조건: seed(100) 직후, default_batch_fetch_size=100 세션. warm buffer cache.
쿼리: EXPLAIN (ANALYZE, BUFFERS)
      SELECT h.* FROM highlights h
      WHERE h.feed_item_id IN (SELECT fi.id FROM feed_items fi
                               ORDER BY fi.first_highlighted_at DESC, fi.id ASC LIMIT 20)
      (배치 페치가 페이지 부모 20개의 highlights 를 IN 한 방으로 채우는 것과 같은 shape)

Hash Semi Join  (cost=72.46..317.64 rows=234 width=710) (actual time=0.295..0.541 rows=1509 loops=1)
  Hash Cond: (h.feed_item_id = "ANY_subquery".id)
  Buffers: shared hit=272
  ->  Seq Scan on highlights h  (cost=0.00..236.43 rows=2343 width=710) (actual time=0.207..0.286 rows=1961 loops=1)
        Buffers: shared hit=213
  ->  Hash  (cost=72.21..72.21 rows=20 width=16) (actual time=0.084..0.085 rows=20 loops=1)
        Buckets: 1024  Batches: 1  Memory Usage: 9kB
        Buffers: shared hit=59
        ->  Subquery Scan on "ANY_subquery"  (cost=71.96..72.21 rows=20 width=16) (actual time=0.077..0.080 rows=20 loops=1)
              Buffers: shared hit=59
              ->  Limit  (cost=71.96..72.01 rows=20 width=24) (actual time=0.077..0.078 rows=20 loops=1)
                    Buffers: shared hit=59
                    ->  Sort  (cost=71.96..72.84 rows=354 width=24) (actual time=0.076..0.077 rows=20 loops=1)
                          Sort Key: fi.first_highlighted_at DESC, fi.id
                          Sort Method: top-N heapsort  Memory: 26kB
                          Buffers: shared hit=59
                          ->  Seq Scan on feed_items fi  (cost=0.00..62.54 rows=354 width=24) (actual time=0.053..0.059 rows=100 loops=1)
                                Buffers: shared hit=59
Planning:
  Buffers: shared hit=28
Planning Time: 0.206 ms
Execution Time: 0.592 ms

관찰(문서 §11):
- Semi Join 이 반환하는 행 = 1509(페이지 20개 부모의 highlights). 부모 M행 × 자식 = M×K 로 곱하지 않는다.
  L3 카테시안(조인이 feed_items ⋈ highlights 를 1961행으로 곱함)과 정반대 — 자식 K행만 반환(합, 곱 아님).
- 배치 페치가 하는 일이 이 shape다: 페이지 부모 키를 모아 WHERE feed_item_id IN (…) 로 한 방에 채운다.
  Hibernate 는 이를 default_batch_fetch_size 만큼 쪼개 ceil(pageItems/batch) 번 발행한다.
- Buffers: read≈0 → warm buffer cache. Execution Time 0.592 ms 는 executor 내부 시간(§6.4 caveat와 동일).
