메모리 저장소에 대한 모범 사례

*이 콘텐츠는 AI(베타)를 사용해 번역되었으며, 오류가 있을 수 있습니다. 이 페이지를 영어로 보려면 여기를 클릭하세요.

이러한 관행을 사용하여 임시 데이터를 구성하고, 부하를 분산시키며, 메모리 저장소 문제에 대응하십시오.

데이터 구조 유형에 따라 MemoryStoreService는 데이터 구조의 메모리 및 항목 수에 대한 제한을 적용합니다. 모든 데이터 구조는 또한 전역 파티션 요청 제한에 의해 제약을 받습니다.

키 및 데이터 구조 설계

데이터 저장소에 대한 모범 사례에서 정적 키 패턴 및 접두사 사용을 참조하십시오. 이러한 패턴을 키와 데이터 구조 이름 모두에 적용하여 모든 서버가 동일한 논리 데이터를 동일한 위치로 라우팅하도록 합니다.

데이터가 유용한 기간에 맞는 만료 시간을 선택하십시오. 지속적인 플레이어 기록이나 만료를 견뎌야 하는 데이터에 메모리 저장소를 사용하지 마십시오. 서비스 간 선택에 대한 도움은 데이터 저장소 대 메모리 저장소를 참조하십시오.

요청 실패 처리

데이터 저장소에 대한 모범 사례에서 일시적인 실패 재시도를 참조하십시오.

SetAsync보다 UpdateAsync 선호

데이터 저장소에 대한 모범 사례에서 SetAsync보다 UpdateAsync 선호를 참조하십시오. 해시 맵과 정렬된 맵은 이 패턴을 위해 MemoryStoreHashMap:UpdateAsync()MemoryStoreSortedMap:UpdateAsync()를 제공합니다.

반복 요청 분산

데이터 저장소에 대한 모범 사례에서 반복 요청 분산을 참조하십시오.

사용량 모니터링

메모리 저장소 가시성 대시보드를 사용하여 쿼터 사용량, 요청량 및 응답 상태를 모니터링하십시오. 내장된 이메일 알림을 검토하고, 중요한 메모리 저장소 메트릭에 대한 사용자 정의 알림을 구성하며, 오류 보고서를 사용하여 실패를 조사하십시오.

용량을 늘리기 전에 요청, 항목 크기 및 만료 시간을 줄이십시오. 정당한 사용량이 기본 쿼터를 초과하는 경우 확장 서비스를 평가하십시오.

정렬된 맵 및 큐 제한 관리

정렬된 맵과 큐는 모두 최대 항목 수 및 최대 총 메모리에 대한 제한이 있습니다. 또한 이러한 데이터 구조의 항목은 항상 단일 파티션에 존재합니다. 이러한 데이터 구조에 대한 모든 요청은 동일한 파티션에 대한 요청입니다.

정렬된 맵이나 큐가 항목 또는 메모리 제한에 도달하면 불필요한 항목을 수동으로 제거하거나 만료 정책을 추가하십시오. 메모리 제한만으로 스로틀링이 발생하는 경우, 키와 값에서 불필요한 정보를 제거하여 항목 크기를 줄이십시오.

모든 항목이 필요하거나 요청 처리량으로 인해 스로틀링이 발생하는 경우, 유일한 해결책은 샤딩입니다.

샤딩으로 부하 분산

샤딩은 관련 데이터 집합을 여러 데이터 구조에 저장하는 과정입니다. 즉, 기존의 고처리량 데이터 구조를 여러 개의 더 작은 데이터 구조로 교체하여 원래와 동일한 데이터 집합을 포함하도록 하는 것입니다.

샤딩의 주요 도전 과제는 원래와 동일한 기능을 유지하면서 데이터를 여러 데이터 구조에 분산시키는 방법을 찾는 것입니다.

Roblox는 이미 해시 맵을 파티셔닝하지만, 여러 키에 요청을 분산시켜 추가로 샤딩할 수 있습니다.

정렬된 맵 샤딩

정렬된 맵에서 플레이어 기록을 샤딩하려면 모듈로 산술을 사용하여 각 Id를 고정된 수의 맵 중 하나에 할당하십시오. 다음 예제는 네 개의 맵을 사용하고 동일한 사용자를 항상 동일한 맵으로 라우팅합니다:

정렬된 맵 샤딩
-- 메모리 저장소 서비스 초기화
local MemoryStoreService = game:GetService("MemoryStoreService")
-- 정렬된 맵 버킷 생성
local sm1 = MemoryStoreService:GetSortedMap("sm1")
local sm2 = MemoryStoreService:GetSortedMap("sm2")
local sm3 = MemoryStoreService:GetSortedMap("sm3")
local sm4 = MemoryStoreService:GetSortedMap("sm4")
local sortedMaps = { sm1, sm2, sm3, sm4 }
-- 항목 키에서 올바른 버킷을 검색하는 도우미 함수
local function getSortedMapBucket(userId)
local bucketIndex = (userId % #sortedMaps) + 1
return sortedMaps[bucketIndex]
end
-- 기본 값 0으로 플레이어 초기화
for _, player in game:GetService("Players"):GetPlayers() do
local userId = player.User.Id
local bucket = getSortedMapBucket(userId)
bucket:SetAsync(tostring(userId), 0, 600)
end
-- 플레이어의 값 검색
local player = game:GetService("Players"):GetPlayers()[1]
local userId = player.User.Id
local bucket = getSortedMapBucket(userId)
local playerScore = bucket:GetAsync(tostring(userId))
print(playerScore)

큐 샤딩

큐 샤딩은 정렬된 맵 샤딩보다 더 복잡합니다. 요청 처리량을 여러 큐에 분산시키고 싶지만, 추가, 읽기 및 제거는 항상 큐의 앞이나 뒤에서만 발생합니다.

한 가지 해결책은 회전 큐를 사용하는 것입니다. 즉, 여러 큐를 만들고 항목을 추가하거나 읽을 때 이들 사이를 회전하는 것입니다:

  1. 여러 큐를 만들고 배열에 추가합니다.
  2. 두 개의 로컬 포인터를 생성합니다. 하나는 항목을 읽고 제거할 큐를 나타내고, 다른 하나는 항목을 추가할 큐를 나타냅니다:
    • 읽기 작업의 경우, 각 큐에서 필요한 항목 수와 읽기 포인터를 이동할 위치를 계산합니다.
    • 제거 작업의 경우, 읽기에서 각 큐로 ID를 전달합니다.
    • 추가 작업의 경우, 추가 포인터에서 큐에 추가하고 포인터를 증가시킵니다.
큐 샤딩
-- 메모리 저장소 서비스 초기화
local MemoryStoreService = game:GetService("MemoryStoreService")
-- 큐 생성
local q1 = MemoryStoreService:GetQueue("q1")
local q2 = MemoryStoreService:GetQueue("q2")
local q3 = MemoryStoreService:GetQueue("q3")
local q4 = MemoryStoreService:GetQueue("q4")
-- 큐를 배열에 넣기
local queueArr = { q1, q2, q3, q4 }
-- 읽기 및 추가 큐의 인덱스를 나타내는 두 개의 포인터 생성
local readIndex = 1
local addIndex = 1
-- 인덱스를 적절하게 업데이트하는 로컬 함수 생성
local function rotateIndex(index, n)
return (index + n - 1) % 4 + 1
end
-- 큐에서 n개의 항목을 읽는 로컬 함수 생성
local function readFromQueue(count, allOrNothing, waitTimeout)
local endIndex = count % 4
local countPerQueue = count // 4
local items = {}
local ids = {}
-- 각 큐를 반복
for i = 1, 4, 1 do
-- 이 큐가 추가 항목을 읽을지 결정
local diff = i - readIndex
if diff < 0 then
diff += 4
end
local queue = queueArr[i]
-- 각 큐에서 항목 읽기
-- 추가 읽기 기준에 맞으면 +1 항목
if diff < endIndex then
items[i], ids[i] = queue:ReadAsync(countPerQueue + 1, allOrNothing, waitTimeout)
else
items[i], ids[i] = queue:ReadAsync(countPerQueue, allOrNothing, waitTimeout)
end
end
readIndex = rotateIndex(readIndex, count)
return items, ids
end
-- 큐에서 n개의 항목을 제거하는 로컬 함수 생성
local function removeFromQueue(ids)
for i = 1, 4, 1 do
local queue = queueArr[i]
queue:RemoveAsync(ids[i])
end
end
-- 큐에 항목을 추가하는 로컬 함수 생성
local function addToQueue(itemKey, expiration, priority)
local queue = queueArr[addIndex]
queue:AddAsync(itemKey, expiration, priority)
addIndex = rotateIndex(addIndex, 1)
end
-- 코드를 작성하세요!
for _, player in game:GetService("Players"):GetPlayers() do
addToQueue(player.User.Id, 600, 0)
end
local players, ids = readFromQueue(20, true, -1)
removeFromQueue(ids)

해시 맵

해시 맵은 개별 메모리 또는 항목 수 제한이 없으며 자동으로 샤딩되지만, 잘못 사용하면 여전히 스로틀링이 발생할 수 있습니다.

예를 들어, metadata라는 단일 키의 값으로 저장된 데이터의 해시 맵이 있는 게임을 고려하십시오. 이 메타데이터가 장소 ID, 플레이어 수 등의 정보를 포함하는 중첩 객체를 포함하는 경우, 메타데이터가 필요할 때마다 GetAsync("metadata")를 호출하여 전체 객체를 검색해야 합니다. 이 경우 모든 요청이 단일 키로 가고 따라서 단일 파티션으로 갑니다.

모든 메타데이터를 단일 중첩 객체로 저장하는 대신, 각 독립적으로 접근되는 필드를 자체 키로 저장하여 해시 맵이 자동 샤딩의 이점을 활용할 수 있도록 하십시오. 메타데이터와 나머지 해시 맵 간의 분리를 원한다면, user_count 대신 metadata_user_count와 같은 이름 접두사를 추가하십시오.

하나 또는 몇 개의 키가 빈번한 요청을 받는 경우, 이러한 호출을 여러 키에 샤딩하십시오. 예를 들어, 모든 게임 서버가 하나의 해시 맵 키에서 값을 검색하는 경우, 요청이 파티션 스로틀링을 유발할 수 있습니다. 부하를 줄이기 위해 값을 여러 키에 복사하고 각 서버를 안정적인 샤드로 라우팅하십시오.

©2026 Roblox Corporation. Roblox 및 Roblox 로고, 'Powering Imagination'은 미국 및 기타 국가 내 당사의 등록 및 미등록 상표입니다.