2 pièce(s) jointe(s)
Zend et base de données : mécanisme interne
Bonjour,
Je développe une application à l'aide du Zend Framework et rencontre quelques "problèmes" dans la gestion de la base de données liés à l'architecture de cette dernière.
En effet, pour faire simple, cette dernière s'architecture autour d'une multitude de serveurs répliqués de la manière qui suit :
http://www.developpez.net/forums/att...1&d=1324371036
Ainsi, les Masters sont accessibles en Lecture/Ecriture, tandis que les Slaves ne le sont qu'en lecture. Des requêtes de MAJ se feront donc sur les Masters tandis que des simples requêtes de lectures se feront sur les Slaves. Un "répartiteur", permet, en fonction de la charge des machines, de répartir le flux vers tel ou tel serveur. Dès qu'une mise à jour est détectée, les Masters se synchronisent entre eux et synchronisent les Slaves de manière avoir des données homogènes et cohérentes. Voici pour l'architecture simplifiée, j'espère avoir été compréhensible... :)
Au niveau du code désormais, sont gérés des logs applicatifs. Le modèle BDD simplifié est le suivant :
http://www.developpez.net/forums/att...1&d=1324371492
Lorsqu'une requête est effectuée, son accès est loggué dans la table Requete et l'identifiant est récupéré. Les paramètres sont loggué dans la table ParametresRequete (avec la référence vers Requete grace à l'id récupéré), idem pour les éventuelles erreurs. Une série de traitement est effectuée pour créer les résultats à retourner et ces derniers sont loggués dans la table RésultatsRequete (avec la aussi la référence vers Requete).
Le problème qui se pose est le suivant. Nous avons en moyenne entre 2 et 4 requêtes à la seconde, et donc autant de logs. Parmi cs nombreux logs ressortent quelques erreurs d'insertion BDD (à hauteur de 0.015%, ce qui est ridicule je vous l'accorde, mais j'aimerais comprendre... :) ). Cette erreur d'insertion est la suivante :
Code:
SQLSTATE[23000]: Integrity constraint violation: 1452 Cannot add or update a child row: a foreign key constraint fails (`ResultatsRequete`, CONSTRAINT `fk_resultatsrequete_requete` FOREIGN KEY (`ref_id_resultats_requete`) REFERENCES `Requete` (`id_requete`) ON DELETE CASCADE ON UPDATE CASCADE)
Comme on peut le voir, ce qui plante est l'insertion dans la table de résultat en cause de la référence vers la table de requete. Il tente en effet d'insérer dans résultat une référence vers une requete qui n'existe pas, d'où l'erreur. Mais alors pourquoi donc, étant donné qu'il n'y a aucune raison à ce que la requête n'ait pas été correctement insérée ? Nos investigations nous poussent à croire, et c'est la première fois que le cas se présente, que le problème est liée à l'architecture BDD mise en place, ou au fonctionnement interne de Zend, tout dépend le point de vue :-). En effet, dans la théorie, le log de requete est bien inséré en base de données, mais sur l'un des serveur Master. L'architecture BDD fait que les masters doivent alors se synchroniser entre eux. Sauf que, les requêtes étant très nombreuses et le laps de temps entre l'insertion requête/résultats très court, il semblerait que la synchro n'est pas eu le temps de s'effectuer. Pour le peu que la répartition de la charge fasse que le log de résultat se fasse alors sur le second Master, non encore synchronisé, il tente alors en effet de référer sur une requete qu'il ne connait pas encore...
La question que nous nous posons, en ce qui concerne Zend, est alors pourquoi ce dernier créé-t-il une autre connexion BDD et ne conserve-t-il pas la même ? (si la même était conservée, il aurait alors écris sur le même serveur). A quel moment est donc effectuée la connexion vers la base de données ? Lors de la création de chacun des objets Mappers ou DbTable ? N'y a-t-il aucun moyen de lui spécifier d'encapsuler un ensemble d'opérations BDD dans une même connexion, même si cet ensemble d'opérations utilise une multitude de DbTable différents ? La mise en place d'une transaction pourrait-elle régler le problème ?
L'architecture BDD mise en place a été longuement pensée et réfléchie pour pouvoir gérer l'énorme charge qu'elle subit et palier les éventuels défaut d'un serveur, d'où cette réplication. Il n'est donc pas envisageable de la revoir par nos amis NAS. D'où la recherche d'une solution au niveau dev.
Merci pour toutes les suggestions que vous pourrez m'apporter.
Solution pour l'insertion en BDD avec clefs étrangères et contrôle par Load Balancer (IP virtuelle)
Pour aider mon prochain, voici la solution que j'ai mise en place pour régler le problème. Le problème étant tellement spécifique, je ne suis pas sûr que beaucoup s'y reconnaîtront, mais sait-on jamais ! :)
Les modifications tournent actuellement en production et tout semble parfaitement fonctionnel.
L'astuce a été de modifier totalement la gestion de la connexion à la base de données. Comme mes précédents posts le montrent, les connexions étaient "effectuées" via l'appel aux objets DbTable de chacune des tables :
Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
|
$dataRequest = array(
//...
);
$logsRequest = new Default_Model_DbTable_Request();
$idRequest = $logsRequest ->insert($dataRequest);
//...
$dataRequestParameters = array(
'id_request' => $idRequest ,
//...
);
$logsRequestParameters = new Default_Model_DbTable_RequestParameters();
$logsRequestParameters->insert($dataRequestParameters);
//etc. |
Pour régler ce problème de Load Balancer et d'IP virtuelle qui donnait la main à l'un ou l'autre serveur suivant la charge, j'ai finalement géré ma connexion totalement à mano :
Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
|
$dbc = $this->_config->resources->multidb->xxx->toArray();
$this->_db = Zend_Db::factory($dbc['adapter'], $dbc);
$this->_db->getConnection();
//...
$this->_db->insert('logsRequest', $dataRequest);
$this->_idRequest = $this->_db->lastInsertId();
//...
$this->_db->insert('logsRequestParameters', $dataRequestParameters);
//..
$this->_db->closeConnection();
$this->_db = null; |
Bien évidemment le code n'est pas si simple, j'ai juste présenté l'idée :D
Ainsi, la connexion n'est ouverte qu'une unique fois (en réalité lors de la construction de l'objet) ainsi l'insertion se fait toujours sur le même serveur octroyé par le Load Balancer. Les insert se font alors sans problème (plus d'erreurs de clefs étrangères, puisque les données sont cette fois ci bel et bien présentes) et la déconnexion s'effectue à la destruction de l'objet...
Voili Voilou ! En espérant que ça puisse en aider d'autres !