Computer >> 컴퓨터 >  >> 프로그래밍 >> 데이터베이스

Redis 해시(Hash) 완벽 가이드: 기본 명령어부터 실전 예제까지

Redis 해시(Hash) 완벽 가이드: 기본 명령어부터 실전 예제까지

Redis의 해시(Hash)는 하나의 키 아래에 여러 필드-값(field-value) 쌍을 저장하는 자료 구조입니다. 필드와 값은 모두 문자열로 저장되며, Redis에서는 해시 전체를 수정할 수 있을 뿐만 아니라 개별 필드 단위로도 수정이 가능합니다. 이러한 특성 덕분에 해시는 애플리케이션에서 객체(Object)를 저장하기 위한 백엔드 스토어로 매우 훌륭하고 빠른 선택지가 됩니다.

CLI 예제

먼저 두 개의 필드를 가진 해시를 생성해 보겠습니다.

127.0.0.1:6379> HMSET my_hash key1 "foo" key2 "bar"
OK

해시에 연결된 모든 필드와 값을 조회합니다.

127.0.0.1:6379> HGETALL my_hash
1) "key1"
2) "foo"
3) "key2"
4) "bar"

특정 필드의 값을 업데이트합니다.

127.0.0.1:6379> HSET my_hash key1 "xyzzy"
(integer) 0
127.0.0.1:6379> HGET my_hash key1
"xyzzy"

해시에서 단일 필드만 삭제합니다.

127.0.0.1:6379> HDEL my_hash key2
(integer) 1
127.0.0.1:6379> HGETALL my_hash
1) "key1"
2) "xyzzy"

해시 전체를 삭제합니다.

127.0.0.1:6379> DEL my_hash
(integer) 1
127.0.0.1:6379> HGETALL my_hash
(empty list or set)

참고로 HMSET은 최신 Redis 버전에서 더 이상 권장되지 않으며(deprecated), 대신 여러 필드-값 쌍을 한 번에 지정할 수 있는 HSET 사용이 권장됩니다. Redis 공식 문서에는 해시에 사용할 수 있는 다양한 명령어가 자세히 정리되어 있으니 참고하시기 바랍니다. 여기서는 문서를 그대로 옮기기보다, 실전 예제를 통해 재미있게 활용법을 살펴보겠습니다.

Dwemthy's Array!

Dwemthy's Array는 Why the Lucky Stiff가 저술한 Why's (poignant) guide to Ruby에서 Ruby 메타프로그래밍을 설명하기 위해 만든 작은 롤플레잉 게임(RPG) 예제입니다. 여기서는 동일한 RPG 컨셉을 Redis 해시(그리고 Redis 리스트의 도움을 받아)로 구현해 보겠습니다.

배열 속 각 몬스터마다 하나의 해시를 생성하며, 그 구조는 다음과 같습니다.

name: "AssistantViceTentacleAndOmbudsman"
life: 320
strength: 6
charisma: 144
weapon: 50

다음은 배열 깊숙이 탐험에 들어가는 실행 예시입니다.

>>> import redis
>>> import dwemthy
>>> conn = redis.StrictRedis(host="localhost", port=6379)
>>> dw = dwemthy.Array.new(conn,
      {
        "name": "AssistantViceTentacleAndOmbudsman",
        "life": 320,
        "strength": 6,
        "charisma": 144,
        "weapon": 50
      }
    )
"[Get ready. AssistantViceTentacleAndOmbudsman has emerged!]"
>>> rabbit = dwemthy.Rabbit(dw)
>>> rabbit.sword()
"[You hit with 2 points of damage!]"
"[Your enemy hit with 10 points of damage!]"
"[Rabbit has died!]"

전체 코드는 dwemthy.py 파일에서 확인할 수 있습니다.

코드 핵심 살펴보기

먼저, 사용자가 팩토리 메서드를 통해 배열을 생성하면 데이터 구조를 초기화하고 새로운 dwemthy.Array 객체를 반환합니다.

class Array(object):
    list_key = "dwemethys_array"

    ...

    @classmethod
    def new(cls, conn, *bad_guys):
        """ Create a new set of problems for our hero to boomerang! """

        conn.delete(cls.list_key)

        # Give each bad guy a cozy spot in the array!
        for bad_guy in bad_guys:
            key = uuid.uuid4()
            conn.hmset(key, bad_guy)
            conn.rpush(cls.list_key, key)

        dw_list = cls(conn)
        dw_list.next_enemy()
        return dw_list

여기서 conn.hmset 호출이 몬스터마다 하나의 해시를 생성합니다. 해시가 만들어지면 해당 몬스터의 키를 Redis 리스트에 push하는데, 이 리스트는 여기서 간단한 큐(queue) 역할을 합니다.

모든 몬스터가 준비되면 Array.next_enemy() 메서드를 통해 배열 맨 앞의 적을 소환합니다.

class Array(object):    
    list_key = "dwemethys_array"

    ...

    def next_enemy(self):
        """ Bring out the next enemy for our rabbit to face! """

        # We're moving on!
        if self.current != None:
            self.conn.delete(self.current)

        enemy_key = self.conn.lpop(self.list_key)

        if enemy_key is None:
            # Like a boss!
            print "[Whoa.  You decimated Dwemthy's List!]"
        else:
            # Get the new enemy ready!
            self.current = enemy_key
            print "[Get ready. {} has emerged!]".format(self["name"])

마지막 줄의 self["name"]은 배열 객체에 __setitem____getitem__ 메서드를 정의했기 때문에 동작합니다. 이를 통해 배열 객체를 파이썬 딕셔너리처럼 다룰 수 있으며, 현재 몬스터의 필드를 손쉽게 조회하고 수정할 수 있습니다.

class Array(object):

    ...

    def __setitem__(self, key, value): 
        self.conn.hset(self.current, key, value)

    def __getitem__(self, key):
        value = self.conn.hget(self.current, key)

        # hashes only store strings!
        if key != "name":
            return int(value)

        return value

마무리

Redis가 제공하는 다양한 해시 조작 기능을 활용하면 개발자는 폭넓은 문제를 유연하게 해결할 수 있습니다. 이 글이 여러분의 프로젝트에 바로 적용할 수 있는 아이디어를 얻는 데 도움이 되기를 바랍니다.