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
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 <module>
#[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 <module>
#[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¶
# 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¶
# 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.
# 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 par serveur par pool de 10 par pool de 20 | 27.582 43.421 35.640 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