PostgreSQL ์•ˆํ‹ฐํŒจํ„ด: ์‚ฌ์ „ ํžˆํŠธ ํ—ค๋น„ ์กฐ์ธ

์šฐ๋ฆฌ๋Š” "๊ฐ„๋‹จํ•ด ๋ณด์ด๋Š”" PostgreSQL ์ฟผ๋ฆฌ์˜ ์„ฑ๋Šฅ์„ ํ–ฅ์ƒ์‹œํ‚ค๊ธฐ ์œ„ํ•ด ์ž˜ ์•Œ๋ ค์ง€์ง€ ์•Š์€ ๋ฐฉ๋ฒ•์— ๋Œ€ํ•œ ์—ฐ๊ตฌ์— ์ „๋…ํ•˜๋Š” ์ผ๋ จ์˜ ๊ธฐ์‚ฌ๋ฅผ ๊ณ„์†ํ•ฉ๋‹ˆ๋‹ค.

์ œ๊ฐ€ JOIN์„ ๋ณ„๋กœ ์•ˆ ์ข‹์•„ํ•œ๋‹ค๊ณ  ์ƒ๊ฐํ•˜์ง€ ๋งˆ์„ธ์š”... :)

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

PostgreSQL ์•ˆํ‹ฐํŒจํ„ด: ์‚ฌ์ „ ํžˆํŠธ ํ—ค๋น„ ์กฐ์ธ

PostgreSQL 12๋ถ€ํ„ฐ ์•„๋ž˜์— ์„ค๋ช…๋œ ์ผ๋ถ€ ์ƒํ™ฉ์€ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์ด์œ ๋กœ ์•ฝ๊ฐ„ ๋‹ค๋ฅด๊ฒŒ ์žฌํ˜„๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ธฐ๋ณธ ๋น„๊ตฌ์ฒดํ™” CTE. ์ด ๋™์ž‘์€ ํ‚ค๋ฅผ ์ง€์ •ํ•˜์—ฌ ๋˜๋Œ๋ฆด ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. MATERIALIZED.

์ œํ•œ๋œ ์–ดํœ˜์— ๋‹ด๊ธด ๋งŽ์€ "์‚ฌ์‹ค"

๋งค์šฐ ์‹ค์ œ์ ์ธ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ž‘์—…์„ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. ๋ชฉ๋ก์„ ํ‘œ์‹œํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๋“ค์–ด์˜ค๋Š” ๋ฉ”์‹œ์ง€ ๋˜๋Š” ๋ณด๋‚ธ ์‚ฌ๋žŒ๊ณผ์˜ ํ™œ์„ฑ ์ž‘์—…:

25.01 | ะ˜ะฒะฐะฝะพะฒ ะ˜.ะ˜. | ะŸะพะดะณะพั‚ะพะฒะธั‚ัŒ ะพะฟะธัะฐะฝะธะต ะฝะพะฒะพะณะพ ะฐะปะณะพั€ะธั‚ะผะฐ.
22.01 | ะ˜ะฒะฐะฝะพะฒ ะ˜.ะ˜. | ะะฐะฟะธัะฐั‚ัŒ ัั‚ะฐั‚ัŒัŽ ะฝะฐ ะฅะฐะฑั€: ะถะธะทะฝัŒ ะฑะตะท JOIN.
20.01 | ะŸะตั‚ั€ะพะฒ ะŸ.ะŸ. | ะŸะพะผะพั‡ัŒ ะพะฟั‚ะธะผะธะทะธั€ะพะฒะฐั‚ัŒ ะทะฐะฟั€ะพั.
18.01 | ะ˜ะฒะฐะฝะพะฒ ะ˜.ะ˜. | ะะฐะฟะธัะฐั‚ัŒ ัั‚ะฐั‚ัŒัŽ ะฝะฐ ะฅะฐะฑั€: JOIN ั ัƒั‡ะตั‚ะพะผ ั€ะฐัะฟั€ะตะดะตะปะตะฝะธั ะดะฐะฝะฝั‹ั….
16.01 | ะŸะตั‚ั€ะพะฒ ะŸ.ะŸ. | ะŸะพะผะพั‡ัŒ ะพะฟั‚ะธะผะธะทะธั€ะพะฒะฐั‚ัŒ ะทะฐะฟั€ะพั.

์ถ”์ƒ์ ์ธ ์„ธ๊ณ„์—์„œ ์ž‘์—… ์ž‘์„ฑ์ž๋Š” ์กฐ์ง์˜ ๋ชจ๋“  ์ง์›์—๊ฒŒ ๊ท ๋“ฑํ•˜๊ฒŒ ๋ถ„๋ฐฐ๋˜์–ด์•ผ ํ•˜์ง€๋งŒ ์‹ค์ œ๋กœ๋Š” ์ผ๋ฐ˜์ ์œผ๋กœ ์ž‘์—…์€ ์ƒ๋‹นํžˆ ์ œํ•œ๋œ ์ˆ˜์˜ ์‚ฌ๋žŒ๋“ค์—๊ฒŒ์„œ ๋‚˜์˜ต๋‹ˆ๋‹ค. - ์ƒ์œ„ ๊ณ„์ธต์˜ "๊ฒฝ์˜์ง„" ๋˜๋Š” ์ธ์ ‘ ๋ถ€์„œ(๋ถ„์„๊ฐ€, ๋””์ž์ด๋„ˆ, ๋งˆ์ผ€ํŒ… ๋“ฑ)์˜ "ํ•˜์ฒญ์—…์ฒด".

1000๋ช…์œผ๋กœ ๊ตฌ์„ฑ๋œ ์šฐ๋ฆฌ ์กฐ์ง์—์„œ ์˜ค์ง 20๋ช…์˜ ์ž‘์„ฑ์ž(๋ณดํ†ต ๊ทธ๋ณด๋‹ค ๋” ์ ์Œ)๋งŒ์ด ๊ฐ ํŠน์ • ์ˆ˜ํ–‰์ž์— ๋Œ€ํ•œ ์ž‘์—…์„ ์„ค์ •ํ•˜๊ณ  ์ด ์ฃผ์ œ ์ง€์‹์„ ํ™œ์šฉํ•˜์ž"์ „ํ†ต์ ์ธ" ์ฟผ๋ฆฌ ์†๋„๋ฅผ ๋†’์ด๊ธฐ ์œ„ํ•ด.

์Šคํฌ๋ฆฝํŠธ ์ƒ์„ฑ๊ธฐ

-- ัะพั‚ั€ัƒะดะฝะธะบะธ
CREATE TABLE person AS
SELECT
  id
, repeat(chr(ascii('a') + (id % 26)), (id % 32) + 1) "name"
, '2000-01-01'::date - (random() * 1e4)::integer birth_date
FROM
  generate_series(1, 1000) id;

ALTER TABLE person ADD PRIMARY KEY(id);

-- ะทะฐะดะฐั‡ะธ ั ัƒะบะฐะทะฐะฝะฝั‹ะผ ั€ะฐัะฟั€ะตะดะตะปะตะฝะธะตะผ
CREATE TABLE task AS
WITH aid AS (
  SELECT
    id
  , array_agg((random() * 999)::integer + 1) aids
  FROM
    generate_series(1, 1000) id
  , generate_series(1, 20)
  GROUP BY
    1
)
SELECT
  *
FROM
  (
    SELECT
      id
    , '2020-01-01'::date - (random() * 1e3)::integer task_date
    , (random() * 999)::integer + 1 owner_id
    FROM
      generate_series(1, 100000) id
  ) T
, LATERAL(
    SELECT
      aids[(random() * (array_length(aids, 1) - 1))::integer + 1] author_id
    FROM
      aid
    WHERE
      id = T.owner_id
    LIMIT 1
  ) a;

ALTER TABLE task ADD PRIMARY KEY(id);
CREATE INDEX ON task(owner_id, task_date);
CREATE INDEX ON task(author_id);

ํŠน์ • ์‹คํ–‰๊ธฐ์— ๋Œ€ํ•œ ๋งˆ์ง€๋ง‰ 100๊ฐœ ์ž‘์—…์„ ๋ณด์—ฌ๋“œ๋ฆฌ๊ฒ ์Šต๋‹ˆ๋‹ค.

SELECT
  task.*
, person.name
FROM
  task
LEFT JOIN
  person
    ON person.id = task.author_id
WHERE
  owner_id = 777
ORDER BY
  task_date DESC
LIMIT 100;

PostgreSQL ์•ˆํ‹ฐํŒจํ„ด: ์‚ฌ์ „ ํžˆํŠธ ํ—ค๋น„ ์กฐ์ธ
[explain.tensor.ru ์ฐธ์กฐ]

๊ทธ๊ฒƒ์€ ๋ฐํ˜€ ๊ทธ ์ด ์‹œ๊ฐ„์˜ 1/3 ๋ฐ ํŒ๋…๊ฐ’์˜ 3/4 ๋ฐ์ดํ„ฐ ํŽ˜์ด์ง€๋Š” ๊ฐ ์ถœ๋ ฅ ์ž‘์—…์— ๋Œ€ํ•ด ์ž‘์„ฑ์ž๋ฅผ 100๋ฒˆ ๊ฒ€์ƒ‰ํ•˜๊ธฐ ์œ„ํ•ด ๋งŒ๋“ค์–ด์กŒ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์šฐ๋ฆฌ๋Š” ์ด ์ˆ˜๋ฐฑ ๋ช… ์ค‘ ๋‹จ์ง€ 20๊ฐ€์ง€ ๋‹ค๋ฅธ - ์ด ์ง€์‹์„ ํ™œ์šฉํ•˜๋Š” ๊ฒƒ์ด ๊ฐ€๋Šฅํ•œ๊ฐ€์š”?

hstore-์‚ฌ์ „

์‚ฌ์šฉํ•˜์ž hstore ์œ ํ˜• "์‚ฌ์ „" ํ‚ค-๊ฐ’์„ ์ƒ์„ฑํ•˜๋ ค๋ฉด ๋‹ค์Œ์„ ์ˆ˜ํ–‰ํ•˜์„ธ์š”.

CREATE EXTENSION hstore

์ž‘์„ฑ์ž์˜ ID์™€ ์ด๋ฆ„์„ ์‚ฌ์ „์— ์ž…๋ ฅํ•˜๋ฉด ์ด ํ‚ค๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ถ”์ถœํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

-- ั„ะพั€ะผะธั€ัƒะตะผ ั†ะตะปะตะฒัƒัŽ ะฒั‹ะฑะพั€ะบัƒ
WITH T AS (
  SELECT
    *
  FROM
    task
  WHERE
    owner_id = 777
  ORDER BY
    task_date DESC
  LIMIT 100
)
-- ั„ะพั€ะผะธั€ัƒะตะผ ัะปะพะฒะฐั€ัŒ ะดะปั ัƒะฝะธะบะฐะปัŒะฝั‹ั… ะทะฝะฐั‡ะตะฝะธะน
, dict AS (
  SELECT
    hstore( -- hstore(keys::text[], values::text[])
      array_agg(id)::text[]
    , array_agg(name)::text[]
    )
  FROM
    person
  WHERE
    id = ANY(ARRAY(
      SELECT DISTINCT
        author_id
      FROM
        T
    ))
)
-- ะฟะพะปัƒั‡ะฐะตะผ ัะฒัะทะฐะฝะฝั‹ะต ะทะฝะฐั‡ะตะฝะธั ัะปะพะฒะฐั€ั
SELECT
  *
, (TABLE dict) -> author_id::text -- hstore -> key
FROM
  T;

PostgreSQL ์•ˆํ‹ฐํŒจํ„ด: ์‚ฌ์ „ ํžˆํŠธ ํ—ค๋น„ ์กฐ์ธ
[explain.tensor.ru ์ฐธ์กฐ]

๊ฐœ์ธ์— ๋Œ€ํ•œ ์ •๋ณด๋ฅผ ์–ป๋Š” ๋ฐ ์†Œ๋น„๋จ 2๋ฐฐ ๋” ์ ์€ ์‹œ๊ฐ„๊ณผ 7๋ฐฐ ๋” ์ ์€ ๋ฐ์ดํ„ฐ ์ฝ๊ธฐ! "์–ดํœ˜" ์™ธ์—๋„ ์ด๋Ÿฌํ•œ ๊ฒฐ๊ณผ๋ฅผ ์–ป๋Š” ๋ฐ ๋„์›€์ด ๋œ ๊ฒƒ์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค. ๋Œ€๋Ÿ‰ ๊ธฐ๋ก ๊ฒ€์ƒ‰ ๋‹ค์Œ์„ ์‚ฌ์šฉํ•˜์—ฌ ๋‹จ์ผ ํŒจ์Šค๋กœ ํ…Œ์ด๋ธ”์—์„œ = ANY(ARRAY(...)).

ํ…Œ์ด๋ธ” ํ•ญ๋ชฉ: ์ง๋ ฌํ™” ๋ฐ ์—ญ์ง๋ ฌํ™”

ํ•˜์ง€๋งŒ ํ•˜๋‚˜์˜ ํ…์ŠคํŠธ ํ•„๋“œ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์‚ฌ์ „์˜ ์ „์ฒด ํ•ญ๋ชฉ์„ ์ €์žฅํ•ด์•ผ ํ•œ๋‹ค๋ฉด ์–ด๋–ป๊ฒŒ ๋ ๊นŒ์š”? ์ด ๊ฒฝ์šฐ PostgreSQL์˜ ๊ธฐ๋Šฅ์ด ๋„์›€์ด ๋  ๊ฒƒ์ž…๋‹ˆ๋‹ค. ํ…Œ์ด๋ธ” ํ•ญ๋ชฉ์„ ๋‹จ์ผ ๊ฐ’์œผ๋กœ ์ฒ˜๋ฆฌ:

...
, dict AS (
  SELECT
    hstore(
      array_agg(id)::text[]
    , array_agg(p)::text[] -- ะผะฐะณะธั #1
    )
  FROM
    person p
  WHERE
    ...
)
SELECT
  *
, (((TABLE dict) -> author_id::text)::person).* -- ะผะฐะณะธั #2
FROM
  T;

์—ฌ๊ธฐ์„œ ๋ฌด์Šจ ์ผ์ด ์ผ์–ด๋‚˜๊ณ  ์žˆ๋Š”์ง€ ์‚ดํŽด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

  1. ์šฐ๋ฆฌ๋Š” ์ „์ฒด ๊ฐœ์ธ ํ…Œ์ด๋ธ” ํ•ญ๋ชฉ์— ๋Œ€ํ•œ ๋ณ„์นญ์ธ p ๊ทธ๋ฆฌ๊ณ  ๊ทธ๊ฒƒ๋“ค์„ ๋ฐฐ์—ด๋กœ ๋ชจ์•˜์Šต๋‹ˆ๋‹ค.
  2. ์ด ๋…น์Œ ๋ฐฐ์—ด์ด ๋‹ค์‹œ ์ „์†ก๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ํ…์ŠคํŠธ ๋ฌธ์ž์—ด ๋ฐฐ์—ด(person[]::text[])๋กœ ๋ณ€ํ™˜ํ•˜์—ฌ hstore ์‚ฌ์ „์— ๊ฐ’ ๋ฐฐ์—ด๋กœ ๋ฐฐ์น˜ํ•ฉ๋‹ˆ๋‹ค.
  3. ๋‹น์‚ฌ๋Š” ๊ด€๋ จ ๊ธฐ๋ก์„ ์ˆ˜์‹ ํ•˜๋ฉด ํ‚ค๋กœ ์‚ฌ์ „์—์„œ ๊ฐ€์ ธ์˜ด ํ…์ŠคํŠธ ๋ฌธ์ž์—ด๋กœ.
  4. ํ…์ŠคํŠธ๊ฐ€ ํ•„์š”ํ•ด์š” ํ…Œ์ด๋ธ” ์œ ํ˜• ๊ฐ’์œผ๋กœ ๋ณ€ํ™˜ ์‚ฌ๋žŒ(๊ฐ ํ…Œ์ด๋ธ”์— ๋Œ€ํ•ด ๋™์ผํ•œ ์ด๋ฆ„์˜ ์œ ํ˜•์ด ์ž๋™์œผ๋กœ ์ƒ์„ฑ๋ฉ๋‹ˆ๋‹ค).
  5. ๋‹ค์Œ์„ ์‚ฌ์šฉํ•˜์—ฌ ์ž…๋ ฅ๋œ ๋ ˆ์ฝ”๋“œ๋ฅผ ์—ด๋กœ "ํ™•์žฅ"ํ•ฉ๋‹ˆ๋‹ค. (...).*.

JSON ์‚ฌ์ „

๊ทธ๋Ÿฌ๋‚˜ ์œ„์—์„œ ์ ์šฉํ•œ ํŠธ๋ฆญ์€ "์บ์ŠคํŒ…"์„ ์ˆ˜ํ–‰ํ•˜๋Š” ํ•ด๋‹น ํ…Œ์ด๋ธ” ์œ ํ˜•์ด ์—†์œผ๋ฉด ์ž‘๋™ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋˜‘๊ฐ™์€ ์ƒํ™ฉ์ด ๋ฐœ์ƒํ•˜๋ฉฐ, ์ด๋ฅผ ์‚ฌ์šฉํ•˜๋ ค๊ณ  ํ•˜๋ฉด "์‹ค์ œ" ํ…Œ์ด๋ธ”์ด ์•„๋‹Œ CTE ํ–‰.

์ด ๊ฒฝ์šฐ ๊ทธ๋“ค์€ ์šฐ๋ฆฌ๋ฅผ ๋„์šธ ๊ฒƒ์ž…๋‹ˆ๋‹ค json ์ž‘์—…์„ ์œ„ํ•œ ํ•จ์ˆ˜:

...
, p AS ( -- ัั‚ะพ ัƒะถะต CTE
  SELECT
    *
  FROM
    person
  WHERE
    ...
)
, dict AS (
  SELECT
    json_object( -- ั‚ะตะฟะตั€ัŒ ัั‚ะพ ัƒะถะต json
      array_agg(id)::text[]
    , array_agg(row_to_json(p))::text[] -- ะธ ะฒะฝัƒั‚ั€ะธ json ะดะปั ะบะฐะถะดะพะน ัั‚ั€ะพะบะธ
    )
  FROM
    p
)
SELECT
  *
FROM
  T
, LATERAL(
    SELECT
      *
    FROM
      json_to_record(
        ((TABLE dict) ->> author_id::text)::json -- ะธะทะฒะปะตะบะปะธ ะธะท ัะปะพะฒะฐั€ั ะบะฐะบ json
      ) AS j(name text, birth_date date) -- ะทะฐะฟะพะปะฝะธะปะธ ะฝัƒะถะฝัƒัŽ ะฝะฐะผ ัั‚ั€ัƒะบั‚ัƒั€ัƒ
  ) j;

๋Œ€์ƒ ๊ตฌ์กฐ๋ฅผ ์„ค๋ช…ํ•  ๋•Œ ์†Œ์Šค ๋ฌธ์ž์—ด์˜ ๋ชจ๋“  ํ•„๋“œ๋ฅผ ๋‚˜์—ดํ•  ์ˆ˜๋Š” ์—†๊ณ  ์‹ค์ œ๋กœ ํ•„์š”ํ•œ ํ•„๋“œ๋งŒ ๋‚˜์—ดํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ์ ์— ์œ ์˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. "๊ธฐ๋ณธ" ํ…Œ์ด๋ธ”์ด ์žˆ๋Š” ๊ฒฝ์šฐ ๋‹ค์Œ ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ๋” ์ข‹์Šต๋‹ˆ๋‹ค. json_populate_record.

์šฐ๋ฆฌ๋Š” ์—ฌ์ „ํžˆ ์‚ฌ์ „์— ํ•œ ๋ฒˆ ์ ‘๊ทผํ•˜์ง€๋งŒ json-[de]์ง๋ ฌํ™” ๋น„์šฉ์ด ์ƒ๋‹นํžˆ ๋†’์Šต๋‹ˆ๋‹ค.๋”ฐ๋ผ์„œ "์ •์งํ•œ" CTE ์Šค์บ” ๊ฒฐ๊ณผ๊ฐ€ ๋” ๋‚˜์  ๊ฒฝ์šฐ์—๋งŒ ์ด ๋ฐฉ๋ฒ•์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ํ•ฉ๋ฆฌ์ ์ž…๋‹ˆ๋‹ค.

ํ…Œ์ŠคํŠธ ์„ฑ๋Šฅ

๊ทธ๋ž˜์„œ ์šฐ๋ฆฌ๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ์‚ฌ์ „์œผ๋กœ ์ง๋ ฌํ™”ํ•˜๋Š” ๋‘ ๊ฐ€์ง€ ๋ฐฉ๋ฒ•์„ ์–ป์—ˆ์Šต๋‹ˆ๋‹ค. hstore/json_object. ๋˜ํ•œ ํ‚ค์™€ ๊ฐ’์˜ ๋ฐฐ์—ด ์ž์ฒด๋Š” ๋‚ด๋ถ€ ๋˜๋Š” ์™ธ๋ถ€ ํ…์ŠคํŠธ๋กœ ๋ณ€ํ™˜ํ•˜๋Š” ๋‘ ๊ฐ€์ง€ ๋ฐฉ๋ฒ•์œผ๋กœ ์ƒ์„ฑํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. array_agg(i::ํ…์ŠคํŠธ) / array_agg(i)::ํ…์ŠคํŠธ[].

์ˆœ์ˆ˜ ํ•ฉ์„ฑ ์˜ˆ์ œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋‹ค์–‘ํ•œ ์œ ํ˜•์˜ ์ง๋ ฌํ™”์˜ ํšจ์œจ์„ฑ์„ ํ™•์ธํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. ๋‹ค๋ฅธ ์ˆ˜์˜ ํ‚ค๋ฅผ ์ง๋ ฌํ™”:

WITH dict AS (
  SELECT
    hstore(
      array_agg(i::text)
    , array_agg(i::text)
    )
  FROM
    generate_series(1, ...) i
)
TABLE dict;

ํ‰๊ฐ€ ์Šคํฌ๋ฆฝํŠธ: ์ง๋ ฌํ™”

WITH T AS (
  SELECT
    *
  , (
      SELECT
        regexp_replace(ea[array_length(ea, 1)], '^Execution Time: (d+.d+) ms$', '1')::real et
      FROM
        (
          SELECT
            array_agg(el) ea
          FROM
            dblink('port= ' || current_setting('port') || ' dbname=' || current_database(), $$
              explain analyze
              WITH dict AS (
                SELECT
                  hstore(
                    array_agg(i::text)
                  , array_agg(i::text)
                  )
                FROM
                  generate_series(1, $$ || (1 << v) || $$) i
              )
              TABLE dict
            $$) T(el text)
        ) T
    ) et
  FROM
    generate_series(0, 19) v
  ,   LATERAL generate_series(1, 7) i
  ORDER BY
    1, 2
)
SELECT
  v
, avg(et)::numeric(32,3)
FROM
  T
GROUP BY
  1
ORDER BY
  1;

PostgreSQL ์•ˆํ‹ฐํŒจํ„ด: ์‚ฌ์ „ ํžˆํŠธ ํ—ค๋น„ ์กฐ์ธ

PostgreSQL 11์—์„œ๋Š” ์ตœ๋Œ€ ์•ฝ 2^12๊ฐœ ํ‚ค์˜ ์‚ฌ์ „ ํฌ๊ธฐ JSON์œผ๋กœ์˜ ์ง๋ ฌํ™”์—๋Š” ์‹œ๊ฐ„์ด ๋œ ๊ฑธ๋ฆฝ๋‹ˆ๋‹ค. ์ด ๊ฒฝ์šฐ ๊ฐ€์žฅ ํšจ๊ณผ์ ์ธ ๋ฐฉ๋ฒ•์€ json_object์™€ "๋‚ด๋ถ€" ์œ ํ˜• ๋ณ€ํ™˜์„ ์กฐํ•ฉํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. array_agg(i::text).

์ด์ œ ๊ฐ ํ‚ค์˜ ๊ฐ’์„ 8๋ฒˆ ์ฝ์–ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. ๊ฒฐ๊ตญ ์‚ฌ์ „์— ์•ก์„ธ์Šคํ•˜์ง€ ์•Š๋Š”๋‹ค๋ฉด ์‚ฌ์ „์ด ์™œ ํ•„์š”ํ•œ๊ฐ€์š”?

ํ‰๊ฐ€ ์Šคํฌ๋ฆฝํŠธ: ์‚ฌ์ „์—์„œ ์ฝ๊ธฐ

WITH T AS (
  SELECT
    *
  , (
      SELECT
        regexp_replace(ea[array_length(ea, 1)], '^Execution Time: (d+.d+) ms$', '1')::real et
      FROM
        (
          SELECT
            array_agg(el) ea
          FROM
            dblink('port= ' || current_setting('port') || ' dbname=' || current_database(), $$
              explain analyze
              WITH dict AS (
                SELECT
                  json_object(
                    array_agg(i::text)
                  , array_agg(i::text)
                  )
                FROM
                  generate_series(1, $$ || (1 << v) || $$) i
              )
              SELECT
                (TABLE dict) -> (i % ($$ || (1 << v) || $$) + 1)::text
              FROM
                generate_series(1, $$ || (1 << (v + 3)) || $$) i
            $$) T(el text)
        ) T
    ) et
  FROM
    generate_series(0, 19) v
  , LATERAL generate_series(1, 7) i
  ORDER BY
    1, 2
)
SELECT
  v
, avg(et)::numeric(32,3)
FROM
  T
GROUP BY
  1
ORDER BY
  1;

PostgreSQL ์•ˆํ‹ฐํŒจํ„ด: ์‚ฌ์ „ ํžˆํŠธ ํ—ค๋น„ ์กฐ์ธ

๊ทธ๋ฆฌ๊ณ ... ์ด๋ฏธ ๋Œ€๋žต 2^6 ํ‚ค๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด json ์‚ฌ์ „์—์„œ ์ฝ๋Š” ๊ฒƒ์ด ์—ฌ๋Ÿฌ ๋ฒˆ ์†์‹ค๋˜๊ธฐ ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค. hstore์—์„œ ์ฝ๋Š” ์ค‘, jsonb์˜ ๊ฒฝ์šฐ 2^9์—์„œ๋„ ๊ฐ™์€ ์ผ์ด ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.

์ตœ์ข… ๊ฒฐ๋ก :

  • ๊ผญ ํ•ด์•ผ ํ•œ๋‹ค๋ฉด ์—ฌ๋Ÿฌ ๋ฐ˜๋ณต ๋ ˆ์ฝ”๋“œ๋กœ JOIN โ€” ํ…Œ์ด๋ธ”์˜ "์‚ฌ์ „"์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ๋” ์ข‹์Šต๋‹ˆ๋‹ค
  • ๋‹น์‹ ์˜ ์‚ฌ์ „์ด ์˜ˆ์ƒ๋œ๋‹ค๋ฉด ์ž‘์•„์„œ ๋งŽ์ด ์ฝ์ง€ ๋ชปํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค - json[b]๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ๋‹ค๋ฅธ ๋ชจ๋“  ๊ฒฝ์šฐ์— hstore + array_agg(i::text) ๋” ํšจ๊ณผ์ ์ผ ๊ฒƒ์ด๋‹ค

์ถœ์ฒ˜ : habr.com

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