I. Introduction

Issue de l'époque lointaine (au moins 10 ans !) ou il n'était pas aisé de faire cohabiter une grosse masse d''information sur de nombreux raw devices, ventilés sur de non moins nombreux disques, la gestion des fichiers de donnée sous Sybase ASE peut paraître quelque peu complexe au néophyte.

Le prix à payer à ce jour est une mauvaise compréhension de la complexité de la gestion des espaces de stockage du DBA Junior, et la mécompréhension de l'inexistence de 2 spécificités existant déjà dans d'autres SGBDR :

  1. L'inexistence d'une clause AUTOEXTENT lors de la création de devices (cf. autre article du même auteur)
  2. L'incapacité de réduire la taille d'un device ayant été trop largement taillé

Cet article a pour but d'apporter une solution à la soit disant impossibilité de réduire un devices sous ASE.

II. L'explication historique

Sybase ASE (anciennement Sybase SQL Server) permettait en fait de définir avant tout ses espaces de stockage, puis ensuite de tailler dans lesdits espaces les segments nécessaires à chaque base de données.

Il faut garder en mémoire que jusqu'en version 11.9, le seul système de stockage supporté en production était le raw device. Il permettait d'assurer une intégrité des accès disques à ASE (en évitant tout cache OS). Ce raw device était généralement créé par l'administrateur système, et non pas par l'administrateur de la base. Une fois cet espace créé, sa taille ne pouvait varier... ce qui explique que l'autoextension ou la réduction du fichier ne pouvait être une option au niveau de l'instance ASE.

Nous avions donc une application claire de ce que Codd souhaitait : une dissociation claire et nette de la partie logique (base de données, tables) et de la partie physique (devices, fichiers sur disques, ...).

Ce qui différencie donc ASE de la plupart de ses concurrents, et ce qui explique certaines contraintes, c'est que l'on y crée des devices neutres. Ce n'est qu'à l'affection de zones que l'on détermine leur contenu, et que le contenu peut être variable. C'est d'ailleurs pour cela que le stockage des informations sur les devices se fait dans la table sysdevices de master, puis que c'est ensuite une table de liens master..sysusages qui stocke le partages des devices de sysdevices avec les dbid de sysdatabases.

Découpage des devices et des segments de base
sysusages

Microsoft a cassé cette logique dès la version 2000 en rapatriant les informations de stockage dans la base elle-même, et en dissociant fortement les fichiers de données (.mdb) et ceux du journal de transactions (.ldb).

Afin d'éviter d'éventuels verrouillages et des accès disques très prétéritants sur les tables système, le métamodèle de Sybase ASE a toujours tenté de rester le plus normalisé possible. Pour des raisons de performances, l'engineering de Sybase s'est toujours fait un devoir de minimiser les accès sur ces tables. Une insertion dans une table, tant qu'elle ne modifie pas un index, ne provoquera quasi pas d'écriture dans le métamodèle.

C'est pour cette raison principale que vous ne trouverez pas, comme chez Oracle avec sa vue DBA_SEGMENTS, une liste référençant l'adresses de toutes les pages allouées pour un objet. Sous ASE, tout est chaînage de pages. Dans les tables systèmes, et principalement dans sysindexes, ne sont stockées que quelques informations concernant l'adressage du début du chaînage. Si l'on souhaite obtenir des informations sur le chaînage complet d'un objet, ce sont des outils lents et gourmands tels que dbcc page, dbcc pglinkage , ... qu'il faut alors utiliser.

Cet état de fait est la raison pour laquelle le rétrécissement d'un segment, et de fait d'un device, n'est pas quelque chose de supporté sous ASE.

Pour réduire un device en dessus de ce qui lui est alloué, il "suffit" de mettre sa valeur high à la valeur souhaitée. Si des segments lui sont déjà attribués, il faudra avant tout aller vider ces segments... ou s'assurer qu'ils soient vides... avant de corriger dans sysusages

III. Réduction de la taille d'un device

Comme je l'ai spécifié plus haut, c'est la table master..sysusages que nous allons devoir traiter ici.

Il faut envisager, pour un dbid spécifique et par ordre de lstart, les couches de segments comme des couches d'un sandwich.

master..sysusages
  1. Il est possible de compacter 2 couches conjointes de même nature
  2. Il est possible de supprimer une couche supérieure du sandwich si elle ne contient rien
  3. Il n'est pas possible de modifier une couche ayant hébergé un segment system

Comme vous pouvez le voir plus haut dans les tables systèmes, depuis la version 15, le lien entre sysusages et sysdevices ne se fait plus sur la liaison vstart between low and high, mais directement sur une clé vdevno (le low étant à 0). Selon votre version, vous aurez donc besoin de corriger ce qui suit en conséquent.

La procédure stockée sp_helpdb ne nous renseigne que partiellement puisqu'elle ne fait le distinguo qu'entre data et log segments.

Liaison entre segments et sgmaps

Afin de déterminer le contenu en segments d'un fragment de fichier, il faut se baser sur la colonne master..sysusages.segmap qui est un masque de bits et sur la table syssegments de chaque base.

Liaison entre segments et sgmaps

Notons la clause de jointure entre syssegments et sysusages qui permet de détecter quel segment se trouve sur quel fragment de device.

Relevons que par choix, nous avons laissé le segment system avec le segment data... puisque nous n'utilisons qu'un seul device spécifique pour ces 2 types de segments.

Notons enfin que la représentation tabulaire par segment pourrait nous induire en erreur puisque les 2 premières lignes ne représentent en fait qu'un device comprenant 2 segments.

Faisons fi de la couche la plus haute de journal et imaginons-nous ces fameuses couches en sandwich. Nous aurions donc:

Image non disponible

Dans le premier cas, il est aisé de coupler 2 segments de même nature puisque nous sommes en mesure de déterminer très clairement qu'ils appartiennent au même device et qu'ils sont conjoints

Image non disponible
Image non disponible

En effet, l'adresse de start logique est dans notre exemple à 133120 et la taille du segment est de 51200. Le départ de l'adresse logique suivante est à 184320 = 133120+51200. On est donc certain qu'aucun segment intercalaire n'existe.

On peut donc sans autre effectuer la modification suivante directement dans la sysusages, ceci étant bien entendu totalement non supporté officiellement par Sybase.

 
Sélectionnez
use master
exec sp_configure "allow update",1 
begin tran
-- Modifications
update sysusages set size=102400 where lstart=133120 and dbid=db_id('DVP') 
delete sysusages where lstart = 184320 and dbid=db_id('DVP') 

-- Contrôle
select * from sysusages where dbid=db_id('DVP')

-- Validation (ou rollback au cas  !)
commit 
exec sp_configure "allow update",0 

use DVP 
dbcc traceon(3601) 
dbcc checkcatalog 
select @@error
Image non disponible

Ce traitement n'as pas réellement de plus-value, hormis de prouver sa faisabilité et de supprimer la trace du disk remap.

Le chaînage des tailles et des adresses logiques explique aussi pourquoi il n'est pas aisé de réduire la taille d'un device. On peut "sans autre" (après avoir déterminé qu'aucun objet ne se trouve dans la partie à tronquer) réduire la taille d'un segment, mais il n'est pas possible de réduire la taille du fichier associé via des commandes T-SQL (on peut toujours el faire à ses risques et péril via une petite routine C insérant un EOF à une adresse donnée).

Nous allons maintenant supprimer le segment. Pour ce faire, nous devions être certain que plus aucun objet ne se trouve dans la tranche que nous voulons supprimer.

Commençons par le bout de journal se trouvant tout au sommet de notre sandwich. Pour tronquer un journal, il suffit de s'assurer que toutes les transactions sont fermée, puis de lancer une opération de troncature.

Image non disponible
 
Sélectionnez
select * from master..syslogshold where dbid=db_id('DVP') 

Si cette requête ne retourne pas de ligne, c'est qu'il n'y a pas de transactions ouvertes sur la base DVP, soit le cas que nous souhaitons.

Nous pouvons dès lors tronquer le log et supprimer le haut du sandwich, à savoir :

 
Sélectionnez

use master 
dump tran DVP with truncate_only 

use DVP 
checkpoint 

use master 
exec sp_configure "allow updates",1 
begin tran
-- Modification
delete sysusages 
where dbid = db_id('DVP') 
and lstart=(select max(lstart) from sysusages where dbid = db_id('DVP') and segmap=4) 

-- contrôle
select * from sysusages where dbid = db_id('DVP')

-- Validation
commit
exec sp_configure "allow updates",0 

use DVP 
dbcc checkcatalog 
select @@error 

dbcc checkdb 
select @@error 
Image non disponible

Dans le cas d'une base de données répliquée, il y a deux points de troncature. Celui, standard, de la dernière transaction fermée, et celui plus ancien de la dernière transaction fermée et répliquée. c'est cette dernière qui fait foi, mais cela ne devrait pas vous poser problème, le DUMP TRAN refusera de tronquer plus base.

Nous allons attaquer maintenant la réduction du device d'index. Nous nous basons sur ce que nous avons appris auparavant sur 2 blocs contigus de même nature. Nous pouvons donc sans autre couper notre segment d'index en 2 (pour cet exemple, 2 blocs de taille similaire, à savoir 50 Mo chacun). Nous en profitons pour invalider le 2e bloc en lui supprimant tout segment (segmap à 0).

 
Sélectionnez
use master
exec sp_configure "allow update",1 
begin tran
-- Modifications
update sysusages set size=size/2 where lstart=128000 and dbid=db_id('DVP') -- Division de la taille par 2, de 100Mo (51200) à 50Mo (25600)
insert into sysusages values(db_id('DVP'),0, 128000+25600, 25600, 0, null, getdate(), 15) -- Insertion du 2e bloc

-- contrôle
select * from sysusages where dbid = db_id('DVP')

-- Validation
commit

exec sp_configure "allow update",0 
Image non disponible

Que cela soit bien clair : en ce moment, le segment "blanc" du haut n'est plus allouable, mais les pages d'index initiales y sont toujours stockés. Il nous faut donc les déplacer dans la partie vive du bas.

Ceci ne peut se faire via un sp_placeobject, cette procédure ne déterminant que l'allocation future pour un objet donné. Il est donc nécessaire de reconstruire les objets du segment.

  1. Pour un segment de données, nous passerons donc par un bcp out/bcp in ou un sp_rename + select into OU par la recréation de son index cluster (s'il en existe un)
  2. Pour un segment d'index (notre cas précis), par une suppression et une recréation d'index (l'export du DDL pouvant se faire via l'utilitaires ddlgen ou des programmes plus robustes de type PowerAMC)

Lorsque le déplacement aura été fait, il nous suffira de supprimer la partie "morte" du segment

 
Sélectionnez
use master
exec sp_configure "allow update",1 
-- Modification
begin tran
delete sysusages where dbid=(db_id('DVP') and segmap=0

-- contrôle
select * from sysusages where dbid = db_id('DVP')

-- Validation
commit
exec sp_configure "allow update",0 
Image non disponible

Afin de déterminer quel objet se trouve dans le segment en question, vous pouvez sans problème requêter sur colonne segment de la table système syspartitions (avant la version 15, sur sysindexes).

Vous comprenez sans doute la raison pour laquelle il n'est pas aisé de supprimer un morceau au milieu du sandwich: la partie supprimée laisserait un trou béant difficile à colmater par la suite. Difficile, mais pas impossible : il sera toujours possible de faire coïncider l'adresse de début du bloc mort avec une adresse sommitale d'un autre sandwich = d'une autre base...

IV. Conclusion

Comme vous l'avez sans doute remarqué, la procédure est loin d'être triviale. Elle a cependant le mérite d'exister. L'autre option moins élégante mais supportée passe par la recréation d'une base de données et une régénération de tous ses objets. Il reste cependant des cas à forte volumétrie ou à haute disponibilité où cette méthode ne peut malheureusement être applicable.

V. Références