Как удаляются большие двоичные объекты в пакете RelStorage?

Этот вопрос связан с тем, как упаковать blobstorage с помощью Plone и RelStorage

Использование базы данных zodb с RelStorage и sqlite в качестве его серверной части. Я пытаюсь удалить неиспользуемые капли. В настоящее время db.pack не удаляет капли с диска. Минимальный рабочий пример ниже демонстрирует это поведение:

import logging
import numpy as np
import os
import persistent
from persistent.list import PersistentList
import shutil
import time
from ZODB import config, blob

connectionString = """
%import relstorage
<zodb main>
<relstorage>
blob-dir ./blob
keep-history false
cache-local-mb 0
<sqlite3>
    data-dir .
</sqlite3>
</relstorage>
</zodb>
"""


class Data(persistent.Persistent):
    def __init__(self, data):
        super().__init__()

        self.children = PersistentList()

        self.data = blob.Blob()
        with self.data.open("w") as f:
            np.save(f, data)


def main():
    logging.basicConfig(level=logging.INFO)
    # Initial cleanup
    for f in os.listdir("."):
        if f.endswith("sqlite3"):
            os.remove(f)

    if os.path.exists("blob"):
        shutil.rmtree("blob", True)

    # Initializing database
    db = config.databaseFromString(connectionString)
    with db.transaction() as conn:
        root = Data(np.arange(10))
        conn.root.Root = root

        child = Data(np.arange(10))
        root.children.append(child)

    # Removing child reference from root
    with db.transaction() as conn:
        conn.root.Root.children.pop()

    db.close()

    print("blob directory:", [[os.path.join(rootDir, f) for f in files] for rootDir, _, files in os.walk("blob") if files])
    db = config.databaseFromString(connectionString)
    db.pack(time.time() + 1)
    db.close()
    print("blob directory:", [[os.path.join(rootDir, f) for f in files] for rootDir, _, files in os.walk("blob") if files])


if __name__ == "__main__":
    main()

Пример выше делает следующее:

  1. Удалите все предыдущие базы данных в текущем каталоге вместе с каталогом больших двоичных объектов.
  2. Создайте базу данных/хранилище с нуля, добавив два объекта (корневой и дочерний), пока на дочерний объект ссылается корень, и выполните транзакцию.
  3. Удалите связь от корня к дочернему элементу и выполните транзакцию.
  4. Закройте базу данных/хранилище
  5. Откройте базу данных/хранилище и выполните db.pack на одну секунду в будущем.

Вывод минимального рабочего примера следующий:

INFO:ZODB.blob:(23376) Blob directory '<some path>/blob/' does not exist. Created new directory.
INFO:ZODB.blob:(23376) Blob temporary directory './blob/tmp' does not exist. Created new directory.
blob directory: [['blob/.layout'], ['blob/3/.lock', 'blob/3/0.03da352c4c5d8877.blob'], ['blob/6/.lock', 'blob/6/0.03da352c4c5d8877.blob']]
INFO:relstorage.storage.pack:pack: beginning pre-pack
INFO:relstorage.storage.pack:Analyzing transactions committed Thu Aug 27 11:48:17 2020 or before (TID 277592791412927078)
INFO:relstorage.adapters.packundo:pre_pack: filling the pack_object table
INFO:relstorage.adapters.packundo:pre_pack: Filled the pack_object table
INFO:relstorage.adapters.packundo:pre_pack: analyzing references from 7 object(s) (memory delta: 256.00 KB)
INFO:relstorage.adapters.packundo:pre_pack: objects analyzed: 7/7
INFO:relstorage.adapters.packundo:pre_pack: downloading pack_object and object_ref.
INFO:relstorage.adapters.packundo:pre_pack: traversing the object graph to find reachable objects.
INFO:relstorage.adapters.packundo:pre_pack: marking objects reachable: 4
INFO:relstorage.adapters.packundo:pre_pack: finished successfully
INFO:relstorage.storage.pack:pack: pre-pack complete
INFO:relstorage.adapters.packundo:pack: will remove 3 object(s)
INFO:relstorage.adapters.packundo:pack: cleaning up
INFO:relstorage.adapters.packundo:pack: finished successfully
blob directory: [['blob/.layout'], ['blob/3/.lock', 'blob/3/0.03da352c4c5d8877.blob'], ['blob/6/.lock', 'blob/6/0.03da352c4c5d8877.blob']]

Как видите, db.pack удаляет 3 объекта, удалит 3 объекта, но большие двоичные объекты в файловой системе не изменились.

В модульных тестах RelStorage видно, что они проверяют, удаляются ли большие двоичные объекты из файловой системы (см. здесь), но в скрипте выше это не работает.

Что я делаю неправильно? Любая подсказка/ссылка/помощь приветствуется.


person Woltan    schedule 27.08.2020    source источник
comment
Совершенно не по теме моего ответа, но: Вау, взрыв из прошлого! Проект RelStorage был задуман, когда самый крупный клиент, который у нас был в Jarn, основной компании экосистемы Plone, в которой я работал, требовал, чтобы все данные хранились в Oracle. Никаких если и но! Поэтому мы поручили Шейну Хэтэуэю создать этот проект на основе его работы с PGStorage, и вы обнаружите, что я вложил много работы, около десяти лет назад. Рад видеть, что проект все еще развивается!   -  person Martijn Pieters    schedule 01.09.2020


Ответы (1)


По умолчанию каталог хранилища BLOB-объектов используется как кеш, в котором хранятся данные BLOB-объектов, которые также хранятся в базе данных; идея заключается в том, что загрузка данных больших двоичных объектов из кэша локального диска выполняется быстрее, чем с удаленного сервера базы данных. Упаковка в хранилище без истории с кэширующим хранилищем BLOB-объектов не удаляет недоступные файлы BLOB-объектов, вместо этого полагаясь на ограничитель размера файла, удаляющий устаревшие данные кэша, когда необходимо освободить место. Однако вы не установили предельный размер, поэтому кеш увеличивается неограниченно, и эти недоступные файлы больших двоичных объектов будут жить вечно.

Упаковка не может удалить файлы больших двоичных объектов здесь, потому что кеш является локальным для каждого клиента ZODB; это как бы вне юрисдикции хранилища ZODB. Это может быть не так очевидно при использовании SQLite в качестве уровня базы данных, но представьте себе, что вместо этого используется Postgres на отдельном сервере с несколькими клиентами на разных компьютерах, и вы можете видеть, что очистка кеша невозможна при упаковке.

Обратите внимание, что другим вариантом хранения больших двоичных объектов является общее хранилище больших двоичных объектов, которое, вероятно, ближе к ожидаемому: все данные больших двоичных объектов хранятся на диске, а не в база данных. При использовании с удаленным сервером базы данных и несколькими клиентами вам нужно будет разместить это на чем-то вроде общего ресурса NTFS. В этом случае упаковка работает непосредственно с большими двоичными объектами, а недоступные файлы больших двоичных объектов удаляются сразу же при упаковке.

У вас есть два варианта:

  • Установите предельный размер кэша больших двоичных объектов, установив blob-cache-size. При упаковке файлы BLOB-объектов по-прежнему не удаляются, но они удаляются, когда заканчивается свободное место.

  • Переключитесь на общий кэш больших двоичных объектов (установите для параметра shared-blob-dir). . Для RelStorage с поддержкой sqlite это, вероятно, имеет больше смысла, чем кеширующее хранилище больших двоичных объектов, несмотря на ужасные предупреждения в документации!

Таким образом, самым простым изменением будет переключение режимов хранения больших двоичных объектов:

connectionString = """
%import relstorage
<zodb main>
<relstorage>
blob-dir ./blob
shared-blob-dir true
keep-history false
cache-local-mb 0
<sqlite3>
    data-dir .
</sqlite3>
</relstorage>
</zodb>
"""

Затем вывод меняется на:

INFO:ZODB.blob:(26177) Blob directory '<some path>/blob/' does not exist. Created new directory.
INFO:ZODB.blob:(26177) Blob temporary directory './blob/tmp' does not exist. Created new directory.
blob directory: [['blob/.layout'], ['blob/0x00/0x00/0x00/0x00/0x00/0x00/0x00/0x03/0x03da4f169582cd22.blob', 'blob/0x00/0x00/0x00/0x00/0x00/0x00/0x00/0x03/.lock'], ['blob/0x00/0x00/0x00/0x00/0x00/0x00/0x00/0x06/0x03da4f169582cd22.blob', 'blob/0x00/0x00/0x00/0x00/0x00/0x00/0x00/0x06/.lock']]
INFO:relstorage.storage.pack:pack: beginning pre-pack
INFO:relstorage.storage.pack:Analyzing transactions committed Tue Sep  1 01:22:35 2020 or before (TID 277621285453417864)
INFO:relstorage.adapters.packundo:pre_pack: filling the pack_object table
INFO:relstorage.adapters.packundo:pre_pack: Filled the pack_object table
INFO:relstorage.adapters.packundo:pre_pack: analyzing references from 7 object(s) (memory delta: 0 KB)
INFO:relstorage.adapters.packundo:pre_pack: objects analyzed: 7/7
INFO:relstorage.adapters.packundo:pre_pack: downloading pack_object and object_ref.
INFO:relstorage.adapters.packundo:pre_pack: traversing the object graph to find reachable objects.
INFO:relstorage.adapters.packundo:pre_pack: marking objects reachable: 4
INFO:relstorage.adapters.packundo:pre_pack: finished successfully
INFO:relstorage.storage.pack:pack: pre-pack complete
INFO:relstorage.adapters.packundo:pack: will remove 3 object(s)
INFO:relstorage.adapters.packundo:pack: cleaning up
INFO:relstorage.adapters.packundo:pack: finished successfully
blob directory: [['blob/.layout'], ['blob/0x00/0x00/0x00/0x00/0x00/0x00/0x00/0x03/0x03da4f169582cd22.blob', 'blob/0x00/0x00/0x00/0x00/0x00/0x00/0x00/0x03/.lock']]

И да, макет каталога больших двоичных объектов меняется, поэтому он может работать со всеми возможными OID. Однако OID 6 был удален.

Найденные вами модульные тесты запускаются только при тестирование с общим кешем BLOB-объектов:

# If the blob directory is a cache, don't test packing,
# since packing can not remove blobs from all caches.
test_packing = shared_blob_dir
person Martijn Pieters    schedule 31.08.2020