개발자를 위한 레디스

레디스에서 특정 패턴을 가진 키 삭제하는 방법

How to delete keys with a specific pattern in Redis

GARIMOO
garimoo

--

레디스에서는 보통 프리픽스를 기반으로 키를 설계하는 것이 일반적입니다. KEYSSCAN 과 같은 커맨드에서도 프리픽스 또는 패턴에 맞는 키를 검색할 수 있도록 지원합니다.

> KEYS user:*
1) "user:234351"
2) "user:234235"
3) "user:231133"
4) "user:123123"
5) "user:123122"


> SCAN 0 MATCH user:* COUNT 10000
1) "8277"
2) 1) "user:123123"
2) "user:123122"
3) "user:231133"
4) "user:234351"

하지만 특정 패턴을 기반으로 키를 삭제하는 기능은 지원하지 않습니다. 그렇기에 쉘 스크립트를 통해 변수를 넘겨주는 방식으로 삭제하거나, 따로 코드를 작성해서 키를 삭제하는 방식을 사용해야 합니다.

일반적인 방식으로 키 삭제

아래는 특정 프리픽스를 기반으로 키를 삭제하는 파이썬 스크립트입니다.

pattern = 'prefix:*'
count = 100
cursor, keys = 0, []

#SCAN명령을 사용해 특정 프리픽스를 가진 키 검색
while True:
cursor, partial_keys = r.scan(cursor, match=pattern)
keys.extend(partial_keys)

if cursor == 0:
break

#검색된 키 삭제
for key in keys:
r.delete(key)

SCAN 커맨드를 사용해서 레디스를 순차 검색하면서 prefix:* 라는 프리픽스를 가진 키를 모두 조회한 뒤, 해당 키를 각각 삭제합니다. 이는 아래 그림과 같이 동작합니다.

일반적인 방식으로 키 삭제

만약 레디스에 1000만개의 키가 있고, 매치되는 패턴이 100만개가 있다면 데이터 조회 이후 레디스에 100만번을 추가로 접근해서 키를 삭제하게 됩니다. 키를 삭제하는 작업은 네트워크 I/O로 인해 상당한 시간이 소요될 수 있습니다.

이 때 루아 스크립트를 이용한다면 네트워크 I/O를 줄이면서 같은 작업을 효율적으로 수행할 수 있습니다.

루아스크립트를 이용한 방식으로 키 삭제

루아스크립트를 이용하면 레디스 서버 내에서 직접 복잡한 로직을 실행할 수 있습니다. 루아(Lua)는 가벼운 스크립트 언어로, 효율적이고 빠른 실행을 가능하게 합니다. 여러 레디스 커맨드를 하나의 스크립트 안에서 연속적으로 실행할 수 있으며, 이는 네트워크 지연 시간을 줄이고 데이터베이스 작업을 최적화하는 데 도움을 줍니다.

# 삭제할 키의 패턴 및 스캔 파라미터 설정
pattern = 'prefix:*'
count = 100
cursor = "0"

# 루아 스크립트 정의
lua_script = """
local cursor = ARGV[1]
local pattern = ARGV[2]
local count = ARGV[3]

local keys = redis.call("SCAN", cursor, "MATCH", pattern, "COUNT", count)
local cursor = keys[1]
local keyList = keys[2]

for _, key in ipairs(keyList) do
redis.call("DEL", key)
end

return {cursor, #keyList}
"""

total_deleted_count = 0

# 반복적으로 루아 스크립트를 호출해 키를 조회하고 삭제
while True:
result = r.eval(lua_script, 0, cursor, pattern, count)
cursor, deleted_count = result[0], result[1]
total_deleted_count += deleted_count

if cursor == "0":
break

위의 스크립트는 아래 그림과 같이 동작합니다.

루아 스크립트를 이용하는 방식으로 키 삭제

이 방식에서는 레디스로 매번 DEL 커맨드를 호출하는 과정이 생략되었습니다. 루아스크립트 내에서 키의 조회와 삭제가 한번에 가능하기 때문에 네트워크 왕복 시간을 줄여 실행 시간을 최소화할 수 있습니다.

하지만 루아 스크립트가 실행되는 동안 다른 클라이언트의 커맨드는 차단되기 때문에 주의해야 합니다. 운영 환경에서 데이터를 삭제해야 한다면, 서비스와 데이터의 특성을 파악하고 다른 클라이언트에게 영향을 미치지 않는 적절한 배치 크기로 count 매개변수를 조절해 차단 시간을 최소화해야 합니다.

성능 비교 테스트

테스트를 위해 첫번째 방법과 두번째 방법으로 키를 삭제하는 방법을 테스트하기 위해 아래와 같은 코드로 3, 4번 데이터베이스에 테스트 데이터를 생성했습니다.


def populate_redis_with_pipeline(host, port, password, db_list, total_keys, prefix_keys):
try:
client = redis.Redis(host=host, port=port, password=password, decode_responses=True)

for db in db_list:
client.execute_command("SELECT", db)
pipeline = client.pipeline()

for i in range(total_keys):
key = f"key::{i}" if i >= prefix_keys else f"prefix::{i}"
pipeline.set(key, 'a')

# 1000개의 명령어마다 한 번씩 실행
if i % 100000 == 0:
pipeline.execute()

# 남은 명령어 실행
pipeline.execute()

print(f"DB {db} populated with {total_keys} keys ({prefix_keys} with 'prefix').")

except Exception as e:
print(f"Error: {e}")

# 사용 설정 - 여기를 자신의 환경에 맞게 수정하세요.
HOST = 'xxxx' # Redis 서버 호스트
PORT = 6379 # Redis 서버 포트
DB_LIST = [3, 4] # 데이터를 채울 데이터베이스 번호
TOTAL_KEYS = 10000000 # 총 생성할 키의 수
PREFIX_KEYS = 1000000 # 'prefix:'를 포함할 키의 수

# 스크립트 실행
populate_redis_with_pipeline(HOST, PORT, PASSWORD, DB_LIST, TOTAL_KEYS, PREFIX_KEYS)

이후 3번 데이터베이스는 는 첫번째 방식으로, 4번 데이터베이스는 루아스크립트를 이용하는 두번째 방식으로 키를 삭제했습니다. 테스트 인스턴스이기 때문에 삭제시의 배치 사이즈는 10만으로 설정했습니다.

[21:07:06] [~]  python3 del1.py
1000000
[21:08:09] [cost 62.238s] python3 del1.py

[21:09:54] [~] python3 del2.py
1000000
[21:10:01] [cost 4.995s] python3 del2.py

첫번째 방식에서는 62.238초가, 두번째 방식에서는 4.995초가 소요되었습니다.

루아스크립트가 레디스 서버 내에서 직접 실행되어 네트워크 지연을 최소화하고, 여러 단계의 작업을 하나의 스크립트 실행으로 일괄 처리할 수 있기 때문입니다. 따라서, 대량의 키를 효율적으로 처리해야 하는 상황에서는 이와 같이 스크립트를 활용하는 등의 배치 처리를 고려하는 것이 좋습니다.

개발자를 위한 레디스

본문에서 설명한 특정 프리픽스를 가진 레디스의 키 삭제 방법과 같이 효율적이고 실용적인 레디스 사용 방법들은 개발자를 위한 레디스에서 더욱 자세히 다루고 있습니다. 이 책은 레디스를 사용해서 성능을 최적화하는 다양한 전략과 팁을 제공하며, 실제 적용 사례와 함께 깊이 있는 설명을 통해 개발자들이 레디스를 보다 효과적으로 활용할 수 있도록 도와줍니다.

--

--