MemoryStoreService는 모든 서버에서 실시간 세션에 접근할 수 있는 빠른 인메모리 데이터 저장소를 제공하는 고처리량 및 저지연 데이터 서비스입니다. 메모리 저장소는 자주 변경되고 내구성이 필요 없는 빠르고 일시적인 데이터에 적합합니다. 이러한 데이터는 접근 속도가 빠르며 최대 수명에 도달하면 사라집니다. 세션 간에 지속해야 하는 데이터는 데이터 저장소를 사용하세요.
데이터 구조
메모리 저장소는 원시 데이터에 직접 접근하는 대신 서버 간에 빠른 처리를 위해 공유되는 세 가지 기본 데이터 구조를 가지고 있습니다: 정렬된 맵, 큐, 해시 맵. 각 데이터 구조는 특정 사용 사례에 적합합니다:
- 기술 기반 매칭 - 서버 간에 공유되는 큐에 사용자 정보를 저장하고, 로비 서버를 사용하여 주기적으로 매칭을 실행합니다.
- 서버 간 거래 및 경매 - 실시간으로 변하는 가격으로 아이템에 입찰할 수 있는 보편적인 거래를 가능하게 하며, 정렬된 맵을 사용하여 키-값 쌍을 관리합니다.
- 글로벌 리더보드 - 정렬된 맵 내의 공유 리더보드에서 사용자 순위를 저장하고 업데이트합니다.
- 공유 인벤토리 - 사용자들이 서로 동시에 인벤토리 아이템을 활용할 수 있도록 해시 맵에 인벤토리 아이템과 통계를 저장합니다.
- 지속 데이터의 캐시 - 데이터 저장소의 지속 데이터를 메모리 저장소 해시 맵에 동기화하고 복사하여 캐시 역할을 하여 게임 성능을 향상시킵니다.
일반적으로 특정 키를 기반으로 데이터에 접근해야 하는 경우 해시 맵을 사용하세요. 데이터가 정렬되어야 하는 경우 정렬된 맵을 사용하세요. 특정 순서로 데이터를 처리해야 하는 경우 큐를 사용하세요.
한계 및 쿼터
확장성과 시스템 성능을 유지하기 위해 메모리 저장소는 메모리 크기, API 요청 및 데이터 구조 크기에 대한 데이터 사용 쿼터를 가지고 있습니다.
메모리 저장소는 만료 시간에 기반한 퇴출 정책을 가지고 있으며, 이를 TTL(생존 시간)이라고도 합니다. 항목은 만료된 후 퇴출되며, 새로운 항목을 위한 메모리 쿼터가 확보됩니다. 메모리 한도에 도달하면 모든 후속 쓰기 요청은 항목이 만료되거나 수동으로 삭제될 때까지 실패합니다.
메모리 크기 쿼터
메모리 쿼터는 게임이 소비할 수 있는 총 메모리 양을 제한합니다. 이는 고정 값이 아니며, 대신 게임의 사용자 수에 따라 64 KB + 1.2 KB * [사용자 수] 공식을 기반으로 시간이 지남에 따라 변경됩니다. 쿼터는 서버 수준이 아닌 게임 수준에 적용됩니다.
사용자가 게임에 참여하면 추가 메모리 쿼터가 즉시 사용 가능해집니다. 사용자가 게임을 떠나면 쿼터는 즉시 줄어들지 않습니다. 쿼터가 더 낮은 값으로 재평가되기까지 8일의 추적 기간이 있습니다.
게임이 메모리 크기 쿼터에 도달하면 메모리 크기를 증가시키는 모든 API 요청은 항상 실패합니다. 메모리 크기를 줄이거나 변경하지 않는 요청은 여전히 성공합니다.
관측 가능성 대시보드를 사용하면 메모리 사용량 차트를 통해 게임의 메모리 크기 쿼터를 실시간으로 확인할 수 있습니다.
API 요청 한도
요청 단위 쿼터는 모든 MemoryStoreService API 호출에 적용됩니다. 이 쿼터는 1000 + 120 * [동시 사용자 수] 요청 단위로 분당 적용됩니다.
대부분의 API 호출은 하나의 요청 단위를 소비하지만 몇 가지 예외가 있습니다:
MemoryStoreSortedMap:GetRangeAsync()
반환된 항목 수에 따라 단위를 소비합니다. 예를 들어, 이 메서드가 10개의 항목을 반환하면 호출은 10개의 요청 단위로 계산됩니다. 빈 응답을 반환하면 하나의 요청 단위로 계산됩니다.
반환된 항목 수에 따라 단위를 소비하며, MemoryStoreSortedMap:GetRangeAsync()와 유사하지만 읽는 동안 매 2초마다 추가 단위를 소비합니다. waitTimeout 매개변수로 최대 읽기 시간을 지정하세요.
MemoryStoreHashMap:UpdateAsync()
최소 2개의 단위를 소비합니다.
MemoryStoreHashMap:ListItemsAsync()
[스캔된 파티션 수] + [반환된 항목 수] 단위를 소비합니다.
요청 쿼터는 서버 수준이 아닌 게임 수준에 적용됩니다. 이는 총 요청 속도가 쿼터를 초과하지 않는 한 서버 간에 요청을 할당할 수 있는 유연성을 제공합니다. 쿼터를 초과하면 서비스가 요청을 제한할 때 오류 응답을 받게 됩니다.
관측 가능성 기능을 사용하면 게임의 요청 단위 쿼터를 실시간으로 확인할 수 있습니다.
데이터 구조 크기 한계
단일 정렬된 맵 또는 큐에 대해 다음 크기 및 항목 수 한계가 적용됩니다:
- 최대 항목 수: 1,000,000
- 최대 총 크기(정렬된 맵의 키 포함): 100 MB
파티션당 한계
파티션당 한계를 참조하세요.
모범 사례
메모리 사용 패턴을 최적화하고 한계에 도달하지 않도록 하려면 다음 모범 사례를 따르세요:
처리된 항목 제거. MemoryStoreQueue:RemoveAsync() 메서드를 사용하여 큐의 읽은 항목을 지속적으로 정리하고, 정렬된 맵의 경우 MemoryStoreSortedMap:RemoveAsync()를 사용하여 메모리를 확보하고 데이터 구조를 최신 상태로 유지할 수 있습니다.
데이터를 추가할 때 가능한 가장 짧은 시간 프레임으로 만료 시간을 설정하세요. 기본 만료 시간은 MemoryStoreQueue:AddAsync()와 MemoryStoreSortedMap:SetAsync() 모두 45일이지만, 가능한 짧은 시간을 설정하면 오래된 데이터를 자동으로 정리하여 메모리 사용 쿼터를 채우는 것을 방지할 수 있습니다.
- 긴 만료 시간으로 많은 데이터를 저장하지 마세요. 이는 메모리 쿼터를 초과할 위험이 있으며, 게임 전체에 문제를 일으킬 수 있습니다.
- 항상 불필요한 항목을 명시적으로 삭제하거나 짧은 항목 만료 시간을 설정하세요.
- 일반적으로 메모리를 해제하기 위해 명시적 삭제를 사용하고, 사용되지 않는 항목이 오랜 시간 동안 메모리를 차지하지 않도록 하기 위해 항목 만료를 안전 장치로 사용해야 합니다.
메모리에 필요한 값만 유지하세요.
예를 들어, 경매장 게임의 경우 가장 높은 입찰가만 유지하면 됩니다. 모든 입찰을 데이터 구조에 유지하는 대신 하나의 키에서 MemoryStoreSortedMap:UpdateAsync()를 사용하여 가장 높은 입찰가를 유지할 수 있습니다.
지수 백오프를 사용하여 API 요청 한도를 초과하지 않도록 하세요.
예를 들어, DataUpdateConflict 오류가 발생하면 두 초 후에 재시도하고, 그 다음 네 초, 여덟 초 등으로 재시도하는 것이 좋습니다. MemoryStoreService에 지속적으로 요청을 보내어 올바른 응답을 받으려 하지 마세요.
샤딩을 통해 거대한 데이터 구조를 여러 개의 작은 데이터 구조로 나누세요.
모든 것을 하나의 큰 데이터 구조에 저장하는 것보다 작은 구조에서 데이터를 관리하는 것이 더 쉽습니다. 이 접근 방식은 사용 및 비율 한도를 피하는 데도 도움이 될 수 있습니다. 예를 들어, 키에 접두사를 사용하는 정렬된 맵이 있는 경우 각 접두사를 별도의 정렬된 맵으로 분리하는 것을 고려하세요. 특히 인기 있는 게임의 경우 사용자 ID의 마지막 숫자에 따라 사용자를 여러 맵으로 분리할 수도 있습니다.
해시 맵에서 여러 개의 키 복사본으로 자주 접근하는 키를 샤드하세요.
저장된 값을 압축하세요.
예를 들어, 저장된 값의 크기를 줄이기 위해 LZW 알고리즘을 사용하는 것을 고려하세요.
확장 서비스에 등록하세요.
확장 서비스에 온보딩하여 저장소 및 요청 한도 쿼터를 늘릴 수 있습니다.
관측 가능성
관측 가능성 대시보드는 메모리 저장소 사용을 모니터링하고 문제를 해결하기 위한 통찰력과 분석을 제공합니다. 메모리 사용량 및 API 요청의 다양한 측면에 대한 실시간 업데이트 차트를 통해 게임의 메모리 사용 패턴을 추적하고, 현재 할당된 쿼터를 확인하며, API 상태를 모니터링하고, 성능 최적화를 위한 잠재적 문제를 식별할 수 있습니다.
다음 표는 관측 가능성 대시보드의 상태별 요청 수 및 API x 상태별 요청 차트에서 사용할 수 있는 모든 API 응답 상태 코드를 나열하고 설명합니다. 이러한 오류를 해결하는 방법에 대한 자세한 내용은 문제 해결을 참조하세요. 오류와 관련된 특정 쿼터 또는 한도에 대한 내용은 한계 및 쿼터를 참조하세요.
| 상태 코드 | 설명 |
|---|---|
| 성공 | 성공. |
| DataStructureMemoryOverLimit | 데이터 구조 수준 메모리 크기 한도(100 MB)를 초과했습니다. |
| DataUpdateConflict | 동시 업데이트로 인한 충돌입니다. |
| AccessDenied | 게임 데이터에 접근할 수 있는 권한이 없습니다. 이 요청은 요청 단위를 소비하지 않거나 쿼터를 사용하지 않습니다. |
| InternalError | 내부 오류입니다. |
| InvalidRequest | 요청에 필수 정보가 없거나 잘못된 정보가 포함되어 있습니다. |
| DataStructureItemsOverLimit | 데이터 구조 수준 항목 수 한도(1M)를 초과했습니다. |
| NoItemFound | MemoryStoreQueue:ReadAsync() 또는 MemoryStoreSortedMap:UpdateAsync()에서 항목을 찾을 수 없습니다. ReadAsync()는 매 2초마다 폴링하며 큐에서 항목을 찾을 때까지 이 상태 코드를 반환합니다. |
| DataStructureRequestsOverLimit | 데이터 구조 수준 요청 단위 한도(분당 100,000 요청 단위)를 초과했습니다. |
| PartitionRequestsOverLimit | 파티션 요청 단위 한도를 초과했습니다. |
| TotalRequestsOverLimit | 우주 수준 요청 단위 한도를 초과했습니다. |
| TotalMemoryOverLimit | 우주 수준 메모리 쿼터를 초과했습니다. |
| ItemValueSizeTooLarge | 값 크기가 한도를 초과했습니다(32 KB). |
다음 표는 클라이언트 측 상태 코드를 나열하며, 현재 관측 가능성 대시보드에서 사용할 수 없습니다.
| 상태 코드 | 설명 |
|---|---|
| InternalError | 내부 오류입니다. |
| UnpublishedPlace | MemoryStoreService를 사용하려면 이 장소를 게시해야 합니다. |
| InvalidClientAccess | MemoryStoreService는 서버에서 호출해야 합니다. |
| InvalidExpirationTime | 'expiration' 시간 필드는 0과 3,888,000 사이여야 합니다. |
| InvalidRequest | 값을 json으로 변환할 수 없습니다. |
| InvalidRequest | sortKey를 유효한 숫자 또는 문자열로 변환할 수 없습니다. |
| TransformCallbackFailed | 변환 콜백 함수 호출에 실패했습니다. |
| RequestThrottled | 최근 MemoryStores 요청이 하나 이상의 한도에 도달했습니다. |
| UpdateConflict | 최대 재시도 횟수를 초과했습니다. |
문제 해결
다음 표는 각 응답 상태 코드에 대한 권장 솔루션을 나열하고 설명합니다:
| 오류 | 문제 해결 옵션 |
|---|---|
| DataStructureRequestsOverLimit / PartitionRequestsOverLimit |
|
| TotalRequestsOverLimit | |
| DataStructureItemsOverLimit |
|
| DataStructureMemoryOverLimit | |
| TotalMemoryOverLimit | |
| DataUpdateConflict |
요청 중단 예제 |
| 내부 오류 |
|
| InvalidRequest |
|
| ItemValueSizeTooLarge |
|
스튜디오에서 테스트 및 디버그
MemoryStoreService의 데이터는 스튜디오와 프로덕션 간에 격리되어 있으므로 스튜디오에서 데이터를 변경해도 프로덕션 동작에 영향을 미치지 않습니다. 즉, 스튜디오에서의 API 호출은 프로덕션 데이터를 접근하지 않으므로 메모리 저장소와 새로운 기능을 안전하게 테스트할 수 있습니다.
스튜디오 테스트는 프로덕션과 동일한 한계 및 쿼터를 가지고 있습니다. 사용자 수에 따라 계산된 쿼터의 경우, 스튜디오 테스트의 경우 유일한 사용자이므로 결과 쿼터가 매우 작을 수 있습니다. 스튜디오에서 테스트할 때는 접근 및 권한을 확인하기 위해 수행되는 추가 검사로 인해 프로덕션에서의 사용에 비해 약간 높은 지연 및 오류율을 경험할 수 있습니다.
실시간 게임에서 메모리 저장소를 디버그하는 방법이나 스튜디오에서 테스트할 때는 개발자 콘솔을 사용하세요.