Key-Value

키-값 데이터베이스(Key-Value Database)는 NoSQL 데이터베이스의 한 유형으로, 간단하고 효율적인 데이터 저장 및 검색 방식을 제공한다.

키-값 데이터베이스는 단순성과 고성능을 바탕으로 특정 사용 사례에서 탁월한 선택이 될 수 있다. 그러나 복잡한 쿼리나 관계형 데이터 모델이 필요한 경우에는 적합하지 않을 수 있으므로, 프로젝트의 요구사항을 신중히 고려하여 선택해야 한다.

Key-Value Database
https://www.geeksforgeeks.org/features-of-key-value-store-in-nosql/

기본 개념

키-값 데이터베이스는 연관 배열(Associative Array) 또는 해시 테이블(Hash Table)과 유사한 구조를 가진다.
각 데이터 항목은 고유한 키(Key)와 그에 연결된 값(Value)의 쌍으로 구성된다.

주요 특징

  1. 단순성: 키-값 쌍의 간단한 구조로 인해 사용이 쉽고 직관적이다.
  2. 유연성: 값에 다양한 데이터 타입을 저장할 수 있어 스키마 변경이 자유롭다.
  3. 확장성: 분산 저장이 용이하여 수평적 확장성이 뛰어나다.
  4. 고성능: 단순한 구조로 인해 빠른 읽기와 쓰기 연산이 가능하다.
  5. 스키마리스: 미리 정의된 스키마 없이 데이터를 저장할 수 있다.

작동 방식

  1. 데이터 저장: 애플리케이션이 키와 값을 데이터베이스에 전송한다.
  2. 데이터 검색: 키를 사용하여 저장된 값을 빠르게 조회한다.
  3. 해시 함수: 많은 키-값 데이터베이스는 내부적으로 해시 함수를 사용하여 데이터를 효율적으로 저장하고 검색한다.

사용 사례

키-값 데이터베이스는 다음과 같은 상황에서 주로 사용된다:

  1. 캐싱: 자주 접근하는 데이터의 빠른 검색을 위한 캐시 시스템.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    
    class CacheManager:
        def __init__(self, redis_client):
            self.redis = redis_client
    
        def get_or_compute(self, key, compute_func, ttl=300):
            # 캐시된 값이 있으면 반환
            cached_value = self.redis.get(key)
            if cached_value:
                return json.loads(cached_value)
    
            # 없으면 계산하고 캐시에 저장
            computed_value = compute_func()
            self.redis.setex(key, ttl, json.dumps(computed_value))
            return computed_value
    
  2. 세션 관리: 웹 애플리케이션의 사용자 세션 정보 저장.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    
    class SessionManager:
        def __init__(self, redis_client):
            self.redis = redis_client
    
        def create_session(self, user_id):
            session_id = generate_unique_id()
            session_data = {
                "user_id": user_id,
                "created_at": datetime.now().isoformat(),
                "last_accessed": datetime.now().isoformat()
            }
            # 세션 저장 (2시간 유효)
            self.redis.setex(f"session:{session_id}", 7200, json.dumps(session_data))
            return session_id
    
        def get_session(self, session_id):
            data = self.redis.get(f"session:{session_id}")
            return json.loads(data) if data else None
    
  3. 사용자 프로필: 사용자 관련 정보의 빠른 저장 및 검색.

  4. 장바구니: 전자상거래 사이트의 장바구니 데이터 관리.

  5. 실시간 분석: 빠른 데이터 접근이 필요한 분석 시나리오.

장점

  1. 높은 성능: 단순한 구조로 인한 빠른 읽기/쓰기 연산.
  2. 확장성: 분산 시스템에서의 쉬운 확장.
  3. 유연성: 다양한 데이터 형식 지원.
  4. 간단한 API: 사용하기 쉬운 인터페이스 제공.

단점

  1. 제한된 쿼리 기능: 복잡한 쿼리나 조인 연산의 어려움.
  2. 데이터 일관성: 일부 시스템에서 강력한 일관성 보장의 어려움.
  3. 값 기반 검색의 한계: 키를 통한 검색만 효율적으로 수행 가능.

주요 키-값 데이터베이스 시스템

  1. Redis: 인메모리 데이터 구조 저장소로, 다양한 데이터 타입 지원.
  2. Amazon DynamoDB: AWS에서 제공하는 완전 관리형 NoSQL 데이터베이스.
  3. Riak: 고가용성과 내결함성을 갖춘 분산 데이터베이스.
  4. Memcached: 분산 메모리 캐싱 시스템.

데이터 타입과 연산

  1. 문자열 연산

    1
    2
    3
    4
    5
    6
    7
    
    # 단순 문자열 저장/조회
    redis_client.set("message", "Hello, World!")
    message = redis_client.get("message")
    
    # 증분/감분 연산 (숫자 문자열의 경우)
    redis_client.incr("counter")
    redis_client.decr("counter")
    
  2. 리스트 연산

    1
    2
    3
    4
    
    # 리스트 조작
    redis_client.lpush("queue:tasks", "task1")
    redis_client.rpush("queue:tasks", "task2")
    task = redis_client.lpop("queue:tasks")
    
  3. 해시 연산

    1
    2
    3
    4
    
    # 해시 데이터 처리
    redis_client.hset("user:1001", "name", "Kim")
    redis_client.hset("user:1001", "email", "kim@email.com")
    name = redis_client.hget("user:1001", "name")
    

최적화 기법

  1. 파이프라이닝

    1
    2
    3
    4
    5
    6
    
    # 여러 명령을 한 번에 실행
    with redis_client.pipeline() as pipe:
        pipe.set("key1", "value1")
        pipe.set("key2", "value2")
        pipe.set("key3", "value3")
        pipe.execute()
    
  2. 메모리 관리

    1
    2
    3
    
    # 메모리 정책 설정
    redis_client.config_set("maxmemory-policy", "allkeys-lru")
    redis_client.config_set("maxmemory", "100mb")
    

주의사항과 제한사항

  1. 데이터 일관성

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    
    # 트랜잭션 처리
    def transfer_points(from_user, to_user, points):
        with redis_client.pipeline() as pipe:
            pipe.watch(f"user:{from_user}:points", f"user:{to_user}:points")
    
            # 포인트 확인
            current_points = int(pipe.get(f"user:{from_user}:points") or 0)
            if current_points < points:
                return False
    
            # 포인트 이동
            pipe.multi()
            pipe.decrby(f"user:{from_user}:points", points)
            pipe.incrby(f"user:{to_user}:points", points)
            pipe.execute()
            return True
    
  2. 복잡한 쿼리의 한계
    Key-Value 데이터베이스는 복잡한 쿼리나 조인 연산을 직접 지원하지 않기 때문에, 이러한 기능이 필요한 경우 애플리케이션 레벨에서 구현해야 한다.


참고 및 출처