Best Practices beim Entwerfen von MemoryStore-Datenstrukturen

*Dieser Inhalt wurde mit KI (Beta) übersetzt und kann Fehler enthalten. Um diese Seite auf Englisch zu sehen, klicke hier.

Abhängig vom Typ der Datenstruktur setzt der MemoryStoreService Grenzen für den Speicher und die Anzahl der Elemente in einer Datenstruktur fest. Alle Datenstrukturen sind auch durch ein globales Anforderungs-Limit pro Partition eingeschränkt.

Jedes Roblox-Spiel verfügt über das Memory Store Observability Dashboard, das eine Reihe von Diagrammen enthält, die Sie verwenden können, um die Nutzung des Speichers zu überwachen.

Sortierte Maps und Warteschlangen

Sortierte Maps und Warteschlangen haben beide Grenzen hinsichtlich der maximalen Anzahl von Elementen und des maximalen Gesamtspiels. Darüber hinaus befinden sich die Elemente in einer dieser Datenstrukturen immer auf einer einzelnen Partition. Jede Anforderung an eine dieser Datenstrukturen ist eine Anfrage an dieselbe Partition.

Wenn eine sortierte Map oder Warteschlange ihre Element- oder Speicherkapazität erreicht, ist der beste Ansatz, unnötige Elemente manuell zu entfernen oder eine Ablaufrichtlinie für Elemente hinzuzufügen. Alternativ, wenn nur das Speicher-Limit eine Drosselung verursacht, können Sie versuchen, die Größe Ihrer Elemente zu verringern, indem Sie unnötige Informationen aus Ihren Schlüsseln und Werten entfernen.

Wenn Sie alle Ihre Elemente benötigen oder aufgrund des Anfrage-Durchsatzes eine Drosselung erfahren, ist die einzige Lösung das Sharding.

Sharding

Sharding ist der Prozess, eine Menge verwandter Daten auf mehreren Datenstrukturen zu speichern. Mit anderen Worten, es bedeutet, eine bestehende, hochgradige Datenstruktur durch mehrere kleinere zu ersetzen, die zusammen die gleiche Menge an Daten wie das Original enthalten.

Die größte Herausforderung beim Sharding besteht darin, einen Weg zu finden, die Daten auf mehrere Datenstrukturen zu verteilen, wobei dieselbe Funktionalität wie das Original beibehalten wird.

Für Hash-Maps, obwohl die Datenstruktur bereits partitioniert ist, wird das Sharding durchgeführt, indem die Anfragen auf mehrere Schlüssel verteilt werden.

Sharding einer sortierten Map

Um eine sortierte Map zu sharden, überlegen Sie sich, Ihre Daten in alphabetische Unterabschnitte mit Zeichenbereichen zu unterteilen. Angenommen, Sie haben nur Schlüssel mit dem ersten Buchstaben von A-Z, und Sie glauben, dass vier sortierte Maps für Ihren aktuellen Anwendungsfall und zukünftiges Wachstum ausreichen:

  • Die erste Map kann A-G abdecken, die zweite H-N, die dritte O-T und die vierte U-Z.
  • Um ein Element einzufügen oder abzurufen, verwenden Sie die entsprechende Map basierend auf dem Anfangszeichen des Elements.
Sharding einer sortierten Map

-- Initialisieren Sie den MemoryStore-Service
local MemoryStoreService = game:GetService("MemoryStoreService")
-- Erstellen Sie Ihre Sortierte Map Eimer
local sm_AtoG = MemoryStoreService:GetSortedMap("AtoG")
local sm_HtoM = MemoryStoreService:GetSortedMap("HtoM")
local sm_NtoT = MemoryStoreService:GetSortedMap("NtoT")
local sm_UtoZ = MemoryStoreService:GetSortedMap("UtoZ")
-- Hilfsfunktion, um den richtigen Eimer aus dem Item-Schlüssel abzurufen
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
-- Initialisieren Sie die Spielernamen mit dem Standardwert 0
for _, player in game:GetService("Players"):GetPlayers() do
local bucket = getSortedMapBucket(player)
bucket:SetAsync(player, 0, 600)
end
-- Abrufen des Wertes eines Spielers
local player = "myPlayer"
local bucket = getSortedMapBucket(player)
local playerScore = bucket:GetAsync(player)
print(playerScore)

Sharding einer Warteschlange

Das Sharding einer Warteschlange ist komplizierter als das Sharding einer sortierten Map. Obwohl Sie den Anfrage-Durchsatz auf mehrere Warteschlangen verteilen möchten, erfolgen Einfügungen, Lesevorgänge und Entfernungen immer nur an der Vorder- oder Rückseite der Warteschlange.

Eine Lösung besteht darin, eine rotierende Warteschlange zu verwenden, was bedeutet, mehrere Warteschlangen zu erstellen und zwischen ihnen zu wechseln, wenn Sie ein Element hinzufügen oder lesen:

  1. Erstellen Sie mehrere Warteschlangen und fügen Sie sie einem Array hinzu.

  2. Erstellen Sie zwei lokale Zeiger. Einer repräsentiert die Warteschlange, aus der Sie Elemente lesen und entfernen möchten. Der andere repräsentiert die Warteschlange, in die Sie Elemente hinzufügen möchten:

    • Für Lesevorgänge berechnen Sie die Anzahl der benötigten Elemente aus jeder Warteschlange sowie, wo der Lesezeiger hin verschoben werden soll.
    • Für Entfernungsoperationen übergeben Sie die IDs von der Leseoperation an jede Warteschlange.
    • Für Hinzufügungsoperationen fügen Sie der Warteschlange am Hinweizeiger hinzu und erhöhen den Zeiger.
Sharding einer Warteschlange

-- Initialisieren Sie den MemoryStore-Service
local MemoryStoreService = game:GetService("MemoryStoreService")
-- Erstellen Sie Ihre Warteschlangen
local q1 = MemoryStoreService:GetQueue("q1")
local q2 = MemoryStoreService:GetQueue("q2")
local q3 = MemoryStoreService:GetQueue("q3")
local q4 = MemoryStoreService:GetQueue("q4")
-- Warteschlangen in ein Array einfügen
local queueArr = { q1, q2, q3, q4 }
-- Erstellen Sie zwei Zeiger, die die Indizes der Lese- und Hinzufügewarteschlangen repräsentieren
local readIndex = 1
local addIndex = 1
-- Erstellen Sie eine lokale Funktion, die die Indizes entsprechend aktualisiert
local function rotateIndex(index, n)
return (index + n - 1) % 4 + 1
end
-- Erstellen Sie eine lokale Funktion, die n Elemente aus der Warteschlange liest
local function readFromQueue(count, allOrNothing, waitTimeout)
local endIndex = count % 4
local countPerQueue = count // 4
local items = {}
local ids = {}
-- Schleifen durch jede Warteschlange
for i = 1, 4, 1 do
-- Bestimmen, ob diese Warteschlange ein zusätzliches Element liest
local diff = i - readIndex
if diff < 0 then
diff += 4
end
local queue = queueArr[i]
-- Elemente aus jeder Warteschlange lesen
-- +1 Element, falls zusätzliche Lesekriterien erfüllt werden
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
-- Erstellen Sie eine lokale Funktion, die n Elemente aus der Warteschlange entfernt
local function removeFromQueue(ids)
for i = 1, 4, 1 do
local queue = queueArr[readIndex]
queue:RemoveAsync(ids[i])
end
end
-- Erstellen Sie eine lokale Funktion, die ein Element zur Warteschlange hinzufügt
local function addToQueue(itemKey, expiration, priority)
local queue = queueArr[readIndex]
queue:AddAsync(itemKey, expiration, priority)
addIndex = rotateIndex(addIndex, 1)
end
-- Schreiben Sie etwas Code!
for _, player in game:GetService("Players"):GetPlayers() do
addToQueue(player, 600, 0)
end
local players, ids = readFromQueue(20, true, -1)
removeFromQueue(ids)

Hash-Maps

Hash-Maps haben keine individuellen Speicher- oder Elementanzahlgrenzen und werden automatisch ge-sharded, aber Sie können dennoch auf Drosselungen stoßen, wenn Sie sie schlecht verwenden.

Betrachten Sie zum Beispiel ein Spiel mit einer Hash-Map von Daten, die als Wert eines einzelnen Schlüssels namens metadata gespeichert sind. Wenn diese Metadaten ein verschachteltes Objekt mit Informationen wie Platz-ID, Spieleranzahl und mehr enthalten, müssen Sie bei jedem Bedarf an den Metadaten GetAsync("metadata") aufrufen und das gesamte Objekt abrufen. In diesem Fall gehen alle Anfragen an einen einzigen Schlüssel und damit an eine einzelne Partition.

Anstelle von Metadaten, die alle als ein einzelnes, verschachteltes Objekt gespeichert werden, ist der bessere Ansatz, jedes Feld als seinen eigenen Schlüssel zu speichern, damit die Hash-Map von automatischem Sharding profitieren kann. Wenn Sie eine Trennung zwischen Metadaten und dem Rest der Hash-Map benötigen, fügen Sie ein Namenspräfix hinzu (z. B. metadata_user_count anstelle von nur user_count).

Darüber hinaus, wenn einer oder wenige Schlüssel häufig abgerufen werden, ist es wichtig, diese Aufrufe auf viele Schlüssel zu verteilen. Wenn beispielsweise alle Spieledienste einen Wert von einem Hash-Map-Schlüssel abrufen müssen, können Sie auf Partitionierung drosseln. Um dies zu verhindern, können Sie diese Aufrufe über mehrere Schlüssel mit dem gleichen Wert streuen, bis keine Partitionierung drosselt.

©2026 Roblox Corporation. Roblox, das Roblox-Logo und "Powering Imagination" gehören zu unseren eingetragenen und nicht eingetragenen Markenzeichen in den USA und anderen Ländern.