Utilisez ces pratiques pour organiser des données temporaires, répartir la charge et répondre aux problèmes de magasin de mémoire.
Selon le type de structure de données, MemoryStoreService impose des 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 requêtes globale par partition.
Concevoir des clés et des structures de données
Voir Utiliser des modèles de clés statiques et des préfixes dans Meilleures pratiques pour les magasins de données. Appliquez ces modèles aux clés et aux noms de structures de données afin que chaque serveur achemine les mêmes données logiques vers le même emplacement.
Choisissez des temps d'expiration qui correspondent à la durée pendant laquelle les données restent utiles. N'utilisez pas les magasins de mémoire pour des enregistrements de joueurs persistants ou des données qui doivent survivre à l'expiration. Pour obtenir de l'aide sur le choix entre les services, voir Magasins de données contre magasins de mémoire.
Gérer les échecs de requêtes
Voir Réessayer les échecs transitoires dans Meilleures pratiques pour les magasins de données.
Préférer UpdateAsync à SetAsync
Voir Préférer UpdateAsync à SetAsync dans Meilleures pratiques pour les magasins de données. Les cartes de hachage et les cartes triées fournissent MemoryStoreHashMap:UpdateAsync() et MemoryStoreSortedMap:UpdateAsync() pour ce modèle.
Échelonner les requêtes récurrentes
Voir Échelonner les requêtes récurrentes dans Meilleures pratiques pour les magasins de données.
Surveiller l'utilisation
Utilisez le Tableau de bord d'observabilité du magasin de mémoire pour surveiller l'utilisation des quotas, le volume des requêtes et les statuts des réponses. Consultez les alertes par e-mail intégrées, configurez des alertes personnalisées pour des métriques importantes du magasin de mémoire, et utilisez le Rapport d'erreur pour enquêter sur les échecs.
Réduisez les requêtes, les tailles d'éléments et les temps d'expiration avant d'augmenter la capacité. Si l'utilisation légitime dépasse les quotas par défaut, évaluez les Services étendus.
Gérer les limites des cartes triées et des files d'attente
Les cartes triées et les files d'attente ont toutes deux des limites sur le nombre maximum d'éléments 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 requête à l'une de ces structures de données est une requête à la même partition.
Lorsqu'une carte triée ou une file d'attente atteint sa limite d'éléments ou de mémoire, supprimez manuellement les éléments inutiles ou en ajoutant une politique d'expiration. Si seule la limite de mémoire cause le throttling, réduisez les tailles d'éléments en supprimant les informations inutiles des clés et des valeurs.
Si vous avez besoin de tous vos éléments ou si vous rencontrez un throttling dû au débit des requêtes, la seule solution est le sharding.
Répartir la charge avec le sharding
Le sharding est le processus de stockage d'un ensemble de données connexes sur plusieurs structures de données. En d'autres termes, cela signifie prendre une structure de données existante à fort débit et la remplacer par plusieurs plus petites qui contiennent ensemble le même ensemble de données que l'original.
Le principal défi du sharding est de trouver un moyen de répartir les données sur plusieurs structures de données de manière à maintenir la même fonctionnalité que l'original.
Bien que Roblox partitionne déjà les cartes de hachage, vous pouvez encore les shard en répartissant les requêtes entre plusieurs clés.
Sharding d'une carte triée
Pour shard les enregistrements des joueurs dans une carte triée, utilisez l'arithmétique modulo pour attribuer chaque Id à l'une d'un nombre fixe de cartes. L'exemple suivant utilise quatre cartes et achemine systématiquement le même utilisateur vers la même carte :
-- Initialiser le service de magasin de mémoire
local MemoryStoreService = game:GetService("MemoryStoreService")
-- Créer vos seaux de carte triée
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 }
-- Fonction d'assistance pour récupérer le seau correct à partir de la clé d'élément
local function getSortedMapBucket(userId)
local bucketIndex = (userId % #sortedMaps) + 1
return sortedMaps[bucketIndex]
end
-- Initialiser les joueurs avec une valeur par défaut de 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
-- Récupérer la valeur d'un joueur
local player = game:GetService("Players"):GetPlayers()[1]
local userId = player.User.Id
local bucket = getSortedMapBucket(userId)
local playerScore = bucket:GetAsync(tostring(userId))
print(playerScore)Sharding d'une file d'attente
Le sharding d'une file d'attente est plus délicat que le sharding d'une carte triée. Bien que vous souhaitiez répartir le débit des requêtes sur plusieurs files d'attente, les ajouts, les lectures et les suppressions ne se produisent qu'à l'avant ou à l'arrière de la file d'attente.
Une solution consiste à utiliser une file d'attente tournante, ce qui signifie créer plusieurs files d'attente et faire tourner entre elles lorsque vous ajoutez ou lisez un élément :
- Créez plusieurs files d'attente et ajoutez-les à un tableau.
- Créez deux pointeurs locaux. L'un représente la file d'attente dont vous souhaitez lire et supprimer des éléments. L'autre représente la file d'attente à laquelle vous souhaitez ajouter des éléments :
- Pour les opérations de lecture, calculez le nombre d'éléments dont vous avez besoin dans chaque file d'attente, ainsi que l'endroit où déplacer le pointeur de lecture.
- Pour les opérations de suppression, passez les ID de la lecture à chaque file d'attente.
- Pour les opérations d'ajout, ajoutez à la file d'attente au pointeur d'ajout et incrémentez le pointeur.
-- Initialiser le service de magasin de mémoire
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 indices des files d'attente de lecture et d'ajout
local readIndex = 1
local addIndex = 1
-- Créer une fonction locale qui met à jour les indices 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 d'attente lira un élément supplémentaire
local diff = i - readIndex
if diff < 0 then
diff += 4
end
local queue = queueArr[i]
-- lire des éléments de chaque file d'attente
-- +1 éléments si correspond aux 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[i]
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[addIndex]
queue:AddAsync(itemKey, expiration, priority)
addIndex = rotateIndex(addIndex, 1)
end
-- Écrivez du code !
for _, player in game:GetService("Players"):GetPlayers() do
addToQueue(player.User.Id, 600, 0)
end
local players, ids = readFromQueue(20, true, -1)
removeFromQueue(ids)Cartes de hachage
Les cartes de hachage n'ont pas de limites individuelles de mémoire ou de nombre d'éléments et sont automatiquement shardées, mais vous pouvez toujours rencontrer un throttling si vous les utilisez mal.
Par exemple, considérez un jeu avec une carte de hachage de données, stockée comme la valeur d'une seule clé nommée metadata. Si ces métadonnées contiennent un objet imbriqué avec des informations telles que l'ID de lieu, le nombre de joueurs, et plus encore, chaque fois que les métadonnées sont nécessaires, vous n'avez d'autre choix que d'appeler GetAsync("metadata") et de récupérer l'objet entier. Dans ce cas, toutes les requêtes vont à une seule clé et donc à une seule partition.
Plutôt que de stocker toutes les métadonnées comme un seul objet imbriqué, stockez chaque champ accessible indépendamment comme sa propre clé afin que la carte de hachage puisse tirer parti du sharding 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, tel que metadata_user_count au lieu de user_count.
Si une ou quelques clés reçoivent des requêtes fréquentes, shard ces appels entre plusieurs clés. Par exemple, si tous les serveurs de jeu récupèrent une valeur d'une clé de carte de hachage, les requêtes pourraient provoquer un throttling de partition. Pour réduire la charge, copiez la valeur dans plusieurs clés et acheminez chaque serveur vers un shard stable.