์๋ก
์ฝ 2.5์ด๊ฐ ๊ฑธ๋ฆฌ๋ ๋ํ ๋ชฉ๋ก ์กฐํ API์ ์๋ต ์๊ฐ์ 0.2์ด๋ก ๊ฐ์ ํ๋ฉฐ ๋ฐฐ์ด ์๊ด ์๋ธ์ฟผ๋ฆฌ์ Hash Join, Nested Loop Join ์ ํน์ง์ ๊ณต์ ํฉ๋๋ค.
๋ฌธ์
๋ํ ๋ชฉ๋ก ์กฐํ API๋ฅผ Postman์ผ๋ก ํ ์คํธํ ๊ฒฐ๊ณผ, ์๋ต ์๊ฐ์ด ์ฝ 2.5์ด๋ ๊ฑธ๋ ธ์ต๋๋ค.

์์ธ ํ์
ํด๋น API๋ DB ์ ๊ทผ์ ์ด 3๋ฒ ํ๋ ๊ตฌ์กฐ์๊ธฐ์ ๋ณ๋ชฉ ์ง์ ์ ๋น ๋ฅด๊ฒ ํ์ ํ๊ธฐ ์ํด IntelliJ Profiler๋ก ๊ฐ ์ฟผ๋ฆฌ์ ์คํ ์๊ฐ์ ์ธก์ ํ์ต๋๋ค.

์ธก์ ๊ฒฐ๊ณผ
์ฒซ ๋ฒ์งธ ์ฟผ๋ฆฌ๋ ์๋ ์ฌ์ง์ ์กฐํ ์กฐ๊ฑด 5๊ฐ์ง(๊ฒ์์ด, ์๋น์ค, ์นดํ ๊ณ ๋ฆฌ ๋ช , ์ฌ์ฉ์ ๋ฐ์, ๋ํ์ผ์)๋ฅผ ํํฐ๋งํด์ ํ์ด์ง ๋น ๋ณด์ฌ์ค ๋ํ UUID 10๊ฐ๋ฅผ ์ถ์ถํ๋ ์ฟผ๋ฆฌ๋ก, 102ms ๊ฐ ๊ฑธ๋ ธ์ต๋๋ค.

๋ ๋ฒ์งธ ์ฟผ๋ฆฌ๋ ์๋ ์ฌ์ง์ ๋ํ UUID ๋ง๋ค ์ฌ์ฉ์ ๋ฐ์์ ์ง๊ณํ๋ ์ฟผ๋ฆฌ๋ก, 51ms ๊ฐ ๊ฑธ๋ ธ์ต๋๋ค.

์ธ ๋ฒ์งธ ์ฟผ๋ฆฌ๋ ๋ํ UUID ๋ง๋ค ์ ์ฌ์ง์ ์ต์ด์ง์์ ๋ต๋ณ์ ์กฐํํ๋ ์ฟผ๋ฆฌ๋ก ๊ฐ์ฅ ๋ฆ์ 2.3์ด๊ฐ ๊ฑธ๋ ธ๊ณ , IntelliJ Profiler ๋ฅผ ํตํด ๋น ๋ฅด๊ฒ ๋ณ๋ชฉ์ง์ ์ ํ์ ํ ์ ์์์ต๋๋ค.
์์ธ ๋ถ์
๋ฌธ์ ์ฟผ๋ฆฌ ์๊ฐ
์ธ ๋ฒ์งธ ์ฟผ๋ฆฌ๋ ์๊ฐ๋ณด๋ค ๋จ์ํ๋๋ฐ 2.3์ด๋ ๊ฑธ๋ ค์ ์์ํ์ต๋๋ค. ์ฟผ๋ฆฌ๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
- ๋ํ(Dialogue)์ ์ฑํ (Chat_log)์ 1 : N ๊ด๊ณ
- ๋ํ UUID 10๊ฐ๋ง๋ค ๊ณต๋ฐฑ์ด ์๋ ์ฑํ ์ ํตํด ์ต์ด ์ง์๋ฅผ ์ฐพ๊ธฐ
select c.*, d.*
from chat_log c
join dialogue d on c.dialogue_id = d.id
where d.id IN ( -- ๋ํ(dialouge) UUID 10๊ฐ
'3f056f44-993c-49ce-9c35-8c64821ef270',
...
'53186f20-784a-4f4b-bd0a-1e66061312eb')
and c.seq = (
select min(c2.seq)
from chat_log c2
where c2.question <> ''
and c2.dialogue_id = d.id -- ๋ฌธ์ ์ ์์ธ
);
์คํ ๊ณํ ๋ถ์
์ฌ์ฉ DB๋ PostgreSQL ์ด๋ฉฐ, EXPLAIN ANALYZE๋ก ์คํ๊ณํ์ ํ์ธํ๋ ์ฟผ๋ฆฌ๊ฐ ๊ธฐ๋ํ ๋๋ก ์คํ๋์ง ์์ ๊ฒ์ ํ์ธํ ์ ์์์ต๋๋ค.
์คํ ๊ณํ์์ ๊ฐ์ฅ ๋๋๋ฌ์ง ๊ฒ์ ์๋ธ ์ฟผ๋ฆฌ๊ฐ 20๋ฒ ๋ฐ๋ณต๋๋ ๋ฌธ์ ๊ฐ ์์ต๋๋ค.
```
1~2. dialogue 10๊ฐ ์กฐํ ์๋ฃ
3~4. chat_log ํด์ ์ค๋น ์๋ฃ
5~6. Hash Join ์์ → dialogue์ ๊ฐ row์ chat_log ๋งค์นญ ์๋ → SubPlan 20๋ฒ ์คํ
```

๊ธฐ๋ํ๋ ์ฟผ๋ฆฌ๋ Join ์ ์ WHERE ์ ์์ ๋ฏธ๋ฆฌ ํํฐ๋ง๋๋ ๊ฒ์ด์์ง๋ง, ์๋ธ์ฟผ๋ฆฌ๊ฐ Hash Join์ ์กฐ์ธ ์กฐ๊ฑด์ฒ๋ผ ๋์ํ๋ฉด์ ๋งค๋ฒ ์ธ๋ถ ๋ฐ์ดํฐ๋ฅผ ์ฐธ์กฐํ๊ธฐ ์ํด ๋ถํ์ํ chat_log ์ค์บ๊ณผ ์ง๊ณ ์์ ์ 20๋ฒ ๋ฐ๋ณต ์ํํ์ต๋๋ค.

์ด์ฒ๋ผ ์๋ธ์ฟผ๋ฆฌ๊ฐ ๋ฉ์ธ ์ฟผ๋ฆฌ์ d.id๋ฅผ ์ฐธ์กฐํ์ฌ ๋ฉ์ธ ์ฟผ๋ฆฌ์ ์์กดํ๋ ๊ฒ์ ์๊ด ์๋ธ์ฟผ๋ฆฌ๋ผ๊ณ ๋ถ๋ฆ ๋๋ค.
์๊ด ์๋ธ์ฟผ๋ฆฌ๋ ๋ฉ์ธ ์ฟผ๋ฆฌ์์ ์กฐํ๋ ๊ฐ ํ๋ง๋ค ๋ฐ๋ณต์ ์ผ๋ก ์คํ๋๊ธฐ ๋๋ฌธ์ ์ฑ๋ฅ ๋ฌธ์ ๋ฅผ ์ผ์ผํต๋๋ค.
Hash Join ํน์ง
๋ํ, Hash Join ์ ๊ณต๋ถํด๋ณด๋ ์คํ๊ณํ์ ๋ณด์ธ Hash Join ์ด ์ ๊ฒฝ์ฐ์ ์ ํฉํ Join ์ด ์๋์ ์๊ฒ ๋์์ต๋๋ค.
Hash Join ์ ๋์ ์๋ฆฌ๋ 1:N ๊ด๊ณ์์ ์์ ํ ์ด๋ธ(1 ํ ์ด๋ธ)์ ๋ฉ๋ชจ๋ฆฌ(PGA ์์ญ, ์ธ์ ๋ผ๋ฆฌ ๊ณต์ ํ์ง ์๋ ๊ณ ์ ํ ๋ฉ๋ชจ๋ฆฌ ์์ญ)์ ํด์ ํ ์ด๋ธ๋ก ๋น๋ํ๊ณ , ํฐ ํ ์ด๋ธ(N ํ ์ด๋ธ)์ ์์ฐจ์ ์ผ๋ก ์ฝ์ผ๋ฉฐ ์กฐ์ธํฉ๋๋ค. ํฐ ํ ์ด๋ธ(N ํ ์ด๋ธ)์ ์์ฐจ ์ ๊ทผํ๊ธฐ ๋๋ฌธ์ Random Access ๋ถํ๊ฐ ์์ต๋๋ค.
์ด ๋, ํด์ ํ ์ด๋ธ์ ๋น๋ํ๋ฉฐ Join ์ปฌ๋ผ์ ๊ธฐ์ค์ผ๋ก ํด์ ํจ์๋ฅผ ์ ์ฉํ๊ธฐ ๋๋ฌธ์ Join ์ปฌ๋ผ์ด ์ค๋ณต ์์ด ๊ณ ์ ํ ๊ฐ์ ๊ฐ์ง ์๋ก ์ฑ๋ฅ์ ์ข๊ณ , equal join ๋ง(๋ฒ์ Join X) ๊ฐ๋ฅํ๋ค๋ ๊ฒ์ ๋๋ค.
์ด๋ ๊ฒ ์์ ํ ์ด๋ธ์ด ํด์ ์์ญ(๋ฉ๋ชจ๋ฆฌ)์ ์ฌ๋ผ๊ฐ๊ธฐ ๋๋ฌธ์ ์ฌ๋ผ๊ฐ๋ ํ ์ด๋ธ์ ํฌ๊ธฐ๊ฐ ์ถฉ๋ถํ ์์์ผ ํฉ๋๋ค.(๋ฉ๋ชจ๋ฆฌ์ Join ์ปฌ๋ผ๋ง ์ฌ๋ผ๊ฐ๋ฉด, ํด๋น ํ ์ด๋ธ์ ๋ค๋ฅธ ์ปฌ๋ผ์ ๊ฐ์ ์ฝ๊ธฐ ์ํด ๋์คํฌ๋ฅผ ๋ค์ ์ฝ์ด์ผ ํฉ๋๋ค. ๊ทธ๋์, ํด์ ์์ญ์๋ Join ์ปฌ๋ผ๋ง ๋ฉ๋ชจ๋ฆฌ์ ์ฌ๋ผ๊ฐ๋๊ฒ ์๋๋ผ, ํ ์ด๋ธ ์ ์ฒด๊ฐ ์ฌ๋ผ๊ฐ๋๋ค). ๋ง์ฝ, ํ ์ด๋ธ์ ํฌ๊ธฐ๊ฐ ํด์ ์์ญ์ ํฌ๊ธฐ๋ฅผ ๋์ด๊ฐ๋ฉด ๋์คํฌ๋ฅผ ์ฌ์ฉํ๊ธฐ ๋๋ฌธ์ ์ฃผ์ํด์ผ ํฉ๋๋ค.
์ด๋ฐ ํน์ง์ ๊ฐ์ง๊ณ ์๊ธฐ ๋๋ฌธ์ ํด์ ์กฐ์ธ์ ์ฅ์ ์ ๋ฐฐ์น(๋์ฉ๋ ํ ์ด๋ธ)์์ ์ฐ๋ฉด ์ข์ Join ์ ๋๋ค. ํฐ ํ ์ด๋ธ(N ํ ์ด๋ธ)์ Join ํ ๋, ๋ฉ๋ชจ๋ฆฌ์ ์ฌ๋ฆฐ ์์ ํ ์ด๋ธ์ ๋ฐํ์ผ๋ก ์์ฐจ์ ์ผ๋ก ์ฝ๊ธฐ ๋๋ฌธ์ ๋๋ค.
ํด์ ์กฐ์ธ์ ๋จ์ ์ ์คํ ๋น๋๊ฐ ๋์ OLTP ํ๊ฒฝ(์ฃผ๋ฌธ, ์ฌ๊ณ ์ฒ๋ฆฌ, ์ํ ์กฐํ ๋ฑ ์งง์ ํธ๋์ญ์ )์ ๋๋ค. ์งง์ ์ฟผ๋ฆฌ๋ฅผ ์ํด ๋งค๋ฒ ํด์ ํ ์ด๋ธ์ ๋น๋ํด ๋ฉ๋ชจ๋ฆฌ์ ์ฌ๋ฆฌ๋ ์ผ์ ์ค๋ฒํค๋๋ฅผ ๋ฐ์์ํค๊ธฐ ๋๋ฌธ์ ๋๋ค. ๋ฐ๋ผ์, ์คํ๋น๋๊ฐ ๋์ OLTP ํ๊ฒฝ์ด๋ผ๋ฉด Join ์ด ํด์ ์กฐ์ธ์ผ๋ก ํ๋ฆฌ๊ณ ์์ง ์์์ง ๊ฒํ ํ ํ์๊ฐ ์์ต๋๋ค.
์ด ํน์ง์ ๋ฐํ์ผ๋ก ํฐ ํ ์ด๋ธ(chat_log)์ด ํด์ ํ ์ด๋ธ๋ก ๋น๋ ๋๋ค๋ ์ ์์ Join ๋ฐฉ์์ ๋ฐ๊ฟ ํ์๊ฐ ์์์ต๋๋ค.
ํด๊ฒฐ ๋ฐฉ์
๋จผ์ , ์๋ธ์ฟผ๋ฆฌ๊ฐ ๋ฉ์ธ ์ฟผ๋ฆฌ์ ์์กดํ์ง ์๋๋ก ๋ ๋ฆฝ์ ์ผ๋ก ๋ณ๊ฒฝํ์ต๋๋ค.
์ด๋ฅผ ํตํด ์๋ธ ์ฟผ๋ฆฌ๊ฐ Hash Join ์ ์กฐ๊ฑด์ผ๋ก ์ฐ์ด์ง ์๊ฒ ๋๋ ๊ฒ์ ๊ธฐ๋ํ์ต๋๋ค.
select c.*, d.*
from chat_log c
join dialogue d
on c.dialogue_id = d.id
where (d.id, c.seq) IN (
select c2.dialogue_id ,min(c2.seq)
from lingo_chat_log c2
where c2.question <> ''
and c2.dialogue_id IN ('3f056f44-993c-49ce-9c35-8c64821ef270',
...
'53186f20-784a-4f4b-bd0a-1e66061312eb')
group by c2.dialogue_id);
์ฟผ๋ฆฌ ๋ณ๊ฒฝ ํ ์คํ ๊ณํ
๋ณ๊ฒฝํ ์ฟผ๋ฆฌ๋ฅผ ์คํ ๊ณํ์ผ๋ก ๋ถ์ํด๋ณด์์ต๋๋ค.

๋จผ์ , ์กฐ์ธ ๋ฐฉ์์ด Hash Join์์ Nested Loop Join์ผ๋ก ๋ณ๊ฒฝ๋์์ต๋๋ค.
Nested Loop Join ํน์ง
Nested Loop Join ์ ๋์ ์๋ฆฌ๋ ์ค์ฒฉ for๋ฌธ ์ฒ๋ผ 1 : N ํ ์ด๋ธ ๊ด๊ณ์์ outer ํ ์ด๋ธ(1 ํ ์ด๋ธ)์ ๊ฐ ํ๋ง๋ค inner ํ ์ด๋ธ(N ํ ์ด๋ธ)์ ํ์ํฉ๋๋ค.
for( i ... outer ) -- 1 ํ
์ด๋ธ
for ( j ... inner ) -- N ํ
์ด๋ธ
์ด๋ฐ ๋์ ์๋ฆฌ ๋๋ฌธ์ Nested Loop Join ์์ inner table ์ Join ์ปฌ๋ผ์ ์ธ๋ฑ์ค๊ฐ ๊ฑธ๋ ค์์ง ์์ผ๋ฉด ๋งค์ฐ๋งค์ฐ ๋นํจ์จ์ ์ ๋๋ค.
inner table ์ ์ธ๋ฑ์ค๊ฐ ์์ผ๋ฉด outer table ์์ ๊ณ ๋ฅธ 1๊ฐ์ row ๋ง๋ค inner table ์ ๋ชจ๋ full scan ํด์ผ ๋๊ธฐ ๋๋ฌธ์ ๋๋ค.
๋ํ, outer ํ ์ด๋ธ์ 1๊ฑด row ๋ง๋ค inner table์ ๋ชจ๋ ํ์ ํ์ํ๊ธฐ ๋๋ฌธ์ ๋๋์ ํ ์ด๋ธ์ Join ํ๋ ๊ฒฝ์ฐ ๋ฐ๋์ง ํ์ง ์์ต๋๋ค.
์ด๋ฐ ํน์ง ๋๋ฌธ์ 1 : N ํ ์ด๋ธ ๊ด๊ณ์์ 1 ์ชฝ์ด outer ํ ์ด๋ธ๋ก ๋๋ ๊ฒ์ด ์ผ๋ฐ์ ์ผ๋ก ์ฑ๋ฅ์ ์ ๋ฆฌํ ์ด์ ์ญ์, outer ํ ์ด๋ธ ํฌ๊ธฐ๋งํผ for๋ฌธ์ ๋๊ธฐ ๋๋ฌธ์ ๋๋ค. ์๋ฅผ ๋ค์ด, ๋ง์ฝ N ํ ์ด๋ธ์ ๋ ์ฝ๋๊ฐ 10๋ง๊ฐ๋ฉด 10๋ง๋ฒ์ ๋๋ ๋ฐ๋ฉด, 1 ํ ์ด๋ธ์ ๋ ์ฝ๋๊ฐ 100๊ฐ๋ฉด for๋ฌธ์ 100๋ฒ๋ง ๋๊ธฐ ๋๋ฌธ์ ๋๋ค.
๋ณธ๋ก ์ผ๋ก ๋์์ ๋ฐ๋ ์ฟผ๋ฆฌ์ ์คํ ๊ณํ์ ๋ณด๋ Nested Loop ์ outer table ์ธ chat_log ํ ์ด๋ธ์ด ํํฐ๋ง์ ์ํด 25๊ฑด(rows=25)์ด ๋จ์ ์๊ณ , inner table ์ธ dialogue ํ ์ด๋ธ์ pk๋ฅผ ํ์ฉํด ์ธ๋ฑ์ค ํ์ผ๋ฉฐ, ํจ์จ์ ์ผ๋ก ์ค์บ ๋จ์ ๋ณผ ์ ์์์ต๋๋ค.
์ถ๊ฐ ๊ฐ์ ํ ์
1. ๋ณตํฉ ์ธ๋ฑ์ค ์ค์
๋จผ์ ์คํ๊ณํ์ "Rows Removed by Filter: 4184" ๋ฅผ ํตํด question <> '' ์กฐ๊ฑด๊ณผ dialogue_Id IN (...) ์กฐ๊ฑด ๋๋ฌธ์ ๋ถํ์ํ 4184๊ฑด์ ํ์ ์ฝ๊ณ ์์์ต๋๋ค. ๊ทธ๋ ๊ธฐ์ question ๊ณผ dialogue_id ์ ๋ณตํฉ ์ธ๋ฑ์ค๋ฅผ ๊ฑธ๋ฉด ๋ถํ์ํ 4184๊ฑด์ ํ์ ์ฝ์ง ์์๋ ๋ ๊ฒ์ด๋ผ๋ ๊ธฐ๋๊ฐ ์์์ต๋๋ค.
๋ง์นจ, Sort Key ๋ก dialogue_id ๋ฅผ ์ฐ๋(๋ณตํฉ ์ธ๋ฑ์ค ํน์ฑ์ ๋ ๋ฒ์งธ ์ปฌ๋ผ๋ถํฐ๋ ์ง์ ์ปฌ๋ผ์ ๊ธฐ์ค์ผ๋ก ์ ๋ ฌ์ด ๋๋), ๋ณตํฉ ์ธ๋ฑ์ค๋ฅผ (dialouge_id, question)์ผ๋ก ๊ฑธ๋ฉด Sort(์ ๋ ฌ)๋ ์คํ๊ณํ์์ ๋น ์ง ๊ฒ ๊ฐ์ ๊ธฐ๋ํ ์ ์์์ต๋๋ค.
2. PostgreSQL ์ partial Index ์ค์
PostgreSQL ์ partial Index ๋ผ๊ณ Create Index ๋ก Index ๋ฅผ ์์ฑํ ์, WHERE ์กฐ๊ฑด์ ํด๋นํ๋ ํ๋ง ์ธ๋ฑ์ฑ์ ํ ์ ์๋ ๊ธฐ๋ฅ์ด ์์ต๋๋ค.(https://www.postgresql.org/docs/current/indexes-partial.html)
์ด๋ฅผ ๋ฐํ์ผ๋ก ์ธ๋ฑ์ค ์ ์ฅ ์ฉ๋์ ์ค์ผ ์ ์์ ๊ฒ์ ๋๋ค.
ํ์ง๋ง, ๊ฒฐ๊ณผ๋ฅผ ๋ณด๋ฉด ์ถฉ๋ถํ ์ฑ๋ฅ์ด ์ด๋ฏธ ๋์ค๊ณ ์์๊ธฐ์ ํ์ฌ ๋ฌธ์ ์ ๋ง๋ ํด๊ฒฐ์ฑ ๋ง ์ ์ฉํ์ต๋๋ค.
๊ฒฐ๊ณผ
๊ทธ ๊ฒฐ๊ณผ๋ก ์ธ๋ฑ์ค ์ถ๊ฐ ์์ด ๋ถํ์ํ ์๊ด ์๋ธ์ฟผ๋ฆฌ๋ฅผ ์ ๊ฑฐํ์ฌ, 2.5์ด ๊ฑธ๋ฆฌ๋ API ์๋ต์ 0.2์ด๋ก ๊ฐ์ ํ ์ ์๊ฒ ๋์์ต๋๋ค.

์ฐธ๊ณ ์๋ฃ
- Join ์ํ ์๋ฆฌ (https://www.youtube.com/@SQL)
- PostgreSQL Partial Index (https://www.postgresql.org/docs/current/indexes-partial.html)