|
19-06-09 / 00:55 : Google AppEngine : limitations / solutions (cjed) | Voici quelques limitations à considérer pour le déploiement sur AppEngine, qui sont pour la plupart dues aux contraintes de réplication ou de garantie des performances. On peut accéder à une liste de compatibilité des différents frameworks J2EE et à une liste des APIs Java supportées.
Limitation à 1000 fichiers
Une application est limitée à 1000 fichiers. Pour Cappuccino il faut donc utiliser l'outil Press (avec option flatten - à tester avec la version 0.71 car posait problème dans la 0.70 beta) ou supprimer les fichiers .j (ne conserver que les fichiers .sj). Pour la démo de CP2JavaWS les fichiers images des composants non utilisés on été également supprimés (les performances devraient cependant être meilleures avec l'utilisation de Press).
Puisque la notion de machine physique n'existe plus il n'est pas possible d'écrire dans des fichiers. On peut uniquement lire des fichiers, qui doivent se trouver dans le WEB-INF ou être accessibles via le classloader.
Limitation des requêtes à 1000 résultats / limitation de l'offset
Pour des raisons de performances les requêtes ne peuvent retourner plus de 1000 résultats. De plus l'offset ne peut dépasser la position 1000 (0 lignes sont alors retournées), ce qui interdit de parcourir les éléments d'une table de milliers d'éléments via un limit(offset, count). On peut ajouter un critère sur l'index dans la requête, afin de récupérer les éléments en plusieurs fois (palliers de 1000), cependant cela n'est possible que si le tri est effectué sur l'index (cette solution ne fonctionne pas si on trie sur une autre colonne). Il en va de même pour une combinaison de select sur les index et de select where index in (plage d'index) limit count (la première requête étant limitée). Même problème si on passe par une table de tri temporaire (le résultat du select utilisé pour l'insert est limité), de plus cette solution oblige à recalculer le contenu de la table temporaire si la colonne de tri ou les critères de sélection additionnels changent.
Une solution est de définir une clé spéciale (voir section Python Queries on Keys, _key_), gérée en mémoire et sans limite de lecture (la même section pour Java/JDO ne précise cependant pas ces informations). Mais il faudrait pouvoir modifier dynamiquement cette clé précisée (dépend du critère de tri), ou définir pour chaque colonne de tri possible une colonne additionnelle (remplies à chaque insert), concaténation de la valeur de la colonne de tri et de l'index. On pourrait alors ajouter à la requête un critère _key_>valeur limite précédente (valeur de la colonne composite du dernier élément lu lors de la dernière requête batch pour 1000 éléments triés). Un critère sur la clé primaire ne fonctionne en effet pas si on trie sur une autre colonne, du fait que le critère s'applique avant le tri (problème contourné si la clé est composite et comprend la valeur de la colonne de tri choisie).
On peut finalement considérer que les critères de recherches doivent être affinés si une requête renvoie (plus de) 1000 résultats.
Limitation du mapping / synchronisation de caches
Hibernate n'est pas supporté, du fait qu'il instancie de manière statique un générateur d'UUID basé sur l'inetAddress (non supportée dans AppEngine comme toutes les instructions liées à la machine physique : threads, etc.) Une version modifiée d'une classe Hibernate est ainsi fournie dans CP2JavaWS (se base sur du code du framework JUG).
La démo de CP2JavaWS utilise une base HSQLDB en mémoire pour simplifier l'insallation (pas de serveur de base de données à créer), et la table et données initiales sont créées au démarrage du contexte (via un context listener). Les valeurs de la 3ème colonne (age) sont générées aléatoirement, afin de permettre le test de tri sur les 1000 enregistrements. C'est pourquoi ces données peuvent varier selon le moment d'accès (les valeurs dépendent de l'instance de l'application atteinte puisqu'elles sont en mémoire), ce qui n'est pas gênant pour cet exemple (pas de persistance).
On pourrait configurer une url vers un serveur de base de données, mais cela nécessiterait l'hébergement de ce serveur hors d'AppEngine. Et enfin l'utilisation d'un fichier local pour persister les données, même dans WEB-INF, n'est pas possible (lecture seule).
Mais surtout il faut considérer la problématique de synchronisation mémoire entre les noeuds (instances) de l'application, du fait que les couches de mapping utilisent deux caches d'objets en mémoire (synchronisation à deux niveaux). On trouve ainsi un cache de niveau 1 par persistanceManager/MappingSession (typiquement pour une session utilisateur) pour pouvoir comparer un objet de travail modifié avec l'objet original lu (fieldLocking), et un cache de niveau 2 par persistanceManagerFactory/SessionFactory pour pouvoir comparer les originaux des persistanceManagers avec les objets courants du cache central (nécessaire pour l'optimistic locking. L'accès direct - backdoor - à la base sans passer par la persistanceManagerFactory - qui est le plus souvent centralisée/récuprée via JNDI - est évidemment interdit afin de garantir l'intégrité).
Le datastore de GAE est basé sur le framework de mapping DataNucleus (implémente JDO et JPA). Par défaut le cache de niveau 2 est désactivé dans DataNucleus, mais en l'activant (paramétrage) on peut choisir son implémentation parmi les frameworks suivants : EHCache, Oracle Coherence, memcached, etc. Dans cette liste seuls Oracle Coherence et memcached permettent un fonctionnement dans un environnement distribué (réplication des caches de niveau 2 sur les différents noeuds du cloud) - également le cas pour les versions plus récentes de EHCache. Grâce à l'architecture de plugins de DataNucleus il est possible de développer une extension pour un autre framework de cache gérant le mode distribué : JBoss cache, OSCache, Terracotta, etc.
Le datastore Google utilise une implémentation propriétaire de l'interface JCache (JSR107) pour permettre la distribution du datastore, et offre une prise en compte automatique de cette réplication. A la base DataNucleus propose une API manuelle pour la réplication des datastores (par exemple le JDOReplicationManager pour synchroniser des PersistanceManagerFactory de différentes machines). L'AppEngine SDK permet aussi via les APIs Memcache de gérer (manuellement) la réplication d'objets particuliers (hors caches du datastore) si nécessaire.
Pour remplacer DataNucleus par Hibernate (peut fonctionner avec des caches distribués comme JBossCache, OSCache, Coherence et les versions plus récentes d'EHCache) dans une application GAE il faudrait pouvoir intervenir au niveau du processus de réplication (synchroniser les HibernateSessionFactory). La configuration (statique) de ces caches distribués nécessite de connaître le host des différents noeuds, information non accessible dans GAE.
Finalement on est obligé d'utiliser le datastore Google, avec les contraintes suivantes (ne viennent pas de limitations de Datanucleus) : pas de requêtes de type aggrégation, pas de requêtes polymorphiques, filtres limités, join limités, relations many-to-many limitées, etc.
BigTable n'étant pas relationnel (contrainte du cloud), l'intérêt de JDO est que cette spécification n'est pas restreinte à un datastore de type base de données relationnelle (contrairement à JPA). DataNucleus gère différents types de datastore, et une extension (plugin) pour BigTable a du être développée par Google (notamment pour gérer l'accès depuis l'interface JPA). Bien que le datastore de GAE soit d'un niveau d'abstraction plus élevé que BigTable, certaines limitations semblent directement liées à ce choix d'implémentation, le but étant de garantir un temps de réponse court quelle que soit la requête (d'où les restrictions).
Pas de support des webservices SOAP
Cela n'est à priori pas gênant puisque les solutions RDA actuelles (GWT et Cappuccino/CP2JavaWS) se basent sur du JSON (enrichi propriétaire).
Communication inter-applications
Pour communiquer avec une autre application il est nécessaire de passer par les APIs URL Fetch du SDK AppEngine.
Outils et intégration à WTP
Le plugin Eclipse permet de créer un projet web (avec le fichier de configuration propre à AppEngine pour la webapp, et les jars requis - doivent cependant être ajoutés manuellement au classpath), mais n'offre pas l'intégration avec WTP (ni le moyen d'arrêter le serveur de test AppEngine une fois lancé - il faut passer par le bouton de WTP). On peut déployer un projet AppEngine depuis un projet WTP si on renomme le WebContent en war, et si on place le fichier appengine-web.xml dans le WEB-INF du projet WTP. Cependant on perd alors la phase d'enrichissement (enhancement) requise pour les implémentations JPA et JDO du datastore (il est probable qu'on puisse simplement ajouter le builder associé dans le fichier .project).
La contrainte de la phase d'enhancement réduit cependant l'intérêt de la solution (d'autant plus si on utilise GWT qui possède également une contrainte de génération). Des solutions JDO comme LIDO proposaient de se passer d'enhancement si besoin (mais performances évidemment moindres dans ce mode dynamique). | | Commentaires | Poster un commentaire | |
|