SQL Server : une contrainte conditionnelle

Mon besoin d’une contrainte conditionnelle s’était exprimé sur une contrainte de type UNIQUE, que je voulais restreindre à certaines lignes, c’est à dire les lignes pour lesquelles une condition du type MaColonne = X est satisfaite. Ce billet montre 2 manières différente de les créer.

Prenons l’exemple suivant :

CREATE TABLE Moron (
	Id INT NOT NULL,
	Name NVARCHAR(25),
	IsDeleted BIT NOT NULL,
	CONSTRAINT UQ_Moron_Name UNIQUE(Name)
)

On définit cette table comme étant les abrutis présents dans une classe.
On sait qu’il ne peut pas y avoir 2 abrutis avec le même nom dans une classe, on tape donc dans le marbre la contrainte UQ_Moron_Name  pour que cette valeur de vérité soit toujours vérifiée.

Supposons maintenant qu’un abruti Aurélien s’en aille, on va le marquer IsDeleted = 1 .
Supposons ensuite, qu’un autre abruti arrive en classe et qu’il s’appelle aussi Aurélien. Boum, on ne peut pas l’insérer dans la table à cause de la contrainte d’intégrité.
L’idéal serait donc de modifier cette contrainte (ça serait vraiment dommage de la supprimer, non ?), et ainsi prendre en compte cette nouvelle réalité, à savoir que ce qui nous intéresse est : il ne peut pas y avoir 2 abrutis avec le même nom dans une classe, c’est à dire que c’est la contrainte précédente mais uniquement lorsque IsDeleted = 0 .

Et donc ?

Il faut trouver le moyen de réaliser une contrainte conditionnelle, et deux moyens au moins s’offrent à nous :

La vue

On peut créer une vue telle que définie:

CREATE View ActiveMoron
AS
	SELECT Name
	FROM Moron
	WHERE IsDeleted = 0
GO
CREATE UNIQUE CLUSTERED INDEX IDX_Active_Moron ON ActiveMoron(Name)

On raffine ainsi la table aux colonnes essentielles à la condition (avec le SELECT ), et aux lignes sur lesquelles on doit l’appliquer (avec le WHERE ).
Il n’y a plus qu’à poser la contrainte, ici via un index UNIQUE .

L’index conditionnel

Disponible à partir de SQL Server 2008, il est possible de poser des index contenant une condition. Ainsi

CREATE UNIQUE INDEX IDX_Active_Moron ON ActiveMoron(Name) WHERE IsDeleted = 0

est complètement équivalent à la solution précédente, du point de vue de la contrainte, mais la vue en moins.

Source

Utiliser ruby (on rails) avec nginx

Ruby on Rails, ce n’est vraiment pas la panacée du point du déploiement, et je dirais même que c’est une technologie chiante avec tous ses modules et bugs obscurs dû à des dépendances pourries, mais lorsqu’on désire utiliser l’excellent gestionnaire de projet redmine, il faut bien mettre les mains dans le cambouis.

Jusqu’à présent, je faisais tourner redmine en utilisant mongrel, donc avec une passerelle fastcgi pour le faire communiquer avec nginx.
C’est une solution complètement naze, j’en conviens, car de cette manière, une seule requête pouvait être traitée à la fois, mais au moins c’était fonctionnel.
J’ai pu tricher en détournant les requêtes vers les ressources statiques vers nginx de la manière suivante :

server {
	listen 443;
	listen [mon_ipv6]:443 ssl ipv6only=on;
	server_name redmine.mondomaine.com;

	access_log /var/log/nginx/redmine.access_log main;
	error_log /var/log/nginx/redmine.error_log notice;

	root /var/www/redmine/htdocs/public;

	location ~* (^$|/$|^/(projects|attachments)|^[^\.]+$) {
		proxy_pass http://127.0.0.1:1091;
		proxy_set_header X-Real-IP $remote_addr;
		proxy_set_header Host $host;
		proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
		proxy_set_header X_FORWARDED_PROTO 'https';
		proxy_redirect http://redmine.mondomaine.com https://$host;
	}
}

Mais ce genre de rustine atteint très rapidement ses limites dès lors que plusieurs personnes utilisent le site en même temps.

Depuis que redmine est passé en version 1.4.x, j’ai mis à jour les modules demandés selon les prérequis, et là ça marche… si on veut :

  • consommation de mon quad-core de manière très variable mais plus souvent vers le 80% que vers le 0%, même lorsqu’il n’y a personne
  • warning en folie sur des instructions dépréciées

C’est une situation qui n’est pas tenable très longtemps, mon instance teamspeak sur le même serveur pourra confirmer, ça laggait grave.

Comment remédier à ces 3 problèmes que je rappelle :

  • consommation CPU délirante
  • warnings
  • mono-tache (1 requête à la fois)

J’ai tenté la solution phusion passenger, qui consiste à compiler un module passenger à Nginx. Nginx n’étant pas modulaire, compiler un module pour Nginx revient à recompiler Nginx.

Solution 1

J’ai installé la dernière version stable disponible dans portage, la version =www-apache/passenger-3.0.7 , puis utilisé la commande passenger-install-nginx-module . J’ai tenté, j’ai eu une erreur, je n’ai pas cherché très longtemps car cette solution ne me plaît pas : la commande va recompiler nginx, avec les modules par défaut et le module passenger, donc exit les USE que j’avais utilisés sur nginx.

Solution 2

J’ai utilisé passenger –start . A la première exécution, il va compiler une version de nginx avec passenger et l’installer quelque part dans /var/lib.

Cette solution est mieux au niveau architecture car on se retrouve avec deux instances nginx :

  • l’instance d’origine, qui était là avant passenger, celle que j’ai compilé et configuré reste et prend le rôle de frontend
  • passenger se lance dans une seconde instance d’une autre compilation de nginx (celle incluant le module passenger, ici ce nginx n’est qu’une coquille pour passenger). Elle a le rôle de backend et héberge donc l’application ruby. Elle communique avec le frontend par fastcgi.

Ca n’a pas marché, passenger n’étant pas fichu de compiler correctement la dépendance libev.

Solution 3

Mettons donc à jour passenger avec gem en version 3.0.13 (gem install passenger -v 3.0.13 ), non encore stable sur ma distribution au moment où je rédige ce billet. En retestant la solution 2, ça marche 🙂
Mais j’ai toujours une consommation CPU délirante.

Solution 4 (c’est la bonne)

OK, la solution 3 fonctionne, mais avoir 2 nginx, c’est un peu naze. N’y-a-t-il pas moyen de compiler « manuellement » nginx ? Ainsi j’aurais passenger directement intégré à mon nginx principal.
En lisant la doc un peu plus loin, c’est effectivement possible.
Et bah c’est partie, je copie l’ebuild nginx dans mon overlay, et zou on intègre passenger.
Ça compile, très bon signe ; on notera que des fichiers supplémentaires ont été générés durant la compilation pour phusion passenger : l’agent Nginx.

J’ajoute la configuration suivante dans nginx :

http {
	# path obtenu avec passenger-config --root
	passenger_root /usr/local/lib64/ruby/gems/1.8/gems/passenger-3.0.13;
	passenger_max_pool_size 10;
}

server {
	listen 443;
	listen [mon_ipv6]:443 ssl ipv6only=on;
	server_name redmine.mondomaine.com;

	access_log /var/log/nginx/redmine.access_log main;
	error_log /var/log/nginx/redmine.error_log notice;

	root /var/www/redmine/htdocs/public;
	passenger_enabled on;
	passenger_use_global_queue on;
	passenger_user mongrel_redmine;
}

Et là c’est jackpot, tous les problèmes ont été résolus, que ça soit les vieux pics cpu ou les warnings, c’est maintenant de l’histoire ancienne (jusqu’à la prochaine grosse update de redmine…), et j’ai gagné en rapidité dans un environnement multi-utilisateurs vu que j’ai désormais un pool et non plus un seul processus pour gérer les requêtes vers l’application redmine (qui repose sur le framework ruby on rails). Le changement se ressent immédiatement.

Je n’ai pas trop cherché à comprendre pourquoi, mais j’ai obtenu une conf parfaitement fonctionnelle, donc ON NE TOUCHE PLUS.

Pour les intéressés sous Gentoo, j’ai un overlay contenant un ebuild permettant de compiler nginx avec passenger. Il faut juste ajouter ça dans layman pour synchroniser l’overlay : https://raw.github.com/LordVeovis/gentoo/master/repositories.xml.

Je suppose, sans l’avoir testé, que cet ebuild est capable de fonctionner avec une version plus ancienne de passenger, comme celle marquée comme stable dans portage, mais je ne testerais pas, tant que ça ne bug pas.

Voir aussi