Перейти к содержанию

Embeddings — Векторный поиск по схожести

Уровень: L2 (details) | ← Назад

Обзор

Система векторного поиска позволяет находить похожие вещи и образы за <5ms. ML-модель на бэкенде генерирует embedding-вектор (1024 dim) для каждой вещи. Фронтенд хранит вектора в IndexedDB, вычисляет cosine similarity в Web Worker и отображает результаты в UI.

Pipeline: от ML до UI

graph LR
    subgraph Backend
        ML[ML Service<br/>ResNet50] --> API[Backend API<br/>EmbeddingDto]
    end

    subgraph "Frontend: Storage"
        API --> Sync[Initial Sync<br/>+ Refresh Sync]
        Sync --> IDB[(IndexedDB<br/>embeddings table)]
    end

    subgraph "Frontend: Compute"
        IDB --> SvcLoad[similarityService<br/>load candidates]
        SvcLoad --> Worker[Web Worker<br/>search.worker.ts]
        Worker --> Results[TopK Results<br/>id + score]
    end

    subgraph "Frontend: UI"
        Results --> Hook[useEmbeddingSearch<br/>useSimilarItems/Outfits]
        Hook --> Rail[SimilarItemsRail]
        Hook --> Grid[SimilarOutfitsGrid]
    end

Диаграмма

Sequence Diagram: Поиск похожих вещей

sequenceDiagram
    participant Page as WardrobeItemPage
    participant Hook as useSimilarItems
    participant Svc as similarityService
    participant Repo as embeddingRepository
    participant IDB as IndexedDB
    participant W as Web Worker

    Page->>Hook: useSimilarItems(itemId, 8)
    Hook->>Svc: findSimilarItems(itemId, 8)

    Svc->>Repo: get('item', itemId)
    Repo->>IDB: embeddings.get(['item', id])
    IDB-->>Repo: LocalEmbedding (query vector)
    Repo-->>Svc: query vector (Float32Array)

    alt Cache stale (first search or after invalidation)
        Svc->>Repo: getAllByType('item')
        Repo->>IDB: embeddings.where('entity_type').equals('item')
        IDB-->>Repo: All item embeddings
        Repo-->>Svc: candidates[]
        Svc->>W: postMessage({type: 'SEARCH', query, candidates, topK: 8})
    else Cache fresh
        Svc->>W: postMessage({type: 'SEARCH', query, candidates: [], topK: 8})
        Note over W: Uses cached candidates
    end

    W->>W: cosineSimilarity for each candidate
    W->>W: sort by score, take topK
    W-->>Svc: {results: [{id, score}...], timeMs: 3.2}

    Svc-->>Hook: results
    Hook-->>Page: {results, isLoading: false}

    Page->>Page: useLocalItemsByIds(results.map(r => r.id))
    Page->>Page: <SimilarItemsRail items={...} />

Диаграмма

Архитектура: 6 слоёв

Слой Модуль Ответственность
1. Storage embeddingRepository CRUD эмбеддингов в IndexedDB
2. Math vectorMath cosine similarity, mean vector, topK
3. Worker search.worker.ts Вычисления в фоновом потоке
4. Service similarityService Оркестрация: кеш, worker lifecycle, timeout
5. Hooks useEmbeddingSearch React-обёртки: state, loading, error
6. UI SimilarItemsRail, SimilarOutfitsGrid Визуализация результатов

Outfit Embeddings (производные)

Образы не имеют собственных ML-эмбеддингов. Вместо этого:

outfit_embedding = mean(item_embeddings[])

Вычисляется на фронтенде после sync: 1. outfitEmbeddingService.computeAndStore(outfitId) — для одного образа 2. outfitEmbeddingService.recomputeAll() — для всех образов (после initial sync)

Производительность

Операция Время Где выполняется
Load query embedding <1ms Main thread (IndexedDB get)
Load all candidates ~5ms Main thread (IndexedDB toArray)
Cosine similarity × N <3ms Web Worker (не блокирует UI)
Total search <5ms
Worker timeout 5000ms Fallback при проблемах

Cache Invalidation

similarityService.invalidateCache('item')  — после sync/update/delete
similarityService.invalidateCache('outfit') — после outfit changes

При инвалидации следующий поиск перезагрузит все кандидаты из IndexedDB и отправит их в Worker.

Навигация