web-dev-qa-db-fra.com

Partager un dict avec plusieurs scripts Python

Je souhaite qu'une base de données dictclé/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.

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).

13
Basj

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.

10
georgexsh

Avant Redis, il y avait Memcached (qui fonctionne sur Windows). Voici un tutoriel. https://realpython.com/blog/python/python-memcache-efficient-caching/

4
Back2Basics

Je considérerais 2 options, les deux sont des bases de données incorporées

SQlite

Comme répondu ici et ici ça devrait aller

BerkeleyDB

lien

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

2
ffeast

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.

Exemple

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

2
irti

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.

0
Martín Gómez

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

0
Marcin

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.

0
SirAirhard

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.

0
ryati