Skip to content

Architecture Overview

GoVector is a lightweight, embeddable vector database written in pure Go, designed to provide high-performance vector similarity search capabilities for local AI applications, desktop applications, and edge computing scenarios. It provides a Qdrant-compatible REST API, uses HNSW graph indexing for approximate nearest neighbor search, and achieves persistent storage through BoltDB.

The core design philosophy of the system is:

  • Layered decoupling: Clear four-layer architecture with explicit and independent responsibilities for each layer
  • Interface abstraction: Transparent switching of index strategies through VectorIndex interface
  • Extensibility: Supports multiple distance metrics, filter conditions, and storage backends
  • Reliability: Transaction-based persistence using bbolt ensures data consistency

Project Structure

GoVector uses a modular directory structure, organizing code by functional layers:

graph TB
subgraph "Application Layer"
CMD[Command Line Entry
cmd/govector/main.go] API[HTTP API Layer
api/server.go] end subgraph "Business Logic Layer" CORE[Core Business Logic
core/collection.go] MODELS[Data Models
core/models.go] end subgraph "Index Layer" INDEX[Index Interface
core/index.go] HNSW[HNSW Index
core/hnsw_index.go] FLAT[Flat Index
core/flat_index.go] end subgraph "Storage Layer" STORAGE[Storage Engine
core/storage.go] QUANT[Quantizer
core/quantization.go] end CMD --> API API --> CORE CORE --> INDEX INDEX --> HNSW INDEX --> FLAT CORE --> STORAGE STORAGE --> QUANT

Core Components

Four-Layer Architecture Design

GoVector adopts a classic four-layer architecture pattern:

  1. API Layer: Provides RESTful HTTP interface, handles client requests
  2. Business Logic Layer: Encapsulates Collection abstraction, manages lifecycle of vector collections
  3. Index Layer: Implements VectorIndex interface, supports HNSW and Flat index strategies
  4. Storage Layer: bbolt-based persistent storage, supports vector quantization compression

Key Data Models

The system uses the following core data structures:

classDiagram
    class Collection {
        +string Name
        +int VectorLen
        +Distance Metric
        +VectorIndex index
        +Storage storage
        +Upsert(points) error
        +Search(query, filter, topK) []ScoredPoint
        +Delete(points, filter) int
        +Count() int

    }
    class VectorIndex {

        <>
        +Upsert(points) error
        +Search(query, filter, topK) []ScoredPoint
        +Delete(id) error
        +Count() int
        +GetIDsByFilter(filter) []string
        +DeleteByFilter(filter) []string

    }
    class HNSWIndex {

        +Graph graph
        +map~string, PointStruct~ points
        +Distance metric
        +HNSWParams params
        +Upsert(points) error
        +Search(query, filter, topK) []ScoredPoint
        +Delete(id) error
        +Count() int

    }
    class FlatIndex {

        +map~string, PointStruct~ points
        +Distance metric
        +Upsert(points) error
        +Search(query, filter, topK) []ScoredPoint
        +Delete(id) error
        +Count() int

    }
    class Storage {

        +bbolt.DB db
        +Quantizer quantizer
        +bool useQuant
        +EnsureCollection(name) error
        +UpsertPoints(name, points) error
        +LoadCollection(name) map[string]*PointStruct
        +DeletePoints(name, ids) error

    }
    class Server {

        +string addr
        +map~string, Collection~ collections
        +Storage store
        +http.Server httpServer
        +Start() error
        +Stop(ctx) error
        +AddCollection(col)

    }
    Collection ..|> VectorIndex : implements
    HNSWIndex ..|> VectorIndex : implements
    FlatIndex ..|> VectorIndex : implements
    Collection --> Storage : uses
    Server --> Collection : manages
    Server --> Storage : uses

Architecture Overview

Complete Data Flow

The complete data flow from client request to data persistence is as follows:

sequenceDiagram
participant Client as Client
participant API as API Layer
participant Collection as Business Logic Layer
participant Index as Index Layer
participant Storage as Storage Layer
Note over Client,Storage : Write Flow (Upsert)
Client->>API : PUT /collections/{name}/points
API->>Collection : handleUpsert()
Collection->>Collection : Validate vector dimension
Collection->>Storage : UpsertPoints()
Storage->>Storage : Serialize and store
Collection->>Index : Upsert()
Index->>Index : Update in-memory index
API-->>Client : Return operation result
Note over Client,Storage : Query Flow (Search)
Client->>API : POST /collections/{name}/points/search
API->>Collection : handleSearch()
Collection->>Index : Search()
Index->>Index : HNSW approximate search
Index->>Index : Post-filter (payload)
Index-->>Collection : Return TopK results
Collection-->>API : Return query results
API-->>Client : Return matching vectors

Architecture Design Principles

  1. Layered decoupling: Each layer has clear responsibility boundaries, communication through interfaces
  2. Interface abstraction: VectorIndex interface allows transparent switching of different index strategies
  3. Extensibility: Supports multiple distance metrics (Cosine, Euclidean, Dot product)
  4. Reliability: Transaction-based operations using bbolt ensure data consistency
  5. Performance optimization: HNSW index provides approximate but efficient search capability

Detailed Component Analysis

API Layer Analysis

The API layer is responsible for handling HTTP requests, providing Qdrant-compatible REST API:

flowchart TD
Start([HTTP request arrives]) --> ParsePath["Parse path parameters
/collections/{name}"] ParsePath --> CheckCollection{"Collection exists?"} CheckCollection --> |No| NotFound["Return 404 Not Found"] CheckCollection --> |Yes| ParseBody["Parse request body JSON"] ParseBody --> ValidateJSON{"JSON format valid?"} ValidateJSON --> |No| BadRequest["Return 400 Bad Request"] ValidateJSON --> |Yes| ProcessOp["Process specific operation"] ProcessOp --> Upsert["Upsert operation"] ProcessOp --> Search["Search operation"] ProcessOp --> Delete["Delete operation"] ProcessOp --> Create["Create Collection operation"] ProcessOp --> DeleteCol["Delete Collection operation"] Upsert --> CallCollection["Call Collection method"] Search --> CallCollection Delete --> CallCollection Create --> CreateCollection["Create new collection"] DeleteCol --> RemoveCollection["Remove collection"] CallCollection --> Success["Return 200 OK"] CreateCollection --> Success RemoveCollection --> Success NotFound --> End([Response end]) BadRequest --> End Success --> End

Business Logic Layer Analysis

The Collection class is the core of business logic, encapsulating all operations for vector collections:

classDiagram
    class Collection {
        +string Name
        +int VectorLen
        +Distance Metric
        +VectorIndex index
        +Storage storage
        +RWMutex mu
        +NewCollection(name, dim, metric, store, useHNSW)
        +NewCollectionWithParams(name, dim, metric, store, useHNSW, params)
        +Upsert(points) error
        +Search(query, filter, topK) []ScoredPoint
        +Delete(points, filter) int
        +Count() int

    }

    class UpsertProcess {

        +Validate vector dimension
        +Set version number
        +Persist to disk
        +Update in-memory index
        +Error rollback

    }
    class SearchProcess {

        +Validate query vector dimension
        +Read lock protection
        +Delegate to index layer

    }
    class DeleteProcess {

        +Determine delete target IDs
        +Delete disk data first
        +Then delete from in-memory index
        +Count successful deletions

    }
    Collection --> UpsertProcess : contains
    Collection --> SearchProcess : contains
    Collection --> DeleteProcess : contains

Index Layer Analysis

The index layer implements the VectorIndex interface, providing two different index strategies:

HNSW Index Implementation

HNSW (Hierarchical Navigable Small World) is an efficient graph-structured index:

flowchart TD
HNSWStart([HNSW Initialization]) --> ConfigDist["Configure distance function
Cosine/Euclid/Dot"] ConfigDist --> SetParams["Set HNSW parameters
M, EfConstruction, EfSearch"] SetParams --> GraphInit["Initialize hnsw.Graph"] GraphInit --> UpsertNodes["Batch add nodes"] UpsertNodes --> UpdateMap["Update metadata map"] SearchStart([Search start]) --> OverFetch["Over-fetch strategy
fetchK = topK * 10"] OverFetch --> HNSWSearch["HNSW graph search"] HNSWSearch --> PostFilter["Post-filter (payload)"] PostFilter --> CalcScore["Exact score calculation"] CalcScore --> ReturnResults["Return TopK results"] DeleteStart([Delete start]) --> DeleteFromGraph["Delete node from graph"] DeleteFromGraph --> DeleteFromMap["Delete metadata from map"] DeleteFromMap --> DeleteComplete([Delete complete])

Flat Index Implementation

Flat index provides exact but inefficient brute-force search:

Storage Layer Analysis

The storage layer provides persistence capability based on bbolt, supporting vector quantization compression:

erDiagram
COLLECTION {
string name PK
int vector_len
enum metric
bool use_hnsw
json hnsw_params
}
POINT {
string id PK
float32[] vector
json payload
uint64 version
}
META_BUCKET {
string name PK
json collection_meta
}
COLLECTION ||--o{ POINT : contains
META_BUCKET ||--|| COLLECTION : metadata_for

Technical Trade-off Analysis

Why choose HNSW index over other algorithms?

  1. Performance advantage: HNSW provides O(log N) search complexity, significant performance improvement over flat index's O(n)
  2. Industrial maturity: HNSW has been verified in multiple production environments with reliable stability
  3. Tunability: Parameters like M, EfConstruction, EfSearch can be optimized for different scenarios
  4. Memory efficiency: HNSW's hierarchical structure is more memory-efficient compared to full graph structures

Why use embedded storage?

  1. Simple deployment: No additional database service required, runs as single file
  2. High reliability: Transaction-based operations using bbolt ensure data consistency
  3. Cross-platform: Pure Go implementation, supports multi-platform compilation
  4. Zero dependencies: Avoids complex C/C++ dependency issues

Dependency Analysis

External Dependencies

GoVector's external dependencies are relatively simple, mainly including three core libraries:

graph TB
GOVECTOR[GoVector Core Code]
subgraph "External Dependencies"
HNSW[coder/hnsw
HNSW graph index implementation] BBOLT[go.etcd.io/bbolt
Embedded key-value storage] PROTOBUF[google.golang.org/protobuf
Protocol Buffers] end GOVECTOR --> HNSW GOVECTOR --> BBOLT GOVECTOR --> PROTOBUF

Internal Module Dependencies

graph TD
MAIN[cmd/govector/main.go] --> API[api/server.go]
MAIN --> CORE[core/collection.go]
API --> CORE
CORE --> INDEX[core/index.go]
INDEX --> HNSW[core/hnsw_index.go]
INDEX --> FLAT[core/flat_index.go]
CORE --> STORAGE[core/storage.go]
STORAGE --> QUANT[core/quantization.go]
CORE --> MODELS[core/models.go]

Performance Considerations

Index Performance Comparison

Based on benchmark data from project documentation:

Index Type Scale (N) Build Time Avg Latency Throughput (QPS) Memory Usage
Flat 100K 186ms 54.46ms 18 QPS 59MB
HNSW 100K 20.9s 0.08ms 11,812 QPS 311MB
HNSW 1M 4m 17s 0.11ms 8,709 QPS 3.32GB

Quantization Compression Effect

The system supports SQ8 8-bit scalar quantization, which can significantly reduce storage space:

  • Compression ratio: Each dimension reduced from 4 bytes to 1 byte + 8 bytes metadata
  • Precision loss: Controlled through minimum and maximum value range, maintaining reasonable precision
  • Performance impact: Quantization performed on load, does not affect query performance

Troubleshooting Guide

Common Problems and Solutions

1. Collection creation failed

Symptoms: Error when creating collection Possible causes: - Collection name already exists - Invalid vector dimension - Unsupported distance metric - Storage initialization failed

Solutions: - Check collection name uniqueness - Verify vector dimension must be positive - Confirm distance metric string is correct - Check database file permissions

2. Data persistence exception

Symptoms: Data lost after Upsert operation Possible causes: - Storage layer write failed - Memory index updated then rolled back - Database connection interrupted

Solutions: - Check disk space and permissions - Verify database file integrity - Check storage layer error logs

3. Abnormal search results

Symptoms: Search results empty or inaccurate Possible causes: - Query vector dimension mismatch - Filter conditions too strict - HNSW parameters improperly configured

Solutions: - Confirm query vector dimension matches collection - Adjust filter conditions - Optimize HNSW parameters (M, EfSearch)

Conclusion

GoVector successfully encapsulates complex vector search functionality into an easy-to-use embedded database through a clear four-layer architecture design. Its core advantages include:

  1. Clear architecture: Layered decoupling makes the system easy to understand and maintain
  2. Excellent performance: HNSW index provides near-real-time search performance
  3. Simple deployment: Single-file deployment, zero-dependency operation
  4. Complete functionality: Supports complete vector search, filtering and persistence

This architecture provides ideal infrastructure for building local AI applications, desktop programs and edge computing scenarios, meeting performance requirements while maintaining development convenience.