# Introduction to similarity search: Part 2–Simplifying with Apache Cassandra® 5&#8217;s new vector data type

[Blog](/blog/)&gt;[Technology](/blog/category/technical/)&gt;Introduction to similarity search: Part 2–Simplifying with Apache Cassandra® 5’s new vector data type 

Introduction to similarity search: Part 2–Simplifying with Apache Cassandra® 5’s new vector data type
=====================================================================================================

March 17, 2025 | By [ Murilo Miranda](https://www.instaclustr.com/blog/author/murilo-miranda/)

 

 

 

 



   [ ](https://x.com/intent/tweet?text=Introduction%20to%20similarity%20search:%20Part%202%E2%80%93Simplifying%20with%20Apache%20Cassandra%C2%AE%205%E2%80%99s%20new%20vector%20data%20type&url=https://www.instaclustr.com/blog/introduction-to-similarity-search-part-2/) [ ](https://www.linkedin.com/shareArticle?mini=true&url=https://www.instaclustr.com/blog/introduction-to-similarity-search-part-2/&title=&summary=Introduction%20to%20similarity%20search:%20Part%202%E2%80%93Simplifying%20with%20Apache%20Cassandra%C2%AE%205%E2%80%99s%20new%20vector%20data%20type&source=) 

In [Part 1 of this series](https://www.instaclustr.com/blog/introduction-to-similarity-search-with-word-embeddings-part-1/), we explored how you can combine Cassandra 4 and OpenSearch to perform similarity searches with word embeddings. While that approach is powerful, it requires managing two different systems.

But with the release of Cassandra 5, things become much simpler.

Cassandra 5 introduces a native **VECTOR data type** and built-in **Vector Search** capabilities, simplifying the architecture by enabling Cassandra 5 to handle storage, indexing, and querying seamlessly within a single system.

Now in Part 2, we’ll dive into how Cassandra 5 streamlines the process of working with word embeddings for similarity search. We’ll walk through how the new vector data type works, how to store and query embeddings, and how the **Storage-Attached Indexing (SAI)** feature enhances your ability to efficiently search through large datasets.

The power of vector search in Cassandra 5
-----------------------------------------

Vector search is a game-changing feature added in Cassandra 5 that enables you to perform similarity searches directly within the database. This is especially useful for AI applications, where embeddings are used to represent data like text or images as high-dimensional vectors. The goal of vector search is to find the closest matches to these vectors, which is critical for tasks like product recommendations or image recognition.

The key to this functionality lies in **embeddings:** arrays of floating-point numbers that represent the similarity of objects. By storing these embeddings as vectors in Cassandra, you can use Vector Search to find connections in your data that may not be obvious through traditional queries.

### How vectors work

Vectors are fixed-size sequences of non-null values, much like lists. However, in Cassandra 5, you cannot modify individual elements of a vector — you must replace the entire vector if you need to update it. This makes vectors ideal for storing embeddings, where you need to work with the whole data structure at once.

When working with embeddings, you’ll typically store them as vectors of floating-point numbers to represent the semantic meaning.

### Storage-Attached Indexing (SAI): The engine behind vector search

Vector Search in Cassandra 5 is powered by Storage-Attached Indexing, which enables high-performance indexing and querying of vector data. SAI is essential for Vector Search, providing the ability to create column-level indexes on vector data types. This ensures that your vector queries are both fast and scalable, even with large datasets.

SAI isn’t just limited to vectors—it also indexes other types of data, making it a versatile tool for boosting the performance of your queries across the board.

Example: Performing similarity search with Cassandra 5’s vector data type
-------------------------------------------------------------------------

Now that we’ve introduced the new vector data type and the power of Vector Search in Cassandra 5, let’s dive into a practical example. In this section, we’ll show how to set up a table to store embeddings, insert data, and perform similarity searches directly within Cassandra.

### Step 1: Setting up the embeddings table

To get started with this example, you’ll need access to a **Cassandra 5 cluster**. Cassandra 5 introduces native support for vector data types and Vector Search, available on Instaclustr’s managed platform. Once you have your cluster up and running, the first step is to create a table to store the embeddings. We’ll also create an index on the vector column to optimize similarity searches using SAI.































CREATE KEYSPACE aisearch WITH REPLICATION = {{'class': 'SimpleStrategy', ' replication\_factor': 1}}; CREATE TABLE IF NOT EXISTS embeddings ( id UUID, paragraph\_uuid UUID, filename TEXT, embeddings vector&lt;float, 300&gt;, text TEXT, last\_updated timestamp, PRIMARY KEY (id, paragraph\_uuid) ); CREATE INDEX IF NOT EXISTS ann\_index ON embeddings(embeddings) USING 'sai';

   1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17



  CREATE KEYSPACE aisearch WITH REPLICATION = {{'class': 'SimpleStrategy', ' replication\_factor': 1}};







CREATE TABLE IF NOT EXISTS embeddings (

 id UUID,

 paragraph\_uuid UUID,

 filename TEXT,

 embeddings vector&lt;float, 300&gt;,

 text TEXT,

 last\_updated timestamp,

 PRIMARY KEY (id, paragraph\_uuid)

);





CREATE INDEX IF NOT EXISTS ann\_index 

 ON embeddings(embeddings) USING 'sai';



   

 

 This setup allows us to store the embeddings as 300-dimensional vectors, along with metadata like file names and text. The SAI index will be used to speed up similarity searches on the embedding’s column.

You can also fine-tune the index by specifying the similarity function to be used for vector comparisons. Cassandra 5 supports three types of similarity functions: **DOT\_PRODUCT**, **COSINE,** and **EUCLIDEAN.** By default, the similarity function is set to **COSINE,** but you can specify your preferred method when creating the index:































CREATE INDEX IF NOT EXISTS ann\_index ON embeddings(embeddings) USING 'sai' WITH OPTIONS = { 'similarity\_function': 'DOT\_PRODUCT' };

   1

2

3



  CREATE INDEX IF NOT EXISTS ann\_index 

 ON embeddings(embeddings) USING 'sai'

WITH OPTIONS = { 'similarity\_function': 'DOT\_PRODUCT' };



   

 

 Each similarity function has its own advantages depending on your use case. **DOT\_PRODUCT** is often used when you need to measure the direction and magnitude of vectors, **COSINE** is ideal for comparing the angle between vectors, and **EUCLIDEAN** calculates the straight-line distance between vectors. By selecting the appropriate function, you can optimize your search results to better match the needs of your application.

### Step 2: Inserting embeddings into Cassandra 5

To insert embeddings into Cassandra 5, we can use the same code from the first part of this series to extract text from files, load the FastText model, and generate the embeddings. Once the embeddings are generated, the following function will insert them into Cassandra:































import time from uuid import uuid4, UUID from cassandra.cluster import Cluster from cassandra.query import SimpleStatement from cassandra.policies import DCAwareRoundRobinPolicy from cassandra.auth import PlainTextAuthProvider from google.colab import userdata # Connect to the single-node cluster cluster = Cluster( # Replace with your IP list \["xxx.xxx.xxx.xxx", "xxx.xxx.xxx.xxx ", " xxx.xxx.xxx.xxx "\], # Single-node cluster address load\_balancing\_policy=DCAwareRoundRobinPolicy(local\_dc='AWS\_VPC\_US\_EAST\_1'), # Update the local data centre if needed port=9042, auth\_provider=PlainTextAuthProvider ( username='iccassandra', password='replace\_with\_your\_password' ) ) session = cluster.connect() print('Connected to cluster %s' % cluster.metadata.cluster\_name) def insert\_embedding\_to\_cassandra(session, embedding, id=None, paragraph\_uuid=None, filename=None, text=None, keyspace\_name=None): try: embeddings = list(map(float, embedding)) # Generate UUIDs if not provided if id is None: id = uuid4() if paragraph\_uuid is None: paragraph\_uuid = uuid4() # Ensure id and paragraph\_uuid are UUID objects if isinstance(id, str): id = UUID(id) if isinstance(paragraph\_uuid, str): paragraph\_uuid = UUID(paragraph\_uuid) # Create the query string with placeholders insert\_query = f""" INSERT INTO {keyspace\_name}.embeddings (id, paragraph\_uuid, filename, embeddings, text, last\_updated) VALUES (?, ?, ?, ?, ?, toTimestamp(now())) """ # Create a prepared statement with the query prepared = session.prepare(insert\_query) # Execute the query session.execute(prepared.bind((id, paragraph\_uuid, filename, embeddings, text))) return None # Successful insertion except Exception as e: error\_message = f"Failed to execute query:\\nError: {str(e)}" return error\_message # Return error message on failure def insert\_with\_retry(session, embedding, id=None, paragraph\_uuid=None, filename=None, text=None, keyspace\_name=None, max\_retries=3, retry\_delay\_seconds=1): retry\_count = 0 while retry\_count &lt; max\_retries: result = insert\_embedding\_to\_cassandra(session, embedding, id, paragraph\_uuid, filename, text, keyspace\_name) if result is None: return True # Successful insertion else: retry\_count += 1 print(f"Insertion failed on attempt {retry\_count} with error: {result}") if retry\_count &lt; max\_retries: time.sleep(retry\_delay\_seconds) # Delay before the next retry return False # Failed after max\_retries # Replace the file path pointing to the desired file file\_path = "/path/to/Cassandra-Best-Practices.pdf" paragraphs\_with\_embeddings = extract\_text\_with\_page\_number\_and\_embeddings(file\_path) from tqdm import tqdm for paragraph in tqdm(paragraphs\_with\_embeddings, desc="Inserting paragraphs"): if not insert\_with\_retry( session=session, embedding=paragraph\['embedding'\], id=paragraph\['uuid'\], paragraph\_uuid=paragraph\['paragraph\_uuid'\], text=paragraph\['text'\], filename=paragraph\['filename'\], keyspace\_name=keyspace\_name, max\_retries=3, retry\_delay\_seconds=1 ): # Display an error message if insertion fails tqdm.write(f"Insertion failed after maximum retries for UUID {paragraph\['uuid'\]}: {paragraph\['text'\]\[:50\]}...")

   1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93



  import time 

from uuid import uuid4, UUID

from cassandra.cluster import Cluster 

from cassandra.query import SimpleStatement 

from cassandra.policies import DCAwareRoundRobinPolicy 

from cassandra.auth import PlainTextAuthProvider 

from google.colab import userdata



\# Connect to the single-node cluster 

cluster = Cluster(

\# Replace with your IP list 

\["xxx.xxx.xxx.xxx", "xxx.xxx.xxx.xxx ", " xxx.xxx.xxx.xxx "\], \# Single-node cluster address 

load\_balancing\_policy=DCAwareRoundRobinPolicy(local\_dc='AWS\_VPC\_US\_EAST\_1'), \# Update the local data centre if needed 

port=9042,

auth\_provider=PlainTextAuthProvider (

username='iccassandra',

password='replace\_with\_your\_password'

)

)

session = cluster.connect()



print('Connected to cluster %s' % cluster.metadata.cluster\_name)



def insert\_embedding\_to\_cassandra(session, embedding, id=None, paragraph\_uuid=None, filename=None, text=None, keyspace\_name=None):

try:

embeddings = list(map(float, embedding))



\# Generate UUIDs if not provided 

if id is None:

id = uuid4()

if paragraph\_uuid is None:

paragraph\_uuid = uuid4()

\# Ensure id and paragraph\_uuid are UUID objects

if isinstance(id, str):

id = UUID(id)

if isinstance(paragraph\_uuid, str):

paragraph\_uuid = UUID(paragraph\_uuid)



\# Create the query string with placeholders

insert\_query = f""" 

INSERT INTO {keyspace\_name}.embeddings (id, paragraph\_uuid, filename, embeddings, text, last\_updated)

VALUES (?, ?, ?, ?, ?, toTimestamp(now()))

"""



\# Create a prepared statement with the query 

prepared = session.prepare(insert\_query)



\# Execute the query 

session.execute(prepared.bind((id, paragraph\_uuid, filename, embeddings, text)))



return None \# Successful insertion



except Exception as e:

error\_message = f"Failed to execute query:\\nError: {str(e)}"

return error\_message \# Return error message on failure



def insert\_with\_retry(session, embedding, id=None, paragraph\_uuid=None,

filename=None, text=None, keyspace\_name=None, max\_retries=3,

retry\_delay\_seconds=1):

retry\_count = 0

while retry\_count &lt; max\_retries:

result = insert\_embedding\_to\_cassandra(session, embedding, id, paragraph\_uuid, filename, text, keyspace\_name)

if result is None:

return True \# Successful insertion 

else:

retry\_count += 1

print(f"Insertion failed on attempt {retry\_count} with error: {result}")

if retry\_count &lt; max\_retries:

time.sleep(retry\_delay\_seconds) \# Delay before the next retry 

return False \# Failed after max\_retries 



\# Replace the file path pointing to the desired file 

file\_path = "/path/to/Cassandra-Best-Practices.pdf"

paragraphs\_with\_embeddings =

extract\_text\_with\_page\_number\_and\_embeddings(file\_path)



from tqdm import tqdm 



for paragraph in tqdm(paragraphs\_with\_embeddings, desc="Inserting paragraphs"):

if not insert\_with\_retry(

session=session,

embedding=paragraph\['embedding'\],

id=paragraph\['uuid'\],

paragraph\_uuid=paragraph\['paragraph\_uuid'\],

text=paragraph\['text'\],

filename=paragraph\['filename'\],

keyspace\_name=keyspace\_name,

max\_retries=3,

retry\_delay\_seconds=1

):

\# Display an error message if insertion fails 

tqdm.write(f"Insertion failed after maximum retries for UUID

{paragraph\['uuid'\]}: {paragraph\['text'\]\[:50\]}...")



   

 

 This function handles inserting embeddings and metadata into Cassandra, ensuring that UUIDs are correctly generated for each entry.

### Step 3: Performing similarity searches in Cassandra 5

Once the embeddings are stored, we can perform similarity searches directly within Cassandra using the following function:































import numpy as np # ------------------ Embedding Functions ------------------ def text\_to\_vector(text): """Convert a text chunk into a vector using the FastText model.""" words = text.split() vectors = \[fasttext\_model\[word\] for word in words if word in fasttext\_model.key\_to\_index\] return np.mean(vectors, axis=0) if vectors else np.zeros(fasttext\_model.vector\_size) def find\_similar\_texts\_cassandra(session, input\_text, keyspace\_name=None, top\_k=5): # Convert the input text to an embedding input\_embedding = text\_to\_vector(input\_text) input\_embedding\_str = ', '.join(map(str, input\_embedding.tolist())) # Adjusted query without the ORDER BY clause and correct comment syntax query = f""" SELECT text, filename, similarity\_cosine(embeddings, ?) AS similarity FROM {keyspace\_name}.embeddings ORDER BY embeddings ANN OF \[{input\_embedding\_str}\] LIMIT {top\_k}; """ prepared = session.prepare(query) bound = prepared.bind((input\_embedding,)) rows = session.execute(bound) # Sort the results by similarity in Python similar\_texts = sorted(\[(row.similarity, row.filename, row.text) for row in rows\], key=lambda x: x\[0\], reverse=True) return similar\_texts\[:top\_k\] from IPython.display import display, HTML # The word you want to find similarities for input\_text = "place" # Call the function to find similar texts in the Cassandra database similar\_texts = find\_similar\_texts\_cassandra(session, input\_text, keyspace\_name="aisearch", top\_k=10)

   1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37



  import numpy as np

\# ------------------ Embedding Functions ------------------ 

def text\_to\_vector(text):

"""Convert a text chunk into a vector using the FastText model."""

words = text.split()

vectors = \[fasttext\_model\[word\] for word in words if word in fasttext\_model.key\_to\_index\]

return np.mean(vectors, axis=0) if vectors else np.zeros(fasttext\_model.vector\_size)



def find\_similar\_texts\_cassandra(session, input\_text, keyspace\_name=None, top\_k=5):

\# Convert the input text to an embedding 

input\_embedding = text\_to\_vector(input\_text)

input\_embedding\_str = ', '.join(map(str, input\_embedding.tolist()))



\# Adjusted query without the ORDER BY clause and correct comment syntax 

query = f""" 

SELECT text, filename, similarity\_cosine(embeddings, ?) AS similarity 

FROM {keyspace\_name}.embeddings 

ORDER BY embeddings ANN OF \[{input\_embedding\_str}\] 

LIMIT {top\_k}; 

"""



prepared = session.prepare(query)

bound = prepared.bind((input\_embedding,))

rows = session.execute(bound)



\# Sort the results by similarity in Python 

similar\_texts = sorted(\[(row.similarity, row.filename, row.text) for row in rows\], key=lambda x: x\[0\], reverse=True)



return similar\_texts\[:top\_k\]



from IPython.display import display, HTML



\# The word you want to find similarities for 

input\_text = "place"



\# Call the function to find similar texts in the Cassandra database 

similar\_texts = find\_similar\_texts\_cassandra(session, input\_text, keyspace\_name="aisearch", top\_k=10)



   

 

 This function searches for similar embeddings in Cassandra and retrieves the top results based on cosine similarity. Under the hood, Cassandra’s vector search uses Hierarchical Navigable Small Worlds (HNSW). HNSW organizes data points in a multi-layer graph structure, making queries significantly faster by narrowing down the search space efficiently—particularly important when handling large datasets.

### Step 4: Displaying the results

To display the results in a readable format, we can loop through the similar texts and present them along with their similarity scores:































\# Print the similar texts along with their similarity scores for similarity, filename, text in similar\_texts: html\_content = f""" &lt;div style="margin-bottom: 10px;"&gt; &lt;p&gt;&lt;b&gt;Similarity:&lt;/b&gt; {similarity:.4f}&lt;/p&gt; &lt;p&gt;&lt;b&gt;Text:&lt;/b&gt; {text}&lt;/p&gt; &lt;p&gt;&lt;b&gt;File:&lt;/b&gt; {filename}&lt;/p&gt; &lt;/div&gt; &lt;hr/&gt; """ display(HTML(html\_content))

   1

2

3

4

5

6

7

8

9

10

11

12



  \# Print the similar texts along with their similarity scores 

for similarity, filename, text in similar\_texts:

html\_content = f""" 

&lt;div style="margin-bottom: 10px;"&gt; 

&lt;p&gt;&lt;b&gt;Similarity:&lt;/b&gt; {similarity:.4f}&lt;/p&gt; 

&lt;p&gt;&lt;b&gt;Text:&lt;/b&gt; {text}&lt;/p&gt; 

&lt;p&gt;&lt;b&gt;File:&lt;/b&gt; {filename}&lt;/p&gt; 

&lt;/div&gt; 

&lt;hr/&gt; 

"""



display(HTML(html\_content))



   

 

 This code will display the top similar texts, along with their similarity scores and associated file names.

Cassandra 5 vs. Cassandra 4 + OpenSearch®
-----------------------------------------

Cassandra 4 relies on an integration with OpenSearch to handle word embeddings and similarity searches. This approach works well for applications that are already using or comfortable with OpenSearch, but it does introduce additional complexity with the need to maintain two systems.

Cassandra 5, on the other hand, brings vector support directly into the database. With its native VECTOR data type and similarity search functions, it simplifies your architecture and improves performance, making it an ideal solution for applications that require embedding-based searches at scale.

**Feature** **Cassandra 4 + OpenSearch** **Cassandra 5 (Preview)** **Embedding Storage** OpenSearch Native VECTOR Data Type **Similarity Search** KNN Plugin in OpenSearch COSINE, EUCLIDEAN, DOT\_PRODUCT **Search Method** Exact K-Nearest Neighbor Approximate Nearest Neighbor (ANN) **System Complexity** Requires two systems All-in-one Cassandra solution Conclusion: A simpler path to similarity search with Cassandra 5
----------------------------------------------------------------

With Cassandra 5, the complexity of setting up and managing a separate search system for word embeddings is gone. The new vector data type and Vector Search capabilities allow you to perform similarity searches directly within Cassandra, simplifying your architecture and making it easier to build AI-powered applications.

**Coming up**: more in-depth examples and use cases that demonstrate how to take full advantage of these new features in Cassandra 5 in future blogs!

Ready to experience vector search with Cassandra 5? [Spin up your first cluster](https://console2.instaclustr.com/signup?_gl=1*i2o9gc*_gcl_au*MjUwNzcwNzUxLjE3MzI0OTE2NzU.*_ga*MTE4NDE2NDQ4NC4xNzE2ODU2NDc1*_ga_4NBQSJMP6D*MTczODg5MzgyOS40NTQuMS4xNzM4ODk0ODc0LjYwLjAuMA..&_ga=2.156842172.665239454.1738889425-1184164484.1716856475) for free on the Instaclustr Managed Platform and try it out!

 

### About the author

**[Murilo Miranda](https://www.instaclustr.com/blog/author/murilo-miranda/)** | Professional Services Consultant

Murilo has demonstrated exceptional expertise in open source technologies, contributing significantly to projects like Apache Cassandra. Murilo's dedication to open source has not only advanced the field but also fostered a collaborative and innovative environment within NetApp.

 



 

 ![mail icon]()#### Get the latest articles for open sourceIn your inbox

 <a class="btn btn-primary btn-popup text-dark" href="">Sign up now</a> 

 

 

 

  ### Related content

 [ Zero Downtime Migration to Instaclustr 

 

 Yes, we can migrate existing Cassandra clusters to Instaclustr without any downtime. Here's what to expect from the process... 

 

 

 

 

 

 

 ](https://www.instaclustr.com/blog/zero-downtime-migration-to-instaclustr/) 

 [ Workflow Comparison: Uber Cadence vs Netflix Conductor 

 

 When choosing what’s right for your company’s opensource workflow needs it is important to know the difference and similarities ... 

 

 

 

 

 

 

 ](https://www.instaclustr.com/blog/workflow-comparison-uber-cadence-vs-netflix-conductor/) 

 [ Will Your Cassandra Database Project Succeed?: The New Stack 

 

 Open source Apache Cassandra® continues to stand out as an enterprise-proven solution for organizations seeking high availability... 

 

 

 

 

 

 

 ](https://www.instaclustr.com/blog/will-your-cassandra-database-project-succeed-the-new-stack/) 

 

  <a class="close-modal" href="">×</a>Sign upto ourNewsletter
-----------------------
