Разделите dict с несколькими скриптами Python

Я хотел бы, чтобы уникальная база данных dict (key/value) была доступна из нескольких сценариев Python, запущенных одновременно.

Если script1.py обновляет d[2839], тогда script2.py должно увидеть измененное значение при запросе d[2839] через несколько секунд после.

Что для этого было бы Pythonic?

Примечание. Я нахожусь в Windows, а dict должен иметь максимум 1M элементов (ключ и значение обоих целых чисел).

Ответ 1

В MOS встроенного хранилища данных, отличного от SQLite, нет оптимизации для одновременного доступа, мне также было интересно, как работает одновременная производительность SQLite, поэтому я сделал контрольный показатель:

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 __name__ == '__main__':
    main()

результат на моем 4-ядерном поле macOS, том 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

результат на сервере с ядрами с 8 ядрами 2012 облачный сервер, объем 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

получается общая пропускная способность, независимо от concurrency, а SQLite медленнее в окнах, чем macOS, надеюсь, что это будет полезно.


Поскольку блокировка записи SQLite - это мудрая база данных, чтобы получить больше 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()

результат на моем mac с 20 разделами:

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

общий TPS выше, чем отдельный файл базы данных.

Ответ 3

Я бы рассмотрел 2 варианта, обе встроенные базы данных

SQlite

Как ответил здесь и здесь, это должно быть хорошо

BerkeleyDB

ссылка

Berkeley DB (BDB) - это программная библиотека, предназначенная для обеспечения высокопроизводительной встроенной базы данных для данных ключа/значения

Он был разработан специально для вашей цели

BDB может поддерживать тысячи одновременных потоков управления или одновременные процессы, обрабатывающие базы данных размером до 256 терабайты, 3 в самых разных операционных системах, включая большинство Unix-подобных и Windows-систем, а также операционных систем реального времени.

Он прочен и существует уже много лет, если не десятилетия.

Включение redis/memcached/любой другой полноценный сервер на основе сокетов, требующий участия sysops IMO, является накладной задачей для обмена данными между двумя скриптами, расположенными в том же поле

Ответ 4

Вы можете использовать словарь python для этой цели.

Создайте общий класс или script с именем G, который инициализирует словарь в нем. G будет запускать script1.py и script2.py и передает словарь в оба файла сценариев, в словаре python по умолчанию передается по ссылке. Таким образом, для хранения данных используется один словарь, и оба сценария могут изменять значения словаря, изменения можно увидеть в обоих сценариях. Я надеюсь, что script1.py и script2.py основаны на классах. Это не гарантирует сохранение данных. Для настойчивости вы можете хранить данные в базе данных через x интервалов.

Пример

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'}

Также создайте пустой файл _ init _.py в каталоге, в котором вы будете запускать скрипты.

Другой вариант:

Redis

Ответ 5

Вы можете использовать диспетчер базы данных на основе документов. Возможно, для вашей системы слишком тяжело, но одновременный доступ обычно является одной из причин, по которым используются системы управления БД и API для подключения к ним.

Я использовал MongoDB с Python, и он отлично работает. Документация API Python достаточно хороша, и каждый документ (элемент базы данных) является словарем, который может быть загружен на питон как таковой.

Ответ 6

Я бы использовал паб /sub websocket-framework, например Autobahn/Python с одним script как "сервер", и он обрабатывает все файлы, но это зависит от масштаба, возможно, это может быть Overkill.

Ответ 7

Вероятно, вам стоит взглянуть на Apache Arrow (pyarrow пакет для python).

Apache Arrow - это платформа разработки на разных языках для данных в памяти. Он определяет стандартизованный независимый от языка формат столбчатой ​​памяти для плоских и иерархических данных, организованный для эффективных аналитических операций на современном оборудовании. Он также предоставляет вычислительные библиотеки и потоковые передачи с нулевым копированием и межпроцессные коммуникации.

Связи Python со стрелками имеют первоклассную интеграцию с NumPy, pandas и встроенными объектами Python.

Формат памяти Arrow поддерживает чтение с нулевой копией для быстрого доступа к данным без накладных расходов сериализации.

Идея заключалась бы в том, чтобы сохранить в памяти представление вашей БД (Arrow), разделяемое всеми процессами и просто пишите на паркет или перо в качестве соответствующих столбчатых форматов данных на диске для сохранения.

Ответ 9

Похоже, вам действительно нужна база данных.

Если redis не будет работать для окон, я бы посмотрел на MongoDB.

https://docs.mongodb.com/manual/tutorial/install-mongodb-on-windows/

MongoDB отлично работает с python и может функционировать подобно redis. Вот установочные документы для PyMongo: http://api.mongodb.com/python/current/installation.html?_ga=2.78008212.1422709185.1517530606-587126476.1517530605

Кроме того, многие люди подняли SQlite. Я думаю, вы были обеспокоены тем, что он позволяет только одному писателю за раз, но для вас это не проблема. Я думаю, что это говорит о том, что если есть два автора, второй будет заблокирован до тех пор, пока первый не будет завершен. Вероятно, это хорошо для вашей ситуации.