AWS re:Invent 2022 - Deploy modern and effective data models with Amazon DynamoDB (DAT320)

AWS re:Invent 2022 - Deploy modern and effective data models with Amazon DynamoDB (DAT320)


AWS re:Invent 2022 - Deploy modern and effective data models with Amazon DynamoDB (DAT320)

Amazon DynamoDB is a fully managed NoSQL database that provides consistent performance at any scale. Customers like Disney+, Zoom, and Snap use DynamoDB to handle some of the largest applications on the planet. In this session, AWS Data Hero Alex DeBrie and DynamoDB Senior Principal Engineer Amrith Kumar walk you through key data modeling concepts for DynamoDB and share why DynamoDB architecture and implementation can help your applications scale seamlessly from 30 to 300 million customers while maintaining consistent, single-digit millisecond performance.

Learn more about AWS re:Invent at https://go.aws/3ikK4dD.

Subscribe:
More AWS videos http://bit.ly/2O3zS75
More AWS events videos http://bit.ly/316g9t4

ABOUT AWS
Amazon Web Services (AWS) hosts events, both online and in-person, bringing the cloud computing community together to connect, collaborate, and learn from AWS experts.

AWS is the world’s most comprehensive and broadly adopted cloud platform, offering over 200 fully featured services from data centers globally. Millions of customers—including the fastest-growing startups, largest enterprises, and leading government agencies—are using AWS to lower costs, become more agile, and innovate faster.

#reInvent2022 #AWSreInvent2022 #AWSEvents


Content

0.06 -> hey everybody I'm Alex debris I'm
2.52 -> excited to talk about Dynamo DB with you
4.5 -> today I'm an AWS data hero work a lot
7.08 -> with dynamodb and I've talked at re
8.639 -> invent the last three years about
10.44 -> dynamodb I'm excited because this year I
13.019 -> have someone that actually knows what
13.98 -> they're talking about with me up here so
15.66 -> I'll be talking with amrith Kumar amreth
17.58 -> is a senior principal engineer on the
19.74 -> dynamodb team they're they're two senior
21.66 -> principal Engineers on the dynamodb team
23.1 -> it's the highest current engineering
25.32 -> position on the team so lots of deep
27.9 -> knowledge that he's going to bring here
29.519 -> we'll we'll do a little bit of
31.08 -> infrastructure a little bit of data
32.579 -> modeling a little bit of everything in
33.66 -> this talk to help you learn how to how
35.64 -> to model well with dynamodb so to kick
38.1 -> it off Amber's going to get us going
39.239 -> here
40.94 -> okay so good afternoon my name is amrith
45.18 -> I'm one of the senior principal
47.28 -> Engineers on the dynamodb team been here
49.86 -> for about two and a half years
52.16 -> the talk is about data modeling and
56.1 -> I'm just going to give you an
57.3 -> introduction about dynamodb and then
59.219 -> hand it off to Alex and he's going to do
60.66 -> the rest of it
62.1 -> let's see if this works okay
64.619 -> so I'll do the overview I'll hand it off
66.54 -> to Alex to do the data modeling and then
68.76 -> I'll finish with some talk about
71.22 -> indexing and transactions
73.56 -> if you were expecting this is going to
75.299 -> be an in-depth architecture talk it is
77.58 -> not
78.78 -> there's a couple of links which I've
80.7 -> provided
81.659 -> a colleague of mine jasso did a talk a
83.82 -> couple of years ago that's a very good
85.56 -> talk
87 -> um Alex's book and a couple of months
90 -> ago
91.32 -> on the 10th 10th year of nanomodb we
94.86 -> published another paper to follow up on
97.259 -> the original Dynamo paper and I'd
99.84 -> strongly recommend all of these if
101.28 -> you're interested in architecture
103.979 -> so without further Ado I expect that
106.68 -> everyone here knows what I'm going to
108.84 -> say dynamodb is a key value in document
111.479 -> store
112.619 -> it is completely serverless it is secure
115.32 -> durable available
118.02 -> and the simple promise we give you is
120.06 -> that you can get predictable single
122.1 -> digit millisecond response times at any
124.079 -> scale and I'm going to talk to you about
126.84 -> some of how dynamodb does this
130.039 -> and then you can build applications
132.42 -> which can leverage these things so
134.04 -> that's the general idea of the talk
136.5 -> all right
138.239 -> so when we were putting this
139.68 -> presentation together Alex sent me this
141.599 -> tweet and by the way I strongly
144.72 -> recommend the five minute video which is
147.48 -> linked there it talks about how Snapchat
149.599 -> built their application
152.16 -> and it talks about how they use
154.14 -> kubernetes and how much data they store
157.92 -> but the thing which really caught my eye
159.54 -> when I saw this video
161.879 -> was that they Scan they scan about 2
164.459 -> billion rows a minute
166.019 -> this is one customer of dynamodb
168.86 -> 400 terabytes is a fairly large table
171.18 -> not the largest table by any means but
173.76 -> that gives you an idea of the kind of
175.92 -> scale to which an application can go 300
178.2 -> million daily active users 400 terabyte
180.36 -> table and so on but
182.4 -> scanning 2 billion rows a minute yeah
186.06 -> so I thought I'd give everyone a little
188.58 -> bit of a glimpse at the scale at which
191.459 -> nanomodb operates
193.379 -> so this is a simple thought exercise
194.94 -> okay
196.14 -> wake up in the morning you have
197.459 -> breakfast that's the important part of
199.319 -> the thought exercise
201.239 -> for every request which dynamodb does
205.019 -> put one sheet of paper on that pile
207.599 -> okay
209.04 -> and work at this till lunch
211.92 -> can anybody give me an idea how tall
213.48 -> this pile is going to be
215.159 -> shout it out
218.459 -> Empire State Building okay
222.06 -> anyone else
224.4 -> to the Moon all right
226.799 -> the answer is yes it is to the Moon so
229.56 -> did we send you the slides for you to
231.48 -> review in advance yeah okay thank you um
234.84 -> so in terms of raw numbers dynamodb does
238.379 -> about 10 trillion requests a day easily
241.68 -> on most days
244.019 -> um
244.739 -> well over that
246.42 -> and each one of those requests is
248.879 -> predictably single digit millisecond
251.28 -> latencies
253.019 -> when you build applications which need
255.54 -> this kind of guarantee they don't happen
257.82 -> by accident you literally have to
259.919 -> engineer your application to it whether
261.959 -> it's a relational database or it's a
263.88 -> nosql database there's engineering
265.919 -> involved and a big part of that is what
268.02 -> Alex is going to talk about which is
269.82 -> data modeling but in order to data model
272.4 -> your application correctly you need to
274.62 -> know something about how dynamodb works
277.199 -> so let's talk about that
280.62 -> um
281.16 -> how does dynamodb scale
283.8 -> simple answer is two things one we scale
286.38 -> horizontally
288.06 -> and the second is with eventual
290.1 -> consistency
292.02 -> if your database requires synchronous
294.56 -> rights and you cannot tolerate eventual
297.54 -> consistency
299.04 -> your ability to scale will be diminished
301.86 -> if you can only scale vertically your
304.32 -> ability or scale is diminished but the
307.139 -> good news is most applications
310.08 -> which need to scale can tolerate both of
313.08 -> these you can scale horizontally and you
315.78 -> can tolerate eventual consistency
318.479 -> in dynamodb all of your data is stored
320.759 -> in tables and very much like a
323.34 -> traditional relational database
325.38 -> you need to have a primary key for every
327.72 -> item by the way we don't call them rows
330.06 -> we call them items so
332.52 -> if I use them interchangeably my
334.74 -> apologies my pedigree is relational
336.78 -> databases before I came here
339.139 -> so we store data in items
342.419 -> and being a nosql database there is no
345.78 -> strict schema in this sample data notice
348.96 -> that each item has slightly different
351.3 -> structure
352.44 -> but there is one thing which is required
354.36 -> that is the primary key
356.639 -> when you create a table in dynamodb we
359.1 -> ask you for two things well three we ask
361.56 -> you for a credit card that's different
363.419 -> um
364.199 -> you need a table name
366.72 -> and you need a primary key that's it
369.6 -> and the primary key can either be just a
371.82 -> partition key
372.96 -> or it can be a partition key and a sort
375 -> key here I've shown you one with just a
377.34 -> partition key and what we do with that
380.039 -> is we store your data
382.139 -> sharded on the storage nodes by
384.78 -> Computing a cryptographic hash on the
387.84 -> partition key
389.88 -> items which have which fall within a
392.759 -> range of hashes go to one storage node
395.039 -> that's easy
396.3 -> we try to keep each keep each partition
398.58 -> at about 20 Gigabytes
402.24 -> so whether your table happens to have
404.94 -> 400 terabytes or more your partition
408.479 -> size is limited
410.46 -> if there's a lot of traffic on your
412.199 -> table we we may even have smaller
414.539 -> partition sizes
416.039 -> that's one of the ways we give you
417.6 -> predictable response time
419.759 -> the thing we do really quickly 10
421.74 -> trillion times a day
423.6 -> is look at a partition key
426.12 -> figure out which partition it's on
428.4 -> go to the operation and return to you
431.28 -> over and over again
433.259 -> right
434.46 -> so
435.9 -> if I were to show you the same data this
438.18 -> way
439.259 -> three storage notes and a bunch of rows
443.34 -> dynamodb is also a region is a regional
445.62 -> service and I said it's durable and
447.66 -> highly available
448.8 -> so we replicate all of your data three
451.139 -> ways
452.4 -> every partition
454.56 -> there's three copies of it and each copy
457.319 -> is in a different availability Zone
459.539 -> so
460.74 -> if you're requesting your data and your
463.199 -> application happens to be in
464.58 -> availability Zone one we will try and
466.68 -> serve it from availability Zone one
469.319 -> and so on but it also means there's
472.199 -> three copies if one of them
474.599 -> goes away for some reason we can always
476.759 -> reconstruct it
478.199 -> and we'll talk about some of the things
479.819 -> which we do to make sure that that
481.8 -> actually works so yes we have three
483.72 -> copies of the data this is how these
485.46 -> partitions are laid out
488.58 -> by the way in case I'm going too fast
490.74 -> somebody just wave or stop me
493.86 -> um
495.419 -> I did also mention that dynamodb is
497.88 -> completely serverless
499.979 -> when you provision a table
502.08 -> we didn't ask you how many servers you
504.66 -> want to provision we do all that for you
506.34 -> oops can't do that
508.919 -> so on these storage nodes
511.68 -> alongside your data
513.659 -> is data for another customer
517.74 -> you may be running an application and on
519.959 -> the same storage node there may be some
521.459 -> of that 400 terabyte snap table
524.039 -> it's our job to make sure that every
526.62 -> partition
528.3 -> gives
529.5 -> customer predictable single digit
532.26 -> response time so we move these
533.76 -> partitions around on a regular basis and
536.459 -> yes there's three availability zones
539.64 -> there's no guarantee that you will be
543.6 -> getting a storage node with the same
545.7 -> partition
547.26 -> on a different availability Zone it's
549.06 -> completely random
550.98 -> okay you make a request to your table we
554.04 -> figure out which partition you're asking
556.2 -> for we get you your data that's
557.94 -> basically what we do
561.18 -> all right
563.519 -> so let's talk a little bit about the
565.019 -> architecture this slide is going to
566.22 -> build from left to right so on the left
568.62 -> you have all of the clients
570.54 -> so your client could be something which
572.1 -> is running on a mobile device it could
574.14 -> be another application running in AWS it
576.48 -> could be ec2 it could be kubernetes it
578.22 -> could be a Lambda whatever it is
580.38 -> dynamodb is a regional service
583.68 -> whether you use the SDK or you directly
586.32 -> talk to a restful endpoint the first
588.54 -> thing you do is resolve an IP resolve a
591.06 -> fully qualified domain name
592.98 -> so if you were in Us East one
595.62 -> region becomes Us East one
598.14 -> if you're in Usos too similar
601.38 -> when you resolve that
603.24 -> you will get an end point which points
605.7 -> to a collection of load balancers so in
608.459 -> this case I'm showing you that you
610.56 -> resolve this fqdn and you got a load
613.019 -> balancer in az2 all right
616.44 -> behind every load balancer is a
618.66 -> collection of hosts we call them request
621 -> routers
622.38 -> and there's a collection of request
624.36 -> routers which we call a gaggle
626.48 -> Collective is a gaggle and the job of
629.64 -> the request router which is completely
631.2 -> stateless is
633.3 -> of course between the load balancers and
635.399 -> the request router we do SSL termination
637.26 -> so your data in flight
640.98 -> your data in Flight let me see if I can
642.839 -> get this video pointer to work there you
644.459 -> go
645.42 -> nope I guess not the data in transit is
648.779 -> going to be SSL encrypted
651.24 -> it gets to a request router and the
653.339 -> request router's job is three things
655.98 -> first
657.6 -> is the request properly signed by the
661.14 -> user
663.66 -> second if it is properly signed and IT
667.019 -> addresses a particular table does that
669.12 -> user have access to the table
672.06 -> and if if the user does
674.82 -> does the user have access to the Keys
676.86 -> used to encrypt the data for that table
678.779 -> three things 10 trillion times a day
682.62 -> get a request
684.18 -> authenticate authorize
686.82 -> make sure you have keys and then
689.64 -> if you do
691.019 -> forward the request to a story mode
693.42 -> which is where the data actually lives
696.18 -> if you're doing a get
698.399 -> send it to the storage node the storage
700.74 -> node stores the data encrypted
702.959 -> so the storage node will decrypted
705.24 -> and send the data all the way back
709.279 -> if your application happens to be an az2
713.16 -> we will try and keep this entire thing
714.839 -> in az2 but remember
717.66 -> we also have the same infrastructure in
721.14 -> the other two azs
724.14 -> so in regions which have three azs this
726.72 -> is what you look like in regions which
728.94 -> have more than three azs we're in more
731.16 -> than three azs
732.72 -> and in the middle of the box there
734.82 -> on the middle of the slide there there's
736.8 -> these other hosts which we manage which
739.14 -> are the infrastructure metadata what
742.8 -> tables do you have
744.48 -> for each table where are the partitions
746.64 -> for that table and we cache a bunch of
748.92 -> this information so when you make a
751.26 -> request
752.399 -> we look up the partition map
755.399 -> we find out where the data is and we
757.44 -> send it to that storage
759.54 -> over and over again that's what we do
762.18 -> okay
765.54 -> um Auto admin is a component which is
768.06 -> listed in the middle there
770.04 -> I told you we try to keep partitions at
772.019 -> about 20 gig
773.7 -> if you insert a bunch of data and your
775.74 -> partition starts to grow
777.54 -> Auto admin will realize that and will
780.36 -> split your partition into two partitions
782.88 -> and put them into different places
784.86 -> update the partition map completely
787.079 -> transparent to you
789.06 -> you don't have to do any of that stuff
791.519 -> and we'll talk about transactions later
793.68 -> so I'll skip that one for now
797.94 -> if your requester was an az3
801.12 -> and you went to a request router in az3
804.779 -> we'll try and serve your request in az3
806.88 -> but
807.959 -> the request router in az3 can talk to a
810.12 -> storage node in az1 or az2 and there are
812.76 -> some cases where we actually do that
814.399 -> like rights or if you do a consistent
817.44 -> read we may have to reach across the
819.12 -> boundary
820.98 -> a lot of time thinking about
823.62 -> fractions of a millisecond
825.959 -> and the time difference between going
828.3 -> across AZ boundaries or within an AC and
831.36 -> we try to make sure that you get
832.92 -> predictable response times so those are
835.44 -> the things we do so you can build on top
837.48 -> of them
839.399 -> okay
841.38 -> I did mention that we were completely
843.18 -> serverless
844.26 -> so we have three azs worth of capacity
846.72 -> and something happens
848.82 -> maybe we do a game day we take an easy
851.16 -> out
852.54 -> there is enough capacity in the other
854.339 -> two azs to serve all of your requests
859.74 -> and we build and we engineer and we test
862.38 -> to this on a regular basis which means
866.22 -> this may be a little bit hard to see but
867.779 -> at the bottom right there's a little X
869.16 -> on the storage node we are continuously
872.16 -> deploying software
874.32 -> if we can take an entire easy out
876.959 -> we can take a storage unit anytime we
879.18 -> want
880.079 -> we can take a request router out anytime
882.18 -> we want
883.68 -> and it is there's no maintenance windows
886.86 -> because of this it's completely
888.48 -> serverless no maintenance windows and
890.579 -> all of this is completely invisible to
892.8 -> you as a user
894.54 -> and of course we do the same thing with
896.16 -> load balancers so
898.56 -> customers like snap
901.38 -> or Dropbox or
903.92 -> see Amazon themself
908.339 -> engineer applications and knowing how
910.74 -> this works
912.66 -> it's important for you to model your
914.579 -> data in a way that exploits this
917.04 -> architecture
918.18 -> so with that
920.699 -> let me hand it over to Alex
923.699 -> thank you
925.92 -> awesome thank you Amarth and yeah I love
927.899 -> that infrastructure stuff partly because
929.699 -> it's super interesting so go check out
931.56 -> those other resources that amberth
932.82 -> mentioned especially jasso's 2018 talk
934.92 -> and the 2020 2022 paper is really cool
937.68 -> if you want to get stuff but like
939.66 -> Amber's saying like this is really
940.98 -> useful for you in modeling your dynamodb
943.62 -> applications so that's what I'm going to
945.24 -> talk about here and you know if that
947.22 -> stuff flew by you we'll sort of refer to
948.899 -> it in different places and reinforce
950.639 -> where that that helps you in your data
952.56 -> modeling right so we'll do some data
953.699 -> modeling here I always like to go with
955.5 -> some sort of example I've done
956.579 -> e-commerce or social media in the past
958.44 -> we're going to do a game this year right
959.76 -> so last year Amazon Studios released new
962.519 -> world which is this you know multiplayer
964.86 -> online RPG game using a lot of AWS
968.339 -> Services behind the scenes and Werner
970.079 -> got up on stage during his keynote and
972.36 -> talked about all the different services
973.32 -> including dynamodb one of the things the
976.38 -> service is doing is you know if you're
977.94 -> playing every 30 seconds it's going to
979.44 -> increment your play time just to keep
980.699 -> track of how much time you've been
981.72 -> playing right so they're doing 800 000
983.76 -> rights to Dynamo every 30 seconds which
985.92 -> as we've seen is is really nothing for
988.26 -> Dynamo but but seems pretty big to me so
990.899 -> we're going to be talking about that
992.82 -> um as the example here I always like to
994.5 -> start off with some terminology so four
996.48 -> key terms table item primary key and
999.36 -> attributes we'll do it in the context of
1001.399 -> that game so we'll start off with some
1003.44 -> users in our game if you see we have
1005.3 -> some users here Amarth myself Janelle
1007.639 -> and Chad uh four records and they're
1010.1 -> going to be contained in what's called a
1011.899 -> table right so similar to a table in a
1014.54 -> relational database but as we're going
1015.8 -> to see different in a lot of ways as
1018.019 -> well
1019.519 -> now if you look at an individual record
1021.019 -> in a table as amorth mentioned that's
1022.759 -> going to be called an item so this you
1024.439 -> know amberth's user record that's going
1025.819 -> to be an individual item in your table
1027.74 -> similar to a row in a relational
1029.9 -> database
1031.88 -> when you create your dynamodb table you
1033.98 -> have to specify what's called a primary
1035.66 -> key and every item you write to your
1037.52 -> table must include that primary key and
1039.079 -> must be uniquely identified by that
1040.52 -> primary key so on the left hand side
1042.38 -> here we have the primary key for our
1043.819 -> table it's username that's going to
1045.62 -> uniquely identify every single user in
1047.54 -> our table right we're not going to have
1048.559 -> two users with the same username
1051.08 -> it's gonna be similar to primary key in
1052.64 -> a relational database but but different
1054.62 -> in a lot of ways as well
1056.6 -> in addition to that primary key we also
1058.52 -> have other attributes you can have here
1060.26 -> right and you can see we have a lot of
1061.94 -> different attributes similar to columns
1063.919 -> in a relational database with a big
1065.059 -> difference being you're not going to
1066.32 -> specify these up front so dynamodb is
1068.72 -> schemaless there's not going to be a
1069.919 -> schema that Dynamo is going to enforce
1071.539 -> for you outside of that primary key
1074.299 -> if you look at these attributes you can
1075.799 -> see we have some simple attributes right
1077.539 -> simple scalar strings numbers like class
1080.059 -> gold total Play Time Guild things like
1082.22 -> that but you can also have complex
1084.08 -> attributes you can have lists and sets
1086.539 -> and Maps we'll talk a little bit about
1088.1 -> when you'd want to use those
1091.039 -> let's talk a little bit more about the
1092.78 -> primary key because it's so important to
1094.46 -> how you work with dynamodb there are two
1097.039 -> types of primary key in dynamodb so
1098.9 -> there's first of all what's called a
1100.039 -> simple primary key which has one element
1101.539 -> called a partition key and there's
1103.28 -> what's called a composite primary key
1104.84 -> that has two elements that partition key
1106.82 -> and the sort key and that user table we
1109.58 -> looked at before that's an example of
1111.14 -> the simple primary key right it has just
1112.64 -> that single element that partition key
1114.2 -> that's unique identifying every record
1115.7 -> in your table this is going to be great
1117.98 -> for simple key value like access
1120.38 -> patterns you're only operating on one
1122 -> record at a time
1123.74 -> but if you have more complex access
1125.66 -> patterns then you might want to use a
1127.34 -> composite primary key and you know in
1129.14 -> addition to users in our application
1130.82 -> we're also going to have quests so let's
1132.38 -> look at a table with a composite primary
1134.66 -> key here we have some quests that our
1136.28 -> different users are going on you can see
1138.32 -> we're using this composite primary key
1139.88 -> it has two different elements here right
1141.38 -> we have that partition key of username
1143.66 -> but we also have that sort key of Guild
1146.12 -> ID
1148.28 -> now as you're looking at these records I
1149.78 -> just want to point out if you look at
1150.799 -> the top three records here those are
1152.179 -> three separate items there but they all
1154.64 -> have the same partition key so you can
1156.2 -> have multiple records that have the same
1158 -> partition key and if you're using this
1159.74 -> is nosql workbench this is what I use
1161.36 -> for modeling a lot if you have multiple
1163.4 -> records with the same partition key it's
1164.84 -> going to sort of merge that cell
1165.919 -> together because they're going to be
1167 -> contained near each other on the the
1168.799 -> same partition in most cases
1171.919 -> um but when you're working with that
1173 -> composite primary key there's sorry this
1174.98 -> is going to be called an item collection
1175.94 -> so if I refer to an item collection that
1177.799 -> refers to you know a set of items that
1179.66 -> have the same partition key
1182.36 -> and while you can have records with the
1184.34 -> same partition key it's the combination
1186.08 -> that still must be unique so you can
1187.7 -> have records with the same partition key
1189.02 -> but the sort Keys need to be unique
1190.66 -> within that combination
1194.66 -> if we look at the two types of primary
1196.52 -> key notice that both of them have a
1198.86 -> partition key right and that partition
1200.179 -> key is really important we can go back
1201.559 -> to what amworth was talking about when
1203.6 -> that request comes in it hits that load
1205.28 -> balancer it hits that request router
1207.32 -> right and that request router is is
1208.76 -> responsible for routing it to the Right
1210.02 -> Storage node it's gonna it's gonna look
1211.34 -> up the metadata for your table it's
1212.96 -> going to figure out which item it's
1214.82 -> going to look at the partition key for
1215.96 -> this item figure out which stores
1216.98 -> partition it goes to and sends it to
1218.6 -> that storage node and that partition key
1220.1 -> is so important because what you're
1221.179 -> doing is no matter how big your table is
1223.46 -> you're reducing it down to 10 gigs right
1225.38 -> so you can have this infinitely scalable
1227.6 -> table you know 400 terabytes or whatever
1230 -> snap has or Amazon retail any of that
1232.1 -> and you're immediately getting down to
1234.14 -> 10 gigabytes or less where you can work
1235.94 -> on something a little bit faster right
1237.38 -> so that primary key in particularly that
1239 -> partition key so important to how you do
1240.919 -> data modeling in Dynamo
1243.08 -> so if we look at our users table right
1244.76 -> we have that primary key that's how we
1246.38 -> want to access our data with that
1247.7 -> primary key we don't want to do it with
1248.96 -> those other attributes which are
1250.1 -> effectively unindexed at this point
1251.84 -> right they're going to be spread across
1252.799 -> our table but that primary key that
1254.12 -> partition key that's how we're
1255.5 -> segmenting our data
1257.12 -> or if we look at our table with a
1258.74 -> composite primary key same deal right we
1260.6 -> want to use that primary key definitely
1261.98 -> the partition key ideally the sort key
1263.72 -> as well to access that data not with
1266.36 -> those other attributes
1268.1 -> so that's the key point with Dynamo
1269.66 -> right with Dynamo you have to design
1271.46 -> your tables specifically for your access
1273.2 -> patterns and this is going to be
1274.34 -> different if you're coming from that
1275.78 -> relational database world where you
1277.64 -> normalize your data first you get into
1279.08 -> third normal form you set up all your
1280.58 -> foreign key relations and that stuff and
1282.08 -> then you think about how you access your
1283.34 -> data you're not going to do that with
1284.78 -> Dynamo
1287.059 -> so I want to talk about a little bit
1288.2 -> about the data modeling process and then
1289.58 -> we'll sort of walk through that and
1291.14 -> understand the features of Dynamo a bit
1292.7 -> I always think of it as a three-step
1294.26 -> process right first step is is you're
1295.76 -> sort of understanding your application
1296.96 -> you're ideally creating an entity
1298.88 -> relationship diagram you know same thing
1300.559 -> you're probably doing in a relational
1301.4 -> database figuring out which objects you
1303.559 -> have figuring out how they relate to
1304.94 -> each other in your application
1307.58 -> then once you've done that now it sort
1309.08 -> of starts to Veer off from a relational
1310.34 -> database right where you actually want
1311.539 -> to list out your access patterns and
1313.22 -> think about how you're going to access
1314.299 -> those different objects and entities in
1315.98 -> your application and once you've listed
1318.32 -> out your access patterns now you
1319.58 -> actually do the design work you go
1320.9 -> through and you design your primary Keys
1322.52 -> specifically to handle those access
1324.26 -> patterns and that's where the primary
1325.52 -> key differs from that relational
1326.539 -> database you know it's not just an auto
1327.86 -> incrementing integer that gets assigned
1329.72 -> to the server it's going to be something
1330.62 -> meaningful in your application to help
1332.659 -> you handle your access patterns
1335.6 -> so let's walk through this a bit we'll
1337.039 -> start with creating our entity
1338.12 -> relationship diagram right and if you
1339.919 -> walk through this you know your your
1341.419 -> entities are usually boxes you have
1343.159 -> something like users in your application
1345.38 -> and as we saw users have inventory so
1347.72 -> you have a one-to-many relationship
1348.74 -> between users and inventory
1351.14 -> also users can go on quests there's a
1353.12 -> one-to-many relationship between users
1354.38 -> and quests and then finally users can
1355.76 -> belong to a guild so users are the
1358.94 -> um the subject of a of a one-to-many
1361.46 -> relationship as well one Guild has many
1363.679 -> users
1366.08 -> all right so once we start to know our
1367.76 -> application our entities in that now
1369.74 -> let's go and list out our access
1371.299 -> patterns and the biggest thing here is
1372.679 -> you want to be thorough and you want to
1374 -> actually list these out and not think
1375.32 -> about them just sort of generally in
1376.4 -> your head but write them down on a piece
1378.38 -> of paper somewhere and get specific
1379.7 -> right you want to say create user
1381.14 -> increase the gold for user add inventory
1383.179 -> to users all these different things and
1384.62 -> list out the different access patterns
1386.48 -> you're going to have
1387.799 -> and note I like to separate it out into
1389.419 -> right access patterns which sort of have
1390.919 -> different needs and then also read
1392.659 -> access patterns I do the same thing and
1394.159 -> think about how I'm going to access my
1395.179 -> data get a user fetch inventory for user
1397.52 -> fetch the Quest for the users all those
1399.08 -> things right so I have read access
1400.46 -> patterns I have write access patterns as
1402.14 -> well
1403.46 -> and this will give me a little bit
1404.78 -> different to you if if you have a little
1406.7 -> trouble thinking about this two ways I
1408.679 -> sort of like to approach it like if
1410.299 -> you're exposing a rest API you can think
1411.919 -> what rest API endpoints do I have right
1414.679 -> what use cases do I sort of need to be
1416.78 -> able to handle in a nice efficient way
1418.88 -> or you know if you're building some sort
1420.14 -> of service you can say what what methods
1422.179 -> do I want to expose on my service object
1423.74 -> that can be used by my application right
1426.08 -> that's a good way to sort of think about
1427.1 -> that and the big thing here is to be
1428.539 -> very thorough be very specific if we go
1430.82 -> look at these different read access
1431.96 -> patterns you can look at the two on the
1433.88 -> bottom I actually have fetch Quests for
1435.559 -> users right I want to get all the quests
1436.76 -> for a particular user but I have another
1438.02 -> one that's that's really specific fetch
1439.88 -> the completed quest for the user right
1441.38 -> that's just a filtered view of that
1443.48 -> original View and sometimes you need to
1444.919 -> be very specific and sort of
1446.179 -> differentiating between those because
1447.2 -> there's going to be different access
1447.98 -> patterns
1450.799 -> all right then once you've listed out
1452.659 -> your access patterns then you want to
1454.22 -> design your primary key to actually
1455.72 -> handle those access patterns and now we
1458.78 -> can actually I guess the big thing you
1460.46 -> want to remember there you want to be
1461.9 -> accessing with that primary key not with
1463.46 -> those attributes right
1466.039 -> so how do we do this you know we can
1467.539 -> look through our access patterns here I
1469.22 -> generally like to start with write
1470.299 -> access patterns and if you look at all
1472.4 -> the right access patterns right creating
1473.9 -> a new user increasing gold for a user
1476.059 -> creating a quest for a user all of these
1478.28 -> are single item patterns we're working
1479.96 -> on a single item in that request right
1483.14 -> and same thing with our read access
1484.64 -> patterns a few of these are what I call
1487.039 -> single item patterns as well get a user
1489.38 -> get the quest details for a particular
1491.419 -> user and quest ID
1494.12 -> and remember what we said earlier that
1495.62 -> the primary key is going to uniquely
1497.059 -> identify each record in your table it's
1498.559 -> going to be very efficient to go look up
1499.94 -> an individual item so working with
1501.32 -> individual items is a very efficient
1503.419 -> consistent predictable action in your
1506.059 -> table right so dynamodb has apis for
1509.12 -> working with single items whenever you
1511.34 -> need to create read manipulate some sort
1513.559 -> of individual record that's going to be
1514.7 -> great there so getting a user adding a
1516.74 -> user to a guild creating a user or
1518.72 -> creating a quest excuse me
1521.12 -> now when you're working with single item
1523.039 -> actions you have to include the full
1525.02 -> primary key so if you're doing put item
1526.76 -> get item update item delete item you
1528.679 -> have to include the full primary key
1529.94 -> because you want to identify the
1530.9 -> specific item you're working on there's
1532.039 -> not like an update where type Clause
1533.72 -> where you can update all these items
1535.34 -> that have the same partition key you
1536.72 -> have to identify it with the full
1538.52 -> primary key
1539.9 -> and notice that all right actions are
1542.059 -> single item actions you can't do sort of
1543.799 -> like a bulk update right type that you
1545.96 -> can do sort of combinations of single
1548.12 -> item actions and transactions in batch
1549.679 -> but all those are just sort of composing
1551.179 -> together multiple single item actions
1553.039 -> we're identifying each item with that
1555.32 -> that full primary key
1558.38 -> so that's gonna be a big chunk of what
1560.299 -> you're doing with Dynamo all those
1561.32 -> single item actions but if you look at
1562.7 -> our access patterns we also have access
1564.799 -> patterns we want to read a set of
1566.6 -> Records in a single request right we can
1568.58 -> handle those with Dynamo first one we
1569.96 -> can handle if you get this top one right
1571.82 -> I want to get all the inventory for a
1573.44 -> user
1574.76 -> and if you look at our users table
1575.96 -> remember we just embedded the inventory
1577.82 -> on our user item right so we have this
1580.46 -> this list that contains all the the
1582.38 -> inventory that this particular user is
1584.36 -> getting so if we want to get the
1585.32 -> inventory for a user we're just doing
1587.12 -> that single item action that get item to
1589.039 -> read that user record right
1591.02 -> so you can do that in certain scenarios
1592.82 -> but but more commonly you'll probably
1594.919 -> use a different pattern so let's look at
1596.24 -> a different one uh fetching Quests for a
1598.58 -> particular user right we can go back to
1599.96 -> our table our Quest table uh has that
1602.299 -> composite primary key
1604.279 -> remember that we said that all the
1606.38 -> records with the same partition key are
1608.299 -> in the same item collection right and
1610.279 -> you can use What's called the query
1611.419 -> operation and in the query operation you
1613.279 -> can retrieve multiple records that have
1614.9 -> the same partition key remember what
1616.82 -> that partition key is doing for us it's
1618.26 -> narrowing our table down from infinite
1620.72 -> size down to 10 gigabytes right so it's
1622.46 -> narrowing where we're going very quickly
1623.9 -> we can retrieve these items together so
1625.64 -> we're pre-grouping them together in some
1628.039 -> particular way right and queries helping
1629.539 -> us actually access those
1632.779 -> now within this item collection they're
1634.82 -> going to be ordered according to that
1636.26 -> sort key and we can use that in smart
1637.7 -> ways of of how we want to order these
1639.74 -> records to be returned to us or how we
1641.179 -> want to filter them in that query in
1643.34 -> this particular one I'm using what's
1644.36 -> called a UL ID so it's kind of like a
1647.24 -> unique ID that has a timestamp prefix
1650 -> encoded in it right so we get unique we
1652.159 -> get time sortable ID so the first 10
1653.9 -> characters you get here if you look that
1655.82 -> actually corresponds to that Quest
1657.14 -> started at date right so so now I'm
1659.299 -> getting some uniqueness of that uuid but
1661.52 -> I also get that time sortability that
1664.159 -> helps me using that sort key
1668.059 -> and I can use that sort key to actually
1669.74 -> filter my data right so I can I can
1671.419 -> query within an individual partition but
1673.039 -> let's say I want to narrow that range to
1674.72 -> a particular time range I can add
1676.64 -> conditions on that sort key and say hey
1678.32 -> when the quest ID is between this time
1680.12 -> and this time which is which is January
1681.559 -> 1st and and August 1st right and it'll
1683.419 -> narrow down and it's just going to get
1685.22 -> those records that that match that for
1686.9 -> me right
1688.46 -> so that query API is going to be really
1689.9 -> powerful for you again that's what
1691.22 -> you're doing is is reading a set of
1693.2 -> Records right getting all the quests for
1694.94 -> a user fetching all the members for a
1696.62 -> guild
1697.34 -> when you're using that query API you
1699.44 -> have to include the partition key but
1700.64 -> you can also include those sort Key
1702.08 -> conditions that can help you do things
1703.94 -> like filtering on a Time range you know
1705.5 -> getting you the most recent alphabetical
1706.94 -> things like that
1710.24 -> I talked a little bit about like two
1711.62 -> patterns to handle related data right we
1714.02 -> had the embedded pattern where inventory
1715.52 -> is embedded on that user we also have
1717.38 -> this item collection pattern where
1718.7 -> quests have the same partition key like
1720.799 -> when should you use each one
1722.9 -> you know the embedded pattern is good
1724.88 -> when you have like a smaller and bounded
1726.799 -> set of relations I'm not going to have a
1728.779 -> user that has a thousand inventory items
1730.46 -> right they're going to have a weapon a
1732.26 -> shield some boots a jacket something
1734.84 -> like that right so a pretty small amount
1737 -> of them it keeps my items small not
1738.799 -> going to run into item size limits or
1740.179 -> repaying a lot for those
1741.799 -> also I'm not searching by those
1743.299 -> relations directly I'm not saying you
1744.98 -> know which user is wearing this cloak or
1746.48 -> something like that I'm saying get that
1747.5 -> user and I want to render out all the
1749.299 -> inventory that they're wearing
1751.22 -> so you can use that embedded pattern
1752.48 -> there but probably more often you'll use
1753.86 -> that item collection pattern using that
1755.539 -> query API and that's good when you have
1757.52 -> a large and unbounded set of relations
1758.96 -> right I don't want to limit the number
1760.64 -> of quests someone could go on just
1762.08 -> because of the dynamodb item size or the
1763.94 -> number of e-commerce orders someone can
1765.679 -> make
1766.52 -> also I might want to fetch that related
1767.96 -> item directly and if it's a separate
1769.7 -> record I can index that in different
1771.44 -> ways that are helpful there
1774.5 -> foreign
1776 -> last thing I want to look at with these
1777.5 -> read access patterns notice we have a
1779.539 -> pattern of fetch users by Guild right so
1781.76 -> we want to retrieve all the users that
1783.2 -> belong to a certain Guild but if we look
1785.48 -> at our users table right it's already
1786.98 -> partitioned according to username
1788.72 -> because we have this get user by
1789.98 -> username access pattern that's getting
1791.36 -> it but then our Guild is some other
1792.98 -> attribute we said that it's not
1794.539 -> efficiently indexed for that so how do
1796.159 -> we handle multiple access patterns on
1798.14 -> the same piece of data that's where
1799.82 -> something called secondary indexes come
1801.26 -> in all right and these are actually
1802.399 -> fairly similar to indexes in your
1804.679 -> relational database with a few
1805.82 -> differences but basically What's
1807.559 -> Happening Here is is data from your main
1809.48 -> table is getting copied onto separate
1811.52 -> partitions with a new primary key so you
1813.98 -> can declare this on your table basically
1815.48 -> give you a new primary key and it's
1817.22 -> going to copy it onto separate
1818.419 -> infrastructure
1819.679 -> so if we look at our table here and we
1821.659 -> want to be able to access these quests
1823.159 -> or sorry the users buy their their Guild
1826.1 -> right we can we can declare a secondary
1827.6 -> index where we set up the guild value as
1830.179 -> the partition key where we set up the
1831.919 -> username as that sort key and now what
1833.72 -> we have is a secondary Index this is the
1835.52 -> exact same data but it's just shifted in
1837.62 -> a different way where our primary key
1839.36 -> uses that Guild and username right and
1842.059 -> then we can query that efficiently
1848.48 -> so again with secondary indexes data
1851.659 -> from your tables copied on separate
1852.86 -> partitions with a new primary key I say
1854.539 -> new primary key that's a little bit with
1855.799 -> an Asterix it's not a true primary key
1857.84 -> it's not maintaining uh uniqueness and
1860.059 -> things like that but in terms of the
1861.26 -> query API it acts very similar to a
1863.48 -> primary key in your diameter DB table
1865.52 -> you can use Simple you can use composite
1867.14 -> things like that
1869.179 -> now the great secondary index is you can
1871.159 -> only use them for read-based operations
1872.72 -> you can enable additional read patterns
1874.399 -> but you can't do rights to your
1876.32 -> secondary index all your rights need to
1878.059 -> flow through your main table and they'll
1879.86 -> get replicated out to those secondary
1881.72 -> indexes
1884.539 -> and again the key Point here is this is
1886.1 -> going to help you have if you have
1887.179 -> multiple access patterns on the same
1888.559 -> item
1890.899 -> they can also provide filtering using a
1892.82 -> pattern called sparse index so let's go
1894.38 -> back to those patterns I talked about
1895.76 -> earlier we want to fetch the Quest for a
1897.32 -> user and also fetch the completed quest
1898.82 -> for a user right how can we efficiently
1900.2 -> filter that out we show how you can get
1902.299 -> all the quests for a user right you go
1903.679 -> to the item collection use that query
1905.48 -> specify that partition key but we
1908 -> basically want to filter out all the
1909.32 -> records that have this that don't have a
1911.659 -> quest completed at date right and we
1913.76 -> could just fetch all those and filter it
1915.26 -> in memory but ideally we could have a
1916.94 -> more narrow more focused query so what
1919.22 -> we can do here is set up a secondary
1921.08 -> index where we set up the partition key
1922.96 -> or the the username as the partition key
1925.94 -> the quest completed that as the sort key
1928.46 -> and that's going to filter those out for
1929.84 -> us and now we have this secondary index
1931.7 -> that's already provided the filter
1932.779 -> filtered out the uncompleted Quest and
1934.82 -> now we can get exactly the quest that we
1936.38 -> need here
1938.559 -> so just a couple takeaways from the the
1940.94 -> basic data modeling portion I'll go into
1942.679 -> some more advanced sections right key
1944.84 -> part here is you want to understand your
1946.34 -> access patterns and model explicitly for
1948.26 -> them and that's where it's going to be
1949.1 -> different from a relational database
1951.08 -> rely heavily on those primary keys
1954.32 -> but then you can use those secondary
1955.82 -> indexes for accessing your data reading
1957.74 -> your data in different ways
1959.84 -> we talk about relationships and the
1961.279 -> different patterns you can do there when
1962.36 -> to do embedded when to do that item
1963.799 -> collection query pattern
1965.48 -> then we talk about different ways with
1966.679 -> filtering right you can filter with that
1968 -> partition key we filtered all the quests
1969.799 -> down to get just the Quest for a
1971.299 -> particular user using that partition key
1972.919 -> and we wanted to filter it even more
1974.659 -> with that sort key right so we said all
1976.279 -> the quests for this particular user
1977.539 -> within this particular time range
1979.58 -> then we can filter even more using that
1981.559 -> sparse index pattern where we said hey
1983.36 -> only include these items if they have
1984.86 -> that Quest completed at Value so we
1986.299 -> filter out the uncompleted quests right
1988.64 -> and I think one thing you want to take
1990.62 -> away with dynamos you want to limit the
1992.6 -> number of requests you're making to
1993.799 -> Dynamo ideally all these access patterns
1995.659 -> you're doing you can handle with a
1997.039 -> single request to Dynamo which is going
1998.36 -> to give you that consistent response
1999.5 -> time that consistent predictable
2001 -> response time that amaranth mentioned
2002.5 -> also you want to limit discarded data
2004.6 -> you don't want to read a bunch of data
2005.74 -> from Dynamo and discard it in your
2007.72 -> application what you want to do is lean
2010.059 -> on that primary key and those secondary
2011.62 -> indexes to do the filtering for you so
2013.419 -> ideally you've got exactly the set you
2014.98 -> need from your response
2019.36 -> all right let's move into single table
2020.559 -> design because uh I know people love
2022.6 -> love a little bit of single table design
2024.039 -> so
2025.779 -> um if if you're new when you're new to
2028.36 -> Dynamo you might hear this pattern
2029.62 -> called single table design I want to
2031.059 -> talk through that what first of all what
2032.679 -> it is kind of how it works and then why
2034.84 -> you might use it right what are the
2035.919 -> benefits of using single table design
2037.299 -> and and really my favorite is like what
2039.46 -> single table design teaches you
2041.679 -> um as you sort of learn that pattern
2043.899 -> so let's start off with what is single
2045.82 -> table design it's basically when you're
2047.019 -> putting multiple different multiple
2048.879 -> heterogeneous entities into a single
2051.099 -> dynamodb table if you look at the
2052.839 -> dynamodb documentation it's going to
2054.76 -> talk about how nosql design is different
2056.26 -> and it'll say you should maintain as few
2058.3 -> tables as possible in a dynamodb
2060.879 -> application right
2062.679 -> so if we go back to our ERD right we
2064.899 -> have these four different tables and
2066.159 -> it's saying rather than having a table
2067.359 -> for each like you would in your
2068.32 -> relational database ideally you know you
2070.179 -> have fewer maybe maybe just one table
2072.04 -> and it doesn't mean by cutting out
2073.119 -> entities but actually combining them
2074.5 -> into the same table
2076.72 -> and you might think hey we already did
2077.919 -> this right if you remember users in
2079.48 -> inventory
2080.44 -> let's go back to our users table right
2082.179 -> we had our users but we also just
2083.8 -> embedded that inventory in our user so
2086.08 -> is this is this single table design I'd
2088.119 -> say not really in addition to putting
2090.46 -> multiple different entities in a single
2091.72 -> table I think single table design is
2093.52 -> you're also using generic primary key
2095.56 -> names right rather than having
2098.5 -> um sort of these specific names like
2100.119 -> like username like quest ID for your
2101.859 -> primary key you have more generic name
2103.18 -> so I want to walk through how we combine
2105.4 -> our users table and our Quest table into
2108.04 -> a single dynamodb table
2109.96 -> let's start off with some users so I
2111.7 -> have myself and Amarth here in our table
2115.359 -> those two user records a couple things
2117.28 -> to call out you know notice that we're
2118.96 -> using generic names for our primary key
2121.78 -> elements right we don't have username we
2123.28 -> don't have quest ID we have PK for
2124.96 -> partition key and SK for short Keys
2127.3 -> that's that's that single table design
2128.38 -> those generic names there if you look at
2130.599 -> the values for our primary key notice
2132.22 -> that they're they're a little funkier
2133.72 -> right we might be combining multiple
2135.76 -> different values in there we might be
2137.14 -> prepending it with the item type in
2139.96 -> there to to help
2142.54 -> signify what kind of item we're working
2144.22 -> with there
2146.26 -> all right so let's add a few quests in
2147.82 -> there so you can see in our table now we
2149.44 -> have five records we still have our two
2150.7 -> user records Amarth and I uh but then
2153.04 -> outlined in red now you can see we've
2154.599 -> also added a few Quest records in here
2157.119 -> a couple other things I want to call out
2158.44 -> here notice that
2159.94 -> um the the primary key values are going
2161.859 -> to be different for a sort key ID the
2163.78 -> patterns are going to be different there
2164.74 -> for
2166 -> um for a quest than it would be for a
2167.859 -> user
2168.88 -> also notice that I have this type
2170.619 -> attribute that I'm including on every
2172.3 -> single item in my table because now when
2174.04 -> I'm reading records back from my table
2175.48 -> it's easier for me to determine what
2178 -> type of item I'm getting and just
2179.5 -> deserializing it into some object in my
2181.3 -> application
2183.04 -> finally if you look at the attributes
2184.54 -> here right notice that they have
2185.619 -> different attributes right users have
2187.06 -> user-like attributes quests have Quest
2188.68 -> like attributes because dynamodb is
2190.72 -> schemeless I didn't have to declare
2191.619 -> these up front they'll just sort of
2193.06 -> handle all that for me
2195.52 -> so that's the basics of single table
2197.26 -> design right we're putting lots of
2198.4 -> different items in a single table we're
2199.66 -> using this generic primary key names
2201.16 -> let's talk about why why you would do
2203.44 -> this because this is this is very weird
2204.82 -> if you're if you're sort of new to this
2206.26 -> right
2207.46 -> so I think the first reason is this can
2209.02 -> improve performance by making fewer
2210.579 -> requests which I said is one of the
2211.72 -> points of dynamodb ideally you're
2213.4 -> handling each use case with a single
2215.079 -> request
2216.22 -> let's see how that could happen right if
2217.599 -> we look at our table here notice we have
2219.76 -> an item collection we have a set of
2221.14 -> records that have the same partition key
2223.42 -> but if you look even within that item
2225.099 -> collection right we have different types
2226.48 -> of entities in that item collection we
2227.74 -> have our user record we also have a
2229.78 -> couple of quest records that belong to
2231.22 -> that user
2232.48 -> and that can help us maybe we have an
2234.16 -> access pattern that says list all the
2235.48 -> quests for a user but we also want to
2237.339 -> get that user record with it to sort of
2238.839 -> enrich those quests right just like you
2240.4 -> would with a join interrelational
2241.66 -> database and that's basically what we're
2243.28 -> doing we're pre-joining our data we're
2245.14 -> at right time rather than a read time
2247.54 -> and now it's a consistent operation at
2250.18 -> read time also we don't have to perform
2251.619 -> that sort of that join that CPU work
2255.579 -> every time we do a read right we do it
2257.26 -> once at right and it's good to go
2259.66 -> so that can reduce the number of
2261.94 -> requests to Dynamics we're not going out
2263.74 -> and getting a user and going out to
2265.359 -> giving a quest so we can do it with one
2266.859 -> query to get all those in in one request
2271.599 -> second benefit here is that it can
2273.16 -> reduce cost and to understand that we
2274.839 -> need to understand sort of how dynamodb
2276.4 -> billing works so dynamodb billing is
2278.859 -> different than more servable billing
2280.3 -> because you know you're paying for reads
2282.099 -> and rights directly you're paying for
2283.18 -> the amount of data you read and write
2284.44 -> from your database rather than paying
2286.24 -> for you know server resources right so a
2288.52 -> traditional database is going to charge
2290.02 -> you on CPU on RAM on iops all that stuff
2293.14 -> whereas Dynamo is going to charge you on
2294.7 -> what are called read capacity units rcus
2296.74 -> and right capacity units wcus
2299.56 -> and read capacity units allow you to
2301.119 -> read four kilobytes of data from Dynamo
2302.56 -> right capacity units allow you to write
2303.88 -> one kilobyte of data and that's what
2305.079 -> you're actually charged on with Dynamo
2307.66 -> so there's a tighter link there between
2309.04 -> what you're actually doing and how
2310.54 -> you're charged for it
2312.579 -> another thing note here is is rights are
2314.38 -> charged for both your main table and
2315.94 -> your secondary indexes so if you have
2317.619 -> three secondary indexes on a table you
2319.72 -> write a new record and you're gonna get
2321.099 -> charged on that your main table but also
2322.96 -> for all three of your secondary indexes
2324.52 -> if it's if it's replicated out to that
2328 -> so how could this actually be cheap if
2330.4 -> we're doing single table design let's go
2331.839 -> back to our user record and just think
2333.76 -> about it and of how this works right we
2335.98 -> have this user record and notice that
2337.66 -> it's kind of a split record right if you
2339.76 -> look at some of those attributes we have
2341.2 -> a lot of smaller but fast moving
2343.48 -> attributes right like gold every time
2344.92 -> I'm picking up gold in the game I have
2346.24 -> to go increment that gold value for my
2348.28 -> player like we're saying with total play
2350.2 -> time every 30 seconds you need to
2351.64 -> increment that total play time to
2353.8 -> um to increment to update that total
2355.78 -> play time for that user
2358.06 -> in addition we have one attribute this
2360.22 -> inventory attribute that's a larger but
2361.9 -> slower moving attribute it's larger
2363.4 -> because it has lots of different items
2365.38 -> in there or different inventory
2367.3 -> instances in there but it's also slow
2369.04 -> moving you're probably not changing your
2370.42 -> inventory a lot
2372.64 -> so when you think about Dyno DB billing
2374.619 -> one thing I always tell people is do the
2376.72 -> math since you're actually charged on on
2378.88 -> the resources you're consuming you can
2380.38 -> actually do the math on this stuff get a
2382 -> decent estimation of how much this will
2383.8 -> cost you or even looking at two
2385.3 -> different ways of designing your table
2386.74 -> and see which one will be cheaper
2388.54 -> so let's do the math here right
2391.24 -> here's our situation we have a four
2393.099 -> kilobyte user item which is basically
2394.66 -> broken into less than a kilobyte of core
2396.7 -> data and about three kilobytes of
2397.96 -> inventory
2398.98 -> maybe we have three secondary indexes on
2400.72 -> our table and we want to increment that
2402.579 -> total play time every 30 seconds
2406.24 -> to do this as we've sort of structured
2407.92 -> out if someone plays an hour on our game
2410.2 -> it's going to cost about 2 000 right
2411.94 -> capacity units to do that
2414.579 -> in the math here you know four right
2416.38 -> capacity units because it's a four
2417.579 -> kilobyte item four different targets our
2419.2 -> main table three secondary indexes and
2421.3 -> it's going to happen 120 times an hour
2422.68 -> because we're doing it every 30 seconds
2424.72 -> if you look at that the two big culprits
2427.119 -> here are we have this three kilobyte
2428.619 -> inventory item that's not changing but
2430.18 -> we have to rewrite it every time we go
2431.56 -> increment our total play time and it's
2433.359 -> also getting replicated to three
2434.5 -> secondary indexes when those secondary
2436.3 -> indexes probably don't need to know
2437.32 -> about the inventory for the user right
2438.579 -> we only need the inventory for the user
2440.079 -> when we're fetching the user to sort of
2442.06 -> load up the game or something like that
2443.98 -> so what we could do is use more of a
2446.079 -> single table design model and do
2447.22 -> vertical partitioning there's a great
2448.54 -> blog post that just went out on the AWS
2450.7 -> blog about how to do vertical
2451.839 -> partitioning in dynamodb but this is the
2454.54 -> idea here where you might chop up an
2455.98 -> item into multiple pieces here right and
2457.96 -> we have our small fast moving user item
2460.359 -> that has the gold it has the total play
2463.3 -> time that's going to get updated a lot
2464.68 -> but it's a small item it's less than a
2466 -> kilobyte right we also have a separate
2468.16 -> item that contains our inventory that's
2470.02 -> our larger slow moving item not going to
2473.02 -> be changing as much but it's a lot
2474.28 -> bigger right and let's let's fix the
2475.599 -> math here on how we do it first of all
2477.82 -> instead of having a four kilobyte user
2479.38 -> item that we need to update whenever we
2480.7 -> update the total play time we only need
2481.96 -> to update that smaller one right so now
2483.579 -> it's a kilobyte or less
2486.04 -> and now we can change our math and one
2488.079 -> hour of play time is going to be less
2489.339 -> than than 500 right capacity units right
2491.2 -> so a fourth of what it was before
2493.599 -> and you might say I'm cheating why
2494.92 -> didn't you just put these in two
2495.82 -> separate tables but one thing it allows
2497.8 -> us to do by being in the same table is
2499.54 -> we can still have that query pattern
2501.579 -> where if we need to fetch the user and
2503.2 -> that user's inventory that's still just
2504.88 -> one operation that's a query operation
2506.38 -> and we'll get sort of the user metadata
2508.359 -> and we'll get the inventory all in one
2509.8 -> request and and the query cost is not
2512.079 -> going to change there because query is
2513.22 -> based on the total amount of data you're
2515.079 -> reading not the how many items you're
2516.46 -> reading
2517.359 -> so this is still great for us
2520.9 -> so that's the second benefit of single
2522.4 -> table design it can reduce the cost
2523.66 -> third benefit is it's just it can be a
2526 -> lower maintenance burden right you might
2527.26 -> be coming from a relational database and
2528.7 -> think hey I can have 50 tables in my
2530.079 -> relational database it's just one server
2531.64 -> instance I need to manage but each
2533.619 -> different dynamodb table you have is
2535.78 -> going to be a different piece of
2536.619 -> infrastructure that you need to set up
2537.76 -> alarms for you need to set up capacity
2539.32 -> for all that sort of stuff so if you put
2541 -> it all in one table or fewer tables at
2543.46 -> least now you have less Auto scaling to
2544.9 -> worry about less fewer alarms all that
2546.52 -> stuff
2548.44 -> and really my my bonus the reason I like
2550.72 -> single table design the most is it
2552.64 -> forces you to think about dynamodb
2554.079 -> differently right it helps you to
2555.46 -> realize that even though the word table
2557.32 -> is the same coming from a relational
2558.76 -> database it's not like modeling a
2560.56 -> relational database right
2562.3 -> it makes you think about your access
2563.8 -> patterns first not denormalization
2565.9 -> because if if you hear hey we should be
2567.64 -> using a single table for this you're not
2569.079 -> going to be normalizing your data into a
2571.18 -> table for each entity right and because
2573.46 -> of that you start reaching out and
2574.54 -> thinking about how to reduce multiple
2575.98 -> requests you start thinking about
2577.18 -> latency and how that interacts
2579.339 -> then you start thinking about cost you
2580.78 -> think about the shape of the data and
2582.339 -> see how can I optimize that to work
2584.26 -> better right
2585.819 -> so my big Takeaway on Dynamo or on
2588.4 -> single table design is like I like it I
2590.079 -> think your pattern should be able to
2591.28 -> work with single table design but you
2592.96 -> don't have to feel boxed in right make
2594.28 -> sure you're learning the mechanics
2595.42 -> learning the theory because I think
2596.92 -> that's going to teach you dynamodb data
2599.14 -> modeling a lot better and then
2600.7 -> understand where you can relax this and
2602.2 -> maybe have a couple different tables if
2603.52 -> you have different needs across items in
2605.14 -> your table or things like that
2609.099 -> all right so that single table design
2610.42 -> one other topic I want to talk about
2611.98 -> that I think is is not well understood
2614.14 -> or or um is underrated about dynamodb is
2617.38 -> just around how dynamodb's consistency
2619.359 -> uh how they handle constraints and
2621.819 -> transactions across your application I
2623.38 -> think if you're coming from you know
2625 -> relational database you hear eventual
2626.38 -> consistency that seems scary or or maybe
2628.42 -> you've worked with other nosql databases
2630.099 -> and you worry about how you can maintain
2631.9 -> conditions and constraints in your data
2633.88 -> and I think dynamodb is kind of unique
2636.28 -> in that way
2637.359 -> so let's go back to what Amarth was
2639.04 -> talking about earlier where remember
2640.48 -> your table is going to be sharded into
2642.22 -> these multiple different partitions so
2643.54 -> we have three different partitions each
2645.099 -> one holding a different segment of our
2647.02 -> data but within one individual partition
2649.3 -> we also have three replicas right and
2651.22 -> that's where the issues of of sort of
2652.78 -> data consistency come in right when you
2654.4 -> have replication now you have to think
2655.72 -> about consistency across those
2658.42 -> so let's look let's look at that a
2660.28 -> little closely more closely right within
2662.079 -> a particular partition we have these
2663.46 -> three nodes they're sinking within
2664.72 -> syncing data across each other this is
2666.819 -> going to be called a replica group
2669.64 -> and now if I make a read to dynamodb
2672.4 -> someone wants to go get that get Amorous
2674.74 -> user record that's going to go through
2676.24 -> the request router right it's going to
2678.04 -> that's where it's going to find out
2679.54 -> which partition it needs to go query and
2681.88 -> it's going to get three nodes back and
2683.02 -> then it's going to send it to one of
2684.52 -> those three nodes and it can be any of
2685.839 -> those three nodes that can actually
2686.859 -> answer that request and it can maybe
2688.96 -> even be kept in the in the same AZ as
2691.18 -> Amarth was mentioning earlier
2692.92 -> but a write operation is different right
2694.48 -> if I want to do a write operation and
2696.52 -> update that that amorth user record it's
2699.04 -> not going to work the same way it's not
2700.18 -> going to go to any of those nodes but
2701.38 -> one of those nodes is going to be
2702.46 -> elected the leader So within a replica
2704.38 -> group there's always a leader it's a
2705.76 -> single leader this is different from how
2707.319 -> you know the original Dynamo work in in
2709.3 -> the Dynamo paper from 2006 is different
2711.22 -> from how Cassandra works and other nosql
2712.839 -> databases but there's a single leader in
2714.7 -> a Dynamo replica group and now when you
2717.04 -> do a write that request router is going
2719.14 -> to send it to that leader node that
2721.24 -> leader node is going to write it locally
2722.44 -> it's going to reach out to the two
2723.64 -> replicas and tell them to write it
2725.26 -> locally and as soon as one of those
2726.4 -> replicas comes back with a response then
2728.5 -> it's going to return to that user and
2730.18 -> say hey this has been this has been
2731.26 -> durably committed to your dynamodb table
2733.66 -> you can see there's that third node that
2735.099 -> maybe hasn't quite committed that read
2736.9 -> yet and you can imagine if someone comes
2738.88 -> in at that particular time and requests
2740.619 -> that Amherst record you know maybe they
2742.599 -> hit that third node that's just slightly
2744.16 -> behind and that's where you can get an
2745.72 -> eventually consistent read that could be
2747.16 -> a stale read That's slightly out of date
2748.96 -> which what's been acknowledged in your
2750.64 -> in your application right
2752.26 -> so Dynamo does have that again just just
2755.319 -> to cover what's going on here each
2756.46 -> partition is going to replicate it
2757.66 -> across three nodes across three azs this
2759.94 -> is called a replica group any node can
2761.98 -> serve a read by default but the leader
2764.26 -> has to process that right and when that
2765.76 -> Leader's processing that's right it's
2767.38 -> going to make sure that two of the three
2768.52 -> nodes have written it so at least one of
2769.839 -> those replicas as well
2772.9 -> so why use a strong leader right this is
2774.64 -> different from from Casino why why use a
2777.579 -> strong leader I think there are two big
2778.72 -> benefits of a strong leader that are
2780.22 -> that are underrated
2781.839 -> number one is if you do need strong
2783.88 -> consistency on reads you can avoid it in
2785.68 -> dynamodb by going straight to the leader
2787.599 -> right so we saw how you can get a stale
2789.579 -> read from from the sort of lagging node
2791.26 -> but that's unacceptable to you when
2793.3 -> you're doing a a read a read request you
2796.599 -> can specify I want a consistent read on
2798.28 -> this one
2799.3 -> and now when it goes to that request
2800.56 -> router it's going to Route it to your
2802.119 -> leader node which has the most recent
2803.56 -> the most up-to-date version of that data
2805.9 -> and you'll get the the latest version
2807.819 -> you won't have any eventual consistency
2809.26 -> issues there
2810.46 -> so that's one benefit that's nice but I
2812.14 -> think the bigger benefit is on the right
2813.819 -> operations right there's one canonical
2815.68 -> version of each item so with Cassandra
2818.8 -> you know you could be writing to
2820.72 -> multiple different nodes at the same
2822.28 -> time in a replica group and they could
2823.9 -> both accept those rights and now you
2825.28 -> need to figure out how to how to
2826.66 -> reconcile those is your database going
2828.099 -> to reconcile it using something like
2829.3 -> last right wins or do you have to push
2830.98 -> that down to your application layer
2832.42 -> right there's there's just difficult
2833.74 -> reconciliation logic that you need to
2835.66 -> think about now
2837.4 -> also not only that but it's difficult in
2840.46 -> a lot of these nosql databases to handle
2842.44 -> conditional rights right because because
2844.359 -> there could be multiple versions of that
2845.92 -> data you need to do coordination at
2848.02 -> right time so and coordination is
2849.46 -> expensive so you have to reach out to
2850.599 -> all those nodes that coordinate to agree
2851.92 -> on a value whereas with Dynamo with that
2854.38 -> single single leader you can do cheap
2856.119 -> and easy conditional rights right and
2857.56 -> conditional rights are super beneficial
2859.06 -> I always say when you're doing write
2860.8 -> access patterns make sure you have a
2862.3 -> column that says conditions because
2863.98 -> there are certainly conditions that you
2865.18 -> want to maintain in your access patterns
2867.22 -> right you don't want to have multiple
2868.24 -> users with the same username right you
2870.52 -> don't want to add a user to a guild if
2871.839 -> they're already in a different Guild or
2872.92 -> something like that
2874.72 -> so condition next session super useful
2876.52 -> right you can assert the existence of an
2878.26 -> item so if someone's selling a piece of
2880.06 -> inventory you can assert that they
2881.079 -> actually have that piece of inventory so
2882.52 -> they're not selling something that
2883.3 -> doesn't exist
2885.22 -> or you can assert uniqueness right
2886.72 -> non-existence right when we're creating
2888.28 -> that new user we want to make sure
2889.42 -> there's not a user with that username
2890.8 -> already
2892.119 -> or you can assert constraints based on
2893.8 -> attributes and the items right Lyft uses
2896.5 -> dynamodb to to schedule ride sharing
2899.079 -> right and when I'm in an application I
2900.819 -> request a lift ride in the back end it
2902.92 -> can go read from its dynamodb table and
2904.72 -> say hey give me all the drivers that are
2906.819 -> currently signed in that don't have a
2909.099 -> rider that are in this particular area
2910.839 -> and that could be an eventually
2911.8 -> consistent read you know maybe that's a
2913.839 -> little behind and two of those drivers
2915.46 -> actually have rides but when I actually
2917.14 -> go when that back end actually goes to
2918.7 -> assign it to me it can assert conditions
2920.68 -> like saying make sure this driver is
2922.24 -> still still on and active make sure they
2924.22 -> haven't already picked up another ride
2925.42 -> and you can prevent yourself from
2926.5 -> getting into a bad situation there so
2928.9 -> make sure you're using these conditions
2930.099 -> and the great thing about this is
2931.119 -> there's no latency penalty of doing this
2933.28 -> because of that single leader right
2934.48 -> there's that one canonical version of
2935.859 -> that of that record that right is going
2937.42 -> to go to that single leader it's
2938.5 -> actually going to be faster if your
2940.06 -> conditional right fails because now it
2941.26 -> doesn't need to reach out to those other
2942.46 -> replicas in your replica group
2945.46 -> another great thing about this is
2946.839 -> because conditions are easy we can also
2948.64 -> add in transactions right so dynodb has
2951.04 -> transactions these were added in 2018
2953.079 -> this allows you to operate on multiple
2954.94 -> items in a single All or Nothing
2956.14 -> operation right so if two users are
2958.66 -> trading in your in your game one selling
2961.119 -> a weapon one's buying it you can assert
2962.619 -> that hey this user actually has the
2964.24 -> weapon this user has the amount of gold
2966.04 -> to make it happen and use a transaction
2967.96 -> that way right so you get all those
2969.819 -> benefits which are which are more
2971.079 -> difficult if conditions are expensive in
2973.18 -> your database
2974.319 -> you can also use this for identity right
2977.14 -> if you have maybe you're incrementing
2978.88 -> some sort of counter through stream
2980.079 -> processing you can you can include a
2981.579 -> client request token through
2982.66 -> transactions to make sure that that
2984.04 -> request is idempotent
2986.44 -> big thing here with with transactions as
2988.24 -> with any transactions like watch out for
2990.339 -> contention right you don't want to be
2991.54 -> involving a record in transactions if
2993.7 -> it's a very hot item because then
2995.079 -> there's going to be contention this is
2996.28 -> true in a relational database in a in
2998.56 -> nosql any of that stuff
3000.9 -> so just the takeaways here because again
3002.76 -> I think this is really underrated about
3004.38 -> how Dynamo works with consistency right
3006.359 -> use those con condition Expressions as
3008.76 -> much as you can that will help maintain
3010.2 -> those constraints in your data you don't
3011.94 -> have to worry about that as much
3013.859 -> again there's no additional cost there's
3016.02 -> no additional right cost there's no
3017.28 -> additional latency hit it's actually
3018.54 -> faster if your condition expression
3020.22 -> fails and you can use that to maintain
3021.9 -> uniqueness prevent double assignment
3024.72 -> if you need to that's where you can pull
3026.22 -> in dynamodb transactions if you need to
3028.2 -> maintain constraints across multiple
3029.46 -> items big issue there watch for
3031.56 -> contention and there's also going to be
3033.06 -> an additional cost for that that
3034.74 -> transaction management
3036.54 -> in fact I think these right things you
3039.96 -> know on the right path about consistency
3041.339 -> that's the important part but if you do
3042.66 -> need strong consistency on your reads
3044.28 -> that's where you can use that consistent
3046.079 -> read if it's truly needed give you a
3047.46 -> strongly consistent view of your data
3049.02 -> with a note that this isn't available on
3051.18 -> on global secondary indexes
3053.52 -> all right now on some of that stuff
3054.78 -> Amber's going to talk about some of the
3056.88 -> infrastructure underlying that
3059.579 -> yep
3061.559 -> yeah sorry
3067.26 -> so how do indexes work Alex talked about
3070.079 -> how indexes are stored as Separate
3072.839 -> Tables let's take some sample data here
3075.359 -> I have orders customers and date and
3078.3 -> potentially other attributes which I'm
3079.92 -> not showing you here the thing I'll
3082.079 -> point out is
3083.88 -> uh the second third and fourth rows are
3086.099 -> with the same customer that's customer
3087.839 -> ID which starts with 204. and all the
3090.54 -> order IDs are unique so on this table
3093.839 -> the order ID is probably the primary key
3097.079 -> it's Unique that's the access pattern
3099.599 -> give me everything about a particular
3102.359 -> order
3103.5 -> very common thing you'd want to do
3104.88 -> another thing I would want to do is give
3107.64 -> me all the orders by a particular
3109.5 -> customer
3110.94 -> and give them back to me ordered by the
3113.16 -> data on which the order happened
3114.9 -> so
3116.46 -> I could have an alternate access pattern
3118.319 -> with the customer ID and the order date
3120.059 -> and the way in which you do that in
3121.5 -> dynamodb
3124.02 -> is you have the table
3125.94 -> and the table is
3127.5 -> defined with the partition key being the
3129.96 -> order ID
3131.22 -> and the global secondary index is
3133.14 -> defined with a customer ID and the order
3134.94 -> date
3136.38 -> Alex did point this out I'm going to
3138.119 -> stress
3139.38 -> the primary key on a table has to be
3142.02 -> unique
3143.88 -> there is no primary key on an index
3146.64 -> there's a key
3148.26 -> there could be
3149.9 -> non-unique attributes
3153.42 -> but there's no guarantee that it's going
3155.28 -> to be unique keep that in mind so
3157.8 -> there's the table and there's a GSI you
3160.68 -> can query the table all day long without
3162.54 -> impacting the GSI
3164.28 -> you can query the GSI all day long and
3166.68 -> not impact the table that is something
3168.72 -> which you cannot do in most other
3170.04 -> databases
3171.42 -> it's a useful pattern to keep in mind so
3174.18 -> how does an update to a GSI actually
3175.92 -> happen
3177.48 -> that's assume I need to write another
3179.46 -> order here
3181.8 -> um a customer in this particular case
3184.619 -> four two four ordered something else or
3187.559 -> sorry the customer ID is one five six
3189.72 -> something or the other uh ordered
3191.7 -> something new
3193.8 -> the item is written first to the table
3197.66 -> asynchronously the item is propagated to
3200.819 -> the GSI
3203.4 -> I mentioned two ways in which dynamodb
3205.5 -> achieved scale one is horizontal
3207.599 -> scalability and the other is eventual
3209.46 -> consistency
3210.599 -> Alex talked about eventual consistency
3212.579 -> on the table this is eventual
3214.68 -> consistency on the index
3217.079 -> by not requiring the GSI to be updated
3220.079 -> synchronously we give you predictable
3221.94 -> single single digit millisecond response
3224.339 -> times
3225.359 -> but your application needs to understand
3227.339 -> that if you write an item to a table
3230.579 -> and virtually instantaneously you query
3233.22 -> the GSI maybe you're not going to see
3234.9 -> the item there propagation delays sub
3237.54 -> millisecond sometimes in the low
3239.88 -> milliseconds
3241.44 -> you'll get the item into the GSA
3244.02 -> keep that in mind okay
3246.839 -> the other thing which Alex talked about
3249.3 -> was transactions the way in which we do
3251.76 -> transactions in dynamodb
3254.04 -> for those of you who are familiar with
3255.78 -> transactional relational database very
3258.18 -> very similar
3259.859 -> in the middle column there with the
3262.44 -> fleets of hosts which we operate I
3264.72 -> mentioned one called a transaction
3265.92 -> coordinator for those of you who are
3267.96 -> familiar with two-phase commit it's the
3270 -> same thing so the way in which two-phase
3272.76 -> commit works
3274.8 -> is and we get standard asset semantics
3277.38 -> as a result the way in which it works is
3279.3 -> that every request to do a right to
3283.2 -> multiple items
3284.7 -> starts with a begin the transaction
3286.859 -> coordinator comes along
3288.66 -> says you're beginning a transaction
3291.66 -> there's a bunch of Rights I'm going to
3293.76 -> send the rights to the various storage
3294.96 -> nodes now remember these rights could be
3297.54 -> to multiple items
3299.579 -> for each item
3301.98 -> you know in the right what is the
3303.9 -> partition key being modified identify
3306.42 -> the storage node send it to that storage
3308.22 -> node could be in different tables
3310.8 -> once the rights are done there's a
3313.319 -> prepare the transaction coordinator
3315.24 -> tells all the storage nodes prepare
3317.94 -> at this point consists all the condition
3320.46 -> checks are evaluated and the storage
3322.559 -> nodes decide whether or not they're
3324.24 -> willing to accept the right
3326.7 -> one of two things can happen
3329.339 -> all the storage nodes can come back and
3331.14 -> say we're good
3332.88 -> okay if that's the case the transaction
3335.64 -> coordinator says commit and all the
3338.28 -> rights happen
3340.14 -> guaranteed either all of them happen or
3344.16 -> suppose the other thing happens at least
3347.16 -> one storage node comes back and says I'm
3349.619 -> sorry condition check failed
3351.839 -> the transaction coordinator comes along
3353.579 -> and tells all the storage nodes roll
3355.2 -> back
3357.24 -> either all the rights go through or none
3359.7 -> of the rights go through we give you
3361.44 -> transactions here again if you're doing
3363.9 -> the kind of thing which Alex was talking
3365.4 -> about you're handing some gold from one
3367.92 -> to another in exchange for some weapon
3371.339 -> or
3372.48 -> the ride share example
3374.94 -> using a condition check in the
3377.16 -> transaction
3378.359 -> will make sure that if the condition
3379.98 -> check fails the transaction rolls back
3383.4 -> okay very useful pattern
3386.339 -> the thing which I love the most about
3387.96 -> all of these um
3390.42 -> things is listening to people who use
3393.18 -> dynamodb and figuring out how they do it
3396.119 -> so I understand nosql data modeling is
3398.94 -> different from the standard relational
3400.38 -> database
3401.46 -> but
3402.54 -> many of the things which you do with the
3404.46 -> relational database carry forward
3407.88 -> take the time to learn the proper
3410.819 -> dynamodb Concepts and model your data
3412.98 -> properly
3414.119 -> think about what it's going to cost
3415.68 -> think about what the latency is going to
3417.839 -> be
3418.44 -> think about what the underlying
3419.88 -> infrastructure is
3421.319 -> think about the API design which you
3423.3 -> want
3424.68 -> and finally
3426.3 -> when you're operating your application
3428.22 -> think about what it's going to take to
3430.619 -> do that
3431.52 -> how many tables do you need fewer tables
3433.8 -> is easier for you
3436.14 -> so with that
3437.819 -> you want to come on up and we have I
3440.04 -> think a couple of minutes for questions
3441.24 -> but we're going to be outside if anybody
3443.16 -> wants to stop us afterwards I'm going to
3445.859 -> be at the database Booth so if you want
3448.319 -> to come by there as well there's a
3450.18 -> microphone here somewhere in case we'll
3452.16 -> probably have to take it outside we
3453.66 -> asked for four hours they gave us once
3455.099 -> we used the whole hour but uh we'll be
3457.559 -> outside as long as you all want thank
3459.119 -> you

Source: https://www.youtube.com/watch?v=SC-YAPgJpms