다차원 검색을 수행하는 데이터베이스 (1) 환경 구축 및 통신 규약 설정

Pt J·2026년 5월 25일
post-thumbnail

다차원 검색을 수행하는 데이터베이스 (1) 전체적인 시스템 및 데이터베이스 설계

다차원 검색을 실습하기 위해 감정 단어 사전을 구현해 보자.
감정 단어는 논문 한국어 감정 단어의 목록 작성과 차원 탐색을 참고했다.

🤖 AI AGENT | INTRO

감정 단어 사전은 단순한 텍스트 매칭을 넘어, 1) 단어의 의미적/차원적 유사도(정서가, 각성가 등), 2) 계층적 분류(Taxonomy), 3) 텍스트 검색(FTS) 이 복합적으로 이루어져야 합니다. 이를 위해 각 언어와 기술의 장점을 극대화하는 마이크로서비스 아키텍처(gRPC 통신)를 채택하겠습니다.

1. 시스템 아키텍처 (Architecture)

  • Database (PostgreSQL 18 - Docker 기반)
    • 다차원 감정 수치(VA: Valence, Arousal) 검색을 위해 pgvector 확장 기능을 사용하여 코사인/유클리디안 거리 기반 유사도 검색을 수행합니다.
    • 감정의 계층 구조(Taxonomy, 예: 기쁨 > 환희 > 벅참)를 질의하기 위해 ltree 확장을 사용합니다.
    • 한국어 형태소 분석 기반의 Full-Text Search(FTS)를 적용합니다.
  • Core Engine (Rust 2024 Edition)
    • DB와 직접 통신하며 복잡한 다차원 쿼리 빌딩 및 고속 연산을 담당합니다.
    • Python 게이트웨이와는 gRPC (Tonic 0.14+)를 통해 통신합니다.
    • 안전성, 고성능, 동시성 처리를 보장하며 우아한 종료(Graceful Shutdown) 및 구조화된 로깅(tracing)을 구현합니다.
  • API Gateway (Python 3.12 + FastAPI + uv)
    • 클라이언트(웹/모바일)의 HTTP REST 요청을 받아 Rust gRPC 엔진으로 전달합니다.
    • 요청하신 UTF8ORJSONResponse 클래스를 기본으로 채택하여 직렬화 성능을 극대화하고 한글 깨짐을 방지합니다.
    • 환경 변수(pydantic-settings)로 API Key 등 보안 요소를 엄격하게 관리합니다.
  • Frontend (Svelte + TypeScript + D3.js/Vis.js)
    • 향후 Force-Directed Graph 시각화를 통해 감정 노드 간의 관계성 및 동적 재배치를 구현합니다.

2. 데이터베이스 스키마 설계

데이터베이스 초기화 스크립트는 프로젝트 루트가 아닌 scripts/db/ 디렉토리에 보관할 예정입니다. 설계될 스키마의 핵심은 다음과 같습니다.

컬럼명데이터 타입설명 (목적)
idUUID각 감정 단어의 고유 식별자 (Primary Key)
wordVARCHAR감정 단어 자체 (예: '기쁘다', '슬프다')
definitionTEXT사전적 정의 및 부가 설명 (Full-Text Search 대상)
taxonomy_pathLTREE계층적 분류 경로 (예: 긍정.활력.기쁨.환희) - 하위 카테고리 고속 탐색용
va_vectorVECTOR(2)정서가(Valence), 각성가(Arousal)의 2차원 벡터 - 유사 감정(K-NN) 검색용
search_vectorTSVECTORworddefinition을 결합한 Full-Text Search 인덱스 벡터

3. 디렉토리 구조 (예상)

emotion-dict/
├── scripts/
│   └── database/
│       ├── 01_init_extensions.sql    # pgvector, ltree 확장 설치
│       └── 02_create_tables.sql      # 스키마 및 인덱스 정의
├── proto/
│   └── emotion_search.proto          # Rust와 Python이 공유할 gRPC 인터페이스
├── rust-engine/                      # Rust (Tonic gRPC Server)
├── python-gateway/                   # Python (FastAPI + uv)
└── frontend-app/                     # Svelte + TS (추후 구현)

데이터베이스 환경 구축

compose.yaml

여기선 postgres:16-alpine 가 아니라
pgvector가 포함된 PostgreSQL 이미지를 사용한다.

compose.yaml

services:
  db:
    image: pgvector/pgvector:pg18
    container_name: emotion_db
    env_file:
      - .env
    ports:
      - "5432:5432"
    volumes:
      - pg_data:/var/lib/postgresql
      - ./scripts/database:/docker-entrypoint-initdb.d
    restart: unless-stopped
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"]
      interval: 5s
      timeout: 5s
      retries: 5

volumes:
  pg_data:
🤖 AI AGENT | docker compose 표준

compose.yaml은 현대 Docker Compose(V2) 스펙의 공식 표준 명칭입니다.

  • docker-compose.yml (과거):
    과거 파이썬으로 작성되었던 독립 실행형 도구인 docker-compose (V1) 시절의 기본 파일명이었습니다.
  • compose.yaml (현재/미래):
    Docker가 Compose 기능을 Go 언어로 재작성하여 Docker CLI 엔진에 자체 통합(docker compose - 하이픈 없음)하면서, Compose Specification이라는 오픈 스펙을 제정했습니다. 이 스펙의 공식 권장 파일명이 compose.yaml 입니다. Docker 외의 다른 컨테이너 오케스트레이션 도구(예: Podman)들도 이 스펙을 따르기 때문에, 도구 종속성을 없애기 위해 'docker-' 접두사를 제거한 것입니다.

따라서 현대적인 환경(최신 Docker)에서는 compose.yaml 을 사용하는 것이 기술 표준에 부합합니다.

이 또한 지금까지는 놓치고 있던 부분인데,
새로 작성하는 코드에서 굳이 오래된 표준을 따를 이유는 없으니
앞으로는 보다 현대적인 방식으로 작성하도록 하자.

.env

보안과 유연성을 위해 프로젝트 루트에 환경변수 파일을 작성한다.
늘 이야기하는 거지만 이것은 공부 기록용이라 이렇게 올려 놓는 거지
실무에서는 환경변수 파일을 어딘가에 업로드하거나 유출하지 않도록 주의하자.

.env

# PostgreSQL Docker
POSTGRES_USER=admin
POSTGRES_PASSWORD=ku201711424
POSTGRES_DB=emotion_dict
POSTGRES_PORT=5432

# 데이터베이스 연결 정보
DATABASE_URL=postgres://admin:ku201711424@localhost:5432/emotion_dict

# gRPC 서버 바인딩 주소
GRPC_HOST=127.0.0.1:50051

01_init_extensions.sql

스크립트 파일은 실행 순서를 파악하기 위해 이름 앞에 넘버링을 붙이기도 한다.

먼저 데이터베이스를 위한 확장을 활성화하는 스크립트를 작성한다.

scripts/database/01_init_extensions.sql

-- pgvector: 다차원 벡터(VAD) 검색용 유클리디안/코사인 거리 계산 확장
CREATE EXTENSION IF NOT EXISTS vector;

-- ltree: 트리 형태의 계층적 데이터(Taxonomy)를 효율적으로 저장하고 검색하는 확장
CREATE EXTENSION IF NOT EXISTS ltree;

-- pg_trgm: Full-Text Search를 보조하기 위한 트라이그램 유사도 검색 확장 (오타 교정 등에 유용)
CREATE EXTENSION IF NOT EXISTS pg_trgm;
🤖 AI AGENT | PostgreSQL의 확장(Extension)

PostgreSQL은 코어 엔진을 가볍고 안정적으로 유지하면서, 필요한 기능을 플러그인처럼 뗐다 붙였다 할 수 있는 강력한 확장 시스템을 갖추고 있습니다.

RDBMS의 기본 기능(B-Tree 인덱스, 기본 데이터 타입, 표준 SQL 연산)만으로는 다차원 벡터의 코사인 유사도를 계산하거나(pgvector), 계층형 트리 구조를 탐색하거나(ltree), 텍스트의 오타를 교정하며 검색하는(pg_trgm) 고연산 작업을 효율적으로 수행하기 어렵습니다. 확장을 활성화하면 새로운 데이터 타입, 인덱스 알고리즘, 내장 함수가 데이터베이스 엔진에 추가됩니다.

01_02_로 스크립트를 분리했는가?

테이블(02_create_tables.sql)을 생성할 때 vector(2)이나 ltree 같은 커스텀 데이터 타입을 사용해야 합니다. 확장이 먼저 데이터베이스 시스템에 설치(활성화)되어 있지 않으면 테이블 생성 스크립트가 데이터 타입을 인식하지 못하고 런타임 에러를 뱉습니다. 따라서 인프라 초기화 시 의존성 순서를 명확히 하고자 분리하는 것이 실무적인 표준입니다.

02_create_tables.sql

다음으로, 테이블을 생성하는 스크립트를 작성한다.

scripts/database/02_create_tables.sql

-- 감정 사전 테이블 생성
CREATE TABLE emotions (
    -- 최적화: B-Tree 인덱스 파편화를 방지하고 순차적 I/O를 유도하는 네이티브 uuid_v7() 사용
    id UUID PRIMARY KEY DEFAULT uuidv7(),
    word VARCHAR(100) NOT NULL UNIQUE,
    definition TEXT NOT NULL,
    taxonomy_path ltree NOT NULL,
    va_vector vector(2) NOT NULL,
    search_vector tsvector GENERATED ALWAYS AS (
        to_tsvector('simple', word || ' ' || definition)
    ) STORED
);

-- ==========================================
-- 데이터베이스 메타데이터 문서화
-- ==========================================
COMMENT ON TABLE emotions IS '다차원 감정 단어 사전 테이블';
COMMENT ON COLUMN emotions.id IS '고유 식별자 (UUID v7: 시간순 정렬로 B-Tree 최적화 적용)';
COMMENT ON COLUMN emotions.word IS '감정 단어 (예: 기쁨, 슬픔)';
COMMENT ON COLUMN emotions.definition IS '사전적 정의 및 부가 설명';
COMMENT ON COLUMN emotions.taxonomy_path IS '계층적 분류 경로 (ltree 구조, 예: positive.high_arousal.joy)';
COMMENT ON COLUMN emotions.va_vector IS '정서가, 각성가 2차원 벡터 (HNSW 인덱스 적용)';
COMMENT ON COLUMN emotions.search_vector IS '단어 및 정의 기반 Full-Text Search를 위한 벡터 (자동 생성 및 저장)';

-- ==========================================
-- 인덱스 생성
-- ==========================================
-- 1. 벡터 검색 인덱스 (HNSW)
-- IVFFlat보다 구축은 느리지만 검색 속도(Recall)와 성능이 압도적으로 우수한 최신 알고리즘입니다.
-- vector_cosine_ops: 코사인 유사도 기반 검색을 최적화합니다.
CREATE INDEX idx_emotions_va_vector ON emotions USING hnsw (va_vector vector_cosine_ops);
-- 2. 계층(Taxonomy) 검색 인덱스 (GiST)
-- ltree 데이터 타입에 대한 계층 질의(예: 특정 노드의 하위 노드 모두 찾기)를 고속화합니다.
CREATE INDEX idx_emotions_taxonomy ON emotions USING gist (taxonomy_path);
-- 3. Full-Text Search 인덱스 (GIN)
-- TSVector 텍스트 매칭을 비약적으로 빠르게 만듭니다.
CREATE INDEX idx_emotions_search_vector ON emotions USING gin (search_vector);

스크립트 실행

~/workspace/emotion-dict$ docker compose up -d
~/workspace/emotion-dict$ docker exec -i emotion_db psql -U admin -d emotion_dict < scripts/database/01_init_extensions.sql
~/workspace/emotion-dict$ docker exec -i emotion_db psql -U admin -d emotion_dict < scripts/database/02_create_tables.sql

gRPC 인터페이스 정의

프로젝트 루트에 proto 디렉토리를 만들고 인터페이스를 정의한다.
여기서 정의한 인터페이스는 Rust와 Python에서 각각 적절하게 가공하여 사용한다.

proto/emotion_search.proto

syntax = "proto3";
package pb.emotion;

// 감정 단어 하나의 정보를 담는 메시지
message Emotion {
    string id = 1;                    // UUID
    string word = 2;                  // 감정 단어 이름
    string definition = 3;            // 사전적 정의
    string taxonomy_path = 4;         // ltree 경로
    repeated float va_vector = 5;    // [Valance, Arousal] 2차원 벡터
}

// 다차원(VA) 유사도 검색 요청
message VectorSearchRequest {
    repeated float target_vector = 1; // VA 쿼리
    int32 limit = 2;                  // 반환할 최대 결과 수
}

// 계층 기반 검색 요청
message TaxonomySearchRequest {
    string path_query = 1;            // ltree 쿼리
    int32 limit = 2;                  // 반환할 최대 결과 수
}

// 검색어 기반 검색 요청
message TextSearchRequest {
    string query = 1;                 // 검색어
    int32 limit = 2;                  // 반환할 최대 결과 수
}

// 검색 결과 응답
message SearchResponse {
    repeated Emotion emotions = 1;    // 해당하는 감정 목록
}

// gRPC 서비스 정의
service EmotionSearchService {
    // VAD 벡터 기반 가장 유사한 감정 검색 (K-NN)
    rpc SearchByVector (VectorSearchRequest) returns (SearchResponse);
   
    // Taxonomy 계층 구조 기반 감정 검색
    rpc SearchByTaxonomy (TaxonomySearchRequest) returns (SearchResponse);
  
    // 사전적 정의 및 단어 기반 텍스트 검색
    rpc SearchByText (TextSearchRequest) returns (SearchResponse);
}

검색 방식은 다양해도 응답 형식은 동일하다.
이와 같은 단일 응답 모델을 사용하면
프론트엔드/게이트웨이의 파싱 로직을 단순화할 수 있으며
향후 복합 검색이 필요해지면 새로운 RPC 메서드를 쉽게 추가할 수 있다.

🤖 AI AGENT | gRPC에서 pb의 의미

pb 는 Protocol Buffers의 약자로, 구글에서 개발한 이진 직렬화 포맷입니다. gRPC 통신의 핵심 언어(IDL)이자 데이터 형식입니다. 언어에 상관없이 데이터를 구조화하고 전송하는 공통 규약 역할을 합니다.

Rust와 Python 양쪽에서 pb 라는 네이밍은 "자동 생성된 gRPC/Protobuf 코드"를 일반 비즈니스 로직과 격리하기 위한 관습이자 시스템적 특징으로 나타납니다.

  • Rust에서의 pb (crate::pb):
    Rust 비즈니스 로직에 자동 생성된 구조체들이 섞이면 코드 가독성과 모듈 관리가 매우 지저분해집니다. 따라서 package pb.emotion; 처럼 선언하여, tonic-build 가 컴파일 타임에 생성하는 모든 서버 트레이트, 클라이언트, 데이터 구조체를 pb 라는 격리된 모듈 안에 몰아넣습니다. 개발자는 use crate::pb::emotion::EmotionSearchServer; 처럼 명시적으로 가져다 쓰게 됩니다.
  • Python에서의 pb (_pb2.py, _pb2_grpc.py):
    Python의 grpcio-tools (protoc)로 코드를 생성하면, proto 파일 명 뒤에 _pb2.py 가 붙습니다. (예: emotion_search_pb2.py). 여기서 2 는 Protocol Buffers V2 API 아키텍처를 의미합니다. (문법을 syntax = "proto3" 로 명시하더라도, 내부 생성기 아키텍처의 레거시 네이밍 규칙 때문에 여전히 _pb2 가 붙습니다). Python에서도 이 파일들은 직접 수정하지 않고 임포트해서 사용하는 인터페이스 모듈임을 나타냅니다.

이전 실습에서는 gRPC를 처음 다뤄보느라 이런 모듈 관리를 놓쳤지만
앞으로는 신경써서 작성하도록 하자.

데이터 삽입

데이터를 삽입하는 스크립트를 생성하여 434개의 감정 단어를 집어 넣는다.
우리가 사용하고 있는 uv 패키지 관리자는 PEP 723: Inline Script Metadata 를 지원하여,
복잡하게 가상환경을 만들거나 pyproject.toml 을 구성할 필요 없이
스크립트 파일 상단에 필요한 패키지를 명시하면
uv 가 실행 시점에 격리된 환경을 자동으로 구성하고 실행한 뒤 정리해 준다.

미리 정리해 놓은 감정 데이터 CSV의 내용을
Python 스크립트를 통해 DB에 삽입한다.

scripts/data_pipeline/seed_emotions.py

# /// script
# requires-python = ">=3.12"
# dependencies = [
#     "asyncpg",
#     "python-dotenv",
# ]
# ///

import asyncio
import csv
import logging
import signal
import sys
import os
from pathlib import Path
from typing import List, Tuple

import asyncpg
from dotenv import load_dotenv

# 로깅 및 환경 변수 설정
logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s [%(levelname)s] %(message)s",
    datefmt="%Y-%m-%d %H:%M:%S",
)
logger = logging.getLogger(__name__)

# 스크립트(scripts/data_pipeline/seed_emotions.py) 위치 기준 루트 디렉토리
BASE_DIR = Path(__file__).resolve().parent.parent.parent
load_dotenv(dotenv_path=BASE_DIR / ".env")

CSV_FILE_PATH = BASE_DIR / "data" / "emotions.csv"

# 우아한 종료 (Graceful Shutdown)
shutdown_event = asyncio.Event()

def handle_sigint(sig, frame):
    logger.warning("Ctrl+C 감지: 데이터 적재를 중단하고 리소스를 정리합니다...")
    shutdown_event.set()

signal.signal(signal.SIGINT, handle_sigint)

# 비즈니스 로직
def normalize_vad(score: float) -> float:
    return round((score - 5.0) / 4.0, 4)

def determine_taxonomy(valence: float, arousal: float) -> str:
    val_path = "positive" if valence > 0 else "negative" if valence < 0 else "neutral"
    aro_path = "high_arousal" if arousal > 0 else "low_arousal" if arousal < 0 else "neutral_arousal"
    return f"{val_path}.{aro_path}"

async def seed_from_local_csv():
    db_url = os.getenv("DATABASE_URL")
    if not db_url:
        logger.error("DATABASE_URL 환경 변수가 설정되지 않았습니다.")
        sys.exit(1)

    if not CSV_FILE_PATH.exists():
        logger.error(f"데이터 파일을 찾을 수 없습니다: {CSV_FILE_PATH}")
        sys.exit(1)

    records_to_insert: List[Tuple] = []
    logger.info(f"로컬 파일({CSV_FILE_PATH.name})에서 데이터를 파싱합니다...")
  
    try:
        with open(CSV_FILE_PATH, mode="r", encoding="utf-8") as f:
            reader = csv.DictReader(f)
            for row_idx, row in enumerate(reader, start=1):
                try:
                    word = row.get("단어", "").strip()
                    definition = row.get("의미", "사전적 정의 없음").strip()
                    v_raw = float(row.get("쾌-불쾌", 5.0))
                    a_raw = float(row.get("활성화", 5.0))

                    if not word: continue

                    v_norm = normalize_vad(v_raw)
                    a_norm = normalize_vad(a_raw)
                  
                    taxonomy = determine_taxonomy(v_norm, a_norm)
                    va_vector = f"[{v_norm}, {a_norm}]"

                    records_to_insert.append((word, definition, taxonomy, va_vector))
                except ValueError as e:
                    logger.warning(f"{row_idx}번째 행 파싱 오류: {e}")
    except Exception as e:
        logger.error(f"파일 읽기 실패: {e}")
        return

    logger.info(f"총 {len(records_to_insert)}개의 데이터를 파싱 완료했습니다.")
    if shutdown_event.is_set(): return

    logger.info("데이터베이스에 연결하여 벌크 삽입을 시작합니다...")
    conn = None
    try:
        conn = await asyncpg.connect(db_url)
        query = """
            INSERT INTO emotions (word, definition, taxonomy_path, va_vector)
            VALUES ($1, $2, $3::ltree, $4::vector)
            ON CONFLICT (word) DO NOTHING;
        """
        await conn.executemany(query, records_to_insert)
        logger.info("데이터 삽입이 완벽하게 처리되었습니다!")
    except asyncpg.PostgresError as e:
        logger.error(f"데이터베이스 에러 발생: {e}")
    finally:
        if conn:
            await conn.close()
            logger.info("데이터베이스 연결을 안전하게 종료했습니다.")

if __name__ == "__main__":
    try:
        asyncio.run(seed_from_local_csv())
    except KeyboardInterrupt:
        pass

스크립트 실행 후 데이터를 확인한다.

~/workspace/emotion-dict$ uv run scripts/data_pipeline/seed_emotions.py
~/workspace/emotion-dict$ docker exec -it emotion_db psql -U admin -d emotion_dict -c "SELECT * FROM emotions LIMIT 10;" 
                  id                  |     word     |                                             definition                                              |     taxonomy_path     |     va_vector     |                                                                                  search_vector                                                                                   
--------------------------------------+--------------+-----------------------------------------------------------------------------------------------------+-----------------------+-------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 019e57dc-1f02-767d-9cf5-67f5da396f7a | 가뜬하다     | 마음이 가볍고 상쾌하다. ‘가든하다’보다 센 느낌을 준다.                                              | negative.low_arousal  | [-0.1225,-0.3]    | '가든하다':5 '가뜬하다':1 '가볍고':3 '느낌을':8 '마음이':2 '보다':6 '상쾌하다':4 '센':7 '준다':9
 019e57dc-1f02-7fac-8cd3-b78cde341585 | 가련하다     | 가엾고 불쌍하다.                                                                                    | negative.low_arousal  | [-0.4725,-0.4575] | '가련하다':1 '가엾고':2 '불쌍하다':3
 019e57dc-1f03-71bc-9ccf-2fb5c91442f1 | 가소롭다     | 같잖아서 우스운 데가 있다.                                                                          | negative.low_arousal  | [-0.63,-0.2725]   | '가소롭다':1 '같잖아서':2 '데가':4 '우스운':3 '있다':5
 019e57dc-1f03-7309-afa4-7a85aa739ab2 | 가엾다       | 마음이 아플 만큼 안되고 처연하다.                                                                   | negative.low_arousal  | [-0.5,-0.4175]    | '가엾다':1 '마음이':2 '만큼':4 '아플':3 '안되고':5 '처연하다':6
 019e57dc-1f03-749a-b1bc-3a74eeb8e74c | 가증스럽다   | 몹시 괘씸하고 얄밉다.                                                                               | negative.high_arousal | [-0.78,0.02]      | '가증스럽다':1 '괘씸하고':3 '몹시':2 '얄밉다':4
 019e57dc-1f03-7669-b93e-42a4e162643e | 가책         | 자기나 남의 잘못에 대하여 꾸짖어 책망함, 몹시 심하게 꾸짖음.                                        | negative.low_arousal  | [-0.6125,-0.1975] | '가책':1 '꾸짖어':6 '꾸짖음':10 '남의':3 '대하여':5 '몹시':8 '심하게':9 '자기나':2 '잘못에':4 '책망함':7
 019e57dc-1f03-7843-a905-f9643bedce46 | 갈등하다     | 두 가지 이상의 상반되는 요구나 욕구, 기회 또는 목표에 직면하였을 때, 선택을 하지 못하고 괴로워하다. | negative.low_arousal  | [-0.6025,-0.045]  | '가지':3 '갈등하다':1 '괴로워하다':16 '기회':8 '두':2 '때':12 '또는':9 '목표에':10 '못하고':15 '상반되는':5 '선택을':13 '요구나':6 '욕구':7 '이상의':4 '직면하였을':11 '하지':14
 019e57dc-1f03-7abe-b9d0-222a47a9f09c | 감개         | 어떤 감동이나 느낌이 마음 깊은 곳에서 배어 나옴. 또는 그 감동이나 느낌.                             | negative.low_arousal  | [-0.01,-0.19]     | '감개':1 '감동이나':3,12 '곳에서':7 '그':11 '깊은':6 '나옴':9 '느낌':13 '느낌이':4 '또는':10 '마음':5 '배어':8 '어떤':2
 019e57dc-1f03-7ccd-8df8-8d31b88f5f1e | 감개무량하다 | 마음속에서 느끼는 감동이나 느낌이 끝이 없다.                                                        | negative.low_arousal  | [-0.0075,-0.0425] | '감개무량하다':1 '감동이나':4 '끝이':6 '느끼는':3 '느낌이':5 '마음속에서':2 '없다':7
 019e57dc-1f04-7065-9f51-17b296dc4a3e | 감격하다     | 마음에 깊이 느끼어 크게 감동하다. 고마움을 깊이 느끼다.                                             | positive.high_arousal | [0.1075,0.0475]   | '감격하다':1 '감동하다':6 '고마움을':7 '깊이':3,8 '느끼다':9 '느끼어':4 '마음에':2 '크게':5
(10 rows)

~/workspace/emotion-dict$ docker exec -it emotion_db psql -U admin -d emotion_dict -c "SELECT COUNT(*) FROM emotions;" 
 count 
-------
   434
(1 row)
profile
Peter J Online Space - since July 2020 | 아무데서나 채용해줬으면 좋겠다

0개의 댓글