IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Zend Framework PHP Discussion :

Zend et base de données : mécanisme interne [ZF 1.11]


Sujet :

Zend Framework PHP

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Membre confirmé Avatar de eaglesnipe
    Homme Profil pro
    Ingénieur Etudes et Développement
    Inscrit en
    Janvier 2008
    Messages
    75
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Ingénieur Etudes et Développement
    Secteur : High Tech - Produits et services télécom et Internet

    Informations forums :
    Inscription : Janvier 2008
    Messages : 75
    Par défaut 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 :



    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 :



    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 : Sélectionner tout - Visualiser dans une fenêtre à part
    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.
    Images attachées Images attachées   

  2. #2
    Membre chevronné

    Homme Profil pro
    Développeur Web
    Inscrit en
    Février 2003
    Messages
    253
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 41
    Localisation : France, Rhône (Rhône Alpes)

    Informations professionnelles :
    Activité : Développeur Web

    Informations forums :
    Inscription : Février 2003
    Messages : 253
    Par défaut
    Bonjour,

    Demande très détaillée, par contre je pense qu'on manque de quelques morceaux pour bien saisir d'où vient le problème...

    Zend_Db n'est pas sensé créer plus d'une seule connexion, sauf bien sûr... si on en créé plusieurs instances.

    La seule subtilité que je connaisse concernant la connexion à la base est qu'elle est temporisée jusqu'à la première requête (ou demande explicite de vérifier la connexion) : ça implique qu'instancier un objet Zend_Db ne déclenche pas la connexion immédiatement, ça se fait plus tard, lorsque le besoin se fait sentir. En gros, rien de ce qui concerne ton problème, ça pose surtout des soucis de contrôle d'erreur.

    Peux-tu répondre à quelques questions :
    - Quel driver est utilisé ? PDO ?
    - As-tu bien une seule connexion Zend_Db, laquelle est utilisée par tous les Zend_Db_Table ?
    - Dans l'idéal si tu pouvais détailler la configuration de ton appli, même sans rentrer dans les détails confidentiels savoir comment tu configures ton instance Zend_Db pourrait aider à comprendre ce qui se passe.

  3. #3
    Membre confirmé Avatar de eaglesnipe
    Homme Profil pro
    Ingénieur Etudes et Développement
    Inscrit en
    Janvier 2008
    Messages
    75
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Ingénieur Etudes et Développement
    Secteur : High Tech - Produits et services télécom et Internet

    Informations forums :
    Inscription : Janvier 2008
    Messages : 75
    Par défaut
    Bonjour Nighty, et merci de t'intéresser à mon problème...

    Pour répondre à tes questions :

    - Quel driver est utilisé ? PDO ?
    Oui. Plus précisément, voici mon adapter defini dans le .ini :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    resources.multidb.xxx.adapter  = "Pdo_Mysql"
    - As-tu bien une seule connexion Zend_Db, laquelle est utilisée par tous les Zend_Db_Table ?
    - Dans l'idéal si tu pouvais détailler la configuration de ton appli, même sans rentrer dans les détails confidentiels savoir comment tu configures ton instance Zend_Db pourrait aider à comprendre ce qui se passe.
    Là j'ai un peu plus de mal à répondre dans la mesure où je reprend cette application existante (depuis de nombreuse années) depuis peu et ne maîtrise donc pas encore toutes les subtilités du code. Ce que je peux dire d'ores et dejà :

    - Est-utilisé un Zend_Db::factory pour gérer les connexions. Le mécanisme mis en place par mon/mes prédécesseur(s) est le suivant. Un exemple avec la classe Default_Model_DbTable_Requete qui gère ma table requete, mais le principe est le même pour tous les DbTable :

    La DbTable :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    class Default_Model_DbTable_Request extends Default_Model_DbTable_Abstract
    {
     
    	/**
    	 * Nom de l'adaptateur
    	 * @var string
    	 */
    	protected $_adapter = 'xxx';
     
    	/**
    	 * Nom de la table
    	 * @var string
    	 */
        protected $_name = 'Request';
     
        /**
         * Clé primaire
         * @var string
         */
        protected $_primary = 'id_request';
    }
    La classe mère :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    abstract class Default_Model_DbTable_Abstract extends Zend_Db_Table_Abstract
    {
     
    	/**
    	 * Adaptateur SQL.
    	 *
    	 * @var Zend_Db_Adapter_Abstract
    	 */
    	protected $_adapter = null;
     
    	/**
    	 * Constructeur.
    	 *
    	 * @param  array   $config
    	 */
    	public function __construct($config = array())
    	{
    		try {
    			if($this->_adapter !== null) {
    				$c  = Zend_Registry::get('config');
    				$db = $c->resources->multidb->{$this->_adapter}->toArray();
    				$config = array(self::ADAPTER => Zend_Db::factory($db['adapter'], $db));
    			}
     
    			parent::__construct($config);
     
    		} catch(Zend_Db_Adapter_Exception $e) {
    			throw new Exception($e->getMessage());
    		}
    	}
    }
    La ressource associée dans le .ini :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    resources.multidb.xxx.adapter  = "Pdo_Mysql"
    resources.multidb.xxx.host     = "ip_server"
    resources.multidb.xxx.username = "username"
    resources.multidb.xxx.password = "password"
    resources.multidb.xxx.dbname   = "dbname"

    Au niveau insertion, le code ressemble à ceci. Avant tout, chaque appel de fonction par un client (il s'agit d'un service web) est loggué. Une exemple de traitement avec l'une des fonctions :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    /****************/
    /* Fonctions du WS**/
    /****************/
    public function maFonction($param1, $param2) {
     
            // Enregistrement de l'appel à la méthode dans les logs.
            $idLogsRequest = $this->_logAccess(
                    __FUNCTION__, array('param1' => $param1, 'param2' => $param2)
            );
     
           // Requete BDD pour récupérer les résultats
     
           // Construction des résultats
     
     
           // Construction des données de logs puis log
           $data = array(
                    //Mes données de logs
           );
           $this->_logResults($idLogsRequest, $data);
     
         // On retourne la réponse
     }
     
     
    /****************************/
    /* Fonctions de traitements des logs */
    /****************************/
    public function logAccess($methodName, array $args = array()) {
     
            //...
     
            // On prepare les données de log
            $dataRequest = array(
                'execution_date' => date('Y-m-d H:i:s'),
                'remote_addr' => $request->getServer('REMOTE_ADDR'),
                'remote_port' => $request->getServer('REMOTE_PORT'),
            );
     
            $logsRequest = new Default_Model_DbTable_Request();
            try {
                $idRequest = $logsRequest ->insert($dataRequest);
            } catch (Exception $e) {
                // Gestion exception
            }
     
                // On constuit correctement le tableau de paramètres pour insertion
                $dataRequestParameters = array(
                    'ref_id_request' => $idRequest,
                );
     
                // On créé le tableau de paramètres
                $parameters = array();
                foreach ($args as $id => $value) {
                        $parameters[$id] = $value;
                }
     
                $dataRequestParameters = array_merge($dataRequestParameters, $parameters);
     
                // On créé le DbTable paramètre qui permettra l'insertion
                $logsRequestParameters = new Default_Model_DbTable_RequestParameters();
     
                try {
                    $logsRequestParameters->insert($dataRequestParameters);
                } catch (Exception $e) {
                    // Traitement de l'exception
                }
            }
     
            return $idLogsRequest;
        }
     
     
    public function logResults($idLogsRequest, array $data) {
     
             //...
     
            // On créé le tableau de données.
            $dataLogsRequestResult = array();
            $dataLogsRequestResult['ref_id_request'] = (int) $idLogsRequest;
            $dataLogsRequestResult[...] = $data[...]
     
     
            // On créé le DbTable abstrait qui permettra l'insertion
            $logRequestResult = new Default_Model_DbTable_RequestResults();
     
            try {
                // On insère
                $logRequestResult->insert($dataLogsRequestResult);
            } catch (Exception $e){
                //Gestion des exceptions
            }
        }
    Voici de manière simplifiée la procédure de logs. Je dis "simplifiée" car toutes les fonctions de logs sont en fait appelées via un objet du modèle afin de ne pas faire ces traitements dans le coeur du web service, ce qui n'aurait pas de sens, mais le traitement n'est ni plus ni moins que celui-ci.

    Point de vue du problème, la difficulté est rencontrée uniquement lors d'une insertion d'un log de résultat (dans moins de 1% des cas) où j'obtiens l'erreur énoncée dans mon premier post, traitement qui se fait donc dans une autre fonction : la requete qu'il cherche à référencer n'existe pas (encore) [lié à la synchronisation Master dont j'ai parlé plus haut].

    La question que je me posais donc : à quel moment précis la connexion à la BDD est-elle effectuée, puis relâchée ?

    J'espère ne pas t'avoir assommé avec tout ce texte.. :-)

  4. #4
    Membre chevronné

    Homme Profil pro
    Développeur Web
    Inscrit en
    Février 2003
    Messages
    253
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 41
    Localisation : France, Rhône (Rhône Alpes)

    Informations professionnelles :
    Activité : Développeur Web

    Informations forums :
    Inscription : Février 2003
    Messages : 253
    Par défaut
    Merci pour tes retours.

    Réponse à prendre avec des réserves, je n'ai jamais expérimenté des connexions à des serveurs de base de données multiples. Par contre, je doute de me tromper en répondant à tes questions :

    à quel moment précis la connexion à la BDD est-elle effectuée
    Lors du premier appel à _connect(), c'est une méthode protected appelée par Zend_Db_Adapter_Abstract dés que le besoin se présente (premier appel à getConnection(), ou tout autre appel qui nécessite que la connexion se fasse).

    puis relâchée ?
    Lors d'un appel explicite à closeConnection(), ou à la fin du script.

    Puisque le problème se présente ponctuellement sur la relation entre Default_Model_DbTable_RequestResults et Default_Model_DbTable_Request, es-tu sûr que les deux DbTable utilisent le même adapter ?

    Si non, ils partagent effectivement une connexion différente ce qui pourrait amener à ce problème de synchro.

    Si ce n'est pas le cas et qu'ils partagent bien la même connexion c'est curieux, tu devrais essayer si une transaction règle le problème.

  5. #5
    Membre confirmé Avatar de eaglesnipe
    Homme Profil pro
    Ingénieur Etudes et Développement
    Inscrit en
    Janvier 2008
    Messages
    75
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Ingénieur Etudes et Développement
    Secteur : High Tech - Produits et services télécom et Internet

    Informations forums :
    Inscription : Janvier 2008
    Messages : 75
    Par défaut
    Merci pour tes précisions.

    Je suis en train d'adapter le code afin de justement que le tout soit encadré par une transaction.

    Dès que j'aurai pu tester la nouvelle version, je ferai un retour ici, en espérant que le problème soit résolu... :-)

    (En ce qui concerne l'adapter, il n'y a aucune raison que les deux DbTable n'utilisent pas le même... Je me penche donc sur la transaction avec l'espoir que ça apportera une solution..)

    Encore merci !!

  6. #6
    Membre chevronné

    Homme Profil pro
    Développeur Web
    Inscrit en
    Février 2003
    Messages
    253
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 41
    Localisation : France, Rhône (Rhône Alpes)

    Informations professionnelles :
    Activité : Développeur Web

    Informations forums :
    Inscription : Février 2003
    Messages : 253
    Par défaut
    Citation Envoyé par eaglesnipe Voir le message
    (En ce qui concerne l'adapter, il n'y a aucune raison que les deux DbTable n'utilisent pas le même... Je me penche donc sur la transaction avec l'espoir que ça apportera une solution..)
    Je me permet de réagir, selon ton code il est tout à fait possible que les deux DbTable utilisent deux connexions différentes : il suffit que leur propriété $_adapter n'ait pas la même valeur. Je ne dis pas que c'est le cas, mais ça reste une possibilité

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. utiliser zend sans base de données
    Par Khleo dans le forum Autres composants
    Réponses: 6
    Dernier message: 31/10/2013, 15h02
  2. [ZF 1.10] Zend et base de donnée
    Par brice97431 dans le forum Zend Framework
    Réponses: 1
    Dernier message: 06/12/2011, 23h43
  3. Problème de base de données avec Zend exception PDO
    Par websurfeur dans le forum Zend Framework
    Réponses: 8
    Dernier message: 20/04/2007, 16h49
  4. Base de Donnée Interne
    Par kedare dans le forum JDBC
    Réponses: 9
    Dernier message: 17/03/2006, 20h15
  5. diffuser sur un réseau interne une base de données Access
    Par comme de bien entendu dans le forum Access
    Réponses: 3
    Dernier message: 22/09/2005, 09h25

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo