Redis 배열

Redis 배열 (Redis Arrays)

Redis 배열(array)은 희소(sparse)하고 인덱스로 주소 지정이 가능한 데이터 구조로, 정수 인덱스(0부터 2⁶⁴−1 범위)를 문자열 값에 매핑해요. 리스트와 달리 요소가 시퀀스의 위치가 아니라 인덱스로 직접 접근되고, 점유된 슬롯 사이의 빈 공간을 할당하지 않고도 어떤 인덱스든 설정할 수 있어요.

출처: Redis 공식 문서 — arrays

기본 사용법 (Basic usage)

ARSET으로 주어진 인덱스에서 시작하는 하나 이상의 연속 값을 쓰고, ARGET으로 특정 인덱스의 값을 읽어요. 설정되지 않은 인덱스에 접근하면 nil 응답을 반환해요.

배열 길이 vs 요소 개수 (Array length vs. element count)

Redis 배열은 두 가지 서로 다른 크기 측정을 제공해요:

  • ARLEN논리적 길이(logical length) 즉, 가장 높게 설정된 인덱스 + 1을 반환해요.
  • ARCOUNT비어있지 않은(non-empty) 요소의 개수를 반환해요.

범위 읽기 (Reading ranges)

복잡도:

  • ARMSET: O(N)
  • ARGETRANGE: O(N)

사용 가능 환경: Redis CLI, Go, Java (Asynchronous - Lettuce), Java (Reactive - Lettuce), JavaScript (Node.js), PHP, Python

Redis CLI

> ARMSET seq 0 "a" 1 "b" 3 "d"
(integer) 3
> ARGETRANGE seq 0 3
1) "a"
2) "b"
3) (nil)
4) "d"

Python

존재하는 요소만 순회하면서 그 인덱스를 값과 함께 가져오려면 ARSCAN을 써요. 빈 슬롯은 건너뛰고 인덱스-값 쌍이 번갈아 나오는 평면(flat) 리스트를 반환하며, 선택적 LIMIT로 결과 크기를 제한할 수 있어요:

> ARSCAN seq 0 3
1) 1) (integer) 0
   2) "a"
2) 1) (integer) 1
   2) "b"
3) 1) (integer) 3
   2) "d"

순차 삽입 (Sequential insertion)

ARINSERT는 각 호출 후 자동으로 전진하는 내부 커서를 사용해 값을 추가해요. ARNEXT로 다음 삽입이 어디에 위치할지 확인하고, ARSEEK로 커서를 다시 위치시킬 수 있어요:

$res1 = $redis->arinsert('log', 'event1');
echo $res1 . PHP_EOL; // >>> 0

$res2 = $redis->arinsert('log', 'event2');
echo $res2 . PHP_EOL; // >>> 1

$res3 = $redis->arnext('log');
echo $res3 . PHP_EOL; // >>> 2

$res4 = $redis->arseek('log', 10);
echo $res4 . PHP_EOL; // >>> 1

$res5 = $redis->arinsert('log', 'event3');
echo $res5 . PHP_EOL; // >>> 10

링 버퍼 모드 (Ring buffer mode)

ARRING은 배열을 고정 크기의 순환 버퍼(circular buffer)로 바꿔요. 각 호출은 insert_idx % size 위치에 값을 삽입하고, 창(window)이 가득 차면 인덱스 0으로 돌아가(wrapping) 가장 오래된 항목을 덮어써요:

명령어: ARRING, ARGET 복잡도:

  • ARRING: O(M)
  • ARGET: O(1)

Redis CLI

> ARRING readings 3 "v0"
(integer) 0
> ARRING readings 3 "v1"
(integer) 1
> ARRING readings 3 "v2"
(integer) 2
> ARRING readings 3 "v3"
(integer) 0
> ARGET readings 0
"v3"

Go

ring0, err := rdb.ARRing(ctx, "readings", 3, "v0").Result()

if err != nil {
    panic(err)
}

fmt.Println(ring0) // >>> 0

ring1, err := rdb.ARRing(ctx, "readings", 3, "v1").Result()

if err != nil {
    panic(err)
}

fmt.Println(ring1) // >>> 1

ring2, err := rdb.ARRing(ctx, "readings", 3, "v2").Result()

JavaScript (Node.js)

const ring0 = await client.arRing('readings', 3, 'v0');
console.log(ring0); // >>> 0

const ring1 = await client.arRing('readings', 3, 'v1');
console.log(ring1); // >>> 1

const ring2 = await client.arRing('readings', 3, 'v2');
console.log(ring2); // >>> 2

const ring3 = await client.arRing('readings', 3, 'v3');
console.log(ring3); // >>> 0

const ringGet = await client.arGet('readings', 0);
console.log(ringGet); // >>> v3

PHP

$res1 = $redis->arring('readings', 3, 'v0');
echo $res1 . PHP_EOL; // >>> 0

$res2 = $redis->arring('readings', 3, 'v1');
echo $res2 . PHP_EOL; // >>> 1

$res3 = $redis->arring('readings', 3, 'v2');
echo $res3 . PHP_EOL; // >>> 2

$res4 = $redis->arring('readings', 3, 'v3');
echo $res4 . PHP_EOL; // >>> 0

$res5 = $redis->arget('readings', 0);
echo $res5 . PHP_EOL; // >>> v3

Python

res19 = r.arring("readings", 3, "v0")
print(res19)
# >>> 0

res20 = r.arring("readings", 3, "v1")
print(res20)
# >>> 1

res21 = r.arring("readings", 3, "v2")
print(res21)
# >>> 2

res22 = r.arring("readings", 3, "v3")
print(res22)
# >>> 0

res23 = r.arget("readings", 0)
print(res23)
# >>> v3

ARLASTITEMS은 시간순으로 가장 최근에 삽입된 N개 요소를 가져와요. REV 플래그를 넘기면 순서를 뒤집을 수 있어요:

Builds upon: arring

Redis CLI

> ARLASTITEMS readings 3
1) "v1"
2) "v2"
3) "v3"
> ARLASTITEMS readings 3 REV
1) "v3"
2) "v2"
3) "v1"

Go

// Set up the ring: insert v0, v1, v2, v3 into a size-3 ring.
for _, v := range []string{"v0", "v1", "v2", "v3"} {
    if err := rdb.ARRing(ctx, "readings", 3, v).Err(); err != nil {
        panic(err)
    }
}

lastRes, err := rdb.ARLastItems(ctx, "readings", 3, false).Result()

if err != nil {
    panic(err)
}

Python

r.arring("readings", 3, "v0")
r.arring("readings", 3, "v1")
r.arring("readings", 3, "v2")
r.arring("readings", 3, "v3")

res24 = r.arlastitems("readings", 3)
print(res24)
# >>> ['v1', 'v2', 'v3']

res25 = r.arlastitems("readings", 3, rev=True)
print(res25)
# >>> ['v3', 'v2', 'v1']

집계 연산 (Aggregate operations)

AROP은 요소의 연속 범위에 대해 단일 패스(single-pass) 집계를 수행해요:

$res4 = $redis->arop('scores', 0, 2, 'MATCH', '10');
echo $res4 . PHP_EOL; // >>> 1

요소 검색 (Searching elements)

ARGREP은 범위 안에서 값이 하나 이상의 텍스트 조건(predicate)과 일치하는 요소를 찾아 그 인덱스를 반환해요. 빈 슬롯은 건너뛰어요. 네 가지 조건 형식이 지원돼요: EXACT(완전 일치), MATCH(부분 문자열), GLOB(SCAN MATCH와 같은 와일드카드 문법), RE(정규식). 여러 조건은 기본적으로 OR로 결합되고, 해당 옵션을 주면 AND로 결합돼요.

요소 삭제 (Deleting elements)

ARDEL은 인덱스로 하나 이상의 요소를 삭제하고 실제로 제거된 요소 개수를 반환해요. ARDELRANGE는 인덱스 범위 안의 모든 요소를 제거하며, startend를 뒤집는 것도 지원해요:

Redis CLI

> ARDEL scores 1
(integer) 1
> ARDELRANGE scores 0 2
(integer) 2

PHP

$res1 = $redis->armset('scores', [0 => '10', 1 => '20', 2 => '30']);
echo $res1 . PHP_EOL; // >>> 3

$res2 = $redis->ardel('scores', 1);
echo $res2 . PHP_EOL; // >>> 1

$res3 = $redis->ardelrange('scores', 0, 2);
echo $res3 . PHP_EOL; // >>> 2

내부 구조 확인 (Introspection)

ARINFO은 배열 내부 구조에 대한 메타데이터를 반환해요. 여기에는 논리적 길이, 요소 개수, 다음 삽입 인덱스가 포함돼요. FULL 옵션을 넘기면 채우기율(fill rates), 밀집(slice) vs 희소 슬라이스 개수 같은 슬라이스별 통계도 포함돼요:

> ARINFO readings
 1) "len"
 2) (integer) 3
 3) "count"
 4) (integer) 3
 5) "next-insert-index"
 6) (integer) 0
...

구성 (Configuration)

다음 구성 파라미터가 배열 동작에 영향을 미쳐요:

  • array-slice-size
  • array-sparse-kmax
  • array-sparse-kmin

자세한 내용은 Redis 구성 문서를 참고하세요.

성능 (Performance)

대부분의 배열 명령은 ARSET, ARGET, ARDEL, ARINSERT, ARNEXT, ARSEEK, ARCOUNT, ARLEN을 포함해 O(1)이에요. N개 요소에 접촉하는 연산(예: ARGETRANGE, ARSCAN, ARDELRANGE, AROP)은 그에 비례하는 복잡도를 가져요.

대안 (Alternatives)

  • 양 끝에서 push/pop 연산이 필요하거나 기존 요소 사이에 요소를 삽입해야 한다면 Redis 리스트를 사용하세요.

제한 (Limits)

ARGETRANGE은 우발적으로 큰 범위 읽기를 방지하기 위해 호출당 최대 1,000,000개 요소라는 하드 리밋을 적용해요.

더 알아보기 (Learn more)