Engineering··2 min read

How We Built Blazing Fast Candidate Search with pgvector

A deep dive into how we use vector embeddings and pgvector to power semantic search that finds candidates based on meaning, not just keywords.

E

Engineering Team

Engineering

How We Built Blazing Fast Candidate Search with pgvector

One of the most requested features from early users was better search. Traditional keyword search just doesn't cut it when you're trying to find the right candidate.

The Problem with Keyword Search

Imagine you're looking for "a strong communicator who can work with stakeholders". Traditional search would look for those exact words. But what about candidates who wrote:

  • "Led cross-functional meetings with product and engineering"
  • "Presented quarterly updates to the executive team"
  • "Collaborated with clients to gather requirements"

These are all strong communication skills - but keyword search would miss them.

Enter Semantic Search

Semantic search understands meaning, not just words. It finds candidates whose experience conceptually matches what you're looking for.

Here's how we built it.

The Architecture

Query → Embedding → pgvector → Ranked Results

1. Generating Embeddings

When a candidate applies, we generate a 1536-dimension embedding vector using OpenAI's text-embedding-3-small model. This captures the semantic meaning of their entire profile.

async def generate_embedding(text: str) -> List[float]:
    response = await openai_client.embeddings.create(
        model="text-embedding-3-small",
        input=text
    )
    return response.data[0].embedding

2. Storing in PostgreSQL

We use the pgvector extension to store embeddings directly in PostgreSQL:

ALTER TABLE candidates ADD COLUMN embedding vector(1536);
CREATE INDEX ON candidates USING hnsw (embedding vector_cosine_ops);

The HNSW (Hierarchical Navigable Small World) index enables sub-second queries even with millions of vectors.

3. Querying

When you search, we:

  1. Convert your query to an embedding
  2. Find similar candidates using cosine similarity
  3. Return results ranked by relevance
SELECT *, 1 - (embedding <=> $1) as similarity
FROM candidates
WHERE job_id = $2
ORDER BY embedding <=> $1
LIMIT 20;

Performance Results

  • Query time: <100ms for 10,000+ candidates
  • Accuracy: Dramatically improved match quality
  • Storage: ~6KB per candidate (1536 floats × 4 bytes)

What's Next

We're working on:

  • Hybrid search - Combining vector and keyword search
  • Query understanding - Better parsing of complex requirements
  • Batch processing - Faster embedding generation for large imports

Interested in the technical details? Check out our GitHub or reach out at engineering@slctapp.com

Ready to hire smarter?

Create your first job posting in under 5 minutes. No credit card required.

Get Started Free

© 2026 SLCT. All rights reserved.