PostgreSQL и pgvector¶
Поддержка PostgreSQL намеренно разделена по контрактам: PgVectorStore и
PostgresProfileStore используют async pool Psycopg 3, а экспериментальный
PostgresMemoryLedger — синхронный command backend для строгой границы
жизненного цикла, admission, recall и checkpoint Ledger. Все три используют
одну optional-зависимость:
Экспериментальный PostgreSQL Memory Ledger¶
PostgresMemoryLedger — opt-in замена SqliteMemoryLedger, а не
автоматический upgrade существующей памяти приложения. Его публичная command
граница синхронная, потому что на ней синхронны MemoryWriter, admission и
bounded Ledger recall. Он отделён от async-адаптеров pgvector и profile; в
async request handler не вызывайте его блокирующие команды напрямую. Для
async-хоста держите его за контролируемой worker/thread boundary.
Схему Ledger нужно один раз создать из migration job в выделенной schema, которой владеет только этот экземпляр Ledger:
from protoprompt.ledger import PostgresMemoryLedger
ledger = PostgresMemoryLedger(
"postgresql://ledger_migrator:secret@db/app",
schema="app_memory_ledger",
)
print(ledger.dry_run_setup()) # только проверка, без DDL
ledger.setup() # явный идемпотентный fresh-v7 setup
ledger.close()
schema= — identifier, а не произвольный SQL. Адаптер отклоняет public,
системные schema PostgreSQL, имена pg_ и небезопасные identifier. Но всё
равно используйте новую в остальном пустую schema: без application tables,
extensions, functions, types, operators, custom triggers и ручных
Ledger-indexes. Fresh setup отклонит загрязнённую schema; затем установленный
Ledger валидирует весь свой reserved layout и fail-closed останавливается, если
он partial или изменён неожиданно. Эта проверка охватывает форму
table/column/sequence, deterministic text collation, indexes/constraints,
атрибуты guard functions, triggers, RLS policies, DML rewrite rules и
inheritance/partitioning. Migration роли нужны права создать schema, tables,
indexes и guard functions/triggers. После setup runtime-роли можно выдать
только нужные ей узкие права.
Эти проверки — fail-closed граница целостности приложения, но не защита от PostgreSQL superuser, owner schema или роли с произвольными DDL/DML. Считайте schema Ledger и credentials migration/runtime-роли доверенными и выдавайте им минимально необходимые права.
Text-колонки Ledger должны использовать deterministic PostgreSQL collation, чтобы равенство scope и key оставалось точным. Обычные deterministic locale поддерживаются; nondeterministic ICU collation приведёт к fail-closed отказу setup и последующей валидации. Не отдавайте internal database connection адаптера plugin-коду и не исполняйте через него произвольный SQL.
Этот backend принимает только свежую Ledger schema v7. Он не мигрирует старую PostgreSQL Ledger schema, не импортирует SQLite Ledger file и не делает destructive downgrade. Найденная v1–v6 или partial layout — сигнал оператору остановиться, а не best-effort upgrade. Сохраните старую систему, при необходимости export/re-ingest-ите данные через reviewed host flow и переключайте traffic только после проверки.
При старте приложения открытие адаптера создаёт синхронное соединение с БД, но не выполняет DDL. Создавайте writer только после завершённой migration job и закрывайте ledger при shutdown процесса:
from protoprompt.ledger import MemoryWriter, PostgresMemoryLedger
from protoprompt.scope import MemoryScope
ledger = PostgresMemoryLedger(dsn, schema="app_memory_ledger")
writer = MemoryWriter(
ledger,
scope=MemoryScope(tenant="acme", user="u-42", thread="support"),
)
# ... используйте обычные trusted-host API admission и lifecycle Ledger ...
ledger.close()
Сериализация записи, retry и capacity¶
Чтобы сохранить финальную проверку lifecycle и checkpoint при PostgreSQL MVCC, каждая запись Ledger в одной выделенной schema получает один transaction-scoped PostgreSQL advisory lock. Lock намеренно общий для schema, а не для каждого scope: он ставит точную семантику выше преждевременной параллелизации. Он покрывает lifecycle-команды, hard erase, изменения checkpoint и их финальные snapshots active records.
Учитывайте это в capacity plan: конкурентные записи Ledger в одной schema
сериализуются. Timeout lock — пять секунд; lock/serialization/deadlock
contention возвращается как LedgerConflictError. Хост должен повторять всю
trusted-команду с тем же стабильным event_id, где команда поддерживает
идемпотентный retry, а не повторять произвольный SQL-фрагмент. Наблюдайте за
conflict rate и задержкой команд прежде, чем считать этот экспериментальный
backend высоконагруженным event store.
Backup, recovery и границы удаления¶
PostgresMemoryLedger.backup() намеренно выдаёт NotImplementedError.
Backup PostgreSQL — ответственность оператора: используйте policy платформы
или pg_dump для выделенной schema, например:
Шифруйте backup и задайте срок хранения согласно политике данных, проверяйте
restore в изолированной БД и согласуйте изменения schema с policy
replica/PITR. forget() и erase() меняют live Ledger rows и сохраняют те же
content-free receipts/tombstones, что описаны в Memory Ledger;
они не стирают исторические backup, WAL, replica или отдельную vector/FTS
projection. Не заявляйте physical erasure без документированной platform
retention и key-management process.
Live recovery integration matrix завершает backend-соединение disposable PostgreSQL после удаления payload и после вставки aggregate receipt внутри scope purge. Fresh connection обязана увидеть точное pre-command состояние и успешно повторить всю immutable host-команду. Отдельная потеря процесса после commit обязана вернуть durable receipt, не удаляя более поздние данные. Bounded волны независимых процессов также покрывают duplicate proposal/purge retry и sibling-scope isolation. Это доказывает transaction/reconnect семантику на проверенном сервере, но не server-crash recovery, managed backup/PITR, поведение replica или physical erasure.
Явный setup¶
Конструктор и открытие store никогда не выполняют DDL. Запускайте setup из
отдельной migration job под ролью, которой разрешено создать schema.
create_extension=True также требует права установить расширение pgvector:
from protoprompt.integrations import PgVectorStore
store = PgVectorStore(
"postgresql://app:secret@db/protoprompt",
dimensions=1536,
)
await store.setup(create_extension=True, create_hnsw_index=True)
await store.close()
При старте приложения открывается только pool:
add заменяет документ целиком в одной транзакции. query поддерживает те же
equality- и $in-фильтры metadata, что core stores, необязательный порог cosine
similarity и параметризованные JSONB-значения. Размерность и конечность чисел
embedding проверяются до сетевого запроса.
Профили и конкуренция¶
Async PostgresProfileStore разделяет одинаковый user_id по закреплённому
хостом tenant и реализует атомарный compare-and-swap для optimistic locking в
ProfileManager:
from protoprompt import ProfileManager
from protoprompt.integrations import PostgresProfileStore
profiles = PostgresProfileStore(dsn, tenant="acme")
await profiles.setup() # только migration job
manager = ProfileManager(profiles)
Передайте уже открытый AsyncConnectionPool через pool=, если vector и
profile stores должны делить lifecycle. Внешний pool адаптеры не открывают и
не закрывают.
Event loop в Windows¶
В Windows async-реализации psycopg нужен selector event loop, а не стандартный
proactor loop. Передайте selector loop_factory в asyncio.Runner:
import asyncio
import selectors
with asyncio.Runner(
loop_factory=lambda: asyncio.SelectorEventLoop(selectors.SelectSelector())
) as runner:
runner.run(main())
Integration suite выбирает этот loop только в Windows.
Локальный integration test¶
docker compose -f docker-compose.postgres.yml up -d --wait
export PROTOPROMPT_POSTGRES_DSN="postgresql://protoprompt:protoprompt@localhost:55432/protoprompt_test"
pytest tests/integration/test_postgres_integration.py -v
pytest tests/integration/test_postgres_memory_ledger.py \
tests/integration/test_postgres_recovery_concurrency.py -v
docker compose -f docker-compose.postgres.yml down
Compose volume сохраняется после перезапуска контейнера. Используйте down -v
только если действительно хотите удалить локальные тестовые данные. Перед
изменением production-схемы сделайте backup: startup приложения не выполняет
автоматический rollback или разрушительный downgrade.