← Back to list

One Entity, One Row: When Multiple pgvector Columns Beat Row-per-Chunk Storage

When developers begin storing embeddings in PostgreSQL with pgvector, one design often appears almost automatically:

Maniallada · 2026-05-19 20:04 · 0 claps · 5.9 min read
#pgvector #embedding #generative-ai-tools #postgresql #design-thinking
Open on Medium ↗
Wiki topics: RAG · RAG & Retrieval AI · AI · General GEN · Genomics & Sequencing PRD · Product Design

One Entity, One Row: When Multiple pgvector Columns Beat Row-per-Chunk Storage

When developers begin storing embeddings in PostgreSQL with pgvector, one design often appears almost automatically:

One embedding = one row.

That pattern makes perfect sense for classic document RAG, where a long article, PDF, or transcript is split into many chunks and each chunk must be independently searchable.

But what if the data is not a long document?

What if each record represents a structured entity with a small, fixed number of meaningful text fields — for example:

  • title
  • summary
  • description

or:

  • short_text
  • detailed_text
  • metadata_explanation

Should those embeddings always become separate rows?

Not necessarily.

In many entity-centric retrieval systems, it can be cleaner — and sometimes more storage-efficient — to store one entity per row with multiple vector columns, rather than duplicating the same entity across multiple rows.

The Two Schema Designs

Assume each entity has three fields that we want to embed:

  1. Title
  2. Summary
  3. Description

Option 1: Row-per-field / row-per-chunk model

Each embedding becomes a separate row.

Row-per-field table

Row-per-field table

Visual model

Entity 101
   ├── Row 1 → title embedding
   ├── Row 2 → summary embedding
   └── Row 3 → description embedding

Option 2: One row per entity with multiple vector columns

Each entity remains a single row.

entity_idtitle_embeddingsummary_embeddingdescription_embedding101vector_1vector_2vector_3

Visual model

Entity 101
   └── Single Row
         ├── title_embedding
         ├── summary_embedding
         └── description_embedding

The Core Question: Are You Retrieving Chunks or Entities?

This is the key design question.

If your goal is:

“Find the most relevant individual text passage,”

then the row-per-chunk model is often the right choice.

But if your goal is:

“Find the most relevant entity by comparing several known semantic fields,”

then the one-row, multi-vector-column model can be a better fit.

That distinction changes the schema.

Why Multiple Vector Columns Can Be Better

1. The database shape matches the business object

If the actual object is an entity, then storing it as one row keeps the data model natural.

In the multi-row design, the same entity is split across multiple records:

101, title
101, summary
101, description

That means retrieval first returns field-level rows, and only afterward do you reconstruct the entity.

In the multi-column design, retrieval remains entity-first. The row itself is already the object you care about.

2. It reduces repeated metadata and row overhead

This is an overlooked cost.

Suppose every entity carries metadata such as:

  • entity_id
  • source_id
  • category
  • created_at
  • updated_at
  • status flags
  • foreign keys

In a row-per-field design, that metadata is repeated again and again for every embedding row.

Row-per-field storage pattern

Row 1: entity_id + metadata + title_embedding
Row 2: entity_id + metadata + summary_embedding
Row 3: entity_id + metadata + description_embedding

Multi-column storage pattern

One row: entity_id + metadata + title_embedding + summary_embedding + description_embedding

The vector data itself is still present in both designs. But the row-per-field model also pays for:

  • repeated IDs
  • repeated chunk-type labels
  • repeated metadata values
  • additional row headers
  • more heap tuples
  • potentially larger indexes on repeated metadata columns

The more metadata you duplicate, the more noticeable this becomes.

3. Storage can grow faster with separate rows

Let’s use a simplified example.

Imagine:

  • 1 million entities
  • 3 embeddings per entity
  • each embedding is 1,536 dimensions

pgvector states that a standard vector uses:

4 × dimensions + 8 bytes

So a 1,536-dimensional vector is roughly:

4 × 1536 + 8 = 6152 bytes ≈ 6 KB

That means the raw embedding payload is roughly:

1 million entities × 3 vectors × ~6 KB ≈ 18 GB

That 18 GB exists in either design because the same three vectors are being stored.

But now consider the extra relational overhead.

Row-per-field design

3 million rows

Multi-column design

1 million rows

The row-per-field model stores the same vectors, but it also requires roughly three times as many table rows. That can increase:

  • tuple/header overhead
  • repeated keys
  • repeated field-type values
  • duplicated non-vector attributes
  • index entries for columns like entity_id, field_type, category, or timestamps

The exact storage difference depends on the schema, data types, indexes, and PostgreSQL storage behavior. But structurally, the row-per-field approach often carries more relational overhead when the entity fields are fixed and known in advance.

4. It simplifies entity-level retrieval

Consider a search query that should compare against several fields.

With multiple vector columns, you can compute entity-level relevance directly:

SELECT
    entity_id,
    0.5 * (1 - (title_embedding <=> :query_vector)) +
    0.3 * (1 - (summary_embedding <=> :query_vector)) +
    0.2 * (1 - (description_embedding <=> :query_vector)) AS score
FROM entities
ORDER BY score DESC
LIMIT 10;

This expresses the business logic clearly:

  • title matters most
  • summary matters next
  • description adds supporting context

With row-per-field storage, you often need to:

  1. search rows
  2. group by entity
  3. aggregate scores
  4. deduplicate entities
  5. handle cases where multiple fields from the same entity dominate the result set

That is doable — but more complex.

5. It makes field-aware weighting natural

Not every field should influence ranking equally.

A query match in a title might matter more than a weak match in a long description.

With separate vector columns, weighting is explicit:

Final score =
0.5 × title similarity
+ 0.3 × summary similarity
+ 0.2 × description similarity

Diagram: entity-level weighted scoring

Query Vector
                      │
       ┌──────────────┼──────────────┐
       │              │              │
 title vector    summary vector   description vector
   weight 0.5       weight 0.3        weight 0.2
       │              │              │
       └──────────────┴──────────────┘
                      │
              Final Entity Score

This makes the retrieval strategy easier to understand, test, and evolve.

But This Is Not Always Better

The one-row, multiple-vector-column design is not a universal replacement for chunk tables.

It works best when:

  • the number of vectorized fields is small
  • the fields are known ahead of time
  • each field has a distinct semantic role
  • retrieval should return whole entities
  • field-specific scoring matters

It is a poor fit when:

  • the number of chunks is variable
  • an entity may have 5 chunks today and 500 tomorrow
  • you are indexing long documents or transcripts
  • the best result should be a specific paragraph, not the full entity
  • the system needs flexible chunk creation and deletion

When Row-per-Chunk Is Still the Right Model

For document retrieval, row-per-chunk remains the natural design.

Example

This is ideal when:

  • documents are long
  • chunk counts vary widely
  • passage-level recall matters
  • you want the best matching text fragment

The design problem is not:

“Which schema is universally better?”

It is:

“Which schema matches the retrieval unit?”

pgvector-Specific Tradeoff: More Columns Can Mean More Indexes

There is a real cost to the multi-column design.

If you want approximate nearest-neighbor search on several vector columns, you may need separate indexes such as:

CREATE INDEX ON entities USING hnsw (title_embedding vector_cosine_ops);
CREATE INDEX ON entities USING hnsw (summary_embedding vector_cosine_ops);
CREATE INDEX ON entities USING hnsw (description_embedding vector_cosine_ops);

That means:

  • more indexes to maintain
  • more index storage
  • more write cost during inserts and updates

So the multi-column model should not be chosen blindly. It is attractive when the schema is stable and the semantic fields are genuinely important enough to search independently or combine in scoring.

PostgreSQL Storage Note: Wide Rows Are Not Free Either

A row containing several vectors can become large. PostgreSQL uses TOAST storage to handle oversized values transparently when a row cannot fit comfortably on standard pages.

That means the multi-column model also has a cost:

  • wider tuples
  • possible out-of-line storage behavior
  • tradeoffs in locality and row access patterns

So the goal is not to pretend that one row with many vectors is “free.”

The point is more specific:

If the number of vectorized fields is small and fixed, keeping them on one entity row can avoid the repeated relational overhead of exploding the same entity into several rows.

Final Takeaway

The common instinct is:

“I have several embeddings, so I need several rows.”

But that is not always the best design.

If your system deals with structured entities that have a small, fixed set of semantically meaningful text fields, then storing those embeddings as multiple pgvector columns in one row can be a better fit than storing one row per field.

It can provide:

  • a cleaner entity-centered schema
  • less repeated metadata
  • fewer total rows
  • simpler entity-level ranking
  • natural weighted scoring across fields
  • less deduplication after retrieval

At the same time, row-per-chunk storage remains the right choice for long documents, variable chunks, and passage-level retrieval.

The better schema is the one that matches what you are actually trying to retrieve:

Chunks? Use rows. Structured entities with fixed semantic fields? Consider columns.


메타데이터
post_id
0887d8b0cd5e
slug
one-entity-one-row-when-multiple-pgvector-columns-beat-row-per-chunk-storage-0887d8b0cd5e
url
https://medium.com/@maniallada28/one-entity-one-row-when-multiple-pgvector-columns-beat-row-per-chunk-storage-0887d8b0cd5e
canonical_url
https://medium.com/@maniallada28/one-entity-one-row-when-multiple-pgvector-columns-beat-row-per-chunk-storage-0887d8b0cd5e
author_url
https://medium.com/@maniallada28
status
ok
fetched_at
2026-06-09 15:37:30