web-dev-qa-db-fra.com

Pourquoi une table avec un index de colonnes en cluster aurait-elle de nombreux groupes de lignes ouverts?

Hier, je rencontrais des problèmes de performances avec une requête et après une enquête plus approfondie, j'ai remarqué ce que je crois être un comportement étrange avec un index columnstore en cluster que j'essaie de comprendre.

Le tableau est

CREATE TABLE [dbo].[NetworkVisits](
    [SiteId] [int] NOT NULL,
    [AccountId] [int] NOT NULL,
    [CreationDate] [date] NOT NULL,
    [UserHistoryId] [int] NOT NULL
)

avec l'index:

CREATE CLUSTERED COLUMNSTORE INDEX [CCI_NetworkVisits] 
   ON [dbo].[NetworkVisits] WITH (DROP_EXISTING = OFF, COMPRESSION_DELAY = 0) ON [PRIMARY]

La table contient actuellement 1,3 milliard de lignes et nous y insérons constamment de nouvelles lignes. Quand je dis constamment, je veux dire tout le temps. Il s'agit d'un flux régulier d'insertion d'une ligne à la fois dans la table.

Insert Into NetworkVisits (SiteId, AccountId, CreationDate, UserHistoryId)
Values (@SiteId, @AccountId, @CreationDate, @UserHistoryId)

Plan d'exécution ici

J'ai également un travail planifié qui s'exécute toutes les 4 heures pour supprimer les lignes en double de la table. La requête est:

With NetworkVisitsRows
  As (Select SiteId, UserHistoryId, Row_Number() Over (Partition By SiteId, UserHistoryId
                                    Order By CreationDate Asc) RowNum
        From NetworkVisits
       Where CreationDate > GETUTCDATE() - 30)
DELETE
FROM NetworkVisitsRows
WHERE RowNum > 1
Option (MaxDop 48)

Le plan d'exécution a été collé ici .

En creusant le problème, j'ai remarqué que la table NetworkVisits contenait environ 2000 groupes de lignes, avec environ 800 d'entre eux dans un état ouvert et aucun près du maximum autorisé (1048576). Voici un petit échantillon de ce que je voyais:

enter image description here

J'ai exécuté une réorganisation sur l'index, qui a compressé tous les groupes de lignes sauf un, mais ce matin, j'ai vérifié à nouveau et nous avons à nouveau plusieurs groupes de lignes ouverts - celui qui a été créé hier après la réorganisation, puis 3 autres créés chacun à peu près au moment de la suppression. travail exécuté:

TableName       IndexName           type_desc               state_desc  total_rows  deleted_rows    created_time
NetworkVisits   CCI_NetworkVisits   CLUSTERED COLUMNSTORE   OPEN        36754       0               2019-12-18 18:30:54.217
NetworkVisits   CCI_NetworkVisits   CLUSTERED COLUMNSTORE   OPEN        172103      0               2019-12-18 20:02:06.547
NetworkVisits   CCI_NetworkVisits   CLUSTERED COLUMNSTORE   OPEN        132628      0               2019-12-19 04:03:10.713
NetworkVisits   CCI_NetworkVisits   CLUSTERED COLUMNSTORE   OPEN        397718      0               2019-12-19 08:02:13.063

J'essaie de déterminer ce qui pourrait éventuellement provoquer cela pour créer de nouveaux groupes de lignes au lieu d'utiliser celui existant.

Est-ce peut-être une pression mémoire ou un conflit entre l'insert et la suppression? Ce comportement est-il documenté quelque part?

Nous exécutons SQL Server 2017 CU 16 Enterprise Edition sur ce serveur.

Le INSERT est MAXDOP 0, le DELETE est MAXDOP 48. Les seuls groupes de lignes fermés sont ceux du BULKLOAD initial puis du REORG_FORCED que j'ai fait hier, donc les raisons de finition dans sys.dm_db_column_store_row_group_physical_stats sont REORG et NO_TRIM respectivement. Il n'y a pas de groupes de lignes fermés au-delà de ceux-ci. Il n'y a aucune mise à jour en cours d'exécution sur cette table. Nous faisons en moyenne environ 520 exécutions par minute sur la déclaration d'insertion. Il n'y a pas de partitionnement sur la table.

Je connais les insertions de filet. Nous faisons la même chose ailleurs et ne rencontrons pas le même problème avec plusieurs groupes de lignes ouvertes. Notre soupçon est que cela a à voir avec la suppression. Chaque groupe de lignes nouvellement créé se situe à peu près au moment du travail de suppression planifié. Il n'y a que deux magasins delta affichant des lignes supprimées. Nous ne supprimons pas vraiment beaucoup de données de cette table, par exemple lors d'une exécution hier, elle a supprimé 266 lignes.

20
Taryn

Pourquoi une table avec un index de colonnes en cluster contient-elle de nombreux groupes de lignes ouverts?

Il existe de nombreux scénarios différents qui peuvent provoquer cela. Je vais continuer à répondre à la question générique en faveur de répondre à votre scénario spécifique, qui, je pense, est ce que vous voulez.

Est-ce peut-être une pression mémoire ou un conflit entre l'insert et la suppression?

Ce n'est pas une pression sur la mémoire. SQL Server ne demandera pas d'allocation de mémoire lors de l'insertion d'une seule ligne dans une table columnstore. Il sait que la ligne sera insérée dans un groupe de lignes delta donc l'allocation de mémoire n'est pas nécessaire. Il est possible d'obtenir plus de groupes de lignes delta que ce à quoi on pourrait s'attendre en insérant plus de 102399 lignes par instruction INSERT et en atteignant le délai d'attente d'allocation de mémoire fixe de 25 secondes. Ce scénario de pression de mémoire est cependant destiné au chargement en masse, pas au chargement lent.

Les verrous incompatibles entre DELETE et INSERT sont une explication plausible de ce que vous voyez avec votre table. Gardez à l'esprit que je n'effectue pas d'insertions en cours de production, mais l'implémentation de verrouillage actuelle pour supprimer des lignes d'un groupe de lignes delta semble nécessiter un verrou UIX. Vous pouvez le voir avec une démo simple:

Jetez quelques lignes dans le magasin delta lors de la première session:

DROP TABLE IF EXISTS dbo.LAMAK;

CREATE TABLE dbo.LAMAK (
ID INT NOT NULL,
INDEX C CLUSTERED COLUMNSTORE
);

INSERT INTO dbo.LAMAK
SELECT TOP (64000) ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
FROM master..spt_values t1
CROSS JOIN master..spt_values t2;

Supprimez une ligne dans la deuxième session, mais ne validez pas encore la modification:

BEGIN TRANSACTION;

DELETE FROM dbo.LAMAK WHERE ID = 1;

Verrous pour DELETE par sp_whoisactive:

<Lock resource_type="HOBT" request_mode="UIX" request_status="GRANT" request_count="1" />
<Lock resource_type="KEY" request_mode="X" request_status="GRANT" request_count="1" />
<Lock resource_type="OBJECT" request_mode="IX" request_status="GRANT" request_count="1" />
<Lock resource_type="OBJECT.INDEX_OPERATION" request_mode="S" request_status="GRANT" request_count="1" />
<Lock resource_type="PAGE" page_type="*" request_mode="IX" request_status="GRANT" request_count="1" />
<Lock resource_type="ROWGROUP" resource_description="ROWGROUP: 5:100000004080000:0" request_mode="UIX" request_status="GRANT" request_count="1" />

Insérez une nouvelle ligne dans la première session:

INSERT INTO dbo.LAMAK
VALUES (0);

Validez les modifications dans la deuxième session et cochez sys.dm_db_column_store_row_group_physical_stats:

dr dmv

Un nouveau groupe de lignes a été créé car l'insertion demande un verrou IX sur le groupe de lignes qu'il modifie. Un verrou IX n'est pas compatible avec un verrou UIX. Cela semble être l'implémentation interne actuelle, et peut-être que Microsoft la modifiera au fil du temps.

En ce qui concerne la procédure à suivre pour y remédier, vous devez réfléchir à la manière dont ces données sont utilisées. Est-il important que les données soient aussi compressées que possible? Avez-vous besoin d'une bonne élimination des groupes de lignes sur le [CreationDate] colonne? Serait-il acceptable que de nouvelles données n'apparaissent pas dans le tableau pendant quelques heures? Les utilisateurs finaux préféreraient-ils que les doublons n'apparaissent jamais dans le tableau plutôt que d'exister dans celui-ci jusqu'à quatre heures?

Les réponses à toutes ces questions déterminent la bonne voie à suivre pour résoudre le problème. Voici quelques options:

  1. Exécutez un REORGANIZE avec le COMPRESS_ALL_ROW_GROUPS = ON option contre le magasin de colonnes une fois par jour. En moyenne, cela signifie que la table ne dépassera pas un million de lignes dans le magasin delta. C'est une bonne option si vous n'avez pas besoin de la meilleure compression possible, vous n'avez pas besoin de la meilleure élimination de groupe de lignes sur le [CreationDate] et vous souhaitez conserver le statu quo de suppression des lignes en double toutes les quatre heures.

  2. Divisez les instructions DELETE en instructions INSERT et DELETE distinctes. Insérez les lignes à supprimer dans une table temporaire dans un premier temps et supprimez-les avec TABLOCKX dans la deuxième requête. Cela n'a pas besoin d'être en une seule transaction en fonction de votre modèle de chargement des données (uniquement des insertions) et de la méthode que vous utilisez pour rechercher et supprimer les doublons. La suppression de quelques centaines de lignes devrait être très rapide avec une bonne élimination sur le [CreationDate] colonne, que vous obtiendrez éventuellement avec cette approche. L'avantage de cette approche est que vos groupes de lignes compressés auront des plages étroites pour [CreationDate], en supposant que la date de cette colonne est la date actuelle. L'inconvénient est que vos insertions ruisselantes ne pourront plus fonctionner pendant quelques secondes.

  3. Écrivez de nouvelles données dans une table intermédiaire et videz-les dans le magasin de colonnes toutes les X minutes. Dans le cadre du processus de vidage, vous pouvez ignorer l'insertion de doublons, de sorte que la table principale ne contiendra jamais de doublons. L'autre avantage est que vous contrôlez la fréquence de vidage des données afin d'obtenir des groupes de lignes de la qualité souhaitée. L'inconvénient est que les nouvelles données ne seront pas affichées dans le [dbo].[NetworkVisits] table. Vous pouvez essayer une vue qui combine les tables, mais vous devez alors veiller à ce que votre processus de vidage des données entraîne une vue cohérente des données pour les utilisateurs finaux (vous ne voulez pas que les lignes disparaissent ou s'affichent deux fois pendant la processus).

Enfin, je ne suis pas d'accord avec d'autres réponses sur le fait qu'une refonte du tableau devrait être envisagée. Vous n'insérez que 9 lignes par seconde en moyenne dans le tableau, ce qui n'est tout simplement pas un taux élevé. Une seule session peut effectuer 1500 insertions singleton par seconde dans une table columnstore à six colonnes. Vous voudrez peut-être changer la conception de la table une fois que vous commencerez à voir des nombres autour de cela.

5
Joe Obbish

Avec des insertions goutte à goutte constantes, vous pouvez très bien vous retrouver avec de nombreux groupes de lignes deltastore ouverts. La raison en est que lorsqu'un insert démarre, un nouveau groupe de lignes est créé si tous les existants sont verrouillés. De Escalier aux index Columnstore Niveau 5: Ajout de nouvelles données aux index Columnstore

Tout insert de 102 399 lignes ou moins est considéré comme un "insert lent". Ces lignes sont ajoutées à un deltastore ouvert s'il en existe un (et non verrouillé), sinon un nouveau groupe de lignes deltastore est créé pour elles.

En général, la conception de l'index columnstore est optimisée pour les insertions en masse, et lorsque vous utilisez des insertions de filet, vous devrez exécuter la réorganisation sur une base périodique.

Une autre option, recommandée dans la documentation Microsoft, consiste à couler dans une table intermédiaire (segment de mémoire), et lorsqu'elle atteint plus de 102 400 lignes, insérez ces lignes dans l'index columstore. Voir Index Columnstore - Guide de chargement des données

Dans tous les cas, après avoir supprimé un grand nombre de données, une réorganisation est recommandée sur un index columnstore afin que les données soient réellement supprimées et que les groupes de lignes deltastore résultants soient nettoyés.

12
Tony Hinkle

Cela ressemble à un cas Edge pour les index de colonnes en cluster, et à la fin, il s'agit davantage d'un scénario HTAP sous la considération actuelle de Microsoft - ce qui signifie qu'un NCCI serait un meilleure solution. Oui, j'imagine que perdre cette compression Columnstore sur l'index cluster serait vraiment mauvais en termes de stockage, mais si votre stockage principal est Delta-Stores, vous exécutez de toute façon non compressé.

Aussi:

  • Que se passe-t-il lorsque vous réduisez le DOP des instructions DELETE?
  • Avez-vous essayé d'ajouter des index Rowstore nonclustered secondaires pour réduire le blocage (oui, cela aura un impact sur la qualité de la compression)
3
Niko Neugebuer