상세 컨텐츠

본문 제목

[redis] redis incr, expire command 동시에 사용하기.

프로그래밍/Python

by jisooo 2019. 10. 26. 23:36

본문

https://redis.io/commands

 

Command reference – Redis

 

redis.io

https://aioredis.readthedocs.io/en/v1.3.0/index.html

 

aioredis — aioredis 1.3.0 documentation

© Copyright 2014-2019, Alexey Popravka Revision bac9b42b.

aioredis.readthedocs.io

 

 

(* 본 포스팅의 코드는 aioredis 라이브러리를 사용한 코드로 작성되었습니다.

aioredis는 asyncio에 기반하여 Redis를 비동기 프로그래밍으로 작성할 수있는 인터페이스를 제공합니다.)

 

필자는 redis(aioredis)를 캐시저장용도로 어플리케이션에서 활용하고 있는데,

key-value구조를 id를 키로 두고, value에 해당 id값에 대한 count를 셋팅하는 구조로 활용해야 하는 상황이 생겼다.

기존에는 incr commands를 제대로 안읽어보고,

해당 키가 존재하지 않으면? set commands를 통해 key와 value값을 0으로 우선 셋팅하고,

해당 키가 존재하면 incr commands를 이용하여 value를 1씩 증가시키는 로직을 작성하였다.

 

 

 

if await TestCache.exists(key=id):
        count = await TestCache.incr(key=id)
        if count > MAX_COUNT :
            print("cache value exceeds MAX_COUNT!")
    else:
        await TestCache.set(ip_address=ip_address, promotion_id=promotion_id)

 

 

그럼 한단계 안으로 들어가서, Redis의 커맨트를 실행시킬 수 있는 function이 정의 되어있는

TestCache의 각각 exists, incr, set function들을 들여다보자.

 

class TestCache:
    redis = None

    @classmethod
    async def _get_connection(cls):
        if cls.redis and not cls.redis.closed:
            return cls.redis

        cls.redis = await aioredis.create_redis('redis://localhost')
        return cls.redis

    @classmethod
    async def exists(cls, id):
        try:
            redis = await cls._get_redis_connection()
            with await redis as conn:
                response = await conn.exists(key=id)
                return response == 1
        except RedisError as ex:
            raise

    @classmethod
    async def set(cls, id, cache_value=1):
        try:
            redis = await cls._get_redis_connection()
            with await redis as conn:
                await conn.set(key=id,
                               value=cache_value,
                               expire=24 * 60 * 60)
        except RedisError as ex:
            raise

    @classmethod
    async def incr(cls, id):
        try:
            redis = await cls._get_redis_connection()
            with await redis as conn:
                return await conn.incr(key=id)
        except RedisError as ex:
            raise

 

 

https://aioredis.readthedocs.io/en/v1.3.0/api_reference.html#commands-interface

 

aioredis — API Reference — aioredis 1.3.0 documentation

This coroutine create high-level Redis client instance bound to connections pool (this allows auto-reconnect and simple pub/sub use). Changed in version v1.0: parser, timeout, pool_cls and connection_cls arguments added.

aioredis.readthedocs.io

 

 

* 참고로, 예시코드처럼 고수준의 commands base 인터페이스를 사용하려면 아래처럼 redis connection 객체를 초기화시켜주면 된다.

cls.redis = await aioredis.create_redis('redis://localhost')

 

 

incr commands는 내부적으로, 해당 키가 존재하지 않으면,

해당 키의 값이 '0'인 레코드를 incr commands를 수행하기전에 셋팅해준다.

(단 이과정에서 expire 값에 대한 셋팅은 되지 않는다. 이것이 문제의 원인이였다...)

아래의 공식사이트에 해당 설명이 잘 나와있다.

우선 그래서 굳이 키가 존재하는지 체크후, incr commands를 날릴 필요가 없어졌다.

 

https://redis.io/commands/incr

 

INCR – Redis

Increments the number stored at key by one. If the key does not exist, it is set to 0 before performing the operation. An error is returned if the key contains a value of the wrong type or contains a string that can not be represented as integer. This oper

redis.io

 

 

그렇다면, set commands를 통해서는 기본적으로 레코드의 ttl(expire time)을 설정할 수 있는데,

incr commands를 사용할 때 expire를 셋팅하려면 어떻게 해야할까..?

Redis commands중에는 'expire' commands도 있었다. 이를 함께 활용하면 된다!

위의 TestCache 클래스에 expire commands를 수행할 수 있는 메소드를 추가한다.

 

 

@classmethod
    async def expire(cls, id, expire=CACHE_DEFAULT_TTL):
        try:
            redis = await cls._get_redis_connection()
            with await redis as conn:
                return await conn.expire(
                    key=id,
                    timeout=expire
                )
        except RedisError as ex:
            raise

 

 

외부에서 이제 아래와같이,

incr, expire commands를 count를 체크하는 로직과 함께 사용할 수 있다.

 

count = await TestCache.incr(id=id)
    if count == 1:
        await TestCache.expire(id=id, timeout= 24 * 60 * 60)
    elif count > 3:
        print("cache value exceeds MAX_COUNT!")
        ...
        ...

 

 

 

첫째줄의 

count = await TestCache.incr(id=id)

과정에서,

만약 해당 key값이 id인 캐시 데이터가 존재하지 않는다면, value가 0 (integer형) 이고 key가 id값인 레코드를 생성한다.

그리고, 해당 레코드의 value값을 1 증가시킨다.

그래서 즉, count값이 1이라는 말은 해당 캐시가 처음 생성된 시점이 되는 것이다.

그래서 그 시점에 expire 커맨트를 수행하여, 해당 레코드의 ttl을 적당히 설정해준다.

그 외에는 이제 캐시를 통해 수행하려는 로직을 작성하면 된다.

필자는 캐시의 value가 3회이상이면 로깅을 해야하는 상황이라 위와 같이 코드를 작성하였다.

관련글 더보기

댓글 영역