Je souhaite qu'une base de données dict
clé/valeur) unique soit accessible à partir de plusieurs scripts Python s'exécutant simultanément.
Si script1.py
met à jour d[2839]
, alors script2.py
devrait voir le valeur modifiée lors de l'interrogation de d[2839]
quelques secondes plus tard.
Je pensais utiliser SQLite mais il semble que l'écriture/lecture simultanée de plusieurs processus n'est pas la force de SQLite (supposons que script1.py
vient de modifier d[2839]
, comment la connexion SQLite de script2.py
pourrait-elle savoir _ (elle doit recharger cette partie spécifique de la base de données? )
J'ai aussi pensé à verrouiller le fichier quand je veux vider les modifications (mais c'est assez difficile à faire ), et utiliser json.dump
pour sérialiser, puis essayer de détecter les modifications, utiliser json.load
pour recharger si modification, etc. ... oh non, je réinvente la roue et je réinvente une base de données clé/valeur particulièrement inefficace!
redis ressemblait à une solution mais il ne supporte pas officiellement Windows , il en va de même pour leveldb .
plusieurs scripts peuvent vouloir écrire exactement au même moment (même s'il s'agit d'un événement très rare), existe-t-il un moyen de laisser le système de base de données gérer cela (grâce à un paramètre de verrouillage? Il semble que SQLite ne puisse pas le faire par défaut) Ceci parce que "SQLite supporte un nombre illimité de lecteurs simultanés, mais il ne permettra qu'un auteur à la fois." )
Quelle serait une solution Pythonic pour cela?
Remarque: Je suis sous Windows et le dict doit contenir au maximum 1 élément (clé et valeur, les deux entiers).
La plupart des magasins de données intégrés autres que SQLite ne disposent pas d'une optimisation pour l'accès simultané. J'étais également curieux des performances simultanées de SQLite. J'ai donc effectué un test de performance:
import time
import sqlite3
import os
import random
import sys
import multiprocessing
class Store():
def __init__(self, filename='kv.db'):
self.conn = sqlite3.connect(filename, timeout=60)
self.conn.execute('pragma journal_mode=wal')
self.conn.execute('create table if not exists "kv" (key integer primary key, value integer) without rowid')
self.conn.commit()
def get(self, key):
item = self.conn.execute('select value from "kv" where key=?', (key,))
if item:
return next(item)[0]
def set(self, key, value):
self.conn.execute('replace into "kv" (key, value) values (?,?)', (key, value))
self.conn.commit()
def worker(n):
d = [random.randint(0, 1<<31) for _ in range(n)]
s = Store()
for i in d:
s.set(i, i)
random.shuffle(d)
for i in d:
s.get(i)
def test(c):
n = 5000
start = time.time()
ps = []
for _ in range(c):
p = multiprocessing.Process(target=worker, args=(n,))
p.start()
ps.append(p)
while any(p.is_alive() for p in ps):
time.sleep(0.01)
cost = time.time() - start
print(f'{c:<10d}\t{cost:<7.2f}\t{n/cost:<20.2f}\t{n*c/cost:<14.2f}')
def main():
print(f'concurrency\ttime(s)\tpre process TPS(r/s)\ttotal TPS(r/s)')
for c in range(1, 9):
test(c)
if __== '__main__':
main()
résultat sur ma boîte macOS à 4 cœurs, volume SSD:
concurrency time(s) pre process TPS(r/s) total TPS(r/s)
1 0.65 7638.43 7638.43
2 1.30 3854.69 7709.38
3 1.83 2729.32 8187.97
4 2.43 2055.25 8221.01
5 3.07 1629.35 8146.74
6 3.87 1290.63 7743.78
7 4.80 1041.73 7292.13
8 5.37 931.27 7450.15
résultat sur un serveur cloud Windows Server 2012 à 8 cœurs, volume SSD:
concurrency time(s) pre process TPS(r/s) total TPS(r/s)
1 4.12 1212.14 1212.14
2 7.87 634.93 1269.87
3 14.06 355.56 1066.69
4 15.84 315.59 1262.35
5 20.19 247.68 1238.41
6 24.52 203.96 1223.73
7 29.94 167.02 1169.12
8 34.98 142.92 1143.39
s'avère que le débit global est cohérent indépendamment de la concurrence, et que SQLite est plus lent sous Windows que macOS, nous espérons que cela sera utile.
Comme le verrouillage en écriture de SQLite concerne la base de données, vous pouvez partitionner les données en plusieurs bases de données afin d’obtenir plus de TPS:
class MultiDBStore():
def __init__(self, buckets=5):
self.buckets = buckets
self.conns = []
for n in range(buckets):
conn = sqlite3.connect(f'kv_{n}.db', timeout=60)
conn.execute('pragma journal_mode=wal')
conn.execute('create table if not exists "kv" (key integer primary key, value integer) without rowid')
conn.commit()
self.conns.append(conn)
def _get_conn(self, key):
assert isinstance(key, int)
return self.conns[key % self.buckets]
def get(self, key):
item = self._get_conn(key).execute('select value from "kv" where key=?', (key,))
if item:
return next(item)[0]
def set(self, key, value):
conn = self._get_conn(key)
conn.execute('replace into "kv" (key, value) values (?,?)', (key, value))
conn.commit()
résultat sur mon mac avec 20 partitions:
concurrency time(s) pre process TPS(r/s) total TPS(r/s)
1 2.07 4837.17 4837.17
2 2.51 3980.58 7961.17
3 3.28 3047.68 9143.03
4 4.02 2486.76 9947.04
5 4.44 2249.94 11249.71
6 4.76 2101.26 12607.58
7 5.25 1903.69 13325.82
8 5.71 1752.46 14019.70
le TPS total est supérieur au fichier de base de données unique.
Avant Redis, il y avait Memcached (qui fonctionne sur Windows). Voici un tutoriel. https://realpython.com/blog/python/python-memcache-efficient-caching/
Je considérerais 2 options, les deux sont des bases de données incorporées
Comme répondu ici et ici ça devrait aller
Berkeley DB (BDB) est une bibliothèque de logiciels destinée à fournir une base de données intégrée hautes performances pour les données clé/valeur.
Il a été conçu exactement pour vos besoins
BDB peut prendre en charge des milliers de threads de contrôle ou processus simultanés manipulant des bases de données atteignant 256 téraoctets, 3 sur une grande variété de systèmes d'exploitation, y compris la plupart . Unix comme les systèmes Windows et les systèmes d’exploitation en temps réel.
Il est robuste et existe depuis des années, voire des décennies
Mise en place de redis
/memcached
/quoi que ce soit d'autre serveur basé sur socket à part entière nécessitant l'implication de sysops IMO constitue un surcoût pour la tâche permettant à la tâche d'échanger des données entre 2 scripts situés sur la même boîte
Vous pouvez utiliser le dictionnaire python à cette fin.
Créez une classe générique ou un script nommé G, qui initialise un dictionnaire. Le G exécutera script1.py & script2.py et passe le dictionnaire aux deux fichiers de script. En python, le dictionnaire est transmis par référence par défaut. De cette manière, un seul dictionnaire sera utilisé pour stocker des données et les deux scripts peuvent modifier les valeurs du dictionnaire. Des modifications peuvent être constatées dans les deux scripts. J'espère que script1.py et script2.py sont basés sur les classes. Cela ne garantit pas la persistance des données. Pour la persistance, vous pouvez stocker les données dans la base de données après x intervalles.
script1.py
class SCRIPT1:
def __init__(self, dictionary):
self.dictionary = dictionary
self.dictionary.update({"a":"a"})
print("SCRIPT1 : ", self.dictionary)
def update(self):
self.dictionary.update({"c":"c"})
script2.py
class SCRIPT2:
def __init__(self, dictionary):
self.dictionary = dictionary
self.dictionary.update({"b":"b"})
print("SCRIPT 2 : " , self.dictionary)
main_script.py
import script1
import script2
x = {}
obj1 = script1.SCRIPT1(x) # output: SCRIPT1 : {'a': 'a'}
obj2 = script2.SCRIPT2(x) # output: SCRIPT 2 : {'a': 'a', 'b': 'b'}
obj1.update()
print("SCRIPT 1 dict: ", obj1.dictionary) # output: SCRIPT 1 dict: {'c': 'c', 'a': 'a', 'b': 'b'}
print("SCRIPT 2 dict: ", obj2.dictionary) # output: SCRIPT 2 dict: {'c': 'c', 'a': 'a', 'b': 'b'}
Créez également un fichier _ init _.py vide dans le répertoire où vous allez exécuter les scripts.
Une autre option est:
Redis
Vous pouvez utiliser un gestionnaire de base de données basé sur des documents. C'est peut-être trop lourd pour votre système, mais l'accès simultané est généralement l'une des raisons pour lesquelles les systèmes de gestion de base de données et l'API permettant de s'y connecter sont en place.
J'ai utilisé MongoDB avec Python et cela fonctionne bien. La documentation de l'API Python est assez bonne et chaque document (élément de la base de données) est un dictionnaire qui peut être chargé dans python en tant que tel.
CodernintyDB pourrait être intéressant d’explorer en utilisant la version du serveur.
http://labs.codernity.com/codernitydb/
Version du serveur: http://labs.codernity.com/codernitydb/server.html
J'utiliserais un framework pub/sub websocket, comme Autobahn/Python , avec un script en tant que "serveur" et il gère toutes les communications de fichiers, mais cela dépend de l'échelle, peut-être Overkill.
On dirait que vous avez vraiment besoin d’une base de données.
Si Redis ne fonctionne pas pour Windows, je regarderais MongoDB.
https://docs.mongodb.com/manual/tutorial/install-mongodb-on-windows/
MongoDB fonctionne très bien avec python et peut fonctionner de la même manière que redis. Voici la documentation d'installation de PyMongo: http://api.mongodb.com/python/current/installation.html?_ga=2.78008212.1422709185.1556060-587126476.1517530605
En outre, de nombreuses personnes ont évoqué SQlite. Je pense que vous craigniez que cela n'autorise qu'un auteur à la fois, mais ce n'est pas vraiment un problème pour vous. Je pense que ce qu'il dit, c'est que, s'il y a deux écrivains, le second sera bloqué jusqu'à ce que le premier soit terminé. C'est probablement bien pour votre situation.