Skip to content

Commit 6d34406

Browse files
committed
GH-4 # fix init_db taking too much time
remove profiling code
1 parent 7f5db7e commit 6d34406

File tree

3 files changed

+27
-11
lines changed

3 files changed

+27
-11
lines changed

README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,16 @@ On propose d'utiliser [Kvrocks](https://github.com/apache/kvrocks) qui est une s
5050

5151
Pour le backend on utilise Python et FastAPI pour une question de simplicité de ces outils.
5252

53+
---
54+
55+
Lors de la création du script d'initialisation, arrive le moment de faire le premier test avec le jeu de donnée complet (Voir [commit](https://github.com/Hugo-C/HIBP/commit/7f5db7e5eecfada55918ec67b2e98cba9b911c9e)). Le script est relativement lent et je l'arrete après quelques minutes. En essayant sur 100k entrée le script prend 34s, ce qui donnerai 80mins avec les 14M d'entrées.
56+
J'utilise un outils de profiling (en l'occurence Sentry car je l'avais sous la main). Il montre que plus de 90% du temps est passé dans `PasswordStorage.add_password`:
57+
58+
![profile Sentry](doc/sentry_profiling.png)
59+
60+
Plusieurs solutions sont envisagées. La première est de pousser les données en batch, ce qui nécessite avec `sadd` de pousser plusieurs password qui ont le même prefix. Cette solution parait compliqué à mettre en place, car elle oblige a stocker beaucoups d'informations en mémoire. D'autres solutions comme utiliser de l'asynchrone, ou encore du multiprocessing implique une forte complexité supplémentaire. Par exemple découper le fichier en entrée en une centaine de mini fichiers pour être traité par des "workers" différents. Heureusement Redis fournit un système de batch de commande via [les pipelines](https://redis.io/docs/latest/develop/use/pipelining/) qui permet d'avoir des commandes différentes dans un seul appel. Après quelques essaie, des batchs de 200 commandes semble être le bon compromis qui permet de faire 100k password en 3s, ce qui est plus acceptable.
61+
62+
5363
### Partie génération de mot de passe
5464

5565
TODO

src/common/classes.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,24 @@ def prefix(password: Password) -> Prefix:
1818

1919

2020
class PasswordStorage:
21+
PIPELINE_MAX_SIZE = 200
22+
2123
def __init__(self, client: Redis):
2224
self.client = client
25+
self._pipe = client.pipeline(transaction=False)
26+
self._pipe_size = 0
2327

2428
def add_password(self, *, prefix: Prefix, password: Password):
25-
self.client.sadd(prefix, password)
29+
self._pipe.sadd(prefix, password)
30+
self._pipe_size += 1
31+
if self._pipe_size == self.PIPELINE_MAX_SIZE:
32+
self._pipe.execute()
33+
self._pipe_size = 0
2634

2735
def get_passwords(self, prefix: Prefix) -> set[Password]:
2836
passwords_as_bytes = self.client.smembers(prefix)
2937
return {password.decode() for password in passwords_as_bytes}
38+
39+
def flush(self):
40+
self._pipe.execute()
41+
self._pipe_size = 0

src/init_scripts/init_db_with_password_file.py

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -30,24 +30,20 @@ def init_db(file_path: str, db_client: Redis) -> int:
3030
hasher = PasswordHasher()
3131
storage = PasswordStorage(client=db_client)
3232
processed = 0
33-
with open(file_path) as file:
33+
with open(file_path, encoding='latin-1') as file:
3434
for line in file:
3535
password = line.strip()
3636
prefix = hasher.prefix(password)
3737
storage.add_password(prefix=prefix, password=password)
3838
processed += 1
3939
if processed % 100 == 0:
4040
print(f'\r{processed=}', end="")
41+
storage.flush()
42+
print("\ndone")
4143
return processed
4244

4345

4446
if __name__ == "__main__":
45-
import sentry_sdk
46-
sentry_sdk.init(
47-
dsn="XXX",
48-
traces_sample_rate=1.0,
49-
)
50-
5147
args = docopt(__doc__)
5248
if args.get("--download"):
5349
print("not implemented yet")
@@ -56,9 +52,7 @@ def init_db(file_path: str, db_client: Redis) -> int:
5652
print(f"Initializing db with {password_path}")
5753
start = time.time()
5854
redis_client = Redis(host=KVROCKS_HOST, port=KVROCKS_PORT)
59-
sentry_sdk.profiler.start_profiler()
6055
init_db(password_path, db_client=redis_client)
61-
sentry_sdk.profiler.stop_profiler()
6256
duration = time.time() - start
63-
print(f"{duration=}")
57+
print(f"Took {duration:.2f}s")
6458
# else docopt will show help

0 commit comments

Comments
 (0)