Die Leistung einer In-Memory-Tabelle ist schlechter als die einer festplattenbasierten Tabelle

10

Ich habe eine Tabelle in SQL Server 2014, die wie folgt aussieht:

CREATE TABLE dbo.MyTable
(
[id1] [bigint] NOT NULL,
[id2] [bigint] NOT NULL,
[col1] [int] NOT NULL default(0),
[col2] [int] NOT NULL default(0)
)

wobei (id1, id2) die PK ist. Grundsätzlich ist id1 ein Bezeichner zum Gruppieren einer Reihe von Ergebnissen (id2, col1, col2), deren pk id2 ist.

Ich versuche, eine In-Memory-Tabelle zu verwenden, um eine vorhandene festplattenbasierte Tabelle zu entfernen, die mein Engpass ist.

  • Die Daten in der Tabelle werden einmal geschrieben -> gelesen -> gelöscht.
  • Jeder id1-Wert hat mehrere (Zehntausende / Hunderttausende) von id2.
  • Die Daten werden für eine sehr kurze Zeit in der Tabelle gespeichert, z. B. 20 Sekunden.

Die in dieser Tabelle ausgeführten Abfragen lauten wie folgt:

-- INSERT (can vary from 10s to 10,000s of records):
INSERT INTO MyTable
  SELECT @fixedValue, id2, col1, col2 FROM AnotherTable

-- READ:
SELECT id2, col1
FROM MyTable INNER JOIN OtherTbl ON MyTable.id2 = OtherTbl.pk
WHERE id1 = @value
ORDER BY col1

-- DELETE:
DELETE FROM MyTable WHERE id1 = @value

Hier ist die aktuelle Definition, die ich für die Tabelle verwendet habe:

CREATE TABLE dbo.SearchItems
(
  [id1] [bigint] NOT NULL,
  [id2] [bigint] NOT NULL,
  [col1] [int] NOT NULL default(0),
  [col2] [int] NOT NULL default(0)

  CONSTRAINT PK_Mem PRIMARY KEY NONCLUSTERED (id1,id2),
  INDEX idx_Mem HASH (id1,id2) WITH (BUCKET_COUNT = 131072)
) WITH (MEMORY_OPTIMIZED = ON, DURABILITY = SCHEMA_ONLY)

Leider führt diese Definition zu einer Leistungsverschlechterung gegenüber der vorherigen Situation mit einer festplattenbasierten Tabelle. Die Größenordnung ist mehr oder weniger 10% höher (die in einigen Fällen 100% erreichen, also doppelte Zeit).

Vor allem hatte ich erwartet, in Szenarien mit hoher Parallelität angesichts der von Microsoft angekündigten sperrenfreien Architektur einen Supervorteil zu erzielen. Stattdessen sind die schlechtesten Leistungen genau dann zu verzeichnen, wenn mehrere Benutzer gleichzeitig mehrere Abfragen in der Tabelle ausführen.

Fragen:

  • Was ist der richtige BUCKET_COUNT zum Einstellen?
  • Welche Art von Index soll ich verwenden?
  • Warum ist die Leistung schlechter als bei der festplattenbasierten Tabelle?

Eine Abfrage von sys.dm_db_xtp_hash_index_stats gibt Folgendes zurück:

total_bucket_count = 131072
empty_bucket_count = 0
avg_chain_len = 873
max_chain_length = 1009

Ich habe die Bucket-Anzahl so geändert, dass die Ausgabe von sys.dm_db_xtp_hash_index_stats wie folgt lautet:

total_bucket_count = 134217728
empty_bucket_count = 131664087
avg_chain_len = 1
max_chain_length = 3

Trotzdem sind die Ergebnisse fast gleich, wenn nicht sogar schlechter.

Cristiano Ghersi
quelle
Sind Sie sicher, dass Sie nicht auf Parameter-Sniffing stoßen? Haben Sie versucht, die Abfragen mit auszuführen OPTION(OPTIMIZE FOR UNKNOWN)(siehe Tabellenhinweise )?
TT.
Ich vermute, Sie haben Probleme mit der Reihenkette. Können Sie uns die Ausgabe von geben select * from sys.dm_db_xtp_hash_index_stats ? Außerdem sollte dieser Link die meisten / alle Ihre Fragen beantworten: msdn.microsoft.com/en-us/library/…
Sean Gallardy
4
Der Hash-Index ist nur für Prädikate in beiden enthaltenen Spalten nützlich. Haben Sie es ohne Hash-Index in der Tabelle versucht?
Mikael Eriksson
Ich habe festgestellt, dass die besten Leistungsverbesserungen mit In-Memory-Technologie nur mit nativ kompilierten gespeicherten Prozeduren erzielt werden können .
Daniel Hutmacher
@DanielHutmacher FWIW Ich habe Gegenbeispiele gesehen, bei denen der gesamte Vorteil darin bestand, die Verriegelung zu entfernen und nativ kompilierte Prozeduren hinzuzufügen, die keine oder eine vernachlässigbare Verbesserung ergaben. Ich glaube nicht, dass es Platz für eine pauschale Aussage gibt (obwohl Sie in diesem Fall vielleicht Recht haben, habe ich mir nicht einmal die Details angesehen).
Aaron Bertrand

Antworten:

7

Obwohl dieser Beitrag aufgrund fehlender Informationen keine vollständige Antwort darstellt, sollte er Sie in die richtige Richtung weisen oder auf andere Weise Einblicke gewinnen können, die Sie später mit der Community teilen können.

Leider führt diese Definition zu einer Leistungsverschlechterung gegenüber der vorherigen Situation mit einer festplattenbasierten Tabelle. Die Größenordnung ist mehr oder weniger 10% höher (die in einigen Fällen 100% erreichen, also doppelte Zeit).

Vor allem hatte ich erwartet, in Szenarien mit hoher Parallelität angesichts der von Microsoft angekündigten sperrenfreien Architektur einen Supervorteil zu erzielen. Stattdessen sind die schlechtesten Leistungen genau dann zu verzeichnen, wenn mehrere Benutzer gleichzeitig mehrere Abfragen in der Tabelle ausführen.

Dies ist beunruhigend, da dies definitiv nicht der Fall sein sollte. Bestimmte Workloads sind nicht für Speichertabellen vorgesehen (SQL 2014), und einige Workloads eignen sich dafür. In den meisten Situationen kann die Leistung nur durch Migration und Auswahl der richtigen Indizes minimal beeinträchtigt werden.

Ursprünglich habe ich sehr eng über Ihre diesbezüglichen Fragen nachgedacht:

Fragen:

  • Was ist der richtige BUCKET_COUNT zum Einstellen?
  • Welche Art von Index soll ich verwenden?
  • Warum ist die Leistung schlechter als bei der festplattenbasierten Tabelle?

Anfangs glaubte ich, dass es ein Problem mit der tatsächlichen In-Memory-Tabelle und den nicht optimalen Indizes gibt. Obwohl es einige Probleme mit der speicheroptimierten Hash-Index-Definition gibt, glaube ich, dass das eigentliche Problem bei den verwendeten Abfragen liegt.

-- INSERT (can vary from 10s to 10,000s of records):
INSERT INTO MyTable
  SELECT @fixedValue, id2, col1, col2 FROM AnotherTable

Diese Einfügung sollte extrem schnell sein, wenn nur die In-Memory-Tabelle beteiligt ist. Es handelt sich jedoch auch um eine festplattenbasierte Tabelle, die allen damit verbundenen Sperren und Blockierungen unterliegt. Somit liegt die Echtzeitverschwendung hier in der festplattenbasierten Tabelle.

Als ich nach dem Laden der Daten in den Speicher einen Schnelltest gegen das Einfügen von 100.000 Zeilen aus der festplattenbasierten Tabelle durchführte, waren es Antwortzeiten von weniger als einer Sekunde. Die meisten Ihrer Daten werden jedoch nur für einen sehr kurzen Zeitraum von weniger als 20 Sekunden gespeichert. Dies gibt ihm nicht viel Zeit, um wirklich im Cache zu leben. Außerdem bin ich mir nicht sicher, wie groß das AnotherTablewirklich ist und weiß nicht, ob die Werte von der Festplatte gelesen werden oder nicht. Für diese Antworten müssen wir uns auf Sie verlassen.

Mit der Select-Abfrage:

SELECT id2, col1
FROM MyTable INNER JOIN OtherTbl ON MyTable.id2 = OtherTbl.pk
WHERE id1 = @value
ORDER BY col1

Auch hier sind wir der Interop + Disk-basierten Tabellenleistung ausgeliefert. Außerdem sind Sortierungen für HASH-Indizes nicht billig, und es sollte ein nicht gruppierter Index verwendet werden. Dies wird in der Indexanleitung erwähnt, die ich in den Kommentaren verlinkt habe.

Um einige aktuelle, auf Forschung basierende Fakten zu liefern, habe ich die SearchItemsIn-Memory-Tabelle mit 10 Millionen Zeilen und AnotherTablemit 100.000 geladen, da ich die tatsächliche Größe oder Statistik nicht kannte. Ich habe dann die Auswahlabfrage oben verwendet, um auszuführen. Zusätzlich habe ich eine erweiterte Ereignissitzung auf wait_completed erstellt und in einen Ringpuffer gestellt. Es wurde nach jedem Lauf gereinigt. Ich habe auch DBCC DROPCLEANBUFFERSeine Umgebung simuliert, in der möglicherweise nicht alle Daten im Speicher gespeichert sind.

Die Ergebnisse waren nichts Spektakuläres, wenn man sie im luftleeren Raum betrachtete. Da der Laptop, auf dem ich dies teste, eine höherwertige SSD verwendet, habe ich die festplattenbasierte Leistung für die von mir verwendete VM künstlich verringert.

Die Ergebnisse kamen ohne Warteinformationen nach 5 Durchläufen der Abfrage nur für die speicherinterne Tabelle (Entfernen des Joins und keine Unterabfragen). Das ist so ziemlich wie erwartet.

Bei Verwendung der ursprünglichen Abfrage hatte ich jedoch Wartezeiten. In diesem Fall war es PAGEIOLATCH_SH, was Sinn macht, wenn die Daten von der Festplatte gelesen werden. Da ich der einzige Benutzer in diesem System bin und keine Zeit damit verbracht habe, eine umfangreiche Testumgebung für Einfügungen, Aktualisierungen und Löschvorgänge für die verknüpfte Tabelle zu erstellen, habe ich nicht erwartet, dass Sperren oder Blockieren wirksam werden.

In diesem Fall wurde erneut der erhebliche Teil der Zeit für die festplattenbasierte Tabelle aufgewendet.

Zum Schluss die Löschabfrage. Das Finden der Zeilen, die nur auf ID1 basieren, ist mit einem has-Index nicht besonders effizient. Während es stimmt, dass Gleichheitsprädikate das sind, wofür Hash-Indizes geeignet sind, basiert der Bucket, in den die Daten fallen, auf den gesamten Hash-Spalten. Somit wird id1, id2, wobei id1 = 1, id2 = 2 und id1 = 1, id2 = 3 in verschiedene Buckets gehasht, da der Hash über (1,2) und (1,3) liegt. Dies ist kein einfacher B-Tree-Bereichsscan, da Hash-Indizes nicht auf die gleiche Weise strukturiert sind. Ich würde dann erwarten, dass dies nicht der ideale Index für diese Operation ist, aber ich würde nicht erwarten, dass es, wie erlebt, Größenordnungen länger dauert. Ich würde gerne die wait_info dazu sehen.

Vor allem hatte ich erwartet, in Szenarien mit hoher Parallelität angesichts der von Microsoft angekündigten sperrenfreien Architektur einen Supervorteil zu erzielen. Stattdessen sind die schlechtesten Leistungen genau dann zu verzeichnen, wenn mehrere Benutzer gleichzeitig mehrere Abfragen in der Tabelle ausführen.

Zwar werden Sperren aus logischen Gründen verwendet, die Operationen müssen jedoch atomar sein. Dies erfolgt über einen speziellen CPU-basierten Vergleichsoperator (weshalb In-Memory nur mit bestimmten [wenn auch fast allen in den letzten 4 Jahren hergestellten CPUs] Prozessoren funktioniert). Wir bekommen also nicht alles kostenlos, es bleibt noch etwas Zeit, um diese Operationen abzuschließen.

Ein weiterer Punkt, der angesprochen werden muss, ist die Tatsache, dass in fast allen Abfragen die verwendete Schnittstelle T-SQL (und nicht nativ kompilierte SPROCs) ist, die alle mindestens eine festplattenbasierte Tabelle berühren. Aus diesem Grund glaube ich, dass wir letztendlich keine Leistungssteigerung erzielen, da wir immer noch auf die Leistung der festplattenbasierten Tabellen beschränkt sind.

Nachverfolgen:

  1. Erstellen Sie eine erweiterte Ereignissitzung für wait_completed und geben Sie eine Ihnen bekannte SPID an. Führen Sie die Abfrage aus und geben Sie uns die Ausgabe oder verbrauchen Sie sie intern.

  2. Geben Sie uns ein Update zur Ausgabe von # 1.

  3. Es gibt keine magische Zahl zum Bestimmen der Bucket-Anzahl für Hash-Indizes. Grundsätzlich sollte die Leistung akzeptabel bleiben, solange die Schaufeln nicht vollständig gefüllt sind und die Reihenketten unter 3 oder 4 bleiben. Dies ist wie die Frage: "Auf was soll ich meine Protokolldatei einstellen?" - Es wird pro Prozess, pro Datenbank, pro Nutzungstyp abhängen.

Sean Gallardy
quelle