Backend / AI matching

Blindlove — AI Matchmaking Engine

A hybrid matching system for an anonymous dating app: weighted preference filtering narrows the pool, then Redis vector search ranks candidates by semantic profile similarity.

Role: Lead Backend Developer and AI Architect

Overview

Blindlove is an anonymous dating platform. I designed and built its matchmaking backend: user bios, interests and preferences are turned into vector embeddings with Hugging Face sentence-transformers and indexed in Redis Stack. A hybrid algorithm combines weighted-preference filtering, bitmap interest scoring, cosine similarity and K-nearest-neighbour vector search, and the best matches are cached in Redis for fast retrieval.

Tech Stack

backend

NestJSTypeScriptNode.js cluster modeJWT + Passport

ai

Hugging Face Transformers (sentence-transformers)Redis Vector Search (RediSearch)Python

data

MongoDBRedis Stack

realtime

Socket.io (Redis adapter)Server-Sent Events

frontend

React.js

devops

AWS

Key Numbers

40%

Match quality improvement (Hugging Face embeddings)

65%

Match relevance improvement (vectors vs traditional)

The Problem

In an anonymous dating app there are no photos or visible profiles to browse, so match quality depends almost entirely on how well the system understands what a person wrote and prefers. Filters alone match on declared fields and miss semantic similarity between free-text bios and interests, and the matching has to stay fast for many concurrent users.

  • Go beyond exact-field filtering and match on the meaning of bios and interests
  • Keep matching fast and scalable for many concurrent users
  • Keep embeddings fresh when a user changes their profile
  • Run real-time chat and notifications across multiple Node.js processes without duplicate connections or duplicate jobs
  • Support multiple languages across every application layer

The Solution

  1. 1Pool first: narrow candidates with general filtration using weighted preferences
  2. 2Score with bitmap interests and cosine similarity
  3. 3Re-rank semantically with Redis Vector Search over Hugging Face embeddings
  4. 4Store top matches per user in Redis sorted lists with their similarity scores
  5. 5Process match requests through a Redis queue so matching never blocks the API

Hybrid matching algorithm

Combines weighted preferences, cosine similarity, bitmap interest scoring and vector search, plus semantic similarity scoring that blends traditional metrics with embedding distances.

Profile tokenization and embeddings

A pipeline using Hugging Face sentence-transformers converts user bios, interests and preferences into searchable embeddings.

Redis vector index

A vector indexing schema in Redis Stack (RediSearch) supports fast K-nearest-neighbour queries, exposed through a vector search API for semantic profile matching and similarity queries.

Automated reindexing

User embeddings are regenerated and reindexed automatically when a profile changes.

Fast match retrieval

Top matches are stored in Redis sorted lists with vector similarity scores; match requests are managed through a Redis list-based queue.

Real-time layer

Socket.io chat with a single socket connection per user, plus Server-Sent Events for general notifications with a custom heartbeat and connection pooling.

Multi-language support

Custom interceptor, decorator and localization service, i18n static translation files, and multi-language push notifications with notification groups in a single API service.

Chat media optimization

A service that processes and optimizes images and video for chat delivery.

Admin panel APIs

User, content and report management APIs, using aggregated queries and pagination for data virtualization.

Architecture

Event-driven NestJS backend with Redis Stack as the matching and real-time backbone

Matching flow
  1. 1Match request queued in Redis
  2. 2Pool candidates with weighted preference filters
  3. 3Score with bitmap interests and cosine similarity
  4. 4Run vector KNN search over profile embeddings
  5. 5Combine scores into a hybrid similarity result
  6. 6Store top matches in a Redis sorted list
Profile indexing flow
  1. 1User creates or edits profile
  2. 2Bio, interests and preferences tokenized
  3. 3Embedding generated with sentence-transformers
  4. 4Vector index updated in Redis

Components

API

NestJS, TypeScript

Modular services with a shared utils module, event listeners for module decoupling, and a custom JWT guard (Passport) with token blacklisting.

Matching

Redis Stack, RediSearch

Weighted filtering and bitmap/cosine scoring, then vector KNN search; top matches cached in sorted lists; requests handled via a Redis queue.

Embeddings

Hugging Face sentence-transformers, Python

Generates embeddings from bios, interests and preferences; reindexes on profile updates.

Real-time

Socket.io, SSE, Redis adapter

One socket per user; Redis adapter keeps clustered processes in sync; SSE with heartbeat for notifications.

Persistence

MongoDB

Aggregation pipelines with indexing and subdocuments to minimize lookups; patch writes for partial updates.

Scaling

Node.js cluster mode, Redis

Horizontal scaling across CPU cores; cron jobs guarded by Redis distributed locks.

Key Decisions

Hybrid matching instead of pure vector search

Why: Weighted filtering keeps hard preferences respected and shrinks the candidate pool, while embeddings add semantic understanding of free-text profiles.

Redis Stack for vectors, queues, caching and real-time

Why: One in-memory system covers KNN search, match queues, sorted top-match lists, the Socket.io adapter and distributed locks.

Embedding model chosen for accuracy versus inference speed

Why: The model was selected to balance match quality against latency for production use.

Event-driven module decoupling

Why: Event listeners separate services and modules, which keeps the codebase maintainable as features grow.

SSE for general notifications, Socket.io for chat

Why: One-directional notifications do not need a bidirectional socket; chat does.

Technical Challenges

Duplicate socket connections per user

Solution: Single socket connection per user management.

Socket events across a clustered Node.js deployment

Solution: Node.js cluster mode with the Socket.io Redis adapter for synchronization.

Cron jobs running more than once in a clustered deployment

Solution: Cron jobs handled on a single process with Redis distributed locks.

Expensive MongoDB reads and writes

Solution: Optimized aggregation pipelines with indexing and subdocuments, and patch writes for partial updates.

Outcomes

  • Hybrid matching combining weighted filters, cosine similarity and vector search
  • Scalable real-time infrastructure supporting chat, notifications and matchmaking
  • Integrating Hugging Face embeddings improved match quality by 40%
  • 65% improvement in match relevance using vector embeddings versus traditional methods

Engineering Practices

  • TypeScript interfaces and type aliases throughout the codebase
  • Custom Redis service extending the Redis client with vector operations
  • Redis transport for real-time features
  • Shared utils module for reuse across the application
  • Custom JWT guard with token blacklisting
  • Real-time fallback mechanisms for communication reliability