PostgreSQL์—์„œ EAV๋ฅผ JSONB๋กœ ๊ต์ฒด

TL; DR: JSONB๋Š” ์ฟผ๋ฆฌ ์„ฑ๋Šฅ์„ ์ €ํ•˜์‹œํ‚ค์ง€ ์•Š๊ณ  ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์Šคํ‚ค๋งˆ ๊ฐœ๋ฐœ์„ ํฌ๊ฒŒ ๋‹จ์ˆœํ™”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์†Œ๊ฐœ

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

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

EAV ํŒจํ„ด์„ ๊ณ ๋ คํ•˜์‹ญ์‹œ์˜ค(์—”ํ‹ฐํ‹ฐ ์†์„ฑ ๊ฐ’), ๊ฝค ์ž์ฃผ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค. ํ•œ ํ…Œ์ด๋ธ”์—๋Š” ์—”ํ„ฐํ‹ฐ(๋ ˆ์ฝ”๋“œ)๊ฐ€ ํฌํ•จ๋˜์–ด ์žˆ๊ณ , ๋‹ค๋ฅธ ํ…Œ์ด๋ธ”์—๋Š” ์†์„ฑ ์ด๋ฆ„(์†์„ฑ)์ด ํฌํ•จ๋˜์–ด ์žˆ์œผ๋ฉฐ, ์„ธ ๋ฒˆ์งธ ํ…Œ์ด๋ธ”์—๋Š” ์—”ํ„ฐํ‹ฐ๋ฅผ ํ•ด๋‹น ํŠน์„ฑ๊ณผ ์—ฐ๊ฒฐํ•˜๊ณ  ํ˜„์žฌ ์—”ํ„ฐํ‹ฐ์— ๋Œ€ํ•œ ํ•ด๋‹น ํŠน์„ฑ ๊ฐ’์ด ํฌํ•จ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ๋‹ค์–‘ํ•œ ๊ฐœ์ฒด์— ๋Œ€ํ•ด ๋‹ค์–‘ํ•œ ์†์„ฑ ์ง‘ํ•ฉ์„ ๊ฐ€์งˆ ์ˆ˜ ์žˆ๊ณ  ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๊ตฌ์กฐ๋ฅผ ๋ณ€๊ฒฝํ•˜์ง€ ์•Š๊ณ ๋„ ์ฆ‰์‹œ ์†์„ฑ์„ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๊ทธ๋Ÿฌ๋‚˜ EVA ์ ‘๊ทผ ๋ฐฉ์‹์— ๋ช‡ ๊ฐ€์ง€ ๋‹จ์ ์ด ์—†์—ˆ๋‹ค๋ฉด ์ €๋Š” ์ด ๊ฒŒ์‹œ๋ฌผ์„ ์ž‘์„ฑํ•˜์ง€ ์•Š์•˜์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด ๊ฐ๊ฐ 1๊ฐœ์˜ ์†์„ฑ์„ ๊ฐ€์ง„ ํ•˜๋‚˜ ์ด์ƒ์˜ ์—”ํ„ฐํ‹ฐ๋ฅผ ์–ป์œผ๋ ค๋ฉด ์ฟผ๋ฆฌ์— 2๊ฐœ์˜ ์กฐ์ธ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ์ฒซ ๋ฒˆ์งธ๋Š” ์†์„ฑ ํ…Œ์ด๋ธ”๊ณผ์˜ ์กฐ์ธ์ด๊ณ  ๋‘ ๋ฒˆ์งธ๋Š” ๊ฐ’ ํ…Œ์ด๋ธ”๊ณผ์˜ ์กฐ์ธ์ž…๋‹ˆ๋‹ค. ์—”ํ„ฐํ‹ฐ์— 2๊ฐœ์˜ ์†์„ฑ์ด ์žˆ๋Š” ๊ฒฝ์šฐ 4๊ฐœ์˜ ์กฐ์ธ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค! ๋˜ํ•œ ๋ชจ๋“  ์†์„ฑ์€ ์ผ๋ฐ˜์ ์œผ๋กœ ๋ฌธ์ž์—ด๋กœ ์ €์žฅ๋˜๋ฏ€๋กœ ๊ฒฐ๊ณผ์™€ WHERE ์ ˆ ๋ชจ๋‘์— ๋Œ€ํ•ด ์œ ํ˜• ์บ์ŠคํŒ…์ด ์ˆ˜ํ–‰๋ฉ๋‹ˆ๋‹ค. ์ฟผ๋ฆฌ๋ฅผ ๋งŽ์ด ์ž‘์„ฑํ•˜๋ฉด ๋ฆฌ์†Œ์Šค ์‚ฌ์šฉ ์ธก๋ฉด์—์„œ ์ƒ๋‹นํžˆ ๋‚ญ๋น„๊ฐ€ ๋ฉ๋‹ˆ๋‹ค.

์ด๋Ÿฌํ•œ ๋ช…๋ฐฑํ•œ ๋‹จ์ ์—๋„ ๋ถˆ๊ตฌํ•˜๊ณ  EAV๋Š” ์ด๋Ÿฌํ•œ ์œ ํ˜•์˜ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๋Š” ๋ฐ ์˜ค๋žซ๋™์•ˆ ์‚ฌ์šฉ๋˜์–ด ์™”์Šต๋‹ˆ๋‹ค. ์ด๋Š” ํ”ผํ•  ์ˆ˜ ์—†๋Š” ๋‹จ์ ์ด์—ˆ๊ณ  ๋” ๋‚˜์€ ๋Œ€์•ˆ์€ ์—†์—ˆ์Šต๋‹ˆ๋‹ค.
๊ทธ๋Ÿฐ๋ฐ PostgreSQL์— ์ƒˆ๋กœ์šด "๊ธฐ์ˆ "์ด ๋“ฑ์žฅํ–ˆ์Šต๋‹ˆ๋‹ค.

PostgreSQL 9.4๋ถ€ํ„ฐ JSON ๋ฐ”์ด๋„ˆ๋ฆฌ ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•˜๊ธฐ ์œ„ํ•ด JSONB ๋ฐ์ดํ„ฐ ์œ ํ˜•์ด ์ถ”๊ฐ€๋˜์—ˆ์Šต๋‹ˆ๋‹ค. JSON์„ ์ด ํ˜•์‹์œผ๋กœ ์ €์žฅํ•˜๋ฉด ์ผ๋ฐ˜์ ์œผ๋กœ ์ผ๋ฐ˜ ํ…์ŠคํŠธ JSON๋ณด๋‹ค ์•ฝ๊ฐ„ ๋” ๋งŽ์€ ๊ณต๊ฐ„๊ณผ ์‹œ๊ฐ„์ด ์†Œ์š”๋˜์ง€๋งŒ ์ž‘์—… ์ˆ˜ํ–‰ ์†๋„๋Š” ํ›จ์”ฌ ๋น ๋ฆ…๋‹ˆ๋‹ค. JSONB๋Š” ์ธ๋ฑ์‹ฑ๋„ ์ง€์›ํ•˜๋ฏ€๋กœ ์ฟผ๋ฆฌ ์†๋„๊ฐ€ ๋”์šฑ ๋นจ๋ผ์ง‘๋‹ˆ๋‹ค.

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

ํ…Œ์ŠคํŠธ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์„ค์ •

์ด ๋น„๊ต๋ฅผ ์œ„ํ•ด $9.5 ๋นŒ๋“œ์— PostgreSQL 80๋ฅผ ์ƒˆ๋กœ ์„ค์น˜ํ•˜์—ฌ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ์ƒ์„ฑํ–ˆ์Šต๋‹ˆ๋‹ค. DigitalOcean ์šฐ๋ถ„ํˆฌ 14.04. postgresql.conf์—์„œ ์ผ๋ถ€ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์„ค์ •ํ•œ ํ›„ ๋‹ค์Œ์„ ์‹คํ–‰ํ–ˆ์Šต๋‹ˆ๋‹ค. ์ด psql์„ ์‚ฌ์šฉํ•˜๋Š” ์Šคํฌ๋ฆฝํŠธ. ๋ฐ์ดํ„ฐ๋ฅผ EAV ํ˜•์‹์œผ๋กœ ํ‘œ์‹œํ•˜๊ธฐ ์œ„ํ•ด ๋‹ค์Œ ํ…Œ์ด๋ธ”์ด ์ƒ์„ฑ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

CREATE TABLE entity ( 
  id           SERIAL PRIMARY KEY, 
  name         TEXT, 
  description  TEXT
);
CREATE TABLE entity_attribute (
  id          SERIAL PRIMARY KEY, 
  name        TEXT
);
CREATE TABLE entity_attribute_value (
  id                  SERIAL PRIMARY KEY, 
  entity_id           INT    REFERENCES entity(id), 
  entity_attribute_id INT    REFERENCES entity_attribute(id), 
  value               TEXT
);

์•„๋ž˜๋Š” ๋™์ผํ•œ ๋ฐ์ดํ„ฐ๊ฐ€ ์ €์žฅ๋˜์ง€๋งŒ JSONB ์œ ํ˜• ์—ด์— ์†์„ฑ์ด ์žˆ๋Š” ํ…Œ์ด๋ธ”์ž…๋‹ˆ๋‹ค. ์†์„ฑ.

CREATE TABLE entity_jsonb (
  id          SERIAL PRIMARY KEY, 
  name        TEXT, 
  description TEXT,
  properties  JSONB
);

ํ›จ์”ฌ ๋” ๋‹จ์ˆœํ•ด ๋ณด์ด์ง€ ์•Š๋‚˜์š”? ๊ทธ๋Ÿฐ ๋‹ค์Œ ์—”ํ„ฐํ‹ฐ ํ…Œ์ด๋ธ”(์‹ค์žฌ & ์—”ํ„ฐํ‹ฐ_jsonb) 10๋งŒ ๊ฐœ์˜ ๋ ˆ์ฝ”๋“œ์— ๋”ฐ๋ผ EAV ํŒจํ„ด๊ณผ JSONB ์—ด์„ ์‚ฌ์šฉํ•œ ์ ‘๊ทผ ๋ฐฉ์‹์„ ์‚ฌ์šฉํ•˜์—ฌ ๋™์ผํ•œ ๋ฐ์ดํ„ฐ๋กœ ํ…Œ์ด๋ธ”์„ ์ฑ„์› ์Šต๋‹ˆ๋‹ค. ์—”ํ„ฐํ‹ฐ_jsonb.properties. ๋”ฐ๋ผ์„œ ์šฐ๋ฆฌ๋Š” ์ „์ฒด ์†์„ฑ ์ง‘ํ•ฉ ์ค‘์—์„œ ์—ฌ๋Ÿฌ ๊ฐ€์ง€ ๋‹ค๋ฅธ ๋ฐ์ดํ„ฐ ์œ ํ˜•์„ ๋ฐ›์•˜์Šต๋‹ˆ๋‹ค. ์˜ˆ์‹œ ๋ฐ์ดํ„ฐ:

{
  id:          1
  name:        "Entity1"
  description: "Test entity no. 1"
  properties:  {
    color:        "red"
    lenght:       120
    width:        3.1882420
    hassomething: true
    country:      "Belgium"
  } 
}

์ด์ œ ๋‘ ์˜ต์…˜ ๋ชจ๋‘์— ๋Œ€ํ•ด ๋™์ผํ•œ ๋ฐ์ดํ„ฐ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ์ง์žฅ์—์„œ์˜ ๊ตฌํ˜„ ๋น„๊ต๋ฅผ ์‹œ์ž‘ํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค!

๋””์ž์ธ์„ ๋‹จ์ˆœํ™”ํ•˜์„ธ์š”

์ด์ „์—๋Š” EAV์— ์„ธ ๊ฐœ์˜ ํ…Œ์ด๋ธ”์„ ์‚ฌ์šฉํ•˜๋Š” ๋Œ€์‹  ์†์„ฑ์— JSONB ์—ด์„ ์‚ฌ์šฉํ•˜์—ฌ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋””์ž์ธ์ด ํฌ๊ฒŒ ๋‹จ์ˆœํ™”๋˜์—ˆ๋‹ค๊ณ  ์–ธ๊ธ‰ํ–ˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ์ด๊ฒƒ์ด ์š”์ฒญ์— ์–ด๋–ป๊ฒŒ ๋ฐ˜์˜๋ฉ๋‹ˆ๊นŒ? ํ•˜๋‚˜์˜ ์—”ํ„ฐํ‹ฐ ์†์„ฑ์„ ์—…๋ฐ์ดํŠธํ•˜๋Š” ๋ฐฉ๋ฒ•์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

-- EAV
UPDATE entity_attribute_value 
SET value = 'blue' 
WHERE entity_attribute_id = 1 
  AND entity_id = 120;

-- JSONB
UPDATE entity_jsonb 
SET properties = jsonb_set(properties, '{"color"}', '"blue"') 
WHERE id = 120;

๋ณด์‹œ๋‹ค์‹œํ”ผ ๋งˆ์ง€๋ง‰ ์š”์ฒญ์€ ๋” ๋‹จ์ˆœํ•ด ๋ณด์ด์ง€ ์•Š์Šต๋‹ˆ๋‹ค. JSONB ๊ฐ์ฒด์˜ ์†์„ฑ ๊ฐ’์„ ์—…๋ฐ์ดํŠธํ•˜๋ ค๋ฉด ๋‹ค์Œ ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. jsonb_set(), ์ƒˆ ๊ฐ’์„ JSONB ๊ฐ์ฒด๋กœ ์ „๋‹ฌํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ์‹๋ณ„์ž๋ฅผ ๋ฏธ๋ฆฌ ์•Œ ํ•„์š”๋Š” ์—†์Šต๋‹ˆ๋‹ค. EAV ์˜ˆ์ œ๋ฅผ ๋ณด๋ฉด ์—…๋ฐ์ดํŠธ๋ฅผ ์ˆ˜ํ–‰ํ•˜๋ ค๋ฉด ์—”ํ„ฐํ‹ฐ_id์™€ ์—”ํ„ฐํ‹ฐ_์†์„ฑ_id๋ฅผ ๋ชจ๋‘ ์•Œ์•„์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๊ฐ์ฒด ์ด๋ฆ„์„ ๊ธฐ๋ฐ˜์œผ๋กœ JSONB ์—ด์˜ ์†์„ฑ์„ ์—…๋ฐ์ดํŠธํ•˜๋ ค๋Š” ๊ฒฝ์šฐ ๋ชจ๋“  ์ž‘์—…์ด ๊ฐ„๋‹จํ•œ ํ•œ ์ค„๋กœ ์™„๋ฃŒ๋ฉ๋‹ˆ๋‹ค.

์ด์ œ ์ƒˆ๋กœ์šด ์ƒ‰์ƒ์„ ๊ธฐ๋ฐ˜์œผ๋กœ ๋ฐฉ๊ธˆ ์—…๋ฐ์ดํŠธํ•œ ์—”ํ„ฐํ‹ฐ๋ฅผ ์„ ํƒํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

-- EAV
SELECT e.name 
FROM entity e 
  INNER JOIN entity_attribute_value eav ON e.id = eav.entity_id
  INNER JOIN entity_attribute ea ON eav.entity_attribute_id = ea.id
WHERE ea.name = 'color' AND eav.value = 'blue';

-- JSONB
SELECT name 
FROM entity_jsonb 
WHERE properties ->> 'color' = 'blue';

๋‚˜๋Š” ๋‘ ๋ฒˆ์งธ ๊ฒƒ์ด ๋” ์งง๊ณ (์กฐ์ธ ์—†์Œ!) ๋” ์ฝ๊ธฐ ์‰ฝ๋‹ค๋Š” ์ ์— ๋™์˜ํ•  ์ˆ˜ ์žˆ๋‹ค๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค. JSONB๊ฐ€ ์—ฌ๊ธฐ์„œ ์Šน๋ฆฌํ–ˆ์Šต๋‹ˆ๋‹ค! JSON ->> ์—ฐ์‚ฐ์ž๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ JSONB ๊ฐœ์ฒด์—์„œ ์ƒ‰์ƒ์„ ํ…์ŠคํŠธ ๊ฐ’์œผ๋กœ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค. @> ์—ฐ์‚ฐ์ž๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ JSONB ๋ชจ๋ธ์—์„œ ๋™์ผํ•œ ๊ฒฐ๊ณผ๋ฅผ ์–ป๋Š” ๋‘ ๋ฒˆ์งธ ๋ฐฉ๋ฒ•๋„ ์žˆ์Šต๋‹ˆ๋‹ค.

-- JSONB 
SELECT name 
FROM entity_jsonb 
WHERE properties @> '{"color": "blue"}';

์ด๋Š” ์ข€ ๋” ๋ณต์žกํ•ฉ๋‹ˆ๋‹ค. ์†์„ฑ ์—ด์˜ JSON ๊ฐœ์ฒด์— @> ์—ฐ์‚ฐ์ž ์˜ค๋ฅธ์ชฝ์— ์žˆ๋Š” ๊ฐœ์ฒด๊ฐ€ ํฌํ•จ๋˜์–ด ์žˆ๋Š”์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค. ๊ฐ€๋…์„ฑ์ด ๋‚ฎ๊ณ  ์ƒ์‚ฐ์„ฑ์ด ๋†’์Šต๋‹ˆ๋‹ค(์•„๋ž˜ ์ฐธ์กฐ).

ํ•œ ๋ฒˆ์— ์—ฌ๋Ÿฌ ์†์„ฑ์„ ์„ ํƒํ•ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ JSONB๋ฅผ ๋”์šฑ ์‰ฝ๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. JSONB ์ ‘๊ทผ ๋ฐฉ์‹์ด ์‹ค์ œ๋กœ ํ•„์š”ํ•œ ๊ณณ์ž…๋‹ˆ๋‹ค. ์กฐ์ธ์ด ํ•„์š” ์—†์ด ๊ฒฐ๊ณผ ์ง‘ํ•ฉ์—์„œ ์†์„ฑ์„ ์ถ”๊ฐ€ ์—ด๋กœ ์„ ํƒํ•˜๊ธฐ๋งŒ ํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค.

-- JSONB 
SELECT name
  , properties ->> 'color'
  , properties ->> 'country'
FROM entity_jsonb 
WHERE id = 120;

EAV๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์ฟผ๋ฆฌํ•˜๋ ค๋Š” ๊ฐ ์†์„ฑ์— ๋Œ€ํ•ด 2๊ฐœ์˜ ์กฐ์ธ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ์ œ ์ƒ๊ฐ์—๋Š” ์œ„์˜ ์ฟผ๋ฆฌ๋Š” ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์„ค๊ณ„๊ฐ€ ํฌ๊ฒŒ ๋‹จ์ˆœํ™”๋˜์—ˆ์Œ์„ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค. JSONB ์ฟผ๋ฆฌ๋ฅผ ์ž‘์„ฑํ•˜๋Š” ๋ฐฉ๋ฒ•์— ๋Œ€ํ•œ ์ถ”๊ฐ€ ์˜ˆ์ œ๋„ ์ฐธ์กฐํ•˜์„ธ์š”. ์ด ์šฐํŽธ.
์ด์ œ ์„ฑ๋Šฅ์— ๋Œ€ํ•ด ์ด์•ผ๊ธฐํ•  ์ฐจ๋ก€์ž…๋‹ˆ๋‹ค.

ะŸั€ะพะธะทะฒะพะดะธั‚ะตะปัŒะฝะพัั‚ัŒ

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

๋ฐ์ดํ„ฐ ์—…๋ฐ์ดํŠธ๋ฅผ ํ†ตํ•ด ์‹œ๊ฐ„(ms) ๊ธฐ์ค€์œผ๋กœ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๊ฒฐ๊ณผ๊ฐ€ ๋‚˜ํƒ€๋‚ฌ์Šต๋‹ˆ๋‹ค. ์ฒ™๋„๋Š” ๋Œ€์ˆ˜์ ์ž…๋‹ˆ๋‹ค.

PostgreSQL์—์„œ EAV๋ฅผ JSONB๋กœ ๊ต์ฒด

์œ„์—์„œ ์„ค๋ช…ํ•œ ์ด์œ ๋กœ ์ธ๋ฑ์Šค๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š์œผ๋ฉด JSONB๊ฐ€ EAV๋ณด๋‹ค ํ›จ์”ฌ(> 50000-x) ๋น ๋ฆ…๋‹ˆ๋‹ค. ๊ธฐ๋ณธ ํ‚ค๋กœ ์—ด์„ ์ธ๋ฑ์‹ฑํ•˜๋ฉด ์ฐจ์ด๊ฐ€ ๊ฑฐ์˜ ์‚ฌ๋ผ์ง€์ง€๋งŒ JSONB๋Š” ์—ฌ์ „ํžˆ EAV๋ณด๋‹ค 1,3๋ฐฐ ๋น ๋ฆ…๋‹ˆ๋‹ค. ํ‰๊ฐ€ ๊ธฐ์ค€์—์„œ ์†์„ฑ ์—ด์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— JSONB ์—ด์˜ ์ธ๋ฑ์Šค๋Š” ์—ฌ๊ธฐ์„œ ์•„๋ฌด๋Ÿฐ ์˜ํ–ฅ์„ ๋ฏธ์น˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

์†์„ฑ ๊ฐ’์„ ๊ธฐ์ค€์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ์„ ํƒํ•˜๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๊ฒฐ๊ณผ๋ฅผ ์–ป์Šต๋‹ˆ๋‹ค(์ผ๋ฐ˜ ์ฒ™๋„).

PostgreSQL์—์„œ EAV๋ฅผ JSONB๋กœ ๊ต์ฒด

JSONB๋Š” ์ธ๋ฑ์Šค๊ฐ€ ์—†๋Š” EAV๋ณด๋‹ค ๋น ๋ฅด๊ฒŒ ์ž‘๋™ํ•˜์ง€๋งŒ ์ธ๋ฑ์Šค๊ฐ€ ์žˆ๋Š” EAV์˜ ๊ฒฝ์šฐ ์—ฌ์ „ํžˆ JSONB๋ณด๋‹ค ๋น ๋ฅด๊ฒŒ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฐ๋ฐ JSONB ์ฟผ๋ฆฌ์˜ ์‹œ๊ฐ„์ด ๋™์ผํ•˜๋‹ค๋Š” ๊ฒƒ์„ ์•Œ์•˜๊ณ  ์ด๋กœ ์ธํ•ด GIN ์ธ๋ฑ์Šค๊ฐ€ ์ž‘๋™ํ•˜์ง€ ์•Š๋Š”๋‹ค๋Š” ์‚ฌ์‹ค์„ ์•Œ๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ์†์„ฑ์ด ์ฑ„์›Œ์ง„ ์—ด์— GIN ์ธ๋ฑ์Šค๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ํฌํ•จ ์—ฐ์‚ฐ์ž @>๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ๋งŒ ์ ์šฉ๋ฉ๋‹ˆ๋‹ค. ๋‚˜๋Š” ์ด๊ฒƒ์„ ์ƒˆ๋กœ์šด ํ…Œ์ŠคํŠธ์— ์‚ฌ์šฉํ–ˆ๊ณ  ์‹œ๊ฐ„์— ํฐ ์˜ํ–ฅ์„ ๋ฏธ์ณค์Šต๋‹ˆ๋‹ค: ๋‹จ 0,153ms! ์ด๋Š” EAV๋ณด๋‹ค 15000๋ฐฐ ๋น ๋ฅด๋ฉฐ ->> ์—ฐ์‚ฐ์ž๋ณด๋‹ค 25000๋ฐฐ ๋น ๋ฆ…๋‹ˆ๋‹ค.

์ถฉ๋ถ„ํžˆ ๋นจ๋ž๋˜ ๊ฒƒ ๊ฐ™์•„์š”!

๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ํ…Œ์ด๋ธ” ํฌ๊ธฐ

๋‘ ์ ‘๊ทผ ๋ฐฉ์‹์˜ ํ…Œ์ด๋ธ” ํฌ๊ธฐ๋ฅผ ๋น„๊ตํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. psql์—์„œ๋Š” ๋‹ค์Œ ๋ช…๋ น์„ ์‚ฌ์šฉํ•˜์—ฌ ๋ชจ๋“  ํ…Œ์ด๋ธ”๊ณผ ์ธ๋ฑ์Šค์˜ ํฌ๊ธฐ๋ฅผ ํ‘œ์‹œํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. dti+

PostgreSQL์—์„œ EAV๋ฅผ JSONB๋กœ ๊ต์ฒด

EAV ์ ‘๊ทผ ๋ฐฉ์‹์˜ ๊ฒฝ์šฐ ํ…Œ์ด๋ธ” ํฌ๊ธฐ๋Š” ์•ฝ 3068MB์ด๊ณ  ์ธ๋ฑ์Šค๋Š” ์ตœ๋Œ€ 3427MB๋กœ ์ด 6,43GB์ž…๋‹ˆ๋‹ค. JSONB ์ ‘๊ทผ ๋ฐฉ์‹์€ ํ…Œ์ด๋ธ”์— 1817MB, ์ธ๋ฑ์Šค์— 318MB(2,08GB)๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. 3 ๋ฐฐ ๋” ์ ์€ ๊ฒƒ์œผ๋กœ ๋‚˜ํƒ€๋‚ฌ์Šต๋‹ˆ๋‹ค! ์šฐ๋ฆฌ๋Š” ๋ชจ๋“  JSONB ๊ฐ์ฒด์— ์†์„ฑ ์ด๋ฆ„์„ ์ €์žฅํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์ด ์‚ฌ์‹ค์— ์กฐ๊ธˆ ๋†€๋ž์Šต๋‹ˆ๋‹ค.

๊ทธ๋Ÿฌ๋‚˜ ์—ฌ์ „ํžˆ ์ˆซ์ž๋Š” ๊ทธ ์ž์ฒด๋กœ ๋‚˜ํƒ€๋‚ฉ๋‹ˆ๋‹ค. EAV์—์„œ๋Š” ์†์„ฑ ๊ฐ’๋‹น 2๊ฐœ์˜ ์ •์ˆ˜ ์™ธ๋ž˜ ํ‚ค๋ฅผ ์ €์žฅํ•˜์—ฌ 8๋ฐ”์ดํŠธ์˜ ์ถ”๊ฐ€ ๋ฐ์ดํ„ฐ๊ฐ€ ์ƒ์„ฑ๋ฉ๋‹ˆ๋‹ค. ๋˜ํ•œ EAV๋Š” ๋ชจ๋“  ์†์„ฑ ๊ฐ’์„ ํ…์ŠคํŠธ๋กœ ์ €์žฅํ•˜๋Š” ๋ฐ˜๋ฉด JSONB๋Š” ๊ฐ€๋Šฅํ•œ ๊ฒฝ์šฐ ๋‚ด๋ถ€์ ์œผ๋กœ ์ˆซ์ž ๋ฐ ๋ถ€์šธ ๊ฐ’์„ ์‚ฌ์šฉํ•˜๋ฏ€๋กœ ๊ณต๊ฐ„์ด ๋” ์ž‘์•„์ง‘๋‹ˆ๋‹ค.

๊ฒฐ๊ณผ

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

๋˜ํ•œ ์ˆ˜ํ–‰๋œ ํ…Œ์ŠคํŠธ์— ๋”ฐ๋ฅด๋ฉด ์„ฑ๋Šฅ ์†์‹ค์ด ๋งค์šฐ ๋ฏธ๋ฏธํ•˜๋‹ค๋Š” ๊ฒฐ๋ก ์„ ๋‚ด๋ฆด ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์–ด๋–ค ๊ฒฝ์šฐ์—๋Š” JSONB๊ฐ€ EAV๋ณด๋‹ค ํ›จ์”ฌ ๋นจ๋ผ์„œ ํ›จ์”ฌ ๋” ์ข‹์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ๋ฌผ๋ก  ์ด ๋ฒค์น˜๋งˆํฌ๊ฐ€ ๋ชจ๋“  ์ธก๋ฉด(์˜ˆ: ๋งค์šฐ ๋งŽ์€ ์ˆ˜์˜ ์†์„ฑ์„ ๊ฐ€์ง„ ์—”ํ„ฐํ‹ฐ, ๊ธฐ์กด ๋ฐ์ดํ„ฐ์˜ ์†์„ฑ ์ˆ˜์˜ ์ƒ๋‹นํ•œ ์ฆ๊ฐ€ ๋“ฑ)์„ ๋‹ค๋ฃจ์ง€๋Š” ์•Š์œผ๋ฏ€๋กœ ์ด๋ฅผ ๊ฐœ์„ ํ•˜๋Š” ๋ฐฉ๋ฒ•์— ๋Œ€ํ•œ ์ œ์•ˆ ์‚ฌํ•ญ์ด ์žˆ์œผ๋ฉด , ์ž์œ ๋กญ๊ฒŒ ๋Œ“๊ธ€๋กœ ๋‚จ๊ฒจ์ฃผ์„ธ์š”!

์ถœ์ฒ˜ : habr.com

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