๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค ์•„ํ‚คํ…์ฒ˜์˜ ์šด์˜ ๋ถ„์„: ๋„์›€๋ง ๋ฐ ํ”„๋กฌํ”„ํŠธ Postgres FDW

์ด ์„ธ์ƒ์˜ ๋ชจ๋“  ๊ฒƒ๊ณผ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค ์•„ํ‚คํ…์ฒ˜์—๋Š” ์žฅ๋‹จ์ ์ด ์žˆ์Šต๋‹ˆ๋‹ค. ์ผ๋ถ€ ํ”„๋กœ์„ธ์Šค๋Š” ๋” ์‰ฌ์›Œ์ง€๊ณ  ๋‹ค๋ฅธ ํ”„๋กœ์„ธ์Šค๋Š” ๋” ์–ด๋ ค์›Œ์ง‘๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๋ณ€ํ™”์˜ ์†๋„์™€ ๋” ๋‚˜์€ ํ™•์žฅ์„ฑ์„ ์œ„ํ•ด์„œ๋Š” ํฌ์ƒ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ ์ค‘ ํ•˜๋‚˜๋Š” ๋ถ„์„์˜ ๋ณต์žก์„ฑ์ด ์ฆ๊ฐ€ํ•˜๊ณ  ์žˆ๋‹ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๋ชจ๋†€๋ฆฌ์Šค์—์„œ ๋ชจ๋“  ์šด์˜ ๋ถ„์„์„ ๋ถ„์„ ๋ณต์ œ๋ณธ์— ๋Œ€ํ•œ SQL ์ฟผ๋ฆฌ๋กœ ์ถ•์†Œํ•  ์ˆ˜ ์žˆ๋‹ค๋ฉด ๋ฉ€ํ‹ฐ์„œ๋น„์Šค ์•„ํ‚คํ…์ฒ˜์—์„œ๋Š” ๊ฐ ์„œ๋น„์Šค๊ฐ€ ์ž์ฒด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ๊ฐ€์ง€๋ฉฐ ํ•˜๋‚˜์˜ ์ฟผ๋ฆฌ๋ฅผ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์—†๋Š” ๊ฒƒ์ฒ˜๋Ÿผ ๋ณด์ž…๋‹ˆ๋‹ค(๋˜๋Š” ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ์„๊นŒ์š”?). ์šฐ๋ฆฌ ํšŒ์‚ฌ์—์„œ ์šด์˜ ๋ถ„์„ ๋ฌธ์ œ๋ฅผ ์–ด๋–ป๊ฒŒ ํ•ด๊ฒฐํ–ˆ๋Š”์ง€, ๊ทธ๋ฆฌ๊ณ  ์ด ์†”๋ฃจ์…˜์„ ์–ด๋–ป๊ฒŒ ํ™œ์šฉํ•˜๋Š”์ง€์— ๊ด€์‹ฌ์ด ์žˆ๋Š” ๋ถ„๋“ค์„ ํ™˜์˜ํ•ฉ๋‹ˆ๋‹ค.

๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค ์•„ํ‚คํ…์ฒ˜์˜ ์šด์˜ ๋ถ„์„: ๋„์›€๋ง ๋ฐ ํ”„๋กฌํ”„ํŠธ Postgres FDW
์ œ ์ด๋ฆ„์€ Pavel Sivash์ž…๋‹ˆ๋‹ค. ์ €๋Š” DomClick์—์„œ ๋ถ„์„ ๋ฐ์ดํ„ฐ ์›จ์–ดํ•˜์šฐ์Šค ์œ ์ง€ ๊ด€๋ฆฌ๋ฅผ ๋‹ด๋‹นํ•˜๋Š” ํŒ€์—์„œ ์ผํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์ผ๋ฐ˜์ ์œผ๋กœ ์šฐ๋ฆฌ์˜ ํ™œ๋™์€ ๋ฐ์ดํ„ฐ ์—”์ง€๋‹ˆ์–ด๋ง์œผ๋กœ ๋ถ„๋ฅ˜๋  ์ˆ˜ ์žˆ์ง€๋งŒ ์‹ค์ œ๋กœ๋Š” ์—…๋ฌด ๋ฒ”์œ„๊ฐ€ ํ›จ์”ฌ ๋” ๋„“์Šต๋‹ˆ๋‹ค. ๋ฐ์ดํ„ฐ ์—”์ง€๋‹ˆ์–ด๋ง, ๋ฐ์ดํ„ฐ ๋ถ„์„์„ ์œ„ํ•œ ๋„๊ตฌ ์ง€์› ๋ฐ ์ ์šฉ๊ณผ ์ž์ฒด ๋„๊ตฌ ๊ฐœ๋ฐœ์„ ์œ„ํ•œ ETL/ELT ํ‘œ์ค€์ด ์žˆ์Šต๋‹ˆ๋‹ค. ํŠนํžˆ, ์šด์˜ ๋ณด๊ณ ๋ฅผ ์œ„ํ•ด ์šฐ๋ฆฌ๋Š” ํ•˜๋‚˜์˜ ๋‹จ์ผ์ฒด๊ฐ€ ์žˆ๋Š” ๊ฒƒ์ฒ˜๋Ÿผ "๊ฐ€์žฅ" ๋ถ„์„๊ฐ€์—๊ฒŒ ํ•„์š”ํ•œ ๋ชจ๋“  ๋ฐ์ดํ„ฐ๊ฐ€ ํฌํ•จ๋œ ํ•˜๋‚˜์˜ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ์ œ๊ณตํ•˜๊ธฐ๋กœ ๊ฒฐ์ •ํ–ˆ์Šต๋‹ˆ๋‹ค.

์ผ๋ฐ˜์ ์œผ๋กœ ์šฐ๋ฆฌ๋Š” ๋‹ค์–‘ํ•œ ์˜ต์…˜์„ ๊ณ ๋ คํ–ˆ์Šต๋‹ˆ๋‹ค. ๋ณธ๊ฒฉ์ ์ธ ์ €์žฅ์†Œ๋ฅผ ๊ตฌ์ถ•ํ•˜๋Š” ๊ฒƒ์ด ๊ฐ€๋Šฅํ–ˆ์Šต๋‹ˆ๋‹ค. ์šฐ๋ฆฌ๋„ ์‹œ๋„ํ–ˆ์ง€๋งŒ ์†”์งํžˆ ๋งํ•ด์„œ ์ €์žฅ์†Œ๋ฅผ ๊ตฌ์ถ•ํ•˜๊ณ  ๋ณ€๊ฒฝํ•˜๋Š” ๋‹ค์†Œ ๋Š๋ฆฐ ํ”„๋กœ์„ธ์Šค์™€ ๋กœ์ง์˜ ์ƒ๋‹นํžˆ ๋นˆ๋ฒˆํ•œ ๋ณ€๊ฒฝ์„ ๊ฒฐํ•ฉํ•  ์ˆ˜ ์—†์—ˆ์Šต๋‹ˆ๋‹ค. , ๋ฐฉ๋ฒ•์„ ๋Œ“๊ธ€์— ์ ์–ด์ฃผ์„ธ์š”). ๋ถ„์„๊ฐ€๋“ค์—๊ฒŒ โ€œ์–˜๋“ค์•„, ํŒŒ์ด์ฌ ์ข€ ๋ฐฐ์›Œ์„œ ๋ถ„์„์šฉ ๋ ˆํ”Œ๋ฆฌ์นด๋กœ ๊ฐ€๊ฑฐ๋ผโ€๋ผ๊ณ  ๋งํ•  ์ˆ˜๋Š” ์žˆ์—ˆ์ง€๋งŒ, ์ด๋Š” ์ฑ„์šฉ์„ ์œ„ํ•œ ์ถ”๊ฐ€ ์š”๊ตฌ ์‚ฌํ•ญ์ด๊ณ , ๊ฐ€๋Šฅํ•˜๋ฉด ํ”ผํ•ด์•ผ ํ•  ๊ฒƒ ๊ฐ™์•˜๋‹ค. ์šฐ๋ฆฌ๋Š” FDW(Foreign Data Wrapper) ๊ธฐ์ˆ ์„ ์‚ฌ์šฉํ•˜๊ธฐ๋กœ ๊ฒฐ์ •ํ–ˆ์Šต๋‹ˆ๋‹ค. ๊ธฐ๋ณธ์ ์œผ๋กœ ์ด๋Š” SQL ํ‘œ์ค€์— ์†ํ•˜์ง€๋งŒ ํ›จ์”ฌ ๋” ํŽธ๋ฆฌํ•œ ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ฐ–์ถ˜ ํ‘œ์ค€ dblink์ž…๋‹ˆ๋‹ค. ์ด๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ ์šฐ๋ฆฌ๋Š” ์†”๋ฃจ์…˜์„ ๋งŒ๋“ค์—ˆ๊ณ , ๊ฒฐ๊ตญ ์„ฑ๊ณต์„ ๊ฑฐ๋‘์—ˆ๊ณ , ์ด๋ฅผ ์ •์ฐฉ์‹œ์ผฐ์Šต๋‹ˆ๋‹ค. ์„ธ๋ถ€ ์‚ฌํ•ญ์€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์Šคํ‚ค๋งˆ ๋™๊ธฐํ™”๋ถ€ํ„ฐ ๊ฐœ์ธ ๋ฐ์ดํ„ฐ์˜ ์•ก์„ธ์Šค ์ œ์–ด ๋ฐ ๊ฐœ์ธํ™” ํ•ด์ œ์— ์ด๋ฅด๊ธฐ๊นŒ์ง€ ๋งŽ์€ ๊ฒƒ์— ๋Œ€ํ•ด ์ด์•ผ๊ธฐํ•˜๊ณ  ์‹ถ๊ธฐ ๋•Œ๋ฌธ์— ๋ณ„๋„์˜ ๊ธฐ์‚ฌ์˜ ์ฃผ์ œ์ด๊ฑฐ๋‚˜ ํ•˜๋‚˜ ์ด์ƒ์ผ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ ์ด ์†”๋ฃจ์…˜์€ ์‹ค์ œ ๋ถ„์„ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋ฐ ๋ฆฌํฌ์ง€ํ† ๋ฆฌ๋ฅผ ๋Œ€์ฒดํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ฉฐ ํŠน์ • ๋ฌธ์ œ๋งŒ ํ•ด๊ฒฐํ•œ๋‹ค๋Š” ์ ๋„ ์œ ๋ณดํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

์ตœ์ƒ์œ„ ์ˆ˜์ค€์—์„œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค ์•„ํ‚คํ…์ฒ˜์˜ ์šด์˜ ๋ถ„์„: ๋„์›€๋ง ๋ฐ ํ”„๋กฌํ”„ํŠธ Postgres FDW
์‚ฌ์šฉ์ž๊ฐ€ ์ž‘์—… ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•  ์ˆ˜ ์žˆ๋Š” PostgreSQL ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๊ฐ€ ์žˆ์œผ๋ฉฐ, ๊ฐ€์žฅ ์ค‘์š”ํ•œ ๊ฒƒ์€ ๋ชจ๋“  ์„œ๋น„์Šค์˜ ๋ถ„์„ ๋ณต์ œ๋ณธ์ด FDW๋ฅผ ํ†ตํ•ด ์ด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์—ฐ๊ฒฐ๋œ๋‹ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ์—ฌ๋Ÿฌ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ๋Œ€ํ•œ ์ฟผ๋ฆฌ๋ฅผ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ ๊ทธ๊ฒƒ์ด ๋ฌด์—‡์ธ์ง€๋Š” ์ค‘์š”ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค: PostgreSQL, MySQL, MongoDB ๋˜๋Š” ๋‹ค๋ฅธ ๊ฒƒ(ํŒŒ์ผ, API, ๊ฐ‘์ž๊ธฐ ์ ํ•ฉํ•œ ๋ž˜ํผ๊ฐ€ ์—†์œผ๋ฉด ์ง์ ‘ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค). ๊ธ€์Ž„, ๋ชจ๋“  ๊ฒƒ์ด ์ข‹์•„ ๋ณด์ธ๋‹ค! ์šฐ๋ฆฌ ํ—ค์–ด์ง€๋Š” ๊ฑด๊ฐ€์š”?

๋ชจ๋“  ๊ฒƒ์ด ๋„ˆ๋ฌด ๋น ๋ฅด๊ณ  ๊ฐ„๋‹จํ•˜๊ฒŒ ๋๋‚ฌ๋‹ค๋ฉด ์•„๋งˆ๋„ ๊ธฐ์‚ฌ๊ฐ€ ์—†์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค.

Postgres๊ฐ€ ์›๊ฒฉ ์„œ๋ฒ„์— ๋Œ€ํ•œ ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ๋ช…ํ™•ํžˆ ์•„๋Š” ๊ฒƒ์ด ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค. ์ด๋Š” ๋…ผ๋ฆฌ์ ์ธ ๊ฒƒ์ฒ˜๋Ÿผ ๋ณด์ด์ง€๋งŒ ์ข…์ข… ์‚ฌ๋žŒ๋“ค์€ ์ด์— ์ฃผ์˜๋ฅผ ๊ธฐ์šธ์ด์ง€ ์•Š์Šต๋‹ˆ๋‹ค. Postgres๋Š” ์š”์ฒญ์„ ์›๊ฒฉ ์„œ๋ฒ„์—์„œ ๋…๋ฆฝ์ ์œผ๋กœ ์‹คํ–‰๋˜๋Š” ๋ถ€๋ถ„์œผ๋กœ ๋‚˜๋ˆ„๊ณ  ์ด ๋ฐ์ดํ„ฐ๋ฅผ ์ˆ˜์ง‘ํ•˜๊ณ  ์ž์ฒด์ ์œผ๋กœ ์ตœ์ข… ๊ณ„์‚ฐ์„ ์ˆ˜ํ–‰ํ•˜๋ฏ€๋กœ ์ฟผ๋ฆฌ ์‹คํ–‰ ์†๋„๋Š” ๋‹ค์Œ ์‚ฌํ•ญ์— ๋”ฐ๋ผ ํฌ๊ฒŒ ๋‹ฌ๋ผ์ง‘๋‹ˆ๋‹ค. ๊ทธ๊ฒƒ์ด ์–ด๋–ป๊ฒŒ ์“ฐ์—ฌ์กŒ๋Š”๊ฐ€. ๋˜ํ•œ ์ฃผ๋ชฉํ•ด์•ผ ํ•  ์ ์€ ๋ฐ์ดํ„ฐ๊ฐ€ ์›๊ฒฉ ์„œ๋ฒ„์—์„œ ๋„์ฐฉํ•˜๋ฉด ๋” ์ด์ƒ ์ธ๋ฑ์Šค๊ฐ€ ์—†์œผ๋ฉฐ ์Šค์ผ€์ค„๋Ÿฌ๋ฅผ ๋„์šธ ์ˆ˜ ์žˆ๋Š” ๊ฒƒ์ด ์—†๊ธฐ ๋•Œ๋ฌธ์— ์šฐ๋ฆฌ ์ž์‹ ๋งŒ์ด ๊ทธ๋ฅผ ๋•๊ณ  ์กฐ์–ธํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์ด๊ฒƒ์ด ๋ฐ”๋กœ ์ œ๊ฐ€ ๋” ์ž์„ธํžˆ ์ด์•ผ๊ธฐํ•˜๊ณ  ์‹ถ์€ ๊ฒƒ์ž…๋‹ˆ๋‹ค.

๊ฐ„๋‹จํ•œ ์ฟผ๋ฆฌ์™€ ๊ทธ์— ๋”ฐ๋ฅธ ๊ณ„ํš

Postgres๊ฐ€ ์›๊ฒฉ ์„œ๋ฒ„์—์„œ 6๋งŒ ํ–‰ ํ…Œ์ด๋ธ”์„ ์ฟผ๋ฆฌํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ๋ณด์—ฌ์ฃผ๊ธฐ ์œ„ํ•ด ๊ฐ„๋‹จํ•œ ๊ณ„ํš์„ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

explain analyze verbose  
SELECT count(1)
FROM fdw_schema.table;

Aggregate  (cost=418383.23..418383.24 rows=1 width=8) (actual time=3857.198..3857.198 rows=1 loops=1)
  Output: count(1)
  ->  Foreign Scan on fdw_schema."table"  (cost=100.00..402376.14 rows=6402838 width=0) (actual time=4.874..3256.511 rows=6406868 loops=1)
        Output: "table".id, "table".is_active, "table".meta, "table".created_dt
        Remote SQL: SELECT NULL FROM fdw_schema.table
Planning time: 0.986 ms
Execution time: 3857.436 ms

VERBOSE ๋ฌธ์„ ์‚ฌ์šฉํ•˜๋ฉด ์›๊ฒฉ ์„œ๋ฒ„๋กœ ์ „์†ก๋  ์ฟผ๋ฆฌ์™€ ์ถ”๊ฐ€ ์ฒ˜๋ฆฌ๋ฅผ ์œ„ํ•ด ์ˆ˜์‹ ํ•  ๊ฒฐ๊ณผ(RemoteSQL ๋ผ์ธ)๋ฅผ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ข€ ๋” ๋‚˜์•„๊ฐ€ ์š”์ฒญ์— ์—ฌ๋Ÿฌ ํ•„ํ„ฐ๋ฅผ ์ถ”๊ฐ€ํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. ๋ถ€์šธ ํ•„๋“œ, ๋ฐœ์ƒ๋ณ„๋กœ ํ•˜๋‚˜์”ฉ ๋”ฐ๋ผ์„œ ์˜ค๋ฅธ์ชฝ ํ•˜๋‹จ์— ๊ฐ„๊ฒฉ์œผ๋กœ ๊ทธ๋ฆฌ๊ณ  ํ•˜๋‚˜์”ฉ JSONB.

explain analyze verbose
SELECT count(1)
FROM fdw_schema.table 
WHERE is_active is True
AND created_dt BETWEEN CURRENT_DATE - INTERVAL '7 month' 
AND CURRENT_DATE - INTERVAL '6 month'
AND meta->>'source' = 'test';

Aggregate  (cost=577487.69..577487.70 rows=1 width=8) (actual time=27473.818..25473.819 rows=1 loops=1)
  Output: count(1)
  ->  Foreign Scan on fdw_schema."table"  (cost=100.00..577469.21 rows=7390 width=0) (actual time=31.369..25372.466 rows=1360025 loops=1)
        Output: "table".id, "table".is_active, "table".meta, "table".created_dt
        Filter: (("table".is_active IS TRUE) AND (("table".meta ->> 'source'::text) = 'test'::text) AND ("table".created_dt >= (('now'::cstring)::date - '7 mons'::interval)) AND ("table".created_dt <= ((('now'::cstring)::date)::timestamp with time zone - '6 mons'::interval)))
        Rows Removed by Filter: 5046843
        Remote SQL: SELECT created_dt, is_active, meta FROM fdw_schema.table
Planning time: 0.665 ms
Execution time: 27474.118 ms

์ฟผ๋ฆฌ ์ž‘์„ฑ ์‹œ ์ฃผ์˜ํ•ด์•ผ ํ•  ์ ์ด ๋ฐ”๋กœ ์—ฌ๊ธฐ์— ์žˆ์Šต๋‹ˆ๋‹ค. ํ•„ํ„ฐ๋Š” ์›๊ฒฉ ์„œ๋ฒ„๋กœ ์ „์†ก๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. ์ฆ‰, ์ด๋ฅผ ์‹คํ–‰ํ•˜๊ธฐ ์œ„ํ•ด Postgres๋Š” 6๋งŒ ๊ฐœ์˜ ํ–‰์„ ๋ชจ๋‘ ๊ฐ€์ ธ์™€์„œ ๋กœ์ปฌ๋กœ ํ•„ํ„ฐ๋งํ•˜๊ณ (ํ•„ํ„ฐ ํ–‰) ์ง‘๊ณ„๋ฅผ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค. ์„ฑ๊ณต์˜ ์—ด์‡ ๋Š” ํ•„ํ„ฐ๊ฐ€ ์›๊ฒฉ ์‹œ์Šคํ…œ์œผ๋กœ ์ „์†ก๋˜๋„๋ก ์ฟผ๋ฆฌ๋ฅผ ์ž‘์„ฑํ•˜๊ณ  ํ•„์š”ํ•œ ํ–‰๋งŒ ์ˆ˜์‹ ํ•˜์—ฌ ์ง‘๊ณ„ํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

๊ทธ๊ฑฐ ์ข€ ๋ง๋„ ์•ˆ ๋˜๋Š” ์†Œ๋ฆฌ์•ผ

๋ถ€์šธ ํ•„๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋ชจ๋“  ๊ฒƒ์ด ๊ฐ„๋‹จํ•ด์ง‘๋‹ˆ๋‹ค. ์›๋ž˜ ์š”์ฒญ์—์„œ๋Š” ๋ฌธ์ œ๊ฐ€ ์šด์˜์ž๋กœ ์ธํ•ด ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค. is. ์œผ๋กœ ๊ต์ฒดํ•˜๋ฉด =, ๊ทธ๋Ÿฌ๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๊ฒฐ๊ณผ๋ฅผ ์–ป์Šต๋‹ˆ๋‹ค.

explain analyze verbose
SELECT count(1)
FROM fdw_schema.table
WHERE is_active = True
AND created_dt BETWEEN CURRENT_DATE - INTERVAL '7 month' 
AND CURRENT_DATE - INTERVAL '6 month'
AND meta->>'source' = 'test';

Aggregate  (cost=508010.14..508010.15 rows=1 width=8) (actual time=19064.314..19064.314 rows=1 loops=1)
  Output: count(1)
  ->  Foreign Scan on fdw_schema."table"  (cost=100.00..507988.44 rows=8679 width=0) (actual time=33.035..18951.278 rows=1360025 loops=1)
        Output: "table".id, "table".is_active, "table".meta, "table".created_dt
        Filter: ((("table".meta ->> 'source'::text) = 'test'::text) AND ("table".created_dt >= (('now'::cstring)::date - '7 mons'::interval)) AND ("table".created_dt <= ((('now'::cstring)::date)::timestamp with time zone - '6 mons'::interval)))
        Rows Removed by Filter: 3567989
        Remote SQL: SELECT created_dt, meta FROM fdw_schema.table WHERE (is_active)
Planning time: 0.834 ms
Execution time: 19064.534 ms

๋ณด์‹œ๋‹ค์‹œํ”ผ ํ•„ํ„ฐ๊ฐ€ ์›๊ฒฉ ์„œ๋ฒ„๋กœ ๋‚ ์•„๊ฐ€์„œ ์‹คํ–‰ ์‹œ๊ฐ„์ด 27์ดˆ์—์„œ 19์ดˆ๋กœ ๋‹จ์ถ•๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

์ฃผ๋ชฉํ•  ์ ์€ ์šด์˜์ž๊ฐ€ is ์šด์˜์ž๋ž‘ ๋‹ค๋ฆ„ = Null ๊ฐ’์œผ๋กœ ์ž‘์—…ํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. ๊ทธ๊ฒƒ์€ ๋‹ค์Œ์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค ์‚ฌ์‹ค์ด ์•„๋‹ˆ๋‹ค ํ•„ํ„ฐ์— False ๋ฐ Null ๊ฐ’์„ ๋‚จ๊ฒจ ๋‘๋Š” ๋ฐ˜๋ฉด != ์ฐธ False ๊ฐ’๋งŒ ๋‚จ๊น๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ์˜คํผ๋ ˆ์ดํ„ฐ๋ฅผ ๊ต์ฒดํ•  ๋•Œ ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค OR ์—ฐ์‚ฐ์ž๊ฐ€ ์žˆ๋Š” ๋‘ ๊ฐ€์ง€ ์กฐ๊ฑด์ด ํ•„ํ„ฐ์— ์ „๋‹ฌ๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, WHERE (col != True) OR (col์€ null์ž…๋‹ˆ๋‹ค).

์šฐ๋ฆฌ๋Š” ๋ถˆ๋ฆฌ์–ธ์„ ๋‹ค๋ฃจ์—ˆ์Šต๋‹ˆ๋‹ค. ๊ณ„์† ์ง„ํ–‰ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. ์ง€๊ธˆ์€ ๋‹ค๋ฅธ ๋ณ€๊ฒฝ ์‚ฌํ•ญ์˜ ํšจ๊ณผ๋ฅผ ๋…๋ฆฝ์ ์œผ๋กœ ๊ณ ๋ คํ•˜๊ธฐ ์œ„ํ•ด ๋ถ€์šธ ํ•„ํ„ฐ๋ฅผ ์›๋ž˜ ํ˜•์‹์œผ๋กœ ๋˜๋Œ๋ ค ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

ํƒ€์ž„์Šคํƒฌํ”„tz? hz

์ผ๋ฐ˜์ ์œผ๋กœ ์›๊ฒฉ ์„œ๋ฒ„์™€ ๊ด€๋ จ๋œ ์š”์ฒญ์„ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์ž‘์„ฑํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์‹คํ—˜ํ•œ ๋‹ค์Œ ์™œ ์ด๋Ÿฐ ์ผ์ด ๋ฐœ์ƒํ•˜๋Š”์ง€์— ๋Œ€ํ•œ ์„ค๋ช…์„ ์ฐพ์•„์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋งŽ์Šต๋‹ˆ๋‹ค. ์ด์— ๋Œ€ํ•œ ์ •๋ณด๋Š” ์ธํ„ฐ๋„ท์—์„œ ๊ฑฐ์˜ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ์‹คํ—˜์—์„œ ๊ณ ์ • ๋‚ ์งœ ํ•„ํ„ฐ๊ฐ€ ์พ… ์†Œ๋ฆฌ์™€ ํ•จ๊ป˜ ์›๊ฒฉ ์„œ๋ฒ„๋กœ ๋‚ ์•„๊ฐ€๋Š” ๊ฒƒ์„ ๋ฐœ๊ฒฌํ–ˆ์ง€๋งŒ now() ๋˜๋Š” CURRENT_DATE์™€ ๊ฐ™์ด ๋‚ ์งœ๋ฅผ ๋™์ ์œผ๋กœ ์„ค์ •ํ•˜๋ ค๋Š” ๊ฒฝ์šฐ์—๋Š” ์ด๋Ÿฐ ์ผ์ด ๋ฐœ์ƒํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์ด ์˜ˆ์—์„œ๋Š” Create_at ์—ด์— ๊ณผ๊ฑฐ ์ •ํ™•ํžˆ 1๊ฐœ์›”(BETWEEN CURRENT_DATE - INTERVAL '7๊ฐœ์›”' AND CURRENT_DATE - INTERVAL '6๊ฐœ์›”')์˜ ๋ฐ์ดํ„ฐ๊ฐ€ ํฌํ•จ๋˜๋„๋ก ํ•„ํ„ฐ๋ฅผ ์ถ”๊ฐ€ํ–ˆ์Šต๋‹ˆ๋‹ค. ์ด ๊ฒฝ์šฐ ์šฐ๋ฆฌ๋Š” ๋ฌด์—‡์„ ํ–ˆ๋‚˜์š”?

explain analyze verbose
SELECT count(1)
FROM fdw_schema.table 
WHERE is_active is True
AND created_dt >= (SELECT CURRENT_DATE::timestamptz - INTERVAL '7 month') 
AND created_dt <(SELECT CURRENT_DATE::timestamptz - INTERVAL '6 month')
AND meta->>'source' = 'test';

Aggregate  (cost=306875.17..306875.18 rows=1 width=8) (actual time=4789.114..4789.115 rows=1 loops=1)
  Output: count(1)
  InitPlan 1 (returns $0)
    ->  Result  (cost=0.00..0.02 rows=1 width=8) (actual time=0.007..0.008 rows=1 loops=1)
          Output: ((('now'::cstring)::date)::timestamp with time zone - '7 mons'::interval)
  InitPlan 2 (returns $1)
    ->  Result  (cost=0.00..0.02 rows=1 width=8) (actual time=0.002..0.002 rows=1 loops=1)
          Output: ((('now'::cstring)::date)::timestamp with time zone - '6 mons'::interval)
  ->  Foreign Scan on fdw_schema."table"  (cost=100.02..306874.86 rows=105 width=0) (actual time=23.475..4681.419 rows=1360025 loops=1)
        Output: "table".id, "table".is_active, "table".meta, "table".created_dt
        Filter: (("table".is_active IS TRUE) AND (("table".meta ->> 'source'::text) = 'test'::text))
        Rows Removed by Filter: 76934
        Remote SQL: SELECT is_active, meta FROM fdw_schema.table WHERE ((created_dt >= $1::timestamp with time zone)) AND ((created_dt < $2::timestamp with time zone))
Planning time: 0.703 ms
Execution time: 4789.379 ms

์„œ๋ธŒ ์ฟผ๋ฆฌ์— ๋‚ ์งœ๋ฅผ ๋ฏธ๋ฆฌ ๊ณ„์‚ฐํ•ด์„œ ๋ฏธ๋ฆฌ ๋งŒ๋“ค์–ด์ง„ ๋ณ€์ˆ˜๋ฅผ ํ•„ํ„ฐ์— ์ „๋‹ฌํ•˜๋ผ๊ณ  ํ”Œ๋ž˜๋„ˆ์—๊ฒŒ ์ง€์‹œํ–ˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์ด ํžŒํŠธ๋Š” ์šฐ๋ฆฌ์—๊ฒŒ ํ›Œ๋ฅญํ•œ ๊ฒฐ๊ณผ๋ฅผ ์ฃผ์—ˆ๊ณ  ์š”์ฒญ์€ ๊ฑฐ์˜ 6๋ฐฐ ๋นจ๋ผ์กŒ์Šต๋‹ˆ๋‹ค!

์—ฌ๊ธฐ์„œ๋„ ์ฃผ์˜ํ•˜๋Š” ๊ฒƒ์ด ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค. ํ•˜์œ„ ์ฟผ๋ฆฌ์˜ ๋ฐ์ดํ„ฐ ์œ ํ˜•์€ ํ•„ํ„ฐ๋งํ•˜๋Š” ํ•„๋“œ์˜ ๋ฐ์ดํ„ฐ ์œ ํ˜•๊ณผ ๋™์ผํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ ‡์ง€ ์•Š์œผ๋ฉด ํ”Œ๋ž˜๋„ˆ๋Š” ์œ ํ˜•์ด ๋‹ค๋ฅด๊ธฐ ๋•Œ๋ฌธ์— ๋จผ์ € ๋ชจ๋“  ํ•ญ๋ชฉ์„ ๊ฐ€์ ธ์™€์•ผ ํ•œ๋‹ค๊ณ  ๊ฒฐ์ •ํ•ฉ๋‹ˆ๋‹ค. ๋ฐ์ดํ„ฐ๋ฅผ ์ˆ˜์ง‘ํ•˜๊ณ  ๋กœ์ปฌ๋กœ ํ•„ํ„ฐ๋งํ•ฉ๋‹ˆ๋‹ค.

๋‚ ์งœ ํ•„ํ„ฐ๋ฅผ ์›๋ž˜ ๊ฐ’์œผ๋กœ ๋˜๋Œ๋ ค ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

ํ”„๋ ˆ๋”” vs. Jsonb

์ผ๋ฐ˜์ ์œผ๋กœ ๋ถ€์šธ ํ•„๋“œ์™€ ๋‚ ์งœ๋Š” ์ด๋ฏธ ์ฟผ๋ฆฌ ์†๋„๋ฅผ ์ถฉ๋ถ„ํžˆ ๋†’์˜€์ง€๋งŒ ๋ฐ์ดํ„ฐ ์œ ํ˜•์ด ํ•˜๋‚˜ ๋” ๋‚จ์•„ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. ์†”์งํžˆ ๋งํ•ด์„œ ํ•„ํ„ฐ๋ง๊ณผ์˜ ์‹ธ์›€์€ ์•„์ง ๋๋‚˜์ง€ ์•Š์•˜์ง€๋งŒ ์—ฌ๊ธฐ์„œ๋„ ์„ฑ๊ณตํ–ˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ž˜์„œ ์ด๊ฒƒ์ด ์šฐ๋ฆฌ๊ฐ€ ํ•„ํ„ฐ๋ฅผ ํ†ต๊ณผํ•˜๋Š” ๋ฐฉ๋ฒ•์ž…๋‹ˆ๋‹ค. JSONB ํ•„๋“œ๋ฅผ ์›๊ฒฉ ์„œ๋ฒ„์— ๋ณด๋ƒ…๋‹ˆ๋‹ค.

explain analyze verbose
SELECT count(1)
FROM fdw_schema.table 
WHERE is_active is True
AND created_dt BETWEEN CURRENT_DATE - INTERVAL '7 month' 
AND CURRENT_DATE - INTERVAL '6 month'
AND meta @> '{"source":"test"}'::jsonb;

Aggregate  (cost=245463.60..245463.61 rows=1 width=8) (actual time=6727.589..6727.590 rows=1 loops=1)
  Output: count(1)
  ->  Foreign Scan on fdw_schema."table"  (cost=1100.00..245459.90 rows=1478 width=0) (actual time=16.213..6634.794 rows=1360025 loops=1)
        Output: "table".id, "table".is_active, "table".meta, "table".created_dt
        Filter: (("table".is_active IS TRUE) AND ("table".created_dt >= (('now'::cstring)::date - '7 mons'::interval)) AND ("table".created_dt <= ((('now'::cstring)::date)::timestamp with time zone - '6 mons'::interval)))
        Rows Removed by Filter: 619961
        Remote SQL: SELECT created_dt, is_active FROM fdw_schema.table WHERE ((meta @> '{"source": "test"}'::jsonb))
Planning time: 0.747 ms
Execution time: 6727.815 ms

ํ•„ํ„ฐ๋ง ์—ฐ์‚ฐ์ž ๋Œ€์‹  ํ•˜๋‚˜์˜ ์—ฐ์‚ฐ์ž๊ฐ€ ์žˆ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. JSONB ๋‹ค๋ฅธ. ์›๋ž˜์˜ 7์ดˆ๊ฐ€ ์•„๋‹Œ 29์ดˆ์ž…๋‹ˆ๋‹ค. ์ง€๊ธˆ๊นŒ์ง€ ์ด๊ฒƒ์€ ํ•„ํ„ฐ๋ฅผ ํ†ตํ•ด ํ•„ํ„ฐ๋ฅผ ์ „์†กํ•˜๋Š” ๋ฐ ์„ฑ๊ณตํ•œ ์œ ์ผํ•œ ์˜ต์…˜์ž…๋‹ˆ๋‹ค. JSONB ๊ทธ๋Ÿฌ๋‚˜ ์—ฌ๊ธฐ์„œ๋Š” ํ•œ ๊ฐ€์ง€ ์ œํ•œ ์‚ฌํ•ญ์„ ๊ณ ๋ คํ•˜๋Š” ๊ฒƒ์ด ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค. ์ฆ‰, ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋ฒ„์ „ 9.6์„ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์ง€๋งŒ 12์›” ๋ง๊นŒ์ง€ ๋งˆ์ง€๋ง‰ ํ…Œ์ŠคํŠธ๋ฅผ ์™„๋ฃŒํ•˜๊ณ  ๋ฒ„์ „ 10๋กœ ์ด๋™ํ•  ๊ณ„ํš์ž…๋‹ˆ๋‹ค. ์—…๋ฐ์ดํŠธํ•œ ํ›„์—๋Š” ์ด๊ฒƒ์ด ์–ด๋–ค ์˜ํ–ฅ์„ ๋ฏธ์ณค๋Š”์ง€ ์“ธ ๊ฒƒ์ž…๋‹ˆ๋‹ค. json_path, ์ƒˆ๋กœ์šด CTE ๋™์ž‘, ํ‘ธ์‹œ๋‹ค์šด(๋ฒ„์ „ XNUMX๋ถ€ํ„ฐ ์กด์žฌํ•จ) ๋“ฑ ๋งŽ์€ ํฌ๋ง์ด ์žˆ๋Š” ๋ณ€๊ฒฝ ์‚ฌํ•ญ์ด ์žˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. ๋นจ๋ฆฌ ํ•ด๋ณด๊ณ  ์‹ถ๋„ค์š”.

๊ทธ๋ฅผ ๋งˆ๋ฌด๋ฆฌ

๊ฐ ๋ณ€๊ฒฝ ์‚ฌํ•ญ์ด ์š”์ฒญ ์†๋„์— ๊ฐœ๋ณ„์ ์œผ๋กœ ์–ด๋–ค ์˜ํ–ฅ์„ ๋ฏธ์น˜๋Š”์ง€ ํ…Œ์ŠคํŠธํ–ˆ์Šต๋‹ˆ๋‹ค. ์ด์ œ ์„ธ ๊ฐœ์˜ ํ•„ํ„ฐ๊ฐ€ ๋ชจ๋‘ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์ž‘์„ฑ๋˜๋ฉด ์–ด๋–ค ์ผ์ด ๋ฐœ์ƒํ•˜๋Š”์ง€ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

explain analyze verbose
SELECT count(1)
FROM fdw_schema.table 
WHERE is_active = True
AND created_dt >= (SELECT CURRENT_DATE::timestamptz - INTERVAL '7 month') 
AND created_dt <(SELECT CURRENT_DATE::timestamptz - INTERVAL '6 month')
AND meta @> '{"source":"test"}'::jsonb;

Aggregate  (cost=322041.51..322041.52 rows=1 width=8) (actual time=2278.867..2278.867 rows=1 loops=1)
  Output: count(1)
  InitPlan 1 (returns $0)
    ->  Result  (cost=0.00..0.02 rows=1 width=8) (actual time=0.010..0.010 rows=1 loops=1)
          Output: ((('now'::cstring)::date)::timestamp with time zone - '7 mons'::interval)
  InitPlan 2 (returns $1)
    ->  Result  (cost=0.00..0.02 rows=1 width=8) (actual time=0.003..0.003 rows=1 loops=1)
          Output: ((('now'::cstring)::date)::timestamp with time zone - '6 mons'::interval)
  ->  Foreign Scan on fdw_schema."table"  (cost=100.02..322041.41 rows=25 width=0) (actual time=8.597..2153.809 rows=1360025 loops=1)
        Output: "table".id, "table".is_active, "table".meta, "table".created_dt
        Remote SQL: SELECT NULL FROM fdw_schema.table WHERE (is_active) AND ((created_dt >= $1::timestamp with time zone)) AND ((created_dt < $2::timestamp with time zone)) AND ((meta @> '{"source": "test"}'::jsonb))
Planning time: 0.820 ms
Execution time: 2279.087 ms

์˜ˆ, ์š”์ฒญ์ด ๋” ๋ณต์žกํ•ด ๋ณด์ž…๋‹ˆ๋‹ค. ์ด๋Š” ๊ฐ•์ œ ์š”๊ธˆ์ด์ง€๋งŒ ์‹คํ–‰ ์†๋„๋Š” 2์ดˆ๋กœ 10๋ฐฐ ์ด์ƒ ๋น ๋ฆ…๋‹ˆ๋‹ค! ๊ทธ๋ฆฌ๊ณ  ์šฐ๋ฆฌ๋Š” ์ƒ๋Œ€์ ์œผ๋กœ ์ž‘์€ ๋ฐ์ดํ„ฐ ์„ธํŠธ์— ๋Œ€ํ•œ ๊ฐ„๋‹จํ•œ ์ฟผ๋ฆฌ์— ๋Œ€ํ•ด ์ด์•ผ๊ธฐํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์‹ค์ œ ์š”์ฒญ์— ๋”ฐ๋ผ ์ตœ๋Œ€ ์ˆ˜๋ฐฑ ๋ฐฐ๊นŒ์ง€ ์ฆ๊ฐ€๋ฅผ ๋ฐ›์•˜์Šต๋‹ˆ๋‹ค.

์š”์•ฝํ•˜์ž๋ฉด: PostgreSQL์„ FDW์™€ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ ํ•ญ์ƒ ๋ชจ๋“  ํ•„ํ„ฐ๊ฐ€ ์›๊ฒฉ ์„œ๋ฒ„๋กœ ์ „์†ก๋˜๋Š”์ง€ ํ™•์ธํ•˜์„ธ์š”. ๊ทธ๋Ÿฌ๋ฉด ๋งŒ์กฑ์Šค๋Ÿฌ์šธ ๊ฒƒ์ž…๋‹ˆ๋‹ค... ์ ์–ด๋„ ๋‹ค๋ฅธ ์„œ๋ฒ„์˜ ํ…Œ์ด๋ธ”์„ ์กฐ์ธํ•  ๋•Œ๊นŒ์ง€๋Š” ๋ง์ด์ฃ . ๊ทธ๋Ÿฌ๋‚˜ ๊ทธ๊ฒƒ์€ ๋‹ค๋ฅธ ๊ธฐ์‚ฌ์— ๋Œ€ํ•œ ์ด์•ผ๊ธฐ์ž…๋‹ˆ๋‹ค.

๊ด€์‹ฌ์„ ๊ฐ€์ ธ์ฃผ์…”์„œ ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค! ๋Œ“๊ธ€๋กœ ์—ฌ๋Ÿฌ๋ถ„์˜ ๊ฒฝํ—˜์— ๋Œ€ํ•œ ์งˆ๋ฌธ, ์˜๊ฒฌ, ์ด์•ผ๊ธฐ๋ฅผ ๋“ฃ๊ณ  ์‹ถ์Šต๋‹ˆ๋‹ค.

์ถœ์ฒ˜ : habr.com

์ฝ”๋ฉ˜ํŠธ๋ฅผ ์ถ”๊ฐ€