Trouver des bugs...
En améliorant les perfs !
Sébastien Besnier
Nouvelle fonctionnalité
Route "GET /subscribers/:order_id"
order_id | user_id |
---|---|
123 | abc |
465 | oij |
{"users": ["abc", "fgh"]}
SELECT user_id FROM subscribers
WHERE order_id = $1
Appel HTTP:
300 ms
Authentification via "HTTP basic"
Requête HTTP
Authorization : Basic user:pa55w0rd
storedHashedPassword <- db.getPassword("user")
Logique métier
Réponse Http
hash("pa55w0rd") == storedHashedPassword ?
À la recherche du temps perdu
console.time("bibi");
// ... des trucs
console.timeEnd("bibi");
Requête HTTP
Authorization : Basic user:pa55w0rd
storedHashedPassword <- db.getPassword("user")
Logique métier
Réponse Http
t=0
t=250ms
t=5ms
hash("pa55w0rd") == storedHashedPassword ?
t=270ms
Requête HTTP
Authorization : Basic user:pa55w0rd
storedHashedPassword <- db.getPassword("user")
Logique métier
Réponse Http
t=0
t=250ms
t=5ms
hash("pa55w0rd") == storedHashedPassword ?
t=270ms
Requête HTTP
Authorization : Basic user:pa55w0rd
storedHashedPassword <- db.getPassword("user")
Logique métier
Réponse Http
t=0
t=250ms
t=5ms
hash("pa55w0rd") == storedHashedPassword ?
bcrypt.compare:
Prévue pour être lente !
CRYPTOGRAPHIE
ASYMÉTRIQUE
t=270ms
Requête HTTP
Authorization : Basic user:pa55w0rd
storedHashedPassword <- db.getPassword("user")
Logique métier
Réponse Http
Cookie : AuthToken(JWT)
t=0
t=250ms
t=270ms
t=5ms
hash("pa55w0d") == storedHashedPassword ?
PREMIER APPEL
Requête HTTP
Authorization : Basic user:pa55w0rd
Cookie : AuthToken(JWT)
checkJWT(authToken)
Logique métier
Réponse Http
t=0
t=0.3ms
t=20ms
APPELS SUIVANTS
Requête HTTP
Authorization : Basic user:pa55w0rd
Cookie : AuthToken(JWT)
checkJWT(authToken)
Logique métier
Réponse Http
t=0
t=0.3ms
t=20ms
Crypyographie "symétrique"
ÇA TRAAAACE
TESTS CYPRESS
7min15
5min20
TESTS CYPRESS
7min15
5min20
FAILED
cy.get("#card").should("not.exist");
✔️ "C'est bon, #card n'apparaît pas!"
t = 30ms
AVANT L'OPTIMISATION
t = 0
Lancement du test
Chargement de la page
t = 5ms
cy.get("#card").should("not.exist");
✔️ "C'est bon, #card n'apparaît pas!"
t = 0
t = 30ms
Lancement du test
Chargement de la page
t = 5ms
AVANT L'OPTIMISATION
t = 300ms
Retour de la requête
Affichage de #card
cy.get("#card").should("not.exist");
❌ "Bah si, y a une #card"
t = 0
t = 30ms
Lancement du test
Chargement de la page
t = 5ms
APRÈS L'OPTIMISATION
t = 25ms
Retour de la requête
Affichage de #card
À ramener à la maison
- C'est possible de créer des bugs en améliorant les perfs !
- Une requête HTTP en localhost
=> 100ms GRAND MAXIMUM - Attention avec les "should.not.exist"!
- Avoir un/des moyens de mesurer les améliorations
Pendant ce temps, chez les commerciaux
Nom | Prénom | |
---|---|---|
Dupont | Jean | jean@dupont.fr |
Dujardin | Jean | jean@dujardin.fr |
Dumuret | Jean | jean@dumuret.fr |
Dupont | Jean | jean@dupont.fr |
Dupuis | Jean | jean@dupuis.fr |
Dumarché | Jean | jean@dumarche.fr |
Dujardin | Jean | jean@dujardin.fr |
Dupond | Jean | jean@dupond.fr |
Dupré | Jean | jean@dupre.fr |
Nom | Prénom | |
---|---|---|
Dupont | Jean | jean@dupont.fr |
Dujardin | Jean | jean@dujardin.fr |
Dumuret | Jean | jean@dumuret.fr |
Dupont | Jean | jean@dupont.fr |
Dupuis | Jean | jean@dupuis.fr |
Dumarché | Jean | jean@dumarche.fr |
Dujardin | Jean | jean@dujardin.fr |
Dupond | Jean | jean@dupond.fr |
Dupré | Jean | jean@dupre.fr |
Trouvons les doublons !
let duplicates = [];
for(let i = 0; i < users.length; i++){
for(let j = i+1; j < users.length; j++){
if(users[i].email === users[j].email){
duplicates.push( [users[i] ,users[j]]);
}
}
}
let duplicates = [];
for(let i = 0; i < users.length; i++){
for(let j = i+1; j < users.length; j++){
if(users[i].email === users[j].email){
duplicates.push( [users[i] ,users[j]]);
}
}
}
(normalize(users[i]) === normalize(users[j])){
function normalize(user) {
return user.email.toLowerCase();
}
48000 utilisateurs
=> 66 secondes !
Compter les paires
Charlie
Compter les paires
Compter les paires
Charlie
3
3
3
3
3 x 4
2
= 6
Compter les paires
4
4
4
4
4 x 5
2
= 10
Charlie
Shrek
4
Nombre de personnes | Chacune dit bonjour à... | Nombre de paires |
---|---|---|
4 | 3 | |
5 | 4 | |
6 | 5 | |
\(\frac{4\times3}{2}\)
\(\frac{5\times4}{2}\)
\(\frac{6\times5}{2}\)
Nombre de personnes | Chacune dit bonjour à... | Nombre de paires |
---|---|---|
4 | 3 | |
5 | 4 | |
6 | 5 | |
... | ... | ... |
... | ... | ... |
48 000 | 47 999 | |
\(\frac{4\times3}{2}\)
\(\frac{5\times4}{2}\)
\(\frac{6\times5}{2}\)
\(\frac{48\ 000\times47\ 999}{2}=1'151'976'000\)
\(\frac{n\times(n-1)}{2}\approx \frac{n\times n}{2} \approx \frac{n^2}2\)
Nombre de personnes | Chacune dit bonjour à... | Nombre de paires |
---|---|---|
4 | 3 | |
5 | 4 | |
6 | 5 | |
... | ... | ... |
... | ... | ... |
48 000 | 47 999 | |
n (un grand nombre) | n - 1 |
\(\frac{48\ 000\times47\ 999}{2}=1'151'976'000\)
\(\frac{n\times(n-1)}2\approx \frac{n\times n}2 = \frac{n^2}2\)
\(\frac{4\times3}{2}\)
\(\frac{5\times4}{2}\)
\(\frac{6\times5}{2}\)
# personnes | # paires |
---|---|
10 | 45 |
... | ... |
100 | 4 950 |
... | ... |
1000 | 499 500 |
... | ... |
10 000 | 49 995 000 |
... | ... |
100 000 | 4 999 995 000 |
... | ... |
1 million | BEAUCOUP!!! |
x10
x10
x10
x10
x10
x100
x100
x100
x100
x100
# personnes | # paires | Temps |
---|---|---|
10 | 45 | 900ns |
... | ... | ... |
100 | 4 950 | 100µs |
... | ... | ... |
1000 | 499 500 | 10ms |
... | ... | ... |
10 000 | 49 995 000 | 1s |
... | ... | ... |
100 000 | 49 999 950 000 | 1min40 |
... | ... | ... |
1 million | BEAUCOUP!!! | 2h45 |
x10
x10
x10
x10
x10
x100
x100
x100
x100
x100
1 comparaison
= 20 nano seconde
let duplicates = [];
for(let i = 0; i < users.length; i++){
for(let j = i+1; j < users.length; j++){
if(users[i].email === users[j].email){
duplicates.push( [users[i] ,users[j]]);
}
}
}
(normalize(users[i]) === normalize(users[j])){
function normalize(user) {
return user.email.toLowerCase();
}
48000 utilisateurs
=> 66 secondes !
let duplicates = [];
for(let i = 0; i < users.length; i++){
let normalized_i = normalize(users[i]);
for(let j = i+1; j < users.length; j++){
if(normalized_i === normalize(users[j]){
duplicates.push( [users[j] ,users[j]]);
}
}
}
48000 utilisateurs
=> 41 secondes !
Optimisation "locale"
# personnes | # paires | Temps | perf X2 | perf X4 |
---|---|---|---|---|
10 | 45 | 900ns | 450ns | 225ns |
... | ... | ... | ||
100 | 4 950 | 100µs | 50µs | 25µs |
... | ... | ... | ||
1000 | 499 500 | 10ms | 5ms | 2.5ms |
... | ... | ... | ||
10 000 | 49 995 000 | 1s | 0.5s | 0.25s |
... | ... | ... | ||
100 000 | 49 999 950 000 | 1min40 | 50s | 25s |
... | ... | ... | ||
1 million | BEAUCOUP!!! | 2h45 | 1h22 | 40min |
Optimisation globale
Clé | Valeur |
---|---|
jean@dupont.fr | [{id: 1, name, email }, { id: 5, name, email}] |
jean@dupuis.fr | [{id: 2, name, email}] |
jean@dujardin.fr | [{id: 4, name, email }, { id: 3, name, email}] |
Dictionnaire !
(hash map)
Opération en temps constant, quelque soit la taille de la "hash map" !
let emailToUsers = new Map();
for(let i = 0; i < users.length; i++){
let normalized = normalize(users[i]);
let withSameEmail = emailToUsers.get(normalized);
if(withSameEmail === undefined){
emailToUsers.set(normalized, [users[i]]);
} else{
withSameEmail.push(users[i]);
emailToUsers.set(normalized, withSameEmail);
}
}
48000 utilisateurs
=> ~50 millisecondes !
for(let key of emailToUsers.keys()){
if(emailToUsers.get(key).length == 1){
emailToUsers.delete(key);
}
}
48000 utilisateurs
=> ~10 millisecondes !
Filtrer le dictionnaire
Clé | Valeur |
---|---|
jean@dupont.fr | [{id: 1, name, email }, { id: 5, name, email}] |
jean@dujardin.fr | [{id: 4, name, email }, { id: 3, name, email}] |
Dictionnaire !
(hash map)
# personnes | Temps | perf X4 | Avec dico |
---|---|---|---|
10 | 900ns | 225ns | 12µs |
... | ... | ||
100 | 100µs | 25µs | 120µs |
... | ... | ||
1000 | 10ms | 2.5ms | 1ms |
... | ... | ||
10 000 | 1s | 0.25s | 12ms |
... | ... | ||
100 000 | 1min40 | 25s | 120 ms |
... | ... | ||
1 million | 2h45 | 40min | 1s200ms |
Solution initiale
L'heure de vérité
Notre client avait 48 000 "contacts"...
... dont 21 000 supprimables !!!
"Presque" la même chose pour les entreprises
Caco calo == Caco calo company
SARL Pespi == Pespi
MacDanold == MacDanold LTD
Dictionnaire
Trie
À ramener à la maison
- Importance de "compter" le nombre d'opérations
- n personnes => ~ n²/2 poignées de mains
- hash map : un outils puissant !
- optimisation locales vs optimisations globales
Des questions ?
Sur la vie, l'univers,... ou le sujet de la présentation ?
deck
By sebbes
deck
- 210