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 email
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 email
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

  • 76