Determining a partition key in Dynamo DB for GSI - amazon-dynamodb

I am new to DynamoDB and I am finding it hard to think of how I should decide my partition key. I am using a condensed version of my use case:
I have an attribute which is a boolean value => B
For a given ID, I need to return all the data for it. The ID is either X or Y attribute. For the given ID, if B is true, I need to read attribute X, else Y.
While inserting into the table I know the the value of B and hence I can fill it in either X or Y depending on the value of it.
However while fetching, I just am given an ID, and I need to figure out whether it exists in column X or column Y ( I won't be getting the value of B in the input).
In a RDBMS I could run a query like select * from tab where (B == true && X == ID) || (B==false && Y == ID).
I think creating a GSI in DynamoDB will be the way to go about solving this in Dynamo. However I am not able to figure out the best way to approach this. Could I get suggestions?

Not sure if I got your use case correctly, but why not just swapping target columns based on value B while inserting a row.
Consider the following input:
+-----+------+--------+
| X | Y | B |
+-----+------+--------+
| ID1 | ID2 | true |
+-----+------+--------+
| ID3 | ID4 | true |
+-----+------+--------+
| ID5 | ID6 | false |
+-----+------+--------+
| ID7 | ID8 | false |
+-----+------+--------+
What if you store the values like this:
+-----------+-------------------------+
| id | opposite id |
|(hash key) | or whatever you call it |
+-----------+-------------------------+
| ID1 | ID2 |
+-----------+-------------------------+
| ID3 | ID4 |
+-----------+-------------------------+
| ID6 | ID5 |
+-----------+-------------------------+
| ID8 | ID7 |
+-----------+-------------------------+
This way, while fetching an item by an IDXXX value you would need to perform a query on the single column id.
UPD: Notice, if your use case allows having multiple records with a same id, you would need an another field to serve as a range key. This holds true no matter whether you swap columns like shown above or not.

As Per AWS DynamoDB Blog Post : Choosing the Right DynamoDB Partition Key
Choosing the Right DynamoDB Partition Key is an important step in the
design and building of scalable and reliable applications on top of
DynamoDB.
What is a partition key?
DynamoDB supports two types of primary keys:
Partition key: Also known as a hash key, the partition key is composed of a single attribute. Attributes in DynamoDB are similar in
many ways to fields or columns in other database systems.
Partition key and sort key: Referred to as a composite primary key or hash-range key, this type of key is composed of two attributes. The
first attribute is the partition key, and the second attribute is the
sort key. Here is an example:
Why do I need a partition key?
DynamoDB stores data as groups of attributes, known as items. Items
are similar to rows or records in other database systems. DynamoDB
stores and retrieves each item based on the primary key value which
must be unique. Items are distributed across 10 GB storage units,
called partitions (physical storage internal to DynamoDB). Each table
has one or more partitions, as shown in Figure 2. For more
information, see the Understand Partition Behavior in the DynamoDB
Developer Guide.
DynamoDB uses the partition key’s value as an input to an internal
hash function. The output from the hash function determines the
partition in which the item will be stored. Each item’s location is
determined by the hash value of its partition key.
All items with the same partition key are stored together, and for
composite partition keys, are ordered by the sort key value. DynamoDB
will split partitions by sort key if the collection size grows bigger
than 10 GB.
Recommendations for partition keys
Use high-cardinality attributes. These are attributes that have
distinct values for each item like e-mail id, employee_no,
customerid, sessionid, ordered, and so on.
Use composite attributes. Try to combine more than one attribute to
form a unique key, if that meets your access pattern. For example,
consider an orders table with customerid+productid+countrycode as the
partition key and order_date as the sort key.
Cache the popular items when there is a high volume of read traffic.
The cache acts as a low-pass filter, preventing reads of unusually
popular items from swamping partitions. For example, consider a table
that has deals information for products. Some deals are expected to be
more popular than others during major sale events like Black Friday or
Cyber Monday.
Add random numbers/digits from a predetermined range for write-heavy
use cases. If you expect a large volume of writes for a partition key,
use an additional prefix or suffix (a fixed number from predeternmined
range, say 1-10) and add it to the partition key. For example,
consider a table of invoice transactions. A single invoice can contain
thousands of transactions per client.
Read More # Choosing the Right DynamoDB Partition Key

Related

DynamoDB Global Secondary Index "Batch" Retrieval

I've see older posts around this but hoping to bring this topic up again. I have a table in DynamoDB that has a UUID for the primary key and I created a secondary global index (SGI) for a more business-friendly key. For example:
| account_id | email | first_name | last_name |
|------------ |---------------- |----------- |---------- |
| 4f9cb231... | linda#gmail.com | Linda | James |
| a0302e59... | bruce#gmail.com | Bruce | Thomas |
| 3e0c1dde... | harry#gmail.com | Harry | Styles |
If account_id is my primary key and email is my SGI, how do I query the table to get accounts with email in ('linda#gmail.com', 'harry#gmail.com')? I looked at the IN conditional expression but it doesn't appear to work with SGI. I'm using the go SDK v2 library but will take any guidance. Thanks.
Short answer, you can't.
DDB is designed to return a single item, via GetItem(), or a set of related items, via Query(). Related meaning that you're using a composite primary key (hash key & sort key) and the related items all have the same hash key (aka partition key).
Another way to think of it, you can't Query() a DDB Table/index. You can only Query() a specific partition in a table or index.
Scan() is the only operation that works across partitions in one shot. But scanning is very inefficient and costly since it reads the entire table every time.
You'll need to issue a GetItem() for every email you want returned.
Luckily, DDB now offers BatchGetItem() with will allow you to send multiple, up to 100, GetItem() requests in a single call. Saves a little bit of network time and automatically runs the requests in parallel; but otherwise is the little different from what your application could do itself directly with GetItem(). Make no mistake, BatchGetItem() is making individual GetItem() requests behind the scenes. In fact, the requests in a BatchGetItem() don't even have to be against the same tables/indexes. The cost for each request in a batch will be the same as if you'd used GetItem() directly.
One difference to make note of, BatchGetItem() can only return 16MB of data. So if your DDB items are large, you may not get as many returned as your requested.
For example, if you ask to retrieve 100 items, but each individual
item is 300 KB in size, the system returns 52 items (so as not to
exceed the 16 MB limit). It also returns an appropriate
UnprocessedKeys value so you can get the next page of results. If
desired, your application can include its own logic to assemble the
pages of results into one dataset.
Because you have a GSI with PK of email (from what I understand) you can use PartiQL command to get your batch of emails back. The API is called ExecuteStatment and you use a SQL like syntax:
SELECT * FROM mytable.myindex WHERE email IN ['email#email.com','email1#email.com']

DynamoDB Single Table Schema Design with Adjacency Lists

I am trying to understand how to properly design a DynamoDB schema. I've read a few articles, watched some YouTube videos but, to be honest, I don't yet feel quite comfortable.
This is what I am trying to design properly:
two entities, "location" (id & name) and "vehicle" (id & name)
a location can have 0-n vehicles
a vehicle can be in 0-1 locations
Access patterns:
get a list of all available locations (id & name)
get a list of all available vehicles and their current location (id, name, location-id, location-name)
get a list of all vehicles in a given location (id, name)
I've read about adjacency lists and because there will be n-m relations I've decided to give it a try.
This is what I've came up with:
# | PK (GSI1-SK) | SK (GSI1-PK) | DATA
==|======================|====================|==============
1 | LOCATION#locationId1 | A | locationName1
2 | LOCATION#locationId2 | A | locationName2
3 | LOCATION#locationId1 | VEHICLE#vehicleId1 |
4 | LOCATION#locationId1 | VEHICLE#vehicleId2 |
5 | LOCATION#locationId2 | VEHICLE#vehicleId3 |
6 | VEHICLE#vehicleId1 | A | vehicleName1
7 | VEHICLE#vehicleId2 | A | vehicleName2
8 | VEHICLE#vehicleId3 | A | vehicleName3
#1-2 & #6-8 are my entity records, those with additional data for the entity itself (e.g. its name).
#3-5 is an example of how I would design a relationship. I've added an inverted GSI in order to be able to search in both ways.
Back to my access patterns:
get a list of all available locations (id & name)
query GSI1 for SK=A and PK begins with LOCATION#
get a list of all available vehicles and their current location (id, name, location-id, location-name)
query GSI1 for SK=A and PK begins with VEHICLE#
for each result item, query GSI1 for SK=VEHICLE#vehicleId and PK begins with LOCATION#
for each result item, query table for PK=LOCATION#locationId and SK=A
... this doesn't seem right
get a list of all vehicles in a given location (id, name)
query table for PK=LOCATION#locationId and SK begins with VEHICLE#
for each result item, query table for PK=VEHICLE#vehicleId and SK=A
... this doesn't seem right
Adjacency lists look like a nice and clean way to design complex relationships but either I am doing something wrong (probably) or they come with alot of querys that are necessary to look things up.
Any advice is appreciated.
I modelled this in DynamoDB Workbench:
Main Index (PK -> SK)
GSI1 (PK1 -> SK)
In order to:
"get a list of all available locations (id & name)"
select * from GS1 where PK1="ALL#LOCATION"
get a list of all available vehicles and their current location (id, name, location-id, location-name)
select * from MAIN-INDEX where PK="ALL#VEHICLE"
get a list of all vehicles in a given location (id, name)
select * from GSI1 where PK1="LOC#ID"
Several things to here:
It's important to distribute the traffic across all partition keys. I'm using "ALL#" partition keys in this design. Ideally you shard that somehow, there are several tricks like using dates or timestamp to the beginning of the day. You can randomly spread them across a fixed number of "ALL#" records and then randomly query 1 if your use case allows it. If you have millions of locations this is probably ok. That's how you take these decisions: think of the traffic and the behaviour of the data.
In order to use both indexes I put the "ALL#LOCATION" and the "ALL#VEHICLE" partition keys in different indexes.
Notice that vehicle 4 doesn't have a PK1. See what happens to GSI1. This is what's called a sparse index.
I denormalized the vehicle-location relationship. Assuming that the location ID and the location name are immutable it's ok to do this, the problem is when the attributes you denormalize are mutable, avoid that if possible.

Dynamo DB - get only unique values from a table column

I have a table with 2 columns, user id & book id.
userId | bookId |
-----------------------
12 | 3
23 | 4
34 | 2
56 | 1
45 | 4
345 | 1
Is there a way to get only the unique values of bookId? like GROUP BY in sql.
Meaning query and get - [1,2,3,4]
Thanks.
DynamoDB doesn't have "columns" like a SQL table. Instead, it has documents (called items in DynamoDB terminology) which are indexed by a key (either simple or composite). And these items have attributes, but for the purposes of retrieval it's useful to imagine the items as being arbitrary payloads.
As such, there are no aggregate query APIs for DynamoDB tables. So you can't ask Dynamo to compute aggregations over multiple items.
If you need to identify unique items in a table you'll have to scan and perform the aggregation in your application. It's useful to think about how you might need to query the data upfront and use secondary indexes, or precompute aggregations as you update the data in your table.

Deleting multiple items based on global secondary index in DynamoDB

I have an existing table which has two fields - primary key and a global secondary index:
----------------------------
primary key | attributeA(GSI)
----------------------------
1 | id1
2 | id1
3 | id2
4 | id2
5 | id1
Since having the attributeA as a global secondary index, can I delete all items by specifying a value for the global secondary index? i.e I want to delete all records with the attributeA being id1 - Is this possible in Dynamo?
Dynamo provides documentation about deleting the index itself, but not specifically if we can use the GSI to delete multiple items
As of now, you cannot delete an item just by passing Non-key attributes or GSI keys.
The simplest way to do this is to Query GSI and get primaryKey(Hash key of the table) and Delete in next request.
You can refer this answer if you want to do batchDeletion.
Hope that helps

DynamoDB key uniqueness across primary and global secondary index

I'm working on creating a DynamoDB table to hold comments associated to a single object.
Comments are posted to an object at a specific time, I am using the time posted as the range so comments are sortable in time descending order. I have a global secondary index of the userId of the user who posted the comment, this should allow me to get all comments posted by a given user.
My question is, will this key be unique? I'm worried that since it is technically possible for two users to post a comment to the same objectId at the same time, the comment hash and range key will be identical.
My hope is that since it should be impossible for the same user to post two comments on the same object at the same time the global secondary index will make the key unique.
Comment table:
Hash Key Range Key Global Secondary Index Hash
---------------------------------------------------------------------------------------
| objectId | datePosted | userId |
| (not unique) | (not unique if multiple users | (unique across objectId and |
| | post for the same object # same time) | datePosted) |
---------------------------------------------------------------------------------------
DynamoDB indexes have nothing to do with uniqueness. Global and local indexes are allowed to have duplicate hash key & range key pairs. Only the hash key and range key of the table itself are unique.
In your example, it would be possible for two different users to comment on an object at the same exact moment and produce a duplicate objectId, datePosted key. There are a couple ways to deal with this. You could use a PutItem request with a condition that the primary key is null as mentioned in the API reference. That would cause the second comment save to fail and you could report an error to the user or simply try again with an updated time-stamp. Without the condition, the second comment will overwrite the first. Alternatively, you could make the range key of the table a composite value of datePosted concatenated with userId. That way, the range keys will always be unique, but will still be sorted in date time order. This is a common practice with DynamoDB.

Resources