Meilleures pratiques lors de la conception des structures de données de MemoryStore

*Ce contenu est traduit en utilisant l'IA (Beta) et peut contenir des erreurs. Pour consulter cette page en anglais, clique ici.

Selon le taperde structure de données, MemoryStoreService impose limites sur la mémoire et le nombre d'éléments dans une structure de données.Toutes les structures de données sont également contraintes par une limite de demande par partition globale.

Chaque expérience Roblox a le tableau de bord d'observabilité du stock de mémoire, qui comprend un ensemble de tableaux que vous pouvez utiliser pour surveiller l'utilisation du stock de mémoire.

Cartes et files d'attente triées

Les cartes triées et les files d'attente ont toutes les deux des limites sur le nombre maximum d'objets et la mémoire totale maximale.De plus, les éléments dans l'une de ces structures de données résident toujours sur une seule partition.Chaque demande à l'une de ces structures de données est une demande à la même partition.

Lorsqu'une carte triée ou des files d'attente atteignent leur limite d'objets ou de mémoire, la meilleure option est de supprimer manuellement les éléments inutiles ou d'ajouter une politique d'expiration pour les éléments.Alternativement, si seule la limite de mémoire provoque un ralentissement, vous pouvez essayer de réduire la taille de vos éléments en enlevant les informations inutiles de vos clés et de vos valeurs.

Si vous avez besoin de tous vos articles ou si vous rencontrez des ralentissements en raison du débit de requête, la seule solution est le découpage.

Éclatement

L'éclatement est le processus de stockage d'un ensemble de données liées à travers plusieurs structures de données.En d'autres termes, cela signifie prendre une structure de données existante à forte capacité de traitement et la remplacer par plusieurs plus petites qui contiennent ensemble le même ensemble de données que l'original.

Le défi clé du découpage est de trouver un moyen de répartir les données sur plusieurs structures de données d'une manière qui maintienne la même fonctionnalité que l'original.

Diviser une carte triée

Pour fragmenter une carte triée, envisagez de diviser vos données en sous-sections alphabétiques avec des plages de caractères.Par exemple, supposez que vous n'avez que des clés avec la première lettre de A à Z, et que vous pensez que quatre cartes triées suffisent pour votre cas d'utilisation actuel et votre croissance future :

  • La première carte peut couvrir A-G, la deuxième H-N, la troisième O-T, et la quatrième U-Z.
  • Pour insérer ou récupérer un item, utilisez la carte appropriée en fonction du caractère de départ de l'item.
Diviser une carte triée

-- Initialiser le service MemoryStore
local MemoryStoreService = game:GetService("MemoryStoreService")
-- Créer vos buckets de carte triée
local sm_AtoG = MemoryStoreService:GetSortedMap("AtoG")
local sm_HtoM = MemoryStoreService:GetSortedMap("HtoM")
local sm_NtoT = MemoryStoreService:GetSortedMap("NtoT")
local sm_UtoZ = MemoryStoreService:GetSortedMap("UtoZ")
-- Fonction d'aide pour récupérer le bon seau à partir de la clé de l'élément
local function getSortedMapBucket(itemKey)
if (itemKey >= "a" and itemKey < "h") then
return sm_AtoG
elseif (itemKey < "n") then
return sm_HtoM
elseif (itemKey < "u") then
return sm_NtoT
else
return sm_UtoZ
end
end
-- Initialiser les noms de joueur avec la valeur par défaut de 0
for _, player in game:GetService("Players"):GetPlayers() do
local bucket = getSortedMapBucket(player)
bucket:SetAsync(player, 0, 600)
end
-- Récupérer la valeur d'un joueur
local player = "myPlayer"
local bucket = getSortedMapBucket(player)
local playerScore = bucket:GetAsync(player)
print(playerScore)

Diviser une file d'attente

Diviser une file d'attente est plus difficile que de diviser une carte triée.Bien que vous souhaitiez répartir la capacité de traitement de la demande sur plusieurs files d'attente, les ajouts, lectures et suppressions se produisent toujours à l'avant ou à l'arrière de la file d'attente.

Une solution consiste à utiliser une file d'attente rotative, ce qui signifie créer plusieurs files d'attente et tourner entre elles lorsque vous ajoutez ou lisez un item:

  1. Créer plusieurs files d'attente et les ajouter à un matrice.

  2. Créez deux pointeurs locaux. L'un représente la file d'attente que vous voulez lire et supprimer des éléments de. L'autre représente la file d'attente que vous voulez ajouter des éléments à :

    • Pour les opérations de lecture, calculez le nombre d'éléments dont vous avez besoin de chaque file d'attente, ainsi que l'endroit où déplacer le pointeur de lecture.
    • Pour les opérations de suppression, passez les IDs de la lecture à chaque file d'attente.
    • Pour ajouter des opérations, ajoutez à la file d'attente au pointeur d'ajout et augmentez le pointeur.
Diviser une file d'attente

-- Initialiser le service MemoryStore
local MemoryStoreService = game:GetService("MemoryStoreService")
-- Créer vos files d'attente
local q1 = MemoryStoreService:GetQueue("q1")
local q2 = MemoryStoreService:GetQueue("q2")
local q3 = MemoryStoreService:GetQueue("q3")
local q4 = MemoryStoreService:GetQueue("q4")
-- Mettre les files d'attente dans un tableau
local queueArr = { q1, q2, q3, q4 }
-- Créer deux pointeurs représentant les index de la lecture et ajouter des files d'attente
local readIndex = 1
local addIndex = 1
-- Créer une fonction locale qui met à jour les index de manière appropriée
local function rotateIndex(index, n)
return (index + n - 1) % 4 + 1
end
-- Créer une fonction locale qui lit n éléments de la file d'attente
local function readFromQueue(count, allOrNothing, waitTimeout)
local endIndex = count % 4
local countPerQueue = count // 4
local items = {}
local ids = {}
-- boucle à travers chaque file d'attente
for i = 1, 4, 1 do
-- déterminer si cette file lira un itemsupplémentaire
local diff = i - readIndex
if diff < 0 then
diff += 4
end
local queue = queueArr[i]
-- lire les éléments de chaque file d'attente
-- +1 articles si les correspondances respectent les critères de lecture supplémentaires
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
-- Créer une fonction locale qui supprime n éléments de la file d'attente
local function removeFromQueue(ids)
for i = 1, 4, 1 do
local queue = queueArr[readIndex]
queue:RemoveAsync(ids[i])
end
end
-- Créer une fonction locale qui ajoute un élément à la file d'attente
local function addToQueue(itemKey, expiration, priority)
local queue = queueArr[readIndex]
queue:AddAsync(itemKey, expiration, priority)
addIndex = rotateIndex(addIndex, 1)
end
-- Écrivez du code !
for _, player in game:GetService("Players"):GetPlayers() do
addToQueue(player, 600, 0)
end
local players, ids = readFromQueue(20, true, -1)
removeFromQueue(ids)

Cartes de hachage

Les cartes de hachage n'ont pas de limites de mémoire ou de nombre d'objets individuelles et sont automatiquement fragmentées, mais vous pouvez toujours rencontrer des ralentissements si vous les utilisez mal.

Par exemple, envisagez une expérience avec une carte de hachage des données du jeu, stockée en tant que valeur d'une seule clé nommée metadata .Si ces métadonnées contiennent un objet imbriqué avec des informations telles que l'ID du lieu, le nombre de joueurs et plus, chaque fois que les métadonnées sont nécessaires, vous n'avez pas d'autre choix que d'appeler GetAsync("metadata") et de récupérer l'ensemble de l'objet.Dans ce cas, toutes les demandes vont à une seule clé et donc à une seule partition.

Plutôt que de stocker toutes les métadonnées en tant qu'objet unique et imbriqué, la meilleure approche consiste à stocker chaque champ comme sa propre clé afin que la carte de hachage puisse profiter d'un éclatement automatique.Si vous avez besoin de séparation entre les métadonnées et le reste de la carte de hachage, ajoutez un préfixe de nom (par exemple plutôt que simplement ).