«ФудТех» использовал MongoDB, PostgreSQL, Redis и Elasticsearch одновременно. Каждая БД требовала мониторинга, бэкапов, обновлений и знания специфического query language. Мы заменили всё на один PostgreSQL 16.
MongoDB → PostgreSQL (JSONB для полу-структурированных данных):
-- Заказы: были в MongoDB, теперь в PostgreSQL с JSONB
CREATE TABLE orders (
id BIGSERIAL PRIMARY KEY,
customer_id BIGINT REFERENCES users(id),
restaurant_id BIGINT REFERENCES restaurants(id),
status VARCHAR(20) DEFAULT 'new',
items JSONB NOT NULL, -- [{"name": "Пицца", "qty": 2, "price": 590}]
delivery_address JSONB,
total_amount DECIMAL(10,2),
created_at TIMESTAMPTZ DEFAULT now(),
updated_at TIMESTAMPTZ DEFAULT now()
);
-- Индекс по JSONB для быстрого поиска
CREATE INDEX idx_orders_items ON orders USING GIN (items);
-- Запрос: найти заказы с пиццей
SELECT * FROM orders
WHERE items @> '[{"name": "Пицца"}]';
-- Время: 2 мс на 500K заказов
Elasticsearch → PostgreSQL (полнотекстовый поиск):
-- Поиск ресторанов: был в Elasticsearch, теперь tsvector
ALTER TABLE restaurants
ADD COLUMN search_vector tsvector;
CREATE INDEX idx_restaurants_search
ON restaurants USING GIN (search_vector);
-- Триггер для автообновления
CREATE FUNCTION update_search_vector() RETURNS trigger AS $$
BEGIN
NEW.search_vector :=
setweight(to_tsvector('russian', COALESCE(NEW.name, '')), 'A') ||
setweight(to_tsvector('russian', COALESCE(NEW.cuisine, '')), 'B') ||
setweight(to_tsvector('russian', COALESCE(NEW.description, '')), 'C');
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER restaurants_search_update
BEFORE INSERT OR UPDATE ON restaurants
FOR EACH ROW EXECUTE FUNCTION update_search_vector();
-- Поиск: "итальянская пицца центр"
SELECT name, ts_rank(search_vector, query) AS rank
FROM restaurants, plainto_tsquery('russian', 'итальянская пицца') query
WHERE search_vector @@ query
ORDER BY rank DESC
LIMIT 20;
-- Время: 4 мс на 5000 ресторанов
Kafka → PostgreSQL LISTEN/NOTIFY (для асинхронных задач):
-- Вместо Kafka для уведомлений: LISTEN/NOTIFY
-- Producer (в Django view):
from django.db import connection
def create_order(request):
order = Order.objects.create(**validated_data)
with connection.cursor() as cursor:
cursor.execute(
"SELECT pg_notify('new_order', %s)",
[json.dumps({'order_id': order.id})]
)
return Response({'id': order.id}, status=201)
# Consumer (Celery worker слушает NOTIFY):
import select
import psycopg2
def listen_orders():
conn = psycopg2.connect(DSN)
conn.set_isolation_level(
psycopg2.extensions.ISOLATION_LEVEL_AUTOCOMMIT
)
cur = conn.cursor()
cur.execute("LISTEN new_order;")
while True:
if select.select([conn], [], [], 5) != ([], [], []):
conn.poll()
while conn.notifies:
notify = conn.notifies.pop(0)
process_new_order(json.loads(notify.payload))
Redis остался только для кэширования сессий — 50 MB RAM вместо отдельного кластера.
Оставить комментарий