Un modèle MVC en javascript/ajax ******************************** il est possible d'imaginer un pseudo modèle MVC avec les technologies ajax/javascript/python. Le modèle est présenter avec le schema suivant .. figure:: data/mvc_html.png le principe est: * le naviguateur ne demande que des fichier statique au serveur html (img,js,html, css). Ce dernier représente la vue * le javascript permet sur le naviguateur de gérer la page, les évenements, les données. Ce dernier représente le modèle * le serveur bottle represente le contrôleur: il permet la mise à jours des données et échanges XML avec le modèle via l'utilisation d'AJAX (exemple de fonction: liste des articles, commande, ...) l'intérêt principal de ce odèle est que le serveur httpd ne fournit en majorité que des pages statisque ce qui permet à l'application d'avoir de très bonne performance. Les échanges entre le modèle et le contrôleur ne sont que des échanges de données ... pas de format. Il est possible alors de modifier l'ensemble du site et des intercation naviguateur/utilisateur en modifiant les css et le html. Il y a une vraie sépration entre le contenu et le contenant Au niveau des performances, l’utilisation d’Ajax change complément la répartition des charges. Dans une application web traditionnelle, le serveur est chargé de la plupart des traitements. Dans une application web Ajax, le navigateur client est de plus en plus sollicité pour effectuer des contrôles, notamment lors de la saisie des formulaires et pour afficher le nouveau contenu en provenance du serveur. Le nombre de requêtes total d’une application Ajax augmente, mais la quantité totale de données transmises a tendance à fortement diminué. Les performances en termes de rapidités sont donc améliorées par l’utilisation de la technologie Ajax. En revanche, il n’est pas rare que la première page soit plus longue à charger car elle doit prendre en compte un plus grand nombre de librairies JavaScript nécessaires au bon fonctionnement de l’application, mais c’est sans comparaison avec un rechargement répété des pages. Les performances varient aussi considérablement en fonction des techniques utilisés pour permettre au serveur d’envoyer les informations au client: certains préfèrent envoyer de multiples requêtes brèves pour avoi des nouvelles du serveur alors que d’autres applications envoient une requête qui reste longtemps en attente côté serveur. Ces deux techniques ont leurs avantages et leurs inconvénient et le choix de l’une ou l’autre dépend par conséquent des besoins de l’application. Même si l’utilisation d’Ajax permet d’améliorer les performances et surtout la simplicité d’utilisation des applications et des sites web, il faut garder à l’esprit lors du développement que tous les postes ne disposent parfois pas de la puissance nécessaire pour exécuter rapidement le code Javascript. C’est d’ailleurs pour cette raison que les navigateurs web ont tout miser ces dernières années sur la rapidité d’exécution de leurs moteurs Javascript. la relation python / sgdb ========================= Un des points crucial dans cette architecture est le relation entre le contrôleur et la base de donnée En mode wsgi il est possible d'imaginer plusieurs solutions qui possèdent chacune des avantages et des défauts dans nos exemples la structure sera la suivante :: C: |_ www |_ test1 |_ index.html |_ wsgi-scripts |_ myapp.wsgi |_ pypyodbc.py Chaque exemple à pour but de fournir le même fichier xml. On se connecte à une base postgresql en utilisant le module pypyodbc En effetlors de nos tests nous avons rencontré des soucis avec le module *pyodbc*. Nous avons l'erreur suivante :: #[Sat May 25 18:41:45 2013] [error] [client 127.0.0.1] File "C:/www/test1/wsgi-scripts/myapp.wsgi", line 16, in #[Sat May 25 18:41:45 2013] [error] [client 127.0.0.1] import odbc #[Sat May 25 18:41:45 2013] [error] [client 127.0.0.1] File "C:\\Python\\Python27\\lib\\site-packages\\odbc-3.0.5-py2.7-win32.egg\\odbc.py", line 11, in #[Sat May 25 18:41:45 2013] [error] [client 127.0.0.1] File "C:\\Python\\Python27\\lib\\site-packages\\odbc-3.0.5-py2.7-win32.egg\\odbc.py", line 10, in __bootstrap__ #[Sat May 25 18:41:45 2013] [error] [client 127.0.0.1] ImportError: DLL load failed: Le module sp\xe9cifi\xe9 est introuvable. A priori il s'agit d'un soucis de dll de .net Pour regler probleme voir http://code.google.com/p/odbc/issues/detail?id=214 et http://stackoverflow.com/questions/10193445/cannot-import-odbc-via-mod-wsgi-under-apache-on-windows une connexion par demande ------------------------- .. code-block:: python # Change working directory so relative paths (and template lookup) work again import os, sys os.chdir(os.path.dirname(__file__)) sys.path.append(os.path.dirname(__file__)) #permet l'utilisation de pypyodbc import bottle from bottle import route, run, template, static_file, request, response import time from xml.dom.minidom import getDOMImplementation import Queue import pypyodbc as odbc CNX_STR='DSN=TESTPG;UID=postgres;PWD=postgres' SQL='select count(*) from test' def generate_response(value): impl = getDOMImplementation() newdoc = impl.createDocument(None,"sample",None) top_element = newdoc.documentElement att = newdoc.createElement('msg') txt = newdoc.createTextNode(str(value)) att.appendChild(txt) top_element.appendChild(att) return newdoc.toxml('iso-8859-15') @route('/') def index(): return static_file('index.html', root='C:/www/test1') @route('/onebyquery') def onebyquery(): a = odbc.connect(CNX_STR) cursor = a.cursor() cursor.execute(SQL) row = cursor.fetchone() a.commit() response.headers['Content-Type'] = 'text/xml' return generate_response(row[0]) application = bottle.default_app() avantage: chaque demande possède une connexion manager (commit et roolback possible) défaut: le temps de connexion qui augmente le temps de réponse. Il est possible de saturer le SGBD de demande un benchmark :: C:\Program Files\Apache Software Foundation\Apache2.2\bin>ab.exe -t 30 -c 5 http://test1.fr/myapp/onebyquery This is ApacheBench, Version 2.3 <$Revision: 655654 $> Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ Licensed to The Apache Software Foundation, http://www.apache.org/ Benchmarking test1.fr (be patient) Finished 1088 requests Server Software: Apache/2.2.22 Server Hostname: test1.fr Server Port: 80 Document Path: /myapp/onebyquery Document Length: 73 bytes Concurrency Level: 5 Time taken for tests: 30.009 seconds Complete requests: 1088 Failed requests: 999 (Connect: 0, Receive: 0, Length: 999, Exceptions: 0) Write errors: 0 Non-2xx responses: 999 Total transferred: 984198 bytes HTML transferred: 756746 bytes Requests per second: 36.26 [#/sec] (mean) Time per request: 137.908 [ms] (mean) Time per request: 27.582 [ms] (mean, across all concurrent requests) Transfer rate: 32.03 [Kbytes/sec] received Connection Times (ms) min mean[+/-sd] median max Connect: 1 3 0.8 3 7 Processing: 74 135 97.8 112 1421 Waiting: 72 132 97.4 109 1413 Total: 76 137 97.7 114 1422 Percentage of the requests served within a certain time (ms) 50% 114 66% 131 75% 154 80% 171 90% 197 95% 224 98% 260 99% 309 100% 1422 (longest request) Nous avons un grand nombre de requête sans réponse cela est dut à l'erreur suivante (trouvé dans le error.log d'apache) :: [Sun May 26 20:32:03 2013] [error] [client 127.0.0.1] DatabaseError: (u'53300', u'[53300] FATAL: d\\xe9sol\\xe9, trop de clients sont d\\xe9j\\xe0 connect\\xe9s') nous saturons la base en nombre de connexion une connexion par serveur ------------------------- .. code-block:: python # Change working directory so relative paths (and template lookup) work again import os, sys os.chdir(os.path.dirname(__file__)) sys.path.append(os.path.dirname(__file__)) #permet l'utilisation de pypyodbc import bottle from bottle import route, run, template, static_file, request, response import time from xml.dom.minidom import getDOMImplementation import Queue import pypyodbc as odbc CNX_STR='DSN=TESTPG;UID=postgres;PWD=postgres' SQL='select count(*) from test' CNX = odbc.connect(CNX_STR) def generate_response(value): impl = getDOMImplementation() newdoc = impl.createDocument(None,"sample",None) top_element = newdoc.documentElement att = newdoc.createElement('msg') txt = newdoc.createTextNode(str(value)) att.appendChild(txt) top_element.appendChild(att) return newdoc.toxml('iso-8859-15') @route('/') def index(): return static_file('index.html', root='C:/www/test1') @route('/onebyserver') def onebyserver(): cursor = CNX.cursor() cursor.execute(SQL) row = cursor.fetchone() CNX.commit() response.headers['Content-Type'] = 'text/xml' return generate_response(row[0]) application = bottle.default_app() défaut: chaque demande ne possède pas une connexion manager (pas de commit ou de roolback possible).Il est possible d'avoir des ralentissements si plusieurs cursors sur la même connexion. si un cursor génère un database error on perd toutes les connexions avantage: le temps de réponse optimal car pas de reconnexion. Il n'est pas possible de saturer le SGBD de demande. un benchmark :: C:\Program Files\Apache Software Foundation\Apache2.2\bin>ab.exe -t 30 -c 5 http://test1.fr/myapp/onebyserver This is ApacheBench, Version 2.3 <$Revision: 655654 $> Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ Licensed to The Apache Software Foundation, http://www.apache.org/ Benchmarking test1.fr (be patient) Finished 3455 requests Server Software: Apache/2.2.22 Server Hostname: test1.fr Server Port: 80 Document Path: /myapp/onebyserver Document Length: 73 bytes Concurrency Level: 5 Time taken for tests: 30.004 seconds Complete requests: 3455 Failed requests: 0 Write errors: 0 Total transferred: 860295 bytes HTML transferred: 252215 bytes Requests per second: 115.15 [#/sec] (mean) Time per request: 43.421 [ms] (mean) Time per request: 8.684 [ms] (mean, across all concurrent requests) Transfer rate: 28.00 [Kbytes/sec] received Connection Times (ms) min mean[+/-sd] median max Connect: 1 3 0.9 3 6 Processing: 19 40 9.1 39 90 Waiting: 9 36 9.7 35 86 Total: 20 43 9.1 42 93 Percentage of the requests served within a certain time (ms) 50% 42 66% 46 75% 49 80% 51 90% 56 95% 61 98% 63 99% 66 100% 93 (longest request) utilisation d'un pool de connexion ---------------------------------- Pour réaliser le pool nous utilisons le module Queue il est possible d'améloirer le fonctionnement du pool. .. code-block:: python # Change working directory so relative paths (and template lookup) work again import os, sys os.chdir(os.path.dirname(__file__)) sys.path.append(os.path.dirname(__file__)) #permet l'utilisation de pypyodbc import bottle from bottle import route, run, template, static_file, request, response import time from xml.dom.minidom import getDOMImplementation import Queue import pypyodbc as odbc CNX_STR='DSN=TESTPG;UID=postgres;PWD=postgres' SQL='select count(*) from test' SIZE_POOL=10 class PoolConnect(Queue.Queue): def put(self, item, block=True, timeout=None): item.commit() Queue.Queue.put(self, item, block, timeout) POOL = PoolConnect(maxsize=SIZE_POOL) for i in range(0,SIZE_POOL): POOL.put(odbc.connect(CNX_STR)) def generate_response(value): impl = getDOMImplementation() newdoc = impl.createDocument(None,"sample",None) top_element = newdoc.documentElement att = newdoc.createElement('msg') txt = newdoc.createTextNode(str(value)) att.appendChild(txt) top_element.appendChild(att) return newdoc.toxml('iso-8859-15') @route('/') def index(): return static_file('index.html', root='C:/www/test1') @route('/bypool') def bypool(): a = POOL.get() cursor = a.cursor() cursor.execute(SQL) row = cursor.fetchone() POOL.put(a) response.headers['Content-Type'] = 'text/xml' return generate_response(row[0]) application = bottle.default_app() défaut: il est possible que certaines demandes de connexion soit longue si la queue est vide avantage: on gère nos connexions (en nombre et en commit). On ne peut saturer le serveur. un benchmark pour un pool de 10 :: C:\Program Files\Apache Software Foundation\Apache2.2\bin>ab.exe -t 30 -c 5 http://test1.fr/myapp/bypool This is ApacheBench, Version 2.3 <$Revision: 655654 $> Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ Licensed to The Apache Software Foundation, http://www.apache.org/ Benchmarking test1.fr (be patient) Finished 4210 requests Server Software: Apache/2.2.22 Server Hostname: test1.fr Server Port: 80 Document Path: /myapp/bypool Document Length: 73 bytes Concurrency Level: 5 Time taken for tests: 30.009 seconds Complete requests: 4210 Failed requests: 0 Write errors: 0 Total transferred: 1048290 bytes HTML transferred: 307330 bytes Requests per second: 140.29 [#/sec] (mean) Time per request: 35.640 [ms] (mean) Time per request: 7.128 [ms] (mean, across all concurrent requests) Transfer rate: 34.11 [Kbytes/sec] received Connection Times (ms) min mean[+/-sd] median max Connect: 1 4 1.1 4 11 Processing: 13 31 4.8 30 104 Waiting: 7 23 7.1 23 102 Total: 17 35 4.8 34 107 Percentage of the requests served within a certain time (ms) 50% 34 66% 35 75% 36 80% 37 90% 40 95% 42 98% 47 99% 53 100% 107 (longest request) un benchmark pour un pool de 20 :: C:\Program Files\Apache Software Foundation\Apache2.2\bin>ab.exe -t 30 -c 5 http://test1.fr/myapp/bypool This is ApacheBench, Version 2.3 <$Revision: 655654 $> Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ Licensed to The Apache Software Foundation, http://www.apache.org/ Benchmarking test1.fr (be patient) Finished 4279 requests Server Software: Apache/2.2.22 Server Hostname: test1.fr Server Port: 80 Document Path: /myapp/bypool Document Length: 73 bytes Concurrency Level: 5 Time taken for tests: 30.009 seconds Complete requests: 4279 Failed requests: 0 Write errors: 0 Total transferred: 1065720 bytes HTML transferred: 312440 bytes Requests per second: 142.59 [#/sec] (mean) Time per request: 35.065 [ms] (mean) Time per request: 7.013 [ms] (mean, across all concurrent requests) Transfer rate: 34.68 [Kbytes/sec] received Connection Times (ms) min mean[+/-sd] median max Connect: 1 4 1.0 4 13 Processing: 17 31 4.5 29 99 Waiting: 7 23 6.7 22 90 Total: 19 35 4.5 33 102 Percentage of the requests served within a certain time (ms) 50% 33 66% 34 75% 35 80% 35 90% 40 95% 43 98% 46 99% 49 100% 102 (longest request) comparaison ----------- +--------------+------------------+ |5 conn simul. | temps moyens (ms)| +--------------+------------------+ |par demande | 27.582 | |par serveur | 43.421 | |par pool de 10| 35.640 | |par pool de 20| 35.065 | +--------------+------------------+ A priori le temps de traitement sans erreur est meilleur avec un pool de 20 Mais avec une base locale et une requête simple les écarts restent négligeable