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 = $1Appel 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












CharlieCompter les paires












Compter les paires












Charlie3
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
- 430