Прототип сервиса на Go для работы с векторными данными в PostgreSQL:
- хранение эмбеддингов в
pgvector; - фоновый импорт датасета (не блокирует API);
- фоновая кластеризация документов с
cluster_id IS NULL; - REST API по требованиям ТЗ;
- запуск через Docker Compose.
Выбрано расширение: pgvector.
Причины:
- нативный тип
vector(N); - SQL-операции расстояний/сходства;
- поддержка индексов HNSW / IVFFlat;
- простая интеграция с Go (
pgx+pgvector-go).
В проекте используется:
VECTOR(384)для эмбеддингов;- индекс
HNSWсvector_cosine_ops.
- схема БД + миграции (
goose); - таблицы
documentsиclusters; - фоновой импорт данных (батчами);
- фоновая кластеризация новых документов;
- обязательный REST API:
GET /clusters?limit=&offset=GET /clusters/{id}/documents?limit=&offset=GET /documents/{id}POST /documents/
- Docker Compose: Postgres (pgvector) + app + Prometheus + подготовка среза датасета;
- graceful shutdown сервера и воркеров.
internal/repository/*— доступ к БД;internal/service/document— бизнес-логика документов;internal/service/importer— импорт датасета;internal/service/cluster— кластеризация + worker;internal/transport/http/handler/*— HTTP-слой;internal/server— wiring, роутер, запуск/остановка воркеров.
Миграция: internal/db/migrations/000001_init.sql
id BIGSERIAL PRIMARY KEYalgorithm TEXTk INTcentroid VECTOR(384)created_at,updated_at
id BIGSERIAL PRIMARY KEYhn_id BIGINTtitle,url,by,textscore INTtime TIMESTAMPTZembedding VECTOR(384) NOT NULLcluster_id BIGINT NULL REFERENCES clusters(id)created_at,updated_at
idx_documents_cluster_idнаdocuments(cluster_id)idx_documents_embedding_hnswнаdocuments USING hnsw (embedding vector_cosine_ops)
- Docker + Docker Compose
goose(для миграций)
- Запуск контейнеров:
docker compose up --build- Применение миграций (отдельно, вручную):
make migrate-up- Просмотр логов:
docker compose logs -f appПримечание:
- сервис
datasetв compose формирует срез датасета200kвhn_200k.csv; appимпортирует этот файл в фоне батчами.
Базовый URL: http://localhost:8080
embedding должен содержать 384 float-значения.
EMB=$(python3 - <<'PY'
print("[" + ",".join(["0.01"]*384) + "]")
PY
)
curl -X POST http://localhost:8080/documents/ \
-H "Content-Type: application/json" \
-d "{
\"hn_id\": 123456789,
\"title\": \"Example\",
\"url\": \"https://example.com\",
\"by\": \"user1\",
\"score\": 10,
\"time\": \"2024-01-01T12:00:00Z\",
\"text\": \"sample text\",
\"embedding\": $EMB
}"curl "http://localhost:8080/documents/1"curl "http://localhost:8080/clusters?limit=20&offset=0"curl "http://localhost:8080/clusters/1/documents?limit=20&offset=0"Endpoint метрик:
http://localhost:8080/metrics- Prometheus UI:
http://localhost:9090
Собираемые метрики:
http_requests_totalhttp_request_duration_secondshttp_active_requestsrate_limit_exceeded_totalcluster_size_mincluster_size_maxcluster_size_avgpct_clustered
SQL-проверки из ТЗ:
-- Размеры кластеров
SELECT cluster_id, COUNT(*) AS size
FROM documents
GROUP BY cluster_id
ORDER BY size DESC;
-- Доля кластеризованных документов
SELECT
100.0 * COUNT(*) FILTER (WHERE cluster_id IS NOT NULL) / COUNT(*) AS pct_clustered
FROM documents;# Postgres
make db-up
make db-logs
make db-down
# Миграции
make migrate-up
make migrate-down
make migrate-status
# Тесты
go test ./...
# Линтер
golangci-lint run ./...- импорт-воркер запускается один раз при старте приложения;
- кластеризация в текущем коде упрощенная (прототипный вариант);
- миграции запускаются вручную, не автоматически.