Introduction to Apache Cassandra

Tanja Adžić
22 min readMar 7, 2024

--

Introduction

Apache Cassandra is an open-source, distributed NoSQL database management system designed to handle large amounts of data across many commodity servers, providing high availability with no single point of failure. It was initially developed by Facebook and later open-sourced as a project under the Apache Software Foundation. Cassandra is highly scalable and designed to handle structured data across multiple data centers and cloud availability zones.

Cassandra is widely used in various industries for applications requiring scalability, high availability, and fault tolerance, such as real-time analytics, IoT (Internet of Things) applications, messaging platforms, and more.

Overview of Cassandra

Apache Cassandra is an open-source, distributed, decentralized, elastically scalable, highly available, fault-tolerant, tunable, and consistent database. Its distribution design is based on Amazon’s Dynamo, and its data model is inspired by Google’s Bigtable. Initially developed at Facebook, Cassandra is now utilized by major web services such as Netflix, Spotify, and Uber.

In comparison to MongoDB, a document store database, Cassandra caters to specific use cases like rapid data recording and immediate availability for read operations amidst high request volumes. For instance, scenarios like recording online shop transactions or managing user access profiles on services like Netflix benefit from Cassandra’s capabilities.

Unlike MongoDB’s focus on consistency, Cassandra prioritizes fast data storage, easy retrieval by key, constant availability, rapid scalability, and geographical distribution of servers. It uses a simpler peer-to-peer architecture compared to MongoDB’s Primary-Secondary setup. Key features of Cassandra include distributed and decentralized architecture, tunable consistency, fault tolerance, high write throughput, linear scalability, support for multiple data centers, and a SQL-like query language.

While Cassandra is highly popular and reliable, it’s not a drop-in replacement for relational databases due to limitations in join support, aggregations, and transactions. For scenarios requiring joins and aggregations, Cassandra can be paired with processing engines like Apache Spark.

Cassandra shines in write-intensive applications with fewer updates or deletes, where data access is primarily via a known partition key. It’s ideal for globally available online services like Netflix, Spotify, and Uber, as well as use cases such as storing eCommerce transactions for analytics, user profile information for personalization, and time-series data like weather updates from sensors.

Architecture of Cassandra

The Apache Cassandra architecture is designed to provide scalability, availability, and reliability to store massive amounts of data. Cassandra is based on a distributed system architecture. In its simplest form, Cassandra can be installed on a single machine or container. A single Cassandra instance is called a node. Cassandra supports horizontal scalability achieved by adding more than one node as a part of a Cassandra cluster.

As well as being a distributed system, Cassandra is designed to be a peer-to-peer architecture, with each node connected to all other nodes. Each Cassandra node can perform all database operations and can serve client requests without the need for a primary node.

How do the nodes in this peer-to-peer architecture (no primary node) know to which node to route a request and if a certain node is down or up? Through Gossip.

Gossip is the protocol used by Cassandra nodes for peer-to-peer communication. The gossip protocol informs a node about the state of all other nodes. A node performs gossip communications with up to three other nodes every second. The gossip messages follow a specific format and use version numbers to make efficient communication, thus shortly each node can build the entire metadata of the cluster (which nodes are up/down, what are the tokens allocated to each node, etc..).

Multi Data Centers Deployment

A Cassandra cluster can be a single data center deployment (like in the above pics), but most of the time Cassandra clusters are deployed in multiple data centers. A multi data-center deployment looks like below — where you can see depicted a 12 nodes Cassandra cluster, topology wise installed in 2 datacenters. Since replication is being set at keyspace level, demo keyspace specifies a replication factor 5: 2 in data center 1 and 3 in data center 2.

Components of a Cassandra Node

There are several components in Cassandra nodes that are involved in the write and read operations. Some of them are listed below:

Memtable

Memtables are in-memory structures where Cassandra buffers writes. In general, there is one active Memtable per table. Eventually, Memtables are flushed onto disk and become immutable SSTables.

This can be triggered in several ways:

  • The memory usage of the Memtables exceeds a configured threshold.
  • The CommitLog approaches its maximum size, and forces Memtable flushes in order to allow Commitlog segments to be freed.
  • When we set a time to flush per table.

CommitLog

Commitlogs are an append-only log of all mutations local to a Cassandra node. Any data written to Cassandra will first be written to a commit log before being written to a Memtable. This provides durability in the case of unexpected shutdown. On startup, any mutations in the commit log will be applied to Memtables.

SSTables

SSTables are the immutable data files that Cassandra uses for persisting data on disk. As SSTables are flushed to disk from Memtables or are streamed from other nodes, Cassandra triggers compactions which combine multiple SSTables into one. Once the new SSTable has been written, the old SSTables can be removed.

Each SSTable is comprised of multiple components stored in separate files, some of which are listed below:

  • Data.db: The actual data.
  • Index.db: An index from partition keys to positions in the Data.db file.
  • Summary.db: A sampling of (by default) every 128th entry in the Index.db file.
  • Filter.db: A Bloom Filter of the partition keys in the SSTable.
  • CompressionInfo.db: Metadata about the offsets and lengths of compression chunks in the Data.db file.

Write Process at Node Level

Cassandra processes data at several stages on the write path, starting with the immediate logging of a write and ending with a write of data to disk:

  • Logging data in the commit log
  • Writing data to the Memtable
  • Flushing data from the Memtable
  • Storing data on disk in SSTables

Read at node level

While writes in Cassandra are very simple and fast operations, done in memory, the read is a bit more complicated, since it needs to consolidate data from both memory (Memtable) and disk (SSTables). Since data on disk can be fragmented in several SSTables, the read process needs to identify which SSTables most likely contain info about the partitions we are querying — this selection is done by the Bloom Filter information. The steps are described below:

  • Checks the Memtable
  • Checks Bloom filter
  • Checks partition key cache, if enabled
  • If the partition is not in the cache, the partition summary is checked
  • Then the partition index is accessed
  • Locates the data on disk
  • Fetches the data from the SSTable on disk
  • Data is consolidated from Memtable and SSTables before being sent to coordinator

Key Features of Cassandra

Apache Cassandra stands out as a distributed and decentralized, highly available, fault-tolerant, performant, elastically scalable, geographically distributed, and user-friendly database. While many NoSQL databases are distributed, few are decentralized like Cassandra.

In Cassandra’s architecture, clusters can span multiple machines while presenting a unified interface to users — this is what is meant by distributed. This is made possible through a combination of Cassandra’s application client and server, which optimally routes user requests within the cluster. Decentralized means that every node in the cluster is identical, with no primary or secondary nodes. Communication among nodes occurs via a peer-to-peer protocol, ensuring synchronization through Gossip.

Data distribution

Data distribution in Cassandra begins with queries, where data grouping is crucial. For instance, data can be grouped based on a partition key, which determines how data is stored and distributed in the cluster. Data distribution relies on hashing each partition key (tokens) to route data to the appropriate node based on predefined token allocation.

Data replication

Replicating data ensures fault tolerance and availability. Replication factors specify how many nodes hold replicas of each piece of data. Data replication proceeds clockwise in the cluster, considering the placement of nodes within racks and data centers. Cassandra aims to distribute data evenly across racks to enhance fault tolerance.

Availability vs Consistency

Cassandra prioritizes availability over consistency, offering eventual or tunable consistency. Developers can control consistency levels based on their requirements, balancing between strong and eventual consistency. Despite potential data inconsistencies, Cassandra ensures availability even in the event of cluster disruptions.

Fault tolerance

Fault tolerance is inherent in Cassandra’s decentralized and distributed nature. All nodes perform the same functions, communicate peer-to-peer, and replicate data, making Cassandra resilient to node failures. Scaling clusters involves simply adding nodes, leading to linear performance increases. New nodes seamlessly integrate into the cluster, while existing nodes redistribute responsibilities.

High Write Throughput

Cassandra handles high write volumes efficiently by parallelizing writes across replica nodes. By default, Cassandra does not perform read before write; writes occur in memory and are later flushed to disk. Data on disk is sequentially appended and reconciled through compaction.

Cassandra Query Language (CQL)

Cassandra Query Language (CQL) facilitates data manipulation and definition, resembling SQL syntax for ease of use. However, Cassandra’s execution of write and read operations differs significantly from relational databases. While syntax similarities exist, Cassandra’s operational mechanisms diverge from traditional relational databases.

Cassandra Data Model

Cassandra organizes data into tables, with each table’s schema defining how data is stored at both the cluster and node levels. Tables are grouped within keyspaces, logical entities containing one or more tables, and defining replication strategies. It’s recommended to use one keyspace per application.

For instance, let’s consider a keyspace named intro_cassandra with a replication factor of 5, distributed between 2 data centers: replication factor 2 in datacenter1 and replication factor 3 in datacenter2. Creating tables within this keyspace involves declaring the keyspace or directly specifying the keyspace name when creating a table, like intro_cassandra.groups:

-- creating the keyspace
CREATE KEYSPACE intro_cassandra
WITH replication = {
'class': 'NetworkTopologyStrategy',
'datacenter1': 2,
'datacenter2': 3
};

-- declaring the keyspace
USE intro_cassandra;

-- creating a table within the keyspace
CREATE TABLE groups (
groupid int,
username text,
age int,
PRIMARY KEY (groupid, username)
);

Tables serve as logical entities organizing data storage, comprising rows and columns. Table creation, modification, and deletion don’t affect ongoing data updates or queries. To create a table, a schema is defined using Cassandra Query Language (CQL), including the primary key and regular columns.

For instance, a ‘groups’ table might store information about various groups, with ‘groupid’ and ‘username’ forming the primary key. The ‘groupid’ acts as the Partition Key, while ‘username’ serves as the Clustering Key. The primary key, once defined, cannot be changed and plays two vital roles: optimizing query performance and ensuring entry uniqueness. The Primary key has two components:

  • Partition Key — mandatory
  • Clustering Keys(s) — optional

When data is inserted into a cluster, it’s grouped per partition key and distributed to nodes based on a hash function. Apache Cassandra utilizes ‘Murmur3 consistent hashing’ to ensure consistent token values, determining data locality within the cluster. Each partition’s data resides on a specific node, facilitating efficient query processing. In large clusters, minimizing the number of nodes queried for a given query is crucial for performance optimization.

Table Types

Cassandra distinguishes between static and dynamic tables based on their primary key composition. A static table has only the partition key, while a dynamic table includes both partition and clustering keys.

In static tables, each partition contains only one entry and is referred to as a static partition. In dynamic tables, partitions may contain multiple entries, offering greater flexibility in data organization. In summary, Cassandra’s table structure and primary key composition significantly impact data storage, distribution, and query performance.

Clustering Key

While the partition key ensures data locality, the clustering column dictates the data’s arrangement within the partition, optimizing the retrieval of similar values.

Typically, the clustering key, whether single or multiple columns, governs the sorting order within the partition. In our scenario, with ‘username’ as the sole clustering key, data within the group partition will be sorted by username in ascending order by default. Consequently, querying all users in a specific group results in ordered data by usernames.

Beyond ensuring uniqueness, the clustering key significantly enhances read query performance. Consider a query seeking users in groupid 12 aged 32. An effective data model might employ ‘groupid’ as the partition key and ‘age’ and ‘username’ as clustering keys. This arrangement groups data by age within each partition, facilitating efficient queries targeting specific age groups.

CREATE TABLE users_by_group_and_age (
groupid int,
age int,
username text,
PRIMARY KEY ((groupid), age, username)
);

Reducing the data read from a partition is paramount for query performance, particularly with large partitions. In dynamic tables, partitions expand dynamically with distinct entries, thanks to the clustering key’s presence. However, it’s important to note that this diagram doesn’t consider replication, with only one node storing data for each partition.

Data Modeling Process

Crafting an optimal primary key is foundational to the Data Modeling process. Two fundamental rules guide this process:

  • selecting a partition key that addresses queries while evenly distributing data across the cluster, and
  • designing a primary key to minimize partition reads for query responses. This ensures optimal query performance by minimizing node accesses.

Lastly, aside from these basic principles, consider structuring the clustering key to further reduce data reads by aligning with query requirements. By ordering clustering key columns accordingly, query efficiency is further enhanced.

Cassandra Query Language Shell

Cassandra Query Language (CQL) is the primary language used to interact with Apache Cassandra databases. It provides a structured and intuitive syntax similar to SQL for creating, querying, updating, and managing Cassandra databases and tables. CQL facilitates the interaction with Cassandra databases by offering a familiar SQL interface to users accustomed to working with relational databases. It is worth repeating that CQL does not support JOIN statements. If you need a join in Cassandra, then you store the data already joined.

Different Options for Running CQL Queries

  1. Using CQL Shell (cqlsh): CQL Shell is a command-line interface that allows users to execute CQL queries directly against a Cassandra database. It provides an interactive environment for database administration and querying.
  2. Through Programming Language Drivers: Various programming language drivers such as Python, Java, Node.js, etc., offer APIs to interact with Cassandra databases. Users can write scripts or applications in their preferred programming language to execute CQL queries programmatically.
  3. Using Integrated Development Environments (IDEs): Some IDEs, such as DataStax DevCenter and JetBrains IntelliJ IDEA, offer plugins or built-in support for executing CQL queries against Cassandra databases. This provides a graphical interface for writing and executing queries.

CQL Shell (cqlsh)

CQL Shell, often referred to as cqlsh, is a command-line utility included with Cassandra installations. It allows users to connect to Cassandra clusters and execute CQL queries interactively. Cqlsh provides a powerful tool for administrators, developers, and database users to perform various database operations, including querying data, creating and altering schema, and managing database resources.

Key ommand line options for the CQL Shell

  • -u, — username: Specifies the username to use for authentication when connecting to the Cassandra cluster.
  • -help: Cassandra Query Language (CQL) is the primary language used to interact with Apache Cassandra databases. It provides a structured and intuitive syntax similar to SQL for creating, querying, updating, and managing Cassandra databases and tables. CQL facilitates the interaction with Cassandra databases by offering a familiar interface to users accustomed to working with relational databases.
  • -p, — password: Specifies the password to use for authentication when connecting to the Cassandra cluster.
  • -version: Shows the version of cqlsh being used
  • -k, — keyspace: Specifies the keyspace to use for executing CQL queries. If not provided, cqlsh uses the default keyspace defined in the configuration.
  • -f, — file: Specifies the path to a file containing CQL commands to execute. This option allows users to run batch scripts containing multiple queries.
  • -e, — execute: Allows users to execute a single CQL command directly from the command line without entering the interactive shell. This option is useful for one-off queries or automation tasks.
  • -C, — color: Enables or disables colored output in the cqlsh shell. Colored output can improve readability but may not be supported in all terminal environments.

cqlsh special commands

  • .exit (or .quit): Terminates the cqlsh session and exits the shell.
  • .clear: Clears the screen, removing any previous output from the cqlsh session.
  • .consistency: Sets the consistency level for subsequent queries. Syntax: .consistency <consistency_level>
  • .copy: Imports data from or exports data to CSV files.
  • .describe: Provides information about the schema of a keyspace, table, or user-defined type. Syntax: .describe [keyspace | table | type] <keyspace_name | table_name | type_name>
  • .show: Displays various types of information, such as the current keyspace, cluster configuration, or available keyspaces. Syntax: .show [version | host | keyspaces | schema | types]
  • .source: Executes CQL commands from a file. Syntax: .source <file_path>
  • .capture: Redirects query output to a file. Syntax: .capture <file_path>
  • .paging: Toggles paging of query results on or off. Syntax: .paging [on | off]
  • .tracing: Enables or disables query tracing for subsequent queries. Syntax: .tracing [on | off]
  • .timing: Toggles display of query execution timings on or off. Syntax: .timing [on | off]

Importing Data with Specified Consistency Level

Let’s say we want to import data from a CSV file named imported_products.csv into a Cassandra table named products, ensuring a certain consistency level:

COPY products FROM 'imported_products.csv' WITH CONSISTENCY QUORUM;

What this code does is:

  • COPY products FROM 'imported_products.csv' imports data from the imported_products.csv file into the products table.
  • WITH CONSISTENCY QUORUM specifies that the operation should maintain a quorum consistency level, requiring acknowledgment from a majority of replicas across all datacenters before considering the operation successful.

Consistency refers to the number of nodes out of the total replicas, that should respond to a query in order to consider the query successful. There are several consistency levels:

  • ONE: Only one replica needs to respond for the operation to be considered successful.
  • LOCAL_ONE: Only one replica in the local datacenter needs to respond.
  • QUORUM: Majority of replicas (quorum) across all datacenters need to respond.
  • LOCAL_QUORUM: Majority of replicas in the local datacenter need to respond.
  • EACH_QUORUM: Majority of replicas in each datacenter need to respond.
  • ALL: All replicas need to respond.
  • ANY: Any replica can respond.
  • SERIAL: Used for lightweight transactions. Requires consensus among replicas.
  • LOCAL_SERIAL: Similar to SERIAL, but only within the local datacenter.

CQL Data Types

Let’s focus on the various data types used when defining tables in Cassandra. Data types essentially specify the kind of variables stored, such as int, char, float in other contexts. In Cassandra Query Language (CQL), these types can be categorized into three main groups:

1. Built-in Data Types:

These are pre-defined in Cassandra and include common types like Ascii, Boolean, Decimal, Double, Float, Int, and Text. Additionally, there are specialized types that may require some clarification. For instance:

  • Blob: Used for storing Binary Large Objects such as images or multimedia files, recommended not to exceed 1MB in size.
  • Bigint: Represents a 64-bit signed long integer, offering a wider range compared to ‘int’.
  • Varchar: Represents UTF8 encoded strings.

2. Collection Data Types:

Cassandra provides collection types to group and store data together within a column. This avoids the need for joins between multiple tables, enhancing performance. Collection data types include:

  • Lists: Used to maintain the order of elements and store values multiple times, such as log entries.
  • Maps: Stores key-value pairs, ideal for journal entries with dates and corresponding text.
  • Sets: Stores a group of sorted elements, useful for maintaining unique collections like email addresses.

Here is an example of the List Collection data type:

USE intro_cassandra;

ALTER TABLE users ADD jobs list<text>;

-- add jobs to the list for a specific user
UPDATE users SET jobs = ['Software Engineer', 'Data Analyst']
WHERE userid = 'user123';

-- add a new job to the list at the end
UPDATE users SET jobs = jobs + ['Project Manager'] WHERE userid = 'user123';

-- add a new job to the list at a specific position (index)
UPDATE users SET jobs[1] = 'Data Scientist' WHERE userid = 'user123';

-- remove a job from the list
UPDATE users SET jobs = jobs - ['Data Analyst'] WHERE userid = 'user123';

-- select all jobs for a specific user
SELECT jobs FROM users WHERE userid = 'user123';

3. User-Defined Data Types (UDTs):

UDTs allow users to create custom data types by combining multiple existing data types, including collections and other UDTs. This is particularly handy for representing complex structures like addresses. Users can create, alter, verify, and drop fields or entire UDTs as needed.

Let’s consider an example where we create a new UDT called ‘address’ and use it to define a column named ‘Location’ in a table. We can then insert data using this ‘address’ data type and even drop the data type if necessary.

-- new udt 'address'
CREATE TYPE address (
street text,
city text,
zip_code int
);

-- creating a table with a column using the 'address' UDT
CREATE TABLE user_location (
user_id uuid PRIMARY KEY,
name text,
location frozen<address>
);

-- inserting data into the table
INSERT INTO user_location (user_id, name, location)
VALUES (
uuid(),
'John Doe',
{street: '123 Main St', city: 'Anytown', zip_code: 12345}
);

-- drop udt 'address' if necessary
DROP TYPE address;

Keyspace Operations

Before creating tables, it’s essential to define a keyspace since there is no default keyspace. A keyspace can contain numerous tables, with each table exclusively belonging to a single keyspace. Replication is a critical aspect determined at the keyspace level. During keyspace creation, you specify the replication factor, which can be subsequently modified.

As an example let’s consider the CQL command ‘CREATE KEYSPACE’. Here, we specify the keyspace name and two crucial parameters: Class (representing the Replication Strategy) and Replication Factor (set at each data center level). For instance, we create a keyspace named ‘intro_cassandra’ with a replication factor of 5, distributing data across 32 nodes in data center 1 and 23 nodes in data center 2.

CREATE KEYSPACE intro_cassandra 
WITH replication = {
'class': 'NetworkTopologyStrategy',
'datacenter1': 5,
'datacenter2': 5
};

To confirm the creation of a keyspace, you can use either ‘DESCRIBE KEYSPACES’ or ‘DESCRIBE <keyspace_name>’. Replication factor denotes the number of data replicas on different nodes, while the Replication Strategy determines the placement of these replicas across the cluster nodes.

Notably, all replicas in Apache Cassandra hold equal importance; there are no primary or secondary replicas. It’s generally advised that the replication factor doesn’t surpass the number of nodes in the cluster.

Now, let’s create a scenario with a 4-node Cassandra cluster and a keyspace named ‘intro_cassandra’ set with a Replication Factor of 3. We use the Network Topology Strategy and specify the replication at the data center level as 3. Visualizing the cluster topology, we ensure that replicas are placed strategically, considering the rack allocation of the servers.

CREATE KEYSPACE intro_cassandra 
WITH replication = {
'class': 'NetworkTopologyStrategy',
'datacenter1': 3,
};

In another scenario, involving a multi-data center environment, where our keyspace has a replication factor of 5, we repeat the process, ensuring proper data distribution across data centers.

Once a keyspace is created, you can modify it using the ‘ALTER KEYSPACE’ command, adjusting parameters such as the replication factor. Likewise, the ‘DROP KEYSPACE’ command enables the deletion of a keyspace and its associated data. As a safety measure, Cassandra takes a snapshot of the keyspace before execution, facilitating data recovery if needed.

ALTER KEYSPACE intro_cassandra 
WITH replication = {
'class': 'NetworkTopologyStrategy',
'datacenter1': 6,
'datacenter2': 6
};

Table Operations

Cassandra tables play a crucial role in storing and organizing data in the Cassandra database. They serve as the fundamental structure for storing data in rows and columns. Here are some key aspects of the role of Cassandra tables:

  1. Data Storage: Tables in Cassandra are where actual data is stored. Each table consists of rows and columns, similar to traditional relational databases, but with some differences in how data is organized and accessed.
  2. Data Organization: Tables organize data logically into rows and columns. Each row represents a single record or entity, while each column represents a specific attribute or field of that record. Cassandra tables allow flexible schema design, enabling varying column structures within the same table.
  3. Data Access: Tables facilitate efficient data retrieval through Cassandra Query Language (CQL) queries. Cassandra’s distributed architecture ensures high availability and scalability for read and write operations across nodes in the cluster.
  4. Partitioning: Tables are partitioned based on the partition key specified during table creation. This partitioning scheme determines how data is distributed across nodes in the cluster, enabling horizontal scalability and fault tolerance.
  5. Replication: Cassandra tables support data replication to ensure data durability and fault tolerance. Replicas of data are stored on multiple nodes based on the replication strategy defined at the keyspace level.

Properties of Cassandra Tables

  1. Partition Key: Each table in Cassandra must have a partition key defined during table creation. The partition key determines how data is partitioned and distributed across nodes in the cluster.
  2. Clustering Columns: Tables may include clustering columns, which define the clustering order within each partition. Clustering columns allow sorting and querying of data within partitions.
  3. Data Types: Cassandra tables support various data types for columns, including primitive types (e.g., text, integer, boolean) and collection types (e.g., set, list, map).
  4. Compaction Strategy: Tables in Cassandra use compaction strategies to manage storage and optimize performance by merging and compacting data files.
  5. TTL (Time to Live): Tables can be configured with a TTL for individual rows or columns, specifying the duration after which data expires and is automatically deleted.

Creating, Altering, and Deleting a Table

Below are examples of how to create, alter, and delete a table using Cassandra Query Language (CQL):

1. Creating a Table

CREATE TABLE users (
user_id UUID PRIMARY KEY,
name TEXT,
email TEXT
);

2. Altering a Table (Adding a Column)

ALTER TABLE users
ADD age INT;

3. Deleting a Table

DROP TABLE users;

These commands respectively demonstrate creating a table named ‘users’ with columns for user ID, name, and email, altering the table to add an ‘age’ column, and finally, deleting the ‘users’ table altogether.

CRUD Operations

Before exploring the syntax of writes in Cassandra, it’s really important to understand how writes are executed at both the cluster and node levels.

Cluster-Level Writes

At the cluster level, when a write occurs, the node receiving the write becomes the coordinator of the operation. This coordinator ensures the completion of the operation and sends the result back to the user. Write operations are directed to all replicas of the partition being written into. However, for the operation to succeed, acknowledgement is expected from at least the minimum number of nodes specified for consistency.

For instance, consider a keyspace with a replication factor of 3 and a write operation with consistency set to 2. If a partition spans nodes 1, 2, and 3, and node 2 is unavailable, the write acknowledgment will be sent only by nodes 1 and 3. The coordinator node checks the number of responses against the expected consistency level and returns an OK signal accordingly.

Node-Level Writes

At the node level, writes are initially stored in memory and later flushed onto disk in files called SSTables. Each flush operation creates a new SSTable file, while on-disk data is appended in subsequent SSTables. Timestamps are assigned to every write operation, aiding in data reconciliation, where the most recent data takes precedence.

Insert Operations

Insert operations in Cassandra require the full primary key to be specified. They behave both as inserts and updates, with new data inserted or existing data updated as needed. Data insertion can be performed with a specific Time-To-Live (TTL), making the data visible for a defined duration.

Update Operations

Updates can be performed on existing records, with the ability to update specific columns or the entire record. Lightweight transactions, supported by introducing IF clauses in INSERT and UPDATE statements, enable operations to be executed only if certain conditions are met.

Example Scenarios

  • Inserting new data into a table, ensuring all mandatory columns are specified.
  • Updating existing records based on primary key values or specific conditions.
  • Using TTL to insert data that will expire after a defined time.
  • Utilizing lightweight transactions to enforce conditions before executing inserts or updates.
-- inserting new data
INSERT INTO users (user_id, username, email)
VALUES (uuid(), 'john_doe', 'john@example.com');

-- updating an existing data entry
INSERT INTO users (user_id, username, age)
VALUES (uuid(), 'jane_doe', 30) IF NOT EXISTS;

-- inserting data with TTL
INSERT INTO users (user_id, username, email)
VALUES (uuid(), 'alice', 'alice@example.com') USING TTL 3600;

Reading Data

When a Read operation is directed to a node in the cluster, that node becomes the coordinator responsible for completing the Read operation. In our example, node 4 serves as the coordinator for this Read. Reads are sent only to the number of replicas specified by the consistency setting. For instance, a consistency level of 2 implies that only two nodes out of the three replicas will be contacted. The coordinator reconciles the responses from the contacted nodes and resolves any inconsistencies based on the operation’s timestamps before sending back the result.

Syntax-wise, in Cassandra, Reads are performed using the SELECT operation. There are specific rules to follow when executing SELECT operations in Cassandra:

  • Always start your query with the partition key to limit the Read to only the replicas for your partition.
  • Follow the order of your primary key fields in the query for optimal performance.

Let’s illustrate with examples based on our ‘groups’ table, where the primary key consists of ‘groupid’ and ‘username’ columns:

--filtering by 'groupid' or 'groupid' and 'username'
SELECT * FROM groups WHERE groupid = 12;
SELECT * FROM groups WHERE groupid = 12 AND username = 'Alice';
-- trying to filter by 'age' (a regular column) will not work
SELECT * FROM groups WHERE age = 30;

Deleting Data

Starting with some data, let’s demonstrate some deletion operations. In Cassandra, you can delete:

  • An entry identified by the full primary key.
  • A cell in an entry identified by the full primary key.
-- deleting an entry identified by the full primary key

DELETE FROM groups WHERE groupid = 12 AND username = 'Elaine';
-- deleting a cell in an entry identified by the full primary key
DELETE age FROM groups WHERE groupid = 12 AND username = 'Alan';

-- deleting at the partition level
-- example of deleting a continuous range in a partition
DELETE FROM sensor_data
WHERE sensorid = '1' AND time >= '2024-02-16 13:00:00' AND time <= '2024-02-16 15:00:00';

However, it’s crucial to use delete operations sparingly in Cassandra because their usage significantly affects system performance. Deleting distributed and replicated data in Cassandra is more complex than in a relational database due to its peer-to-peer architecture. In Cassandra, a Delete operation is essentially a Write operation with a special value appended to indicate that the data has been deleted. This special value, known as a tombstone, ensures that deleted data is not visible to queries but still resides on disk for a configurable period.

Tombstones are deleted only after a configurable period, known as ‘gc_grace_seconds’, which is set at the table level.

DISCLAIMER

Disclaimer: The notes and information presented in this blog post were compiled during the course “Introduction to NoSQL Databases” and are intended to provide an educational overview of the subject matter for personal use.

--

--

Tanja Adžić

Data Scientist and aspiring Data Engineer, skilled in Python, SQL. I love to solve problems.