Данные
На площадке два хранилища: Postgres (data.postgres) и S3-бакет (data.bucket). Диск
процесса временный: он стирается при перезапуске, сне и новом релизе, а у нескольких
экземпляров он у каждого свой. Всё, что должно пережить перезапуск, храните в базе или
бакете.
Подключите хранилища
Заголовок раздела «Подключите хранилища»data: postgres: { extensions: [vector] } bucket: {}| Хранилище | Что получает процесс |
|---|---|
| Postgres | строку подключения в DATABASE_URL |
| Бакет | AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_ENDPOINT_URL_S3, AWS_REGION и имя бакета в LOOMA_BUCKET |
Расширения Postgres выбираются из списка: vector, pg_trgm, uuid-ossp, citext,
hstore, pgcrypto, btree_gin, btree_gist, unaccent, fuzzystrmatch.
Таблицы и миграции создаёт сам сервис: при старте веба или отдельной командой перед
ним, например cmd: "python manage.py migrate && gunicorn …".
Чем заменить то, чего нет
Заголовок раздела «Чем заменить то, чего нет»| Было | Замена |
|---|---|
| SQLite, MySQL, MariaDB | Postgres: SQLite, MySQL |
| MongoDB | Postgres с jsonb: MongoDB |
| Redis | память процесса или таблица в Postgres: Redis |
| Очередь задач, брокеры | таблица задач в Postgres и процесс worker: Брокеры |
| Elasticsearch, Meilisearch, Qdrant, Chroma | поиск и pgvector в Postgres: Поиск |
| Файлы на диске | бакет: Файлы |
SQLite → Postgres
Заголовок раздела «SQLite → Postgres»- Добавьте в манифест
data: { postgres: {} }. - Берите строку подключения из
DATABASE_URL(форматpostgresql://…). - Драйвер: для Python
psycopg[binary]илиpsycopg2-binary(асинхронныйasyncpg), для Node.jspg. - Проверьте SQL на особенности SQLite:
AUTOINCREMENT→GENERATED ... AS IDENTITYилиSERIAL,INSERT OR REPLACE→INSERT ... ON CONFLICT ... DO UPDATE,datetime('now')→now(), булевы0/1→true/false. В PostgresLIKEразличает регистр, без учёта регистра нуженILIKE. - Схема: создавайте таблицы при старте (
CREATE TABLE IF NOT EXISTS) или миграциями перед запуском веба.
По фреймворкам:
| Что | Правка |
|---|---|
| SQLAlchemy | create_engine(os.environ["DATABASE_URL"]); схему postgres:// замените на postgresql+psycopg:// |
| Django | dj-database-url: DATABASES = {"default": dj_database_url.config(default="sqlite:///db.sqlite3")} |
| Peewee | PostgresqlDatabase через playhouse.db_url.connect(os.environ["DATABASE_URL"]) |
| Prisma | provider = "postgresql", url = env("DATABASE_URL"), prisma migrate deploy перед запуском |
| Knex, Sequelize, TypeORM, Drizzle | клиент pg, connectionString: process.env.DATABASE_URL |
Существующие данные переносятся отдельно. Снаружи к базе площадки не подключиться, поэтому данные загружает сам сервис: одноразовым шагом при старте (с отметкой в базе, чтобы шаг не повторялся) или через свой API.
MySQL/MariaDB → Postgres
Заголовок раздела «MySQL/MariaDB → Postgres»Шаги те же, что для SQLite. Отличия диалекта: кавычки идентификаторов ` → ",
AUTO_INCREMENT → GENERATED ... AS IDENTITY, ON DUPLICATE KEY UPDATE →
ON CONFLICT ... DO UPDATE, LIMIT a, b → LIMIT b OFFSET a, IFNULL → COALESCE,
TINYINT(1) → boolean. ORM обычно справляется сменой драйвера и строки
подключения.
MongoDB → Postgres
Заголовок раздела «MongoDB → Postgres»Переделка большая. MongoDB на площадке нет, а внешний (Atlas) работает на порту
27017, который наружу закрыт. Обычный путь: коллекция становится таблицей
(id uuid primary key, data jsonb, …), частые поля запросов выносятся в отдельные
колонки с индексами, остальное лежит в jsonb (операторы ->>, @>, индекс GIN).
Redis → Postgres или память процесса
Заголовок раздела «Redis → Postgres или память процесса»Redis на площадке нет, а внешний работает на порту 6379, который закрыт.
| Для чего был Redis | Замена |
|---|---|
| Кэш | память процесса (functools.lru_cache, cachetools, lru-cache) или таблица в Postgres с полем expires_at |
| Сессии | подписанные cookie или таблица в Postgres |
| Очередь задач (Celery, RQ, Bull) | таблица задач в Postgres и процесс worker, который берёт задачи через SELECT … FOR UPDATE SKIP LOCKED; готовые решения: для Python procrastinate, для Node.js pg-boss и graphile-worker |
| Pub/sub | LISTEN/NOTIFY Postgres |
| Счётчики, лимиты | UPDATE … SET n = n + 1 … RETURNING n в Postgres |
Celery с брокером Redis проще всего заменить на procrastinate (Python), сохранив сами
функции задач. Расписание Celery beat переходит в процесс по расписанию (schedule в
манифесте).
Брокеры (RabbitMQ, Kafka, NATS)
Заголовок раздела «Брокеры (RabbitMQ, Kafka, NATS)»На площадке их нет, порты закрыты. Очередь делается таблицей в Postgres и процессом
worker, как в разделе Redis. Если брокер был шиной между несколькими
частями проекта, это повод собрать их в один сервис с несколькими процессами.
Поисковые движки и векторные базы
Заголовок раздела «Поисковые движки и векторные базы»| Было | Замена |
|---|---|
| Elasticsearch, OpenSearch, Meilisearch, Typesense | полнотекстовый поиск Postgres: tsvector, to_tsquery, индекс GIN; для нечёткого поиска pg_trgm |
| Qdrant, Chroma, Pinecone, Milvus, Weaviate | pgvector через data.postgres: { extensions: [vector] }; векторы считает модель эмбеддингов сети (looma models, тип embedding) |
Файлы на диске → бакет
Заголовок раздела «Файлы на диске → бакет»- Добавьте в манифест
data: { bucket: {} }. - Создайте клиент S3 с адресацией по пути (path-style). Доступы и адрес клиент берёт из окружения сам.
Python (boto3):
import os, boto3from botocore.config import Config
s3 = boto3.client("s3", endpoint_url=os.environ.get("AWS_ENDPOINT_URL_S3"), config=Config(s3={"addressing_style": "path"}))BUCKET = os.environ["LOOMA_BUCKET"]
s3.put_object(Bucket=BUCKET, Key=f"uploads/{name}", Body=data, ContentType=mime)body = s3.get_object(Bucket=BUCKET, Key=f"uploads/{name}")["Body"].read()url = s3.generate_presigned_url("get_object", Params={"Bucket": BUCKET, "Key": key}, ExpiresIn=3600)Node.js (@aws-sdk/client-s3):
import { S3Client, PutObjectCommand, GetObjectCommand } from "@aws-sdk/client-s3";
const s3 = new S3Client({ endpoint: process.env.AWS_ENDPOINT_URL_S3, region: process.env.AWS_REGION, forcePathStyle: true });await s3.send(new PutObjectCommand({ Bucket: process.env.LOOMA_BUCKET, Key: key, Body: data }));Что куда:
- загрузки пользователей, сгенерированные файлы и результаты храните в бакете;
- отдавайте их через сервис (прочитать из бакета и вернуть ответом) или временной ссылкой (presigned URL);
- временные файлы на время запроса можно класть в
/tmp; UPLOAD_FOLDERиMEDIA_ROOTDjango переведите на хранилище S3:django-storagesсAWS_S3_ENDPOINT_URL,AWS_S3_ADDRESSING_STYLE = "path"иAWS_STORAGE_BUCKET_NAMEизLOOMA_BUCKET.
Чтобы проект работал и вне площадки, пишите в локальную папку, как раньше, если
LOOMA_BUCKET не задан.
Где видно
Заголовок раздела «Где видно»Вкладка «Данные» сервиса в кабинете автора показывает базу и бакет. Пароли и ключи там не показываются: процессы получают их переменными окружения.