Ответ 1
Ваша цель - сделать ваш процесс управляемым в пределах ограничений памяти. Чтобы иметь возможность сделать это с помощью ZODB в качестве инструмента, вам нужно понять, как работают транзакции ZODB и как их использовать.
Почему ваш ZODB растет настолько большим
Прежде всего, вам нужно понять, что совершает транзакция здесь, что также объясняет, почему ваш Data.fs становится настолько большим.
ZODB записывает данные за транзакцию, где любой постоянный объект, который был изменен, записывается на диск. Важная деталь здесь - постоянный объект, который изменился; ZODB работает в единицах постоянных объектов.
Не каждое значение python является постоянным объектом. Если я определяю прямолинейный класс python, он не будет постоянным и не будет встроенных типов python, таких как int или list. С другой стороны, любой класс, который вы определяете, который наследуется от persistence.Persistent
, является постоянным объектом. Набор классов BTrees
, а также класс PeristentList
, который вы используете в своем коде, наследуют от Persistent
.
Теперь, при фиксации транзакции, любой постоянный объект, который был изменен, записывается на диск как часть этой транзакции. Таким образом, любой объект PersistentList
, который был добавлен, будет полностью записан на диск. BTrees
справиться с этим немного более эффективно; они хранят ведра, сами по себе стойкие, которые, в свою очередь, хранят фактически сохраненные объекты. Поэтому для каждого нескольких новых узлов, которые вы создаете, в транзакцию записывается Bucket, а не вся структура BTree. Обратите внимание, что поскольку элементы, хранящиеся в дереве, сами являются постоянными объектами, только ссылки на них хранятся в записях Bucket.
Теперь ZODB записывает данные транзакций, добавляя их в файл Data.fs
, и он не удаляет старые данные автоматически. Он может построить текущее состояние базы данных, найдя самую последнюю версию данного объекта из магазина. Вот почему ваш Data.fs
растет так сильно, вы пишете новые версии больших и больших экземпляров PersistentList
по мере совершения транзакций.
Удаление старых данных называется упаковкой, которая похожа на команду VACUUM
в PostgreSQL и других реляционных базах данных. Просто вызовите метод .pack()
в переменной db
, чтобы удалить все старые версии, или используйте параметры t
и days
этого метода, чтобы установить ограничения на сколько истории сохранить, первая временная метка time.time()
(секунды с эпохи), перед которой вы можете упаковать, а days
- это количество дней в прошлом для сохранения с текущего времени или t
, если указано, Упаковка должна значительно уменьшить ваш файл данных, поскольку частичные списки в более старых транзакциях будут удалены. Обратите внимание, что упаковка является дорогостоящей операцией и, следовательно, может занять некоторое время, в зависимости от размера вашего набора данных.
Использование транзакции для управления памятью
Вы пытаетесь создать очень большой набор данных, используя упорство, чтобы обойти ограничения с памятью, и используете транзакции, чтобы попытаться сбросить все на диск. Обычно, однако, используя сигналы фиксации транзакции, вы завершили построение своего набора данных, что вы можете использовать как одно атомное целое.
То, что вам нужно использовать здесь, - это точка сохранения. Savepoints - это по существу субтранзакции, точка во время всей транзакции, где вы можете запросить временное сохранение данных на диске. При совершении транзакции они станут постоянными. Чтобы создать точку сохранения, вызовите метод .savepoint
в транзакции:
for Gnodes in G.nodes(): # Gnodes iterates over 10000 values
Gvalue = someoperation(Gnodes)
for Hnodes in H.nodes(): # Hnodes iterates over 10000 values
Hvalue =someoperation(Hnodes)
score = SomeOperation on (Gvalue,Hvalue)
btree_container.setdefault(Gnodes, PersistentList()).append(
[Hnodes, score, -1 ])
transaction.savepoint(True)
transaction.commit()
В приведенном выше примере я установил флаг optimistic
равным True, что означает: я не намерен откатываться к этой точке сохранения; некоторые хранилища не поддерживают откатывание, и сигнализация о том, что вам не нужна, заставит ваш код работать в таких ситуациях.
Также обратите внимание, что transaction.commit()
происходит, когда весь набор данных обрабатывается, что и должно делать фиксация.
Одна вещь, которую выполняет точка сохранения, - это вызов сборщика мусора в кэшах ZODB, что означает, что любые данные, которые в настоящее время не используются, удаляются из памяти.
Обратите внимание на часть "не используется в данный момент"; если какой-либо из ваших кодов содержит большие значения в переменной, данные не могут быть удалены из памяти. Насколько я могу определить из кода, который вы нам показали, это выглядит отлично. Но я не знаю, как работают ваши операции или как вы создаете узлы; будьте осторожны, чтобы не создавать полные списки в памяти там, когда итератор будет делать, или создавать большие словари, на которые ссылаются, например, все списки списков.
Вы можете немного поэкспериментировать о том, где вы создаете свои точки сохранения; вы можете создавать его каждый раз, когда вы обрабатывали один HNodes
, или только когда это делается с помощью цикла GNodes
, как я сделал выше. Вы создаете список на GNodes
, поэтому он будет храниться в памяти, пока все петли по всем H.nodes()
, и сброс на диск, вероятно, будет иметь смысл только после завершения его полного создания.
Если вы обнаружите, что вам нужно чаще очищать память, вам следует использовать класс BTrees.OOBTree.TreeSet
или класс BTrees.IOBTree.BTree
вместо PersistentList
, чтобы разбить ваши данные на более постоянные объекты. A TreeSet
упорядочен, но не (легко) индексируется, а BTree
может использоваться как список с помощью простых инкрементных клавиш индекса:
for i, Hnodes in enumerate(H.nodes()):
...
btree_container.setdefault(Gnodes, IOBTree())[i] = [Hnodes, score, -1]
if i % 100 == 0:
transaction.savepoint(True)
В приведенном выше коде используется BTree вместо PersistentList и создается точка сохранения каждые 100 HNodes
. Поскольку BTree использует ведра, которые являются постоянными объектами сами по себе, вся структура может быть легко очищена до точки сохранения без необходимости оставаться в памяти для всех H.nodes()
для обработки.