Skip to content
All notes

October 20, 2026 · 2 min read

A personalized job feed with two-tower embeddings on Bedrock and pgvector

How Instant ranks jobs for each worker and applicants for each company: embeddings and scores are computed by SQS-driven workers on AWS Bedrock, so reading the feed is a single PostgreSQL query.

Event-driven
AWS SQS
AWS Bedrock
pgvector

Instant’s worker app opens on a feed of jobs ranked for that worker, and companies see their applicants ranked by fit. Both are powered by the inference worker, which sits behind SQS so that no model call is ever on a request path.

Write path: the api publishes embedding and scoring jobs to SQS, the inference worker calls Bedrock and writes to PostgreSQL. Read path: the api serves the feed and ranked applicants with a single PostgreSQL query.
The write path is event-driven; the read path is one query. Click to open at full resolution.

Two towers, one vector space

A two-tower setup embeds the two sides of the marketplace independently: a worker tower built from the worker’s profile and a job tower built from the job posting. Both go through Amazon Titan Embeddings on Bedrock, so workers and jobs land in the same vector space and “relevant to this worker” becomes a similarity search.

Write path: asynchronous

  1. When a vector needs (re)computing, or a CV needs parsing and scoring, the API publishes a message to SQS (instant-embedding or instant-scoring) instead of calling a model.
  2. The Go inference worker long-polls both queues.
  3. It calls Bedrock: Titan Embeddings for vectors, gpt-oss for CV parsing and match scoring. Hosts authenticate with their EC2 instance role, so there are no model credentials to manage.
  4. Results go to PostgreSQL: vectors with pgvector, structured CV extractions, and each applicant’s matchScore.

Every queue has a dead-letter queue (redrive after 4 receives, 14-day retention) and a 120-second visibility timeout, so a message that keeps failing is parked for inspection and replay instead of blocking the queue or being lost.

Read path: one query

Serving the feed (step 5) never touches Bedrock. The API ranks job vectors by similarity to the worker’s vector with pgvector, alongside the PostGIS location search the platform already uses. Applicants are simply ordered by their precomputed matchScore.

Why this shape

  • Model latency and cost stay off the request path. Models run when data changes, not on every page view.
  • Failure isolation. If Bedrock is slow or throttled, the queues absorb it and the feed keeps serving the last computed vectors.
  • One datastore. Embeddings live next to the relational and geospatial data, with no separate vector store to run or keep in sync.
Instant Empleos