Les performances d'une table en mémoire sont pires qu'une table sur disque

10

J'ai une table dans SQL Server 2014 qui ressemble à ceci:

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

avec (id1, id2) étant le PK. Fondamentalement, id1 est un identifiant pour regrouper un ensemble de résultats (id2, col1, col2), dont pk est id2.

J'essaie d'utiliser une table en mémoire pour se débarrasser d'une table sur disque existante qui est mon goulot d'étranglement.

  • Les données du tableau sont écrites -> lues -> supprimées une fois.
  • Chaque valeur id1 a plusieurs (dizaines / centaines de) milliers de id2.
  • Les données sont stockées dans le tableau pendant très peu de temps, par exemple 20 secondes.

Les requêtes effectuées sur ce tableau sont les suivantes:

-- 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

Voici la définition actuelle que j'ai utilisée pour le tableau:

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)

Malheureusement, cette définition entraîne une dégradation des performances par rapport à la situation précédente avec une table sur disque. L'ordre de grandeur est plus ou moins 10% plus élevé (qui dans certains cas atteint 100%, donc double temps).

Surtout, je m'attendais à gagner un super-avantage dans les scénarios à forte concurrence, compte tenu de l'architecture sans verrouillage annoncée par Microsoft. Au lieu de cela, les pires performances sont exactement lorsque plusieurs utilisateurs simultanés exécutent plusieurs requêtes sur la table.

Des questions:

  • quel est le BUCKET_COUNT correct à définir?
  • quel type d'index dois-je utiliser?
  • pourquoi les performances sont pires qu'avec la table sur disque?

Une requête de sys.dm_db_xtp_hash_index_stats renvoie:

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

J'ai changé le nombre de compartiments afin que la sortie de sys.dm_db_xtp_hash_index_stats soit:

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

Pourtant, les résultats sont presque les mêmes, sinon pire.

Cristiano Ghersi
la source
Êtes-vous sûr de ne pas avoir à renifler les paramètres? Avez-vous essayé d'exécuter les requêtes avec OPTION(OPTIMIZE FOR UNKNOWN)(voir les conseils de tableau )?
TT.
Je suppose que vous rencontrez des problèmes de chaîne de lignes. Pouvez-vous nous donner la sortie de select * from sys.dm_db_xtp_hash_index_stats ? En outre, ce lien devrait répondre à la plupart / à toutes vos questions: msdn.microsoft.com/en-us/library/…
Sean Gallardy
4
L'index de hachage n'est utile que pour les prédicats sur les deux colonnes incluses. Avez-vous essayé sans index de hachage sur la table?
Mikael Eriksson
J'ai constaté que les meilleures améliorations des performances avec la technologie en mémoire ne peuvent être obtenues qu'en utilisant des procédures stockées compilées en mode natif .
Daniel Hutmacher
@DanielHutmacher FWIW J'ai vu des contre-exemples où tous les avantages étaient de supprimer le verrouillage et d'ajouter des procédures compilées en mode natif ont donné une amélioration nulle ou négligeable. Je ne pense pas qu'il y ait de la place pour une déclaration générale (bien que vous ayez raison dans ce cas, je n'ai même pas regardé les détails).
Aaron Bertrand

Réponses:

7

Bien que ce message ne soit pas une réponse complète en raison du manque d'informations, il devrait être en mesure de vous orienter dans la bonne direction ou d'obtenir des informations que vous pourrez ensuite partager avec la communauté.

Malheureusement, cette définition entraîne une dégradation des performances par rapport à la situation précédente avec une table sur disque. L'ordre de grandeur est plus ou moins 10% plus élevé (qui dans certains cas atteint 100%, donc double temps).

Surtout, je m'attendais à gagner un super-avantage dans les scénarios à forte concurrence, compte tenu de l'architecture sans verrouillage annoncée par Microsoft. Au lieu de cela, les pires performances sont exactement lorsque plusieurs utilisateurs simultanés exécutent plusieurs requêtes sur la table.

C'est troublant car cela ne devrait certainement pas être le cas. Certaines charges de travail ne sont pas destinées aux tables en mémoire (SQL 2014) et certaines charges de travail s'y prêtent. Dans la plupart des situations, il peut y avoir une augmentation minimale des performances simplement en migrant et en choisissant les index appropriés.

À l'origine, je pensais très étroitement à vos questions à ce sujet:

Des questions:

  • quel est le BUCKET_COUNT correct à définir?
  • quel type d'index dois-je utiliser?
  • pourquoi les performances sont pires qu'avec la table sur disque?

Au début, je pensais qu'il y avait un problème avec la table et les index réels en mémoire qui n'étaient pas optimaux. Bien qu'il y ait des problèmes avec la définition d'index de hachage optimisé en mémoire, je pense que le vrai problème concerne les requêtes utilisées.

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

Cet insert devrait être extrêmement rapide s'il ne concernait que la table in memory. Cependant, il implique également une table sur disque et est soumis à tous les verrous et blocages associés à cela. Ainsi, le gaspillage en temps réel se trouve ici sur la table basée sur le disque.

Quand j'ai fait un test rapide contre 100 000 lignes d'insertion à partir de la table sur disque après avoir chargé les données en mémoire - c'était un temps de réponse inférieur à la seconde. Cependant, la plupart de vos données ne sont conservées que très peu de temps, moins de 20 secondes. Cela ne lui laisse pas beaucoup de temps pour vraiment vivre dans le cache. De plus, je ne suis pas sûr de la taille AnotherTableréelle et je ne sais pas si les valeurs sont lues sur le disque ou non. Nous devons compter sur vous pour ces réponses.

Avec la requête Select:

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

Encore une fois, nous sommes à la merci des performances des tables basées sur disque + interop. De plus, les tris ne sont pas bon marché sur les index HASH et un index non cluster doit être utilisé. Ceci est indiqué dans le guide d'index que j'ai lié dans les commentaires.

Pour donner des faits réels basés sur la recherche, j'ai chargé la SearchItemstable en mémoire avec 10 millions de lignes et AnotherTable100 000 car je n'en connaissais pas la taille réelle ni les statistiques. J'ai ensuite utilisé la requête de sélection ci-dessus pour exécuter. De plus, j'ai créé une session d'événements étendue sur wait_completed et l'ai mise dans un tampon en anneau. Il a été nettoyé après chaque passage. J'ai également couru DBCC DROPCLEANBUFFERSpour simuler un environnement où toutes les données peuvent ne pas résider en mémoire.

Les résultats n'ont rien de spectaculaire quand on les regarde dans le vide. Étant donné que l'ordinateur portable sur lequel je teste cela utilise un SSD de qualité supérieure, j'ai artificiellement réduit les performances basées sur le disque pour la machine virtuelle que j'utilise.

Les résultats sont arrivés sans aucune information d'attente après 5 exécutions de la requête uniquement sur la table en mémoire (suppression de la jointure et pas de sous-requêtes). C'est à peu près comme prévu.

Cependant, lors de l'utilisation de la requête d'origine, j'ai dû attendre. Dans ce cas, c'est PAGEIOLATCH_SH qui a du sens lorsque les données sont lues sur le disque. Étant donné que je suis le seul utilisateur de ce système et que je n'ai pas passé de temps à créer un environnement de test massif pour les insertions, les mises à jour et les suppressions par rapport à la table jointe, je ne m'attendais pas à ce qu'un verrouillage ou un blocage entre en vigueur.

Dans ce cas, encore une fois, la partie importante du temps a été consacrée à la table sur disque.

Enfin la requête de suppression. Trouver les lignes basées uniquement sur ID1 n'est pas extrêmement efficace avec un index has. S'il est vrai que les prédicats d'égalité sont les indices de hachage appropriés, le compartiment dans lequel les données tombent est basé sur l'ensemble des colonnes hachées. Ainsi, id1, id2 où id1 = 1, id2 = 2 et id1 = 1, id2 = 3 seront hachés dans des compartiments différents car le hachage sera à travers (1,2) et (1,3). Ce ne sera pas un simple scan de plage B-Tree car les index de hachage ne sont pas structurés de la même manière. Je m'attendrais alors à ce que ce ne soit pas l' indice idéal pour cette opération, mais je ne m'attendrais pas à ce qu'il prenne des ordres de grandeur plus longtemps que l'expérience. Je serais intéressé de voir le wait_info à ce sujet.

Surtout, je m'attendais à gagner un super-avantage dans les scénarios à forte concurrence, compte tenu de l'architecture sans verrouillage annoncée par Microsoft. Au lieu de cela, les pires performances sont exactement lorsque plusieurs utilisateurs simultanés exécutent plusieurs requêtes sur la table.

S'il est vrai que les verrous sont utilisés pour la cohérence logique, les opérations doivent toujours être atomiques. Cela se fait via un opérateur de comparaison basé sur le processeur spécial (c'est pourquoi In-Memory ne fonctionne qu'avec certains processeurs [quoique presque tous les processeurs fabriqués au cours des 4 dernières années]). Ainsi, nous ne recevons pas tout gratuitement, il nous restera encore du temps pour terminer ces opérations.

Un autre point à évoquer est le fait que dans presque toutes les requêtes, l'interface utilisée est T-SQL (et non les SPROC compilés nativement) qui touchent tous au moins une table sur disque. C'est pourquoi je pense qu'en fin de compte, nous n'avons en fait aucune augmentation des performances car nous sommes toujours limités aux performances des tables sur disque.

Suivre:

  1. Créez une session d'événement étendue pour wait_completed et spécifiez un SPID connu de vous. Exécutez la requête et donnez-nous la sortie ou consommez-la en interne.

  2. Donnez-nous une mise à jour sur la sortie de # 1.

  3. Il n'y a pas de nombre magique pour déterminer le nombre de compartiments pour les index de hachage. Fondamentalement, tant que les godets ne sont pas complètement remplis et que les chaînes de rangées restent en dessous de 3 ou 4, les performances doivent rester acceptables. C'est un peu comme demander: "À quoi dois-je définir mon fichier journal?" - cela va dépendre par processus, par base de données, par type d'utilisation.

Sean Gallardy
la source