Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 28 additions & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ jobs:
docker stop bubblog-ai || true
docker rm bubblog-ai || true

docker stop bubblog-ai-worker || true
docker rm bubblog-ai-worker || true

docker pull $IMAGE_NAME:latest

docker run -d --name bubblog-ai -p 8000:3000 \
Expand All @@ -51,4 +54,28 @@ jobs:
-e ALGORITHM="${{ secrets.ALGORITHM }}" \
-e EMBED_MODEL="${{ secrets.EMBED_MODEL }}" \
-e CHAT_MODEL="${{ secrets.CHAT_MODEL }}" \
$IMAGE_NAME:latest
-e REDIS_URL="${{ secrets.REDIS_URL }}" \
-e REDIS_HOST="${{ secrets.REDIS_HOST }}" \
-e REDIS_PORT="${{ secrets.REDIS_PORT }}" \
-e EMBEDDING_QUEUE_KEY="${{ secrets.EMBEDDING_QUEUE_KEY }}" \
-e EMBEDDING_FAILED_QUEUE_KEY="${{ secrets.EMBEDDING_FAILED_QUEUE_KEY }}" \
-e EMBEDDING_WORKER_MAX_RETRIES="${{ secrets.EMBEDDING_WORKER_MAX_RETRIES }}" \
-e EMBEDDING_WORKER_BACKOFF_MS="${{ secrets.EMBEDDING_WORKER_BACKOFF_MS }}" \
$IMAGE_NAME:latest

docker run -d --name bubblog-ai-worker \
-e OPENAI_API_KEY="${{ secrets.OPENAI_API_KEY }}" \
-e DATABASE_URL="${{ secrets.DATABASE_URL }}" \
-e SECRET_KEY="${{ secrets.SECRET_KEY }}" \
-e TOKEN_AUDIENCE="${{ secrets.TOKEN_AUDIENCE }}" \
-e ALGORITHM="${{ secrets.ALGORITHM }}" \
-e EMBED_MODEL="${{ secrets.EMBED_MODEL }}" \
-e CHAT_MODEL="${{ secrets.CHAT_MODEL }}" \
-e REDIS_URL="${{ secrets.REDIS_URL }}" \
-e REDIS_HOST="${{ secrets.REDIS_HOST }}" \
-e REDIS_PORT="${{ secrets.REDIS_PORT }}" \
-e EMBEDDING_QUEUE_KEY="${{ secrets.EMBEDDING_QUEUE_KEY }}" \
-e EMBEDDING_FAILED_QUEUE_KEY="${{ secrets.EMBEDDING_FAILED_QUEUE_KEY }}" \
-e EMBEDDING_WORKER_MAX_RETRIES="${{ secrets.EMBEDDING_WORKER_MAX_RETRIES }}" \
-e EMBEDDING_WORKER_BACKOFF_MS="${{ secrets.EMBEDDING_WORKER_BACKOFF_MS }}" \
$IMAGE_NAME:latest node dist/worker/queue-consumer.js
203 changes: 67 additions & 136 deletions TASK.md

Large diffs are not rendered by default.

19 changes: 19 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
version: "3.9"

services:
api:
build:
context: .
command: ["node", "dist/server.js"]
env_file:
- .env
ports:
- "3000:3000"
restart: unless-stopped
worker:
build:
context: .
command: ["node", "dist/worker/queue-consumer.js"]
env_file:
- .env
restart: unless-stopped
136 changes: 136 additions & 0 deletions docs/history-tasks/HybridSearchUpgradePlan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
## Hybrid Search Upgrade Plan (Working Doc)

### 1. Current Implementation Snapshot
- `runHybridSearch(question, userId, plan)` (src/services/hybrid-search.service.ts)
- Embeds `[question, ...plan.rewrites]` and runs `findSimilarChunksV2` per embedding.
- Executes `textSearchChunksV2` once using the original question + keywords.
- Merges chunk candidates by `postId:postChunk`, keeps max vector/text score per chunk, minโ€“max normalizes each modality, then fuses via `alpha` blend.
- Returns top `plan.top_k` chunks (capped 10); `plan.limit` ignored here.
- Semantic-only fallback uses same repository call without text blending (`runSemanticSearch`).
- Planner (`generateSearchPlan`) currently emits rewrites/keywords but keyword quality/quantity varies; schema clamps counts post-hoc.
- Category filters from API are not wired into hybrid search; text rewrites are not reused in lexical search; chunk key uses raw text.

### 2. Pain Points & Gaps
1. **Filtering gaps** โ€“ category/time filters partially ignored, final `limit` unused, vector threshold normalization can collapse to zero when max=min.
2. **Keyword quality** โ€“ LLM often emits multi-word phrases or duplicates; count not consistently within intended range.
3. **Rewrite redundancy** โ€“ All rewrites treated equally; no semantic-distance-aware weighting โ†’ aggressive rewrites may be undervalued or noisy ones over-weighted.
4. **Fused scoring sensitivity** โ€“ Minโ€“max normalization across union is brittle when modalities have outliers; no similarity-based bonus for high-confidence hits.
5. **Post-level UX** โ€“ Current pipeline optimized for RAG chunk retrieval; no reusable API that returns deduplicated post-level hits with pagination.
6. **Observability** โ€“ Limited metrics around rewrite effectiveness, keyword usage, or threshold activations.

### 3. Goals & Guiding Principles
- Preserve strong recall via multi-embedding + lexical hybrid while adding stability and transparency.
- Make rewrite/keyword generation purposeful: enforce concise tokens, staged semantic drift, and maintain question coverage.
- Provide a standalone hybrid search endpoint for user-facing search with post-level results.
- Instrument similarity thresholds and modality contributions to support tuning.

### 4. Retrieval Quality Enhancements (Track A)

4.1 **Similarity Threshold Boosting**
- Reuse the existing retrieval bias labels (`lexical`, `balanced`, `semantic`) to derive both `alpha` and default modality thresholds (`sem_boost_threshold`, `lex_boost_threshold`) so planner output stays compact. Defaults (retain current behavior for now):
- `lexical`: `alpha = 0.30`, `sem_boost_threshold = 0.65`, `lex_boost_threshold = 0.80`
- `balanced`: `alpha = 0.50`, `sem_boost_threshold = 0.70`, `lex_boost_threshold = 0.75`
- `semantic`: `alpha = 0.75`, `sem_boost_threshold = 0.80`, `lex_boost_threshold = 0.65`
- Encode the mapping as a single constants table (e.g., `RETRIEVAL_BIAS_PRESETS`) so both planner normalization and hybrid scoring reference identical values.
- Permit optional overrides in `plan.hybrid`, but clamp to sensible bounds (e.g., 0.4โ€“0.85) for consistency.
- When a normalized vector/text score crosses its threshold, apply a bounded boost (e.g., multiply by 1.1โ€“1.3 or add 0.1), log activations, and cap boosts to maintain ranking stability.

4.2 **Rewrite Strategy & Weighting**
- Update planner prompt to generate staged rewrites:
- `rewrite_1`: conservative paraphrase.
- `rewrite_2`: adds synonymous term / clarifies entity.
- `rewrite_3+`: higher semantic drift or alternative framing.
- After plan normalization (`search-plan.service.ts`):
- Compute embedding-based cosine similarity between original question and each rewrite.
- Drop rewrites below a floor (e.g., <0.35) or route them to lexical-only usage.
- Derive per-rewrite weights (e.g., `weight = clamp(similarity, 0.6, 1.2)`) and supply to `runHybridSearch`.
- Similarity calculations use fresh embedding API calls (no caching) for both the question and rewrites within the request.
- In hybrid service, apply weights when aggregating vector scores (weighted max/avg instead of pure max) so high-quality rewrites contribute proportionally.

4.3 **Keyword Constraints & Quality**
- Modify `planSchema` / prompt: keywords must be single Korean/English tokens (no spaces), trimmed, 1โ€“5 items.
- In normalization, enforce `.slice(0,5)`, drop tokens <2 chars or containing whitespace/punctuation (except hyphen/underscore if needed).
- Extend text search to run over `[question, ...filtered rewrites]` for lexical recall or compute textual similarity per rewrite (optional v2 step).

4.4 **Repository/Data Adjustments**
- Update `findSimilarChunksV2` / `textSearchChunksV2` to return `chunk_index`, `post_created_at`, and optionally `post_tags` for downstream boosts.
- Tag aggregation via `post_tag` โ‡” `tag` should be added only if such tables exist; otherwise return `[]` and skip joins.
- Switch dedup key to `${postId}:${chunk_index}` to avoid string-heavy keys.
- Filters wiring: Do NOT add `filters.category_ids` to the plan. Keep the plan schema limited to `filters.time`.
- Use `categoryId` from the controller as a server-side pre-filter only.
- Derive `from/to` from the normalized plan time window (label โ†’ absolute) and apply in repositories.
- Respect `plan.limit` at the final slicing stage.
- Keep retrieval as exact KNN on `pgvector` (ORDER BY `<=>`); `top_k` stays per-source fetch size while final slicing respects `plan.limit`.

<!-- moved to Backlog: see section 11 -->

### 5. Search API & Post-Level Experience (Track B)
- **Service decomposition** โ€“ Extract shared primitive `buildHybridCandidates({ question, rewrites, keywords, plan, userId, categoryId })` returning chunk-level scores + metadata + diagnostic stats.
- **Post aggregation** โ€“ Create aggregator to deduplicate by post (max score, optional average of top 2, representative snippet) and apply deterministic `limit/offset` pagination (page size default 10, max 10).
- **Public API** โ€“ Add unauthenticated REST endpoint (JSON, no SSE) such as `GET /search/hybrid` accepting question, filters, paging params; reuse the planner or a lightweight variant as UX dictates.
- **QA reuse** โ€“ `answerStreamV2` continues calling chunk-level layer; search endpoint uses same embeddings/threshold logic to avoid drift.

### 6. Prompts & Planner Improvements
- Update `buildSearchPlanPrompt` instructions:
- Require keywords to be single words, explicitly request โ€œ1~5 ๋‹จ์ผ ํ‚ค์›Œ๋“œโ€.
- Outline staged rewrite roles to nudge LLM output.
- Remind that temporal expressions stay in `filters.time`.
- Keep the client-facing `planSchema` minimal (no explicit `alpha`/threshold/weight fields). Server derives weights/thresholds internally from the retrieval bias label and does not surface them to the frontend.
- Update schema docs only for keyword bounds (1โ€“5) and any internal validation notes; no additional fields are exposed over the API.
- In normalization, log keyword count, rewrite count, threshold values to support telemetry.

### 7. Observability & Telemetry
- Structured logs/SSE events:
- For each query: number of rewrites retained, similarity weights, threshold boosts triggered, counts per modality.
- Emit metrics for search endpoint (total posts returned, pagination info, latency).
- Standardize a log payload (e.g., `type: 'retrieval.boost', bias, alpha, sem_thr, lex_thr, modality, original_score, boosted_score`) to simplify analysis and tuning.
- Add debug flags to inspect per-rewrite vector/text hit lists for evaluation.

### 8. Performance Considerations
- Generate embeddings for `[question, rewrites]` with fresh API calls per request (no caching); accept the additional cost for correctness.
- Cap total vector queries by `plan.hybrid.max_rewrites`; consider batching embeddings via OpenAI API if supported.
- Monitor effect of threshold boosts on latency; adjust SQL to prefetch needed metadata in single round-trip.

### 9. Execution Roadmap (Detailed)

**Phase 0 โ€“ Foundations & Bugfixes**
- Task 0.1: Thread request filters (`categoryId`, `limit`) through `qa.v2.service.ts`. Do NOT add `filters.category_ids` to the plan; the server applies `categoryId` as a pre-filter. Derive `from/to` solely from the normalized plan `filters.time` (label โ†’ absolute) and use in repositories.
- Task 0.2: Honor `plan.limit` when returning hybrid results, switch dedupe key to `${postId}:${chunk_index}`, and propagate `chunk_index` through types.
- Task 0.3: Expand `findSimilarChunksV2`/`textSearchChunksV2` to select `chunk_index`, `post_created_at`, and optionally aggregated `post_tags` (only if tag tables exist); update SQL joins and DTOs with safe fallbacks.
- Task 0.4: Update hybrid/semantic services to surface new metadata in SSE payloads, keeping backward compatibility for existing clients.

**Phase 1 โ€“ Planner & Prompt Hardening**
- Task 1.1: Tighten `planSchema` validation (keywords 1โ€“5 single tokens, rewrites โ‰ค max_rewrites) and normalize via shared helpers with telemetry hooks.
- Task 1.2: Revise `buildSearchPlanPrompt` instructions to enforce staged rewrites, single-token keywords, and explicit temporal guidance; add regression fixtures for prompt drift.
- Task 1.3: Implement normalization pass that cleans keywords, generates embeddings for rewrites, filters low-similarity variants, and records per-rewrite cosine similarity.
- Task 1.4: Persist summary logs (`rewrites_len`, similarity weights, keyword counts) via structured logger for observability.

**Phase 2 โ€“ Retrieval Scoring Upgrades**
- Task 2.1: Introduce `RETRIEVAL_BIAS_PRESETS` mapping (`alpha`, `sem_boost_threshold`, `lex_boost_threshold`) and clamp overrides in normalization.
- Task 2.2: Apply threshold-based boosts in `runHybridSearch`, logging activations and capping final scores for stability.
- Task 2.3: Weight vector scores by rewrite similarity (e.g., weighted max/avg) and expose diagnostics per rewrite.
- Task 2.4: Extend lexical search to iterate across `[question, rewrites]`, merging results while respecting keyword filters and avoiding redundant queries.
- Task 2.5: Enforce post-level diversity (max N chunks/post) before final ranking and respect `plan.limit` after fusion.

**Phase 3 โ€“ Search API Delivery**
- Task 3.1: Extract `buildHybridCandidates` service returning chunk-level hits plus diagnostics; retrofit QA flow to consume it.
- Task 3.2: Build post aggregation layer (score fusion, snippet selection, pagination respecting `limit/offset`) with deterministic ordering.
- Task 3.3: Add `GET /search/hybrid` route, request validation, and integration tests covering filters, pagination, and telemetry events.
- Task 3.4: Document API usage and ensure rate-limiting/auth hooks match product requirements.

**Phase 4 โ€“ Tuning & Observability**
- Task 4.1: Emit structured SSE/log events for threshold boosts, rewrite weighting, keyword pruning, and modality contributions.
- Task 4.2: Backfill dashboards or log queries (e.g., BigQuery/Redash) to monitor latency, hit counts, and boost frequency.
- Task 4.3: Create evaluation playbook with canonical queries, offline regression scripts, and guidance for tuning boost factors.
- Task 4.4: Investigate alternative fusion strategies (RRF/z-score) gated behind feature flags for safe experimentation.

### 10. Open Questions
- Do we need separate planner settings for public search vs QA (e.g., higher keyword count)?
- Should rewrite weights persist back into plan schema for transparency to the client?
- What default boost factors strike best balance between recall and precision? Requires offline eval.

---
Use this document as the anchor before implementation; update sections as design decisions finalize or metrics inform threshold choices.

### 11. Backlog
- Normalization stability (minโ€“max collapse): evaluate mitigations without immediate implementation. Candidates include constant fallback (e.g., 0.5), epsilon guards, rank-based fusion (RRF), z-score fusion, unimodal fallback, and telemetry for activation frequency.
71 changes: 71 additions & 0 deletions docs/reports/REPORT-embedding-worker.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# ๋ณด๊ณ ์„œ: Redis ํ ๊ธฐ๋ฐ˜ ์ž„๋ฒ ๋”ฉ ์›Œ์ปค ๋„์ž… ๋ฐ ๋ฐฐํฌ ๊ตฌ์„ฑ

## 1. ๊ฐœ์š”
- ๋ชฉ์ : Spring Boot โ†’ Redis โ†’ Node.js ํŒŒ์ดํ”„๋ผ์ธ์œผ๋กœ ์ž„๋ฒ ๋”ฉ ์ƒ์„ฑ์„ ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌํ•˜๊ณ , Express API์™€ ๋ถ„๋ฆฌ๋œ ์›Œ์ปค๋ฅผ ์šด์˜ํ•œ๋‹ค.
- ์ƒํƒœ: ์›Œ์ปค ์—”ํŠธ๋ฆฌํฌ์ธํŠธยทํ™˜๊ฒฝ ๋ณ€์ˆ˜ ์Šคํ‚ค๋งˆยท๋„์ปค ์ปดํฌ์ฆˆยทGitHub Actions ๋ฐฐํฌ ํ๋ฆ„๊นŒ์ง€ ๋ฐ˜์˜ ์™„๋ฃŒ.
- ๋ฒ”์œ„: ๊ธฐ์กด API ์„œ๋ฒ„ ์ฝ”๋“œ๋Š” ์œ ์ง€ํ•˜๋ฉด์„œ Redis ํ ์†Œ๋น„ ๋กœ์ง์„ ์ถ”๊ฐ€ํ•˜๊ณ , ๋‹จ์ผ Docker ์ด๋ฏธ์ง€๋กœ API/์›Œ์ปค ์ปจํ…Œ์ด๋„ˆ๋ฅผ ๋ถ„๋ฆฌ ์šด์šฉํ•œ๋‹ค.

## 2. ์›Œ์ปค ๊ตฌ์กฐ
- ํŒŒ์ผ: `src/worker/queue-consumer.ts`
- Redis ์—ฐ๊ฒฐ: `REDIS_URL`(์šฐ์„ ) ๋˜๋Š” `REDIS_HOST`/`REDIS_PORT`.
- ์ž‘์—… ํ˜•์‹: `{ postId, title?, content?, attempt? }`.
- ์ฒ˜๋ฆฌ ์ˆœ์„œ
1. `BRPOP` ์œผ๋กœ `EMBEDDING_QUEUE_KEY` ๋Œ€๊ธฐ.
2. ์ œ๋ชฉ(`storeTitleEmbedding`)๊ณผ ๋ณธ๋ฌธ(`chunkText` โ†’ `createEmbeddings` โ†’ `storeContentEmbeddings`) ์ˆœ์ฐจ ์ฒ˜๋ฆฌ.
3. ์˜ค๋ฅ˜ ์‹œ ์žฌ์‹œ๋„: `attempt` ์ฆ๊ฐ€, `EMBEDDING_WORKER_MAX_RETRIES`, `EMBEDDING_WORKER_BACKOFF_MS` ๊ธฐ๋ฐ˜ backoff, ํ•œ๊ณ„๋ฅผ ๋„˜์œผ๋ฉด `EMBEDDING_FAILED_QUEUE_KEY` ๋กœ ์ด๋™.
- ๊ธฐํƒ€: Graceful shutdown(SIGINT/SIGTERM), DebugLogger ๋กœ ์ฃผ์š” ์ด๋ฒคํŠธ ๊ธฐ๋ก.

## 3. ํ™˜๊ฒฝ ๋ณ€์ˆ˜ (์ถ”๊ฐ€ ํ•ญ๋ชฉ)
| ํ‚ค | ์šฉ๋„ | ๊ธฐ๋ณธ๊ฐ’ |
| --- | --- | --- |
| `REDIS_URL` | ์™ธ๋ถ€ Redis ์ ‘์† URL (์šฐ์„  ์‚ฌ์šฉ) | ์—†์Œ |
| `REDIS_HOST` / `REDIS_PORT` | URL ๋ฏธ์ง€์ • ์‹œ ํ˜ธ์ŠคํŠธ/ํฌํŠธ | `127.0.0.1` / `6379` |
| `EMBEDDING_QUEUE_KEY` | ์ž‘์—… ํ ์ด๋ฆ„ | `embedding:queue` |
| `EMBEDDING_FAILED_QUEUE_KEY` | ์‹คํŒจ ํ ์ด๋ฆ„ | `embedding:failed` |
| `EMBEDDING_WORKER_MAX_RETRIES` | ์ตœ๋Œ€ ์žฌ์‹œ๋„ ํšŸ์ˆ˜ | `3` |
| `EMBEDDING_WORKER_BACKOFF_MS` | ์žฌ์‹œ๋„ ๊ฐ„ ๋Œ€๊ธฐ(ms) | `5000` |

## 4. ๋„์ปค ์ด๋ฏธ์ง€ & ์‹คํ–‰
- Dockerfile ๊ธฐ๋ณธ CMD: `node dist/server.js` (Express API).
- ๋™์ผ ์ด๋ฏธ์ง€๋ฅผ ์žฌ์‚ฌ์šฉํ•˜๋˜ `docker run ... node dist/worker/queue-consumer.js` ๋กœ ์ปค๋งจ๋“œ๋ฅผ ์˜ค๋ฒ„๋ผ์ด๋“œํ•˜๋ฉด ์›Œ์ปค๊ฐ€ ์‹คํ–‰๋œ๋‹ค.
- pm2 ๋ถˆํ•„์š”: ์ปจํ…Œ์ด๋„ˆ ๋‹จ์ผ ํ”„๋กœ์„ธ์Šค ๊ฐ€์ • + Docker `restart` ์ •์ฑ…์œผ๋กœ ๋ณต๊ตฌ.

## 5. docker-compose (๊ฐœ๋ฐœ์šฉ)
```yaml
services:
api:
build: .
command: ["node", "dist/server.js"]
env_file: [.env]
ports: ["3000:3000"]
restart: unless-stopped

worker:
build: .
command: ["node", "dist/worker/queue-consumer.js"]
env_file: [.env]
restart: unless-stopped
```
- ์™ธ๋ถ€ Redis ์‚ฌ์šฉ์ด ๊ธฐ๋ณธ ์ „์ œ. ํ•„์š” ์‹œ ๊ฐœ๋ฐœ ํ™˜๊ฒฝ์—์„œ๋งŒ Redis ์„œ๋น„์Šค๋ฅผ ์ถ”๊ฐ€ํ•ด `.env` ๋ฅผ ํ•ด๋‹น ์ปจํ…Œ์ด๋„ˆ๋กœ ์ง€์ •.

## 6. GitHub Actions ๋ฐฐํฌ (main.yml)
- ์ด๋ฏธ์ง€: `${{ secrets.DOCKER_USERNAME }}/bubblog-ai:latest` ๋นŒ๋“œ/ํ‘ธ์‹œ.
- EC2 ๋ฐฐํฌ ๋‹จ๊ณ„:
1. ๊ธฐ์กด `bubblog-ai`, `bubblog-ai-worker` ์ปจํ…Œ์ด๋„ˆ ์ •์ง€/์‚ญ์ œ.
2. ์ตœ์‹  ์ด๋ฏธ์ง€ pull.
3. API ์ปจํ…Œ์ด๋„ˆ ์‹คํ–‰(๊ธฐ๋ณธ CMD).
4. ์›Œ์ปค ์ปจํ…Œ์ด๋„ˆ ์‹คํ–‰(`node dist/worker/queue-consumer.js` ๋ช…๋ น).
- ๋‘ ์ปจํ…Œ์ด๋„ˆ ๋ชจ๋‘ Redis ๋ฐ ์žฌ์‹œ๋„ ๊ด€๋ จ Secrets๋ฅผ ์ „๋‹ฌํ•˜์—ฌ ๊ตฌ์„ฑ ๋ˆ„๋ฝ์„ ๋ฐฉ์ง€.
- Secrets(์˜ˆ์‹œ): `REDIS_URL`, `REDIS_HOST`, `REDIS_PORT`, `EMBEDDING_QUEUE_KEY`, `EMBEDDING_FAILED_QUEUE_KEY`, `EMBEDDING_WORKER_MAX_RETRIES`, `EMBEDDING_WORKER_BACKOFF_MS` ๋“ฑ.

## 7. ์šด์˜ ์ฐธ๊ณ  ์‚ฌํ•ญ
- Spring Boot ํ”„๋กœ๋“€์„œ๋Š” LPUSH ๋กœ ์ž‘์—…์„ ํ์— ์ ์žฌ(์ด๋ฏธ ๊ตฌํ˜„๋จ).
- Redis ๋Š” ์™ธ๋ถ€ ์„œ๋ฒ„/๋งค๋‹ˆ์ง€๋“œ ํ™˜๊ฒฝ์„ ์‚ฌ์šฉ; ๋ณธ ํ”„๋กœ์ ํŠธ ์ปจํ…Œ์ด๋„ˆ์—์„œ๋Š” Consumer ์—ญํ• ๋งŒ ์ˆ˜ํ–‰.
- ์‹คํŒจ ํ(`embedding:failed`) ๋ชจ๋‹ˆํ„ฐ๋ง ๋ฐ ์žฌ์ฒ˜๋ฆฌ(์˜ˆ: RPOP โ†’ LPUSH โ†’ ์žฌ์‹œ๋„ ์Šค์ผ€์ค„๋Ÿฌ) ์ „๋žต ํ•„์š”.
- API ์ปจํ…Œ์ด๋„ˆ์—์„œ Redis ๋ณ€์ˆ˜๊ฐ€ ํ•„์š”ํ•˜์ง€๋Š” ์•Š์ง€๋งŒ, ๋น„์ƒ์‹œ ์ปค๋งจ๋“œ ์˜ค๋ฒ„๋ผ์ด๋“œ๋ฅผ ๋Œ€๋น„ํ•ด ๊ณตํ†ต์œผ๋กœ ์ฃผ์ž…ํ•ด ๋‘” ์ƒํƒœ.

## 8. ํ–ฅํ›„ ์ฒดํฌ๋ฆฌ์ŠคํŠธ
- [ ] ์Šคํ…Œ์ด์ง• ํ™˜๊ฒฝ์—์„œ Redis/DB ์—ฐ๊ฒฐ ๋ฐ ์ž„๋ฒ ๋”ฉ ์ €์žฅ ์„ฑ๊ณต ์—ฌ๋ถ€ ๊ฒ€์ฆ.
- [ ] ์‹คํŒจ ํ ๋ชจ๋‹ˆํ„ฐ๋ง/์•Œ๋ฆผ ๊ตฌ์„ฑ.
- [ ] ์›Œ์ปค ์Šค์ผ€์ผ ์•„์›ƒ ์ „๋žต ์ •์˜ (์ปจํ…Œ์ด๋„ˆ ์ˆ˜ ํ™•์žฅ ์‹œ ์ฒ˜๋ฆฌ ์ถฉ๋Œ ์—†๋Š”์ง€ ํ™•์ธ).
- [ ] Redis ์ ‘๊ทผ ์ œ์–ด/TLS ์—ฌ๋ถ€ ์ ๊ฒ€.
Loading