سورس باز شدن MSVC's STL
https://github.com/microsoft/STL is our new repository, containing all of our product source code, a new CMake build system, and a README with more information.
۱۸ ویژگی گیتلب متن باز میشود
عموما در ORMها دو سطح کش میتواند وجود داشته باشد:
الف) سطح اول کش
که نمونه بارز آن در EF Code first استفاده از متد context.Entity.Find است. در بار اول فراخوانی این متد، مراجعهای به بانک اطلاعاتی صورت گرفته تا بر اساس primary key ذکر شده در آرگومان آن، رکورد متناظری بازگشت داده شود. در بار دوم فراخوانی متد Find، دیگر مراجعهای به بانک اطلاعاتی صورت نخواهد گرفت و اطلاعات از سطح اول کش (یا همان Context جاری) خوانده میشود.
بنابراین سطح اول کش در طول عمر یک تراکنش معنا پیدا میکند و به صورت خودکار توسط EF مدیریت میشود.
ب) سطح دوم کش
سطح دوم کش در ORMها طول عمر بیشتری داشته و سراسری است. هدف از آن کش کردن اطلاعات عمومی و پر مصرفی است که در دید تمام کاربران قرار دارد و همچنین تمام کاربران میتوانند به آن دسترسی داشته باشند. بنابراین محدود به یک Context نیست.
عموما پیاده سازی سطح دوم کش خارج از ORM مورد استفاده قرار میگیرد و توسط اشخاص و شرکتهای ثالث تهیه میشود.
در حال حاضر پیاده سازی توکاری از سطح دوم کش در EF Code first وجود ندارد و قصد داریم در مطلب جاری به یک پیاده سازی نسبتا خوب از آن برسیم.
تلاشهای صورت گرفته
تا کنون دو پیاده سازی نسبتا خوب از سطح دوم کش در EF صورت گرفته:
Entity Framework Code First Caching
Caching the results of LINQ queries
مورد اول برای ایده گرفتن خوب است. بحث اصلی پیاده سازی سطح دوم کش، یافتن کلیدی است که معادل کوئری LINQ در حال فراخوانی است. سطح دوم کش را به صورت یک Dictionary تصور کنید. هر آیتم آن تشکیل شده است از یک کلید و یک مقدار. از کلید برای یافتن مقدار متناظر استفاده میشود.
اکنون مشکل چیست؟ در یک برنامه ممکن است صدها کوئری لینک وجود داشته باشد. چطور باید به ازای هر کوئری LINQ یک کلید منحصربفرد تولید کرد؟
در مطلب «Entity Framework Code First Caching» از متد ToString استفاده شده است. اگر این متد، بر روی یک عبارت LINQ در EF Code first فراخوانی شود، معادل SQL آن نمایش داده میشود. بنابراین یک قدم به تولید کلید منحصربفرد متناظر با یک کوئری نزدیک شدهایم. اما ... مشکل اینجا است که متد ToString پارامترها را لحاظ نمیکند. بنابراین این روش اصلا قابل استفاده نیست. چون کاربر به ازای تمام پارامترهای ارسالی، همواره یک نتیجه را دریافت خواهد کرد.
در مقاله «Caching the results of LINQ queries» این مشکل برطرف شده است. با parse کامل expression tree یک عبارت LINQ کلید منحصربفرد معادل آن یافت میشود. سپس بر این اساس میتوان نتیجه کوئری را به نحو صحیحی کش کرد. در این روش پارامترها هم لحاظ میشوند و مشکل مقاله قبلی را ندارد.
اما این مقاله دوم یک مشکل مهم را به همراه دارد: روشی را برای حذف آیتمها از کش ارائه نمیدهد. فرض کنید مقالات سایت را در سطح دوم کش قرار دادهاید. اکنون یک مقاله جدید در سایت ثبت شده است. اصطلاحا برای invalidating کش در این روش، راهکاری پیشنهاد نشده است.
پیاده سازی بهتری از سطح دوم کش در EF Code fist
میتوان از همان روش یافتن کلید منحصربفرد معادل با یک کوئری LINQ، که در مقاله دوم فوق، یاد شد، کار را شروع کرد و سپس آنرا به مرحلهای رساند که مباحث حذف کش نیز به صورت خودکار مدیریت شود. پیاده سازی آن را برای برنامههای وب در ذیل ملاحظه میکنید:
using System; using System.Collections.Generic; using System.Data; using System.Data.Entity; using System.Data.Objects; using System.Diagnostics; using System.Linq; using System.Web; using System.Web.Caching; namespace EfSecondLevelCaching.Core { public static class EfHttpRuntimeCacheProvider { #region Methods (6) // Public Methods (2) public static IList<TEntity> ToCacheableList<TEntity>( this IQueryable<TEntity> query, int durationMinutes = 15, CacheItemPriority priority = CacheItemPriority.Normal) { return query.Cacheable(x => x.ToList(), durationMinutes, priority); } /// <summary> /// Returns the result of the query; if possible from the cache, otherwise /// the query is materialized and the result cached before being returned. /// The cache entry has a one minute sliding expiration with normal priority. /// </summary> public static TResult Cacheable<TEntity, TResult>( this IQueryable<TEntity> query, Func<IQueryable<TEntity>, TResult> materializer, int durationMinutes = 15, CacheItemPriority priority = CacheItemPriority.Normal) { // Gets a cache key for a query. var queryCacheKey = query.GetCacheKey(); // The name of the cache key used to clear the cache. All cached items depend on this key. var rootCacheKey = typeof(TEntity).FullName; // Try to get the query result from the cache. printAllCachedKeys(); var result = HttpRuntime.Cache.Get(queryCacheKey); if (result != null) { debugWriteLine("Fetching object '{0}__{1}' from the cache.", rootCacheKey, queryCacheKey); return (TResult)result; } // Materialize the query. result = materializer(query); // Adding new data. debugWriteLine("Adding new data: queryKey={0}, dependencyKey={1}", queryCacheKey, rootCacheKey); storeRootCacheKey(rootCacheKey); HttpRuntime.Cache.Insert( key: queryCacheKey, value: result, dependencies: new CacheDependency(null, new[] { rootCacheKey }), absoluteExpiration: DateTime.Now.AddMinutes(durationMinutes), slidingExpiration: Cache.NoSlidingExpiration, priority: priority, onRemoveCallback: null); return (TResult)result; } /// <summary> /// Call this method in `public override int SaveChanges()` of your DbContext class /// to Invalidate Second Level Cache automatically. /// </summary> public static void InvalidateSecondLevelCache(this DbContext ctx) { var changedEntityNames = ctx.ChangeTracker .Entries() .Where(x => x.State == EntityState.Added || x.State == EntityState.Modified || x.State == EntityState.Deleted) .Select(x => ObjectContext.GetObjectType(x.Entity.GetType()).FullName) .Distinct() .ToList(); if (!changedEntityNames.Any()) return; printAllCachedKeys(); foreach (var item in changedEntityNames) { item.removeEntityCache(); } printAllCachedKeys(); } // Private Methods (4) private static void debugWriteLine(string format, params object[] args) { if (!Debugger.IsAttached) return; Debug.WriteLine(format, args); } private static void printAllCachedKeys() { if (!Debugger.IsAttached) return; debugWriteLine("Available cached keys list:"); int count = 0; var enumerator = HttpRuntime.Cache.GetEnumerator(); while (enumerator.MoveNext()) { if (enumerator.Key.ToString().StartsWith("__")) continue; // such as __System.Web.WebPages.Deployment debugWriteLine("queryKey: {0}", enumerator.Key.ToString()); count++; } debugWriteLine("count: {0}", count); } private static void removeEntityCache(this string rootCacheKey) { if (string.IsNullOrWhiteSpace(rootCacheKey)) return; debugWriteLine("Removing items with dependencyKey={0}", rootCacheKey); // Removes all cached items depend on this key. HttpRuntime.Cache.Remove(rootCacheKey); } private static void storeRootCacheKey(string rootCacheKey) { // The cacheKeys of a cacheDependency that are not already in cache ARE NOT inserted into the cache // on the Insert of the item in which the dependency is used. if (HttpRuntime.Cache.Get(rootCacheKey) != null) return; HttpRuntime.Cache.Add( rootCacheKey, rootCacheKey, null, Cache.NoAbsoluteExpiration, Cache.NoSlidingExpiration, CacheItemPriority.Default, null); } #endregion Methods } }
توضیحات کدهای فوق
در اینجا یک متدالحاقی به نام Cacheable توسعه داده شده است که میتواند در انتهای کوئریهای LINQ شما قرار گیرد. مثلا:
var data = context.Products.AsQueryable().Cacheable(x => x.FirstOrDefault());
کاری که در این متد انجام میشود به این شرح است:
الف) ابتدا کلید منحصربفرد معادل کوئری LINQ فراخوانی شده محاسبه میشود.
ب) بر اساس نام کامل نوع Entity در حال استفاده، کلید دیگری به نام rootCacheKey تولید میگردد.
شاید بپرسید اهمیت این کلید چیست؟
فرض کنید در حال حاضر 1000 آیتم در کش وجود دارند. چه روشی را برای حذف آیتمهای مرتبط با کش Entity1 پیشنهاد میدهید؟ احتمالا خواهید گفت تمام کش را بررسی کرده و آیتمها را یکی یکی حذف میکنیم.
این روش بسیار کند است (و جواب هم نمیدهد؛ چون کلیدی که در اینجا تولید شده، هش MD5 معادل کوئری است و نمیتوان آنرا به موجودیتی خاص ربط داد) و ... نکته جالبی در متد HttpRuntime.Cache.Insert برای مدیریت آن پیش بینی شده است: استفاده از CacheDependency.
توسط CacheDependency میتوان گروهی از آیتمهای همخانواده را تشکیل داد. سپس برای حذف کل این گروه کافی است کلید اصلی CacheDependency را حذف کرد. به این ترتیب به صورت خودکار کل کش مرتبط خالی میشود.
ج) مراحل بعدی آن هم یک سری اعمال متداول هستند. ابتدا توسط HttpRuntime.Cache.Get بررسی میشود که آیا بر اساس کلید متناظر با کوئری جاری، اطلاعاتی در کش وجود دارد یا خیر. اگر بله، نتیجه از کش خوانده میشود. اگر خیر، کوئری اصطلاحا materialized میشود تا بر روی بانک اطلاعاتی اجرا شده و نتیجه بازگشت داده شود. سپس این نتیجه را در کش قرار میدهیم.
مورد بعدی که باید به آن دقت داشت، خالی کردن کش، پس از به روز رسانی اطلاعات توسط کاربران است. این کار در متد InvalidateSecondLevelCache صورت میگیرد. به کمک ChangeTracker میتوان نام نوعهای موجودیتهای تغییر کرده را یافت. چون کلید اصلی CacheDependency را بر مبنای همین نام نوعهای موجودیتها تعیین کردهایم، به سادگی میتوان کش مرتبط با موجودیت یافت شده را خالی کرد.
استفاده از متد InvalidateSecondLevelCache یاد شده به نحو زیر است:
using System.Data.Entity; using EfSecondLevelCaching.Core; using EfSecondLevelCaching.Test.Models; namespace EfSecondLevelCaching.Test.DataLayer { public class ProductContext : DbContext { public DbSet<Product> Products { get; set; } public override int SaveChanges() { this.InvalidateSecondLevelCache(); return base.SaveChanges(); } } }
در اینجا با تحریف متد SaveChanges، میتوان درست در زمان اعمال تغییرات به بانک اطلاعاتی، قسمتی از کش را غیرمعتبر کرد.
نحوه استفاده از سطح دوم کش توسعه داده شده
مثالی از کاربرد متدهای الحاقی توسعه داده شده را در ذیل مشاهده میکنید:
using System.Data.Entity; using System.Linq; using EfSecondLevelCaching.Core; using EfSecondLevelCaching.Test.DataLayer; using EfSecondLevelCaching.Test.Models; using System; namespace EfSecondLevelCaching { public static class TestUsages { public static void RunQueries() { using (ProductContext context = new ProductContext()) { var isActive = true; var name = "Product1"; // reading from db var list1 = context.Products .OrderBy(one => one.ProductNumber) .Where(x => x.IsActive == isActive && x.ProductName == name) .ToCacheableList(); // reading from cache var list2 = context.Products .OrderBy(one => one.ProductNumber) .Where(x => x.IsActive == isActive && x.ProductName == name) .ToCacheableList(); // reading from cache var list3 = context.Products .OrderBy(one => one.ProductNumber) .Where(x => x.IsActive == isActive && x.ProductName == name) .ToCacheableList(); // reading from db var list4 = context.Products .OrderBy(one => one.ProductNumber) .Where(x => x.IsActive == isActive && x.ProductName == "Product2") .ToCacheableList(); } // removes products cache using (ProductContext context = new ProductContext()) { var p = new Product() { IsActive = false, ProductName = "P4", ProductNumber = "004" }; context.Products.Add(p); context.SaveChanges(); } using (ProductContext context = new ProductContext()) { var data = context.Products.AsQueryable().Cacheable(x => x.FirstOrDefault()); var data2 = context.Products.AsQueryable().Cacheable(x => x.FirstOrDefault()); context.SaveChanges(); } } } }
در این حالت اگر برنامه را اجرا کنیم به یک چنین خروجی در پنجره Debug ویژوال استودیو خواهیم رسید:
Adding new data: queryKey=72AF5DA1BA9B91E24DCCF83E88AD1C5F, dependencyKey=EfSecondLevelCaching.Test.Models.Product Available cached keys list: queryKey: EfSecondLevelCaching.Test.Models.Product queryKey: 72AF5DA1BA9B91E24DCCF83E88AD1C5F count: 2 Fetching object 'EfSecondLevelCaching.Test.Models.Product__72AF5DA1BA9B91E24DCCF83E88AD1C5F' from the cache. Available cached keys list: queryKey: EfSecondLevelCaching.Test.Models.Product queryKey: 72AF5DA1BA9B91E24DCCF83E88AD1C5F count: 2 Fetching object 'EfSecondLevelCaching.Test.Models.Product__72AF5DA1BA9B91E24DCCF83E88AD1C5F' from the cache. Available cached keys list: queryKey: EfSecondLevelCaching.Test.Models.Product queryKey: 72AF5DA1BA9B91E24DCCF83E88AD1C5F count: 2 Adding new data: queryKey=11A2C33F9AD7821A0A31003BFF1DF886, dependencyKey=EfSecondLevelCaching.Test.Models.Product Available cached keys list: queryKey: 72AF5DA1BA9B91E24DCCF83E88AD1C5F queryKey: 11A2C33F9AD7821A0A31003BFF1DF886 queryKey: EfSecondLevelCaching.Test.Models.Product count: 3 Removing items with dependencyKey=EfSecondLevelCaching.Test.Models.Product Available cached keys list: count: 0 Available cached keys list: count: 0 Adding new data: queryKey=02E6FE403B461E45C5508684156C1D10, dependencyKey=EfSecondLevelCaching.Test.Models.Product Available cached keys list: queryKey: 02E6FE403B461E45C5508684156C1D10 queryKey: EfSecondLevelCaching.Test.Models.Product count: 2 Fetching object 'EfSecondLevelCaching.Test.Models.Product__02E6FE403B461E45C5508684156C1D10' from the cache.
توضیحات:
در زمان تولید list1 چون اطلاعاتی در کش سطح دوم وجود ندارد، پیغام Adding new data قابل مشاهده است. اطلاعات از بانک اطلاعاتی دریافت شده و سپس در کش قرار داده میشود.
حین فراخوانی list2 که دقیقا همان کوئری list1 را یکبار دیگر فراخوانی میکند، به عبارت Fetching object خواهیم رسید که بر دریافت اطلاعات از کش سطح دوم بجای مراجعه به بانک اطلاعاتی دلالت دارد.
در list4 چون پارامترهای کوئری تغییر کردهاند، بنابراین دیگر کلید منحصربفرد معادل آن با list1 و lis2 یکی نیست و اینبار پیغام Adding new data مشاهده میشود؛ چون برای دریافت اطلاعات آن نیاز است که به بانک اطلاعاتی مراجعه شود.
در ادامه یک context دیگر باز شده و در آن رکوردی به بانک اطلاعاتی اضافه میشود. به همین دلیل اینبار پیام Removing items with dependencyKey قابل مشاهده است. به عبارتی متد InvalidateSecondLevelCache وارد عمل شده است و بر اساس تغییری که صورت گرفته، کش را غیرمعتبر کرده است.
سپس در context بعدی تعریف شده، دوبار متد FirstOrDefault فراخوانی شده است. اولین مورد Adding new data است و دومین فراخوانی به Fetching object ختم شده است (دریافت اطلاعات از کش).
کدهای کامل این پروژه را از اینجا میتوانید دریافت کنید:
EfSecondLevelCaching.zip
- Installation
- Creating a SQL Server Database Project
- Modify Database Schema
- Schema Compare and Update
- Snapshot Project
- Publish
- نصب Sql Server Data Tools
آخرین نسخه این ابزار را از این آدرس دانلود کنید یا زمان نصب Sql Server 2012 گزینه آن را انتخاب کنید
- ایجاد یک پروژه جدید از نوع
SQL Server Database Project
پس از نصب SSDT شما از طریق Visual Studio 2012 Shell که همراه SqlServer 2012 نصب میشود یا با Visual Stadio 2012 یک پروژه جدید از نوع SQL Server Database Project ایجاد کنید.
Server Database Project Project به شما امکان توسعه پایگاه داده Sql Server را با استفاده از محیط یکپارچه Visual Studio با در اختیار گذاشتن ابزارهای همچون navigation, intellisense, validation, debugging, declarative editing و غیره را میدهد. شی پایگاه داده شما در پروژه و فایلهای مجزا ذخیره میشود مثل آن که شما در حال توسعه برنامه #C یا VB.NET هستید. - ورود پایگاه داده موجود به پروژه SQL Server Database
شما میتوانید شمای پایگاه داده موجود یا SQL Server DAC Package File (.dacpac) یا هر T-SQL دیگری را با راست کلیک بر روی پروژه و انتخاب گزینه Import به پروژه خود اضافه و تغییرات لازم را اعمال نماید شکل زیر پنجره Import Database را نشان میدهد:
پنجره Solution Explorer امکانات زیر را در اختیار شما قرارمی دهد:
- اضافه کردن اشیای همچون Table، View و غیره با راست کیلک کردن بر روی پروژه و انتخاب گزینه Add
- ویرایش اشیا موجود با دبل کیلک کردن بر روی اشیا
- مقایسه شمای پایگاه داده با پایگاه داده دیگر و یا Microsoft SQL Server DAC Package File کافیست بر روی پروژه راست کیلک و گزینه Schema Compare را انتخاب نمایید.
- ایجاد یک Snapshot از شمای پایگاه داده در یک Microsoft SQL Server DAC Package File . یک snapshot برای ایجاد یک پایگاه داده یا ورود در پروژه ای دیگر یا در مقایسه دو پایگاه داده کاربرد دارد.
- انتشار پایگاه داده که امکان ایجاد پایگاه داده یر روی یک سرور SQL Server را فراهم مینماید.
- مقایسه شمای دو پایگاه داده با هم
SSDT ابزاری برای مقایسه دو پایگاه داده و بروز رسانی پایگاه داده مقصد از روی شمای منبع از طریق ایجاد یک Script یا به صورت مستقیم را میدهد.
- انتشار پروژه با استفاده از SQL Server Data Tools
این ابزار امکان گسترش پروژه شما بر روی SQL Server 2005, 2008, 2008 R2, 2012, یا SQL Azure instance را میدهد. برای انتشار کافیت از پنجره Solution Explorer بر روی پروژه راست کیلک و گزینه Publish را انتخاب کنید.
- پنجره SQL Server Object Explorer
این پنجره با اتصال به سرور SQL Server امکان دسترسی و مدیریت پایگاه داده را به ما میدهد. علاوه بر دسترسی به اجزای یک پایگاه داده امکان مدیریت پوشههای Security , Server Objects نیز فراهم میباشد. همچنین امکان اتصال به Sql Server Express , localdb نیز وجود دارد.
Security in software development should be a first-order requirement, but it’s often implemented in projects as an afterthought. With Application Security in .NET Succinctly, author Stan Drapkin provides a refresher of .NET security practices and fills common knowledge gaps for experienced developers and novices alike. Learn about hashes, machine authentication code, key derivation, binary and text encodings, symmetric and authenticated encryption, and much more, and begin properly securing your .NET apps today.
- Preface
- .NET Security
- Hashes and MACs
- Key Derivation
- Comparing Byte Arrays
- Binary Encodings
- Text Encodings
- Symmetric Encryption
- Authenticated Encryption
- Asymmetric Cryptography
- Two-Factor Authentication (2FA)
- Web Security
در این تصویر اطلاعات کاملی از مکانیزم ذخیره سازی دادهها در پلتفرم اندروید مشاهده میکنید.
ما میتوانیم دادههای خود را در یک بانک از نوع SQLite در نظر بگیریم. پایگاهداده به این دلیل، ما را از تصمیم غیرضروری برای اجرای ساختار بی نظم دادهها بینیاز میکند. بیایید به یک مثال از نحوه ذخیره و بازیابی دادهها با استفاده از یکی از این مکانیزمها نگاه کنیم.
Shared Preferences یا اولویتهای اشتراکی
اولویتهای مشترک عمدتا برای ذخیرهسازی تنظیمات برنامهها مفید هستند و تا زمانی که راهاندازی مجدد توسط دستگاه انجام شود، معتبر خواهد بود. بیایید بگوییم که باید اطلاعات مربوط به یک سرور ایمیل را ذخیره کنیم که برنامه ما برای بازیابی دادهها به آن نیاز دارد.
ما باید نام میزبان ایمیل (hostname) , درگاه (port) و کارگزاری که از SSL استفاده میکند را ذخیره کنیم. کلاس زیر این کار را برای ما انجام میدهد به قطعه کد زیر توجه نمایید:
package net.zenconsult.android; import java.util.Hashtable; import android.content.Context; import android.content.SharedPreferences; import android.content.SharedPreferences.Editor; import android.preference.PreferenceManager; public class StoreData { public static boolean storeData(Hashtable data, Context ctx) { SharedPreferences prefs = PreferenceManager .getDefaultSharedPreferences(ctx); String hostname = (String) data.get("hostname"); int port = (Integer) data.get("port"); boolean useSSL = (Boolean) data.get("ssl"); Editor ed = prefs.edit(); ed.putString("hostname", hostname); ed.putInt("port", port); ed.putBoolean("ssl", useSSL); return ed.commit(); } }
package net.zenconsult.android; import java.util.Hashtable; import android.content.Context; import android.content.SharedPreferences; import android.preference.PreferenceManager; public class RetrieveData { public static Hashtable get(Context ctx) { String hostname = "hostname"; String port = "port"; String ssl = "ssl"; Hashtable data = new Hashtable(); SharedPreferences prefs = PreferenceManager .getDefaultSharedPreferences(ctx); data.put(hostname, prefs.getString(hostname, null)); data.put(port, prefs.getInt(port, 0)); data.put(ssl, prefs.getBoolean(ssl, true)); return data; } }
package net.zenconsult.android; import java.util.Hashtable; import android.app.Activity; import android.content.Context; import android.os.Bundle; import android.util.Log; import android.widget.EditText; public class StorageExample1Activity extends Activity { /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); Context cntxt = getApplicationContext(); Hashtable data = new Hashtable(); data.put("hostname", "smtp.gmail.com"); data.put("port", 587); data.put("ssl", true); if (StoreData.storeData(data, cntxt)) Log.i("SE", "Successfully wrote data"); else Log.e("SE", "Failed to write data to Shared Prefs"); EditText ed = (EditText) findViewById(R.id.editText1); ed.setText(RetrieveData.get(cntxt).toString()); } }
لیست مشترکات بانکهای اطلاعاتی NoSQL
قبل از اینکه بخواهیم وارد ریز جزئیات بانکهای اطلاعاتی NoSQL شویم، نیاز است لیست و سرفصلی از مفاهیم اصلی و مشترک بین اینگونه بانکهای اطلاعاتی را تدارک ببینیم که شامل موارد ذیل میشود:
الف) Non-Relational یا غیر رابطهای
از کلمه NoSQL عموما اینطور برداشت میشود که در اینجا دیگر خبری از SQL نویسی نیست که در عمل برداشت نادرستی است. شاید جالب باشد که بدانید، تعدادی از بانکهای اطلاعاتی NoSQL از زبان SQL نیز به عنوان اینترفیسی برای نوشتن کوئریهای مرتبط، پشتیبانی میکنند.
کلمه NoSQL بیشتر به Non-Relational یا غیر رابطهای بودن اینگونه بانکهای اطلاعاتی بر میگردد. مباحثی مانند مدلهای دادهای نرمال شده، اتصالات و Join جداول، در دنیای NoSQL وجود خارجی ندارند.
ب) Non-schematized/schema free یا بدون اسکیما
مفهوم مهم و مشترک دیگری که در بین بانکهای اطلاعاتی NoSQL وجود دارد، بدون اسکیما بودن اطلاعات آنها است. به این معنا که با حرکت از رکورد یک به رکورد دو، ممکن است با دو ساختار دادهای متفاوت مواجه شوید.
ج) Eventual consistency یا عاقبت یک دست شدن
عاقبت یک دست شدن، به معنای دریافت دستوری از شما و نحوه پاسخ دادن به آن (یا حتی پاسخ ندادن به آن) از طرف بانک اطلاعاتی NoSQL است. برای مثال، زمانیکه یک رکورد جدید را اضافه میکنید، یا اطلاعات موجودی را به روز رسانی خواهید کرد، اغلب بانکهای اطلاعاتی NoSQL این دستور را بسیار سریع دریافت و پردازش خواهند کرد. اما تفاوت است بین دریافت پیام و پردازش واقعی آن در اینجا.
اکثر بانکهای اطلاعاتی NoSQL، پردازش و اعمال واقعی دستورات دریافتی را با یک تاخیر انجام میدهند. به این ترتیب میتوان خیلی سریع به بانک اطلاعاتی اعلام کرد که چه میخواهیم و بانک اطلاعاتی بلافاصله مجددا کنترل را به شما بازخواهد گرداند. اما اعمال و انتشار واقعی این دستور، مدتی زمان خواهد برد.
د) Open source یا منبع باز بودن
اغلب بانکهای اطلاعاتی NoSQL موجود، منبع باز هستند که علاوه بر بهره بردن از مزایای اینگونه پروژهها، استفاده کنندگان سورس باز دیگری را نیز ترغیب به استفاده از آنها کردهاند.
ه) Distributed یا توزیع شده
هرچند امکان پیاده سازی توزیع شده بانکهای اطلاعاتی رابطهای نیز وجود دارد، اما نیاز به تنظیمات قابل توجهی برای حصول این امر میباشد. در دنیای NoSQL، توزیع شده بودن جزئی از استاندارد تهیه اینگونه بانکهای اطلاعاتی است و بر اساس این مدل ذهنی شکل گرفتهاند. به این معنا که اطلاعات را میتوان بین چندین سیستم تقسیم کرد، که حتی این سیستمها ممکن است فواصل جغرافیایی قابل توجهی نیز با یکدیگر داشته باشند.
و) Web scale یا مناسب برای برنامههای تحت وب پر کاربر
امروزه بسیاری از کمپانیهای بزرگ اینترنتی، برای مدیریت تعداد بالایی از کاربران همزمان خود، مانند فیسبوک، یاهو، گوگل، Linkedin، مایکروسافت و غیره، نیاز به بانکهای اطلاعاتی پیدا کردهاند که باید در مقابل این حجم عظیم درخواستها و همچنین اطلاعاتی که دارند، بسیار بسیار سریع پاسخ دهند. به همین جهت بانکهای اطلاعاتی NoSQL ابداع شدهاند تا بتوان برای این نوع سناریوها پاسخی را ارائه داد.
و نکته مهم دیگر اینجا است که خود این کمپانیهای بزرگ اینترنتی، بزرگترین توسعه دهندههای بانکهای اطلاعاتی NoSQL نیز هستند.
نحوه مدیریت یکپارچگی اطلاعات در بانکهای اطلاعاتی NoSQL
مدیریت یکپارچگی اطلاعات بانکهای اطلاعاتی NoSQL به علت ذات و طراحی توزیع شده آنها، با نحوه مدیریت یکپارچگی اطلاعات بانکهای اطلاعاتی رابطهای متفاوت است. اینجا است که تئوری خاصی به نام CAP مطرح میشود که شامل یکپارچگی یا Consistency به همراه Availability یا دسترسی پذیری (همیشه برقرار بودن) و partition tolerance یا توزیع پذیری است. در تئوری CAP مطرح میشود که هر بانک اطلاعاتی خاص، تنها دو مورد از سه مورد مطرح شده را میتواند با هم پوشش دهد.
به این ترتیب بانکهای اطلاعاتی رابطهای عموما دو مورد C و P یا یکپارچگی (Consistency) و partition tolerance یا میزان تحمل تقسیم شدن اطلاعات را ارائه میدهند. اما بانکهای اطلاعاتی NoSQL از این تئوری، تنها دو مورد A و P را پوشش میدهند (دسترسی پذیری و توزیع پذیری مطلوب).
بنابراین مفهومی به نام ACID که در بانکهای اطلاعاتی رابطهای ضامن یکپارچگی اطلاعات آنها است، در دنیای NoSQL وجود خارجی ندارد. کلمه ACID مخفف موارد ذیل است:
Atomicity، Consistency، Isolation و Durability
ACID در بانکهای اطلاعاتی رابطهای تضمین شده است. در این نوع سیستمها، با ایجاد تراکنشها، مباحث ایزوله سازی و یکپارچگی اطلاعات به نحو مطلوبی مدیریت میگردد؛ اما دنیای NoSQL، دسترسی پذیری را به یکپارچگی ترجیح داده است و به همین جهت پیشتر مطرح شد که مفهوم «Eventual consistency یا عاقبت یک دست شدن» در این نوع بانکهای اطلاعاتی در پشت صحنه بکار گرفته میشود. یک مثال دنیای واقعی از عاقبت یک دست شدن اطلاعات را حتما در مباحث DNS مطالعه کردهاید. زمانیکه یک رکورد DNS اضافه میشود یا به روز خواهد شد، اعمال این دستورات در سراسر دنیا به یکباره و همزمان نیست. هرچند اعمال این اطلاعات جدید در یک نود شبکه ممکن است آنی باشد، اما پخش و توزیع آن در سراسر سرورهای DNS دنیا، مدتی زمان خواهد برد (گاهی تا یک روز یا بیشتر).
به همین جهت است که بانکهای اطلاعاتی رابطهای در حجمهای عظیم اطلاعات و تعداد کاربران همزمان بالا، کند عمل میکنند. حجم اطلاعات بالا است، مدتی زمان خواهد برد تا تغییرات اعمال شوند، و چون مفهوم ACID در این نوع بانکهای اطلاعاتی تضمین شده است، کاربران باید مدتی منتظر بمانند و نمونهای از آنها را با dead lockهای شایع، احتمالا پیشتر بررسی یا تجربه کردهاید. در مقابل، بانکهای اطلاعاتی NoSQL بجای یکپارچگی، دسترسی پذیری را اولویت اول خود میدانند و نه یکپارچگی اطلاعات را. در یک بانک اطلاعاتی NoSQL، دستور ثبت اطلاعات دریافت میشود (این مرحله آنی است)، اما اعمال نهایی آن آنی نیست و مدتی زمان خواهد برد تا تمام اطلاعات در کلیه سرورها یک دست شوند.
نحوه مدیریت Indexing اطلاعات در بانکهای اطلاعاتی NoSQL
اغلب بانکهای اطلاعاتی NoSQL تنها بر اساس اطلاعات کلیدهای اصلی جداول آنها index میشوند (البته نام خاصی به نام «جدول»، بسته به نوع بانک اطلاعاتی NoSQL ممکن است متفاوت باشد، اما منظور ظرف دربرگیرنده تعدادی رکورد است در اینجا). این ایندکس نیز از نوع clustered است. به این معنا که اطلاعات به صورت فیزیکی، بر همین مبنا ذخیره و مرتب خواهند شد.
یک مثال: بانک اطلاعاتی NoSQL خاصی به نام Hbase که بر فراز Hadoop distributed file system طراحی شده است، دقیقا به همین روش عمل میکند. این فایل سیستم، تنها از روش Append only برای ذخیره سازی اطلاعات استفاده میکند و در آن مفهوم دسترسی اتفاقی یا random access پیاده سازی نشده است. در این حالت، تمام نوشتنها در بافر، لاگ میشوند و در بازههای زمانی متناوب و مشخصی سبب باز تولید فایلهای موجود و مرتب سازی مجدد آنها از ابتدا خواهند شد. دسترسی به این اطلاعات پس از تکمیل نوشتن، به علت مرتب سازی فیزیکی که صورت گرفته، بسیار سریع است. همچنین مصرف کننده سیستم نیز چون بلافاصله پس از ثبت اطلاعات در بافر سیستم، کنترل را به دست میگیرد، احساس کار با سیستمی را خواهد داشت که بسیار سریع است.
به علاوه Indexهای دیگری نیز وجود دارند که بر اساس کلیدهای اصلی جداول تولید نمیشوند و به آنها ایندکسهای ثانویه یا secondary indexes نیز گفته میشود و تنها تعداد محدودی از بانکهای اطلاعاتی NoSQL از آنها پشتیبانی میکنند. این مساله هم از اینجا ناشی میشود که با توجه به بدون اسکیما بودن جداول بانکهای اطلاعاتی NoSQL، چگونه میتوان اطلاعاتی را ایندکس کرد که ممکن است در رکورد دیگری، ساختار متناظر با آن اصلا وجود خارجی نداشته باشد.
نحوه پردازش Queries در بانکهای اطلاعاتی NoSQL
بانکهای اطلاعاتی NoSQL عموما از زبان کوئری خاصی پشتیبانی نمیکنند. در اینجا باید به اطلاعات به شکل فایلهایی که حاوی رکوردها هستند نگاه کرد. به این ترتیب برای پردازش و یافتن اطلاعات درون این فایلها، نیاز به ایجاد برنامههایی است که این فایلها را گشوده و بر اساس منطق خاصی، اطلاعات مورد نظر را استخراج کنند. گاهی از اوقات زبان SQL نیز پشتیبانی میشود ولی آنچنان عمومیت ندارد. الگوریتمی که در این برنامهها بکار گرفته میشود، Map Reduce نام دارد.
Map Reduce به معنای نوشتن کدی است، با دو تابع. اولین تابع اصطلاحا Map step یا مرحله نگاشت نام دارد. در این مرحله کوئری به قسمتهای کوچکتری خرد شده و بر روی سیستمهای توزیع شده به صورت موازی اجرا میشود. مرحله بعد Reduce step نام دارد که در آن، نتیجه دریافتی حاصل از کوئریهای اجرا شده بر روی سیستمهای مختلف، با هم یکی خواهند شد.
این روش برای نمونه در سیستم Hadoop بسیار مرسوم است. Hadoop دارای یک فایل سیستم توزیع شده است (که پیشتر در مورد آن بحث شد) به همراه یک موتور Map Reduce توکار. همچنین رده دیگری از بانکهای اطلاعاتی NoSQL، اصطلاحا Wide column store نام دارند (مانند Hbase) که عموما به همراه Hadoop بکارگرفته میشوند. موتور Map Reduce متعلق به Hadoop بر روی جداول Hbase اجرا میشوند.
به علاوه Amazon web services دارای سرویسی است به نام Elastic map reduce یا EMR که در حقیقت مجموعهی پردازش ابری است که بر مبنای Hadoop کار میکند. این سرویس قادر است با بانکهای اطلاعاتی NoSQL دیگر و یا حتی بانکهای اطلاعاتی رابطهای نیز کار کند.
بنابراین MapReduce، یک بانک اطلاعاتی نیست؛ بلکه یک روش پردازش اطلاعات است که فایلها را به عنوان ورودی دریافت کرده و یک فایل را به عنوان خروجی تولید میکند. از آنجائیکه بسیاری از بانکهای اطلاعاتی NoSQL کار عمدهاشان، ایجاد و تغییر فایلها است، اغلب جداول اطلاعات آنها ورودی و خروجیهای معتبری برای یک موتور Map reduce به حساب میآیند.
در این بین، افزونهای برای Hadoop به نام Hive طراحی شده است که با ارائه HiveSQL، امکان نوشتن کوئریهایی SQL مانند را بر فراز موتورهای Map reduce ممکن میسازد. این افزونه با Hive tables خاص خودش و یا با Hbase سازگار است.
آشنایی مقدماتی با مفاهیمی مانند الگوهای Sharding و Partitioning در بانکهای اطلاعاتی NoSQL
Sharding (شاردینگ تلفظ میشود) یک الگوی تقسیم اطلاعات بر روی چندین سرور است که اساس توزیع شده بودن بانکهای اطلاعاتی NoSQL را تشکیل میدهد. این نوع تقسیم اطلاعات، از کوئریهایی به نام Fan-out پشتیبانی میکند. به این معنا که شما کوئری خود را به نود اصلی ارسال میکنید و سپس به کمک موتورهای Map reduce، این کوئری بر روی سرورهای مختلف اجرا شده و نتیجه نهایی جمع آوری خواهد شد. به این ترتیب تقسیم اطلاعات، صرفا به معنای قرار دادن یک سری فایل بر روی سرورهای مختلف نیست، بلکه هر کدام از این سرورها به صورت مستقل نیز قابلیت پردازش اطلاعات را دارند.
امکان تکثیر و همچنین replication هر کدام از سرورها نیز وجود دارد که قابلیت بازیابی سریع و مقاومت در برابر خرابیها و مشکلات را افزایش میدهند.
از آنجائیکه Shardها را میتوان در سرورهای بسیار متفاوت و گستردهای از لحاظ جغرافیایی قرار داد، هر Shard میتواند همانند مفاهیم CDN نیز عمل کند؛ به این معنا که میتوان Shard مورد نیاز سروری خاص را در محلی نزدیکتر به او قرار داد. به این ترتیب سرعت عملیات افزایش یافته و همچنین بار شبکه نیز کاهش مییابد.