- μ±μ©κ³΅κ³ κ²μ μ£Όμ μ νλ‘μ νΈ
- OpenSearch μ€μ΅
- Spring Boot
- Spring Data JPA
- PostgreSQL
- Flyway
- OpenSearch
- k6
- λ‘컬 PostgreSQL λ° OpenSearch λ컀 μ»΄ν¬μ¦ μ€μΉ
docker-compose up -d
-
μ μ μ 보
- PostgreSQL: jdbc:postgresql://localhost:5432/jobboard
- OpenSearch: http://localhost:9200
- OpenSearch Dashboards: http://localhost:5601
-
μ ν리μΌμ΄μ μ€ννλ©΄ Flywayκ° DBμ μν λ°μ΄ν° μ μ¬
- resources/db/migration/V1__init.sql : μ€ν€λ§ μμ±
- resources/db/migration/V2__alter_sequence.sql : PostgreSQL μνμ€ μ€μ μμ
- resources/db/migration/R__seed.sql : μν λ°μ΄ν° μ μ¬
-
k6 μ±λ₯ν μ€νΈ μ€ν νκ²½ μ€μ
- k6 μ€μΉ μ¬μ΄νΈμμ μ€μΉ ν λͺ λ Ήμ΄ μν
- RDB λͺ©λ‘ μ‘°ν κΈ°λ₯:
k6 run .\k6\perf\rdb.js - OpenSearch κ²μ κΈ°λ₯:
k6 run .\k6\perf\opensearch.js
- RDB κΈ°λ°μ μ±μ©κ³΅κ³ μμ€ν μ OpenSearch κ²μ μ μ© μμ§μΌλ‘ λΆλ¦¬ μ μ©
- Outbox ν¨ν΄μ νμ©ν λΉλκΈ° μΈλ±μ± ꡬ쑰
- RDB μ‘°ν κ²μ vs OpenSearch κ²μ μν Β·μ±λ₯Β·νμ§ μ°¨μ΄ λΉκ΅
- μΈλ±μ€ νμ© μ νμ
- κ΄λ ¨λ μ λ ¬ μ΄λ €μ
- λ°μ΄ν°κ° λμ΄λ μλ‘ μ±λ₯ κΈκ²©ν μ ν
- μ ν리μΌμ΄μ μμ μ OpenSearch μΈλ±μ€κ° μμΌλ©΄ μλ μμ±
- λ‘컬/μ€μ΅ νκ²½μ λ§μΆ settings & mappings μ μ©
- νν°λ§μ© νλλ keywordλ‘ μ€κ³
job_postings
ββ text : title, description, companyName, location
ββ keyword: status, employmentType, companyIndustry, skillNames
ββ number : companyId, experienceMin, experienceMax
ββ date : updatedAt
- DB νΈλμμ
κ³Ό OpenSearch μΈλ±μ±μ λκΈ° μ²λ¦¬λ‘ λ¬ΆμΌλ©΄,
- OpenSearch μ₯μ μ β API μ₯μ μ ν
- DB μ»€λ° μ±κ³΅, But κ²μ λ°μ μ€ν¨ β μ ν©μ± λ¬Έμ
- μ±μ©κ³΅κ³ μμ±/μμ /μμ
- DB νΈλμμ 컀λ°
outbox_eventν μ΄λΈμ μ΄λ²€νΈ μ μ¬- Outbox Pollerκ° μ΄λ²€νΈλ₯Ό μ½μ΄ OpenSearch μ²λ¦¬
- Outbox μ΄λ²€νΈ μ€μΌμ€λ¬ (OutboxScheduler.kt)
outbox_event
- aggregate_type = JOB_POSTING
- event_type = UPSERT | DELETE
- payload = { "jobPostingId": 123 }
- status = PENDING / PROCESSING / DONE / FAILED
- Outbox μΆκ° μ΄μ μ μ΄λ―Έ μ‘΄μ¬νλ DB λ°μ΄ν°μ λν΄ OpenSearch μΈλ±μ±
- λ‘컬/μ€μ΅ νκ²½μμ
ApplicationRunnerκΈ°λ°BackfillRunnerμΆκ° - μ€λ¬΄ νκ²½μ΄λΌλ©΄ λ³λ λ°°μΉ/κ΄λ¦¬ 컀맨λλ‘ λΆλ¦¬
OpenSearchJobPostingSearchAdapter.kt
bool
ββ must : ν€μλ κ²μ (score κ³μ°)
ββ filter : μ ν λ§€μΉ νν° (score μν₯ μμ)
title^3
companyName^2
description
- μ λͺ© (title) κ°μ₯ μ€μ
- νμ¬λͺ 2μμ
- 본문 보쑰
- μ λ ₯ν λ¨μ΄λ₯Ό ν¬ν¨ν λ¬Έμ μ°μ κ²μ
match_all+ filter- κ²μμ΄ μλ νν°λ λͺ©λ‘ μ©λ
| νλͺ© | RDB | OpenSearch |
|---|---|---|
| μ ν λ§€μΉ | κ°ν¨ | κ°ν¨ |
| ν€μλ κ²μ | μ νμ | λ§€μ° κ°ν¨ |
| κ΄λ ¨λ μ λ ¬ | μ΄λ €μ | μ 곡 |
| νμ₯μ± | μ ν | λμ |
| κ²μ μλ | λΉ λ¦ | λ³΄ν΅ |
- 1000λͺ
μ μ¬μ©μκ° 20μ΄λμ μμ²νλ μλ리μ€
- RDB μ‘°ν κΈ°λ₯ μ±λ₯ν μ€νΈ k6 μ€ν¬λ¦½νΈ: rdb.js
- OpenSearch κ²μ κΈ°λ₯ μ±λ₯ν μ€νΈ k6 μ€ν¬λ¦½νΈ: opensearch.js
| RDB | OpenSearch |
|---|---|
![]() |
![]() |
- p95 κΈ°μ€ μ½ 1.76λ°° RDB μ‘°ν κΈ°λ₯μ΄ λ λΉ λ¦ (477ms vs 847ms)
- μμ² νλΌλ―Έν°:
?q=Java&status=OPEN&page=0&size=20
{
"items": [
{
"id": 20,
"title": "[μμ λΉ] νλ«νΌ λ°±μλ κ°λ°μ (3λ
μ΄μ~5λ
μ΄ν)",
"location": "Seoul",
"status": "μ±μ© μ€",
"createdAt": "2026-01-21T03:54:57.145811Z",
"company": {
"id": 20,
"name": "μμ λ€νΈμμ€"
},
"skills": [
"Kotlin",
"Java",
"Spring Boot",
"AWS"
]
},
{
"id": 22,
"title": "λ°±μλ μμ§λμ΄",
"location": "Seoul",
"status": "μ±μ© μ€",
"createdAt": "2026-01-21T03:54:57.145811Z",
"company": {
"id": 22,
"name": "μΈμ»¨μ λ둬"
},
"skills": [
"Kotlin",
"Java",
"Spring Boot",
"MySQL",
"PostgreSQL",
"AWS"
]
},
{
"id": 23,
"title": "μλ² κ°λ° λ΄λΉμ",
"location": "Seoul",
"status": "μ±μ© μ€",
"createdAt": "2026-01-21T03:54:57.145811Z",
"company": {
"id": 23,
"name": "νμ
μ€μ»΄νΌλ"
},
"skills": [
"Java",
"Spring Boot",
"AWS"
]
},
{
"id": 25,
"title": "[μμ΄λμ΄μ€] λ°±μλ κ°λ°μ (Java/PHP)",
"location": "Seoul",
"status": "μ±μ© μ€",
"createdAt": "2026-01-21T03:54:57.145811Z",
"company": {
"id": 25,
"name": "λ°±ν¨μ»€"
},
"skills": [
"Java",
"Spring Boot",
"AWS"
]
},
{
"id": 26,
"title": "μ»€λ¨Έμ€ λ°±μλ κ°λ° (5λ
μ΄μ)",
"location": "Seoul",
"status": "μ±μ© μ€",
"createdAt": "2026-01-21T03:54:57.145811Z",
"company": {
"id": 26,
"name": "νΌλΉμ§"
},
"skills": [
"Java",
"Spring Boot",
"Node.js",
"Python",
"FastAPI",
"Django",
"MySQL",
"PostgreSQL",
"AWS"
]
}
// μλ΅ ...
]
}{
"items": [
{
"id": 25,
"title": "[μμ΄λμ΄μ€] λ°±μλ κ°λ°μ (Java/PHP)",
"location": "Seoul",
"status": "μ±μ© μ€",
"createdAt": "2026-01-21T03:54:57.145811Z",
"company": {
"id": 25,
"name": "λ°±ν¨μ»€"
},
"skills": [
"Java",
"Spring Boot",
"AWS"
]
},
{
"id": 37,
"title": "[Tech] Java Backend Developer - μ μ°μμ€ν
μ΄μ/κ°λ°",
"location": "Seoul",
"status": "μ±μ© μ€",
"createdAt": "2026-01-21T03:54:57.145811Z",
"company": {
"id": 37,
"name": "μλνμμ"
},
"skills": [
"Java",
"Spring Boot",
"MySQL",
"MongoDB",
"Kafka",
"AWS"
]
},
{
"id": 23,
"title": "μλ² κ°λ° λ΄λΉμ",
"location": "Seoul",
"status": "μ±μ© μ€",
"createdAt": "2026-01-21T03:54:57.145811Z",
"company": {
"id": 23,
"name": "νμ
μ€μ»΄νΌλ"
},
"skills": [
"Java",
"Spring Boot",
"AWS"
]
},
{
"id": 29,
"title": "μΉ μλΉμ€ κ°λ°μ",
"location": "Gwacheon",
"status": "μ±μ© μ€",
"createdAt": "2026-01-21T03:54:57.145811Z",
"company": {
"id": 29,
"name": "μμ€ν μ΄"
},
"skills": [
"Kotlin",
"Java",
"Javascript",
"React"
]
},
{
"id": 26,
"title": "μ»€λ¨Έμ€ λ°±μλ κ°λ° (5λ
μ΄μ)",
"location": "Seoul",
"status": "μ±μ© μ€",
"createdAt": "2026-01-21T03:54:57.145811Z",
"company": {
"id": 26,
"name": "νΌλΉμ§"
},
"skills": [
"Java",
"Spring Boot",
"Node.js",
"Python",
"FastAPI",
"Django",
"MySQL",
"PostgreSQL",
"AWS"
]
}
// μλ΅ ...
]
}- OpenSearchμ κ²½μ° ν€μλ κ²μ μ νλ κ°μ€μΉμ λ°λΌ κ΄λ ¨λ κ²μ
- RDB λͺ©λ‘ μ‘°νμ κ²½μ° ν€μλκ° ν¬ν¨λμ΄ μλ λ¬Έμ μ 체 μ‘°ν
- λ°μ΄ν° κ·λͺ¨κ° 컀μ§κ±°λ ν€μλ 볡μ‘λκ° μ¦κ°νλ©΄ ν μλ‘ OpenSearch κ²μ νμ§ λμμ§

