Chunk Documents for Vector Search
On this page
SingleStore's vector search allows you to perform semantic, vector-based search on sets of documents or text and receive relevant results.
-
Chunk the documents.
-
Create embeddings and load them into SingleStore.
-
Run similarity search over the embeddings.
Choosing an appropriate chunking strategy is essential as embedding quality depends on how documents are chunked.
What is Chunking and Why is it Important?
Chunking is the process of breaking documents down into smaller chunks of text that are more suitable for embedding models and downstream LLMs.
When chunking documents, ensure that the chunks:
-
Are small enough to fit within the input limits of the embedding model, e.
g. , 256 - 8192 tokens, depending on the model. A token is typically 3-4 characters or ¾ of a word in English. -
Contain enough context to remain meaningful.
-
Preserve semantics to obtain high-quality search results.
Chunking strategy directly impacts the quality of search results:
-
Chunks that are too large return vague matches.
-
Chunks that are too small lose context.
-
Chunks that split mid-sentence or mid-entity return fragments that confuse both people and LLMs.
High-quality vector similarity search depends on creating semantically coherent chunks that fit within an embedding model's limits.
Note: If the text is short enough to fit comfortably within the embedding model's input limit (for example, product descriptions, tweets, or short articles), chunking is unnecessary and may reduce retrieval quality by splitting related context.
Choose a Chunking Strategy
|
Strategy |
How it Works |
When to Use |
|
Fixed-size |
Splits at a fixed character, word, or token count regardless of content. |
Useful as a simple baseline or when guaranteed size limits are required. Fast, no dependencies, predictable. Splits mid-sentence and mid-entity with no semantic awareness; chunks may start and end in meaningless places. |
|
Sentence-based |
Splits at sentence boundaries detected by NLP tokenizers or regexes. |
Useful for Q&A retrieval where the user asks a specific question with a relatively small answer. Each chunk is a complete thought. Sentence length varies significantly and chunk sizes are unpredictable. Adjacent sentences about the same topic may end up in different chunks. |
|
Paragraph / structure |
Splits on document structure such as blank lines, headings, chapter markers. |
Useful for well-structured documents where the organization reflects the topics. Preserves semantically coherent sections that naturally belong together. Works best for well-structured documents. Less effective on raw, unstructured text. Paragraph sizes can vary significantly and may exceed embedding model limits. |
|
Recursive |
Tries splitting on paragraphs first, falls back to sentences, then words, then characters. |
Useful as a general-purpose default. Adapts to content, respects structure when present, degrades gracefully. Ultimately relies on character-count limits rather than semantic understanding. |
|
Entity-preserving |
Splits between sentences, but only where named entities (people, places, organizations) don't carry over into the next sentence. Keeps entities and their surrounding context in the same chunk. |
Useful for documents where names, places, and organizations are central to search. Chunks are self-contained and meaningful. Requires an NLP pipeline such as spaCy or Natural Language Toolkit (NLTK), is slower, and chunk sizes are less predictable. Can produce oversized chunks if entities span many sentences. |
|
Semantic |
Embeds each sentence in a document, compares adjacent embeddings, and identifies topic boundaries where similarity drops significantly. |
Useful for documents where topics don't follow visual structure. Topic-aware, regardless of text formatting. Expensive, as it requires an extra embedding API call per sentence for the initial chunking. |
|
Structure-aware (JSON/HTML/Markdown) |
Parses the format's native structure (tags, keys, headings) and splits along those boundaries. |
Useful for structured or semi-structured data. Preserves metadata and format semantics. Requires a different parser per format, and is less effective for long, unstructured text within a field. |
Chunk Data in SingleStore
To support effective semantic or vector-based search, users may need to chunk document files or text stored in a table column when the text exceeds the embedding model’s input limits.
For example:
# Chunk documentsfrom langchain_text_splitters import RecursiveCharacterTextSplittersplitter = RecursiveCharacterTextSplitter(chunk_size=1000,chunk_overlap=200,separators=["\n\n", "\n", ". ", " ", ""],length_function=len,)chunks = splitter.split_text(text)
-- Load chunks into SingleStoreCREATE TABLE IF NOT EXISTS chunks (id BIGINT AUTO_INCREMENT PRIMARY KEY,chunk_id BIGINT,text TEXT,source VARCHAR(255),strategy VARCHAR(50),length INT,created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,FULLTEXT KEY idx_text (text));INSERT INTO chunks (chunk_id, text, source, strategy, length)VALUES (...);-- Generate embeddingsALTER TABLE chunks ADD COLUMN embedding_1024 VECTOR(1024);UPDATE chunksSET embedding_1024 = ...WHERE chunk_id = ...;-- Vector similarity searchSELECTchunk_id,text,DOT_PRODUCT(embedding_1024, ... :> VECTOR(1024)) AS similarityFROM chunksORDER BY similarity DESCLIMIT 5;
This simplified example illustrates key steps in the workflow.
The following example provides an end-to-end walkthrough using the recursive chunking strategy with sample data and configuration files.
Example: Using the Recursive Strategy
The recursive strategy tries splitting on paragraphs first, then sentences, then words, then characters.
Sample document (test_
SingleStore is a distributed SQL database designed for modern data-intensive applications.
It combines the capabilities of a traditional relational database with the performance characteristics needed for real-time analytics and operational workloads.
The architecture uses a dual-storage engine approach with both rowstore and columnstore formats.
Rowstore is optimized for transactional OLTP workloads with fast inserts, updates, and point queries.
Columnstore is designed for analytical OLAP queries that scan large amounts of data efficiently.
The query optimizer in SingleStore automatically analyzes each query and chooses the optimal execution plan.
It considers multiple factors including data distribution across partitions, available indexes, query selectivity, and join order.
The optimizer can push computation down to storage nodes to minimize data movement.
Advanced features include native support for vector search using approximate nearest neighbor algorithms.
This enables AI and machine learning applications to perform semantic search over embeddings.
The database also supports geospatial data types, JSON operations, and full-text search capabilities.
SingleStore scales horizontally by distributing data across multiple nodes in a cluster.
The architecture separates compute and storage, allowing independent scaling of each layer.
Data is automatically partitioned and replicated for high availability and fault tolerance.Step 1: Configure connection
Create a configuration file (config.) with SingleStore database connection details and embedding settings.
{"singlestore": {"host": "your-host.singlestore.com","port": 3306,"user": "admin","password": "your_password","database": "chunking_test","table_name": "chunks"},"embeddings": {"dimension": 1024}}
Step 2: Chunk the document
Chunk the document using langchain_, a Python script that uses LangChain text splitters.
Options used in this command:
--strategy recursive: Uses recursive splitting, trying paragraphs, then sentences, then words, then characters
--size 500: Sets a target chunk size of 500 characters
--overlap 100: Adds a 100-character overlap between consecutive chunks to preserve context
--output: Specifies the output JSON file path
--stats: Displays statistics about the generated chunks
python chunkers/langchain_chunker.py data/test_doc.txt --strategy recursive --size 500 --overlap 100 --output data/test_chunks.json --stats
Output:
Using recursive strategy...✅ Created 5 chunks📊 Statistics:total_chunks: 5avg_chunk_size: 291min_chunk_size: 255max_chunk_size: 327total_chars: 1,456overlap_estimate: 100💾 Saved to data/test_chunks.json
Step 3: Load into SingleStore
Load the generated chunks into SingleStore.load_ script reads the configuration from config. and automatically creates the database and table if they don't exist.
python singlestore/load_chunks_s2.py data/test_chunks.json
Setting up database...✅ Database 'chunking_test' ready✅ Table 'chunks' readyLoading chunks from data/test_chunks.json...Found 5 chunks to loadLoaded 5/5 chunks...✅ Successfully loaded 5 chunksChunk Statistics:Strategy Count Avg Length Min Max------------------------------------------------------------unknown 5 291 255 327✅ All done! Your chunks are loaded in SingleStore.Database: chunking_testTable: chunks
Step 4: Generate embeddings
Generate vector embeddings with create_, which uses the sentence-transformers library.config. to determine the embedding dimension and automatically selects the appropriate embedding model.embedding_ in the chunks table.
python singlestore/create_embeddings.py
Output:
SingleStore 1024-Dimensional Vector Embeddings Generator============================================================Using CPU for embeddingsLoading embedding model: BAAI/bge-large-en-v1.5 for 1024 dimensionsModel loaded. Embedding dimension: 1024Adding vector column 'embedding_1024' (dimension: 1024)...Vector column added successfullySkipping vector index creationGenerating 1024-dim embeddings for 5 chunks...Device: cpuBatch size: 32Model: BAAI/bge-large-en-v1.5Embeddings generated and stored successfully!Processed: 5 chunks in 0.0 minutesAverage rate: 2.4 chunks/second...[Output truncated - test queries omitted]
Step 5: Vector Search
Perform vector similarity search using a natural language query.--topk option returns the top matching chunks ranked by similarity score, and the --verify flag displays the actual chunk text along with similarity scores.
python singlestore/vector_search.py --topk "how does the query optimizer work" --verify
Output:
Loading embedding model: BAAI/bge-large-en-v1.5 for 1024 dimensions...Connected to SingleStoreVerification mode: Running once with text outputVector search for: 'how does the query optimizer work'============================================================Results:1. Chunk 2 (similarity: 0.679)The query optimizer in SingleStore automatically analyzes each query and choosesthe optimal execution plan. It considers multiple factors including datadistribution across partitions, available indexes, query selectivity, and join order...2. Chunk 1 (similarity: 0.563)The architecture uses a dual-storage engine approach with both rowstore andcolumnstore formats. Rowstore is optimized for transactional OLTP workloads...3. Chunk 0 (similarity: 0.556)SingleStore is a distributed SQL database designed for modern data-intensiveapplications. It combines the capabilities of a traditional relational database...
Use the Repository
The preceding example uses scripts from the following repository:
https://github.
This repository provides end-to-end code for vector similarity search in SingleStore, including tools to:
-
Chunk document files.
-
Create embeddings from the chunks.
-
Load the embeddings and chunks into SingleStore.
-
Run a similarity search over the data.
It includes five chunking implementations (text_, langchain_, nltk_, spacy_, json_), embedding generation scripts, vector search tools, sample data (pride_) and an evaluation script (evaluate_) to measure chunk quality.
Chunkers
The chunkers directory in the repository contains five standalone Python scripts for chunking text in files.
|
Chunker File Name |
Strategies |
Options |
Best For |
|
|
|
Size in characters or words. Overlap supported. Chapter splitting for books. |
Quick start. |
|
|
|
Token counting via tiktoken or sentence-transformers tokenizer. Configurable overlap. Sentence detection via NLTK or spaCy. |
Recursive mode is a solid general-purpose default. LangChain wrapper that supports multiple strategies. Recursive mode (splits on paragraphs → sentences → words → characters). Supports token-aware splitting (exact token limits) for meeting exact model limits. Supports embedding-based semantic splitting for topic-shift detection (requires OpenAI API key). |
|
|
|
Token counting via NLTK word tokenizer. Punkt sentence detection. Entity detection via NER or noun-phrase grammar. Overlap supported. |
Linguistically-aware chunking using NLTK. Best for not splitting mid-entity or mid-phrase and documents with important named entities. |
|
|
|
Full NLP pipeline. Semantic grouping keeps pronouns with antecedents. Token counting via spaCy tokenizer. Overlap supported. |
Highest-quality NLP-aware splitting for entity-rich documents, but with a higher runtime cost. Similar to NLTK but adds pronoun / antecedent grouping. Documents with important names/entities that must not be split. NER-aware boundaries. Note: Requires Python 3. |
|
|
Structure-aware |
Reads NDJSON (article per line). Splits text fields at sentence boundaries. Preserves title/URL metadata per chunk. |
Useful for structured data with text fields to be chunked. Purpose-built for Wikipedia NDJSON ingestion. Reads article-per-line format, chunks abstracts at sentence boundaries, preserves title/URL metadata per chunk. |
Evaluation
The repository also contains a chunking-strategy comparison workflow in which chunks are generated with different strategies and then evaluated with an evaluation script.
-
Size metrics, including count, mean size, median size, standard deviation, min size, max size, and size variance coefficient
-
Boundary quality, including ratios for complete sentences, starts-with-capital, ends-with-punctuation, broken quotes, and broken parentheses
-
Semantic coherence, including pronoun/entity ratios and average sentences per chunk
-
Overlap analysis between consecutive chunks
-
Information density
To learn more, try the sample workflow in the repository: generate chunks with a few different strategies, load them into SingleStore, run similarity searches, and compare the results to see how each approach affects retrieval quality.
Recommendations
Chunking strategy depends on the document type and search requirements.langchain_ as a general-purpose default across various document types.
Choose a Strategy
Consider these common document types when choosing a strategy for your workload:
-
General purpose documents: Recursive adapts to structure
-
Legal documents, news articles: Entity-preserving strategies (
nltkorspacy) keep named entities intact -
Emails, chat logs, transcripts: Semantic chunking detects topic shifts
-
JSON/structured data:
json_preserves metadatachunker -
Q&A systems: Sentence-based chunking provides complete thoughts
Optimize Results
-
Reduce chunk size (500-800 characters) if results seem too vague
-
Increase chunk size (1200 characters) if results appear fragmented
-
Switch to entity-preserving strategy if named entities are being split
-
Check size variance coefficient if chunk quality is inconsistent
-
Test with representative queries, as boundary quality metrics don't always correlate with search performance
Tie Chunks to Enclosing Documents
In many applications, retrieving chunks is sufficient and the chunks can be consumed directly by people, Retrieval-Augmented Generation (RAG) applications, or AI agents.
For example, given a table like this:
CREATE TABLE doc_chunks (chunk_id BIGINT,doc_id BIGINT /* id of enclosing document */,chunk TEXT,chunk_embedding VECTOR(1024));
The following query finds the top K documents with the maximum chunk score:
SET @qv = ('[...]'):>VECTOR(1024);SELECT doc_id, MAX(chunk_embedding <*> @qv) AS max_scoreFROM doc_chunksGROUP BY doc_idORDER BY max_score DESCLIMIT 5;
To use ANN indexing on the chunk_
The optimized query:
SET @qv = ('[...]'):>VECTOR(1024);WITH top_chunks AS (SELECT doc_id, chunk_embedding <*> @qv AS scoreFROM doc_chunksORDER BY score DESCLIMIT 200)SELECT doc_id, MAX(score) AS max_scoreFROM top_chunksGROUP BY doc_idORDER BY max_score DESCLIMIT 5;
This approach is more efficient for large datasets because the ANN index helps quickly find the top 200 candidate chunks, then the aggregation operates on a much smaller subset rather than scanning all chunks.
Related Topics
Last modified: