بررسی عمیق DynamoDB برای مصاحبههای طراحی سیستم
مدل داده، partition key و sort key، ایندکسهای ثانویه GSI و LSI، دسترسی به داده، مدلهای سازگاری، مقیاسپذیری، مدل قیمتگذاری، DAX و Streams در DynamoDB و زمان مناسب استفاده از آن در مصاحبه
DynamoDB
در این فصل با نحوه استفاده از DynamoDB برای حل تعداد زیادی از مسائل طراحی سیستم آشنا میشویم.
DynamoDB یک سرویس کلید-مقدار (key-value) کاملاً مدیریتشده و بسیار مقیاسپذیر است که AWS ارائه میدهد. کلمههای پرزرقوبرق. ولی این به چه معناست و چرا مهم است؟
- کاملاً مدیریتشده: یعنی AWS همه جنبههای عملیاتی پایگاه داده را به عهده میگیرد. ماهیت کاملاً مدیریتشده به AWS اجازه میدهد همه کارهای عملیاتی (تهیه سختافزار، پیکربندی، patch و مقیاسدهی) را انجام دهد و توسعهدهندگان را آزاد کند تا روی توسعه اپلیکیشن تمرکز کنند.
- بسیار مقیاسپذیر: DynamoDB میتواند حجم عظیمی از داده و ترافیک را مدیریت کند. خودکار بزرگ یا کوچک میشود تا با نیازهای اپلیکیشن شما هماهنگ شود، بدون downtime یا افت کارایی.
- کلید-مقدار: DynamoDB پایگاه داده NoSQL است، یعنی از مدل پایگاه داده رابطهای سنتی استفاده نمیکند. در عوض از مدل کلید-مقدار استفاده میکند که ذخیره و بازیابی انعطافپذیر داده را ممکن میکند.
نتیجه این است که DynamoDB بسیار آسان برای استفاده است و میتواند مقیاس عظیمی برای پشتیبانی از طیف گستردهای از اپلیکیشنها داشته باشد. بهخصوص برای مصاحبههای طراحی سیستم، تقریباً هر چیزی را که از پایگاه داده میخواهید دارد. حتی حالا تراکنش را هم پشتیبانی میکند! این یکی از بزرگترین انتقادهای گذشته به DynamoDB را بیاثر میکند.
نکته مهم این است که DynamoDB متنباز نیست، پس نمیتوانیم مثل تحلیل فناوریهای متنباز مانند Kafka و Redis بهراحتی ساختار داخلی آن را توصیف کنیم. در عوض بیشتر روی نحوه تعامل شما با آن تمرکز میکنیم. برای نگاه به پشت صحنه، به اطلاعات محدودی تکیه میکنیم که AWS از طریق مستندات و مقاله DynamoDB (USENIX ATC 2022) ارائه کرده است.
در این بررسی عمیق، دقیقاً توضیح میدهیم برای اینکه در مصاحبه طراحی سیستم از DynamoDB استفاده کنید، چه چیزهایی را باید بدانید. در این مسیر یادگیری عملی هم به دست میآورید که بعداً میتوانید در پروژههای خودتان به کار ببرید. شروع کنیم!
کاندیداها اغلب میپرسند: «اصلاً اجازه دارم در مصاحبه از DynamoDB استفاده کنم؟» جواب ساده است: از مصاحبهگرتان بپرسید! خیلیها میگویند بله و فقط انتظار دارند بدانید چطور از آن استفاده کنید. بعضیها ممکن است بگویند نه و منتظر جایگزینهای متنباز باشند که از vendor lock-in جلوگیری کنند. مثل همیشه فقط بپرسید.
مدل داده
در DynamoDB، دادهها در جدولها سازماندهی میشوند و هر جدول چند item دارد که رکوردهای تکی را نشان میدهند. این دقیقاً مثل پایگاه داده رابطهای است، اما با چند تفاوت مشخص که برای مقیاسپذیری و انعطاف طراحی شدهاند.
- Table (جدول): ساختار داده سطح بالا در DynamoDB است و هر جدول با یک کلید اصلی اجباری تعریف میشود که itemهای آن را یکتا شناسایی میکند. جدولها از ایندکسهای ثانویه پشتیبانی میکنند و کوئری روی attributeهای غیرکلید را برای بازیابی همهمنظورهتر داده ممکن میکنند.
- Item (مورد): معادل سطر در پایگاه داده رابطهای است و مجموعهای از attributeها دارد. هر item باید کلید اصلی داشته باشد و میتواند تا ۴۰۰ کیلوبایت داده، از جمله همه attributeهایش، داشته باشد.
- Attribute (ویژگی): جفتهای کلید-مقدار که داده یک item را تشکیل میدهند. میتوانند نوعهای مختلفی داشته باشند، از جمله نوعهای اسکالر (رشته، عدد، بولی) و نوعهای مجموعه (مجموعهای از رشتهها یا عددها). attributeها میتوانند تودرتو هم باشند و ساختارهای داده پیچیده را داخل یک item بسازند.
راهاندازی DynamoDB ساده است: میتوانید جدولها را مستقیم در کنسول AWS بسازید و بلافاصله شروع به وارد کردن داده کنید. برخلاف RDBMS سنتی، DynamoDB schemaless است، یعنی لازم نیست قبل از درج داده schema تعریف کنید. یعنی itemهای یک جدول میتوانند مجموعه attributeهای متفاوتی داشته باشند و attributeهای جدید را میتوان هر زمان بدون اثر روی itemهای موجود اضافه کرد. این طراحی بدون schema انعطاف زیادی میدهد، اما اعتبارسنجی دقیق داده را در سطح اپلیکیشن میطلبد، چون DynamoDB یکنواختی attributeها را بین itemها اعمال نمیکند.
ساخت جدول
جدول کاربران در DynamoDB را در نظر بگیرید که ساختارش اینطور است:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
{
"PersonID": 101,
"LastName": "Smith",
"FirstName": "Fred",
"Phone": "555-4321"
},
{
"PersonID": 102,
"LastName": "Jones",
"FirstName": "Mary",
"Address": {
"Street": "123 Main",
"City": "Anytown",
"State": "OH",
"ZIPCode": 12345
}
},
{
"PersonID": 103,
"LastName": "Stephens",
"FirstName": "Howard",
"Address": {
"Street": "123 Main",
"City": "London",
"PostalCode": "ER3 5K8"
},
"FavoriteColor": "Blue"
}
هر item نماینده یک کاربر با attributeهای متفاوت است. توجه کنید بعضی کاربران attributeهایی دارند که بقیه ندارند، مثل FavoriteColor، و این انعطاف DynamoDB را در مدیریت attribute نشان میدهد.
با اینکه DynamoDB برای انتقال داده از JSON استفاده میکند، این فقط قالب انتقال است. قالب ذخیرهسازی واقعی DynamoDB اختصاصی است و به کاربران اجازه میدهد روی مدلسازی داده تمرکز کنند، بدون اینکه وارد پیچیدگیهای ذخیرهسازی فیزیکی داده شوند.
Partition Key و Sort Key
جدولهای DynamoDB با یک کلید اصلی تعریف میشوند که میتواند یک یا دو attribute داشته باشد.
- Partition Key: یک attribute واحد که همراه با sort key (در صورت وجود) هر item را در جدول یکتا شناسایی میکند. DynamoDB از مقدار partition key برای تعیین محل فیزیکی item در پایگاه داده استفاده میکند. این مقدار hash میشود تا partitionی که item در آن ذخیره میشود تعیین شود.
- Sort Key (اختیاری): attribute اضافهای که وقتی با partition key ترکیب شود، کلید اصلی مرکب (composite primary key) را میسازد. از sort key برای مرتب کردن itemهایی با مقدار partition key یکسان استفاده میشود و جستجوی بازهای کارآمد و مرتبسازی داخل partition را ممکن میکند.
در مصاحبه باید مطمئن شوید هنگام معرفی DynamoDB، partition key و در صورت لزوم sort key را مشخص میکنید. این انتخاب برای بهینه کردن کارایی کوئری و کارایی بازیابی داده مهم است. مثل هر پایگاه داده دیگر، partition keyای انتخاب میکنید که برای رایجترین الگوهای کوئری اپلیکیشن بهینه باشد و داده را یکنواخت بین partitionها توزیع کند. اگر باید کوئری بازهای یا مرتبسازی انجام دهید، باید sort key هم مشخص کنید.
مثلاً اگر اپلیکیشن چت گروهی ساده میسازید، منطقی است chat_id را partition key و message_id را sort key بگیرید. به این ترتیب میتوانید همه پیامهای یک گروه چت مشخص را کوئری کنید و قبل از نمایش به کاربر به ترتیب زمانی مرتب کنید.
توجه کنید که ما message_id را بهجای timestamp بهعنوان sort key میگذاریم. با اینکه timestamp برای مرتب کردن پیامها شهودی به نظر میرسد، یکتا بودن را تضمین نمیکند و چند پیام میتوانند در یک میلیثانیه ساخته شوند. شناسه یکنوا و افزایشی (مثل شمارنده auto-increment یا UUID v1) هم ترتیب زمانی و هم یکتا بودن را میدهد. شناسه را میتوان با تکنیکهایی مثل اینها ساخت:
- شمارنده auto-increment به ازای هر partition.
- UUIDهای مبتنی بر timestamp (UUID v1).
- شناسه Snowflake.
- ULID.
کلید اصلی
اما در پشت صحنه واقعاً چه اتفاقی میافتد؟
DynamoDB برای مدیریت کارآمد توزیع و بازیابی داده از ترکیب Consistent Hashing و B-Tree استفاده میکند.
- Consistent Hashing برای partition key: محل فیزیکی داده را partition key تعیین میکند. این کلید را با Consistent Hashing hash میکنیم تا بفهمیم کدام گره در cluster DynamoDB آن item را ذخیره میکند. این تضمین میکند داده یکنواخت در cluster توزیع شود.
- B-Tree برای sort key: داخل هر partition، DynamoDB itemها را در ساختار داده B-Tree سازمان میدهد که با sort key ایندکس شده است. این کار کوئری بازهای کارآمد و بازیابی مرتب داده را داخل partition ممکن میکند.
- عملیات با کلید مرکب: هنگام جستجو با هر دو کلید، DynamoDB اول از hash مربوط به partition key برای پیدا کردن گره مناسب استفاده میکند و بعد از sort key برای پیمایش B-Tree و پیدا کردن itemهای مشخص.
این رویکرد دولایه به DynamoDB اجازه میدهد هم به مقیاسپذیری افقی (از طریق پارتیشنبندی) برسد و هم به جستجوی کارآمد داخل partitionها (از طریق ایندکس B-Tree). همین ترکیب است که DynamoDB را قادر میکند حجم عظیمی از داده را مدیریت کند و همزمان برای کوئری با partition key و sort key کارایی سریع و قابلپیشبینی بدهد.
ایندکسهای ثانویه
اما اگر بخواهید دادههایتان را با attributeای که partition key نیست کوئری کنید چه؟ اینجاست که ایندکس ثانویه وارد میشود. DynamoDB از دو نوع ایندکس ثانویه پشتیبانی میکند.
- Global Secondary Index (GSI): ایندکسی با partition key و sort key اختیاری که با partition key جدول فرق دارد. GSIها اجازه میدهند itemها را بر اساس attributeهایی غیر از partition key جدول کوئری کنید. چون GSIها partition key متفاوتی دارند، داده در partitionهای فیزیکی کاملاً متفاوت از جدول پایه ذخیره میشود و جداگانه replicate میشود.
- Local Secondary Index (LSI): ایندکسی با همان partition key کلید اصلی جدول، اما sort key متفاوت. LSIها کوئری بازهای و مرتبسازی را داخل یک partition ممکن میکنند. چون LSIها همان partition key جدول پایه را دارند، روی همان partitionهای فیزیکی itemهایی که ایندکس میکنند ذخیره میشوند.
فهمیدن تفاوت ذخیرهسازی فیزیکی GSI و LSI مهم است. GSIها partition و replica مستقل خودشان را دارند، که انعطاف کوئری بیشتری میدهد اما فضای ذخیرهسازی و سربار پردازش اضافه میخواهد. در مقابل، LSIها بهصورت محلی کنار itemهای جدول پایه ذخیره میشوند، که آنها را برای کوئری داخل یک partition کارآمدتر میکند اما انعطافشان را محدود میکند.
عملاً در هر دو مورد، این ایندکسها فقط در کنسول AWS یا از طریق AWS SDK پیکربندی میشوند. DynamoDB بقیه کار را مدیریت میکند و تضمین میکند این ایندکسها با تغییر داده نگهداری و بهروز شوند.
وقتی باید داده را کارآمد با attributeهایی که partition key نیستند جستجو کنید، میخواهید GSI معرفی کنید. مثلاً اگر جدول چتی دارید که پیامهای اپلیکیشن چتتان را نگه میدارد، partition key جدول اصلی احتمالاً chat_id با sort key روی message_id است. به این ترتیب بهراحتی میتوانید همه پیامهای یک چت مشخص را به ترتیب زمان مرتب کنید. اما اگر بخواهید همه پیامهایی را که یک کاربر در همه چتها فرستاده نشان دهید چه؟ حالا به GSI با partition key برابر user_id و sort key برابر message_id نیاز دارید.
LSIها وقتی مفیدند که باید کوئری بازهای یا مرتبسازی داخل یک partition روی attributeای غیر از sort key انجام دهید. به اپلیکیشن چتمان برگردیم. میتوانیم از قبل داخل یک گروه چت بر اساس message_id مرتب کنیم، اما اگر بخواهیم پیامهای دارای بیشترین پیوست را در یک گروه چت کوئری کنیم چه؟ میتوانیم LSI روی attribute به نام num_attachments بسازیم تا این کوئریها آسان شوند و پیامهای دارای پیوست زیاد را سریع پیدا کنیم.
| ویژگی | Global Secondary Index (GSI) | Local Secondary Index (LSI) |
|---|---|---|
| تعریف | ایندکس با partition key و sort key اختیاری متفاوت از جدول اصلی | ایندکس با همان partition key جدول اصلی اما sort key متفاوت |
| زمان استفاده | وقتی باید روی attributeهایی که بخشی از کلید اصلی نیستند کوئری کنید | وقتی برای جستجو داخل همان partition key به sort keyهای اضافه نیاز دارید |
| محدودیت اندازه | ندارد | محدود به ۱۰ گیگابایت برای هر partition key |
| ظرفیت (throughput) | واحدهای ظرفیت خواندن و نوشتن جدا از جدول پایه | واحدهای ظرفیت خواندن و نوشتن جدول پایه را به اشتراک میگذارد |
| سازگاری | فقط سازگار نهایی (eventually consistent) | هم سازگار نهایی (پیشفرض) و هم سازگار قوی |
| حذف | حذف GSI روی جدول پایه اثر ندارد | بدون حذف جدول پایه نمیتوان حذفش کرد |
| حداکثر تعداد | حداکثر ۲۰ GSI در هر جدول | حداکثر ۵ LSI در هر جدول |
| مثال کاربرد | GSI برای جستجوی سراسری، مثل پیدا کردن کاربر با ایمیل در پایگاه داده کاربران | LSI برای جستجوی محلی داخل partition، مثل پیدا کردن سفارشهای اخیر یک مشتری |
در پشت صحنه چه میشود؟
ایندکسهای ثانویه در DynamoDB بهصورت جدولهای جداگانهای پیادهسازی میشوند که سیستم خودکار نگهداریشان میکند.
GSIها:
- هر GSI اساساً یک جدول جداگانه با طرح partition مخصوص خودش است.
- وقتی itemی در جدول اصلی اضافه، بهروز یا حذف میشود، DynamoDB بهصورت ناهمزمان GSI را بهروز میکند.
- GSIها از همان مکانیزم hash جدول اصلی استفاده میکنند، اما با partition key و sort key متفاوت.
- این کار کوئری کارآمد روی attributeهای غیرکلید اصلی را در همه partitionها ممکن میکند.
LSIها:
- LSIها کنار partitionهای جدول اصلی قرار میگیرند و همان partition key را به اشتراک میگذارند.
- در هر partition یک ساختار B-Tree جداگانه دارند که روی sort key مربوط به LSI ایندکس شده است.
- بهروزرسانی LSIها همزمان با بهروزرسانی جدول اصلی انجام میشود و سازگاری قوی را تضمین میکند.
نگهداری ایندکس:
- DynamoDB تغییرات را از جدول اصلی به همه ایندکسهای ثانویه خودکار منتشر میکند.
- برای GSIها این انتشار سازگار نهایی است، در حالی که برای LSIها سازگار قوی است.
- سیستم ظرفیت نوشتن اضافهای را که برای بهروزرسانی ایندکس لازم است مدیریت میکند.
پردازش کوئری:
- وقتی کوئری از ایندکس ثانویه استفاده میکند، DynamoDB کوئری را به جدول ایندکس مناسب (برای GSIها) یا ساختار ایندکس (برای LSIها) هدایت میکند.
- بعد از partition ایندکس و مکانیک sort key برای بازیابی کارآمد داده موردنظر استفاده میکند.
دسترسی به داده
قبلاً کمی به این موضوع پرداختیم، اما عمیقتر ببینیم دسترسی به داده در DynamoDB چطور است. دو راه اصلی برای دسترسی به داده در DynamoDB هست: عملیات Scan و Query.
- عملیات Scan: همه itemهای جدول یا ایندکس را میخواند و نتایج را در پاسخی صفحهبندیشده برمیگرداند. Scan وقتی مفید است که باید همه itemهای جدول یا ایندکس را بخوانید، اما برای مجموعه دادههای بزرگ ناکارآمد است، چون باید هر item را بخواند، و در صورت امکان باید از آن پرهیز کرد.
- عملیات Query: itemها را بر اساس attributeهای کلید اصلی یا کلید ایندکس ثانویه بازیابی میکند. Query از Scan کارآمدتر است، چون فقط itemهایی را میخواند که با شرط کلید مشخصشده میخوانند. Queryها را میتوان برای کوئری بازهای روی sort key هم به کار برد.
برخلاف SQL، Dynamo زبان کوئری ساختاریافته ندارد. در عوض، برای تعامل با پایگاه داده از AWS SDK یا کنسول AWS استفاده میکنید. یک مثال ساده از کوئری روی جدول کاربران را ببینیم.
در SQL مینویسید:
1
SELECT * FROM users WHERE user_id = 101
اما در DynamoDB این به عملیات query مثل این ترجمه میشود:
1
2
3
4
5
6
7
8
9
10
11
12
const params = {
TableName: 'users',
KeyConditionExpression: 'user_id = :id',
ExpressionAttributeValues: {
':id': 101
}
};
dynamodb.query(params, (err, data) => {
if (err) console.error(err);
else console.log(data);
});
برای انجام عملیات scan، بهجای query از متد scan استفاده کنید. معادل SQL:
1
SELECT * FROM users
عملیات scan در DynamoDB:
1
2
3
4
5
6
7
8
const params = {
TableName: 'users'
};
dynamodb.scan(params, (err, data) => {
if (err) console.error(err);
else console.log(data);
});
هنگام کار با Dynamo معمولاً میخواهید تا جای ممکن از عملیات scan گران پرهیز کنید. اینجا مدلسازی دقیق داده وارد کار میشود. با انتخاب partition key و sort key مناسب میتوانید مطمئن شوید کوئریهایتان کارآمدند.
هنگام کوئری گرفتن از DynamoDB، مهم است که بهطور پیشفرض کل item (رکورد) را میخوانید، برخلاف SQL که میتوانید fieldهای مشخص را انتخاب کنید. این میتواند از نظر واحدهای ظرفیت خواندن (RCU) و پهنای باند شبکه، بهخصوص برای itemهای بزرگ، پرهزینه باشد. برای جلوگیری از انتقال داده غیرضروری، باید دادههایتان را مناسب normalize کنید.
مثلاً سناریویی را در نظر بگیرید که Yelp را طراحی میکنید و باید جزئیات کسبوکار و نقدها را ذخیره کنید. ممکن است جدول کسبوکار با attributeهایی مثل business_id، نام، آدرس، شهر، ایالت، کد پستی، دسته و زیردسته داشته باشید. با اینکه میتوانید فهرست نقدها را در جدول کسبوکار ذخیره کنید، یعنی هر بار که اطلاعات پایه کسبوکار را میخوانید، باید کل سابقه نقدها را هم بخوانید، حتی اگر فقط نام و آدرس کسبوکار را بخواهید. در عوض عاقلانه است که نقدها را در جدول جداگانهای بکشید و آن جدول را با business_id کوئری کنید.
قضیه CAP
معمولاً در مرحله الزامات غیرعملکردی (Non-Functional Requirements) مصاحبه درباره سازگاری و دسترسپذیری تصمیمهای اولیه میگیرید. پس مهم است پایگاه دادهای انتخاب کنید که با آن الزامات همراستا باشد.
بیشتر کاندیداهایی که با آنها کار میکنم وقتی به دسترسپذیری و مقیاسپذیری بالا نیاز دارند DynamoDB را انتخاب میکنند. این اشتباه نیست، اما مثل بحث سنتی SQL در برابر NoSQL، قدیمی شده است.
DynamoDB را میتوان برای پشتیبانی از دو مدل سازگاری مختلف پیکربندی کرد: سازگاری نهایی (Eventual Consistency) و سازگاری قوی (Strong Consistency).
- سازگاری نهایی: مدل سازگاری پیشفرض در DynamoDB است. بیشترین دسترسپذیری و کمترین تأخیر را فراهم میکند، اما میتواند به خواندن کهنه بینجامد. یعنی اگر داده بنویسید و بلافاصله بخوانید، ممکن است داده بهروزشده را فوراً نبینید. با این پیکربندی، Dynamo سیستم AP است که ویژگیهای BASE را نشان میدهد.
- سازگاری قوی: این مدل تضمین میکند همه خواندنها آخرین نوشتن را منعکس میکنند. بهایش تأخیر بیشتر و دسترسپذیری بالقوه کمتر است. با این پیکربندی، Dynamo سیستم CP است که ویژگیهای ACID را نشان میدهد.
باز هم این فقط یک تغییر ساده پیکربندی در کنسول AWS یا از طریق SDK است و یعنی میتوانید از DynamoDB در طیف گستردهای از سناریوها استفاده کنید، از جمله مواردی که سازگاری قوی لازم است، مثل اپلیکیشنهای بانکی یا سیستم رزرو بلیت.
در پشت صحنه چه میشود؟
مدلهای سازگاری DynamoDB با معماری توزیعشده و مکانیزمهای replication پیاده میشوند.
خواندن سازگار نهایی (پیشفرض):
- عملیات نوشتن اول روی replica اصلی (primary) نوشته میشود و بعد ناهمزمان روی replicaهای ثانویه تکثیر میشود.
- خواندنها ممکن است از هر replicaای جواب داده شوند، که ممکن است هنوز آخرین بهروزرسانی را نداشته باشد.
- فرایندهای پسزمینه مدام داده را بین replicaها همگام میکنند.
- ظرفیت خواندن کمتری مصرف میکند (۰٫۵ RCU به ازای هر ۴ کیلوبایت) و تأخیر کمتری دارد.
خواندن سازگار قوی:
- خواندنهای سازگار قوی مستقیم به گره leader مربوط به partition هدایت میشوند.
- leader پیش از پاسخ دادن مطمئن میشود بهروزترین داده را دارد.
- ظرفیت خواندن بیشتری مصرف میکند (۱ RCU به ازای هر ۴ کیلوبایت) و ممکن است تأخیر بیشتری داشته باشد.
- جدیدترین نسخه داده را که همه نوشتنهای موفق را منعکس میکند ارائه میدهد.
معماری و مقیاسپذیری
مقیاسپذیری
DynamoDB با Sharding و Load Balancing خودکار مقیاس میگیرد. وقتی سروری به ظرفیتش میرسد، داده بهطور خودکار توزیع میشود. Consistent Hashing توزیع یکنواخت بین گرهها را تضمین میکند و ترافیک و بار را متعادل میکند.
زیرساخت جهانی AWS این مقیاسپذیری را تقویت میکند. Global Tableها replication بلادرنگ بین ریجنها را ممکن میکنند و خواندن و نوشتن محلی را در سراسر دنیا فراهم میکنند. این کار تأخیر را کم میکند و تجربه کاربری را بهتر میکند. DynamoDB همچنین در چند Availability Zone در هر ریجن یکپارچه میشود و افزونگی سرویس و دوام داده را تضمین میکند.
انتخاب ریجنها و zoneهای مناسب مهم است، چون کارایی و انطباق را با توجه به نزدیکی کاربر و مقررات بهینه میکند. محل قرارگیری داده (data locality) بهطور کلی کلید کم کردن تأخیر و بهتر کردن throughput است.
هنگام طراحی اپلیکیشنهای جهانی در مصاحبه، اشاره به Global Tableها برای replication منطقهای اغلب کافی است.
تحمل خطا و دسترسپذیری
DynamoDB برای ارائه دسترسپذیری بالا و تحمل خطا از طریق معماری توزیعشده و مکانیزمهای replication داده طراحی شده است. این سرویس داده را بهطور خودکار در چند Availability Zone در یک ریجن تکثیر میکند، پس داده حتی در صورت خرابی سختافزار یا اختلال شبکه ماندگار و در دسترس است.
میتوانید بهراحتی سطح افزونگی دادههایتان را با تعیین تعداد replicaهای خواندن و نوشتن برای هر جدول پیکربندی کنید. این به شما اجازه میدهد چند نسخه از دادههایتان را در zoneهای مختلف نگه دارید و دوام و دسترسپذیری داده را بیشتر کنید.
در پشت صحنه، DynamoDB از سیستم replication مبتنی بر quorum برای اطمینان از سازگاری و دوام داده استفاده میکند. عملیات نوشتن قبل از موفق حساب شدن نیاز به تأیید اکثریت replicaها دارد. برای خواندن سازگار قوی، DynamoDB درخواست را به replica اصلی هدایت میکند و بهروزترین داده را تضمین میکند. اما در حالت سازگاری نهایی، خواندنها را میتوان از هر replicaای جواب داد، که بهخاطر ماهیت ناهمزمان replication ممکن است اطلاعات کمی کهنه داشته باشد.
امنیت
دادهها در DynamoDB بهصورت پیشفرض در حالت ذخیرهشده (at rest) رمزگذاری میشوند، پس دادههایتان حتی وقتی کسی به آنها دسترسی ندارد امن است. همچنین میتوانید رمزگذاری حین انتقال (in transit) را فعال کنید تا مطمئن شوید داده هنگام حرکت بین DynamoDB و اپلیکیشن شما رمزگذاری شده است.
DynamoDB با AWS Identity and Access Management (IAM) یکپارچه میشود تا کنترل دسترسی ریزدانه روی دادههای شما بدهد. میتوانید سیاستهای IAM بسازید که مشخص میکنند چه کسی به دادههایتان دسترسی دارد و چه کارهایی میتواند بکند. این کار اجازه میدهد دسترسی به دادههایتان را فقط برای کسانی که لازم دارند محدود کنید.
علاوه بر این، میتوانید از VPC endpoint (Virtual Private Cloud) برای دسترسی امن به DynamoDB از داخل VPC خود استفاده کنید، بدون اینکه دادههایتان را در معرض اینترنت عمومی بگذارید. این یک لایه امنیتی اضافه میدهد و تضمین میکند دادههایتان فقط از داخل VPC شما قابل دسترسی است.
در مصاحبه، هنگام کار با دادههای حساس کاربر، ممکن است بد نباشد بگویید میدانید DynamoDB داده را at rest رمزگذاری میکند و در صورت نیاز میتوانید رمزگذاری in transit را فعال کنید. فراتر از این، هر چیز دیگری احتمالاً زیادی است.
مدل قیمتگذاری
قیمتگذاری ممکن است چیزی کاملاً نامرتبط با مصاحبه به نظر برسد، اما با من همراه باشید. فهمیدن مدل قیمتگذاری محدودیتهای روشنی روی معماری شما میگذارد.
دو مدل قیمتگذاری برای DynamoDB هست: ظرفیت on-demand و ظرفیت provisioned. قیمتگذاری on-demand به ازای هر درخواست هزینه میگیرد و برای بارهای کاری غیرقابلپیشبینی مناسب است. ظرفیت provisioned از طرف دیگر لازم میکند کاربران واحدهای ظرفیت خواندن و نوشتن را مشخص کنند که ساعتی صورتحساب میشود. این مدل برای بارهای کاری قابلپیشبینی مقرونبهصرفهتر است، اما ممکن است در دورههای کمترافیک به استفاده ناکافی از ظرفیت بینجامد.
قیمتگذاری بر اساس چیزی است که آمازون «واحد ظرفیت خواندن و نوشتن» مینامد. این واحدها معیاری از throughput هستند که برای جدول DynamoDB خود لازم دارید. میتوانید آنها را معیاری برای سنجیدن حجم دادهای ببینید که میتوانید در هر ثانیه بخوانید یا بنویسید.
یک واحد ظرفیت خواندن (RCU) اجازه میدهد ۴ کیلوبایت داده در ثانیه بخوانید. یک واحد ظرفیت نوشتن (WCU) اجازه میدهد ۱ کیلوبایت داده در ثانیه بنویسید.
| ویژگی | هزینه (طبق متن اصلی) | جزئیات |
|---|---|---|
| واحد ظرفیت خواندن (RCU) | ۱٫۱۲ دلار به ازای هر میلیون خواندن (هرکدام ۴ کیلوبایت) | یک خواندن سازگار قوی در ثانیه برای itemهای تا ۴ کیلوبایت، یا دو خواندن سازگار نهایی در ثانیه |
| واحد ظرفیت نوشتن (WCU) | ۵٫۶۲ دلار به ازای هر میلیون نوشتن (هرکدام ۱ کیلوبایت) | یک نوشتن در ثانیه برای itemهای تا ۱ کیلوبایت |
با اینکه هزینه بهخودیخود در مصاحبه جالب نیست، داشتن درک سطح بالا از اعداد مفید است. یک partition در AWS از ۱۰۰۰ واحد ظرفیت خواندن و ۱۰۰۰ واحد ظرفیت نوشتن پشتیبانی میکند. یعنی یک partition میتواند ۴ مگابایت خواندن در ثانیه و ۱ مگابایت نوشتن در ثانیه را پشتیبانی کند. با اینکه Sharding و مقیاسدهی بهطور خودکار انجام میشود، استفاده از این اعداد برای چک کردن اینکه آیا اپلیکیشن شما میتواند بار موردانتظار را بدون هزینه غیرواقعی تحمل کند مفید است.
مثلاً اگر میخواستید بازدیدهای YouTube را در Dynamo ذخیره کنید، میتوانید حساب کنید هر item فقط شامل شناسه کاربر، شناسه ویدیو و timestamp است که حدود ۱۰۰ بایت میشود. یعنی در هر partition میتوانید ۱۰ هزار بازدید در ثانیه ذخیره کنید. اگر انتظار ۱۰ میلیون بازدید در ثانیه دارید، برای پشتیبانی از بار نوشتن به ۱۰۰۰ shard نیاز دارید که طبق قیمت فعلی میتواند حدود ۱۵۰ هزار دلار در روز هزینه داشته باشد. این اعداد کمک میکنند بررسی کنید آیا اپلیکیشن شما میتواند بار موردانتظار را بدون هزینه غیرواقعی تحمل کند یا نه.
ویژگیهای پیشرفته
DAX (DynamoDB Accelerator)
یک واقعیت جالب: Dynamo یک کش داخلی به نام DynamoDB Accelerator (DAX) دارد. پس ممکن است لازم نباشد سرویس اضافه (Redis، Memcached) به معماریتان وارد کنید، فقط DAX را فعال کنید.
DAX سرویس کشی است که برای بهتر کردن کارایی DynamoDB طراحی شده و برای بارهای کاری خواندنسنگین زمان پاسخ زیر میلیثانیه میدهد. DAX بومی DynamoDB است و نیازی به تغییر کد اپلیکیشن ندارد. فقط باید روی جدولهای شما فعال شود.
هم بهعنوان کش read-through و هم write-through عمل میکند. یعنی نتایج خواندن از جدولهای DynamoDB را خودکار کش میکند و مستقیم به اپلیکیشنها تحویل میدهد، و همچنین داده را هم در کش و هم در جدول DynamoDB زیرین مینویسد. itemهای کششده وقتی داده مربوط در جدول بهروز شود یا وقتی کش به حد مجاز برسد باطل میشوند.
DAX را میتوان بهصورت کش item یا کش query پیکربندی کرد. کش item نتایج عملیات GetItem را کش میکند، در حالی که کش query نتایج عملیات Query و Scan را کش میکند. مثل DynamoDB، DAX را هم میتوان برای سازگاری نهایی یا قوی پیکربندی کرد تا نیازهای خاص سیستم شما برآورده شود.
Streams
Dynamo همچنین از Change Data Capture (CDC) از طریق DynamoDB Streams پشتیبانی داخلی دارد. Streamها تغییرات itemهای جدول را ثبت میکنند و آنها را برای پردازش بلادرنگ در دسترس میگذارند. هر رویداد تغییر در جدول، مثل درج، بهروزرسانی یا حذف، بهصورت یک stream record در stream ثبت میشود تا اپلیکیشنهای پاییندست آن را مصرف کنند.
این را میتوان برای کاربردهای مختلف به کار برد، مثل راه انداختن تابع Lambda در پاسخ به تغییرات پایگاه داده، نگه داشتن نسخهای از پایگاه داده در سیستم دیگر، یا ساخت اپلیکیشنهای تحلیل بلادرنگ.
- یکپارچگی با Elasticsearch: از DynamoDB Streams میتوان برای همگام نگه داشتن ایندکس Elasticsearch با جدول DynamoDB استفاده کرد. این برای اضافه کردن قابلیت جستجو روی داده DynamoDB مفید است.
- تحلیل بلادرنگ: با پردازش DynamoDB Streams با Kinesis Data Firehose میتوانید داده را برای تحلیل بلادرنگ در Amazon S3، Redshift یا Elasticsearch بارگذاری کنید.
- اعلان تغییر: میتوانید از DynamoDB Streams برای فعال کردن توابع Lambda در پاسخ به تغییرات پایگاه داده استفاده کنید. این برای ارسال اعلان، بهروزرسانی کش یا انجام اقدامهای دیگر در پاسخ به تغییر داده مفید است.
DynamoDB در مصاحبه
زمان استفاده
در مصاحبهها اغلب میتوانید استفاده از DynamoDB را برای تقریباً هر نیاز لایه ماندگاری (persistence) توجیه کنید. بسیار مقیاسپذیر و بادوام است، از تراکنش پشتیبانی میکند و تأخیر زیر میلیثانیه میدهد. ویژگیهای اضافه مثل DAX برای کش و DynamoDB Streams برای سازگاری بین ذخیرهگاهها، آن را حتی قدرتمندتر میکنند. پس اگر مصاحبهگر اجازه بدهد، احتمالاً گزینه عالی است.
با این حال مهم است بدانید کی بهخاطر نقاط ضعف مشخصش نباید از DynamoDB استفاده کنید.
شناخت محدودیتها
چند دلیل هست که ممکن است پایگاه داده دیگری انتخاب کنید (فراتر از آشنایی بیشتر با فناوری دیگر).
- کارایی هزینه: مدل قیمتگذاری DynamoDB بر اساس عملیات خواندن و نوشتن بهعلاوه داده ذخیرهشده است، که با بار کاری زیاد میتواند گران شود. اگر به صدها هزار نوشتن در ثانیه نیاز دارید، ممکن است هزینه از مزایا بیشتر شود.
- الگوهای کوئری پیچیده: اگر سیستم شما به کوئری پیچیده نیاز دارد، مثل کوئری که Join یا تراکنش چندجدولی میخواهد، DynamoDB ممکن است جواب ندهد. برای کوئریهای پایه عالی است اما با عملیات پیچیدهتر سختش میشود.
- محدودیتهای مدلسازی داده: DynamoDB برای کارایی خوب به مدلسازی دقیق داده نیاز دارد و برای ساختارهای کلید-مقدار و سند بهینه شده است. اگر مرتب از GSI و LSI استفاده میکنید، پایگاه داده رابطهای مثل PostgreSQL ممکن است مناسبتر باشد.
- vendor lock-in: انتخاب DynamoDB یعنی قفل شدن در AWS. بسیاری از مصاحبهگرها میخواهند از فروشنده خاصی جانبداری نکنید، پس ممکن است لازم باشد جایگزینهای متنباز را در نظر بگیرید تا درگیر نشوید.
جمعبندی
تمام شد. DynamoDB همهکاره و قدرتمند است و کار کردن با آن لذتبخش است. در مصاحبه برای بیشتر کاربردها انتخاب محکمی است، اما باید از محدودیتهایش آگاه باشید و درک کاملی از نحوه عملکرد آن داشته باشید، از جمله انتخاب مدل داده مناسب، partition key، sort key، ایندکسهای ثانویه و اینکه کی ویژگیهای پیشرفته مثل DAX و Streams را فعال کنید. در نهایت، یادتان باشد DynamoDB حتی ویژگیها و تراکنشهای ACID را هم پشتیبانی میکند، پس بحث قدیمی SQL در برابر NoSQL خبر کهنهای است.