Skip to content

JiNookk/eav-elasticSearch-benchmark

Repository files navigation

EAV vs Elasticsearch Performance Benchmark

๋™์  ์ปค์Šคํ…€ ํ•„๋“œ ๊ฒ€์ƒ‰์—์„œ MySQL EAV ํŒจํ„ด์˜ ํ•œ๊ณ„๋ฅผ Elasticsearch๋กœ ํ•ด๊ฒฐํ•œ ์„ฑ๋Šฅ ์ตœ์ ํ™” ํ”„๋กœ์ ํŠธ

๐Ÿ”— Live Demo: eav-elasticsearch-benchmark.vercel.app


TL;DR

50๋งŒ ๊ฑด ๋ฐ์ดํ„ฐ ๊ธฐ์ค€, ์ปค์Šคํ…€ ํ•„๋“œ ๊ฒ€์ƒ‰์—์„œ ์ตœ๋Œ€ 218๋ฐฐ ์„ฑ๋Šฅ ๊ฐœ์„  ๋‹ฌ์„ฑ

์‹œ๋‚˜๋ฆฌ์˜ค MySQL (EAV) Elasticsearch ๊ฐœ์„ ์œจ
์ปค์Šคํ…€ ํ•„๋“œ ํ•„ํ„ฐ 1,847ms 21ms 88x
์ปค์Šคํ…€ ํ•„๋“œ ์ •๋ ฌ 3,521ms 19ms 185x
ํ•„ํ„ฐ + ์ •๋ ฌ ์กฐํ•ฉ 5,234ms 24ms 218x

ํ•ต์‹ฌ ๊ธฐ์ˆ  ํฌ์ธํŠธ

  • Transactional Outbox Pattern: MySQL-ES ๊ฐ„ ๋ฐ์ดํ„ฐ ์ผ๊ด€์„ฑ ๋ณด์žฅ
  • BullMQ ๊ธฐ๋ฐ˜ ๋น„๋™๊ธฐ ๋™๊ธฐํ™”: ํ‰๊ท  ~10ms ๋‚ด ES ๋ฐ˜์˜
  • Clean Architecture: Domain/Application/Infrastructure ๋ ˆ์ด์–ด ๋ถ„๋ฆฌ
  • EAV โ†’ Document ๋น„์ •๊ทœํ™”: 5.64GB โ†’ 312MB (18๋ฐฐ ์ €์žฅ ํšจ์œจ)

๋ฌธ์ œ ์ •์˜

EAV(Entity-Attribute-Value) ํŒจํ„ด์˜ ํ•œ๊ณ„

Salesforce์™€ ๊ฐ™์€ SaaS ํ”Œ๋žซํผ์€ ์‚ฌ์šฉ์ž๊ฐ€ ๋™์ ์œผ๋กœ ์ปค์Šคํ…€ ํ•„๋“œ๋ฅผ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋ฅผ RDBMS์—์„œ ๊ตฌํ˜„ํ•˜๋Š” ์ „ํ†ต์ ์ธ ๋ฐฉ๋ฒ•์€ EAV ํŒจํ„ด์ž…๋‹ˆ๋‹ค:

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”       โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ contacts        โ”‚       โ”‚ custom_field_values      โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค       โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ id              โ”‚โ”€โ”€โ”€โ”€โ”€โ”€โ–ถโ”‚ contact_id (FK)          โ”‚
โ”‚ email           โ”‚       โ”‚ field_definition_id (FK) โ”‚
โ”‚ name            โ”‚       โ”‚ value                    โ”‚
โ”‚ created_at      โ”‚       โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜                    โ”‚
                                       โ–ผ
                          โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
                          โ”‚ custom_field_definitions โ”‚
                          โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
                          โ”‚ id                       โ”‚
                          โ”‚ api_name (tier__c)       โ”‚
                          โ”‚ field_type               โ”‚
                          โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

๋ฌธ์ œ์ :

์ž‘์—… EAV ์ฟผ๋ฆฌ ๋ณต์žก๋„ ์„ฑ๋Šฅ
์ปค์Šคํ…€ ํ•„๋“œ ๊ฒ€์ƒ‰ N๊ฐœ์˜ ์„œ๋ธŒ์ฟผ๋ฆฌ ํ•„์š” O(N ร— M)
์ปค์Šคํ…€ ํ•„๋“œ ์ •๋ ฌ PIVOT ์ฟผ๋ฆฌ + GROUP BY ๋งค์šฐ ๋А๋ฆผ
์ง‘๊ณ„ (GROUP BY) ๋ณต์žกํ•œ CASE WHEN ๋งค์šฐ ๋А๋ฆผ

50๋งŒ ๊ฑด ๊ธฐ์ค€ ์„ฑ๋Šฅ ๋น„๊ต:

์ž‘์—… MySQL (EAV) Elasticsearch
๋‹จ์ˆœ ๋ชฉ๋ก ์กฐํšŒ ~200ms ~15ms
์ปค์Šคํ…€ ํ•„๋“œ ์ •๋ ฌ ~3,000ms ~20ms
์ปค์Šคํ…€ ํ•„๋“œ ํ•„ํ„ฐ + ์ •๋ ฌ ~5,000ms+ ~25ms

ํ•ด๊ฒฐ์ฑ…: Elasticsearch ๋„์ž…

๋น„์ •๊ทœํ™”๋œ ๋ฌธ์„œ ์ €์žฅ

{
  "id": "uuid-1234",
  "email": "john@example.com",
  "name": "John Doe",
  "customFields": {
    "tier__c": "GOLD",
    "score__c": 95,
    "notes__c": "VIP ๊ณ ๊ฐ"
  },
  "createdAt": "2024-01-15T09:00:00Z",
  "updatedAt": "2024-01-15T09:00:00Z"
}

์žฅ์ :

  • ๋ชจ๋“  ํ•„๋“œ๊ฐ€ ํ•˜๋‚˜์˜ ๋ฌธ์„œ์— โ†’ JOIN ๋ถˆํ•„์š”
  • ์—ญ์ธ๋ฑ์Šค(Inverted Index)๋กœ ๋น ๋ฅธ ๊ฒ€์ƒ‰
  • ์ปค์Šคํ…€ ํ•„๋“œ ์ •๋ ฌ/์ง‘๊ณ„๊ฐ€ ๊ธฐ๋ณธ ํ•„๋“œ์™€ ๋™์ผํ•œ ์„ฑ๋Šฅ

์•„ํ‚คํ…์ฒ˜

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                              Client (Next.js)                               โ”‚
โ”‚                   https://eav-elasticsearch-benchmark.vercel.app            โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                                      โ”‚
                                      โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                            NestJS API Server                                โ”‚
โ”‚                                                                             โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”   โ”‚
โ”‚  โ”‚                         ContactController                           โ”‚   โ”‚
โ”‚  โ”‚                                                                     โ”‚   โ”‚
โ”‚  โ”‚   GET /search โ”€โ”€โ”ฌโ”€โ”€ dataSource=es โ”€โ”€โ”€โ–ถ ElasticsearchService        โ”‚   โ”‚
โ”‚  โ”‚                 โ”‚                                                   โ”‚   โ”‚
โ”‚  โ”‚                 โ””โ”€โ”€ dataSource=mysql โ”€โ–ถ MySQL (EAV) ์ง์ ‘ ์ฟผ๋ฆฌ      โ”‚   โ”‚
โ”‚  โ”‚                                                                     โ”‚   โ”‚
โ”‚  โ”‚   POST/PATCH/DELETE โ”€โ”€โ–ถ ContactService                             โ”‚   โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜   โ”‚
โ”‚                                      โ”‚                                      โ”‚
โ”‚                                      โ–ผ                                      โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”   โ”‚
โ”‚  โ”‚                        ContactService                               โ”‚   โ”‚
โ”‚  โ”‚                                                                     โ”‚   โ”‚
โ”‚  โ”‚   1. MySQL ํŠธ๋žœ์žญ์…˜ ์‹œ์ž‘                                            โ”‚   โ”‚
โ”‚  โ”‚   2. Contact ์ €์žฅ                                                   โ”‚   โ”‚
โ”‚  โ”‚   3. Outbox ์ด๋ฒคํŠธ ์ €์žฅ (๊ฐ™์€ ํŠธ๋žœ์žญ์…˜)                              โ”‚   โ”‚
โ”‚  โ”‚   4. ํŠธ๋žœ์žญ์…˜ ์ปค๋ฐ‹                                                   โ”‚   โ”‚
โ”‚  โ”‚   5. BullMQ์— ์ž‘์—… ์ถ”๊ฐ€ (best-effort)                               โ”‚   โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜   โ”‚
โ”‚                                      โ”‚                                      โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                                       โ”‚
       โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
       โ”‚                               โ”‚                               โ”‚
       โ–ผ                               โ–ผ                               โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”              โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”              โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚   MySQL     โ”‚              โ”‚     Redis       โ”‚              โ”‚ Elasticsearchโ”‚
โ”‚             โ”‚              โ”‚                 โ”‚              โ”‚             โ”‚
โ”‚ - contacts  โ”‚              โ”‚ BullMQ Queue    โ”‚              โ”‚ contacts    โ”‚
โ”‚ - field_    โ”‚              โ”‚ (es-sync)       โ”‚              โ”‚ index       โ”‚
โ”‚   values    โ”‚              โ”‚                 โ”‚              โ”‚             โ”‚
โ”‚ - outbox    โ”‚โ—€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”‚                 โ”‚โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ถโ”‚ ๋น„์ •๊ทœํ™”๋œ  โ”‚
โ”‚             โ”‚   Cron์ด     โ”‚                 โ”‚   Worker๊ฐ€   โ”‚ ๋ฌธ์„œ ์ €์žฅ   โ”‚
โ”‚             โ”‚   PENDING    โ”‚                 โ”‚   ES ๋™๊ธฐํ™”  โ”‚             โ”‚
โ”‚             โ”‚   ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌโ”‚                 โ”‚              โ”‚             โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜              โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜              โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

๋ฐ์ดํ„ฐ ๋™๊ธฐํ™” ์ „๋žต

Transactional Outbox Pattern + BullMQ

๋ฐ์ดํ„ฐ ์ผ๊ด€์„ฑ์„ ๋ณด์žฅํ•˜๋ฉด์„œ ๋น„๋™๊ธฐ๋กœ ES๋ฅผ ๋™๊ธฐํ™”ํ•ฉ๋‹ˆ๋‹ค.

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                            Write Path (์“ฐ๊ธฐ)                                 โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

     โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
     โ”‚                    MySQL Transaction                             โ”‚
     โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚
     โ”‚  โ”‚  1. Contact INSERT/UPDATE/DELETE                           โ”‚  โ”‚
     โ”‚  โ”‚  2. Outbox INSERT (PENDING ์ƒํƒœ)                           โ”‚  โ”‚
     โ”‚  โ”‚                                                            โ”‚  โ”‚
     โ”‚  โ”‚  โ†’ ๊ฐ™์€ ํŠธ๋žœ์žญ์…˜ = ์›์ž์„ฑ ๋ณด์žฅ                              โ”‚  โ”‚
     โ”‚  โ”‚  โ†’ Contact ์ €์žฅ ์‹คํŒจ ์‹œ Outbox๋„ ๋กค๋ฐฑ                       โ”‚  โ”‚
     โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚
     โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                                      โ”‚
                                      โ–ผ
     โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
     โ”‚                   Best-Effort Queue                              โ”‚
     โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚
     โ”‚  โ”‚  ํŠธ๋žœ์žญ์…˜ ์ปค๋ฐ‹ ํ›„ BullMQ์— ์ž‘์—… ์ถ”๊ฐ€ ์‹œ๋„                    โ”‚  โ”‚
     โ”‚  โ”‚                                                            โ”‚  โ”‚
     โ”‚  โ”‚  ์„ฑ๊ณต โ†’ Worker๊ฐ€ ์ฆ‰์‹œ ES ๋™๊ธฐํ™” (~10ms)                     โ”‚  โ”‚
     โ”‚  โ”‚  ์‹คํŒจ โ†’ Outbox Cron์ด ๋‚˜์ค‘์— ์ฒ˜๋ฆฌ (~10์ดˆ ํ›„)                 โ”‚  โ”‚
     โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚
     โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                         Background Processors                               โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

     โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
     โ”‚  EsSyncProcessor (BullMQ Worker)                                 โ”‚
     โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚
     โ”‚  โ”‚  Queue์—์„œ ์ž‘์—… ์ˆ˜์‹                                         โ”‚  โ”‚
     โ”‚  โ”‚  โ†’ CONTACT_CREATED: ES ๋ฌธ์„œ ์ƒ์„ฑ                            โ”‚  โ”‚
     โ”‚  โ”‚  โ†’ CONTACT_UPDATED: ES ๋ฌธ์„œ ์—…๋ฐ์ดํŠธ                         โ”‚  โ”‚
     โ”‚  โ”‚  โ†’ CONTACT_DELETED: ES ๋ฌธ์„œ ์‚ญ์ œ                            โ”‚  โ”‚
     โ”‚  โ”‚                                                            โ”‚  โ”‚
     โ”‚  โ”‚  ์‹คํŒจ ์‹œ ์ž๋™ ์žฌ์‹œ๋„ (BullMQ ๋‚ด์žฅ)                           โ”‚  โ”‚
     โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚
     โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

     โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
     โ”‚  OutboxProcessor (Cron - Fallback)                               โ”‚
     โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚
     โ”‚  โ”‚  @Cron(EVERY_10_SECONDS)                                   โ”‚  โ”‚
     โ”‚  โ”‚  โ†’ PENDING ์ƒํƒœ ์ด๋ฒคํŠธ โ†’ Queue์— ์ถ”๊ฐ€ โ†’ DONE ์ฒ˜๋ฆฌ           โ”‚  โ”‚
     โ”‚  โ”‚                                                            โ”‚  โ”‚
     โ”‚  โ”‚  @Cron(EVERY_MINUTE)                                       โ”‚  โ”‚
     โ”‚  โ”‚  โ†’ FAILED ์ด๋ฒคํŠธ ์žฌ์‹œ๋„ (์ตœ๋Œ€ 5ํšŒ)                          โ”‚  โ”‚
     โ”‚  โ”‚                                                            โ”‚  โ”‚
     โ”‚  โ”‚  @Cron(EVERY_DAY_AT_MIDNIGHT)                               โ”‚  โ”‚
     โ”‚  โ”‚  โ†’ 7์ผ ์ง€๋‚œ DONE ์ด๋ฒคํŠธ ์ •๋ฆฌ                                โ”‚  โ”‚
     โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚
     โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Outbox ์ด๋ฒคํŠธ ์ƒํƒœ ํ๋ฆ„

PENDING โ”€โ”€โ”ฌโ”€โ”€โ–ถ PROCESSING โ”€โ”€โ”ฌโ”€โ”€โ–ถ DONE (์„ฑ๊ณต)
          โ”‚                 โ”‚
          โ”‚                 โ””โ”€โ”€โ–ถ FAILED โ”€โ”€โ”ฌโ”€โ”€โ–ถ PROCESSING (์žฌ์‹œ๋„)
          โ”‚                               โ”‚
          โ”‚                               โ””โ”€โ”€โ–ถ FAILED (์ตœ๋Œ€ 5ํšŒ ํ›„ ํฌ๊ธฐ)
          โ”‚
          โ””โ”€โ”€โ–ถ (๋‹ค๋ฅธ ํ”„๋กœ์„ธ์Šค๊ฐ€ ์„ ์ )

ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ

src/
โ”œโ”€โ”€ domain/                          # ๋„๋ฉ”์ธ ๋ ˆ์ด์–ด (์ˆœ์ˆ˜ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง)
โ”‚   โ”œโ”€โ”€ contact/
โ”‚   โ”‚   โ””โ”€โ”€ contact.domain.ts
โ”‚   โ””โ”€โ”€ customField/
โ”‚       โ”œโ”€โ”€ customFieldDefinition.domain.ts
โ”‚       โ””โ”€โ”€ customFieldValue.domain.ts
โ”‚
โ”œโ”€โ”€ application/                     # ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋ ˆ์ด์–ด (์œ ์Šค์ผ€์ด์Šค)
โ”‚   โ”œโ”€โ”€ contact/
โ”‚   โ”‚   โ”œโ”€โ”€ contact.service.ts       # Contact CRUD + Outbox
โ”‚   โ”‚   โ””โ”€โ”€ contact-search.service.ts # MySQL/ES ๊ฒ€์ƒ‰
โ”‚   โ””โ”€โ”€ customField/
โ”‚       โ””โ”€โ”€ customField.service.ts
โ”‚
โ”œโ”€โ”€ infrastructure/                  # ์ธํ”„๋ผ ๋ ˆ์ด์–ด
โ”‚   โ”œโ”€โ”€ elasticsearch/
โ”‚   โ”‚   โ””โ”€โ”€ elasticsearch.service.ts # ES ์ธ๋ฑ์‹ฑ/๊ฒ€์ƒ‰
โ”‚   โ”œโ”€โ”€ queue/
โ”‚   โ”‚   โ”œโ”€โ”€ es-sync.processor.ts     # BullMQ Worker
โ”‚   โ”‚   โ””โ”€โ”€ es-sync.types.ts
โ”‚   โ”œโ”€โ”€ outbox/
โ”‚   โ”‚   โ””โ”€โ”€ outbox-processor.service.ts # Cron Fallback
โ”‚   โ””โ”€โ”€ persistence/
โ”‚       โ””โ”€โ”€ typeorm/
โ”‚           โ”œโ”€โ”€ entity/
โ”‚           โ”‚   โ”œโ”€โ”€ contact.entity.ts
โ”‚           โ”‚   โ”œโ”€โ”€ fieldDefinition.entity.ts
โ”‚           โ”‚   โ”œโ”€โ”€ fieldValue.entity.ts
โ”‚           โ”‚   โ””โ”€โ”€ outbox.entity.ts
โ”‚           โ””โ”€โ”€ repository/
โ”‚
โ”œโ”€โ”€ interface/                       # ์ธํ„ฐํŽ˜์ด์Šค ๋ ˆ์ด์–ด (API)
โ”‚   โ””โ”€โ”€ http/
โ”‚       โ””โ”€โ”€ contact/
โ”‚           โ””โ”€โ”€ contact.controller.ts
โ”‚
dashboard/                           # Next.js ํ”„๋ก ํŠธ์—”๋“œ
โ”œโ”€โ”€ src/
โ”‚   โ”œโ”€โ”€ app/
โ”‚   โ”œโ”€โ”€ components/
โ”‚   โ”‚   โ”œโ”€โ”€ ContactsTable.tsx
โ”‚   โ”‚   โ”œโ”€โ”€ DataSourceToggle.tsx     # MySQL/ES ์ „ํ™˜
โ”‚   โ”‚   โ””โ”€โ”€ QueryTimeDisplay.tsx     # ์ฟผ๋ฆฌ ์‹œ๊ฐ„ ํ‘œ์‹œ
โ”‚   โ””โ”€โ”€ hooks/
โ”‚       โ””โ”€โ”€ useContacts.ts

๊ธฐ์ˆ  ์Šคํƒ

Backend: NestJS ยท TypeORM ยท TypeScript Database: MySQL 8.0 (EAV) ยท Elasticsearch 8.11 Queue: BullMQ ยท Redis Frontend: Next.js 14 ยท React Query ยท TailwindCSS Infra: Docker Compose


์‹คํ–‰ ๋ฐฉ๋ฒ•

1. ์ธํ”„๋ผ ์‹คํ–‰

# Docker Compose๋กœ MySQL, ES, Redis ์‹คํ–‰
docker compose up -d

# ์ƒํƒœ ํ™•์ธ
docker compose ps

2. ๋ฐฑ์—”๋“œ ์‹คํ–‰

# ์˜์กด์„ฑ ์„ค์น˜
npm install

# ๊ฐœ๋ฐœ ์„œ๋ฒ„ ์‹คํ–‰
npm run start:dev

3. ์‹œ๋“œ ๋ฐ์ดํ„ฐ ์ƒ์„ฑ

# ๊ธฐ๋ณธ (50๋งŒ ๊ฑด)
npm run seed:large

# ES๋งŒ ์žฌ์ƒ์„ฑ
npm run seed:es-only

# ์†Œ๋Ÿ‰ ํ…Œ์ŠคํŠธ (1๋งŒ ๊ฑด)
npm run seed:small

4. ํ”„๋ก ํŠธ์—”๋“œ ์‹คํ–‰

cd dashboard
npm install
npm run dev

5. ์ ‘์†


API ์—”๋“œํฌ์ธํŠธ

Contact ๊ฒ€์ƒ‰

# Elasticsearch ๊ฒ€์ƒ‰ (๋น ๋ฆ„)
GET /api/v1/contacts/search?dataSource=es&page=1&pageSize=20

# MySQL ๊ฒ€์ƒ‰ (EAV - ๋А๋ฆผ)
GET /api/v1/contacts/search?dataSource=mysql&page=1&pageSize=20

# ํ‚ค์›Œ๋“œ ๊ฒ€์ƒ‰
GET /api/v1/contacts/search?dataSource=es&search=john

# ์ปค์Šคํ…€ ํ•„๋“œ ํ•„ํ„ฐ
GET /api/v1/contacts/search?dataSource=es&filter=[{"field":"tier__c","operator":"eq","value":"GOLD"}]

# ์ปค์Šคํ…€ ํ•„๋“œ ์ •๋ ฌ
GET /api/v1/contacts/search?dataSource=es&sort=[{"field":"score__c","direction":"desc"}]

Contact CRUD

# ์ƒ์„ฑ
POST /api/v1/contacts
{
  "email": "new@example.com",
  "name": "New User",
  "customFields": {
    "tier__c": "SILVER",
    "score__c": 50
  }
}

# ์กฐํšŒ
GET /api/v1/contacts/:id

# ์ˆ˜์ •
PATCH /api/v1/contacts/:id
{
  "name": "Updated Name",
  "customFields": {
    "tier__c": "GOLD"
  }
}

# ์‚ญ์ œ
DELETE /api/v1/contacts/:id

์ €์žฅ ์šฉ๋Ÿ‰ ๋น„๊ต

์ €์žฅ์†Œ ์šฉ๋Ÿ‰ ์„ค๋ช…
MySQL (์ „์ฒด) 5.64 GB contacts + custom_field_values (EAV)
Elasticsearch 312.8 MB 50๋งŒ ๋ฌธ์„œ (๋น„์ •๊ทœํ™”)
MySQL:  โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆ 5.64 GB
ES:     โ–ˆโ–ˆ 312.8 MB (18๋ฐฐ ์ž‘์Œ)

EAV ํŒจํ„ด์€ ์œ ์—ฐํ•˜์ง€๋งŒ ์ €์žฅ ํšจ์œจ์ด ๋‚ฎ์Šต๋‹ˆ๋‹ค. Contact 1๊ฐœ๋‹น ์ปค์Šคํ…€ ํ•„๋“œ 10๊ฐœ = ํ–‰ 10๊ฐœ (500๋งŒ ํ–‰ ํญ๋ฐœ) ES์˜ ๋น„์ •๊ทœํ™” + Lucene ์••์ถ•์ด ์˜คํžˆ๋ ค ๋” ํšจ์œจ์ ์ž…๋‹ˆ๋‹ค.


๋ฐฐ์šด ์ 

  • ํŠธ๋ ˆ์ด๋“œ์˜คํ”„ ์„ค๊ณ„: RDBMS์˜ ACID vs ๊ฒ€์ƒ‰ ์—”์ง„์˜ ์„ฑ๋Šฅ, ์–‘์ชฝ ์žฅ์ ์„ ์‚ด๋ฆฌ๋Š” ์•„ํ‚คํ…์ฒ˜
  • ๋ถ„์‚ฐ ์‹œ์Šคํ…œ์˜ ์ผ๊ด€์„ฑ: Transactional Outbox๋กœ Eventually Consistent ๋ณด์žฅ
  • ์‹ค์ธก ๊ธฐ๋ฐ˜ ์˜์‚ฌ๊ฒฐ์ •: ์ถ”์ธก์ด ์•„๋‹Œ ๋ฒค์น˜๋งˆํฌ ์ˆ˜์น˜๋กœ ๊ธฐ์ˆ  ์„ ํƒ ๊ฒ€์ฆ

๋ผ์ด์„ ์Šค

MIT License

Releases

No releases published

Packages

 
 
 

Contributors

Languages