NoSQL Notes
NoSQL Notes
NOSQL
Module-1 Notes
when data is spread across different nodes, further complicating the use of relational
databases in clustered environments.
This mismatch between relational databases and clusters led some organization to
consider anlternative route to data storage. Two companies in particular—Google and
Amazon—have been very influential. Both were on the forefront of running large
clusters of this kind; furthermore, they were capturing huge amounts of data.
Emergence of NOSQL
The emergence of NoSQL databases addresses many of the limitations of traditional
relational databases, especially in handling large-scale, distributed, and unstructured
data. NoSQL databases are designed to provide flexible schemas and horizontal
scalability, making them well-suited for modern applications that require quick access
to large volumes of diverse data. Unlike relational databases, which rely on a rigid
schema and ACID transactions, NoSQL databases often sacrifice some aspects of
consistency to achieve higher availability and partition tolerance, as per the CAP
theorem.
NoSQL databases come in various types, including document stores (e.g., MongoDB),
key-value stores (e.g., Redis), column-family stores (e.g., Cassandra), and graph
databases (e.g., Neo4j), each optimized for specific use cases. They allow for efficient
storage and retrieval of unstructured or semi-structured data and are particularly
effective in handling large-scale, distributed data environments typical of big data
applications. The flexibility, scalability, and performance of NoSQL databases have
made them popular in industries such as social media, e-commerce, and real-time
analytics, where traditional RDBMS solutions struggle to meet the demands of modern,
high-velocity data processing.
The term "NoSQL" originally emerged to describe databases that do not adhere to the
traditional relational database model, particularly those that do not use Structured
Query Language (SQL) for data management. While "NoSQL" is often interpreted as
"no SQL," it more accurately conveys "not only SQL," highlighting that these databases
can support various data models and query languages beyond the standard relational
approach. The name reflects a broad category of database systems designed to handle
unstructured, semi-structured, and rapidly changing data with greater flexibility and
scalability than traditional relational databases.
Features of NoSQL
The key characteristics of NoSQL databases that highlight their distinct advantages over
traditional relational databases:
1. Schema Flexibility
NoSQL databases allow for dynamic and flexible schemas, enabling users to store unstructured,
semi-structured, or structured data without predefined schemas. This flexibility allows for
easier adaptation to changing data requirements and makes it simpler to incorporate new data
types without extensive modifications to the database.
2. Horizontal Scalability
NoSQL databases are designed to scale out horizontally by adding more servers or nodes to
distribute data across multiple machines. This scalability allows them to handle large volumes
of data and high levels of concurrent user requests, making them well-suited for applications
with rapidly growing datasets.
3. High Availability and Fault Tolerance
Many NoSQL databases are built to ensure high availability and fault tolerance. They often
employ data replication across multiple nodes, enabling continuous operation even if some
nodes fail. This design ensures that the database remains accessible and resilient in the face of
hardware failures or other disruptions.
4. Support for Various Data Models
NoSQL databases support multiple data models, including document, key-value, column-
family, and graph models. This variety allows developers to choose the most appropriate model
based on their specific use cases, optimizing data storage and retrieval according to the needs
of their applications.
5. Eventual Consistency
Unlike traditional relational databases that emphasize strong consistency through ACID
transactions, many NoSQL databases adopt an eventual consistency model. This approach
allows for higher availability and partition tolerance, as data updates may not be immediately
consistent across all nodes. Instead, the system guarantees that, given enough time, all updates
will propagate throughout the database, making it suitable for distributed environments where
immediate consistency is less critical.
Polyglot persistence refers to the practice of using multiple data storage technologies, each
optimized for specific use cases, within a single application or system architecture. In the
context of NoSQL databases, this approach allows developers to leverage the strengths of
various NoSQL database types—such as document stores, key-value stores, column-family
stores, and graph databases—to handle diverse data needs efficiently.
Data as per the RDBMS design is shown in the following example figure:
A single logical address record appears three times in the example data, but instead of using
IDs it’s treated as a value and copied each time. This fits the domain where we would not
want the shipping address, nor the payment’s billing address, to change. In a relational
database, we would ensure that the address rows aren’t updated for this case, making a new
row instead. With aggregates, we can copy the whole address structure into the aggregate
as we need to.
A typical NoSQL code for the same is:
The link between the customer and the order isn’t within either aggregate—it’s a
relationship between aggregates. Similarly, the link from an order item would cross into a
separate aggregate structure for products. It is more common with aggregates because we
want to minimize the number of aggregates we access during a data interaction. The more
embedded aggregate model is shown in the following figure:
Relational databases typically require a fixed schema, which can lead to complex
designs when trying to model aggregates. This can result in numerous tables and
relationships, making it difficult to manage and evolve the schema over time.
Aggregates often span multiple tables in RDBMS, necessitating complex JOIN
operations to retrieve related data. This can lead to performance bottlenecks, especially
when dealing with large datasets or frequent queries.
The need for multiple queries to fetch related data can degrade performance in relational
databases, particularly in scenarios where quick access to aggregate data is essential,
such as in real-time applications.
Maintaining ACID properties across multiple tables can introduce additional overhead,
making transactions more complex and potentially impacting throughput and latency.
Aggregate Relationships
Aggregate relationships are a key concept in NoSQL databases, particularly in
document-oriented and key-value stores, where data is grouped into aggregates that
represent a cohesive unit of related information.
Aggregate relationships define how different pieces of data within an aggregate relate
to one another. An aggregate is a cluster of related data that can be treated as a single
unit, encapsulating all the necessary information needed to describe a specific entity or
concept, such as a user profile, order, or product.
In the context of aggregate relationships, an aggregate root is the primary entity
through which the aggregate is accessed and modified. It serves as the entry point for
operations and ensures that all changes to the aggregate maintain its integrity.
Aggregate oriented databases treat the aggregate as the unit of data-retrieval.
Consequently, atomicity is only supported within the contents of a single aggregate. If
you update multiple aggregates at once, you have to deal yourself with a failure partway
through. Relational databases help you with this by allowing you to modify multiple
records in a single transaction, providing ACID guarantees while altering many rows.
Graph Databases
Graph databases are a type of NoSQL database specifically designed to represent and store
data in graph structures, which consist of nodes (entities) and edges (relationships)
connecting them.
This model allows for the representation of complex relationships and interconnected data
in a way that is both intuitive and efficient. Unlike traditional relational databases, which
rely on tables and foreign keys to establish relationships, graph databases treat relationships
as first-class citizens.
They excel in scenarios where relationships are as important as the data itself, such as social
networks, recommendation systems, fraud detection, and network analysis.
One of the key advantages of graph databases is their ability to perform complex queries
on relationships with high efficiency. By leveraging graph traversal algorithms, they can
quickly retrieve interconnected data without the need for expensive JOIN operations typical
in relational databases.
This capability allows for real-time analytics and insights into data relationships, enabling
applications to provide richer, context-driven user experiences. Popular graph databases
include Neo4j, Amazon Neptune, and ArangoDB, each offering unique features and
optimizations for handling graph data.
As data continues to grow in complexity and interconnectivity, graph databases are
increasingly recognized as essential tools for managing and analyzing relational data at
scale.
An example Graph structure is shown in the following figure:
With this structure, we can ask questions such as “find the books in the Databases category
that are written by someone whom a friend of mine likes.” Graph databases specialize in
capturing this sort of information—but on a much larger scale than a readable diagram
could capture. This is ideal for capturing any data consisting of complex relationships such
as social networks, product preferences.
The data model of a graph database is fundamentally centered around two primary
components: nodes and edges. Nodes represent entities or objects, such as users, products,
or locations, while edges define the relationships between these entities, indicating how
they are interconnected.
Each node and edge can have associated properties, which are key-value pairs that provide
additional context or attributes to the entities and relationships. For example, in a social
network graph database, a node might represent a user with properties like name and age,
while an edge could represent a "follows" relationship with properties such as the date the
connection was made.
This flexible structure allows graph databases to efficiently model complex, interconnected
data and perform sophisticated queries that explore relationships, making them particularly
effective for applications requiring insights into data relationships, such as social networks,
recommendation engines, and fraud detection.
Schemaless databases
Schemaless databases, often associated with NoSQL systems, are designed to allow for
flexible data storage without the constraints of a predefined schema. This characteristic
enables users to store unstructured, semi-structured, or structured data in a way that can
evolve over time without requiring significant alterations to the database design. Here are
some key features and advantages of schemaless databases:
They allow developers to add or modify fields within data records without needing to
update the entire database schema.
Because there are no strict schema requirements, schemaless databases can easily integrate
various data types and formats. This characteristic makes them suitable for handling data
from multiple sources, such as JSON, XML, or even binary data, allowing for the
aggregation of diverse datasets within a single system.
The lack of a fixed schema facilitates faster development cycles, enabling teams to iterate
quickly as they build and refine applications.
Schemaless databases are often designed to scale horizontally, meaning they can distribute
data across multiple servers or nodes. This scalability is especially beneficial for
applications experiencing rapid growth or fluctuating workloads.
Popular examples of schemaless databases include document stores like MongoDB, key-
value stores like Redis, and wide-column stores like Cassandra. These databases leverage
their schemaless nature to provide developers with the flexibility needed to handle a wide
range of applications, from content management systems to real-time analytics.
A schemaless store also makes it easier to deal with nonuniform data: data where each
record has a different set of fields. Eg code to parse such schemaless stores:
//pseudo code
foreach (Record r in records) {
foreach Field f in [Link]) {
print([Link], [Link])
}}
Materialized views
Materialized views in RDBMS are pre-computed query results stored as database objects.
Unlike regular views, which are virtual and calculated on-the-fly each time they are
accessed, materialized views store the results physically on disk, allowing for faster query
performance, especially for complex aggregations or joins that involve large datasets.
When modeling for column-family stores, we have the benefit of the columns being ordered,
allowing us to name columns that are frequently used so that they are fetched first.
When using graph databases to model the same data, we model all objects as nodes and
relations within them as relationships; these relationships have types and directional
significance.
Module II – Notes
Distribution Models
NoSQL databases employ various distribution models to manage data across multiple nodes or
servers effectively. These models are designed to enhance scalability, availability, and
performance, allowing NoSQL systems to handle large volumes of data and high levels of
concurrent requests. Broadly, there are two paths to data distribution: replication and sharding.
Replication takes the same data and copies it over multiple nodes. Sharding puts different data
on different nodes. Replication and sharding are orthogonal technique. Replication comes into
two forms: master-slave and peer-to-peer.
Sharding
Sharding is a database architecture pattern that involves partitioning data across multiple
servers or nodes, enabling horizontal scaling and improving performance for large datasets.
In a sharded database, data is divided into smaller, more manageable pieces called "shards,"
which are distributed across different servers.
Each shard contains a subset of the data, allowing the database to handle a higher volume
of read and write operations simultaneously.
This model is particularly beneficial for applications with large datasets, as it helps mitigate
performance bottlenecks and ensures that no single server is overwhelmed by excessive
requests.
A sharding example is shown in the following figure. Sharding puts different data on
separate nodes, each of which does its own reads and writes
Many NoSQL databases offer auto-sharding, where the database takes on the
responsibility of allocating data to shards and ensuring that data access goes to the right
shard.
Sharding does little to improve resilience when used alone. Although the data is on different
nodes, a node failure makes that shard’s data unavailable just as surely as it does for a
single-server solution. The resilience benefit it does provide is that only the users of the
data on that shard will suffer; however, it’s not good to have a database with part of its data
missing.
Maste-Slave Replication
With master-slave distribution, you replicate data across multiple nodes. One node is
designated as the master, or primary. This master is the authoritative source for the data and
is usually responsible for processing any updates to that data. The other nodes are slaves,
or secondaries.
An example master-slave process is shown in the following figure:
Master-slave replication is most helpful for scaling when you have a read-intensive dataset.
You can scale horizontally to handle more read requests by adding more slave nodes and
ensuring that all read requests are routed to the slaves
Read resilience in a master-slave database architecture refers to the ability of the system to
effectively handle read operations even in the presence of failures or high load, leveraging
the characteristics of replication and redundancy inherent in this setup.
The ability to appoint a slave to replace a failed master means that master-slave replication
is useful even if you don’t need to scale out. Masters can be appointed manually or
automatically. Manual appointing typically means that when you configure your cluster,
you configure one node as the master. With automatic appointment, you create a cluster of
nodes and they elect one of themselves to be the master.
Replication comes with some alluring benefits, but it also comes with an inevitable dark
side— inconsistency. You have the danger that different clients, reading different slaves,
will see different values because the changes haven’t all propagated to the slaves.
Peer-to-Peer Replication
The biggest complication even with P2P replication is consistency. When you can write to two
different places, you run the risk that two people will attempt to update the same record at the
same time—a write-write conflict. Inconsistencies on read lead to problems but at least they
are relatively transient.
a common strategy for column-family databases. In a scenario like this you might have
tens or hundreds of nodes in a cluster with data sharded over them.
Combining Master-Slave replication with sharding is shown in the following figure:
Updating Consistency
Coincidentally, Martin and Pramod are looking at the company website and notice that the
phone number is out of date. Implausibly, they both have update access, so they both go in
at the same time to update the number. This issue is called a write-write conflict: two people
updating the same data item at the same time.
When the writes reach the server, the server will serialize them—decide to apply one, then
the other. Let’s assume it uses alphabetical order and picks Martin’s update first, then
Pramod’s. Without any concurrency control, Martin’s update would be applied and
immediately overwritten by Pramod’s. In this case Martin’s is a lost update.
Approaches for maintaining consistency in the face of concurrency are often described as
pessimistic or optimistic. A pessimistic approach works by preventing conflicts from
occurring; an optimistic approach lets conflicts occur, but detects them and takes action to
sort them out.
The most common pessimistic approach is to have write locks, so that in order to change a
value you need to acquire a lock, and the system ensures that only one client can get a lock
at a time.
A common optimistic approach is a conditional update where any client that does an update
to test the value just before updating it to see if it’s changed since his last read.
There is another optimistic way to handle a write-write conflict—save both updates and
record that they are in conflict. Pessimistic approaches often severely degrade the
responsiveness of a system to the degree that it becomes unfit for its purpose. This problem
is made worse by the danger of errors—pessimistic concurrency often leads to deadlocks,
which are hard to prevent and debug.
Read Consistency
Let’s imagine we have an order with line items and a shipping charge. The shipping charge is
calculated based on the line items in the order. If we add a line item, we thus also need to
recalculate and update the shipping charge. In a relational database, the shipping charge and
line items will be in separate tables. The danger of inconsistency is that Martin adds a line item
to his order, Pramod then reads the line items and shipping charge, and then Martin updates the
shipping charge. This is an inconsistent read or read-write conflict.
The scenario of read-write conflict is shown in the following figure:
NoSQL databases don’t support transactions and thus can’t be consistent. Such claim is
mostly wrong because lack of transactions usually only applies to some NoSQL databases,
in particular the aggregate-oriented ones. In contrast, graph databases tend to support ACID
transactions just the same as relational databases. Secondly, aggregate-oriented databases
do support atomic updates, but only within a single aggregate. This means that you will
have logical consistency within an aggregate but not between aggregates.
Not all data can be put in the same aggregate, so any update that affects multiple aggregates
leaves open a time when clients could perform an inconsistent read. The length of time an
inconsistency is present is called the inconsistency window. A NoSQL system may have a
quite short inconsistency window.
Replication consistency refers to the degree to which data remains consistent across
multiple replicas in a distributed database system. In architectures utilizing replication—
whether master-slave, peer-to-peer, or multi-master—ensuring that all copies of the data
reflect the same state is crucial for maintaining data integrity and application reliability. An
example for the replication consistency is depicted in the following figure:
With replication, there can be eventually consistency, meaning that at any time nodes
may have replication inconsistencies but, if there are no further updates, eventually all
nodes will be updated to the same value.
One can tolerate reasonably long inconsistency windows, but you need read your-writes
consistency which means that, once you’ve made an update, you’re guaranteed to continue
seeing that update.
One way to get this in an otherwise eventually consistent system is to provide session
consistency: Within a user’s session there is read-your-writes consistency. This does mean
that the user may lose that consistency should their session end for some reason or should
the user access the same system simultaneously from different computers, but these cases
are relatively rare.
There are a couple of techniques to provide session consistency. A common way, and often
the easiest way, is to have a sticky session: a session that’s tied to one node (this is also
called session affinity).
A sticky session allows you to ensure that as long as you keep read-your-writes consistency
on a node, you’ll get it for sessions too. The downside is that sticky sessions reduce the
ability of the load balancer to do its job.
Relaxing consistency
Relaxing consistency refers to the practice of intentionally allowing some degree of
inconsistency in a distributed database system to improve performance, availability, and
scalability.
In traditional database systems, strong consistency is often enforced, ensuring that all
replicas reflect the same state at all times.
However, in distributed environments, maintaining this strict consistency can introduce
latency and bottlenecks, particularly during network partitions or high-traffic scenarios. By
relaxing consistency, systems can achieve greater responsiveness and fault tolerance while
still meeting the needs of many applications.
CAP Theorem
The CAP theorem, also known as Brewer's theorem, is a fundamental principle in distributed
systems that states that it is impossible for a distributed data store to simultaneously provide
all three of the following guarantees:
1. Consistency (C)
Consistency ensures that every read operation returns the most recent write for a given piece
of data. In other words, all nodes in the distributed system view the same data at the same time.
2. Availability (A)
Availability guarantees that every request to the system receives a response, regardless of
whether it is successful or contains the latest data. This means that the system is operational
and accessible, even if some nodes are down or unreachable
3. Partition Tolerance (P)
Partition tolerance ensures that the system continues to operate even in the presence of network
partitions, where communication between some nodes is lost. In a distributed environment,
network failures can occur, and partition tolerance guarantees that the system can still function,
allowing for either consistent or available responses.
According to the CAP theorem, a distributed database can only achieve two of the three
guarantees at any given time:
• CP (Consistency and Partition Tolerance): Systems that prioritize consistency and
partition tolerance may sacrifice availability during network partitions. An example is
a system that returns errors or unavailable responses if it cannot ensure that all nodes
are consistent.
• AP (Availability and Partition Tolerance): Systems that focus on availability and
partition tolerance may allow for eventual consistency, meaning that some reads may
return stale data while the system continues to operate. Examples include systems like
Cassandra and DynamoDB, which prioritize availability.
• CA (Consistency and Availability): It is impossible to achieve consistency and
availability in the presence of network partitions. Systems that claim to provide both
will inevitably fail during network issues.
CAP theorem can be summarized with following figure:
Relaxing durability
Durability is a key property of database systems that ensures once a transaction has
been committed, it remains permanently recorded in the system, even in the event of a
failure such as a power outage or system crash. This property is typically achieved
through mechanisms like write-ahead logging and data replication, which safeguard the
integrity and availability of data.
If a database can run mostly in memory, apply updates to its in-memory representation,
and periodically flush changes to disk, then it may be able to provide substantially
higher responsiveness to requests.
A big website may have many users and keep temporary information about what each
user is doing in some kind of session state. There’s a lot of activity on this state, creating
lots of demand, which affects the responsiveness of the website.
Another example of relaxing durability is capturing telemetric data from physical
devices. It may be that you’d rather capture data at a faster rate, at the cost of missing
the last updates should the server go down.
Replication durability refers to the assurance that data changes made in a distributed
system will persist even in the face of failures, thanks to the mechanisms in place to
replicate data across multiple nodes or servers.
Quorums
A quorum is a minimum number of votes or acknowledgments required from nodes in a
distributed system to consider a read or write operation valid and successful. This mechanism
helps ensure consistency and availability in the face of network partitions or node failures.
There are typically two types of quorums used in distributed systems:
• Write Quorum (W): The minimum number of replicas that must acknowledge a write
operation before it is considered successful. This ensures that the data is sufficiently
replicated across the system to maintain consistency.
• Read Quorum (R): The minimum number of replicas that must be accessed for a read
operation to return a valid result. This ensures that the read operation reflects the most
recent write, maintaining consistency from the user’s perspective.
This relationship between the number of nodes you need to contact for a read (R), those
confirming a write (W), and the replication factor (N) can be captured in an inequality:
W>N/2
R+W>N
There are various ways you can construct your version stamps. You can use a counter,
always incrementing it when you update the resource. Counters are useful since they make
it easy to tell if one version is more recent than another. On the other hand, they require the
server to generate the counter value, and also need a single master to ensure the counters
aren’t duplicated.
Another approach is to create a GUID, a large random number that’s guaranteed to be
unique. These use some combination of dates, hardware information, and whatever other
sources of randomness they can pick up. The nice thing about GUIDs is that they can be
generated by anyone and you’ll never get a duplicate; a disadvantage is that they are large
and can’t be compared directly for recentness.
A third approach is to make a hash of the contents of the resource. With a big enough hash
key size, a content hash can be globally unique like a GUID and can also be generated by
anyone; the advantage is that they are deterministic—any node will generate the same
content hash for same resource data. However, like GUIDs they can’t be directly compared
for recentness, and they can be lengthy.
A fourth approach is to use the timestamp of the last update. Like counters, they are
reasonably short and can be directly compared for recentness, yet have the advantage of
not needing a single master. Multiple machines can generate timestamps—but to work
properly, their clocks have to be kept in sync. One node with a bad clock can cause all sorts
of data corruptions.
Module-3 Notes
Map-Reduce
MapReduce is a programming model and processing framework for distributed computing,
invented by Google, that allows for processing large data sets with a distributed algorithm on
a cluster. The MapReduce framework operates primarily in two phases: the Map phase and the
Reduce phase.
Map Function: The Map function processes a key/value pair to generate a set of
intermediate key/value pairs. It is applied to each input record in parallel.
Reduce Function: The Reduce function merges all intermediate values associated with
the same intermediate key. It processes each group of intermediate key/value pairs to
generate the final output.
Workflow:
1. Splitting: The input data is split into manageable chunks, usually by a distributed file
system like Hadoop Distributed File System (HDFS).
2. Mapping: The Map function is applied to each chunk, generating intermediate
key/value pairs.
3. Shuffling and Sorting: The framework sorts and groups the intermediate data by key.
4. Reducing: The Reduce function is applied to each group of intermediate data to
produce the final output.
5. Output: The final results are written to an output store, which can be HDFS or another
distributed storage system.
Basic Map-Reduce
The first stage in a map-reduce job is the map. A map is a function whose input is a single
aggregate and whose output is a bunch of keyvalue pairs. In this case, the input would be an
order. The output would be key-value pairs corresponding to the line items. Each one would
have the product ID as the key and an embedded map with the quantity and price as the values
A map function reads records from the database and emits key-value pairs
A reduce function takes several key-value pairs with the same key and aggregates them
into one.
A combiner function is, in essence, a reducer function—indeed, in many cases the same
function can be used for combining as the final reduction. The reduce function needs a special
shape for this to work: Its output must match its input. We call such a function a combinable
reducer.
This reduce function, which counts how many unique customers order a particular tea, is not
combinable
When calculating averages, the sum and count can be combined in the reduce calculation, but
the average must be calculated from the combined sum and count.
When making a count, each map emits 1, which can be summed to get a total
A calculation broken down into two map-reduce steps, which will be expanded in the
next three figures
The second stage mapper creates base records for year-on-year comparisons.
Incremental Map-Reduce
Incremental MapReduce is an extension of the traditional MapReduce framework
designed to handle dynamic data by processing only the changes in the dataset rather
than reprocessing the entire dataset.
This approach is particularly useful for applications where data is continuously updated,
such as in real-time analytics or iterative machine learning algorithms.
In incremental MapReduce, the framework identifies and processes only the newly
added or modified data since the last computation.
The map function processes these changes to generate updated intermediate key/value
pairs, while the reduce function merges these updates with the previously computed
results.
By focusing on the incremental changes, this method significantly reduces
computational overhead and improves efficiency, allowing for faster and more
responsive data processing.
This approach also enhances resource utilization and minimizes latency, making it ideal
for applications requiring frequent updates and real-time insights.
The map stages of a map-reduce are easy to handle incrementally—only if the input
data changes does the mapper need to be rerun. Since maps are isolated from each other,
incremental updates are straightforward.
Key-Value Databases
Key-value databases are a type of NoSQL database that store data as a collection of key-
value pairs, where each key is unique and directly associated with a specific value. This
simple and flexible data model allows for high performance and scalability, making key-
value databases ideal for applications that require fast read and write operations, such as
caching, session management, and real-time analytics. The value in a key-value database
can be any type of data, ranging from simple strings to complex objects like JSON or binary
data. Operations are performed using keys, which enables quick retrieval of values without
the need for complex query language or table scans. Examples of key-value databases
include Redis, DynamoDB, and Riak. These databases are particularly well-suited for
distributed systems, as they can easily scale horizontally by partitioning data across
multiple nodes, ensuring high availability and fault tolerance.
Eg:
High Performance: Due to their simple structure, key-value stores can achieve high
read and write performance. Operations like get, put, and delete are optimized for speed,
making these databases ideal for applications that require rapid data access.
Scalability: Key-value stores are designed to scale horizontally by adding more nodes
to the system. Data can be partitioned and distributed across multiple nodes, allowing
the database to handle large volumes of data and high traffic loads efficiently.
Flexibility: Values in key-value stores can be of various types, including strings,
numbers, JSON objects, or binary data. This flexibility allows developers to store a
wide range of data types without the constraints of a rigid schema.
Distributed Architecture: Many key-value stores are built to operate in distributed
environments. They provide mechanisms for data replication and partitioning, ensuring
high availability and fault tolerance.
In-Memory Capabilities: Some key-value stores, like Redis, offer in-memory data
storage, which significantly boosts read and write speeds by keeping data in RAM. This
is particularly useful for caching and real-time applications.
Eventual Consistency: Key-value stores often adopt an eventual consistency model,
where updates to the data are propagated to all nodes over time. This model provides a
good balance between consistency and availability in distributed systems.
Replication and High Availability: Key-value stores often support data replication
across multiple nodes to ensure data durability and high availability. This replication
can be synchronous or asynchronous, depending on the desired consistency level.
Support for Large Data Volumes: Key-value stores can handle large amounts of data
by distributing it across multiple nodes. This capability makes them suitable for big
data applications.
Schema-Free: Key-value stores do not require a fixed schema, allowing for dynamic
and flexible data models. Developers can easily add or modify data without schema
migrations.
Deleting objects:
Scaling in Riak
Riak is designed to scale horizontally by adding more nodes to a cluster, allowing it to
handle large volumes of data and increasing traffic efficiently. Data is automatically
distributed across the cluster using consistent hashing and virtual nodes (vnodes), which
ensures even distribution and balancing of data. Each node in the cluster manages
multiple vnodes, and when new nodes are added, Riak automatically redistributes data
to maintain balance. This horizontal scalability allows Riak to grow seamlessly without
affecting the performance or availability of the system. The cluster's data is replicated
across multiple nodes for redundancy, and the replication factor can be adjusted based
on the desired level of fault tolerance. Riak's eventual consistency model ensures that
updates to data are eventually propagated to all replicas, while vector clocks help
resolve conflicts that might occur due to network partitions or concurrent writes.
Riak also provides fault tolerance and automatic recovery mechanisms. If a node fails,
the system continues to serve data from other replicas, ensuring high availability. Riak
can re-replicate data to restore the desired replication factor after a failure. Additionally,
the system is optimized for both read and write scalability. Write-heavy workloads
benefit from Riak's ability to handle concurrent writes across multiple nodes, while
read-heavy applications can take advantage of Riak’s ability to read from any replica,
reducing latency. Through its quorum-based reads and writes, Riak offers flexibility in
balancing consistency and availability, depending on the application's needs. This
makes Riak a robust and scalable solution for large-scale distributed applications.
User Profiles, Preferences- Storing user profiles and preferences in a key-value store
is an effective way to manage and quickly retrieve personalized data for users in web
or mobile applications. In this model, each user profile can be stored with a unique key,
typically a user ID, and the corresponding value contains the user’s preferences,
settings, and other personalized data in the form of a structured format like JSON or
serialized objects. This approach allows for fast access to user-specific information, as
the key-value store is optimized for quick lookups by key. Key-value stores also scale
easily to handle millions of users and can store complex, nested data such as theme
preferences, language settings, or recently viewed items. Additionally, the flexibility of
key-value stores means that as user preferences evolve, new attributes can be added
without a rigid schema, providing adaptability in dynamic applications. The ability to
quickly retrieve and update these profiles makes key-value stores an excellent choice
for personalized experiences, particularly when performance and scalability are
essential.
Shopping Cart Data-Using a key-value store to manage shopping cart data is a popular
approach due to its simplicity and high performance. In this model, each shopping cart
is associated with a unique key, typically a user session ID or user ID, and the value
contains the items added to the cart, along with relevant data such as quantities, product
IDs, prices, and any discounts or promotions. The key-value store allows for fast
retrieval and updates to the shopping cart, making it ideal for real-time applications
where users may add, remove, or modify items frequently. This design also scales
efficiently, as it can handle thousands or even millions of carts across distributed
systems. Many key-value stores support features like TTL (Time to Live), which can
automatically expire carts that remain inactive for a certain period, helping to manage
resources and prevent stale data. The flexibility of key-value stores also enables
seamless handling of cart data with varying structures, providing adaptability as the
shopping cart evolves with different user interactions and business requirements.
Query by Data-Key-value stores are not suitable for querying data based on non-key
attributes, as they are designed to retrieve values using a specific key. They do not
natively support complex queries or secondary indexes that allow for filtering, sorting,
or searching by data fields other than the key. In scenarios where you need to perform
queries like "find all users with a specific age" or "retrieve products within a certain
price range," key-value stores are limited because they lack built-in query mechanisms
or advanced indexing features. These types of queries typically require scanning
through all data or using additional processing, which can be inefficient and time-
consuming, especially as the data set grows. For use cases requiring flexible querying
capabilities, such as filtering, range queries, or joining data across different fields,
relational databases or document-based NoSQL systems with richer querying support
(e.g., MongoDB or SQL databases) are more appropriate.
Operations by Sets - Key-value stores are not well-suited for operations that involve
sets or collections of data, such as union, intersection, or difference, especially when
the data is spread across multiple keys. Since key-value databases store data as simple
key-value pairs, they lack native support for set operations or the ability to treat values
as collections that can be manipulated in bulk. For example, performing set-based
operations like finding common elements between two sets of data, or combining
multiple sets, would require manually retrieving and processing each key-value pair
individually, which can be inefficient and cumbersome. Furthermore, key-value stores
typically do not support advanced data types like lists or sets in the way that document-
oriented databases or graph databases do. For applications requiring frequent and
efficient set operations, such as collaborative filtering, tagging systems, or social
network analysis, databases that provide native support for sets or collections, such as
Redis (with its set data type) or graph databases, are more suitable.
Module-4 Notes
Document Databases
Document databases are a type of NoSQL database that store, retrieve, and manage data in the
form of documents, typically using formats like JSON, BSON, or XML. Unlike relational
databases that store data in tables with predefined schemas, document databases are schema-
less or have flexible schemas, allowing for the storage of varied data structures in the same
collection. Each document in a document database is a self-contained unit of data, and
documents can contain nested structures, such as arrays or sub-documents, making them highly
flexible and suitable for hierarchical or complex data. Popular document databases like
MongoDB and CouchDB enable fast querying, indexing, and scalability, and they support
features like full-text search, aggregation, and secondary indexing. Because of their flexible
schema and ability to scale horizontally across distributed systems, document databases are
ideal for applications with rapidly changing data structures, such as content management
systems, e-commerce platforms, and real-time analytics. Eg:
MongoDB
MongoDB is a popular, open-source, document-oriented NoSQL database designed for
scalability, flexibility, and high performance. It stores data in a JSON-like format called BSON
(Binary JSON), which allows for complex, nested structures that can evolve over time without
requiring a fixed schema. MongoDB supports horizontal scaling through sharding, where data
is distributed across multiple servers, ensuring high availability and the ability to handle large
datasets and high-traffic applications. Its powerful query language allows for efficient data
retrieval, including support for full-text search, aggregation, and geospatial queries. MongoDB
also provides features like automatic failover, replica sets for data redundancy, and ACID-
compliant transactions for reliable data consistency. Because of its flexibility and scalability,
MongoDB is widely used in applications ranging from real-time analytics and content
management systems to e-commerce platforms and mobile applications.
Availability in MongoDB
In MongoDB, replica sets are a group of MongoDB servers that maintain the same data set,
providing redundancy and high availability. A replica set consists of a primary node and one or
more secondary nodes. The primary node handles all write operations, while the secondary
nodes replicate the data from the primary. If the primary node fails, one of the secondary nodes
can be automatically promoted to become the new primary, ensuring that the database remains
available and minimizing downtime.
Eg:
Replica set configuration with higher priority assigned to nodes in the same datacenter
The application writes or reads from the primary (master) node. When connection is
established, the application only needs to connect to one node (primary or not, does not matter)
in the replica set, and the rest of the nodes are discovered automatically. When the primary
node goes down, the driver talks to the new primary elected by the replica set
[Link]([
{
title: "Post Title 2",
body: "Body of post.",
category: "Event",
likes: 2,
tags: ["news", "events"],
date: Date()
},
{
title: "Post Title 3",
body: "Body of post.",
category: "Technology",
likes: 3,
tags: ["news", "events"],
date: Date()
},
{
title: "Post Title 4",
body: "Body of post.",
category: "Event",
likes: 4,
tags: ["news", "events"],
date: Date()
}
])
//Retrieval operations
[Link]()
[Link]()
[Link]( {category: "News"} )
//Update operations
[Link]( { title: "Post Title 1" }, { $set: { likes: 2 } } )
[Link](
{ title: "Post Title 5" },
{
$set:
{
title: "Post Title 5",
body: "Body of post.",
category: "Event",
likes: 5,
tags: ["news", "events"],
date: Date()
}
},
{ upsert: true }
)
//Delete operations
[Link]({ title: "Post Title 5" })
[Link]({ category: "Technology" })
Module-4 (Contd..)
Availability
MongoDB offers several options to ensure high availability:
1. Replica Sets:
o Primary and Secondary Nodes: In a replica set, data is replicated across multiple
nodes. One node is the primary that receives all write operations, while
secondary nodes replicate the data from the primary and can serve read
operations.
o Automatic Failover: If the primary node goes down, an eligible secondary node
is automatically promoted to primary, ensuring that the database remains
available.
2. Sharding:
Replica-sets
A MongoDB replica set is a group of MongoDB instances that maintain the same data set,
providing redundancy and high availability. Here’s a detailed overview of how replica sets
work:
Components of a Replica Set
1. Primary Node:
o Handles all write operations.
o Only one primary per replica set.
o Clients can read from the primary.
2. Secondary Nodes:
o Replicate the data from the primary node.
o Can serve read operations if configured.
o If the primary fails, an eligible secondary is elected as the new primary.
3. Arbiter:
o Does not store data.
o Participates in elections to break ties but does not become a primary.
Key Features of Replica Sets
1. Data Replication:
o The primary node records all changes in the oplog (operations log).
o Secondary nodes replicate these changes asynchronously from the oplog to
maintain the same dataset.
2. Automatic Failover:
o If the primary node becomes unavailable, the replica set members initiate an
election process to elect a new primary from the secondary nodes.
The new primary is chosen based on factors like node priority and freshness of
o
data.
3. Read Preference:
o Clients can specify read preferences to read from the primary or secondary
nodes based on their requirements.
o Read preferences include: primary, primaryPreferred, secondary,
secondaryPreferred, and nearest.
4. Data Consistency:
o By default, MongoDB ensures strong consistency for writes by acknowledging
write operations only when the data is written to the primary.
o Read operations can be configured for eventual consistency by reading from
secondary nodes.
Working
The application writes or reads from the primary (master) node. When connection is
established, the application only needs to connect to one node (primary or not, does not matter)
in the replica set, and the rest of the nodes are discovered automatically. When the primary
node goes down, the driver talks to the new primary elected by the replica set. The application
does not have to manage any of the communication failures or node selection criteria. Using
replica sets gives you the ability to have a highly available document data store.
Scaling
Scaling in MongoDB is a critical process that ensures a database can handle increasing data
volumes, user traffic and processing demands. As applications grow, maintaining optimal
performance and resource utilization becomes essential.
The need for scaling in MongoDB arises due to below factors:
• Increased Data Volume: As the amount of data stored in a MongoDB database grows
the performance of queries and operations can be affected. Scaling allows to
distribution of this data across various servers or nodes and prevents poor performance.
• Rising User Traffic: Growing applications often experience a lot of users and
increased concurrent requests. Scaling ensures the database can handle a large number
of read and write operations and also maintains responsiveness and user experience.
• Diverse Workloads: Applications may implement various methods to support new
features or functionalities which lead to diverse workloads. Scaling allows
organizations to adapt to changing demands by optimizing the database system for
different types of queries and operations.
• Improved Fault Tolerance: Horizontal scaling in MongoDB is achieved through
sharding and enhances fault tolerance. By distributing data across multiple servers
(shards), the system can continue to operate even if one server fails. It
ensures high availability and reliability.
• Cost-Efficiency: Horizontal scaling, which involves adding more hardware as needed
and offers a cost-effective approach to handling increased workloads. Organizations
can scale incrementally based on demand, optimizing infrastructure costs.
Working
Scaling for heavy read-applications:
Scaling for heavy-read loads can be achieved by adding more read slaves, be directed to the
slaves. Given a heavy-read application, with our 3-node replica-set cluster, we can add more
read capacity to the cluster as the read load increases just by adding more slave nodes to the
replica set to execute reads.
When a new node is added, it will sync up with the existing nodes, join the replica set as
secondary node, and start serving read requests. An advantage of this setup is that we do not
have to restart any other nodes, and there is no downtime for the application either.
The shard key plays an important role. You may want to place your MongoDB database shards
closer to their users, so sharding based on user location may be a good idea. When sharding by
customer location, all user data for the East Coast of the USA is in the shards that are served
from the East Coast, and all user data for the West Coast is in the shards that are on the West
Coast.
providing timely insights and enabling dynamic dashboards and reports. The database's
scalability ensures that it can handle the growing data needs of analytics platforms,
while its integration with tools like Apache Kafka and Spark enhances its ability to
support complex data processing and analysis pipelines, making it a robust choice for
real-time and web analytics solutions.
Queries against Varying Aggregate Structure- MongoDB may not be the best choice
for scenarios requiring complex queries against varying aggregate structures due to its
document-oriented nature. While MongoDB supports aggregation through its powerful
aggregation framework, the flexibility of its schema can lead to inconsistent data
structures across documents, making it challenging to perform efficient queries or joins
on heterogeneous data. In cases where the data structure varies significantly between
records or requires frequent schema changes, querying and aggregating data can
become inefficient and complex. Relational databases or specialized analytics
platforms may be more suitable for handling complex queries against data with varying
aggregate structures, as they provide a more rigid and standardized schema for
optimized querying and aggregation.
Module-5
Graph Databases
Overview
Graph databases are specialized NoSQL databases that store data in the form of graphs,
consisting of nodes (entities) and edges (relationships). They are designed to efficiently handle
highly interconnected data, making them ideal for use cases where relationships between
entities are central, such as social networks, recommendation systems, and fraud detection.
Unlike traditional relational databases, graph databases explicitly define relationships,
allowing for fast traversal and querying of complex, dynamic networks. Their flexible schema
and ability to perform real-time queries on interconnected data make them a powerful tool for
applications that require deep relationship analysis and pattern recognition. Popular graph
databases include Neo4j, Amazon Neptune, and ArangoDB.
Example structure
Once we have a graph of these nodes and edges created, we can query the graph in many ways,
such as “get all nodes employed by Big Co that like NoSQL Distilled.” A query on the graph
is also known as traversing the graph. An advantage of the graph databases is that we can
change the traversing requirements without having to change the nodes or edges. If we want to
“get all nodes that like NoSQL Distilled,” we can do so without having to change the existing
data or the model of the database, because we can traverse the graph any way we like.
Features
Graph databases offer several key features that make them highly effective for handling
complex, interconnected data. Some of the main features include:
1. Efficient Relationship Handling: Graph databases are designed to handle
relationships between data points as first-class citizens, allowing for fast and efficient
querying of complex relationships, even when they involve multiple hops between
nodes.
2. Flexible Schema: Unlike relational databases, graph databases have a flexible schema
that allows nodes and edges to have varying structures, making them ideal for handling
dynamic and evolving data with changing relationships.
3. Real-Time Querying: Graph databases support real-time traversal and querying of
data, enabling quick insights into interconnected data. This is particularly beneficial for
use cases like social networks, fraud detection, and recommendation systems where
real-time performance is crucial.
This query finds the Movie nodes that are connected to the Person node representing Alice via
a LIKES relationship and returns the titles and release years of those movies.
This query deletes the LIKES relationship between Alice and "The Matrix" while leaving the
nodes intact.
Example: Delete a node (person) and ensure that related relationships are also deleted.
This query deletes the Person node for Alice and also deletes any relationships connected to
her.
1. Sharding Strategy
The first step in application-level sharding is deciding on a strategy to partition the graph data.
There are a few common approaches to this:
• Entity-based Sharding: The graph is divided by types of entities (e.g., Person, Movie,
Product). Each entity type or group of related entities is stored on different nodes or
databases.
o Example: All Person nodes might be stored on one database or server, and all
Movie nodes on another. Relationships like LIKES could then be stored across
these databases, requiring queries to traverse between them.
• Range-based Sharding: Data is partitioned based on specific ranges of values for node
properties (e.g., partitioning users by age, geographic location, or any other suitable
property). This is often more suitable for applications that can divide the graph based
on identifiable segments.
o Example: You might shard users in a social network by the first letter of their
name or by their geographical region.
• Graph Component Sharding: The graph is split based on connected components, where
each subgraph (which could be a community or cluster) is stored on a different shard.
This is particularly useful when the graph has distinct groups that don't share many
relationships across groups.
o Example: In a social network, one shard could contain all the users in a
particular community or group of users that interact frequently, while another
shard holds different groups.
2. Handling Relationships Between Shards
One of the challenges with graph sharding is how to manage relationships (edges) that span
multiple shards. To maintain performance, strategies need to be implemented to minimize
cross-shard traversal:
• Cross-shard Traversal: When relationships span multiple shards, the system must be
able to efficiently locate the related data. This can be done using techniques like storing
references or mappings between shards and routing queries to the appropriate shard.
o Example: If a query needs to traverse from a Person node on one shard to a
Movie node on another, the application can use metadata about shard locations
to fetch the relevant data.
• Query Routing and Aggregation: The application layer needs to be responsible for
query routing, ensuring that queries are sent to the appropriate shards based on the data
being requested. Additionally, results from multiple shards may need to be aggregated
to provide a unified response.
o Example: A recommendation system may need to gather data from multiple
shards to suggest friends based on mutual connections across different graph
partitions.
3. Replication for Availability and Fault Tolerance
To ensure high availability, graph databases can use replication. In a sharded setup, each shard
can have multiple replicas distributed across different servers, ensuring that if one server fails,
the data can still be accessed from another replica.
• Replica Sets: Similar to MongoDB’s replica sets, graph databases like Neo4j can have
replication mechanisms to provide fault tolerance and ensure that data is available even
if one server or shard fails.
4. Consistency Models
In a distributed graph database setup, maintaining consistency across shards can be tricky,
especially for graph-based operations that require ACID properties. Depending on the
application’s needs, different consistency models can be used:
• Eventual Consistency: In scenarios where strict consistency isn’t required for all
operations, eventual consistency can be used, allowing for faster writes and lower
overhead in managing consistency across distributed nodes.
• Strong Consistency: For applications that require strong consistency (e.g., financial
transactions or other critical operations), distributed transactions across shards must be
managed, ensuring data integrity and consistency.
5. Sharding in Graph Database Solutions
• Neo4j: Neo4j supports a form of sharding through its clustering features, allowing for
horizontal scaling by replicating graph data across different nodes. However, its
sharding capabilities are limited in terms of true application-level sharding and require
manual configuration to distribute data across nodes.
• Amazon Neptune: Amazon Neptune, a managed graph database service, allows scaling
through replication and partitioning. It automatically distributes data across multiple
nodes and shards, enabling high availability and performance in large-scale graph
applications.
By traversing this graph, the system can identify patterns like "users who bought this
product also bought" or "products similar to those the user has liked." Graph databases
allow for real-time, highly personalized recommendations based on the user’s past
behavior and the behaviors of similar users or products. This is particularly effective in
uncovering hidden relationships and offering more relevant, context-aware suggestions,
making them ideal for industries such as e-commerce, media streaming, and online
content platforms.