Added documentation for data modeling section.

Patch by Jeff Carpenter, Reviewed by Jon Haddad for CASSANDRA-15443.
This commit is contained in:
Jeffrey Carpenter 2019-12-20 09:56:47 -07:00 committed by Jon Haddad
parent 59d2c3c6e8
commit 2b52ce219e
22 changed files with 1101 additions and 4 deletions

View File

@ -28,6 +28,7 @@ Merged from 2.2:
* Fix SELECT JSON output for empty blobs (CASSANDRA-15435)
* In-JVM DTest: Set correct internode message version for upgrade test (CASSANDRA-15371)
* In-JVM DTest: Support NodeTool in dtest (CASSANDRA-15429)
* Added data modeling documentation (CASSANDRA-15443)
4.0-alpha2
* Fix SASI non-literal string comparisons (range operators) (CASSANDRA-15169)

View File

@ -40,7 +40,7 @@
<tr>
<td class="left-column">
<p class="biglink"><a class="biglink" href="{{ pathto("data_modeling/index") }}">{% trans %}Data Modeling{% endtrans %}</a><br/>
<span class="linkdescr">{% trans %}Or how to make square pegs fit round holes{% endtrans %}</span></p>
<span class="linkdescr">{% trans %}Hint: it's not relational{% endtrans %}</span></p>
</td>
<td class="right-column">
<p class="biglink"><a class="biglink" href="{{ pathto("troubleshooting/index") }}">{% trans %}Troubleshooting{% endtrans %}</a><br/>

View File

@ -0,0 +1,63 @@
.. Licensed to the Apache Software Foundation (ASF) under one
.. or more contributor license agreements. See the NOTICE file
.. distributed with this work for additional information
.. regarding copyright ownership. The ASF licenses this file
.. to you under the Apache License, Version 2.0 (the
.. "License"); you may not use this file except in compliance
.. with the License. You may obtain a copy of the License at
..
.. http://www.apache.org/licenses/LICENSE-2.0
..
.. Unless required by applicable law or agreed to in writing, software
.. distributed under the License is distributed on an "AS IS" BASIS,
.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
.. See the License for the specific language governing permissions and
.. limitations under the License.
.. conceptual_data_modeling
Conceptual Data Modeling
^^^^^^^^^^^^^^^^^^^^^^^^
First, lets create a simple domain model that is easy to understand in
the relational world, and then see how you might map it from a relational
to a distributed hashtable model in Cassandra.
Let's use an example that is complex enough
to show the various data structures and design patterns, but not
something that will bog you down with details. Also, a domain thats
familiar to everyone will allow you to concentrate on how to work with
Cassandra, not on what the application domain is all about.
For example, let's use a domain that is easily understood and that
everyone can relate to: making hotel reservations.
The conceptual domain includes hotels, guests that stay in the hotels, a
collection of rooms for each hotel, the rates and availability of those
rooms, and a record of reservations booked for guests. Hotels typically
also maintain a collection of “points of interest,” which are parks,
museums, shopping galleries, monuments, or other places near the hotel
that guests might want to visit during their stay. Both hotels and
points of interest need to maintain geolocation data so that they can be
found on maps for mashups, and to calculate distances.
The conceptual domain is depicted below using the entityrelationship
model popularized by Peter Chen. This simple diagram represents the
entities in the domain with rectangles, and attributes of those entities
with ovals. Attributes that represent unique identifiers for items are
underlined. Relationships between entities are represented as diamonds,
and the connectors between the relationship and each entity show the
multiplicity of the connection.
.. image:: images/data_modeling_hotel_erd.png
Obviously, in the real world, there would be many more considerations
and much more complexity. For example, hotel rates are notoriously
dynamic, and calculating them involves a wide array of factors. Here
youre defining something complex enough to be interesting and touch on
the important points, but simple enough to maintain the focus on
learning Cassandra.
*Material adapted from Cassandra, The Definitive Guide. Published by
O'Reilly Media, Inc. Copyright © 2020 Jeff Carpenter, Eben Hewitt.
All rights reserved. Used with permission.*

View File

@ -0,0 +1,219 @@
.. Licensed to the Apache Software Foundation (ASF) under one
.. or more contributor license agreements. See the NOTICE file
.. distributed with this work for additional information
.. regarding copyright ownership. The ASF licenses this file
.. to you under the Apache License, Version 2.0 (the
.. "License"); you may not use this file except in compliance
.. with the License. You may obtain a copy of the License at
..
.. http://www.apache.org/licenses/LICENSE-2.0
..
.. Unless required by applicable law or agreed to in writing, software
.. distributed under the License is distributed on an "AS IS" BASIS,
.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
.. See the License for the specific language governing permissions and
.. limitations under the License.
Logical Data Modeling
=====================
Now that you have defined your queries, youre ready to begin designing
Cassandra tables. First, create a logical model containing a table
for each query, capturing entities and relationships from the conceptual
model.
To name each table, youll identify the primary entity type for which you
are querying and use that to start the entity name. If you are querying
by attributes of other related entities, append those to the table
name, separated with ``_by_``. For example, ``hotels_by_poi``.
Next, you identify the primary key for the table, adding partition key
columns based on the required query attributes, and clustering columns
in order to guarantee uniqueness and support desired sort ordering.
The design of the primary key is extremely important, as it will
determine how much data will be stored in each partition and how that
data is organized on disk, which in turn will affect how quickly
Cassandra processes reads.
Complete each table by adding any additional attributes identified by
the query. If any of these additional attributes are the same for every
instance of the partition key, mark the column as static.
Now that was a pretty quick description of a fairly involved process, so
it will be worthwhile to work through a detailed example. First,
lets introduce a notation that you can use to represent logical
models.
Several individuals within the Cassandra community have proposed
notations for capturing data models in diagrammatic form. This document
uses a notation popularized by Artem Chebotko which provides a simple,
informative way to visualize the relationships between queries and
tables in your designs. This figure shows the Chebotko notation for a
logical data model.
.. image:: images/data_modeling_chebotko_logical.png
Each table is shown with its title and a list of columns. Primary key
columns are identified via symbols such as **K** for partition key
columns and **C**\ ↑ or **C**\ ↓ to represent clustering columns. Lines
are shown entering tables or between tables to indicate the queries that
each table is designed to support.
Hotel Logical Data Model
------------------------
The figure below shows a Chebotko logical data model for the queries
involving hotels, points of interest, rooms, and amenities. One thing you'll
notice immediately is that the Cassandra design doesnt include dedicated
tables for rooms or amenities, as you had in the relational design. This
is because the workflow didnt identify any queries requiring this
direct access.
.. image:: images/data_modeling_hotel_logical.png
Lets explore the details of each of these tables.
The first query Q1 is to find hotels near a point of interest, so youll
call this table ``hotels_by_poi``. Searching by a named point of
interest is a clue that the point of interest should be a part
of the primary key. Lets reference the point of interest by name,
because according to the workflow that is how users will start their
search.
Youll note that you certainly could have more than one hotel near a
given point of interest, so youll need another component in the primary
key in order to make sure you have a unique partition for each hotel. So
you add the hotel key as a clustering column.
An important consideration in designing your tables primary key is
making sure that it defines a unique data element. Otherwise you run the
risk of accidentally overwriting data.
Now for the second query (Q2), youll need a table to get information
about a specific hotel. One approach would have been to put all of the
attributes of a hotel in the ``hotels_by_poi`` table, but you added
only those attributes that were required by the application workflow.
From the workflow diagram, you know that the ``hotels_by_poi`` table is
used to display a list of hotels with basic information on each hotel,
and the application knows the unique identifiers of the hotels returned.
When the user selects a hotel to view details, you can then use Q2, which
is used to obtain details about the hotel. Because you already have the
``hotel_id`` from Q1, you use that as a reference to the hotel youre
looking for. Therefore the second table is just called ``hotels``.
Another option would have been to store a set of ``poi_names`` in the
hotels table. This is an equally valid approach. Youll learn through
experience which approach is best for your application.
Q3 is just a reverse of Q1—looking for points of interest near a hotel,
rather than hotels near a point of interest. This time, however, you need
to access the details of each point of interest, as represented by the
``pois_by_hotel`` table. As previously, you add the point of
interest name as a clustering key to guarantee uniqueness.
At this point, lets now consider how to support query Q4 to help the
user find available rooms at a selected hotel for the nights they are
interested in staying. Note that this query involves both a start date
and an end date. Because youre querying over a range instead of a single
date, you know that youll need to use the date as a clustering key.
Use the ``hotel_id`` as a primary key to group room data for each hotel
on a single partition, which should help searches be super fast. Lets
call this the ``available_rooms_by_hotel_date`` table.
To support searching over a range, use :ref:`clustering columns
<clustering-columns>` to store
attributes that you need to access in a range query. Remember that the
order of the clustering columns is important.
The design of the ``available_rooms_by_hotel_date`` table is an instance
of the **wide partition** pattern. This
pattern is sometimes called the **wide row** pattern when discussing
databases that support similar models, but wide partition is a more
accurate description from a Cassandra perspective. The essence of the
pattern is to group multiple related rows in a partition in order to
support fast access to multiple rows within the partition in a single
query.
In order to round out the shopping portion of the data model, add the
``amenities_by_room`` table to support Q5. This will allow users to
view the amenities of one of the rooms that is available for the desired
stay dates.
Reservation Logical Data Model
------------------------------
Now let's switch gears to look at the reservation queries. The figure
shows a logical data model for reservations. Youll notice that these
tables represent a denormalized design; the same data appears in
multiple tables, with differing keys.
.. image:: images/data_modeling_reservation_logical.png
In order to satisfy Q6, the ``reservations_by_guest`` table can be used
to look up the reservation by guest name. You could envision query Q7
being used on behalf of a guest on a self-serve website or a call center
agent trying to assist the guest. Because the guest name might not be
unique, you include the guest ID here as a clustering column as well.
Q8 and Q9 in particular help to remind you to create queries
that support various stakeholders of the application, not just customers
but staff as well, and perhaps even the analytics team, suppliers, and so
on.
The hotel staff might wish to see a record of upcoming reservations by
date in order to get insight into how the hotel is performing, such as
what dates the hotel is sold out or undersold. Q8 supports the retrieval
of reservations for a given hotel by date.
Finally, you create a ``guests`` table. This provides a single
location that used to store guest information. In this case, you specify a
separate unique identifier for guest records, as it is not uncommon
for guests to have the same name. In many organizations, a customer
database such as the ``guests`` table would be part of a separate
customer management application, which is why other guest
access patterns were omitted from the example.
Patterns and Anti-Patterns
--------------------------
As with other types of software design, there are some well-known
patterns and anti-patterns for data modeling in Cassandra. Youve already
used one of the most common patterns in this hotel model—the wide
partition pattern.
The **time series** pattern is an extension of the wide partition
pattern. In this pattern, a series of measurements at specific time
intervals are stored in a wide partition, where the measurement time is
used as part of the partition key. This pattern is frequently used in
domains including business analysis, sensor data management, and
scientific experiments.
The time series pattern is also useful for data other than measurements.
Consider the example of a banking application. You could store each
customers balance in a row, but that might lead to a lot of read and
write contention as various customers check their balance or make
transactions. Youd probably be tempted to wrap a transaction around
writes just to protect the balance from being updated in error. In
contrast, a time seriesstyle design would store each transaction as a
timestamped row and leave the work of calculating the current balance to
the application.
One design trap that many new users fall into is attempting to use
Cassandra as a queue. Each item in the queue is stored with a timestamp
in a wide partition. Items are appended to the end of the queue and read
from the front, being deleted after they are read. This is a design that
seems attractive, especially given its apparent similarity to the time
series pattern. The problem with this approach is that the deleted items
are now :ref:`tombstones <asynch-deletes>` that Cassandra must scan past
in order to read from the front of the queue. Over time, a growing number
of tombstones begins to degrade read performance.
The queue anti-pattern serves as a reminder that any design that relies
on the deletion of data is potentially a poorly performing design.
*Material adapted from Cassandra, The Definitive Guide. Published by
O'Reilly Media, Inc. Copyright © 2020 Jeff Carpenter, Eben Hewitt.
All rights reserved. Used with permission.*

View File

@ -0,0 +1,117 @@
.. Licensed to the Apache Software Foundation (ASF) under one
.. or more contributor license agreements. See the NOTICE file
.. distributed with this work for additional information
.. regarding copyright ownership. The ASF licenses this file
.. to you under the Apache License, Version 2.0 (the
.. "License"); you may not use this file except in compliance
.. with the License. You may obtain a copy of the License at
..
.. http://www.apache.org/licenses/LICENSE-2.0
..
.. Unless required by applicable law or agreed to in writing, software
.. distributed under the License is distributed on an "AS IS" BASIS,
.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
.. See the License for the specific language governing permissions and
.. limitations under the License.
Physical Data Modeling
======================
Once you have a logical data model defined, creating the physical model
is a relatively simple process.
You walk through each of the logical model tables, assigning types to
each item. You can use any valid :ref:`CQL data type <data-types>`,
including the basic types, collections, and user-defined types. You may
identify additional user-defined types that can be created to simplify
your design.
After youve assigned data types, you analyze the model by performing
size calculations and testing out how the model works. You may make some
adjustments based on your findings. Once again let's cover the data
modeling process in more detail by working through an example.
Before getting started, lets look at a few additions to the Chebotko
notation for physical data models. To draw physical models, you need to
be able to add the typing information for each column. This figure
shows the addition of a type for each column in a sample table.
.. image:: images/data_modeling_chebotko_physical.png
The figure includes a designation of the keyspace containing each table
and visual cues for columns represented using collections and
user-defined types. Note the designation of static columns and
secondary index columns. There is no restriction on assigning these as
part of a logical model, but they are typically more of a physical data
modeling concern.
Hotel Physical Data Model
-------------------------
Now lets get to work on the physical model. First, you need keyspaces
to contain the tables. To keep the design relatively simple, create a
``hotel`` keyspace to contain tables for hotel and availability
data, and a ``reservation`` keyspace to contain tables for reservation
and guest data. In a real system, you might divide the tables across even
more keyspaces in order to separate concerns.
For the ``hotels`` table, use Cassandras ``text`` type to
represent the hotels ``id``. For the address, create an
``address`` user defined type. Use the ``text`` type to represent the
phone number, as there is considerable variance in the formatting of
numbers between countries.
While it would make sense to use the ``uuid`` type for attributes such
as the ``hotel_id``, this document uses mostly ``text`` attributes as
identifiers, to keep the samples simple and readable. For example, a
common convention in the hospitality industry is to reference properties
by short codes like "AZ123" or "NY229". This example uses these values
for ``hotel_ids``, while acknowledging they are not necessarily globally
unique.
Youll find that its often helpful to use unique IDs to uniquely
reference elements, and to use these ``uuids`` as references in tables
representing other entities. This helps to minimize coupling between
different entity types. This may prove especially effective if you are
using a microservice architectural style for your application, in which
there are separate services responsible for each entity type.
As you work to create physical representations of various tables in the
logical hotel data model, you use the same approach. The resulting design
is shown in this figure:
.. image:: images/data_modeling_hotel_physical.png
Note that the ``address`` type is also included in the design. It
is designated with an asterisk to denote that it is a user-defined type,
and has no primary key columns identified. This type is used in
the ``hotels`` and ``hotels_by_poi`` tables.
User-defined types are frequently used to help reduce duplication of
non-primary key columns, as was done with the ``address``
user-defined type. This can reduce complexity in the design.
Remember that the scope of a UDT is the keyspace in which it is defined.
To use ``address`` in the ``reservation`` keyspace defined below
design, youll have to declare it again. This is just one of the many
trade-offs you have to make in data model design.
Reservation Physical Data Model
-------------------------------
Now, lets examine reservation tables in the design.
Remember that the logical model contained three denormalized tables to
support queries for reservations by confirmation number, guest, and
hotel and date. For the first iteration of your physical data model
design, assume you're going to manage this denormalization
manually. Note that this design could be revised to use Cassandras
(experimental) materialized view feature.
.. image:: images/data_modeling_reservation_physical.png
Note that the ``address`` type is reproduced in this keyspace and
``guest_id`` is modeled as a ``uuid`` type in all of the tables.
*Material adapted from Cassandra, The Definitive Guide. Published by
O'Reilly Media, Inc. Copyright © 2020 Jeff Carpenter, Eben Hewitt.
All rights reserved. Used with permission.*

View File

@ -0,0 +1,85 @@
.. Licensed to the Apache Software Foundation (ASF) under one
.. or more contributor license agreements. See the NOTICE file
.. distributed with this work for additional information
.. regarding copyright ownership. The ASF licenses this file
.. to you under the Apache License, Version 2.0 (the
.. "License"); you may not use this file except in compliance
.. with the License. You may obtain a copy of the License at
..
.. http://www.apache.org/licenses/LICENSE-2.0
..
.. Unless required by applicable law or agreed to in writing, software
.. distributed under the License is distributed on an "AS IS" BASIS,
.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
.. See the License for the specific language governing permissions and
.. limitations under the License.
Defining Application Queries
============================
Lets try the query-first approach to start designing the data model for
a hotel application. The user interface design for the application is
often a great artifact to use to begin identifying queries. Lets assume
that youve talked with the project stakeholders and your UX designers
have produced user interface designs or wireframes for the key use
cases. Youll likely have a list of shopping queries like the following:
- Q1. Find hotels near a given point of interest.
- Q2. Find information about a given hotel, such as its name and
location.
- Q3. Find points of interest near a given hotel.
- Q4. Find an available room in a given date range.
- Q5. Find the rate and amenities for a room.
It is often helpful to be able to refer
to queries by a shorthand number rather that explaining them in full.
The queries listed here are numbered Q1, Q2, and so on, which is how they
are referenced in diagrams throughout the example.
Now if the application is to be a success, youll certainly want
customers to be able to book reservations at hotels. This includes
steps such as selecting an available room and entering their guest
information. So clearly you will also need some queries that address the
reservation and guest entities from the conceptual data model. Even
here, however, youll want to think not only from the customer
perspective in terms of how the data is written, but also in terms of
how the data will be queried by downstream use cases.
You natural tendency as might be to focus first on
designing the tables to store reservation and guest records, and only
then start thinking about the queries that would access them. You may
have felt a similar tension already when discussing the
shopping queries before, thinking “but where did the hotel and point of
interest data come from?” Dont worry, you will see soon enough.
Here are some queries that describe how users will access
reservations:
- Q6. Lookup a reservation by confirmation number.
- Q7. Lookup a reservation by hotel, date, and guest name.
- Q8. Lookup all reservations by guest name.
- Q9. View guest details.
All of the queries are shown in the context of the workflow of the
application in the figure below. Each box on the diagram represents a
step in the application workflow, with arrows indicating the flows
between steps and the associated query. If youve modeled the application
well, each step of the workflow accomplishes a task that “unlocks”
subsequent steps. For example, the “View hotels near POI” task helps
the application learn about several hotels, including their unique keys.
The key for a selected hotel may be used as part of Q2, in order to
obtain detailed description of the hotel. The act of booking a room
creates a reservation record that may be accessed by the guest and
hotel staff at a later time through various additional queries.
.. image:: images/data_modeling_hotel_queries.png
*Material adapted from Cassandra, The Definitive Guide. Published by
O'Reilly Media, Inc. Copyright © 2020 Jeff Carpenter, Eben Hewitt.
All rights reserved. Used with permission.*

View File

@ -0,0 +1,171 @@
.. Licensed to the Apache Software Foundation (ASF) under one
.. or more contributor license agreements. See the NOTICE file
.. distributed with this work for additional information
.. regarding copyright ownership. The ASF licenses this file
.. to you under the Apache License, Version 2.0 (the
.. "License"); you may not use this file except in compliance
.. with the License. You may obtain a copy of the License at
..
.. http://www.apache.org/licenses/LICENSE-2.0
..
.. Unless required by applicable law or agreed to in writing, software
.. distributed under the License is distributed on an "AS IS" BASIS,
.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
.. See the License for the specific language governing permissions and
.. limitations under the License.
RDBMS Design
============
When you set out to build a new data-driven application that will use a
relational database, you might start by modeling the domain as a set of
properly normalized tables and use foreign keys to reference related
data in other tables.
The figure below shows how you might represent the data storage for your application
using a relational database model. The relational model includes a
couple of “join” tables in order to realize the many-to-many
relationships from the conceptual model of hotels-to-points of interest,
rooms-to-amenities, rooms-to-availability, and guests-to-rooms (via a
reservation).
.. image:: images/data_modeling_hotel_relational.png
.. design_differences_between_rdbms_and_cassandra
Design Differences Between RDBMS and Cassandra
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Lets take a minute to highlight some of the key differences in doing
ata modeling for Cassandra versus a relational database.
No joins
~~~~~~~~
You cannot perform joins in Cassandra. If you have designed a data model
and find that you need something like a join, youll have to either do
the work on the client side, or create a denormalized second table that
represents the join results for you. This latter option is preferred in
Cassandra data modeling. Performing joins on the client should be a very
rare case; you really want to duplicate (denormalize) the data instead.
No referential integrity
~~~~~~~~~~~~~~~~~~~~~~~~
Although Cassandra supports features such as lightweight transactions
and batches, Cassandra itself has no concept of referential integrity
across tables. In a relational database, you could specify foreign keys
in a table to reference the primary key of a record in another table.
But Cassandra does not enforce this. It is still a common design
requirement to store IDs related to other entities in your tables, but
operations such as cascading deletes are not available.
Denormalization
~~~~~~~~~~~~~~~
In relational database design, you are often taught the importance of
normalization. This is not an advantage when working with Cassandra
because it performs best when the data model is denormalized. It is
often the case that companies end up denormalizing data in relational
databases as well. There are two common reasons for this. One is
performance. Companies simply cant get the performance they need when
they have to do so many joins on years worth of data, so they
denormalize along the lines of known queries. This ends up working, but
goes against the grain of how relational databases are intended to be
designed, and ultimately makes one question whether using a relational
database is the best approach in these circumstances.
A second reason that relational databases get denormalized on purpose is
a business document structure that requires retention. That is, you have
an enclosing table that refers to a lot of external tables whose data
could change over time, but you need to preserve the enclosing document
as a snapshot in history. The common example here is with invoices. You
already have customer and product tables, and youd think that you could
just make an invoice that refers to those tables. But this should never
be done in practice. Customer or price information could change, and
then you would lose the integrity of the invoice document as it was on
the invoice date, which could violate audits, reports, or laws, and
cause other problems.
In the relational world, denormalization violates Codds normal forms,
and you try to avoid it. But in Cassandra, denormalization is, well,
perfectly normal. Its not required if your data model is simple. But
dont be afraid of it.
Historically, denormalization in Cassandra has required designing and
managing multiple tables using techniques described in this documentation.
Beginning with the 3.0 release, Cassandra provides a feature known
as :ref:`materialized views <materialized-views>`
which allows you to create multiple denormalized
views of data based on a base table design. Cassandra manages
materialized views on the server, including the work of keeping the
views in sync with the table.
Query-first design
~~~~~~~~~~~~~~~~~~
Relational modeling, in simple terms, means that you start from the
conceptual domain and then represent the nouns in the domain in tables.
You then assign primary keys and foreign keys to model relationships.
When you have a many-to-many relationship, you create the join tables
that represent just those keys. The join tables dont exist in the real
world, and are a necessary side effect of the way relational models
work. After you have all your tables laid out, you can start writing
queries that pull together disparate data using the relationships
defined by the keys. The queries in the relational world are very much
secondary. It is assumed that you can always get the data you want as
long as you have your tables modeled properly. Even if you have to use
several complex subqueries or join statements, this is usually true.
By contrast, in Cassandra you dont start with the data model; you start
with the query model. Instead of modeling the data first and then
writing queries, with Cassandra you model the queries and let the data
be organized around them. Think of the most common query paths your
application will use, and then create the tables that you need to
support them.
Detractors have suggested that designing the queries first is overly
constraining on application design, not to mention database modeling.
But it is perfectly reasonable to expect that you should think hard
about the queries in your application, just as you would, presumably,
think hard about your relational domain. You may get it wrong, and then
youll have problems in either world. Or your query needs might change
over time, and then youll have to work to update your data set. But
this is no different from defining the wrong tables, or needing
additional tables, in an RDBMS.
Designing for optimal storage
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In a relational database, it is frequently transparent to the user how
tables are stored on disk, and it is rare to hear of recommendations
about data modeling based on how the RDBMS might store tables on disk.
However, that is an important consideration in Cassandra. Because
Cassandra tables are each stored in separate files on disk, its
important to keep related columns defined together in the same table.
A key goal that you will see as you begin creating data models in
Cassandra is to minimize the number of partitions that must be searched
in order to satisfy a given query. Because the partition is a unit of
storage that does not get divided across nodes, a query that searches a
single partition will typically yield the best performance.
Sorting is a design decision
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In an RDBMS, you can easily change the order in which records are
returned to you by using ``ORDER BY`` in your query. The default sort
order is not configurable; by default, records are returned in the order
in which they are written. If you want to change the order, you just
modify your query, and you can sort by any list of columns.
In Cassandra, however, sorting is treated differently; it is a design
decision. The sort order available on queries is fixed, and is
determined entirely by the selection of clustering columns you supply in
the ``CREATE TABLE`` command. The CQL ``SELECT`` statement does support
``ORDER BY`` semantics, but only in the order specified by the
clustering columns.
*Material adapted from Cassandra, The Definitive Guide. Published by
O'Reilly Media, Inc. Copyright © 2020 Jeff Carpenter, Eben Hewitt.
All rights reserved. Used with permission.*

View File

@ -0,0 +1,218 @@
.. Licensed to the Apache Software Foundation (ASF) under one
.. or more contributor license agreements. See the NOTICE file
.. distributed with this work for additional information
.. regarding copyright ownership. The ASF licenses this file
.. to you under the Apache License, Version 2.0 (the
.. "License"); you may not use this file except in compliance
.. with the License. You may obtain a copy of the License at
..
.. http://www.apache.org/licenses/LICENSE-2.0
..
.. Unless required by applicable law or agreed to in writing, software
.. distributed under the License is distributed on an "AS IS" BASIS,
.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
.. See the License for the specific language governing permissions and
.. limitations under the License.
.. role:: raw-latex(raw)
:format: latex
..
Evaluating and Refining Data Models
===================================
Once youve created a physical model, there are some steps youll want
to take to evaluate and refine table designs to help ensure optimal
performance.
Calculating Partition Size
--------------------------
The first thing that you want to look for is whether your tables will have
partitions that will be overly large, or to put it another way, too
wide. Partition size is measured by the number of cells (values) that
are stored in the partition. Cassandras hard limit is 2 billion cells
per partition, but youll likely run into performance issues before
reaching that limit.
In order to calculate the size of partitions, use the following
formula:
.. math:: N_v = N_r (N_c - N_{pk} - N_s) + N_s
The number of values (or cells) in the partition (N\ :sub:`v`) is equal to
the number of static columns (N\ :sub:`s`) plus the product of the number
of rows (N\ :sub:`r`) and the number of of values per row. The number of
values per row is defined as the number of columns (N\ :sub:`c`) minus the
number of primary key columns (N\ :sub:`pk`) and static columns
(N\ :sub:`s`).
The number of columns tends to be relatively static, although it
is possible to alter tables at runtime. For this reason, a
primary driver of partition size is the number of rows in the partition.
This is a key factor that you must consider in determining whether a
partition has the potential to get too large. Two billion values sounds
like a lot, but in a sensor system where tens or hundreds of values are
measured every millisecond, the number of values starts to add up pretty
fast.
Lets take a look at one of the tables to analyze the partition size.
Because it has a wide partition design with one partition per hotel,
look at the ``available_rooms_by_hotel_date`` table. The table has
four columns total (N\ :sub:`c` = 4), including three primary key columns
(N\ :sub:`pk` = 3) and no static columns (N\ :sub:`s` = 0). Plugging these
values into the formula, the result is:
.. math:: N_v = N_r (4 - 3 - 0) + 0 = 1N_r
Therefore the number of values for this table is equal to the number of
rows. You still need to determine a number of rows. To do this, make
estimates based on the application design. The table is
storing a record for each room, in each of hotel, for every night.
Let's assume the system will be used to store two years of
inventory at a time, and there are 5,000 hotels in the system, with an
average of 100 rooms in each hotel.
Since there is a partition for each hotel, the estimated number of rows
per partition is as follows:
.. math:: N_r = 100 rooms/hotel \times 730 days = 73,000 rows
This relatively small number of rows per partition is not going to get
you in too much trouble, but if you start storing more dates of inventory,
or dont manage the size of the inventory well using TTL, you could start
having issues. You still might want to look at breaking up this large
partition, which you'll see how to do shortly.
When performing sizing calculations, it is tempting to assume the
nominal or average case for variables such as the number of rows.
Consider calculating the worst case as well, as these sorts of
predictions have a way of coming true in successful systems.
Calculating Size on Disk
------------------------
In addition to calculating the size of a partition, it is also an
excellent idea to estimate the amount of disk space that will be
required for each table you plan to store in the cluster. In order to
determine the size, use the following formula to determine the size
S\ :sub:`t` of a partition:
.. math:: S_t = \displaystyle\sum_i sizeOf\big (c_{k_i}\big) + \displaystyle\sum_j sizeOf\big(c_{s_j}\big) + N_r\times \bigg(\displaystyle\sum_k sizeOf\big(c_{r_k}\big) + \displaystyle\sum_l sizeOf\big(c_{c_l}\big)\bigg) +
.. math:: N_v\times sizeOf\big(t_{avg}\big)
This is a bit more complex than the previous formula, but let's break it
down a bit at a time. Lets take a look at the notation first:
- In this formula, c\ :sub:`k` refers to partition key columns,
c\ :sub:`s` to static columns, c\ :sub:`r` to regular columns, and
c\ :sub:`c` to clustering columns.
- The term t\ :sub:`avg` refers to the average number of bytes of
metadata stored per cell, such as timestamps. It is typical to use an
estimate of 8 bytes for this value.
- You'll recognize the number of rows N\ :sub:`r` and number of values
N\ :sub:`v` from previous calculations.
- The **sizeOf()** function refers to the size in bytes of the CQL data
type of each referenced column.
The first term asks you to sum the size of the partition key columns. For
this example, the ``available_rooms_by_hotel_date`` table has a single
partition key column, the ``hotel_id``, which is of type
``text``. Assuming that hotel identifiers are simple 5-character codes,
you have a 5-byte value, so the sum of the partition key column sizes is
5 bytes.
The second term asks you to sum the size of the static columns. This table
has no static columns, so the size is 0 bytes.
The third term is the most involved, and for good reason—it is
calculating the size of the cells in the partition. Sum the size of
the clustering columns and regular columns. The two clustering columns
are the ``date``, which is 4 bytes, and the ``room_number``,
which is a 2-byte short integer, giving a sum of 6 bytes.
There is only a single regular column, the boolean ``is_available``,
which is 1 byte in size. Summing the regular column size
(1 byte) plus the clustering column size (6 bytes) gives a total of 7
bytes. To finish up the term, multiply this value by the number of
rows (73,000), giving a result of 511,000 bytes (0.51 MB).
The fourth term is simply counting the metadata that that Cassandra
stores for each cell. In the storage format used by Cassandra 3.0 and
later, the amount of metadata for a given cell varies based on the type
of data being stored, and whether or not custom timestamp or TTL values
are specified for individual cells. For this table, reuse the number
of values from the previous calculation (73,000) and multiply by 8,
which gives 0.58 MB.
Adding these terms together, you get a final estimate:
.. math:: Partition size = 16 bytes + 0 bytes + 0.51 MB + 0.58 MB = 1.1 MB
This formula is an approximation of the actual size of a partition on
disk, but is accurate enough to be quite useful. Remembering that the
partition must be able to fit on a single node, it looks like the table
design will not put a lot of strain on disk storage.
Cassandras storage engine was re-implemented for the 3.0 release,
including a new format for SSTable files. The previous format stored a
separate copy of the clustering columns as part of the record for each
cell. The newer format eliminates this duplication, which reduces the
size of stored data and simplifies the formula for computing that size.
Keep in mind also that this estimate only counts a single replica of
data. You will need to multiply the value obtained here by the number of
partitions and the number of replicas specified by the keyspaces
replication strategy in order to determine the total required total
capacity for each table. This will come in handy when you
plan your cluster.
Breaking Up Large Partitions
----------------------------
As discussed previously, the goal is to design tables that can provide
the data you need with queries that touch a single partition, or failing
that, the minimum possible number of partitions. However, as shown in
the examples, it is quite possible to design wide
partition-style tables that approach Cassandras built-in limits.
Performing sizing analysis on tables may reveal partitions that are
potentially too large, either in number of values, size on disk, or
both.
The technique for splitting a large partition is straightforward: add an
additional column to the partition key. In most cases, moving one of the
existing columns into the partition key will be sufficient. Another
option is to introduce an additional column to the table to act as a
sharding key, but this requires additional application logic.
Continuing to examine the available rooms example, if you add the ``date``
column to the partition key for the ``available_rooms_by_hotel_date``
table, each partition would then represent the availability of rooms
at a specific hotel on a specific date. This will certainly yield
partitions that are significantly smaller, perhaps too small, as the
data for consecutive days will likely be on separate nodes.
Another technique known as **bucketing** is often used to break the data
into moderate-size partitions. For example, you could bucketize the
``available_rooms_by_hotel_date`` table by adding a ``month`` column to
the partition key, perhaps represented as an integer. The comparision
with the original design is shown in the figure below. While the
``month`` column is partially duplicative of the ``date``, it provides
a nice way of grouping related data in a partition that will not get
too large.
.. image:: images/data_modeling_hotel_bucketing.png
If you really felt strongly about preserving a wide partition design, you
could instead add the ``room_id`` to the partition key, so that each
partition would represent the availability of the room across all
dates. Because there was no query identified that involves searching
availability of a specific room, the first or second design approach
is most suitable to the application needs.
*Material adapted from Cassandra, The Definitive Guide. Published by
O'Reilly Media, Inc. Copyright © 2020 Jeff Carpenter, Eben Hewitt.
All rights reserved. Used with permission.*

View File

@ -0,0 +1,144 @@
.. Licensed to the Apache Software Foundation (ASF) under one
.. or more contributor license agreements. See the NOTICE file
.. distributed with this work for additional information
.. regarding copyright ownership. The ASF licenses this file
.. to you under the Apache License, Version 2.0 (the
.. "License"); you may not use this file except in compliance
.. with the License. You may obtain a copy of the License at
..
.. http://www.apache.org/licenses/LICENSE-2.0
..
.. Unless required by applicable law or agreed to in writing, software
.. distributed under the License is distributed on an "AS IS" BASIS,
.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
.. See the License for the specific language governing permissions and
.. limitations under the License.
.. highlight:: cql
Defining Database Schema
========================
Once you have finished evaluating and refining the physical model, youre
ready to implement the schema in CQL. Here is the schema for the
``hotel`` keyspace, using CQLs comment feature to document the query
pattern supported by each table::
CREATE KEYSPACE hotel WITH replication =
{class: SimpleStrategy, replication_factor : 3};
CREATE TYPE hotel.address (
street text,
city text,
state_or_province text,
postal_code text,
country text );
CREATE TABLE hotel.hotels_by_poi (
poi_name text,
hotel_id text,
name text,
phone text,
address frozen<address>,
PRIMARY KEY ((poi_name), hotel_id) )
WITH comment = Q1. Find hotels near given poi
AND CLUSTERING ORDER BY (hotel_id ASC) ;
CREATE TABLE hotel.hotels (
id text PRIMARY KEY,
name text,
phone text,
address frozen<address>,
pois set )
WITH comment = Q2. Find information about a hotel;
CREATE TABLE hotel.pois_by_hotel (
poi_name text,
hotel_id text,
description text,
PRIMARY KEY ((hotel_id), poi_name) )
WITH comment = Q3. Find pois near a hotel;
CREATE TABLE hotel.available_rooms_by_hotel_date (
hotel_id text,
date date,
room_number smallint,
is_available boolean,
PRIMARY KEY ((hotel_id), date, room_number) )
WITH comment = Q4. Find available rooms by hotel date;
CREATE TABLE hotel.amenities_by_room (
hotel_id text,
room_number smallint,
amenity_name text,
description text,
PRIMARY KEY ((hotel_id, room_number), amenity_name) )
WITH comment = Q5. Find amenities for a room;
Notice that the elements of the partition key are surrounded
with parentheses, even though the partition key consists
of the single column ``poi_name``. This is a best practice that makes
the selection of partition key more explicit to others reading your CQL.
Similarly, here is the schema for the ``reservation`` keyspace::
CREATE KEYSPACE reservation WITH replication = {class:
SimpleStrategy, replication_factor : 3};
CREATE TYPE reservation.address (
street text,
city text,
state_or_province text,
postal_code text,
country text );
CREATE TABLE reservation.reservations_by_confirmation (
confirm_number text,
hotel_id text,
start_date date,
end_date date,
room_number smallint,
guest_id uuid,
PRIMARY KEY (confirm_number) )
WITH comment = Q6. Find reservations by confirmation number;
CREATE TABLE reservation.reservations_by_hotel_date (
hotel_id text,
start_date date,
end_date date,
room_number smallint,
confirm_number text,
guest_id uuid,
PRIMARY KEY ((hotel_id, start_date), room_number) )
WITH comment = Q7. Find reservations by hotel and date;
CREATE TABLE reservation.reservations_by_guest (
guest_last_name text,
hotel_id text,
start_date date,
end_date date,
room_number smallint,
confirm_number text,
guest_id uuid,
PRIMARY KEY ((guest_last_name), hotel_id) )
WITH comment = Q8. Find reservations by guest name;
CREATE TABLE reservation.guests (
guest_id uuid PRIMARY KEY,
first_name text,
last_name text,
title text,
emails set,
phone_numbers list,
addresses map<text,
frozen<address>,
confirm_number text )
WITH comment = Q9. Find guest by ID;
You now have a complete Cassandra schema for storing data for a hotel
application.
*Material adapted from Cassandra, The Definitive Guide. Published by
O'Reilly Media, Inc. Copyright © 2020 Jeff Carpenter, Eben Hewitt.
All rights reserved. Used with permission.*

View File

@ -0,0 +1,64 @@
.. Licensed to the Apache Software Foundation (ASF) under one
.. or more contributor license agreements. See the NOTICE file
.. distributed with this work for additional information
.. regarding copyright ownership. The ASF licenses this file
.. to you under the Apache License, Version 2.0 (the
.. "License"); you may not use this file except in compliance
.. with the License. You may obtain a copy of the License at
..
.. http://www.apache.org/licenses/LICENSE-2.0
..
.. Unless required by applicable law or agreed to in writing, software
.. distributed under the License is distributed on an "AS IS" BASIS,
.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
.. See the License for the specific language governing permissions and
.. limitations under the License.
Cassandra Data Modeling Tools
=============================
There are several tools available to help you design and
manage your Cassandra schema and build queries.
* `Hackolade <https://hackolade.com/nosqldb.html#cassandra>`_
is a data modeling tool that supports schema design for Cassandra and
many other NoSQL databases. Hackolade supports the unique concepts of
CQL such as partition keys and clustering columns, as well as data types
including collections and UDTs. It also provides the ability to create
Chebotko diagrams.
* `Kashlev Data Modeler <http://kdm.dataview.org/>`_ is a Cassandra
data modeling tool that automates the data modeling methodology
described in this documentation, including identifying
access patterns, conceptual, logical, and physical data modeling, and
schema generation. It also includes model patterns that you can
optionally leverage as a starting point for your designs.
* DataStax DevCenter is a tool for managing
schema, executing queries and viewing results. While the tool is no
longer actively supported, it is still popular with many developers and
is available as a `free download <https://academy.datastax.com/downloads>`_.
DevCenter features syntax highlighting for CQL commands, types, and name
literals. DevCenter provides command completion as you type out CQL
commands and interprets the commands you type, highlighting any errors
you make. The tool provides panes for managing multiple CQL scripts and
connections to multiple clusters. The connections are used to run CQL
commands against live clusters and view the results. The tool also has a
query trace feature that is useful for gaining insight into the
performance of your queries.
* IDE Plugins - There are CQL plugins available for several Integrated
Development Environments (IDEs), such as IntelliJ IDEA and Apache
NetBeans. These plugins typically provide features such as schema
management and query execution.
Some IDEs and tools that claim to support Cassandra do not actually support
CQL natively, but instead access Cassandra using a JDBC/ODBC driver and
interact with Cassandra as if it were a relational database with SQL
support. Wnen selecting tools for working with Cassandra youll want to
make sure they support CQL and reinforce Cassandra best practices for
data modeling as presented in this documentation.
*Material adapted from Cassandra, The Definitive Guide. Published by
O'Reilly Media, Inc. Copyright © 2020 Jeff Carpenter, Eben Hewitt.
All rights reserved. Used with permission.*

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 228 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 139 KiB

View File

@ -15,6 +15,21 @@
.. limitations under the License.
Data Modeling
=============
*************
.. toctree::
:maxdepth: 2
data_modeling_conceptual
data_modeling_rdbms
data_modeling_queries
data_modeling_logical
data_modeling_physical
data_modeling_refining
data_modeling_schema
data_modeling_tools
.. todo:: TODO

View File

@ -29,8 +29,8 @@ Contents:
getting_started/index
new/index
architecture/index
data_modeling/index
cql/index
data_modeling/index
configuration/index
operating/index
tools/index