Skip to content

فصل ۱۲ — آیندهٔ سیستم‌های داده

اگر چیزی به چیز دیگری به‌عنوان غایت منظور شده باشد، غایت نهایی آن نمی‌تواند حفظ وجود خود باشد. از این رو، ناخدای کشتی غایت نهایی‌اش حفظ کشتی‌ای نیست که به او سپرده شده، زیرا کشتی به چیز دیگری به‌عنوان غایت منظور شده، یعنی ناوبری. (اغلب به این صورت نقل می‌شود: اگر بالاترین هدف ناخدا حفظ کشتی‌اش بود، آن را برای همیشه در بندر نگه می‌داشت.) — توماس آکویناس، مجموعهٔ الهیات (۱۲۶۵–۱۲۷۴)

تا اینجا، این کتاب عمدتاً دربارهٔ توصیف وضعیت فعلی چیزها بوده است. در این فصل پایانی، دیدگاهمان را به سمت آینده می‌چرخانیم و بحث می‌کنیم چیزها چگونه باید باشند: ایده‌ها و رویکردهایی را پیشنهاد می‌کنم که — به باور من — ممکن است اساساً شیوهٔ طراحی و ساخت برنامه‌ها را بهبود دهند.

نظرات و حدس‌ها دربارهٔ آینده البته ذاتیاً ذهنی‌اند، بنابراین در این فصل هنگام نوشتن دربارهٔ نظرات شخصی‌ام از ضمیر اول شخص استفاده می‌کنم. خوش آمدید با آن‌ها مخالفت کنید و نظرات خود را شکل دهید، اما امیدوارم ایده‌های این فصل حداقل نقطهٔ شروعی برای بحثی سازنده باشد و وضوحی به مفاهیمی ببخشد که اغلب گیج‌کننده‌اند.

هدف این کتاب در فصل ۱ ترسیم شد: کاوش در چگونگی ساخت برنامه‌ها و سیستم‌هایی که قابل اعتماد، مقیاس‌پذیر و قابل نگهداری‌اند. این مضامین در همهٔ فصل‌ها جریان داشته‌اند: مثلاً الگوریتم‌های تحمل خطای زیادی را بحث کردیم که قابلیت اطمینان را بهبود می‌دهند، پارتیشن‌بندی برای مقیاس‌پذیری، و مکانیزم‌های تکامل و انتزاع برای قابلیت نگهداری. در این فصل همهٔ این ایده‌ها را کنار هم می‌آوریم و بر پایهٔ آن‌ها آینده را تصور می‌کنیم. هدفمان کشف چگونگی طراحی برنامه‌هایی است که بهتر از برنامه‌های امروز باشند — مقاوم، درست، قابل تکامل، و در نهایت سودمند برای بشریت.

یکپارچه‌سازی داده

موضوع تکرارشونده در این کتاب این بوده که برای هر مسئلهٔ مشخص، چند راه‌حل وجود دارد که هر کدام مزایا، معایب و trade-offهای متفاوتی دارند. مثلاً هنگام بحث موتورهای ذخیره‌سازی در فصل ۳، ذخیره‌سازی مبتنی بر لاگ، B-tree و ذخیره‌سازی ستونی را دیدیم. هنگام بحث رپلیکاسیون در فصل ۵، رویکردهای single-leader، multi-leader و leaderless را دیدیم.

اگر مسئله‌ای مثل «می‌خواهم داده‌ای ذخیره کنم و بعداً دوباره بخوانم» دارید، یک راه‌حل درست واحد وجود ندارد، بلکه رویکردهای مختلفی هست که هر کدام در شرایط متفاوتی مناسب‌اند. پیاده‌سازی نرم‌افزار معمولاً باید یک رویکرد مشخص را انتخاب کند. رساندن یک مسیر کد به حالت مقاوم و با عملکرد خوب به‌اندازهٔ کافی سخت است — تلاش برای انجام همهٔ کارها در یک قطعه نرم‌افزار تقریباً تضمین می‌کند پیاده‌سازی ضعیف باشد.

بنابراین، انتخاب مناسب ابزار نرم‌افزاری هم به شرایط بستگی دارد. هر قطعه نرم‌افزار، حتی پایگاه دادهٔ به‌اصطلاح «عمومی»، برای الگوی استفادهٔ خاصی طراحی شده است.

در مواجهه با این انبوه گزینه‌ها، اولین چالش تعیین نگاشت بین محصولات نرم‌افزاری و شرایطی است که در آن‌ها مناسب‌اند. فروشندگان — قابل درک — کم‌مایل‌اند بگویند نرم‌افزارشان برای چه بارهایی نامناسب است، اما امیدوارم فصل‌های قبلی شما را با پرسش‌هایی مجهز کرده باشد تا بین خطوط بخوانید و trade-offها را بهتر درک کنید.

با این حال، حتی اگر نگاشت بین ابزارها و شرایط استفاده را کاملاً درک کنید، چالش دیگری هست: در برنامه‌های پیچیده، داده اغلب به چند روش متفاوت استفاده می‌شود. بعید است یک قطعه نرم‌افزار برای همهٔ شرایط متفاوت استفاده از داده مناسب باشد، پس ناگزیر چند قطعه نرم‌افزار مختلف را کنار هم می‌چینید تا عملکرد برنامه را فراهم کنید.

ترکیب ابزارهای تخصصی با مشتق‌سازی داده

مثلاً رایج است که پایگاه دادهٔ OLTP را با ایندکس جستجوی متن کامل یکپارچه کنید تا پرس‌وجوهای کلمات کلیدی دلخواه را مدیریت کنید. اگرچه برخی پایگاه‌های داده (مثل PostgreSQL) قابلیت ایندکس‌گذاری متن کامل دارند که برای برنامه‌های ساده کافی است [۱]، امکانات جستجوی پیچیده‌تر به ابزارهای تخصصی بازیابی اطلاعات نیاز دارد. برعکس، ایندکس‌های جستجو معمولاً به‌عنوان سیستم ثبت پایدار مناسب نیستند، پس بسیاری از برنامه‌ها باید دو ابزار مختلف را ترکیب کنند تا همهٔ الزامات را برآورند.

به مسئلهٔ یکپارچه‌سازی سیستم‌های داده در «همگام نگه‌داشتن سیستم‌ها» در صفحه ۴۵۲ اشاره کردیم. با افزایش تعداد بازنمایی‌های مختلف داده، مشکل یکپارچه‌سازی سخت‌تر می‌شود. علاوه بر پایگاه داده و ایندکس جستجو، شاید لازم باشد کپی‌های داده را در سیستم‌های تحلیلی (انبار داده، یا سیستم‌های پردازش دسته‌ای و جریانی) نگه دارید؛ کش‌ها یا نسخه‌های denormalize‌شدهٔ اشیایی که از دادهٔ اصلی مشتق شده‌اند را نگهداری کنید؛ داده را از سیستم‌های یادگیری ماشین، طبقه‌بندی، رتبه‌بندی یا توصیه عبور دهید؛ یا بر اساس تغییرات داده اعلان بفرستید.

شگفت‌آورانه اغلب می‌بینم مهندسان نرم‌افزار جملاتی مثل «به تجربهٔ من، ۹۹٪ مردم فقط به X نیاز دارند» یا «…به X نیاز ندارند» (برای مقادیر مختلف X) می‌گویند. فکر می‌کنم چنین جملاتی بیشتر دربارهٔ تجربهٔ گوینده می‌گویند تا سودمندی واقعی فناوری. دامنهٔ کارهایی که ممکن است با داده بخواهید انجام دهید سرگیج‌کننده‌اند. آنچه یک نفر ویژگی مبهم و بی‌فایده می‌داند ممکن است برای دیگری الزام مرکزی باشد. نیاز به یکپارچه‌سازی داده اغلب فقط وقتی آشکار می‌شود که زوم کنید و جریان‌های داده در کل سازمان را ببینید.

استدلال دربارهٔ جریان‌های داده

وقتی کپی‌های یک داده باید در چند سیستم ذخیره‌سازی نگه داشته شوند تا الگوهای دسترسی مختلف را برآورند، باید کاملاً روشن باشید ورودی‌ها و خروجی‌ها کجاست: داده اول کجا نوشته می‌شود و کدام بازنمایی‌ها از کدام منابع مشتق شده‌اند؟ چگونه داده را به همهٔ جاهای درست، با قالب‌های درست می‌رسانید؟

مثلاً ممکن است داده ابتدا به پایگاه دادهٔ سیستم ثبت نوشته شود، تغییرات آن پایگاه (به «Change Data Capture» در صفحه ۴۵۴ مراجعه کنید) ضبط شود و سپس تغییرات به همان ترتیب روی ایندکس جستجو اعمال شوند. اگر CDC تنها راه به‌روزرسانی ایندکس باشد، می‌توانید مطمئن باشید ایندکس کاملاً از سیستم ثبت مشتق شده و بنابراین با آن سازگار است (به‌جز باگ‌های نرم‌افزار). نوشتن به پایگاه داده تنها راه ورود دادهٔ جدید به این سیستم است.

اجازه دادن به برنامه برای نوشتن مستقیم هم به ایندکس جستجو و هم به پایگاه داده، مشکلی را ایجاد می‌کند که در شکل ۱۱-۴ نشان داده شد: دو کلاینت همزمان نوشتن‌های متضاد می‌فرستند و دو سیستم ذخیره‌سازی آن‌ها را با ترتیب متفاوت پردازش می‌کنند. در این حالت، نه پایگاه داده و نه ایندکس جستجو «مسئول» تعیین ترتیب نوشتن‌ها نیستند، پس ممکن است تصمیم‌های متضاد بگیرند و برای همیشه با هم ناسازگار شوند.

اگر بتوانید همهٔ ورودی کاربر را از یک سیستم عبور دهید که ترتیب همهٔ نوشتن‌ها را تعیین کند، مشتق‌سازی بازنمایی‌های دیگر داده با پردازش نوشتن‌ها به همان ترتیب بسیار آسان‌تر می‌شود. این کاربرد رویکرد state machine replication است که در «Total Order Broadcast» در صفحه ۳۴۸ دیدیم. استفاده از CDC یا لاگ event sourcing کمتر از اصل تعیین ترتیب کل مهم است.

به‌روزرسانی سیستم دادهٔ مشتق بر اساس لاگ رویداد اغلب می‌تواند قطعی و idempotent شود (به «Idempotence» در صفحه ۴۷۸ مراجعه کنید)، که بازیابی از خطا را نسبتاً آسان می‌کند.

دادهٔ مشتق در برابر تراکنش‌های توزیع‌شده

رویکرد کلاسیک برای همگام نگه‌داشتن سیستم‌های دادهٔ مختلف، تراکنش‌های توزیع‌شده است، همان‌طور که در «Atomic Commit and Two-Phase Commit (2PC)» در صفحه ۳۵۴ بحث شد. رویکرد استفاده از سیستم‌های دادهٔ مشتق در مقایسه با تراکنش‌های توزیع‌شده چگونه است؟

در سطح انتزاعی، هر دو با ابزارهای متفاوت هدف مشابهی را دنبال می‌کنند. تراکنش‌های توزیع‌شده با قفل برای mutual exclusion ترتیب نوشتن را تعیین می‌کنند (به «Two-Phase Locking (2PL)» در صفحه ۲۵۷ مراجعه کنید)، در حالی که CDC و event sourcing از لاگ برای ترتیب‌دهی استفاده می‌کنند. تراکنش‌های توزیع‌شده از atomic commit برای اطمینان از اثر دقیقاً یک‌بار استفاده می‌کنند، در حالی که سیستم‌های مبتنی بر لاگ اغلب بر retry قطعی و idempotence تکیه می‌کنند.

بزرگ‌ترین تفاوت این است که سیستم‌های تراکنش معمولاً linearizability فراهم می‌کنند (به «Linearizability» در صفحه ۳۲۴ مراجعه کنید)، که تضمین‌های مفیدی مثل خواندن نوشتن‌های خودتان (به «Reading Your Own Writes» در صفحه ۱۶۲ مراجعه کنید) را به‌دنبال دارد. از سوی دیگر، سیستم‌های دادهٔ مشتق اغلب به‌صورت ناهمگام به‌روز می‌شوند، پس به‌طور پیش‌فرض همان تضمین‌های زمانی را ندارند.

در محیط‌های محدودی که حاضر به پرداخت هزینهٔ تراکنش‌های توزیع‌شده‌اند، با موفقیت استفاده شده‌اند. با این حال، فکر می‌کنم XA ویژگی‌های تحمل خطا و عملکرد ضعیفی دارد (به «Distributed Transactions in Practice» در صفحه ۳۶۰ مراجعه کنید) که سودمندی آن را شدیداً محدود می‌کند. باور دارم ممکن است پروتکل بهتری برای تراکنش‌های توزیع‌شده ساخته شود، اما پذیرش گسترده و یکپارچه‌سازی با ابزارهای موجود چالش‌برانگیز است و به‌زودی محتمل نیست.

در غیاب پشتیبانی گسترده از پروتکل خوب تراکنش توزیع‌شده، باور دارم دادهٔ مشتق مبتنی بر لاگ امیدوارکننده‌ترین رویکرد برای یکپارچه‌سازی سیستم‌های دادهٔ مختلف است. با این حال، تضمین‌هایی مثل خواندن نوشتن‌های خودتان مفیدند، و فکر نمی‌کنم مولد باشد به همه بگوییم «consistency نهایی اجتناب‌ناپذیر است — تحمل کنید و یاد بگیرید با آن کنار بیایید» (حداقل بدون راهنمایی خوب برای کنار آمدن با آن).

در «تلاش برای درستی» در صفحه ۵۱۵، رویکردهایی برای پیاده‌سازی تضمین‌های قوی‌تر روی سیستم‌های مشتق ناهمگام و حرکت به سمت میانهٔ راه بین تراکنش‌های توزیع‌شده و سیستم‌های مبتنی بر لاگ ناهمگام بحث می‌کنیم.

محدودیت‌های ترتیب کل

در سیستم‌های به‌اندازهٔ کافی کوچک، ساخت لاگ رویداد کاملاً مرتب شدنی است (همان‌طور که محبوبیت پایگاه‌های داده با رپلیکاسیون single-leader نشان می‌دهد که دقیقاً چنین لاگی می‌سازند). اما با مقیاس‌پذیری به بارهای بزرگ‌تر و پیچیده‌تر، محدودیت‌ها آشکار می‌شوند:

  • در بیشتر موارد، ساخت لاگ کاملاً مرتب نیاز دارد همهٔ رویدادها از یک گره leader عبور کنند که ترتیب را تعیین کند. اگر throughput رویدادها از ظرفیت یک ماشین بیشتر باشد، باید آن را بین چند ماشین پارتیشن کنید (به «Partitioned Logs» در صفحه ۴۴۶ مراجعه کنید). ترتیب رویدادها در دو پارتیشن مختلف مبهم می‌شود.
  • اگر سرورها در چند دیتاسنتر جغرافیایی توزیع‌شده‌اند — مثلاً برای تحمل خاموشی کل دیتاسنتر — معمولاً leader جدا در هر دیتاسنتر دارید، چون تأخیر شبکه هماهنگی همزمان بین دیتاسنترها را ناکارآمد می‌کند (به «Multi-Leader Replication» در صفحه ۱۶۸ مراجعه کنید). این یعنی ترتیب تعریف‌نشدهٔ رویدادهایی که در دو دیتاسنتر مختلف منشأ می‌گیرند.
  • وقتی برنامه‌ها به‌صورت microservice مستقر می‌شوند (به «Dataflow Through Services: REST and RPC» در صفحه ۱۳۱ مراجعه کنید)، انتخاب رایج این است که هر سرویس و state پایدارش را به‌عنوان واحد مستقل مستقر کنید، بدون state پایدار مشترک بین سرویس‌ها. وقتی دو رویداد در سرویس‌های مختلف منشأ می‌گیرند، ترتیب تعریف‌شده‌ای برای آن رویدادها نیست.
  • برخی برنامه‌ها state سمت کلاینت نگه می‌دارند که بلافاصله پس از ورودی کاربر به‌روز می‌شود (بدون انتظار تأیید سرور) و حتی آفلاین کار می‌کنند (به «Clients with offline operation» در صفحه ۱۷۰ مراجعه کنید). در چنین برنامه‌هایی، کلاینت‌ها و سرورها به‌احتمال زیاد رویدادها را با ترتیب متفاوت می‌بینند.

از نظر رسمی، تعیین ترتیب کل رویدادها total order broadcast نامیده می‌شود که معادل consensus است (به «Consensus algorithms and total order broadcast» در صفحه ۳۶۶ مراجعه کنید). بیشتر الگوریتم‌های consensus برای موقعیت‌هایی طراحی شده‌اند که throughput یک گره برای پردازش کل جریان رویدادها کافی است، و این الگوریتم‌ها مکانیزمی برای اشتراک‌گذاری کار ترتیب‌دهی بین چند گره ندارند. طراحی الگوریتم‌های consensus که فراتر از throughput یک گره مقیاس یابند و در محیط جغرافیایی توزیع‌شده خوب کار کنند، هنوز مسئلهٔ باز تحقیقاتی است.

ترتیب‌دهی رویدادها برای ثبت علیّت

در مواردی که پیوند علیّی بین رویدادها نیست، نبود ترتیب کل مشکل بزرگی نیست، چون رویدادهای همزمان را می‌توان به‌صورت دلخواه مرتب کرد. برخی موارد دیگر آسان‌اند: مثلاً وقتی چند به‌روزرسانی از یک شیء وجود دارد، می‌توان همهٔ به‌روزرسانی‌های یک object ID مشخص را به همان پارتیشن لاگ هدایت کرد و کاملاً مرتب کرد. اما وابستگی‌های علیّی گاه به‌شکل ظریف‌تری پدید می‌آیند (به «Ordering and Causality» در صفحه ۳۳۹ هم مراجعه کنید).

مثلاً سرویس شبکهٔ اجتماعی را در نظر بگیرید: دو کاربر در رابطه بوده‌اند و تازه جدا شده‌اند. یکی دیگری را از لیست دوستان حذف می‌کند و سپس به دوستان باقی‌مانده پیامی دربارهٔ شریک سابقش می‌فرستد. قصد کاربر این است که شریک سابق پیام توهین‌آمیز را نبیند، چون پیام پس از لغو وضعیت دوستی ارسال شده است.

اما در سیستمی که وضعیت دوستی در یک جا و پیام‌ها در جای دیگر ذخیره می‌شوند، وابستگی ترتیبی بین رویداد unfriend و رویداد ارسال پیام ممکن است از دست برود. اگر وابستگی علیّی ثبت نشود، سرویسی که اعلان پیام‌های جدید می‌فرستد ممکن است رویداد ارسال پیام را پیش از رویداد unfriend پردازش کند و اشتباه به شریک سابق اعلان بفرستد.

در این مثال، اعلان‌ها در عمل join بین پیام‌ها و لیست دوستان‌اند و به مسائل زمانی joinهایی مربوط است که قبلاً بحث کردیم (به «Time-dependence of joins» در صفحه ۴۷۵ مراجعه کنید). متأسفانه پاسخ ساده‌ای به نظر نمی‌رسد [۲، ۳]. نقطه‌های شروع شامل موارد زیر است:

  • timestampهای منطقی می‌توانند بدون هماهنگی ترتیب کل فراهم کنند (به «Sequence Number Ordering» در صفحه ۳۴۳ مراجعه کنید)، پس در مواردی که total order broadcast ممکن نیست کمک می‌کنند. با این حال، گیرندگان همچنان باید رویدادهایی که خارج از ترتیب تحویل می‌شوند را مدیریت کنند و metadata اضافی باید رد و بدل شود.
  • اگر بتوانید رویدادی را برای ثبت وضعیت سیستمی که کاربر پیش از تصمیم دیده لاگ کنید و شناسهٔ یکتایی به آن بدهید، هر رویداد بعدی می‌تواند به آن شناسه ارجاع دهد تا وابستگی علیّی ثبت شود [۴]. در «خواندن‌ها هم رویدادند» در صفحه ۵۱۳ به این ایده برمی‌گردیم.
  • الگوریتم‌های حل تعارض (به «Automatic Conflict Resolution» در صفحه ۱۷۴ مراجعه کنید) در پردازش رویدادهایی که با ترتیب غیرمنتظره تحویل می‌شوند کمک می‌کنند. برای نگهداری state مفیدند، اما اگر عملیات اثرات جانبی خارجی داشته باشد (مثل ارسال اعلان به کاربر) کمک نمی‌کنند.

شاید با گذشت زمان، الگوهایی برای توسعهٔ برنامه ظهور کنند که وابستگی‌های علیّی را به‌طور کارآمد ثبت کنند و state مشتق را درست نگه دارند، بدون اینکه همهٔ رویدادها را از گلوگاه total order broadcast عبور دهند.

پردازش دسته‌ای و جریانی

می‌گویم هدف یکپارچه‌سازی داده اطمینان از این است که داده به شکل درست در همهٔ جاهای درست برسد. این کار نیاز به مصرف ورودی‌ها، تبدیل، join، فیلتر، تجمیع، آموزش مدل، ارزیابی و در نهایت نوشتن به خروجی‌های مناسب دارد. پردازشگرهای دسته‌ای و جریانی ابزار رسیدن به این هدف‌اند.

خروجی‌های پردازش دسته‌ای و جریانی مجموعه‌داده‌های مشتق‌اند مثل ایندکس جستجو، نمای مادی‌شده، توصیه‌ها برای نمایش به کاربران، معیارهای تجمیعی و غیره (به «The Output of Batch Workflows» در صفحه ۴۱۱ و «Uses of Stream Processing» در صفحه ۴۶۵ مراجعه کنید).

همان‌طور که در فصل ۱۰ و ۱۱ دیدیم، پردازش دسته‌ای و جریانی اصول مشترک زیادی دارند و تفاوت بنیادی اصلی این است که پردازشگرهای جریانی روی مجموعه‌داده‌های بدون مرز کار می‌کنند در حالی که ورودی‌های پردازش دسته‌ای اندازهٔ مشخص و محدود دارند. تفاوت‌های جزئی زیادی در پیاده‌سازی موتورهای پردازش هم هست، اما این تمایزها در حال محو شدن‌اند.

Spark پردازش جریانی را با شکستن جریان به microbatch روی موتور پردازش دسته‌ای انجام می‌دهد، در حالی که Apache Flink پردازش دسته‌ای را روی موتور پردازش جریانی انجام می‌دهد [۵]. در اصل، یک نوع پردازش را می‌توان روی دیگری شبیه‌سازی کرد، اگرچه ویژگی‌های عملکرد متفاوت است: مثلاً microbatching ممکن است روی پنجره‌های hopping یا sliding ضعیف عمل کند [۶].

نگهداری state مشتق

پردازش دسته‌ای طعم نسبتاً تابعی دارد (حتی اگر کد به زبان تابعی نوشته نشده باشد): توابع قطعی و خالص را تشویق می‌کند که خروجی‌شان فقط به ورودی بستگی دارد و اثر جانبی جز خروجی‌های صریح ندارند، ورودی‌ها را تغییرناپذیر و خروجی‌ها را فقط append می‌داند.

پردازش جریانی مشابه است، اما اپراتورها را گسترش می‌دهد تا state مدیریت‌شده و تحمل‌پذیر خطا را مجاز کند (به «Rebuilding state after a failure» در صفحه ۴۷۸ مراجعه کنید).

اصل توابع قطعی با ورودی و خروجی تعریف‌شده نه فقط برای تحمل خطا خوب است (به «Idempotence» در صفحه ۴۷۸ مراجعه کنید)، بلکه استدلال دربارهٔ جریان‌های داده در سازمان را ساده می‌کند [۷]. فرقی نمی‌کند دادهٔ مشتق ایندکس جستجو، مدل آماری یا کش باشد — مفید است دربارهٔ خطوط لولهٔ داده فکر کنید که یک چیز را از دیگری مشتق می‌کنند، تغییرات state در یک سیستم را از طریق کد تابعی عبور می‌دهند و اثر را روی سیستم‌های مشتق اعمال می‌کنند.

در اصل، سیستم‌های دادهٔ مشتق می‌توانند به‌صورت همزمان نگه داشته شوند، درست مثل اینکه پایگاه دادهٔ رابطه‌ای ایندکس‌های ثانویه را همزمان در همان تراکنش با نوشتن به جدول ایندکس‌شده به‌روز می‌کند. اما ناهمگامی چیزی است که سیستم‌های مبتنی بر لاگ رویداد را مقاوم می‌کند: خطا در بخشی از سیستم را محلی نگه می‌دارد، در حالی که تراکنش‌های توزیع‌شده اگر هر شرکت‌کننده‌ای شکست بخورد abort می‌شوند و تمایل دارند خطا را به بقیهٔ سیستم گسترش دهند (به «Limitations of distributed transactions» در صفحه ۳۶۳ مراجعه کنید).

در «Partitioning and Secondary Indexes» در صفحه ۲۰۶ دیدیم که ایندکس‌های ثانویه اغلب مرز پارتیشن را قطع می‌کنند. سیستم پارتیشن‌شده با ایندکس ثانویه یا باید نوشتن‌ها را به چند پارتیشن بفرستد (اگر ایندکس term-partitioned باشد) یا خواندن‌ها را به همهٔ پارتیشن‌ها (اگر document-partitioned باشد). چنین ارتباط بین‌پارتیشنی هم اگر ایندکس به‌صورت ناهمگام نگه داشته شود قابل اعتمادتر و مقیاس‌پذیرتر است [۸] (به «Multi-partition data processing» در صفحه ۵۱۴ هم مراجعه کنید).

پردازش مجدد داده برای تکامل برنامه

هنگام نگهداری دادهٔ مشتق، پردازش دسته‌ای و جریانی هر دو مفیدند. پردازش جریانی تغییرات ورودی را با تأخیر کم در نماهای مشتق منعکس می‌کند، در حالی که پردازش دسته‌ای امکان پردازش مجدد حجم زیاد دادهٔ تاریخی انباشته‌شده را برای مشتق‌سازی نماهای جدید روی مجموعه‌دادهٔ موجود می‌دهد.

به‌ویژه، پردازش مجدد دادهٔ موجود مکانیزم خوبی برای نگهداری سیستم و تکامل آن برای پشتیبانی از ویژگی‌ها و الزامات جدید است (فصل ۴ را ببینید). بدون پردازش مجدد، تکامل schema به تغییرات ساده محدود می‌شود مثل افزودن فیلد اختیاری جدید به رکورد یا نوع رکورد جدید — هم در schema-on-write و هم schema-on-read (به «Schema flexibility in the document model» در صفحه ۳۹ مراجعه کنید). با پردازش مجدد، می‌توان مجموعه‌داده را به مدل کاملاً متفاوت بازساخت تا بهتر به الزامات جدید پاسخ دهد.

مهاجرت schema در راه‌آهن

«مهاجرت‌های schema» در مقیاس بزرگ در سیستم‌های غیررایانه‌ای هم رخ می‌دهد. مثلاً در روزهای اول ساخت راه‌آهن در انگلستان قرن نوزدهم، استانداردهای رقابتی مختلفی برای gauge (فاصلهٔ دو ریل) وجود داشت. قطارهای ساخته‌شده برای یک gauge نمی‌توانستند روی ریل gauge دیگر حرکت کنند، که اتصالات ممکن در شبکهٔ قطار را محدود می‌کرد [۹].

پس از تصمیم نهایی بر gauge استاندارد واحد در ۱۸۴۶، ریل‌های gauge دیگر باید تبدیل می‌شدند — اما چگونه بدون تعطیلی خط قطار برای ماه‌ها یا سال‌ها؟ راه‌حل این بود که ابتدا ریل را به dual gauge یا mixed gauge با افزودن ریل سوم تبدیل کنید. این تبدیل می‌تواند تدریجی انجام شود و پس از آن، قطارهای هر دو gauge می‌توانند روی خط با دو ریل از سه ریل حرکت کنند. در نهایت، وقتی همهٔ قطارها به gauge استاندارد تبدیل شدند، ریل gauge غیراستاندارد را می‌توان حذف کرد.

«پردازش مجدد» ریل‌های موجود به این شکل و اجازهٔ هم‌زیستی نسخهٔ قدیم و جدید، امکان تغییر تدریجی gauge در طول سال‌ها را می‌دهد. با این حال، کار پرهزینه‌ای است و gaugeهای غیراستاندارد هنوز امروز وجود دارند — مثلاً سیستم BART در منطقهٔ خلیج سان‌فرانسیسکو gauge متفاوتی از اکثریت آمریکا دارد.

نماهای مشتق تکامل تدریجی را ممکن می‌کنند. اگر بخواهید مجموعه‌داده را بازساخت کنید، لازم نیست مهاجرت را به‌صورت تعویض ناگهانی انجام دهید. می‌توانید schema قدیم و جدید را به‌عنوان دو نمای مشتق مستقل روی همان دادهٔ زیرین کنار هم نگه دارید. سپس تعداد کمی کاربر را به نمای جدید منتقل کنید تا عملکرد را آزمایش و باگ‌ها را پیدا کنید، در حالی که بیشتر کاربران به نمای قدیم هدایت می‌شوند. تدریجاً سهم کاربران نمای جدید را افزایش دهید و در نهایت نمای قدیم را حذف کنید [۱۰].

زیبایی چنین مهاجرت تدریجی این است که هر مرحله به‌راحتی قابل برگشت است اگر مشکلی پیش آید: همیشه سیستم کارایی برای بازگشت دارید. با کاهش ریسک آسیب غیرقابل برگشت، می‌توانید با اطمینان بیشتر پیش بروید و سریع‌تر سیستم را بهبود دهید [۱۱].

معماری lambda

اگر پردازش دسته‌ای برای پردازش مجدد دادهٔ تاریخی و پردازش جریانی برای به‌روزرسانی‌های اخیر استفاده شود، چگونه دو را ترکیب کنیم؟ معماری lambda [۱۲] پیشنهادی در این حوزه است که توجه زیادی جلب کرده است.

ایدهٔ هستهٔ معماری lambda این است که دادهٔ ورودی با الحاق رویدادهای تغییرناپذیر به مجموعه‌داده‌ای همیشه در حال رشد ثبت شود، مشابه event sourcing (به «Event Sourcing» در صفحه ۴۵۷ مراجعه کنید). از این رویدادها، نماهای بهینه‌شده برای خواندن مشتق می‌شوند. معماری lambda پیشنهاد می‌کند دو سیستم مختلف به‌صورت موازی اجرا شوند: سیستم پردازش دسته‌ای مثل Hadoop MapReduce و سیستم پردازش جریانی جدا مثل Storm.

در رویکرد lambda، پردازشگر جریانی رویدادها را مصرف می‌کند و به‌سرعت به‌روزرسانی تقریبی نما را تولید می‌کند؛ پردازشگر دسته‌ای بعداً همان مجموعه رویدادها را مصرف می‌کند و نسخهٔ اصلاح‌شدهٔ نمای مشتق را تولید می‌کند. منطق این طراحی این است که پردازش دسته‌ای ساده‌تر و در نتیجه کمتر مستعد باگ است، در حالی که پردازشگرهای جریانی کمتر قابل اعتماد و سخت‌تر برای fault-tolerant شدن تلقی می‌شوند (به «Fault Tolerance» در صفحه ۴۷۶ مراجعه کنید). علاوه بر این، لایهٔ جریانی می‌تواند از الگوریتم‌های تقریبی سریع و لایهٔ دسته‌ای از الگوریتم‌های دقیق کندتر استفاده کند.

معماری lambda ایدهٔ تأثیرگذاری بود که طراحی سیستم‌های داده را بهتر کرد، به‌ویژه با محبوب کردن اصل مشتق‌سازی نما روی جریان رویدادهای تغییرناپذیر و پردازش مجدد رویدادها در صورت نیاز. با این حال، فکر می‌کنم مشکلات عملی متعددی دارد:

  • نگه‌داشتن همان منطق برای اجرا هم در چارچوب دسته‌ای و هم جریانی تلاش اضافی قابل توجهی است. اگرچه کتابخانه‌هایی مثل Summingbird [۱۳] انتزاعی برای محاسباتی که در هر دو زمینه اجرا شوند فراهم می‌کنند، پیچیدگی عملیاتی دیباگ، تنظیم و نگهداری دو سیستم مختلف باقی می‌ماند [۱۴].
  • چون خط لولهٔ جریانی و دسته‌ای خروجی‌های جدا تولید می‌کنند، برای پاسخ به درخواست کاربر باید ادغام شوند. این ادغام برای تجمیع ساده روی پنجرهٔ tumbling آسان است، اما برای نماهای مشتق‌شده با عملیات پیچیده‌تر مثل join و sessionization یا خروجی غیرسری زمانی، بسیار سخت‌تر می‌شود.
  • اگرچه توانایی پردازش مجدد کل مجموعه‌دادهٔ تاریخی عالی است، انجام مکرر آن روی مجموعه‌داده‌های بزرگ پرهزینه است. بنابراین خط لولهٔ دسته‌ای اغلب باید برای پردازش دسته‌های افزایشی (مثلاً یک ساعت داده در پایان هر ساعت) تنظیم شود نه پردازش مجدد همهٔ چیز. این مسائل بحث‌شده در «Reasoning About Time» در صفحه ۴۶۸ را مطرح می‌کند، مثل مدیریت straggler و پنجره‌هایی که مرز بین دسته‌ها را قطع می‌کنند. افزایشی کردن محاسبهٔ دسته‌ای پیچیدگی اضافه می‌کند و آن را شبیه لایهٔ جریانی می‌کند، در تضاد با هدف ساده نگه‌داشتن لایهٔ دسته‌ای.

یکپارچه‌سازی پردازش دسته‌ای و جریانی

کارهای اخیرتر امکان بهره‌مندی از مزایای معماری lambda بدون معایب آن را فراهم کرده‌اند، با اجازهٔ پیاده‌سازی هم محاسبات دسته‌ای (پردازش مجدد دادهٔ تاریخی) و هم جریانی (پردازش رویدادها هنگام رسیدن) در یک سیستم [۱۵].

یکپارچه‌سازی پردازش دسته‌ای و جریانی در یک سیستم به ویژگی‌های زیر نیاز دارد که به‌طور فزاینده در دسترس‌اند:

  • توانایی پخش مجدد رویدادهای تاریخی از همان موتور پردازشی که جریان رویدادهای اخیر را مدیریت می‌کند. مثلاً message brokerهای مبتنی بر لاگ می‌توانند پیام‌ها را replay کنند (به «Replaying old messages» در صفحه ۴۵۱ مراجعه کنید) و برخی پردازشگرهای جریانی ورودی را از filesystem توزیع‌شده مثل HDFS می‌خوانند.
  • semantics دقیقاً یک‌بار برای پردازشگرهای جریانی — یعنی اطمینان از اینکه خروجی همان است که اگر خطایی رخ نداده بود، حتی اگر خطا رخ داده باشد (به «Fault Tolerance» در صفحه ۴۷۶ مراجعه کنید). مانند پردازش دسته‌ای، این نیاز به دور انداختن خروجی جزئی هر task شکست‌خورده دارد.
  • ابزارهای پنجره‌بندی بر اساس event time نه processing time، چون processing time هنگام پردازش مجدد رویدادهای تاریخی بی‌معناست (به «Reasoning About Time» در صفحه ۴۶۸ مراجعه کنید). مثلاً Apache Beam API برای بیان چنین محاسباتی فراهم می‌کند که سپس با Apache Flink یا Google Cloud Dataflow اجرا می‌شود.

باز کردن بستهٔ پایگاه‌های داده

در سطح انتزاعی، پایگاه‌های داده، Hadoop و سیستم‌عامل‌ها همگی یک کار را انجام می‌دهند: داده ذخیره می‌کنند و اجازهٔ پردازش و پرس‌وجو می‌دهند [۱۶]. پایگاه داده داده را در رکوردهایی با مدل دادهٔ مشخص (سطر در جدول، سند، رأس در گراف و غیره) ذخیره می‌کند، در حالی که filesystem سیستم‌عامل داده را در فایل‌ها ذخیره می‌کند — اما در هسته، هر دو «سیستم مدیریت اطلاعات»‌اند [۱۷]. همان‌طور که در فصل ۱۰ دیدیم، اکوسیستم Hadoop تا حدی نسخهٔ توزیع‌شدهٔ Unix است.

البته تفاوت‌های عملی زیادی وجود دارد. مثلاً بسیاری از filesystemها با دایرکتوری حاوی ۱۰ میلیون فایل کوچک خوب کنار نمی‌آیند، در حالی که پایگاه داده با ۱۰ میلیون رکورد کوچک کاملاً عادی است. با این حال، شباهت‌ها و تفاوت‌های سیستم‌عامل و پایگاه داده ارزش کاوش دارند.

Unix و پایگاه‌های دادهٔ رابطه‌ای با فلسفه‌های بسیار متفاوت به مسئلهٔ مدیریت اطلاعات نزدیک شده‌اند. Unix هدفش ارائهٔ انتزاع سخت‌افزاری منطقی اما نسبتاً سطح پایین به برنامه‌نویسان بود، در حالی که پایگاه‌های دادهٔ رابطه‌ای می‌خواستند به برنامه‌نویسان انتزاع سطح بالا بدهند که پیچیدگی ساختار داده روی دیسک، همزمانی، بازیابی از crash و غیره را پنهان کند. Unix pipe و فایل — فقط دنبالهٔ بایت — ساخت، در حالی که پایگاه‌های داده SQL و تراکنش ساختند.

کدام بهتر است؟ البته به خواستهٔ شما بستگی دارد. Unix در معنای «ساده‌تر» بودن لایهٔ نسبتاً نازکی روی منابع سخت‌افزاری است؛ پایگاه‌های دادهٔ رابطه‌ای در معنای «ساده‌تر» بودن اینکه پرس‌وجوی declarative کوتاه می‌تواند از زیرساخت قدرتمند (بهینه‌سازی پرس‌وجو، ایندکس، join، کنترل همزمانی، رپلیکاسیون و غیره) بدون درک جزئیات پیاده‌سازی توسط نویسندهٔ پرس‌وجو بهره ببرد.

تنش بین این فلسفه‌ها دهه‌ها ادامه داشته (هر دو Unix و مدل رابطه‌ای اوایل دههٔ ۱۹۷۰ ظهور کردند) و هنوز حل نشده. مثلاً جنبش NoSQL را تفسیر می‌کنم به‌عنوان خواست اعمال رویکرد شبیه Unix با انتزاع‌های سطح پایین به حوزهٔ ذخیره‌سازی دادهٔ OLTP توزیع‌شده.

در این بخش سعی می‌کنم دو فلسفه را آشتی دهم، با امید ترکیب بهترین هر دو جهان.

ترکیب فناوری‌های ذخیره‌سازی داده

در طول این کتاب، ویژگی‌های مختلف پایگاه‌های داده و نحوهٔ کار آن‌ها را بحث کردیم، از جمله:

  • ایندکس‌های ثانویه، که جستجوی کارآمد رکورد بر اساس مقدار فیلد را ممکن می‌کنند (به «Other Indexing Structures» در صفحه ۸۵ مراجعه کنید)
  • نماهای مادی‌شده، نوعی کش از پیش محاسبه‌شده از نتایج پرس‌وجو (به «Aggregation: Data Cubes and Materialized Views» در صفحه ۱۰۱ مراجعه کنید)
  • لاگ‌های رپلیکاسیون، که کپی داده روی گره‌های دیگر را به‌روز نگه می‌دارند (به «Implementation of Replication Logs» در صفحه ۱۵۸ مراجعه کنید)
  • ایندکس‌های جستجوی متن کامل (به «Full-text search and fuzzy indexes» در صفحه ۸۸ مراجعه کنید) که در برخی پایگاه‌های دادهٔ رابطه‌ای [۱] تعبیه شده‌اند

در فصل‌های ۱۰ و ۱۱ نیز مضامین مشابهی آمد: ساخت ایندکس جستجوی متن کامل (به «The Output of Batch Workflows» در صفحه ۴۱۱)، نگهداری نمای مادی‌شده (به «Maintaining materialized views» در صفحه ۴۶۷)، و رپلیکه‌کردن تغییرات از پایگاه داده به سیستم‌های دادهٔ مشتق (به «Change Data Capture» در صفحه ۴۵۴).

به نظر می‌رسد موازی بین ویژگی‌های تعبیه‌شده در پایگاه‌های داده و سیستم‌های دادهٔ مشتق که مردم با پردازشگرهای دسته‌ای و جریانی می‌سازند وجود دارد.

ساخت ایندکس

به این فکر کنید هنگام اجرای CREATE INDEX برای ساخت ایندکس جدید در پایگاه دادهٔ رابطه‌ای چه می‌شود. پایگاه داده باید snapshot سازگار از جدول را اسکن کند، همهٔ مقادیر فیلد ایندکس‌شده را استخراج و مرتب کند و ایندکس را بنویسد. سپس باید backlog نوشتن‌هایی که از زمان snapshot انجام شده را پردازش کند (فرض بر اینکه جدول هنگام ساخت ایندکس قفل نبوده و نوشتن ادامه داشته). پس از آن، باید ایندکس را هر زمان تراکنشی به جدول می‌نویسد به‌روز نگه دارد.

این فرایند به‌طرز چشمگیری شبیه راه‌اندازی follower رپلیکای جدید است (به «Setting Up New Followers» در صفحه ۱۵۵ مراجعه کنید) و بسیار شبیه bootstrap کردن CDC در سیستم جریانی (به «Initial snapshot» در صفحه ۴۵۵ مراجعه کنید).

هر بار CREATE INDEX اجرا می‌کنید، پایگاه داده در عمل مجموعه‌دادهٔ موجود را دوباره پردازش می‌کند (همان‌طور که در «پردازش مجدد داده برای تکامل برنامه» در صفحه ۴۹۶ بحث شد) و ایندکس را به‌عنوان نمای جدید روی دادهٔ موجود مشتق می‌کند. دادهٔ موجود ممکن است snapshot وضعیت باشد نه لاگ همهٔ تغییراتی که هرگز رخ داده، اما دو نزدیک‌اند (به «State, Streams, and Immutability» در صفحه ۴۵۹ مراجعه کنید).

متادیتابیس همه‌چیز

از این منظر، فکر می‌کنم جریان داده در کل سازمان شبیه یک پایگاه دادهٔ عظیم می‌شود [۷]. هرگاه فرایند batch، stream یا ETL داده را از جایی و قالبی به جا و قالب دیگر منتقل کند، مانند زیرسیستم پایگاه داده‌ای عمل می‌کند که ایندکس‌ها یا نماهای مادی‌شده را به‌روز نگه می‌دارد.

از این دید، پردازشگرهای دسته‌ای و جریانی مانند پیاده‌سازی‌های پیچیدهٔ trigger، stored procedure و نگهداری نمای مادی‌شده‌اند. سیستم‌های دادهٔ مشتق که نگه می‌دارند مانند انواع مختلف ایندکس‌اند. مثلاً پایگاه دادهٔ رابطه‌ای ممکن است ایندکس B-tree، hash، فضایی (به «Multi-column indexes» در صفحه ۸۷ مراجعه کنید) و انواع دیگر را پشتیبانی کند. در معماری در حال ظهور سیستم‌های دادهٔ مشتق، به‌جای پیاده‌سازی این امکانات به‌عنوان ویژگی یک محصول پایگاه دادهٔ یکپارچه، توسط قطعات نرم‌افزاری مختلف روی ماشین‌های مختلف و تیم‌های مختلف اداره می‌شوند.

این تحولات ما را به کجا می‌برد؟ اگر از فرض شروع کنیم که هیچ مدل داده یا قالب ذخیره‌سازی واحدی برای همهٔ الگوهای دسترسی مناسب نیست، حدس می‌زنم دو مسیر وجود دارد که ابزارهای ذخیره‌سازی و پردازش مختلف را می‌توان در سیستم منسجم ترکیب کرد:

پایگاه‌های دادهٔ فدرال: یکپارچه‌سازی خواندن

می‌توان رابط پرس‌وجوی یکپارچه روی انواع موتورهای ذخیره‌سازی و روش‌های پردازشی فراهم کرد — رویکردی به نام federated database یا polystore [۱۸، ۱۹]. مثلاً foreign data wrapper در PostgreSQL در این الگو می‌گنجد [۲۰]. برنامه‌هایی که مدل داده یا رابط پرس‌وجوی تخصصی نیاز دارند همچنان می‌توانند مستقیم به موتورهای زیرین دسترسی داشته باشند، در حالی که کاربرانی که می‌خواهند داده از مکان‌های مختلف را ترکیب کنند از رابط فدرال به‌راحتی این کار را انجام می‌دهند.

رابط پرس‌وجوی فدرال سنت رابطه‌ای سیستم یکپارچه با زبان پرس‌وجوی سطح بالا و semantics ظریف را دنبال می‌کند، اما پیاده‌سازی پیچیده است.

باز کردن بستهٔ پایگاه‌های داده: یکپارچه‌سازی نوشتن

در حالی که federation پرس‌وجوی فقط-خواندنی روی چند سیستم را پوشش می‌دهد، پاسخ خوبی برای همگام‌سازی نوشتن‌ها بین آن سیستم‌ها ندارد. گفتیم در یک پایگاه داده، ساخت ایندکس سازگار ویژگی تعبیه‌شده است. وقتی چند سیستم ذخیره‌سازی را ترکیب می‌کنیم، باید اطمینان حاصل کنیم همهٔ تغییرات داده به همهٔ جاهای درست می‌رسند، حتی در حضور خطا. آسان‌تر کردن اتصال قابل اعتماد سیستم‌های ذخیره‌سازی (مثلاً از طریق CDC و لاگ رویداد) مانند باز کردن بستهٔ ویژگی‌های نگهداری ایندکس پایگاه داده به‌شکلی است که نوشتن‌ها را بین فناوری‌های مختلف همگام می‌کند [۷، ۲۱].

رویکرد باز کردن بسته سنت Unix ابزارهای کوچکی که یک کار را خوب انجام می‌دهند [۲۲]، از طریق API سطح پایین یکنواخت (pipe) ارتباط می‌گیرند و با زبان سطح بالاتر (shell) ترکیب می‌شوند [۱۶].

عملی کردن باز کردن بسته

federation و باز کردن بسته دو روی یک سکه‌اند: ساخت سیستمی قابل اعتماد، مقیاس‌پذیر و قابل نگهداری از اجزای متنوع. پرس‌وجوی فقط-خواندنی فدرال نیاز به نگاشت یک مدل داده به دیگری دارد که نیاز به تفکر دارد اما در نهایت قابل مدیریت است. فکر می‌کنم همگام نگه‌داشتن نوشتن‌ها به چند سیستم ذخیره‌سازی مسئلهٔ مهندسی سخت‌تری است، پس روی آن تمرکز می‌کنم.

رویکرد سنتی همگام‌سازی نوشتن‌ها تراکنش‌های توزیع‌شده بین سیستم‌های ذخیره‌سازی ناهمگن را می‌طلبد [۱۸] که فکر می‌کنم راه‌حل اشتباه است (به «دادهٔ مشتق در برابر تراکنش‌های توزیع‌شده» در صفحه ۴۹۲ مراجعه کنید). تراکنش‌ها در یک سیستم ذخیره‌سازی یا پردازش جریانی شدنی‌اند، اما وقتی داده مرز بین فناوری‌های مختلف را قطع می‌کند، باور دارم لاگ رویداد ناهمگام با نوشتن idempotent بسیار مقاوم‌تر و عملی‌تر است.

مثلاً تراکنش‌های توزیع‌شده در برخی پردازشگرهای جریانی برای semantics دقیقاً یک‌بار استفاده می‌شوند (به «Atomic commit revisited» در صفحه ۴۷۷ مراجعه کنید) و می‌تواند خوب کار کند. اما وقتی تراکنش باید سیستم‌هایی را در بر گیرد که توسط گروه‌های مختلف نوشته شده‌اند (مثلاً نوشتن از پردازشگر جریانی به key-value store توزیع‌شده یا ایندکس جستجو)، نبود پروتکل تراکنش استاندارد یکپارچه‌سازی را بسیار سخت‌تر می‌کند. لاگ مرتب رویدادها با مصرف‌کننده‌های idempotent انتزاع بسیار ساده‌تری است و بنابراین پیاده‌سازی بین سیستم‌های ناهمگن عملی‌تر [۷].

مزیت بزرگ یکپارچه‌سازی مبتنی بر لاگ، coupling سست بین اجزاست که در دو سطح ظاهر می‌شود:

  1. در سطح سیستم، جریان‌های رویداد ناهمگام سیستم را در برابر outage یا افت عملکرد اجزای منفرد مقاوم‌تر می‌کنند. اگر مصرف‌کننده کند یا خراب شود، لاگ رویداد می‌تواند پیام buffer کند (به «Disk space usage» در صفحه ۴۵۰ مراجعه کنید) و تولیدکننده و مصرف‌کننده‌های دیگر بدون تأثیر ادامه دهند. مصرف‌کنندهٔ معیوب پس از تعمیر جبران می‌کند و داده‌ای از دست نمی‌دهد و خطا محدود می‌ماند. برعکس، تعامل همزمان تراکنش‌های توزیع‌شده تمایل دارد خطاهای محلی را به شکست‌های بزرگ تبدیل کند (به «Limitations of distributed transactions» در صفحه ۳۶۳ مراجعه کنید).
  2. در سطح انسانی، باز کردن بستهٔ سیستم‌های داده به تیم‌های مختلف اجازه می‌دهد اجزا و سرویس‌های نرم‌افزاری را مستقل توسعه، بهبود و نگه دارند. تخصص به هر تیم اجازه می‌دهد روی یک کار تمرکز کند با رابط‌های تعریف‌شده به سیستم‌های تیم‌های دیگر. لاگ رویداد رابطی قدرتمند برای ثبت ویژگی‌های consistency نسبتاً قوی (به‌خاطر پایداری و ترتیب رویدادها) و در عین حال عمومی برای تقریباً هر نوع داده فراهم می‌کند.

سیستم‌های بازشده در برابر یکپارچه

اگر باز کردن بسته واقعاً راه آینده شود، جایگزین پایگاه‌های داده در شکل فعلی نمی‌شود — همچنان به اندازهٔ قبل لازم‌اند. پایگاه‌های داده برای نگهداری state در پردازشگرهای جریانی و برای سرویس پرس‌وجو از خروجی پردازشگرهای دسته‌ای و جریانی لازم‌اند (به «The Output of Batch Workflows» در صفحه ۴۱۱ و «Processing Streams» در صفحه ۴۶۴ مراجعه کنید). موتورهای پرس‌وجوی تخصصی برای بارهای خاص همچنان مهم‌اند: مثلاً موتورهای پرس‌وجو در انبارهای داده MPP برای پرس‌وجوهای تحلیلی اکتشافی بهینه‌اند (به «Comparing Hadoop to Distributed Databases» در صفحه ۴۱۴ مراجعه کنید).

پیچیدگی اجرای چند قطعه زیرساخت می‌تواند مشکل باشد: هر نرم‌افزار منحنی یادگیری، مسائل پیکربندی و ویژگی‌های عملیاتی دارد، پس بهتر است تا حد ممکن قطعات متحرک کم مستقر شوند. محصول نرم‌افزاری یکپارچه هم ممکن است عملکرد بهتر و قابل پیش‌بینی‌تری برای بارهایی که برای آن طراحی شده داشته باشد، نسبت به سیستمی از چند ابزار که با کد برنامه ترکیب شده‌اند [۲۳]. همان‌طور که در مقدمه گفتم، ساخت برای مقیاسی که نیاز ندارید اتلاف تلاش است و ممکن است شما را در طراحی انعطاف‌ناپذیر قفل کند — در عمل نوعی بهینه‌سازی زودهنگام.

هدف باز کردن بسته رقابت با پایگاه‌های داده منفرد روی عملکرد برای بارهای خاص نیست؛ هدف ترکیب چند پایگاه داده مختلف برای دستیابی به عملکرد خوب برای دامنهٔ بسیار گسترده‌تری از بارها نسبت به یک نرم‌افزار است. دربارهٔ پهناست نه عمق — همان‌طور که تنوع مدل‌های ذخیره‌سازی و پردازش در «Comparing Hadoop to Distributed Databases» در صفحه ۴۱۴ بحث شد.

بنابراین اگر فناوری واحدی همهٔ نیازهایتان را برآورد، احتمالاً بهتر است همان محصول را استفاده کنید تا بازسازی از اجزای سطح پایین‌تر. مزایای باز کردن بسته و ترکیب فقط وقتی مطرح می‌شود که هیچ نرم‌افزار واحدی همهٔ الزامات را برآورده نکند.

چه چیزی کم است؟

ابزارهای ترکیب سیستم‌های داده بهتر می‌شوند، اما فکر می‌کنم بخش مهم یکی کم است: هنوز معادل بازشدهٔ پایگاه داده برای shell یونیکس نداریم — یعنی زبان سطح بالا برای ترکیب سیستم‌های ذخیره‌سازی و پردازش به‌شکل ساده و declarative.

مثلاً دوست دارم بتوانیم ساده اعلام کنیم mysql | elasticsearch، به‌مانند pipe یونیکس [۲۲]، که معادل بازشدهٔ CREATE INDEX است: همهٔ اسناد پایگاه MySQL را بگیرد و در خوشه Elasticsearch ایندکس کند، سپس به‌طور مداوم تغییرات پایگاه را ضبط و خودکار روی ایندکس جستجو اعمال کند، بدون نوشتن کد سفارشی. چنین یکپارچه‌سازی باید با تقریباً هر نوع ذخیره‌سازی یا ایندکس ممکن باشد.

به‌همین ترتیب، پیش‌محاسبه و به‌روزرسانی کش‌ها آسان‌تر بود عالی بود. یادآوری کنید نمای مادی‌شده اساساً کش از پیش محاسبه‌شده است، پس می‌توانید کش را با اعلام declarative نماهای مادی‌شده برای پرس‌وجوهای پیچیده — از جمله پرس‌وجوهای بازگشتی روی گراف (به «Graph-Like Data Models» در صفحه ۴۹ مراجعه کنید) و منطق برنامه — بسازید. تحقیقات اولیهٔ جالب در این حوزه وجود دارد، مثل differential dataflow [۲۴، ۲۵]، و امیدوارم این ایده‌ها به سیستم‌های تولیدی راه یابند.

طراحی برنامه‌ها حول جریان داده

رویکرد باز کردن بستهٔ پایگاه‌های داده با ترکیب ذخیره‌سازی و پردازش تخصصی با کد برنامه، به «database inside-out» [۲۶] هم معروف شده، از عنوان سخنرانی‌ام در ۲۰۱۴ [۲۷]. اما نامیدن آن «معماری جدید» بیش از حد بزرگ‌نمایی است. بیشتر الگوی طراحی و نقطهٔ شروع بحث می‌بینمش و نام می‌دهیم تا بهتر درباره‌اش صحبت کنیم.

این ایده‌ها مال من نیستند؛ آمیزه‌ای از ایده‌های دیگران‌اند که فکر می‌کنم باید از آن‌ها بیاموزیم. به‌ویژه هم‌پوشانی زیادی با زبان‌های dataflow مثل Oz [۲۸] و Juttle [۲۹]، زبان‌های برنامه‌نویسی reactive تابعی (FRP) مثل Elm [۳۰، ۳۱]، و زبان‌های logic programming مثل Bloom [۳۲] وجود دارد. اصطلاح unbundling در این زمینه را Jay Kreps [۷] پیشنهاد کرد.

حتی صفحه‌گسترده‌ها قابلیت‌های dataflow programming دارند که از بیشتر زبان‌های برنامه‌نویسی رایج جلوترند [۳۳]. در صفحه‌گسترده، می‌توانید فرمولی در یک سلول بگذارید (مثلاً جمع ستون دیگر) و هر زمان ورودی فرمول تغییر کند، نتیجه خودکار دوباره محاسبه شود. دقیقاً همان چیزی است که در سطح سیستم داده می‌خواهیم: وقتی رکوردی در پایگاه داده تغییر کند، هر ایندکس آن رکورد خودکار به‌روز شود و هر نمای کش یا تجمیعی که به رکورد وابسته است خودکار تازه شود. نباید نگران جزئیات فنی نحوهٔ این تازه‌سازی باشید، بلکه بتوانید به درستی کار کردن آن اعتماد کنید.

بنابراین فکر می‌کنم بیشتر سیستم‌های داده هنوز چیزهایی برای یادگیری از ویژگی‌هایی دارند که VisiCalc در ۱۹۷۹ داشت [۳۴]. تفاوت با صفحه‌گسترده این است که سیستم‌های دادهٔ امروز باید fault-tolerant، مقیاس‌پذیر و داده را پایدار ذخیره کنند. همچنین باید فناوری‌های ناهمگن نوشته‌شده توسط گروه‌های مختلف در طول زمان یکپارچه کنند و کتابخانه‌ها و سرویس‌های موجود را بازاستفاده کنند — انتظار واقع‌بینانه نیست همهٔ نرم‌افزار با یک زبان، چارچوب یا ابزار توسعه یابد.

در این بخش این ایده‌ها را گسترش می‌دهم و راه‌هایی برای ساخت برنامه حول ایده‌های باز کردن بسته و dataflow کاوش می‌کنم.

کد برنامه به‌عنوان تابع مشتق‌سازی

وقتی یک مجموعه‌داده از دیگری مشتق می‌شود، از تابع تبدیلی عبور می‌کند. مثلاً:

  • ایندکس ثانویه نوعی مجموعه‌دادهٔ مشتق با تابع تبدیل ساده است: برای هر سطر یا سند در جدول پایه، مقادیر ستون‌ها یا فیلدهای ایندکس‌شده را استخراج و بر اساس آن‌ها مرتب می‌کند (فرض B-tree یا SSTable که بر اساس کلید مرتب‌اند، فصل ۳).
  • ایندکس جستجوی متن کامل با اعمال توابع پردازش زبان طبیعی مثل تشخیص زبان، تقسیم کلمه، stemming یا lemmatization، تصحیح املا و شناسایی مترادف ساخته می‌شود، سپس ساختار داده برای جستجوی کارآمد (مثل inverted index).
  • در سیستم یادگیری ماشین، مدل را می‌توان از دادهٔ آموزشی با اعمال feature extraction و تحلیل آماری مشتق دانست. هنگام اعمال مدل روی دادهٔ ورودی جدید، خروجی مدل از ورودی و مدل (و غیرمستقیم از دادهٔ آموزشی) مشتق می‌شود.
  • کش اغلب تجمیع داده به شکلی است که در UI نمایش داده می‌شود. پر کردن کش نیاز به دانستن فیلدهای مرجع در UI دارد؛ تغییر UI ممکن است نیاز به به‌روزرسانی تعریف پر کردن کش و بازسازی کش داشته باشد.

تابع مشتق‌سازی ایندکس ثانویه آن‌قدر رایج است که در بسیاری پایگاه‌های داده به‌عنوان ویژگی هسته تعبیه شده و با CREATE INDEX فراخوانی می‌شود. برای ایندکس متن کامل، ویژگی‌های زبانی پایه برای زبان‌های رایج ممکن است در پایگاه داده باشد، اما ویژگی‌های پیچیده‌تر اغلب نیاز به تنظیم دامنه‌ای دارد. در یادگیری ماشین، feature engineering notoriously وابسته به برنامه است و اغلب باید دانش دقیق تعامل کاربر و استقرار برنامه را بگنجاند [۳۵].

وقتی تابع ساخت مجموعه‌دادهٔ مشتق تابع استاندارد cookie-cutter مثل ساخت ایندکس ثانویه نیست، کد سفارشی برای جنبه‌های خاص برنامه لازم است. و بسیاری پایگاه‌های داده در این کد سفارشی مشکل دارند. اگرچه پایگاه‌های دادهٔ رابطه‌ای معمولاً trigger، stored procedure و تابع تعریف‌شده توسط کاربر دارند که کد برنامه را در پایگاه داده اجرا می‌کنند، تا حدی afterthought در طراحی پایگاه داده بوده‌اند (به «Transmitting Event Streams» در صفحه ۴۴۰ مراجعه کنید).

تفکیک کد برنامه و state

در تئوری، پایگاه‌های داده می‌توانستند محیط استقرار برای کد برنامهٔ دلخواه باشند، مانند سیستم‌عامل. اما در عمل برای این هدف نامناسب بودند. با الزامات توسعهٔ برنامهٔ مدرن — مدیریت وابستگی و بسته، کنترل نسخه، به‌روزرسانی rolling، قابلیت تکامل، مانیتورینگ، معیارها، فراخوانی سرویس شبکه و یکپارچه‌سازی با سیستم‌های خارجی — خوب جور نمی‌آیند.

از سوی دیگر، ابزارهای استقرار و مدیریت خوشه مثل Mesos، YARN، Docker، Kubernetes برای اجرای کد برنامه طراحی شده‌اند. با تمرکز روی انجام یک کار خوب، آن را بهتر از پایگاه داده‌ای که اجرای تابع تعریف‌شده توسط کاربر یکی از ویژگی‌هایش است انجام می‌دهند.

فکر می‌کنم منطقی است بخش‌هایی از سیستم تخصص ذخیره‌سازی پایدار داده داشته باشند و بخش‌های دیگر تخصص اجرای کد برنامه. دو می‌توانند تعامل داشته باشند و همچنان مستقل بمانند.

بیشتر برنامه‌های وب امروز به‌صورت سرویس stateless مستقر می‌شوند که هر درخواست کاربر می‌تواند به هر سرور برنامه هدایت شود و سرور پس از ارسال پاسخ همه‌چیز را فراموش می‌کند. این سبک استقرار راحت است — سرورها آزادانه اضافه یا حذف می‌شوند — اما state باید جایی باشد: معمولاً پایگاه داده. روند جدا نگه‌داشتن منطق برنامهٔ stateless از مدیریت state (پایگاه داده) است: منطق برنامه در پایگاه داده نگذارید و state پایدار در برنامه نگذارید [۳۶]. همان‌طور که در جامعهٔ برنامه‌نویسی تابعی شوخی می‌کنند: «به تفکیک کلیسا و دولت باور داریم» [۳۷].i

در این مدل رایج برنامهٔ وب، پایگاه داده نوعی متغیر مشترک قابل تغییر است که همزمان از شبکه قابل دسترسی است. برنامه می‌تواند بخواند و به‌روز کند و پایگاه داده پایداری، کنترل همزمانی و تحمل خطا را مدیریت می‌کند.

اما در بیشتر زبان‌های برنامه‌نویسی نمی‌توانید به تغییرات متغیر قابل تغییر subscribe کنید — فقط دوره‌ای می‌خوانید. برخلاف صفحه‌گسترده، خوانندگان متغیر اگر مقدار تغییر کند مطلع نمی‌شوند. (می‌توانید چنین اعلان‌هایی در کد خود پیاده کنید — الگوی observer — اما بیشتر زبان‌ها این را به‌عنوان ویژگی تعبیه‌شده ندارند.)

پایگاه‌های داده این رویکرد منفعل به دادهٔ قابل تغییر را به ارث برده‌اند: اگر بخواهید بدانید محتوای پایگاه داده تغییر کرده، اغلب تنها گزینه poll است (تکرار دوره‌ای پرس‌وجو). subscribe به تغییرات تازه در حال ظهور است (به «API support for change streams» در صفحه ۴۵۶ مراجعه کنید).

i. توضیح شوخی معمولاً آن را بهتر نمی‌کند، اما نمی‌خواهم کسی احساس جا ماندن کند. اینجا Church اشاره به ریاضیدان Alonzo Church است که lambda calculus را ساخت — مبنای بیشتر زبان‌های برنامه‌نویسی تابعی. lambda calculus state قابل تغییر ندارد (متغیر قابل بازنویسی نیست)، پس می‌توان گفت state قابل تغییر جدا از کار Church است.

dataflow: تعامل بین تغییرات state و کد برنامه

فکر کردن به برنامه‌ها در قالب dataflow یعنی بازنگری رابطهٔ کد برنامه و مدیریت state. به‌جای دیدن پایگاه داده به‌عنوان متغیر منفعل که برنامه دستکاری می‌کند، بیشتر به تعامل و همکاری state، تغییرات state و کدی که آن‌ها را پردازش می‌کند فکر می‌کنیم. کد برنامه به تغییرات state در یک جا با تحریک تغییرات state در جای دیگر پاسخ می‌دهد.

این خط فکر را در «Databases and Streams» در صفحه ۴۵۱ دیدیم — لاگ تغییرات پایگاه داده را به‌عنوان جریان رویداد در نظر بگیریم که subscribe می‌کنیم. سیستم‌های message-passing مثل actor (به «Message-Passing Dataflow» در صفحه ۱۳۶) هم این مفهوم پاسخ به رویداد را دارند. از دههٔ ۱۹۸۰، مدل tuple spaces محاسبات توزیع‌شده را بر اساس فرایندهایی که تغییرات state را مشاهده و واکنش نشان می‌دهند بیان کرد [۳۸، ۳۹].

همان‌طور که بحث شد، درون پایگاه داده وقتی trigger به‌خاطر تغییر داده شلیک می‌شود یا ایندکس ثانویه برای بازتاب تغییر جدول به‌روز می‌شود، اتفاق مشابهی می‌افتد. باز کردن بستهٔ پایگاه داده یعنی اعمال این ایده به ساخت مجموعه‌داده‌های مشتق خارج از پایگاه دادهٔ اصلی: کش، ایندکس متن کامل، یادگیری ماشین یا سیستم‌های تحلیلی. می‌توانیم پردازش جریانی و سیستم‌های messaging را برای این منظور به کار ببریم.

نکتهٔ مهم: نگهداری دادهٔ مشتق همان اجرای ناهمگام job نیست که سیستم‌های messaging سنتی برای آن طراحی شده‌اند (به «Logs compared to traditional messaging» در صفحه ۴۴۸ مراجعه کنید):

  • هنگام نگهداری دادهٔ مشتق، ترتیب تغییرات state اغلب مهم است (اگر چند نما از لاگ رویداد مشتق می‌شوند، باید رویدادها را به همان ترتیب پردازش کنند تا با هم سازگار بمانند). همان‌طور که در «Acknowledgments and redelivery» در صفحه ۴۴۵ بحث شد، بسیاری message broker هنگام تحویل مجدد پیام‌های تأییدنشده این ویژگی را ندارند. dual write هم رد می‌شود (به «Keeping Systems in Sync» در صفحه ۴۵۲ مراجعه کنید).
  • تحمل خطا برای دادهٔ مشتق کلیدی است: از دست رفتن یک پیام می‌تواند مجموعه‌دادهٔ مشتق را برای همیشه با منبع داده ناسازگار کند. تحویل پیام و به‌روزرسانی state مشتق باید قابل اعتماد باشد. مثلاً بسیاری سیستم actor به‌طور پیش‌فرض state actor و پیام‌ها را در حافظه نگه می‌دارند و با crash ماشین از دست می‌روند.

ترتیب پایدار پیام و پردازش پیام fault-tolerant خواسته‌های سختگیرانه‌اند، اما بسیار ارزان‌تر و از نظر عملیاتی مقاوم‌تر از تراکنش‌های توزیع‌شده‌اند. پردازشگرهای جریانی مدرن می‌توانند این تضمین‌های ترتیب و قابلیت اطمینان را در مقیاس فراهم کنند و کد برنامه را به‌عنوان اپراتور جریانی اجرا کنند.

این کد برنامه می‌تواند پردازش دلخواهی انجام دهد که توابع مشتق تعبیه‌شده در پایگاه‌های داده معمولاً نمی‌دهند. مانند ابزارهای Unix زنجیره‌شده با pipe، اپراتورهای جریانی را می‌توان برای ساخت سیستم‌های بزرگ حول dataflow ترکیب کرد. هر اپراتور جریان‌های تغییر state را به‌عنوان ورودی می‌گیرد و جریان‌های دیگر تغییر state را به‌عنوان خروجی تولید می‌کند.

پردازشگرهای جریانی و سرویس‌ها

سبک رایج توسعهٔ برنامه امروز شکستن عملکرد به مجموعه‌ای سرویس است که از طریق درخواست شبکهٔ همزمان مثل REST API ارتباط می‌گیرند (به «Dataflow Through Services: REST and RPC» در صفحه ۱۳۱ مراجعه کنید). مزیت چنین معماری سرویس‌گرا نسبت به برنامهٔ monolithic واحد عمدتاً مقیاس‌پذیری سازمانی از طریق coupling سست است: تیم‌های مختلف روی سرویس‌های مختلف کار می‌کنند و هماهنگی بین تیم‌ها کم می‌شود (تا وقتی سرویس‌ها مستقل مستقر و به‌روز شوند).

ترکیب اپراتورهای جریانی در سیستم‌های dataflow ویژگی‌های مشابه زیادی با رویکرد microservice دارد [۴۰]. اما مکانیزم ارتباط زیرین بسیار متفاوت است: جریان پیام یک‌طرفه و ناهمگام به‌جای تعامل همزمان request/response.

علاوه بر مزایای فصل «Message-Passing Dataflow» در صفحه ۱۳۶، مثل تحمل خطای بهتر، سیستم‌های dataflow می‌توانند عملکرد بهتری داشته باشند. مثلاً مشتری کالایی با قیمت یک ارز ولی پرداخت با ارز دیگر می‌خرد. برای تبدیل ارز باید نرخ فعلی را بدانید. این عملیات دو شکل می‌گیرد [۴۰، ۴۱]:

  1. در رویکرد microservice، کد پردازش خرید احتمالاً سرویس یا پایگاه دادهٔ نرخ ارز را برای نرخ فعلی ارز خاص query می‌کند.
  2. در رویکرد dataflow، کد پردازش خرید از قبل به جریان به‌روزرسانی نرخ ارز subscribe کرده و هر زمان تغییر کرد در پایگاه محلی ثبت می‌کند. هنگام پردازش خرید فقط پایگاه محلی را query می‌کند.

رویکرد دوم درخواست شبکهٔ همزمان به سرویس دیگر را با query به پایگاه محلی جایگزین کرده (شاید روی همان ماشین، حتی همان فرایند).ii نه فقط سریع‌تر، بلکه در برابر خرابی سرویس دیگر مقاوم‌تر است. سریع‌ترین و قابل‌اعتمادترین درخواست شبکه، درخواست شبکه نیست! به‌جای RPC، stream join بین رویدادهای خرید و به‌روزرسانی نرخ ارز داریم (به «Stream-table join (stream enrichment)» در صفحه ۴۷۳ مراجعه کنید).

join وابسته به زمان است: اگر رویدادهای خرید بعداً دوباره پردازش شوند، نرخ ارز تغییر کرده. برای بازسازی خروجی اصلی باید نرخ تاریخی زمان خرید اصلی را بگیرید. چه سرویس query کنید چه به جریان به‌روزرسانی subscribe کنید، باید این وابستگی زمانی را مدیریت کنید (به «Time-dependence of joins» در صفحه ۴۷۵ مراجعه کنید).

subscribe به جریان تغییرات به‌جای query کردن state فعلی هنگام نیاز، ما را به مدل محاسباتی شبیه صفحه‌گسترده نزدیک می‌کند: وقتی داده‌ای تغییر کند، هر دادهٔ مشتق وابسته می‌تواند سریع به‌روز شود. هنوز سؤالات باز زیادی هست، مثلاً حول joinهای وابسته به زمان، اما باور دارم ساخت برنامه حول ایده‌های dataflow جهت امیدوارکننده‌ای است.

ii. در رویکرد microservice می‌توانید با cache محلی نرخ ارز در سرویس پردازش خرید از درخواست شبکهٔ همزمان اجتناب کنید. اما برای تازه نگه‌داشتن cache باید دوره‌ای نرخ را poll کنید یا به جریان تغییرات subscribe کنید — دقیقاً همان چیزی که در رویکرد dataflow اتفاق می‌افتد.

مشاهدهٔ state مشتق

در سطح انتزاعی، سیستم‌های dataflow که در بخش قبل بررسی کردیم، فرایندی برای ایجاد مجموعه‌داده‌های مشتق — مانند ایندکس‌های جستجو، نماهای مادی‌شده و مدل‌های پیش‌بینی — و به‌روز نگه‌داشتن آن‌ها فراهم می‌کنند. این فرایند را مسیر نوشتن می‌نامیم: هرگاه بخشی از اطلاعات در سیستم نوشته شود، ممکن است از چند مرحلهٔ پردازش دسته‌ای و جریانی عبور کند و در نهایت همهٔ مجموعه‌داده‌های مشتق به‌روزرسانی شوند تا دادهٔ نوشته‌شده را در بر بگیرند. شکل ۱۲-۱ نمونه‌ای از به‌روزرسانی ایندکس جستجو را نشان می‌دهد.

شکل ۱۲-۱. در ایندکس جستجو، نوشتن‌ها (به‌روزرسانی اسناد) به خواندن‌ها (پرس‌وجوها) می‌رسند.

اما اصلاً چرا مجموعه‌دادهٔ مشتق ایجاد می‌کنید؟ به‌احتمال زیاد چون می‌خواهید بعداً دوباره آن را query کنید. این مسیر خواندن است: هنگام پاسخ‌گویی به درخواست کاربر، داده را از مجموعه‌دادهٔ مشتق می‌خوانید، شاید پردازش بیشتری روی نتایج انجام دهید و پاسخ کاربر را می‌سازید.

مسیر نوشتن و مسیر خواندن در کنار هم کل سفر داده را در بر می‌گیرند؛ از نقطه‌ای که داده جمع‌آوری می‌شود تا نقطه‌ای که مصرف می‌شود — احتمالاً توسط انسانی دیگر. مسیر نوشتن بخشی از سفر است که از پیش محاسبه می‌شود؛ یعنی به‌محض ورود داده و صرف‌نظر از اینکه کسی درخواست دیدن آن را داده باشد، با اشتیاق انجام می‌شود. مسیر خواندن بخشی است که فقط وقتی کسی آن را درخواست کند اتفاق می‌افتد. اگر با زبان‌های برنامه‌نویسی تابعی آشنا باشید، شاید متوجه شوید مسیر نوشتن شبیه ارزیابی eager و مسیر خواندن شبیه ارزیابی lazy است.

همان‌طور که در شکل ۱۲-۱ نشان داده شده، مجموعه‌دادهٔ مشتق محل تلاقی مسیر نوشتن و مسیر خواندن است. این مجموعه‌داده trade-off میان مقدار کاری را نشان می‌دهد که باید هنگام نوشتن و هنگام خواندن انجام شود.

نماهای مادی‌شده و cache

ایندکس جستجوی متن کامل نمونهٔ خوبی است: مسیر نوشتن ایندکس را به‌روز می‌کند و مسیر خواندن ایندکس را برای یافتن کلمات کلیدی جستجو می‌کند. خواندن و نوشتن هر دو باید مقداری کار انجام دهند. نوشتن باید ورودی‌های ایندکس را برای همهٔ اصطلاحات موجود در سند به‌روز کند. خواندن باید هر کلمهٔ پرس‌وجو را جستجو و منطق بولی را اعمال کند تا اسنادی را بیابد که همهٔ کلمات پرس‌وجو را در بر دارند (عملگر AND)، یا یکی از مترادف‌های هر کلمه را دارند (عملگر OR).

اگر ایندکس نداشتید، پرس‌وجوی جستجو باید همهٔ اسناد را اسکن می‌کرد — مانند grep — که با تعداد زیاد اسناد بسیار پرهزینه می‌شد. نبود ایندکس یعنی کار کمتر در مسیر نوشتن، چون ایندکسی برای به‌روزرسانی نیست، اما کار بسیار بیشتر در مسیر خواندن.

از سوی دیگر، می‌توانید تصور کنید نتایج جستجو برای همهٔ پرس‌وجوهای ممکن از پیش محاسبه شوند. در این صورت، مسیر خواندن کار کمتری دارد: هیچ منطق بولی لازم نیست؛ فقط نتایج پرس‌وجو را پیدا و بازگردانید. با این حال، مسیر نوشتن بسیار پرهزینه‌تر خواهد بود: مجموعهٔ پرس‌وجوهای جستجوی ممکن نامتناهی است، بنابراین پیش‌محاسبهٔ همهٔ نتایج جستجوی ممکن به زمان و فضای ذخیره‌سازی نامتناهی نیاز دارد. این راه چندان خوب کار نمی‌کند.iii

گزینهٔ دیگر، پیش‌محاسبهٔ نتایج جستجو فقط برای مجموعه‌ای ثابت از رایج‌ترین پرس‌وجوهاست تا بدون مراجعه به ایندکس به‌سرعت پاسخ داده شوند. پرس‌وجوهای نامتداول همچنان می‌توانند از ایندکس پاسخ بگیرند. این معمولاً cache پرس‌وجوهای رایج نامیده می‌شود، هرچند می‌توان آن را نمای مادی‌شده نیز نامید، زیرا وقتی اسناد جدیدی ظاهر می‌شوند که باید در نتایج یکی از پرس‌وجوهای رایج قرار بگیرند، باید به‌روز شود.

iii. اگر جدی‌تر بگوییم، با فرض متناهی بودن پیکرهٔ اسناد، مجموعهٔ پرس‌وجوهای جستجوی متمایزی که نتیجهٔ غیرتهی دارند متناهی است. با این حال، اندازهٔ آن نسبت به تعداد اصطلاحات پیکره نمایی است که همچنان خبر بسیار بدی است.

از این مثال می‌بینیم که ایندکس تنها مرز ممکن میان مسیر نوشتن و مسیر خواندن نیست. می‌توان نتایج جستجوی رایج را cache کرد و برای تعداد کمی سند نیز بدون ایندکس، اسکن شبیه grep انجام داد. از این منظر، نقش cacheها، ایندکس‌ها و نماهای مادی‌شده ساده است: آن‌ها مرز میان مسیر خواندن و مسیر نوشتن را جابه‌جا می‌کنند. با پیش‌محاسبهٔ نتایج اجازه می‌دهند کار بیشتری در مسیر نوشتن انجام دهیم تا در مسیر خواندن تلاش کمتری لازم باشد.

جابه‌جایی مرز میان کار مسیر نوشتن و مسیر خواندن در واقع موضوع مثال Twitter در ابتدای کتاب، در «توصیف بار» در صفحه ۱۱ بود. در آن مثال نیز دیدیم که مرز مسیر نوشتن و خواندن ممکن است برای افراد مشهور با کاربران عادی متفاوت ترسیم شود. پس از ۵۰۰ صفحه دوباره به نقطهٔ آغاز بازگشته‌ایم!

کلاینت‌های stateful با قابلیت کار آفلاین

ایدهٔ وجود مرز میان مسیرهای نوشتن و خواندن برایم جالب است، زیرا می‌توانیم دربارهٔ جابه‌جایی این مرز و معنای عملی آن بحث کنیم. بیایید این ایده را در زمینه‌ای متفاوت بررسی کنیم.

محبوبیت عظیم برنامه‌های وب در دو دههٔ گذشته ما را به فرض‌هایی دربارهٔ توسعهٔ برنامه رسانده که به‌آسانی بدیهی پنداشته می‌شوند. به‌ویژه مدل کلاینت/سرور — که در آن کلاینت‌ها عمدتاً stateless و سرورها مرجع داده‌اند — آن‌قدر رایج است که تقریباً فراموش می‌کنیم گزینهٔ دیگری هم وجود دارد. با این حال، فناوری پیوسته پیشرفت می‌کند و فکر می‌کنم مهم است هر از گاهی وضع موجود را زیر سؤال ببریم.

مرورگرهای وب به‌طور سنتی کلاینت‌هایی stateless بوده‌اند که فقط با اتصال اینترنت می‌توانند کار مفیدی انجام دهند. تقریباً تنها کاری که آفلاین می‌توانستید انجام دهید، بالا و پایین رفتن در صفحه‌ای بود که قبلاً به‌صورت آنلاین بارگذاری کرده بودید. با این حال، برنامه‌های وب JavaScript تک‌صفحه‌ای جدید قابلیت‌های stateful زیادی به دست آورده‌اند؛ از جمله تعامل رابط کاربری در سمت کلاینت و ذخیره‌سازی محلی پایدار در مرورگر. برنامه‌های موبایل نیز می‌توانند state زیادی را روی دستگاه ذخیره کنند و برای بیشتر تعاملات کاربر به رفت‌وبرگشت با سرور نیاز ندارند.

این قابلیت‌های در حال تغییر، علاقه به برنامه‌های offline-first را دوباره زنده کرده‌اند؛ برنامه‌هایی که با استفاده از پایگاه دادهٔ محلی روی همان دستگاه و بدون نیاز به اتصال اینترنت تا حد امکان کار می‌کنند و هنگام دسترسی به شبکه در پس‌زمینه با سرورهای دور همگام می‌شوند [۴۲]. از آنجا که دستگاه‌های موبایل اغلب اتصال اینترنت سلولی کند و نامطمئنی دارند، برای کاربران مزیت بزرگی است اگر رابط کاربری مجبور نباشد منتظر درخواست‌های همزمان شبکه بماند و برنامه عمدتاً آفلاین کار کند (به «کلاینت‌هایی با عملیات آفلاین» در صفحه ۱۷۰ مراجعه کنید).

وقتی فرض کلاینت‌های stateless متصل به پایگاه داده‌ای مرکزی را کنار می‌گذاریم و به سمت state نگهداری‌شده روی دستگاه کاربران نهایی می‌رویم، دنیایی از فرصت‌های جدید گشوده می‌شود. به‌ویژه می‌توان state روی دستگاه را cache وضعیت سرور دانست. پیکسل‌های صفحه نمایی مادی‌شده روی اشیای مدل در برنامهٔ کلاینت‌اند؛ اشیای مدل نیز رپلیکای محلی state در دیتاسنتر دور هستند [۲۷].

فرستادن تغییرات state به کلاینت‌ها

در صفحهٔ وب معمولی، اگر صفحه را در مرورگر بارگذاری کنید و سپس داده روی سرور تغییر کند، مرورگر تا زمانی که صفحه را دوباره بارگذاری نکنید از تغییر باخبر نمی‌شود. مرورگر داده را فقط در یک نقطهٔ زمانی می‌خواند و فرض می‌کند ثابت است؛ به به‌روزرسانی‌های سرور subscribe نمی‌کند. بنابراین state روی دستگاه cache کهنه‌ای است که جز با polling صریح تغییرات به‌روز نمی‌شود. پروتکل‌های اشتراک feed مبتنی بر HTTP مانند RSS نیز در واقع فقط شکل ابتدایی polling هستند.

پروتکل‌های جدیدتر از الگوی سادهٔ request/response در HTTP فراتر رفته‌اند: server-sent events با API موسوم به EventSource و WebSocketها کانال‌های ارتباطی‌ای فراهم می‌کنند که مرورگر وب می‌تواند اتصال TCP بازی به سرور نگه دارد و سرور تا زمانی که اتصال برقرار است فعالانه پیام‌هایی را به مرورگر push کند. به این ترتیب سرور می‌تواند کلاینت کاربر نهایی را فعالانه از هر تغییری در state ذخیره‌شدهٔ محلی آن آگاه کند و کهنگی state سمت کلاینت را کاهش دهد.

از منظر مدل مسیر نوشتن و خواندن، push فعال تغییرات state تا دستگاه‌های کلاینت به‌معنای امتداد مسیر نوشتن تا کاربر نهایی است. هنگام مقداردهی اولیهٔ کلاینت، همچنان باید برای دریافت state اولیه از مسیر خواندن استفاده شود، اما پس از آن کلاینت می‌تواند بر جریان تغییرات state ارسال‌شده از سرور تکیه کند. ایده‌هایی که دربارهٔ پردازش جریانی و پیام‌رسانی بررسی کردیم به اجرا در دیتاسنتر محدود نیستند: می‌توانیم آن‌ها را بیشتر گسترش دهیم و تا دستگاه‌های کاربران نهایی ادامه دهیم [۴۳].

دستگاه‌ها گاهی آفلاین‌اند و در این مدت نمی‌توانند اعلان تغییر state را از سرور دریافت کنند. اما این مسئله را قبلاً حل کرده‌ایم: در «offsetهای مصرف‌کننده» در صفحه ۴۴۹ بررسی کردیم که مصرف‌کنندهٔ message broker مبتنی بر لاگ چگونه می‌تواند پس از خرابی یا قطع اتصال دوباره متصل شود و مطمئن شود هیچ‌یک از پیام‌هایی را که هنگام قطع بودن رسیده‌اند از دست نمی‌دهد. همین تکنیک برای کاربران منفرد نیز کار می‌کند؛ جایی که هر دستگاه subscriber کوچکی برای جریان کوچکی از رویدادهاست.

جریان‌های سرتاسری رویداد

ابزارهای جدید توسعهٔ کلاینت‌های stateful و رابط‌های کاربری، مانند زبان Elm‏ [۳۰] و زنجیره‌ابزار React، Flux و Redux از Facebook‏ [۴۴]، از هم‌اکنون state داخلی سمت کلاینت را با subscribe کردن به جریان رویدادهایی مدیریت می‌کنند که ورودی کاربر یا پاسخ‌های سرور را نمایش می‌دهند و ساختاری شبیه event sourcing دارند (به «Event Sourcing» در صفحه ۴۵۷ مراجعه کنید).

گسترش این مدل برنامه‌نویسی به‌گونه‌ای که سرور نیز بتواند رویدادهای تغییر state را وارد خط لولهٔ رویداد سمت کلاینت کند بسیار طبیعی است. به این ترتیب تغییرات state می‌توانند در مسیر نوشتن سرتاسری جریان یابند: از تعامل روی یک دستگاه که تغییر state را تحریک می‌کند، از طریق لاگ رویدادها و چند سیستم دادهٔ مشتق و پردازشگر جریانی، تا رابط کاربری شخصی که آن state را روی دستگاهی دیگر مشاهده می‌کند. این تغییرات state می‌توانند با تأخیر نسبتاً کم — مثلاً کمتر از یک ثانیه از ابتدا تا انتها — منتشر شوند.

برخی برنامه‌ها، مانند پیام‌رسانی فوری و بازی‌های آنلاین، از هم‌اکنون چنین معماری «بلادرنگی» دارند؛ بلادرنگ به‌معنای تعامل با تأخیر کم، نه به‌معنای «تضمین‌های زمان پاسخ» در صفحه ۲۹۸. اما چرا همهٔ برنامه‌ها را به این شکل نمی‌سازیم؟

چالش این است که فرض کلاینت‌های stateless و تعاملات request/response عمیقاً در پایگاه‌های داده، کتابخانه‌ها، چارچوب‌ها و پروتکل‌های ما ریشه دوانده است. بسیاری از datastoreها عملیات خواندن و نوشتنی را پشتیبانی می‌کنند که در آن یک درخواست یک پاسخ برمی‌گرداند، اما تعداد بسیار کمتری امکان subscribe به تغییرات را فراهم می‌کنند؛ یعنی درخواستی که در طول زمان جریانی از پاسخ‌ها برمی‌گرداند (به «پشتیبانی API از جریان تغییرات» در صفحه ۴۵۶ مراجعه کنید).

برای امتداد مسیر نوشتن تا کاربر نهایی باید شیوهٔ ساخت بسیاری از این سیستم‌ها را اساساً بازنگری کنیم: از تعامل request/response فاصله بگیریم و به سمت dataflow مبتنی بر publish/subscribe حرکت کنیم [۲۷]. فکر می‌کنم مزایای رابط‌های کاربری پاسخ‌گوتر و پشتیبانی بهتر از کار آفلاین ارزش این تلاش را دارد. اگر سیستم داده طراحی می‌کنید، امیدوارم گزینهٔ subscribe به تغییرات و نه فقط query کردن state فعلی را در نظر داشته باشید.

خواندن‌ها هم رویدادند

گفتیم وقتی پردازشگر جریانی دادهٔ مشتق را در یک محل ذخیره‌سازی — پایگاه داده، cache یا ایندکس — می‌نویسد و درخواست‌های کاربران آن را query می‌کنند، محل ذخیره‌سازی مرز میان مسیر نوشتن و مسیر خواندن است. محل ذخیره‌سازی اجازه می‌دهد پرس‌وجوهای خواندن با دسترسی تصادفی روی داده‌ای اجرا شوند که در غیر این صورت نیازمند اسکن کل لاگ رویداد بود.

در بسیاری موارد، ذخیره‌سازی داده از سیستم جریانی جداست. اما به یاد آورید که پردازشگرهای جریانی نیز برای انجام تجمیع و join باید state نگه دارند (به «joinهای جریانی» در صفحه ۴۷۲ مراجعه کنید). این state معمولاً درون پردازشگر جریانی پنهان است، اما برخی چارچوب‌ها اجازه می‌دهند کلاینت‌های خارجی نیز آن را query کنند [۴۵] و خود پردازشگر جریانی را به نوعی پایگاه دادهٔ ساده تبدیل می‌کنند.

می‌خواهم این ایده را فراتر ببرم. در آنچه تاکنون بررسی کردیم، نوشتن‌ها از لاگ رویداد عبور می‌کنند، در حالی که خواندن‌ها درخواست‌های گذرای شبکه‌ای‌اند که مستقیم به گره‌های نگهدارندهٔ دادهٔ مورد query می‌روند. این طراحی معقول است، اما تنها طراحی ممکن نیست. درخواست‌های خواندن را نیز می‌توان به‌صورت جریان رویداد نمایش داد و رویدادهای خواندن و نوشتن را هر دو از پردازشگر جریانی عبور داد؛ پردازشگر نیز با انتشار نتیجهٔ خواندن در جریان خروجی به رویدادهای خواندن پاسخ می‌دهد [۴۶].

وقتی نوشتن‌ها و خواندن‌ها هر دو به‌صورت رویداد نمایش داده و برای پردازش به یک اپراتور جریانی هدایت شوند، در واقع میان جریان پرس‌وجوهای خواندن و پایگاه داده یک stream-table join انجام می‌دهیم. رویداد خواندن باید به پارتیشن پایگاه داده‌ای فرستاده شود که داده را نگه می‌دارد (به «مسیریابی درخواست» در صفحه ۲۱۴ مراجعه کنید)، درست همان‌طور که پردازشگرهای دسته‌ای و جریانی هنگام join باید ورودی‌ها را بر اساس کلیدی یکسان copartition کنند (به «joinها و گروه‌بندی سمت reduce» در صفحه ۴۰۳ مراجعه کنید).

این تناظر میان پاسخ‌گویی به درخواست‌ها و انجام join بسیار بنیادی است [۴۷]. درخواست خواندن یک‌باره فقط از اپراتور join عبور می‌کند و بلافاصله فراموش می‌شود؛ درخواست subscribe یک join پایدار با رویدادهای گذشته و آینده در سمت دیگر join است.

ثبت لاگ رویدادهای خواندن می‌تواند برای ردیابی وابستگی‌های علّی و منشأ داده در سراسر سیستم نیز مزایایی داشته باشد: اجازه می‌دهد آنچه کاربر پیش از گرفتن تصمیمی مشخص دیده بازسازی کنید. مثلاً در فروشگاه آنلاین، احتمالاً تاریخ پیش‌بینی‌شدهٔ ارسال و وضعیت موجودی نمایش‌داده‌شده به مشتری بر انتخاب او برای خرید کالا اثر می‌گذارد [۴]. برای تحلیل این ارتباط باید نتیجهٔ پرس‌وجوی کاربر دربارهٔ وضعیت ارسال و موجودی را ثبت کنید.

بنابراین نوشتن رویدادهای خواندن در ذخیره‌سازی پایدار امکان ردیابی بهتر وابستگی‌های علّی را فراهم می‌کند (به «ترتیب‌دهی رویدادها برای ثبت علیّت» در صفحه ۴۹۳ مراجعه کنید)، اما هزینهٔ ذخیره‌سازی و I/O بیشتری دارد. بهینه‌سازی چنین سیستم‌هایی برای کاهش سربار هنوز مسئله‌ای باز در تحقیقات است [۲]. با این حال، اگر از قبل درخواست‌های خواندن را برای اهداف عملیاتی و به‌عنوان اثر جانبی پردازش درخواست لاگ می‌کنید، تبدیل لاگ به منبع درخواست‌ها تغییر چندان بزرگی نیست.

پردازش دادهٔ چندپارتیشنی

برای پرس‌وجوهایی که فقط با یک پارتیشن سروکار دارند، فرستادن پرس‌وجوها از طریق یک جریان و جمع‌آوری جریانی از پاسخ‌ها شاید زیاده‌روی باشد. با این حال، این ایده امکان اجرای توزیع‌شدهٔ پرس‌وجوهای پیچیده‌ای را فراهم می‌کند که باید دادهٔ چند پارتیشن را ترکیب کنند و از زیرساخت مسیریابی پیام، پارتیشن‌بندی و join موجود در پردازشگرهای جریانی بهره ببرند.

قابلیت RPC توزیع‌شدهٔ Storm از این الگوی استفاده پشتیبانی می‌کند (به «ارسال پیام و RPC» در صفحه ۴۶۸ مراجعه کنید). مثلاً از آن برای محاسبهٔ تعداد افرادی استفاده شده که URL مشخصی را در Twitter دیده‌اند؛ یعنی اجتماع مجموعهٔ دنبال‌کنندگان همهٔ کسانی که آن URL را tweet کرده‌اند [۴۸]. از آنجا که مجموعهٔ کاربران Twitter پارتیشن‌بندی شده، این محاسبه نیازمند ترکیب نتایج پارتیشن‌های متعدد است.

نمونهٔ دیگر این الگو در پیشگیری از تقلب رخ می‌دهد: برای ارزیابی ریسک متقلبانه بودن رویداد خرید مشخص، می‌توانید امتیاز اعتبار آدرس IP، آدرس ایمیل، آدرس صورت‌حساب، آدرس ارسال و موارد دیگر کاربر را بررسی کنید. هر یک از این پایگاه‌های دادهٔ اعتبار خود پارتیشن‌بندی شده‌اند، بنابراین جمع‌آوری امتیازهای مربوط به رویداد خرید مشخص به دنباله‌ای از joinها با مجموعه‌داده‌هایی نیاز دارد که به‌شکل‌های متفاوت پارتیشن‌بندی شده‌اند [۴۹].

گراف‌های اجرای داخلی پرس‌وجو در پایگاه‌های دادهٔ MPP ویژگی‌های مشابهی دارند (به «مقایسهٔ Hadoop با پایگاه‌های دادهٔ توزیع‌شده» در صفحه ۴۱۴ مراجعه کنید). اگر به چنین join چندپارتیشنی نیاز دارید، احتمالاً استفاده از پایگاه داده‌ای که این قابلیت را فراهم می‌کند ساده‌تر از پیاده‌سازی آن با پردازشگر جریانی است. با این حال، در نظر گرفتن پرس‌وجوها به‌عنوان جریان، گزینه‌ای برای پیاده‌سازی برنامه‌های بزرگی فراهم می‌کند که به محدودیت راه‌حل‌های متعارف آماده رسیده‌اند.

تلاش برای درستی

در سرویس‌های stateless که فقط داده می‌خوانند، اگر مشکلی پیش بیاید مسئلهٔ بزرگی نیست: باگ را رفع و سرویس را restart می‌کنید و همه‌چیز به حالت عادی برمی‌گردد. سیستم‌های stateful مانند پایگاه‌های داده به این سادگی نیستند: آن‌ها طوری طراحی شده‌اند که چیزها را کم‌وبیش برای همیشه به خاطر بسپارند؛ بنابراین اگر مشکلی پیش بیاید، اثرات آن نیز ممکن است برای همیشه باقی بماند و به تفکر دقیق‌تری نیاز دارند [۵۰].

می‌خواهیم برنامه‌هایی قابل اعتماد و درست بسازیم؛ یعنی برنامه‌هایی که semantics آن‌ها حتی در برابر خطاهای مختلف به‌خوبی تعریف و درک شده باشد. حدود چهار دهه، ویژگی‌های تراکنش شامل atomicity، isolation و durability (فصل ۷) ابزارهای منتخب برای ساخت برنامه‌های درست بوده‌اند. با این حال، این مبانی ضعیف‌تر از آن‌اند که به نظر می‌رسند؛ برای نمونه به سردرگمی پیرامون سطوح isolation ضعیف مراجعه کنید (به «سطوح isolation ضعیف» در صفحه ۲۳۳ مراجعه کنید).

در بعضی حوزه‌ها تراکنش‌ها کاملاً کنار گذاشته شده‌اند و مدل‌هایی جای آن‌ها را گرفته‌اند که عملکرد و مقیاس‌پذیری بهتر اما semantics بسیار آشفته‌تری دارند (مثلاً «رپلیکاسیون بدون leader» در صفحه ۱۷۷). دربارهٔ consistency زیاد صحبت می‌شود، اما تعریف آن اغلب ضعیف است (به «Consistency» در صفحه ۲۲۴ و فصل ۹ مراجعه کنید). بعضی افراد ادعا می‌کنند برای availability بهتر باید «consistency ضعیف را بپذیریم»، بی‌آنکه تصور روشنی از معنای عملی آن داشته باشند.

برای موضوعی چنین مهم، درک و روش‌های مهندسی ما به‌شکل شگفت‌آوری سست‌اند. مثلاً تعیین اینکه اجرای برنامه‌ای مشخص با سطح isolation یا پیکربندی رپلیکاسیون مشخص ایمن است یا نه بسیار دشوار است [۵۱، ۵۲]. راه‌حل‌های ساده اغلب در همزمانی کم و نبود خطا درست به نظر می‌رسند، اما در شرایط دشوارتر معلوم می‌شود باگ‌های ظریف بسیاری دارند.

برای نمونه، آزمایش‌های Jepsen از Kyle Kingsbury‏ [۵۳] اختلاف آشکار میان تضمین‌های ایمنی ادعاشدهٔ بعضی محصولات و رفتار واقعی آن‌ها هنگام مشکلات شبکه و crash را برجسته کرده است. حتی اگر محصولات زیرساختی مانند پایگاه‌های داده بدون مشکل بودند، کد برنامه همچنان باید قابلیت‌های آن‌ها را درست به کار می‌گرفت؛ کاری که وقتی پیکربندی دشوارفهم باشد — مانند سطوح isolation ضعیف و پیکربندی quorum — مستعد خطاست.

اگر برنامهٔ شما می‌تواند خراب یا گم شدن گاه‌به‌گاه و پیش‌بینی‌ناپذیر داده را تحمل کند، زندگی بسیار ساده‌تر است و شاید بتوانید فقط انگشتانتان را ضربدری کنید و به بهترین نتیجه امیدوار باشید. اما اگر به اطمینان قوی‌تری از درستی نیاز دارید، serializability و atomic commit رویکردهای جاافتاده‌ای‌اند، ولی هزینه دارند: معمولاً فقط در یک دیتاسنتر کار می‌کنند — و معماری‌های توزیع‌شدهٔ جغرافیایی را منتفی می‌کنند — و مقیاس و ویژگی‌های تحمل خطایی را که می‌توانید به دست آورید محدود می‌کنند.

اگرچه رویکرد سنتی تراکنش از میان نمی‌رود، باور دارم آخرین سخن دربارهٔ درست و مقاوم‌کردن برنامه‌ها در برابر خطا نیز نیست. در این بخش راه‌هایی برای اندیشیدن به درستی در زمینهٔ معماری‌های dataflow پیشنهاد می‌کنم.

استدلال سرتاسری برای پایگاه‌های داده

صرف استفادهٔ برنامه از سیستم داده‌ای با ویژگی‌های ایمنی نسبتاً قوی، مانند تراکنش‌های serializable، تضمین نمی‌کند برنامه از گم‌شدن یا خرابی داده مصون باشد. مثلاً اگر باگی در برنامه باعث نوشتن دادهٔ نادرست یا حذف داده از پایگاه داده شود، تراکنش‌های serializable شما را نجات نمی‌دهند.

شاید این مثال سطحی به نظر برسد، اما ارزش دارد جدی گرفته شود: باگ‌های برنامه رخ می‌دهند و انسان‌ها اشتباه می‌کنند. در «State، جریان‌ها و تغییرناپذیری» در صفحه ۴۵۹ از این مثال برای دفاع از دادهٔ تغییرناپذیر و append-only استفاده کردم، زیرا اگر امکان نابود کردن دادهٔ خوب را از کد معیوب بگیرید، بازیابی از چنین اشتباه‌هایی آسان‌تر است.

اگرچه تغییرناپذیری مفید است، به‌تنهایی درمان همه‌چیز نیست. بیایید نمونهٔ ظریف‌تری از خرابی احتمالی داده را بررسی کنیم.

اجرای دقیقاً یک‌بارهٔ عملیات

در «تحمل خطا» در صفحه ۴۷۶ با ایده‌ای به نام semantics دقیقاً یک‌بار یا عملاً یک‌بار روبه‌رو شدیم. اگر هنگام پردازش پیام مشکلی پیش بیاید، می‌توانید تسلیم شوید — پیام را کنار بگذارید و داده از دست بدهید — یا دوباره تلاش کنید. اگر دوباره تلاش کنید، این خطر وجود دارد که عملیات بار اول واقعاً موفق شده باشد اما شما از موفقیت آن باخبر نشده باشید و در نتیجه پیام دو بار پردازش شود.

پردازش دوباره شکلی از خرابی داده است: مطلوب نیست مشتری برای خدمتی یکسان دو بار شارژ شود یا شمارنده‌ای دو بار افزایش یابد و متریکی را بیش از مقدار واقعی نشان دهد. در این زمینه، دقیقاً یک‌بار یعنی محاسبه طوری سازمان‌دهی شود که اثر نهایی همانند حالتی باشد که هیچ خطایی رخ نداده است، حتی اگر عملیات به‌دلیل خطا واقعاً retry شده باشد. پیش‌تر چند رویکرد دستیابی به این هدف را بررسی کردیم.

یکی از مؤثرترین رویکردها idempotent کردن عملیات است (به «Idempotence» در صفحه ۴۷۸ مراجعه کنید)؛ یعنی تضمین شود اجرای عملیات یک‌بار یا چند بار اثر یکسانی دارد. با این حال، تبدیل عملیاتی که ذاتاً idempotent نیست به عملیات idempotent به تلاش و دقت نیاز دارد: شاید لازم باشد metadata اضافه‌ای نگه دارید — مانند مجموعهٔ شناسهٔ عملیاتی که مقداری را به‌روز کرده‌اند — و هنگام failover از یک گره به گره دیگر fencing را تضمین کنید (به «leader و قفل» در صفحه ۳۰۱ مراجعه کنید).

حذف موارد تکراری

الگوی نیاز به حذف موارد تکراری در جاهای بسیار دیگری غیر از پردازش جریانی هم رخ می‌دهد. مثلاً TCP از شماره‌های توالی بسته‌ها برای قرار دادن آن‌ها در ترتیب درست در سمت گیرنده و تعیین گم یا تکراری شدن بسته‌ها در شبکه استفاده می‌کند. بسته‌های گم‌شده دوباره ارسال و موارد تکراری پیش از تحویل داده به برنامه توسط پشتهٔ TCP حذف می‌شوند.

با این حال، این حذف موارد تکراری فقط در زمینهٔ یک اتصال TCP کار می‌کند. تصور کنید اتصال TCP، اتصال کلاینت به پایگاه داده است و اکنون تراکنش مثال ۱۲-۱ را اجرا می‌کند. در بسیاری پایگاه‌های داده، تراکنش به اتصال کلاینت گره خورده است؛ اگر کلاینت چند پرس‌وجو بفرستد، پایگاه داده می‌داند آن‌ها به تراکنش یکسانی تعلق دارند، زیرا روی اتصال TCP یکسانی فرستاده شده‌اند. اگر پس از ارسال COMMIT اما پیش از دریافت پاسخ از سرور پایگاه داده، شبکهٔ کلاینت قطع و اتصال timeout شود، کلاینت نمی‌داند تراکنش commit شده یا abort (شکل ۸-۱).

مثال ۱۲-۱. انتقال غیرـidempotent پول از یک حساب به حسابی دیگر

sql
BEGIN TRANSACTION;
UPDATE accounts SET balance = balance + 11.00 WHERE account_id = 1234;
UPDATE accounts SET balance = balance - 11.00 WHERE account_id = 4321;
COMMIT;

کلاینت می‌تواند دوباره به پایگاه داده متصل شود و تراکنش را retry کند، اما اکنون این کار خارج از محدودهٔ حذف موارد تکراری TCP است. چون تراکنش مثال ۱۲-۱ idempotent نیست، ممکن است به‌جای ۱۱ دلار موردنظر، ۲۲ دلار منتقل شود. بنابراین، اگرچه مثال ۱۲-۱ نمونهٔ استاندارد atomicity تراکنش است، در واقع درست نیست و بانک‌های واقعی به این شکل کار نمی‌کنند [۳].

پروتکل‌های two-phase commit (به «Atomic Commit و Two-Phase Commit (2PC)» در صفحه ۳۵۴ مراجعه کنید) نگاشت یک‌به‌یک میان اتصال TCP و تراکنش را می‌شکنند، زیرا باید اجازه دهند coordinator تراکنش پس از خطای شبکه دوباره به پایگاه داده متصل شود و به آن بگوید تراکنش بلاتکلیف را commit یا abort کند. آیا این برای تضمین اینکه تراکنش فقط یک‌بار اجرا شود کافی است؟ متأسفانه نه.

حتی اگر بتوانیم تراکنش‌های تکراری میان کلاینت و سرور پایگاه داده را حذف کنیم، همچنان باید نگران شبکهٔ میان دستگاه کاربر نهایی و سرور برنامه باشیم. مثلاً اگر کلاینت کاربر نهایی مرورگر وب باشد، احتمالاً از درخواست HTTP POST برای ارسال دستور به سرور استفاده می‌کند. شاید کاربر اتصال دادهٔ سلولی ضعیفی داشته باشد و POST را با موفقیت بفرستد، اما پیش از دریافت پاسخ سرور سیگنال بیش از حد ضعیف شود.

در این حالت احتمالاً پیام خطایی به کاربر نمایش داده می‌شود و ممکن است دستی دوباره تلاش کند. مرورگرهای وب هشدار می‌دهند: «آیا مطمئنید می‌خواهید این فرم را دوباره ارسال کنید؟» و کاربر پاسخ مثبت می‌دهد، چون می‌خواسته عملیات انجام شود. الگوی Post/Redirect/Get‏ [۵۴] در عملیات عادی از این هشدار جلوگیری می‌کند، اما اگر درخواست POST timeout شود کمکی نمی‌کند. از دید سرور وب، retry درخواستی جدا و از دید پایگاه داده تراکنشی جداست. مکانیزم‌های معمول deduplication کمکی نمی‌کنند.

شناسه‌های عملیات

برای idempotent کردن عملیات در چند مرحلهٔ ارتباط شبکه‌ای، اتکای صرف به مکانیزم تراکنش پایگاه داده کافی نیست؛ باید جریان سرتاسری درخواست را در نظر بگیرید.

مثلاً می‌توانید شناسه‌ای یکتا مانند UUID برای عملیات تولید کنید و آن را به‌صورت فیلد مخفی فرم در برنامهٔ کلاینت قرار دهید، یا از همهٔ فیلدهای مرتبط فرم hash بگیرید تا شناسهٔ عملیات ساخته شود [۳]. اگر مرورگر درخواست POST را دو بار ارسال کند، هر دو درخواست شناسهٔ عملیات یکسانی خواهند داشت. سپس می‌توانید آن شناسه را تا پایگاه داده منتقل کنید و بررسی کنید که برای هر شناسه فقط یک عملیات اجرا می‌شود، همان‌طور که در مثال ۱۲-۲ نشان داده شده است.

مثال ۱۲-۲. حذف درخواست‌های تکراری با شناسه‌ای یکتا

sql
ALTER TABLE requests ADD UNIQUE (request_id);

BEGIN TRANSACTION;

INSERT INTO requests
  (request_id, from_account, to_account, amount)
  VALUES('0286FDB8-D7E1-423F-B40B-792B3608036C', 4321, 1234, 11.00);

UPDATE accounts SET balance = balance + 11.00 WHERE account_id = 1234;
UPDATE accounts SET balance = balance - 11.00 WHERE account_id = 4321;

COMMIT;

مثال ۱۲-۲ به قید یکتایی ستون request_id متکی است. اگر تراکنشی بخواهد شناسه‌ای را insert کند که از قبل وجود دارد، INSERT شکست می‌خورد و تراکنش abort می‌شود و اجازه نمی‌دهد عملیات دو بار اثر بگذارد. پایگاه‌های دادهٔ رابطه‌ای معمولاً حتی در سطوح isolation ضعیف نیز می‌توانند قید یکتایی را درست نگه دارند؛ در حالی که بررسی و سپس insert در سطح برنامه ممکن است زیر isolation غیرـserializable شکست بخورد، همان‌طور که در «Write Skew و Phantomها» در صفحه ۲۴۶ بررسی شد.

جدول requests در مثال ۱۲-۲ علاوه بر حذف درخواست‌های تکراری، مانند نوعی لاگ رویداد عمل می‌کند و به سمت event sourcing اشاره دارد (به «Event Sourcing» در صفحه ۴۵۷ مراجعه کنید). به‌روزرسانی مانده‌حساب‌ها در واقع لازم نیست در همان تراکنش insert رویداد رخ دهد، زیرا افزونه است و می‌تواند از رویداد درخواست در مصرف‌کنندهٔ downstream مشتق شود؛ به‌شرط آنکه رویداد دقیقاً یک‌بار پردازش شود که آن را نیز می‌توان با شناسهٔ درخواست اعمال کرد.

استدلال سرتاسری

این سناریوی حذف تراکنش‌های تکراری فقط نمونه‌ای از اصلی عمومی‌تر به نام استدلال سرتاسری است که Saltzer، Reed و Clark در سال ۱۹۸۴ بیان کردند [۵۵]:

تابع مورد بحث فقط با دانش و کمک برنامه‌ای که در نقاط انتهایی سیستم ارتباطی قرار دارد می‌تواند به‌طور کامل و درست پیاده‌سازی شود. بنابراین، فراهم کردن آن تابع به‌عنوان قابلیتی از خود سیستم ارتباطی ممکن نیست. (گاهی نسخه‌ای ناقص از تابع که سیستم ارتباطی فراهم می‌کند می‌تواند به‌عنوان بهبود عملکرد مفید باشد.)

در مثال ما، تابع مورد بحث حذف موارد تکراری بود. دیدیم TCP بسته‌های تکراری را در سطح اتصال TCP حذف می‌کند و بعضی پردازشگرهای جریانی در سطح پردازش پیام semantics موسوم به دقیقاً یک‌بار فراهم می‌کنند، اما این برای جلوگیری از ارسال درخواست تکراری توسط کاربر در صورت timeout شدن درخواست اول کافی نیست. TCP، تراکنش‌های پایگاه داده و پردازشگرهای جریانی به‌تنهایی نمی‌توانند این موارد تکراری را کاملاً منتفی کنند. حل مسئله به راه‌حلی سرتاسری نیاز دارد: شناسهٔ تراکنشی که از کلاینت کاربر نهایی تا پایگاه داده منتقل شود.

استدلال سرتاسری برای بررسی یکپارچگی داده نیز صدق می‌کند: checksumهای تعبیه‌شده در Ethernet، TCP و TLS می‌توانند خرابی بسته‌ها در شبکه را تشخیص دهند، اما نمی‌توانند خرابی ناشی از باگ نرم‌افزار در دو انتهای فرستنده و گیرندهٔ اتصال شبکه یا خرابی دیسک محل ذخیرهٔ داده را تشخیص دهند. اگر می‌خواهید همهٔ منابع احتمالی خرابی داده را پیدا کنید، به checksumهای سرتاسری نیز نیاز دارید.

استدلال مشابهی دربارهٔ رمزنگاری صدق می‌کند [۵۵]: رمز عبور شبکهٔ WiFi خانگی از شنود ترافیک WiFi شما جلوگیری می‌کند، اما در برابر مهاجمان نقاط دیگر اینترنت محافظتی ندارد؛ TLS/SSL میان کلاینت و سرور از مهاجمان شبکه محافظت می‌کند، اما در برابر compromise شدن سرور نه. فقط رمزنگاری و احراز هویت سرتاسری می‌تواند در برابر همهٔ این موارد محافظت کند.

اگرچه قابلیت‌های سطح پایین — حذف موارد تکراری TCP، checksumهای Ethernet و رمزنگاری WiFi — نمی‌توانند به‌تنهایی قابلیت‌های سرتاسری مطلوب را فراهم کنند، همچنان مفیدند، زیرا احتمال وقوع مشکل در سطوح بالاتر را کاهش می‌دهند. مثلاً اگر TCP بسته‌ها را دوباره در ترتیب درست قرار نمی‌داد، درخواست‌های HTTP اغلب به‌هم‌ریخته می‌شدند. فقط باید به یاد داشته باشیم قابلیت‌های اطمینان‌پذیری سطح پایین به‌تنهایی برای تضمین درستی سرتاسری کافی نیستند.

به‌کارگیری تفکر سرتاسری در سیستم‌های داده

این بحث مرا به ادعای اولیه‌ام بازمی‌گرداند: صرف استفادهٔ برنامه از سیستم داده‌ای با ویژگی‌های ایمنی نسبتاً قوی مانند تراکنش‌های serializable تضمین نمی‌کند برنامه از گم‌شدن یا خرابی داده مصون باشد. خود برنامه نیز باید اقدامات سرتاسری مانند حذف موارد تکراری را انجام دهد.

این مایهٔ تأسف است، زیرا درست پیاده‌سازی کردن مکانیزم‌های تحمل خطا دشوار است. مکانیزم‌های اطمینان‌پذیری سطح پایین، مانند مکانیزم‌های TCP، بسیار خوب کار می‌کنند و در نتیجه خطاهای باقی‌مانده در سطوح بالاتر نسبتاً نادرند. بسیار خوب بود اگر می‌توانستیم ماشین باقی‌ماندهٔ تحمل خطای سطح بالا را در انتزاعی بسته‌بندی کنیم تا کد برنامه نگران آن نباشد، اما می‌ترسم هنوز انتزاع درست را پیدا نکرده‌ایم.

تراکنش‌ها مدت‌ها انتزاع خوبی تلقی شده‌اند و باور دارم مفیدند. همان‌طور که در مقدمهٔ فصل ۷ بررسی شد، آن‌ها طیف گسترده‌ای از مشکلات احتمالی — نوشتن‌های همزمان، نقض قیدها، crash، قطعی شبکه و خرابی دیسک — را به دو نتیجهٔ ممکن کاهش می‌دهند: commit یا abort. این ساده‌سازی عظیمی در مدل برنامه‌نویسی است، اما می‌ترسم کافی نباشد.

تراکنش‌ها پرهزینه‌اند، به‌ویژه وقتی فناوری‌های ذخیره‌سازی ناهمگن را در بر می‌گیرند (به «تراکنش‌های توزیع‌شده در عمل» در صفحه ۳۶۰ مراجعه کنید). وقتی به‌دلیل هزینهٔ زیاد از تراکنش‌های توزیع‌شده استفاده نمی‌کنیم، مجبور می‌شویم مکانیزم‌های تحمل خطا را در کد برنامه دوباره پیاده‌سازی کنیم. همان‌طور که مثال‌های فراوان این کتاب نشان داده‌اند، استدلال دربارهٔ همزمانی و خرابی جزئی دشوار و خلاف شهود است؛ بنابراین گمان می‌کنم بیشتر مکانیزم‌های سطح برنامه درست کار نمی‌کنند. نتیجه، گم‌شدن یا خرابی داده است.

به این دلایل، فکر می‌کنم ارزش دارد انتزاع‌های تحمل خطایی را بررسی کنیم که فراهم کردن ویژگی‌های درستی سرتاسری خاص هر برنامه را آسان می‌کنند و در عین حال عملکرد و ویژگی‌های عملیاتی خوبی در محیط توزیع‌شدهٔ بزرگ‌مقیاس دارند.

اعمال قیدها

بیایید دربارهٔ درستی در زمینهٔ ایده‌های باز کردن بستهٔ پایگاه‌های داده فکر کنیم («باز کردن بستهٔ پایگاه‌های داده» در صفحه ۴۹۹). دیدیم حذف سرتاسری موارد تکراری با شناسهٔ درخواستی انجام می‌شود که از کلاینت تا پایگاه دادهٔ ثبت‌کنندهٔ نوشتن منتقل می‌شود. قیدهای دیگر چطور؟

به‌ویژه روی قیدهای یکتایی تمرکز می‌کنیم، مانند قیدی که در مثال ۱۲-۲ به آن متکی بودیم. در «قیدها و تضمین‌های یکتایی» در صفحه ۳۳۰ چند نمونهٔ دیگر از قابلیت‌های برنامه را دیدیم که باید یکتایی را اعمال کنند: نام کاربری یا آدرس ایمیل باید کاربر را به‌طور یکتا مشخص کند، سرویس ذخیره‌سازی فایل نمی‌تواند بیش از یک فایل با نام یکسان داشته باشد و دو نفر نمی‌توانند صندلی یکسانی را در هواپیما یا سالن نمایش رزرو کنند.

انواع دیگر قیدها بسیار مشابه‌اند: مثلاً تضمین اینکه مانده‌حساب هیچ‌گاه منفی نشود، بیش از موجودی انبار کالا نفروشید یا اتاق جلسه رزروهای هم‌پوشان نداشته باشد. تکنیک‌های اعمال یکتایی اغلب برای این قیدها نیز قابل استفاده‌اند.

قیدهای یکتایی به consensus نیاز دارند

در فصل ۹ دیدیم که در محیط توزیع‌شده، اعمال قید یکتایی به consensus نیاز دارد: اگر چند درخواست همزمان با مقدار یکسان وجود داشته باشد، سیستم باید به‌شکلی تصمیم بگیرد کدام عملیات متضاد پذیرفته و بقیه به‌دلیل نقض قید رد شوند.

رایج‌ترین راه دستیابی به این consensus، leader کردن یک گره و سپردن همهٔ تصمیم‌ها به آن است. این روش تا وقتی مشکلی با هدایت همهٔ درخواست‌ها از یک گره ندارید — حتی اگر کلاینت آن سوی جهان باشد — و تا وقتی آن گره خراب نشود، خوب کار می‌کند. اگر لازم است خرابی leader را تحمل کنید، دوباره به مسئلهٔ consensus بازمی‌گردید (به «رپلیکاسیون single-leader و consensus» در صفحه ۳۶۷ مراجعه کنید).

بررسی یکتایی را می‌توان با پارتیشن‌بندی بر اساس مقداری که باید یکتا باشد scale out کرد. مثلاً اگر مانند مثال ۱۲-۲ باید یکتایی بر اساس شناسهٔ درخواست تضمین شود، می‌توانید مطمئن شوید همهٔ درخواست‌های دارای شناسهٔ یکسان به پارتیشن یکسانی هدایت می‌شوند (فصل ۶ را ببینید). اگر نام‌های کاربری باید یکتا باشند، می‌توانید بر اساس hash نام کاربری پارتیشن‌بندی کنید.

با این حال، رپلیکاسیون multi-master ناهمگام منتفی است، زیرا ممکن است masterهای مختلف نوشتن‌های متضاد را همزمان بپذیرند و مقادیر دیگر یکتا نباشند (به «پیاده‌سازی سیستم‌های linearizable» در صفحه ۳۳۲ مراجعه کنید). اگر می‌خواهید هر نوشتنی را که قید را نقض می‌کند فوراً رد کنید، هماهنگی همزمان اجتناب‌ناپذیر است [۵۶].

یکتایی در پیام‌رسانی مبتنی بر لاگ

لاگ تضمین می‌کند همهٔ مصرف‌کنندگان پیام‌ها را با ترتیب یکسان ببینند؛ تضمینی که به‌طور رسمی total order broadcast نامیده می‌شود و معادل consensus است (به «Total Order Broadcast» در صفحه ۳۴۸ مراجعه کنید). در رویکرد پایگاه دادهٔ بازشده با پیام‌رسانی مبتنی بر لاگ، می‌توانیم برای اعمال قیدهای یکتایی از رویکرد بسیار مشابهی استفاده کنیم.

پردازشگر جریانی همهٔ پیام‌های یک پارتیشن لاگ را به‌طور متوالی روی یک thread مصرف می‌کند (به «مقایسهٔ لاگ‌ها با پیام‌رسانی سنتی» در صفحه ۴۴۸ مراجعه کنید). بنابراین اگر لاگ بر اساس مقداری که باید یکتا باشد پارتیشن‌بندی شود، پردازشگر جریانی می‌تواند بدون ابهام و به‌شکلی قطعی تصمیم بگیرد کدام‌یک از چند عملیات متضاد زودتر آمده است. مثلاً در حالت چند کاربر که می‌خواهند نام کاربری یکسانی را تصاحب کنند [۵۷]:

۱. هر درخواست نام کاربری به‌صورت پیام encode و به پارتیشنی append می‌شود که hash نام کاربری تعیین می‌کند.

۲. پردازشگر جریانی درخواست‌ها را به‌طور متوالی از لاگ می‌خواند و با استفاده از پایگاه داده‌ای محلی مشخص می‌کند کدام نام‌های کاربری گرفته شده‌اند. برای هر درخواست نام کاربریِ آزاد، نام را گرفته‌شده ثبت و پیام موفقیت را در جریان خروجی منتشر می‌کند. برای هر درخواست نام کاربریِ گرفته‌شده، پیام رد را در جریان خروجی منتشر می‌کند.

۳. کلاینت درخواست‌کنندهٔ نام کاربری، جریان خروجی را مشاهده و منتظر پیام موفقیت یا رد متناظر با درخواستش می‌ماند.

این الگوریتم اساساً همان الگوریتم «پیاده‌سازی ذخیره‌سازی linearizable با total order broadcast» در صفحه ۳۵۰ است. با افزایش تعداد پارتیشن‌ها به‌آسانی برای throughput بالای درخواست مقیاس می‌یابد، زیرا هر پارتیشن می‌تواند مستقل پردازش شود.

این رویکرد نه‌فقط برای قیدهای یکتایی، بلکه برای انواع بسیاری از قیدهای دیگر نیز کار می‌کند. اصل بنیادی آن این است که هر نوشتنی که ممکن است با دیگری تضاد داشته باشد به پارتیشن یکسان هدایت و به‌طور متوالی پردازش شود. همان‌طور که در «تعارض چیست؟» در صفحه ۱۷۴ و «Write Skew و Phantomها» در صفحه ۲۴۶ بررسی شد، تعریف تعارض ممکن است به برنامه بستگی داشته باشد، اما پردازشگر جریانی می‌تواند برای اعتبارسنجی درخواست از منطق دلخواه استفاده کند. این ایده شبیه رویکرد پیشگام Bayou در دههٔ ۱۹۹۰ است [۵۸].

پردازش درخواست چندپارتیشنی

وقتی چند پارتیشن درگیرند، تضمین اجرای اتمیک عملیات همراه با رعایت قیدها جالب‌تر می‌شود. در مثال ۱۲-۲، احتمالاً سه پارتیشن داریم: پارتیشن حاوی شناسهٔ درخواست، پارتیشن حاوی حساب دریافت‌کننده و پارتیشن حاوی حساب پرداخت‌کننده. دلیلی ندارد این سه مورد در یک پارتیشن باشند، زیرا همگی از یکدیگر مستقل‌اند.

در رویکرد سنتی پایگاه داده، اجرای این تراکنش نیازمند atomic commit در هر سه پارتیشن است که اساساً آن را نسبت به همهٔ تراکنش‌های دیگر روی هر یک از آن پارتیشن‌ها وارد ترتیب کلی می‌کند. چون اکنون هماهنگی میان پارتیشن‌ها وجود دارد، پارتیشن‌های مختلف دیگر نمی‌توانند مستقل پردازش شوند و احتمالاً throughput آسیب می‌بیند.

با این حال، معلوم می‌شود می‌توان با لاگ‌های پارتیشن‌بندی‌شده و بدون atomic commit به درستی معادل رسید:

۱. کلاینت به درخواست انتقال پول از حساب A به حساب B شناسه‌ای یکتا می‌دهد و آن را بر اساس شناسهٔ درخواست به پارتیشنی از لاگ append می‌کند.

۲. پردازشگر جریانی لاگ درخواست‌ها را می‌خواند. برای هر پیام درخواست، دو پیام در جریان‌های خروجی منتشر می‌کند: دستور بدهکار کردن حساب پرداخت‌کنندهٔ A که بر اساس A پارتیشن‌بندی شده، و دستور بستانکار کردن حساب دریافت‌کنندهٔ B که بر اساس B پارتیشن‌بندی شده است. شناسهٔ اصلی درخواست در پیام‌های منتشرشده گنجانده می‌شود.

۳. پردازشگرهای بعدی جریان دستورهای بستانکار و بدهکار را مصرف می‌کنند، موارد تکراری را بر اساس شناسهٔ درخواست حذف و تغییرات را روی مانده‌حساب‌ها اعمال می‌کنند.

گام‌های ۱ و ۲ ضروری‌اند، زیرا اگر کلاینت مستقیماً دستورهای بستانکار و بدهکار را می‌فرستاد، برای تضمین اجرای هر دو یا هیچ‌کدام به atomic commit در آن دو پارتیشن نیاز بود. برای اجتناب از تراکنش توزیع‌شده، ابتدا درخواست را به‌صورت یک پیام به‌شکل پایدار لاگ می‌کنیم و سپس دستورهای بستانکار و بدهکار را از آن پیام اول مشتق می‌کنیم. نوشتن روی یک شیء تقریباً در همهٔ سیستم‌های داده اتمیک است (به «نوشتن روی یک شیء» در صفحه ۲۳۰ مراجعه کنید)، بنابراین درخواست یا در لاگ ظاهر می‌شود یا نمی‌شود و به atomic commit چندپارتیشنی نیازی نیست.

اگر پردازشگر جریانی در گام ۲ crash کند، پردازش را از آخرین checkpoint خود ادامه می‌دهد. هیچ پیام درخواستی را نادیده نمی‌گیرد، اما ممکن است درخواست‌ها را چند بار پردازش و دستورهای بستانکار و بدهکار تکراری تولید کند. با این حال، چون قطعی است فقط همان دستورها را دوباره تولید می‌کند و پردازشگرهای گام ۳ می‌توانند با شناسهٔ سرتاسری درخواست به‌آسانی موارد تکراری را حذف کنند.

اگر می‌خواهید تضمین کنید حساب پرداخت‌کننده در این انتقال دچار اضافه‌برداشت نمی‌شود، می‌توانید پردازشگر جریانی دیگری داشته باشید که بر اساس شمارهٔ حساب پرداخت‌کننده پارتیشن‌بندی شده، مانده‌حساب‌ها را نگه می‌دارد و تراکنش‌ها را اعتبارسنجی می‌کند. در این صورت فقط تراکنش‌های معتبر در گام ۱ وارد لاگ درخواست می‌شوند.

با شکستن تراکنش چندپارتیشنی به دو مرحله با پارتیشن‌بندی متفاوت و استفاده از شناسهٔ سرتاسری درخواست، حتی در حضور خطا و بدون استفاده از پروتکل atomic commit به همان ویژگی درستی رسیده‌ایم: هر درخواست دقیقاً یک‌بار روی حساب پرداخت‌کننده و دریافت‌کننده اعمال می‌شود. ایدهٔ استفاده از چند مرحله با پارتیشن‌بندی متفاوت شبیه چیزی است که در «پردازش دادهٔ چندپارتیشنی» در صفحه ۵۱۴ بررسی کردیم (همچنین به «کنترل همزمانی» در صفحه ۴۶۲ مراجعه کنید).

به‌موقع بودن و یکپارچگی

ویژگی مناسب تراکنش‌ها این است که معمولاً linearizable هستند (به «Linearizability» در صفحه ۳۲۴ مراجعه کنید): نویسنده تا commit شدن تراکنش منتظر می‌ماند و پس از آن نوشتن‌هایش بلافاصله برای همهٔ خوانندگان قابل مشاهده‌اند.

وقتی عملیاتی را میان چند مرحلهٔ پردازشگر جریانی باز می‌کنیم چنین نیست: مصرف‌کنندگان لاگ ذاتاً ناهمگام‌اند، بنابراین فرستنده تا پردازش پیام توسط مصرف‌کنندگان منتظر نمی‌ماند. با این حال، کلاینت می‌تواند منتظر ظاهر شدن پیامی در جریان خروجی بماند. این همان کاری است که در «یکتایی در پیام‌رسانی مبتنی بر لاگ» در صفحه ۵۲۲ هنگام بررسی رعایت قید یکتایی انجام دادیم.

در این مثال، درستی بررسی یکتایی به منتظر ماندن فرستنده برای نتیجه وابسته نیست. انتظار فقط برای آگاه کردن همزمان فرستنده از موفق یا ناموفق بودن بررسی یکتایی است، اما این اعلان را می‌توان از اثرات پردازش پیام جدا کرد.

به‌طور کلی‌تر، فکر می‌کنم اصطلاح consistency دو الزام متفاوت را با هم درمی‌آمیزد که بهتر است جداگانه بررسی شوند:

به‌موقع بودن

به‌موقع بودن یعنی تضمین اینکه کاربران سیستم را در state به‌روز مشاهده کنند. پیش‌تر دیدیم اگر کاربر از کپی کهنهٔ داده بخواند، ممکن است آن را در state ناسازگار ببیند (به «مشکلات تأخیر رپلیکاسیون» در صفحه ۱۶۱ مراجعه کنید). با این حال، این ناسازگاری موقتی است و در نهایت فقط با منتظر ماندن و تلاش دوباره حل می‌شود.

قضیهٔ CAP (به «هزینهٔ Linearizability» در صفحه ۳۳۵ مراجعه کنید) consistency را به‌معنای linearizability به کار می‌برد که راهی قوی برای دستیابی به به‌موقع بودن است. ویژگی‌های ضعیف‌تر به‌موقع بودن، مانند consistency خواندن پس از نوشتن (به «خواندن نوشتن‌های خودتان» در صفحه ۱۶۲ مراجعه کنید)، نیز می‌توانند مفید باشند.

یکپارچگی

یکپارچگی یعنی نبود خرابی؛ یعنی داده از دست نرود و دادهٔ متناقض یا نادرست وجود نداشته باشد. به‌ویژه اگر مجموعه‌داده‌ای مشتق به‌عنوان نما روی دادهٔ زیرین نگهداری شود (به «مشتق‌سازی state فعلی از لاگ رویداد» در صفحه ۴۵۸ مراجعه کنید)، مشتق‌سازی باید درست باشد. مثلاً ایندکس پایگاه داده باید محتوای پایگاه را درست بازتاب دهد؛ ایندکسی که بعضی رکوردها در آن گم شده‌اند چندان مفید نیست.

اگر یکپارچگی نقض شود، ناسازگاری دائمی است: در بیشتر موارد منتظر ماندن و تلاش دوباره خرابی پایگاه داده را رفع نمی‌کند. در عوض، بررسی و تعمیر صریح لازم است. در زمینهٔ تراکنش‌های ACID (به «معنای ACID» در صفحه ۲۲۳ مراجعه کنید)، consistency معمولاً نوعی مفهوم یکپارچگی خاص برنامه دانسته می‌شود. Atomicity و durability ابزارهای مهم حفظ یکپارچگی‌اند.

در قالب شعار: نقض به‌موقع بودن «consistency نهایی» است، در حالی که نقض یکپارچگی «ناسازگاری همیشگی» است.

ادعا می‌کنم در بیشتر برنامه‌ها، یکپارچگی بسیار مهم‌تر از به‌موقع بودن است. نقض به‌موقع بودن می‌تواند آزاردهنده و گیج‌کننده باشد، اما نقض یکپارچگی ممکن است فاجعه‌بار باشد.

مثلاً اگر تراکنشی که در ۲۴ ساعت گذشته انجام داده‌اید هنوز در صورت‌حساب کارت اعتباری ظاهر نشده، تعجب‌آور نیست؛ طبیعی است این سیستم‌ها مقداری تأخیر داشته باشند. می‌دانیم بانک‌ها تراکنش‌ها را به‌شکل ناهمگام تطبیق و تسویه می‌کنند و به‌موقع بودن اینجا چندان مهم نیست [۳]. اما بسیار بد خواهد بود اگر ماندهٔ صورت‌حساب با مجموع تراکنش‌ها به‌اضافهٔ ماندهٔ صورت‌حساب قبلی برابر نباشد — خطایی در جمع — یا تراکنشی از شما دریافت ولی به فروشنده پرداخت نشود — پول ناپدید شود. چنین مشکلاتی نقض یکپارچگی سیستم‌اند.

درستی سیستم‌های dataflow

تراکنش‌های ACID معمولاً هم تضمین به‌موقع بودن — مانند linearizability — و هم تضمین یکپارچگی — مانند atomic commit — فراهم می‌کنند. بنابراین اگر از دید تراکنش‌های ACID به درستی برنامه نزدیک شوید، تمایز میان به‌موقع بودن و یکپارچگی چندان مهم نیست.

از سوی دیگر، ویژگی جالب سیستم‌های dataflow مبتنی بر رویداد که در این فصل بررسی کردیم این است که به‌موقع بودن و یکپارچگی را از هم جدا می‌کنند. هنگام پردازش ناهمگام جریان رویداد، هیچ تضمینی برای به‌موقع بودن وجود ندارد، مگر اینکه صریحاً مصرف‌کنندگانی بسازید که پیش از بازگشت منتظر رسیدن پیام بمانند. اما یکپارچگی در واقع محور سیستم‌های جریانی است.

Semantics دقیقاً یک‌بار یا عملاً یک‌بار (به «تحمل خطا» در صفحه ۴۷۶ مراجعه کنید) مکانیزمی برای حفظ یکپارچگی است. اگر رویدادی گم شود یا دو بار اثر بگذارد، ممکن است یکپارچگی سیستم داده نقض شود. بنابراین تحویل تحمل‌پذیر خطای پیام و حذف موارد تکراری — مثلاً عملیات idempotent — برای حفظ یکپارچگی سیستم داده در برابر خطا مهم‌اند.

همان‌طور که در بخش قبل دیدیم، سیستم‌های پردازش جریانی قابل اعتماد می‌توانند بدون نیاز به تراکنش توزیع‌شده و پروتکل atomic commit یکپارچگی را حفظ کنند؛ یعنی احتمالاً می‌توانند با عملکرد و استحکام عملیاتی بسیار بهتر، به درستی مشابهی برسند. این یکپارچگی را با ترکیبی از مکانیزم‌ها به دست آوردیم:

  • نمایش محتوای عملیات نوشتن به‌صورت یک پیام واحد که به‌آسانی می‌توان آن را اتمیک نوشت؛ رویکردی که با event sourcing بسیار خوب جور است (به «Event Sourcing» در صفحه ۴۵۷ مراجعه کنید)
  • مشتق‌سازی همهٔ به‌روزرسانی‌های دیگر state از آن پیام واحد با توابع مشتق‌سازی قطعی، مشابه stored procedureها (به «اجرای سریال واقعی» در صفحه ۲۵۲ و «کد برنامه به‌عنوان تابع مشتق‌سازی» در صفحه ۵۰۵ مراجعه کنید)
  • عبور دادن شناسهٔ درخواست تولیدشده توسط کلاینت از همهٔ این سطوح پردازش، برای ممکن کردن حذف سرتاسری موارد تکراری و idempotence
  • تغییرناپذیر کردن پیام‌ها و اجازهٔ پردازش مجدد دوره‌ای دادهٔ مشتق، که بازیابی از باگ‌ها را آسان‌تر می‌کند (به «مزایای رویدادهای تغییرناپذیر» در صفحه ۴۶۰ مراجعه کنید)

این ترکیب مکانیزم‌ها به نظرم مسیری بسیار امیدوارکننده برای ساخت برنامه‌های تحمل‌پذیر خطا در آینده است.

تفسیر سست قیدها

همان‌طور که پیش‌تر گفتیم، اعمال قید یکتایی به consensus نیاز دارد که معمولاً با هدایت همهٔ رویدادهای یک پارتیشن از گره‌ای واحد پیاده‌سازی می‌شود. اگر شکل سنتی قید یکتایی را بخواهیم، این محدودیت اجتناب‌ناپذیر است و پردازش جریانی نمی‌تواند از آن دوری کند.

با این حال، نکتهٔ دیگر این است که بسیاری از برنامه‌های واقعی می‌توانند با مفاهیم بسیار ضعیف‌تری از یکتایی کنار بیایند:

  • اگر دو نفر همزمان نام کاربری یکسانی ثبت یا صندلی یکسانی رزرو کنند، می‌توانید برای یکی از آن‌ها پیام عذرخواهی بفرستید و بخواهید گزینهٔ دیگری انتخاب کند. این نوع تغییر برای اصلاح اشتباه، تراکنش جبرانی نامیده می‌شود [۵۹، ۶۰].
  • اگر مشتریان بیشتر از موجودی انبار سفارش دهند، می‌توانید موجودی بیشتری تهیه کنید، بابت تأخیر از مشتریان عذرخواهی و به آن‌ها تخفیف بدهید. این در واقع همان کاری است که مثلاً اگر لیفتراک از روی بعضی اقلام انبار عبور کند و موجودی واقعی از تصور شما کمتر شود باید انجام دهید [۶۱]. بنابراین گردش‌کار عذرخواهی در هر حال باید بخشی از فرایندهای کسب‌وکار باشد و شاید نیازی به قید linearizable روی تعداد اقلام موجود نباشد.
  • به‌همین ترتیب، بسیاری از شرکت‌های هواپیمایی با انتظار اینکه بعضی مسافران پرواز را از دست بدهند بیش از ظرفیت هواپیما رزرو می‌پذیرند و بسیاری از هتل‌ها با انتظار لغو بعضی مهمانان بیش از ظرفیت اتاق رزرو می‌کنند. در این موارد قید «یک نفر برای هر صندلی» عمداً به دلایل تجاری نقض می‌شود و فرایندهای جبرانی — بازپرداخت، ارتقای کلاس یا فراهم کردن اتاق رایگان در هتل همسایه — برای مواقعی در نظر گرفته می‌شوند که تقاضا از عرضه بیشتر است. حتی بدون رزرو بیش از ظرفیت، برای رسیدگی به لغو پروازها به‌دلیل آب‌وهوای بد یا اعتصاب کارکنان، فرایندهای عذرخواهی و جبران لازم بود؛ بازیابی از چنین مشکلاتی بخشی عادی از کسب‌وکار است [۳].
  • اگر کسی بیش از موجودی حسابش پول برداشت کند، بانک می‌تواند کارمزد اضافه‌برداشت از او بگیرد و بخواهد بدهی‌اش را بازپرداخت کند. با محدود کردن مجموع برداشت روزانه، ریسک بانک محدود می‌شود.

در بسیاری زمینه‌های تجاری، نقض موقت قید و رفع بعدی آن با عذرخواهی در واقع قابل قبول است. هزینهٔ عذرخواهی — از نظر پول یا اعتبار — متفاوت است، اما اغلب بسیار کم است: نمی‌توانید ایمیل ارسال‌شده را پس بگیرید، اما می‌توانید ایمیل بعدی را با اصلاحیه بفرستید. اگر کارت اعتباری را تصادفاً دو بار شارژ کنید، می‌توانید یکی از مبالغ را بازپرداخت کنید و هزینهٔ شما فقط کارمزد پردازش و شاید شکایت مشتری است. وقتی پول از ATM پرداخت شد نمی‌توانید مستقیم آن را پس بگیرید، هرچند در اصل اگر حساب دچار اضافه‌برداشت شده و مشتری بدهی را نپردازد، می‌توانید مأمور وصول بدهی بفرستید.

قابل قبول بودن هزینهٔ عذرخواهی تصمیمی تجاری است. اگر قابل قبول باشد، مدل سنتی بررسی همهٔ قیدها پیش از نوشتن داده بی‌دلیل محدودکننده است و به قید linearizable نیازی نیست. شاید انتخاب معقول این باشد که خوش‌بینانه نوشتن را انجام دهید و قید را پس از آن بررسی کنید. همچنان می‌توانید تضمین کنید اعتبارسنجی پیش از انجام کارهایی رخ می‌دهد که بازیابی از آن‌ها پرهزینه است، اما این به آن معنا نیست که باید حتی پیش از نوشتن داده اعتبارسنجی کنید.

این برنامه‌ها به یکپارچگی نیاز دارند: نمی‌خواهید رزروی گم شود یا پول به‌دلیل ناهماهنگی بستانکار و بدهکار ناپدید شود. اما به به‌موقع بودن اعمال قید نیاز ندارند: اگر بیشتر از موجودی انبار فروخته‌اید، می‌توانید بعداً با عذرخواهی مشکل را جبران کنید. این کار شبیه رویکردهای حل تعارضی است که در «مدیریت تعارض‌های نوشتن» در صفحه ۱۷۱ بررسی کردیم.

سیستم‌های دادهٔ بی‌نیاز از هماهنگی

اکنون دو مشاهدهٔ جالب داریم:

۱. سیستم‌های dataflow می‌توانند بدون atomic commit،‏ linearizability یا هماهنگی همزمان میان پارتیشن‌ها، تضمین‌های یکپارچگی دادهٔ مشتق را حفظ کنند.

۲. اگرچه قیدهای یکتایی سختگیرانه به به‌موقع بودن و هماهنگی نیاز دارند، بسیاری از برنامه‌ها با قیدهای سستی که ممکن است موقتاً نقض و بعداً اصلاح شوند مشکلی ندارند، به‌شرط آنکه یکپارچگی در تمام مدت حفظ شود.

این دو مشاهده در کنار هم یعنی سیستم‌های dataflow می‌توانند بدون نیاز به هماهنگی، خدمات مدیریت دادهٔ بسیاری از برنامه‌ها را فراهم کنند و همچنان تضمین‌های یکپارچگی قوی بدهند. چنین سیستم‌های دادهٔ بی‌نیاز از هماهنگی بسیار جذاب‌اند: می‌توانند نسبت به سیستم‌هایی که باید هماهنگی همزمان انجام دهند عملکرد و تحمل خطای بهتری داشته باشند [۵۶].

مثلاً چنین سیستمی می‌تواند به‌صورت توزیع‌شده در چند دیتاسنتر و با پیکربندی multi-leader کار کند و میان مناطق به‌شکل ناهمگام رپلیکه شود. هر دیتاسنتر می‌تواند مستقل از بقیه به کار ادامه دهد، زیرا به هماهنگی همزمان میان مناطق نیازی نیست. چنین سیستمی تضمین به‌موقع بودن ضعیفی دارد — بدون افزودن هماهنگی نمی‌تواند linearizable باشد — اما همچنان می‌تواند تضمین یکپارچگی قوی داشته باشد.

در این زمینه، تراکنش‌های serializable همچنان به‌عنوان بخشی از نگهداری state مشتق مفیدند، اما می‌توانند در محدوده‌ای کوچک اجرا شوند که در آن خوب کار می‌کنند [۸]. تراکنش‌های توزیع‌شدهٔ ناهمگن مانند تراکنش‌های XA (به «تراکنش‌های توزیع‌شده در عمل» در صفحه ۳۶۰ مراجعه کنید) لازم نیستند. هماهنگی همزمان همچنان می‌تواند در جاهایی که نیاز است وارد شود — مثلاً برای اعمال قیدهای سختگیرانه پیش از عملیاتی که بازیابی از آن ممکن نیست — اما اگر فقط بخش کوچکی از برنامه به آن نیاز دارد، لازم نیست همه‌چیز هزینهٔ هماهنگی را بپردازد [۴۳].

نگاه دیگر به هماهنگی و قیدها این است که آن‌ها تعداد عذرخواهی‌های لازم به‌دلیل ناسازگاری را کاهش می‌دهند، اما شاید عملکرد و availability سیستم را نیز کم کنند و در نتیجه تعداد عذرخواهی‌های لازم به‌دلیل outage را افزایش دهند. نمی‌توانید تعداد عذرخواهی‌ها را به صفر برسانید، اما می‌توانید بهترین trade-off را برای نیازهای خود پیدا کنید؛ نقطهٔ مطلوبی که نه ناسازگاری بیش از حد وجود دارد و نه مشکلات فراوان availability.

اعتماد کن، اما بررسی هم بکن

تمام بحث ما دربارهٔ درستی، یکپارچگی و تحمل خطا بر این فرض استوار بوده که بعضی چیزها ممکن است اشتباه شوند و بعضی دیگر نه. این فرض‌ها را مدل سیستم می‌نامیم (به «نگاشت مدل‌های سیستم به دنیای واقعی» در صفحه ۳۰۹ مراجعه کنید): مثلاً باید فرض کنیم فرایندها ممکن است crash کنند، ماشین‌ها ناگهان برقشان را از دست بدهند و شبکه ممکن است پیام‌ها را خودسرانه به تأخیر بیندازد یا drop کند. اما شاید فرض کنیم دادهٔ نوشته‌شده روی دیسک پس از fsync گم نمی‌شود، دادهٔ حافظه خراب نمی‌شود و دستور ضرب CPU همیشه نتیجهٔ درست را برمی‌گرداند.

این فرض‌ها کاملاً معقول‌اند، زیرا بیشتر اوقات درست‌اند و اگر مجبور بودیم دائماً نگران اشتباه کردن رایانه‌ها باشیم، انجام هر کاری دشوار می‌شد. مدل‌های سیستم به‌طور سنتی رویکردی دودویی به خطا دارند: فرض می‌کنیم بعضی چیزها ممکن و بعضی دیگر هرگز ممکن نیستند.

در واقعیت، مسئله بیشتر احتمالات است: بعضی چیزها محتمل‌تر و بعضی کم‌احتمال‌ترند. پرسش این است که آیا نقض فرض‌های ما آن‌قدر تکرار می‌شود که در عمل با آن روبه‌رو شویم.

دیدیم داده می‌تواند در حالی که دست‌نخورده روی دیسک مانده خراب شود (به «رپلیکاسیون و Durability» در صفحه ۲۲۷ مراجعه کنید) و خرابی داده در شبکه گاهی می‌تواند از checksumهای TCP بگریزد (به «شکل‌های ضعیف دروغ‌گویی» در صفحه ۳۰۶ مراجعه کنید). شاید باید توجه بیشتری به این موضوع داشته باشیم؟

یکی از برنامه‌هایی که قبلاً روی آن کار می‌کردم گزارش‌های crash را از کلاینت‌ها جمع‌آوری می‌کرد و بعضی گزارش‌های دریافتی فقط با flip تصادفی بیت‌ها در حافظهٔ آن دستگاه‌ها قابل توضیح بودند. بعید به نظر می‌رسد، اما اگر دستگاه‌های کافی نرم‌افزار شما را اجرا کنند، حتی رویدادهای بسیار نامحتمل نیز رخ می‌دهند. افزون بر خرابی تصادفی حافظه بر اثر نقص سخت‌افزاری یا تابش، بعضی الگوهای بیمارگونهٔ دسترسی به حافظه حتی در حافظهٔ سالم نیز می‌توانند بیت‌ها را flip کنند [۶۲]؛ اثری که می‌توان از آن برای شکستن مکانیزم‌های امنیتی سیستم‌عامل استفاده کرد [۶۳] و rowhammer نام دارد. وقتی دقیق نگاه کنید، سخت‌افزار آن انتزاع بی‌نقصی نیست که به نظر می‌رسد.

برای روشن شدن موضوع، flip تصادفی بیت‌ها همچنان در سخت‌افزار مدرن بسیار نادر است [۶۴]. فقط می‌خواهم اشاره کنم که خارج از قلمرو امکان نیست و بنابراین شایستهٔ توجه است.

حفظ یکپارچگی در برابر باگ‌های نرم‌افزاری

افزون بر چنین مشکلات سخت‌افزاری، همیشه خطر باگ‌های نرم‌افزاری وجود دارد که checksumهای سطح پایین شبکه، حافظه یا filesystem آن‌ها را پیدا نمی‌کنند. حتی نرم‌افزارهای پایگاه دادهٔ پرکاربرد باگ دارند: شخصاً مواردی دیده‌ام که MySQL نتوانسته قید یکتایی را درست نگه دارد [۶۵] و سطح isolation سریال PostgreSQL ناهنجاری write skew نشان داده است [۶۶]؛ با اینکه MySQL و PostgreSQL پایگاه‌های دادهٔ مستحکم و خوش‌نامی‌اند که افراد بسیاری سال‌ها آن‌ها را در میدان عمل آزموده‌اند. در نرم‌افزارهای کم‌بلوغ‌تر احتمالاً وضعیت بسیار بدتر است.

با وجود تلاش قابل توجه برای طراحی دقیق، آزمایش و بازبینی، باگ‌ها همچنان راه خود را پیدا می‌کنند. اگرچه نادرند و در نهایت پیدا و رفع می‌شوند، بازه‌ای وجود دارد که در آن چنین باگ‌هایی می‌توانند داده را خراب کنند.

در کد برنامه باید باگ‌های بسیار بیشتری را فرض کنیم، زیرا بیشتر برنامه‌ها حتی نزدیک به میزان بازبینی و آزمایشی که کد پایگاه داده دریافت می‌کند بررسی نمی‌شوند. بسیاری از برنامه‌ها حتی قابلیت‌هایی را که پایگاه داده برای حفظ یکپارچگی فراهم می‌کند — مانند قیدهای foreign key یا یکتایی — درست به کار نمی‌برند [۳۶].

Consistency در معنای ACID (به «Consistency» در صفحه ۲۲۴ مراجعه کنید) بر این ایده استوار است که پایگاه داده از state سازگار آغاز می‌کند و تراکنش آن را از یک state سازگار به state سازگار دیگری تبدیل می‌کند. بنابراین انتظار داریم پایگاه داده همیشه در state سازگار باشد. اما این مفهوم فقط در صورتی معنا دارد که فرض کنید تراکنش بدون باگ است. اگر برنامه به‌شکلی از پایگاه داده نادرست استفاده کند — مثلاً سطح isolation ضعیفی را ناامن به کار ببرد — یکپارچگی پایگاه داده تضمین‌پذیر نیست.

کورکورانه به وعده‌ها اعتماد نکنید

وقتی سخت‌افزار و نرم‌افزار همیشه با ایدئال مورد انتظار ما مطابقت ندارند، به نظر می‌رسد خرابی داده دیر یا زود اجتناب‌ناپذیر است. بنابراین دست‌کم باید راهی برای فهمیدن خراب شدن داده داشته باشیم تا بتوانیم آن را اصلاح و منبع خطا را پیدا کنیم. بررسی یکپارچگی داده ممیزی نامیده می‌شود.

همان‌طور که در «مزایای رویدادهای تغییرناپذیر» در صفحه ۴۶۰ بررسی شد، ممیزی فقط برای برنامه‌های مالی نیست. با این حال، auditability دقیقاً به این دلیل در امور مالی بسیار مهم است که همه می‌دانند اشتباه رخ می‌دهد و همگی نیاز به تشخیص و رفع مشکلات را می‌پذیریم.

سیستم‌های بالغ نیز معمولاً احتمال اشتباه شدن چیزهای نامحتمل را در نظر می‌گیرند و آن ریسک را مدیریت می‌کنند. مثلاً سیستم‌های ذخیره‌سازی بزرگ‌مقیاس مانند HDFS و Amazon S3 کاملاً به دیسک‌ها اعتماد نمی‌کنند: فرایندهای پس‌زمینه‌ای اجرا می‌کنند که پیوسته فایل‌ها را دوباره می‌خوانند، با رپلیکاهای دیگر مقایسه می‌کنند و فایل‌ها را از دیسکی به دیسک دیگر انتقال می‌دهند تا ریسک خرابی خاموش کاهش یابد [۶۷].

اگر می‌خواهید مطمئن شوید داده هنوز وجود دارد، باید واقعاً آن را بخوانید و بررسی کنید. بیشتر اوقات همچنان وجود دارد، اما اگر وجود نداشته باشد، قطعاً می‌خواهید زودتر متوجه شوید. با همین استدلال، مهم است هر از گاهی بازیابی از backupها را امتحان کنید؛ وگرنه ممکن است فقط وقتی بفهمید backup خراب است که بسیار دیر شده و داده را از دست داده‌اید. کورکورانه اعتماد نکنید که همه‌چیز کار می‌کند.

فرهنگ بررسی

سیستم‌هایی مانند HDFS و S3 همچنان باید فرض کنند دیسک‌ها بیشتر اوقات درست کار می‌کنند؛ فرضی معقول که با فرض همیشه درست کار کردن آن‌ها یکسان نیست. با این حال، اکنون سیستم‌های زیادی چنین رویکرد «اعتماد کن، اما بررسی هم بکن» برای ممیزی پیوستهٔ خود ندارند. بسیاری فرض می‌کنند تضمین‌های درستی مطلق‌اند و امکانی برای خرابی نادر داده در نظر نمی‌گیرند. امیدوارم در آینده سیستم‌های خوداعتبارسنج یا خودممیز بیشتری ببینیم که به‌جای تکیه بر اعتماد کورکورانه، پیوسته یکپارچگی خود را بررسی کنند [۶۸].

می‌ترسم فرهنگ پایگاه‌های دادهٔ ACID ما را به سمت توسعهٔ برنامه بر پایهٔ اعتماد کورکورانه به فناوری — مانند مکانیزم تراکنش — و غفلت از هرگونه auditability در این فرایند سوق داده باشد. چون فناوری مورد اعتماد بیشتر اوقات به‌اندازهٔ کافی خوب کار می‌کرد، مکانیزم‌های ممیزی ارزش سرمایه‌گذاری تلقی نمی‌شدند.

اما بعد چشم‌انداز پایگاه داده تغییر کرد: تضمین‌های consistency ضعیف‌تر زیر پرچم NoSQL به هنجار تبدیل شدند و فناوری‌های ذخیره‌سازی کم‌بلوغ‌تر به‌شکل گسترده استفاده شدند. با این حال، چون مکانیزم‌های ممیزی توسعه نیافته بودند، همچنان برنامه‌ها را بر پایهٔ اعتماد کورکورانه ساختیم، هرچند این رویکرد اکنون خطرناک‌تر شده بود. بیایید لحظه‌ای دربارهٔ طراحی برای auditability فکر کنیم.

طراحی برای auditability

اگر تراکنشی چند شیء را در پایگاه داده تغییر دهد، پس از وقوع دشوار است بفهمیم آن تراکنش چه معنایی داشته است. حتی اگر لاگ تراکنش‌ها را capture کنید (به «Change Data Capture» در صفحه ۴۵۴ مراجعه کنید)، insertها، updateها و deleteهای جدول‌های مختلف لزوماً تصویر روشنی از دلیل آن تغییرات نمی‌دهند. اجرای منطق برنامه‌ای که دربارهٔ آن تغییرات تصمیم گرفته گذراست و نمی‌توان آن را بازتولید کرد.

در مقابل، سیستم‌های مبتنی بر رویداد می‌توانند auditability بهتری فراهم کنند. در رویکرد event sourcing، ورودی کاربر به سیستم به‌صورت رویدادی واحد و تغییرناپذیر نمایش داده می‌شود و هر به‌روزرسانی حاصل در state از آن رویداد مشتق می‌شود. مشتق‌سازی را می‌توان قطعی و تکرارپذیر کرد، به‌طوری‌که اجرای لاگ یکسان رویدادها با نسخهٔ یکسان کد مشتق‌سازی به به‌روزرسانی‌های state یکسانی منجر شود.

صریح بودن دربارهٔ dataflow (به «فلسفهٔ خروجی‌های پردازش دسته‌ای» در صفحه ۴۱۳ مراجعه کنید) منشأ داده را بسیار روشن‌تر می‌کند و بررسی یکپارچگی را عملی‌تر می‌سازد. برای لاگ رویداد می‌توانیم با hash بررسی کنیم ذخیره‌سازی رویداد خراب نشده باشد. برای هر state مشتق می‌توانیم پردازشگرهای دسته‌ای و جریانی سازندهٔ آن از لاگ رویداد را دوباره اجرا کنیم تا ببینیم به نتیجهٔ یکسانی می‌رسیم یا حتی مشتق‌سازی افزونه‌ای را به‌صورت موازی اجرا کنیم.

Dataflow قطعی و به‌خوبی تعریف‌شده، دیباگ و ردیابی اجرای سیستم را نیز آسان‌تر می‌کند تا مشخص شود چرا کاری را انجام داده است [۴، ۶۹]. اگر رویداد غیرمنتظره‌ای رخ دهد، داشتن قابلیت تشخیصی برای بازتولید دقیق شرایط منتهی به آن ارزشمند است؛ نوعی قابلیت دیباگ با سفر در زمان.

دوباره استدلال سرتاسری

اگر نتوانیم کاملاً اعتماد کنیم که تک‌تک اجزای سیستم از خرابی مصون‌اند — هر قطعهٔ سخت‌افزار بدون نقص و هر قطعهٔ نرم‌افزار بدون باگ است — دست‌کم باید یکپارچگی داده را دوره‌ای بررسی کنیم. اگر بررسی نکنیم، تا زمانی که خرابی خیلی دیر شده و به بخشی downstream آسیب زده از آن باخبر نمی‌شویم؛ در آن نقطه پیدا کردن مشکل بسیار دشوارتر و پرهزینه‌تر خواهد بود.

بررسی یکپارچگی سیستم‌های داده بهتر است به‌شکل سرتاسری انجام شود (به «استدلال سرتاسری برای پایگاه‌های داده» در صفحه ۵۱۶ مراجعه کنید): هرچه سیستم‌های بیشتری را در بررسی یکپارچگی بگنجانیم، فرصت‌های کمتری وجود دارد که خرابی در مرحله‌ای از فرایند دیده نشود. اگر بتوانیم درستی سرتاسری کل خط لولهٔ دادهٔ مشتق را بررسی کنیم، هر دیسک، شبکه، سرویس و الگوریتم موجود در مسیر به‌طور ضمنی در بررسی گنجانده می‌شود.

داشتن بررسی‌های پیوستهٔ یکپارچگی سرتاسری اعتماد شما را به درستی سیستم افزایش می‌دهد و در نتیجه اجازه می‌دهد سریع‌تر حرکت کنید [۷۰]. ممیزی مانند آزمایش خودکار احتمال یافتن سریع باگ‌ها را افزایش می‌دهد و بنابراین ریسک آسیب ناشی از تغییر سیستم یا فناوری ذخیره‌سازی جدید را کاهش می‌دهد. اگر از ایجاد تغییر نترسید، می‌توانید برنامه را برای پاسخ به نیازهای در حال تغییر بسیار بهتر تکامل دهید.

ابزارهای سیستم‌های دادهٔ ممیزی‌پذیر

در حال حاضر سیستم‌های دادهٔ زیادی auditability را دغدغه‌ای سطح‌بالا نمی‌دانند. بعضی برنامه‌ها مکانیزم ممیزی خود را پیاده‌سازی می‌کنند، مثلاً با لاگ کردن همهٔ تغییرات در جدول ممیزی جداگانه، اما تضمین یکپارچگی لاگ ممیزی و state پایگاه داده همچنان دشوار است. لاگ تراکنش را می‌توان با امضای دوره‌ای توسط ماژول امنیت سخت‌افزاری در برابر دست‌کاری مقاوم کرد، اما این تضمین نمی‌کند تراکنش‌های درست از ابتدا وارد لاگ شده باشند.

جالب خواهد بود اگر ابزارهای رمزنگاری برای اثبات یکپارچگی سیستم به‌شکلی مقاوم در برابر طیف گسترده‌ای از مشکلات سخت‌افزاری و نرم‌افزاری و حتی اقدامات احتمالاً مخرب به کار روند. رمزارزها، blockchainها و فناوری‌های دفتر کل توزیع‌شده مانند Bitcoin،‏ Ethereum،‏ Ripple،‏ Stellar و موارد مختلف دیگر برای کاوش این حوزه پدید آمده‌اند [۷۱، ۷۲، ۷۳].

صلاحیت اظهارنظر دربارهٔ شایستگی این فناوری‌ها به‌عنوان ارز یا مکانیزم توافق بر قراردادها را ندارم. با این حال، از دید سیستم‌های داده ایده‌های جالبی در بر دارند. اساساً پایگاه‌های دادهٔ توزیع‌شده‌ای با مدل داده و مکانیزم تراکنش‌اند که رپلیکاهای مختلف آن‌ها می‌توانند توسط سازمان‌هایی میزبانی شوند که به یکدیگر اعتماد ندارند. رپلیکاها پیوسته یکپارچگی یکدیگر را بررسی و با پروتکل consensus دربارهٔ تراکنش‌هایی که باید اجرا شوند توافق می‌کنند.

تا حدی نسبت به جنبه‌های تحمل خطای Byzantine این فناوری‌ها تردید دارم (به «خطاهای Byzantine» در صفحه ۳۰۴ مراجعه کنید) و تکنیک proof of work — مانند استخراج Bitcoin — را به‌شدت اتلاف‌گر می‌دانم. Throughput تراکنش Bitcoin نسبتاً پایین است، هرچند دلایل آن بیشتر سیاسی و اقتصادی‌اند تا فنی. با این حال، جنبه‌های بررسی یکپارچگی جالب‌اند.

ممیزی رمزنگاری‌شده و بررسی یکپارچگی اغلب به درخت‌های Merkle‏ [۷۴] متکی‌اند؛ درخت‌هایی از hash که می‌توان از آن‌ها برای اثبات کارآمد وجود رکوردی در مجموعه‌داده و چند کار دیگر استفاده کرد. خارج از هیاهوی رمزارزها، شفافیت گواهی فناوری امنیتی‌ای است که برای بررسی اعتبار گواهی‌های TLS/SSL به درخت‌های Merkle متکی است [۷۵، ۷۶].

می‌توانم تصور کنم الگوریتم‌های بررسی یکپارچگی و ممیزی مانند الگوریتم‌های شفافیت گواهی و دفتر کل توزیع‌شده به‌طور کلی در سیستم‌های داده رایج‌تر شوند. برای رساندن مقیاس‌پذیری آن‌ها به سطح سیستم‌های بدون ممیزی رمزنگاری‌شده و پایین نگه داشتن جریمهٔ عملکرد به کار بیشتری نیاز است. اما فکر می‌کنم این حوزه در آینده ارزش دنبال کردن دارد.

انجام کار درست

در بخش پایانی این کتاب می‌خواهم یک گام به عقب بردارم. در سراسر کتاب طیف گسترده‌ای از معماری‌های مختلف سیستم داده را بررسی، مزایا و معایبشان را ارزیابی و تکنیک‌های ساخت برنامه‌های قابل اعتماد، مقیاس‌پذیر و قابل نگهداری را کاوش کردیم. با این حال، بخش مهم و بنیادی‌ای از بحث را کنار گذاشتیم که اکنون می‌خواهم آن را تکمیل کنم.

هر سیستم برای هدفی ساخته می‌شود؛ هر اقدام ما پیامدهای عمدی و غیرعمدی دارد. هدف ممکن است به‌سادگی پول درآوردن باشد، اما پیامدهای آن برای جهان می‌تواند بسیار فراتر از هدف اولیه برود. ما، مهندسان سازندهٔ این سیستم‌ها، مسئولیم آن پیامدها را با دقت بررسی و آگاهانه تصمیم بگیریم می‌خواهیم در چه جهانی زندگی کنیم.

دربارهٔ داده به‌عنوان چیزی انتزاعی صحبت می‌کنیم، اما به یاد داشته باشید بسیاری از مجموعه‌داده‌ها دربارهٔ انسان‌ها هستند: رفتار، علایق و هویتشان. باید با چنین داده‌ای انسانی و محترمانه رفتار کنیم. کاربران نیز انسان‌اند و کرامت انسانی بر هر چیز مقدم است.

توسعهٔ نرم‌افزار به‌شکل فزاینده‌ای انتخاب‌های اخلاقی مهمی را در بر می‌گیرد. دستورالعمل‌هایی برای کمک به مهندسان نرم‌افزار در پیمودن این مسائل وجود دارد، مانند آیین‌نامهٔ اخلاق و عملکرد حرفه‌ای مهندسی نرم‌افزار ACM‏ [۷۷]، اما در عمل به‌ندرت دربارهٔ آن‌ها بحث یا آن‌ها را اعمال و اجرا می‌کنیم. در نتیجه، مهندسان و مدیران محصول گاهی نگرشی بسیار بی‌ملاحظه نسبت به حریم خصوصی و پیامدهای منفی احتمالی محصولاتشان دارند [۷۸، ۷۹، ۸۰].

فناوری به‌خودی خود خوب یا بد نیست؛ مهم این است چگونه استفاده می‌شود و چه اثری بر انسان‌ها دارد. این دربارهٔ سیستم نرم‌افزاری مانند موتور جستجو به همان اندازه درست است که دربارهٔ سلاحی مانند تفنگ. فکر می‌کنم کافی نیست مهندسان نرم‌افزار فقط روی فناوری تمرکز و پیامدهای آن را نادیده بگیرند: مسئولیت اخلاقی نیز بر دوش ماست. استدلال اخلاقی دشوار است، اما آن‌قدر مهم است که نمی‌توان نادیده‌اش گرفت.

تحلیل پیش‌بینی‌کننده

برای نمونه، تحلیل پیش‌بینی‌کننده بخش بزرگی از هیاهوی «Big Data» است. استفاده از تحلیل داده برای پیش‌بینی آب‌وهوا یا گسترش بیماری‌ها یک چیز است [۸۱]؛ پیش‌بینی اینکه آیا محکومی احتمالاً دوباره مرتکب جرم می‌شود، متقاضی وامی احتمالاً نکول می‌کند یا مشتری بیمه‌ای احتمالاً ادعاهای پرهزینه خواهد داشت چیز دیگری است. موارد اخیر اثری مستقیم بر زندگی افراد دارند.

طبیعتاً شبکه‌های پرداخت می‌خواهند از تراکنش‌های متقلبانه جلوگیری کنند، بانک‌ها می‌خواهند از وام‌های بد دوری کنند، شرکت‌های هواپیمایی می‌خواهند جلوی هواپیماربایی را بگیرند و شرکت‌ها نمی‌خواهند افراد ناکارآمد یا غیرقابل اعتماد استخدام کنند. از دید آن‌ها، هزینهٔ فرصت تجاری ازدست‌رفته کم اما هزینهٔ وام بد یا کارمند مشکل‌ساز بسیار بیشتر است، پس طبیعی است سازمان‌ها محتاط باشند. در صورت تردید، بهتر است پاسخ منفی دهند.

با این حال، هرچه تصمیم‌گیری الگوریتمی گسترده‌تر شود، کسی که به‌درستی یا نادرستی توسط الگوریتمی پرریسک برچسب خورده ممکن است با تعداد زیادی از آن پاسخ‌های «نه» روبه‌رو شود. محرومیت نظام‌مند از شغل، سفر هوایی، پوشش بیمه، اجارهٔ ملک، خدمات مالی و دیگر جنبه‌های کلیدی جامعه آن‌قدر آزادی فرد را محدود می‌کند که «زندان الگوریتمی» نامیده شده است [۸۲]. در کشورهایی که به حقوق بشر احترام می‌گذارند، نظام عدالت کیفری فرد را تا اثبات جرم بی‌گناه می‌داند؛ در مقابل، سیستم‌های خودکار می‌توانند فرد را بدون هیچ مدرکی از گناه و با فرصت اندک برای اعتراض، نظام‌مند و خودسرانه از مشارکت در جامعه محروم کنند.

سوگیری و تبعیض

تصمیم الگوریتم لزوماً بهتر یا بدتر از تصمیم انسان نیست. هر فرد احتمالاً سوگیری‌هایی دارد، حتی اگر فعالانه برای مقابله با آن‌ها تلاش کند، و رویه‌های تبعیض‌آمیز می‌توانند در فرهنگ نهادینه شوند. امید می‌رود تصمیم‌گیری بر اساس داده، به‌جای ارزیابی ذهنی و غریزی انسان‌ها، منصفانه‌تر باشد و به افرادی که اغلب در سیستم سنتی نادیده گرفته می‌شوند فرصت بهتری بدهد [۸۳].

وقتی سیستم‌های تحلیل پیش‌بینی‌کننده توسعه می‌دهیم، صرفاً تصمیم انسانی را با استفاده از نرم‌افزار برای مشخص کردن قواعد پاسخ مثبت یا منفی خودکار نمی‌کنیم؛ حتی استنتاج خود قواعد را نیز به داده می‌سپاریم. با این حال، الگوهایی که این سیستم‌ها می‌آموزند مبهم‌اند: حتی اگر همبستگی‌ای در داده وجود داشته باشد، شاید ندانیم چرا. اگر ورودی الگوریتم سوگیری نظام‌مندی داشته باشد، سیستم به‌احتمال زیاد آن سوگیری را در خروجی خود می‌آموزد و تقویت می‌کند [۸۴].

در بسیاری کشورها، قوانین ضدتبعیض رفتار متفاوت با افراد بر اساس ویژگی‌های حفاظت‌شده‌ای مانند قومیت، سن، جنسیت، گرایش جنسی، ناتوانی یا باورها را ممنوع می‌کنند. می‌توان سایر ویژگی‌های دادهٔ فرد را تحلیل کرد، اما اگر با ویژگی‌های حفاظت‌شده هم‌بستگی داشته باشند چه؟ مثلاً در محله‌هایی با جداسازی نژادی، کد پستی یا حتی آدرس IP فرد پیش‌بینی‌کنندهٔ قدرتمندی برای نژاد است.

با این بیان، باور اینکه الگوریتمی بتواند دادهٔ سوگیری‌دار را به‌عنوان ورودی بگیرد و خروجی منصفانه و بی‌طرف تولید کند مضحک به نظر می‌رسد [۸۵]. با این حال، چنین باوری اغلب در سخنان طرفداران تصمیم‌گیری داده‌محور ضمنی است؛ نگرشی که با عبارت «یادگیری ماشین برای سوگیری مانند پول‌شویی است» به طنز کشیده شده است [۸۶].

سیستم‌های تحلیل پیش‌بینی‌کننده صرفاً گذشته را تعمیم می‌دهند؛ اگر گذشته تبعیض‌آمیز باشد، آن‌ها تبعیض را مدون می‌کنند. اگر می‌خواهیم آینده از گذشته بهتر باشد، به تخیل اخلاقی نیاز داریم و این چیزی است که فقط انسان‌ها می‌توانند فراهم کنند [۸۷]. داده و مدل باید ابزار ما باشند، نه ارباب ما.

مسئولیت و پاسخ‌گویی

تصمیم‌گیری خودکار پرسش مسئولیت و پاسخ‌گویی را مطرح می‌کند [۸۷]. اگر انسانی اشتباه کند، می‌توان او را پاسخ‌گو دانست و فرد متأثر از تصمیم می‌تواند اعتراض کند. الگوریتم‌ها نیز اشتباه می‌کنند، اما اگر خطا کنند چه کسی پاسخ‌گوست [۸۸]؟ وقتی خودروی خودران تصادف می‌کند چه کسی مسئول است؟ اگر الگوریتم خودکار امتیازدهی اعتباری به‌طور نظام‌مند علیه افراد نژاد یا دین مشخصی تبعیض کند، راه جبرانی وجود دارد؟ اگر تصمیم سیستم یادگیری ماشین شما زیر بازبینی قضایی قرار گیرد، می‌توانید برای قاضی توضیح دهید الگوریتم چگونه تصمیم گرفته است؟

مؤسسه‌های رتبه‌بندی اعتباری نمونه‌ای قدیمی از جمع‌آوری داده برای تصمیم‌گیری دربارهٔ انسان‌ها هستند. امتیاز اعتباری بد زندگی را دشوار می‌کند، اما دست‌کم معمولاً بر واقعیت‌های مرتبط با سابقهٔ واقعی وام‌گیری شخص استوار است و خطاهای سوابق را می‌توان اصلاح کرد، هرچند مؤسسه‌ها معمولاً این کار را آسان نمی‌کنند.

با این حال، الگوریتم‌های امتیازدهی مبتنی بر یادگیری ماشین معمولاً از دامنهٔ بسیار گسترده‌تری از ورودی‌ها استفاده می‌کنند و بسیار مبهم‌ترند؛ بنابراین درک چگونگی رسیدن به تصمیمی خاص و اینکه آیا با کسی ناعادلانه یا تبعیض‌آمیز رفتار شده دشوارتر است [۸۹].

امتیاز اعتباری خلاصه‌ای از این پرسش است: «در گذشته چگونه رفتار کردید؟» در حالی که تحلیل پیش‌بینی‌کننده معمولاً بر این اساس کار می‌کند: «چه کسی شبیه شماست و افراد شبیه شما در گذشته چگونه رفتار کردند؟» مقایسه با رفتار دیگران به‌معنای کلیشه‌سازی دربارهٔ افراد است، مثلاً بر اساس محل زندگی آن‌ها که جانشین نزدیکی برای نژاد و طبقهٔ اجتماعی‌اقتصادی است. تکلیف کسانی که در دستهٔ اشتباه قرار می‌گیرند چیست؟ افزون بر این، اگر تصمیم به‌دلیل دادهٔ اشتباه نادرست باشد، جبران آن تقریباً ناممکن است [۸۷].

بخش زیادی از داده ماهیتی آماری دارد؛ یعنی حتی اگر توزیع احتمال در مجموع درست باشد، موارد منفرد ممکن است کاملاً نادرست باشند. مثلاً اگر میانگین امید به زندگی در کشور شما ۸۰ سال باشد، به این معنا نیست که انتظار می‌رود در روز تولد ۸۰ سالگی‌تان ناگهان بمیرید. از میانگین و توزیع احتمال نمی‌توانید دربارهٔ سنی که فرد مشخصی تا آن زنده می‌ماند چیز زیادی بگویید. به‌همین ترتیب، خروجی سیستم پیش‌بینی احتمالی است و ممکن است در موارد منفرد کاملاً اشتباه باشد.

باور کورکورانه به برتری داده در تصمیم‌گیری نه‌تنها توهم‌آمیز، بلکه واقعاً خطرناک است. با گسترش تصمیم‌گیری داده‌محور باید بفهمیم چگونه الگوریتم‌ها را پاسخ‌گو و شفاف کنیم، چگونه از تقویت سوگیری‌های موجود جلوگیری کنیم و وقتی به‌ناچار اشتباه می‌کنند چگونه آن‌ها را اصلاح کنیم.

همچنین باید بفهمیم چگونه از استفادهٔ داده برای آسیب رساندن به انسان‌ها جلوگیری و در عوض ظرفیت مثبت آن را محقق کنیم. مثلاً تحلیل می‌تواند ویژگی‌های مالی و اجتماعی زندگی افراد را آشکار کند. از یک سو، می‌توان از این قدرت برای هدف‌گیری کمک و پشتیبانی به کسانی استفاده کرد که بیشترین نیاز را دارند. از سوی دیگر، گاهی کسب‌وکارهای استثمارگر از آن برای شناسایی افراد آسیب‌پذیر و فروش محصولات پرریسکی مانند وام‌های پرهزینه و مدارک دانشگاهی بی‌ارزش استفاده می‌کنند [۸۷، ۹۰].

حلقه‌های بازخورد

حتی در برنامه‌های پیش‌بینی‌کننده‌ای با اثرات فوری و گستردهٔ کمتر بر انسان‌ها، مانند سیستم‌های توصیه، مسائل دشواری وجود دارد که باید با آن‌ها روبه‌رو شویم. وقتی سرویس‌ها در پیش‌بینی محتوای موردعلاقهٔ کاربران خوب می‌شوند، شاید در نهایت فقط دیدگاه‌هایی را نشان دهند که کاربران از قبل با آن‌ها موافق‌اند و به اتاق‌های پژواکی منجر شوند که در آن کلیشه‌ها، اطلاعات نادرست و قطبی‌شدن رشد می‌کنند. اکنون نیز اثر اتاق‌های پژواک رسانه‌های اجتماعی را بر کارزارهای انتخاباتی می‌بینیم [۹۱].

وقتی تحلیل پیش‌بینی‌کننده بر زندگی انسان‌ها اثر می‌گذارد، مشکلاتی به‌ویژه زیان‌بار بر اثر حلقه‌های بازخورد خودتقویت‌کننده پدید می‌آیند. مثلاً کارفرمایانی را در نظر بگیرید که برای ارزیابی متقاضیان شغل از امتیاز اعتباری استفاده می‌کنند. ممکن است کارمند خوبی با امتیاز اعتباری مناسب باشید، اما ناگهان بر اثر بداقبالی خارج از کنترل خود دچار مشکل مالی شوید. با عقب افتادن پرداخت صورت‌حساب‌ها، امتیاز اعتباری‌تان آسیب می‌بیند و احتمال یافتن کار کمتر می‌شود. بیکاری شما را به سمت فقر می‌راند که امتیازهایتان را باز هم بدتر و یافتن کار را دشوارتر می‌کند [۸۷]. این مارپیچی نزولی ناشی از فرض‌های سمی است که پشت استتار دقت ریاضی و داده پنهان شده‌اند.

همیشه نمی‌توانیم زمان وقوع چنین حلقه‌های بازخوردی را پیش‌بینی کنیم. با این حال، بسیاری از پیامدها را می‌توان با اندیشیدن به کل سیستم — نه فقط بخش‌های رایانه‌ای، بلکه انسان‌هایی که با آن تعامل دارند — پیش‌بینی کرد؛ رویکردی که تفکر سیستمی نامیده می‌شود [۹۲]. می‌توانیم بفهمیم سیستم تحلیل داده به رفتارها، ساختارها یا ویژگی‌های مختلف چگونه پاسخ می‌دهد. آیا سیستم تفاوت‌های موجود میان افراد را تقویت و بزرگ‌تر می‌کند — مثلاً ثروتمندان را ثروتمندتر یا فقرا را فقیرتر می‌کند — یا می‌کوشد با بی‌عدالتی مقابله کند؟ حتی با بهترین نیت‌ها نیز باید مراقب پیامدهای ناخواسته باشیم.

حریم خصوصی و ردیابی

افزون بر مشکلات تحلیل پیش‌بینی‌کننده — یعنی استفاده از داده برای تصمیم‌گیری خودکار دربارهٔ انسان‌ها — خود جمع‌آوری داده نیز مشکلات اخلاقی دارد. رابطهٔ سازمان‌های جمع‌آوری‌کنندهٔ داده با افرادی که داده‌شان جمع‌آوری می‌شود چیست؟

وقتی سیستم فقط داده‌ای را ذخیره می‌کند که کاربر صریحاً وارد کرده، چون می‌خواهد سیستم آن را به‌شکلی مشخص ذخیره و پردازش کند، سیستم خدمتی برای کاربر انجام می‌دهد: کاربر مشتری است. اما وقتی فعالیت کاربر به‌عنوان اثر جانبی کارهای دیگرش ردیابی و لاگ می‌شود، رابطه کمتر روشن است. سرویس دیگر فقط کاری را انجام نمی‌دهد که کاربر گفته؛ بلکه منافعی از آن خود پیدا می‌کند که ممکن است با منافع کاربر تعارض داشته باشد.

ردیابی دادهٔ رفتاری برای قابلیت‌های روبه‌کاربر بسیاری از سرویس‌های آنلاین اهمیت فزاینده‌ای یافته است: ردیابی اینکه روی کدام نتایج جستجو کلیک می‌شود به بهبود رتبه‌بندی نتایج کمک می‌کند؛ توصیهٔ «افرادی که X را پسندیده‌اند Y را نیز پسندیده‌اند» به کاربران کمک می‌کند چیزهای جالب و مفید را کشف کنند؛ آزمایش‌های A/B و تحلیل جریان کاربر می‌توانند نشان دهند رابط کاربری چگونه بهبود یابد. این قابلیت‌ها به مقداری ردیابی رفتار کاربر نیاز دارند و کاربران از آن‌ها سود می‌برند.

با این حال، بسته به مدل کسب‌وکار شرکت، ردیابی اغلب همان‌جا متوقف نمی‌شود. اگر هزینهٔ سرویس با تبلیغات تأمین شود، تبلیغ‌کنندگان مشتریان واقعی‌اند و منافع کاربران در جایگاه دوم قرار می‌گیرد. دادهٔ ردیابی جزئی‌تر، تحلیل‌ها گسترده‌تر و داده برای مدت طولانی نگهداری می‌شود تا برای اهداف بازاریابی پروفایل دقیقی از هر فرد ساخته شود.

اکنون رابطهٔ شرکت با کاربری که داده‌اش جمع‌آوری می‌شود بسیار متفاوت به نظر می‌رسد. سرویس رایگانی به کاربر داده و او ترغیب می‌شود تا حد امکان با آن درگیر شود. ردیابی کاربر در درجهٔ اول در خدمت آن فرد نیست، بلکه نیاز تبلیغ‌کنندگانی را تأمین می‌کند که هزینهٔ سرویس را می‌پردازند. فکر می‌کنم این رابطه را می‌توان به‌درستی با واژه‌ای توصیف کرد که دلالت شوم‌تری دارد: نظارت.

نظارت

به‌عنوان آزمایش ذهنی، واژهٔ «داده» را با «نظارت» جایگزین کنید و ببینید آیا عبارت‌های رایج همچنان خوب به نظر می‌رسند [۹۳]. این چطور است: «در سازمان نظارت‌محور خود، جریان‌های بلادرنگ نظارت را جمع‌آوری و در انبار نظارت ذخیره می‌کنیم. دانشمندان نظارت ما از تحلیل پیشرفته و پردازش نظارت برای استخراج بینش‌های جدید استفاده می‌کنند.»

این آزمایش ذهنی برای کتاب طراحی برنامه‌های نظارت‌محور به‌طور غیرمعمولی جدلی است، اما فکر می‌کنم برای تأکید بر این نکته به کلمات قوی نیاز داریم. در تلاش برای واداشتن نرم‌افزار به «بلعیدن جهان» [۹۴]، بزرگ‌ترین زیرساخت نظارت جمعی تاریخ جهان را ساخته‌ایم. با شتاب به سوی اینترنت اشیا، به‌سرعت به جهانی نزدیک می‌شویم که هر فضای مسکونی دست‌کم یک میکروفون متصل به اینترنت دارد: گوشی‌های هوشمند، تلویزیون‌های هوشمند، دستیارهای کنترل‌شونده با صدا، دوربین‌های مراقبت کودک و حتی اسباب‌بازی‌هایی که از تشخیص گفتار ابری استفاده می‌کنند. بسیاری از این دستگاه‌ها سابقهٔ امنیتی وحشتناکی دارند [۹۵].

حتی تمامیت‌خواه‌ترین و سرکوبگرترین حکومت‌ها فقط می‌توانستند رؤیای گذاشتن میکروفون در هر اتاق و واداشتن همه به حمل همیشگی دستگاهی را داشته باشند که مکان و حرکاتشان را ردیابی کند. با این حال، ظاهراً داوطلبانه و حتی مشتاقانه خود را به این جهان نظارت کامل می‌سپاریم. تنها تفاوت این است که شرکت‌ها داده را جمع‌آوری می‌کنند، نه سازمان‌های دولتی [۹۶].

هر جمع‌آوری داده‌ای لزوماً نظارت به شمار نمی‌رود، اما بررسی آن از این منظر می‌تواند به درک رابطه‌مان با گردآورندهٔ داده کمک کند. چرا ظاهراً نظارت شرکت‌ها را با خوشحالی می‌پذیریم؟ شاید احساس می‌کنید چیزی برای پنهان کردن ندارید؛ به‌عبارت دیگر، کاملاً با ساختارهای قدرت موجود همسو هستید، به اقلیتی به‌حاشیه‌رانده‌شده تعلق ندارید و لازم نیست از آزار و تعقیب بترسید [۹۷]. همه چنین خوش‌اقبال نیستند. یا شاید به این دلیل است که هدف بی‌ضرر به نظر می‌رسد: نه اجبار و واداشتن آشکار به همرنگی، بلکه فقط توصیه‌های بهتر و بازاریابی شخصی‌تر. اما با توجه به بحث تحلیل پیش‌بینی‌کننده در بخش قبل، این تمایز کمتر روشن است.

هم‌اکنون حق بیمهٔ خودروهایی را می‌بینیم که به دستگاه‌های ردیابی داخل خودرو پیوند خورده و پوشش بیمهٔ درمانی‌ای که به پوشیدن دستگاه fitness tracker وابسته است. وقتی نظارت برای تعیین چیزهایی به کار رود که بر جنبه‌های مهم زندگی مانند پوشش بیمه یا اشتغال تسلط دارند، دیگر چندان بی‌ضرر به نظر نمی‌رسد.

افزون بر این، تحلیل داده می‌تواند چیزهایی به‌شکلی شگفت‌آور مداخله‌گرانه آشکار کند: مثلاً می‌توان از حسگر حرکت ساعت هوشمند یا fitness tracker با دقت نسبتاً خوب فهمید چه چیزی تایپ می‌کنید، از جمله رمزهای عبور [۹۸]. الگوریتم‌های تحلیل نیز فقط بهتر خواهند شد.

رضایت و آزادی انتخاب

ممکن است ادعا کنیم کاربران داوطلبانه استفاده از سرویسی را انتخاب می‌کنند که فعالیتشان را ردیابی می‌کند و با شرایط استفاده و سیاست حریم خصوصی موافقت کرده‌اند، پس به جمع‌آوری داده رضایت داده‌اند. حتی ممکن است ادعا کنیم کاربران در برابر داده‌ای که فراهم می‌کنند خدمتی ارزشمند می‌گیرند و ردیابی برای ارائهٔ سرویس ضروری است. بی‌تردید شبکه‌های اجتماعی، موتورهای جستجو و سرویس‌های رایگان آنلاین مختلف برای کاربران ارزشمندند، اما این استدلال مشکلاتی دارد.

کاربران دانش اندکی از داده‌ای دارند که وارد پایگاه‌های دادهٔ ما می‌کنند یا از چگونگی نگهداری و پردازش آن؛ و بیشتر سیاست‌های حریم خصوصی بیش از آنکه روشنگر باشند، موضوع را مبهم می‌کنند. کاربران بدون درک آنچه بر سر داده‌شان می‌آید نمی‌توانند رضایت معناداری بدهند. اغلب دادهٔ یک کاربر دربارهٔ افراد دیگری نیز اطلاعات می‌دهد که کاربر سرویس نیستند و با هیچ شرایطی موافقت نکرده‌اند. مجموعه‌داده‌های مشتقی که در این بخش کتاب بررسی کردیم — که ممکن است دادهٔ کل کاربران را با ردیابی رفتاری و منابع دادهٔ خارجی ترکیب کرده باشند — دقیقاً از انواع داده‌ای‌اند که کاربران نمی‌توانند درک معناداری از آن داشته باشند.

افزون بر این، داده طی فرایندی یک‌طرفه از کاربران استخراج می‌شود، نه در رابطه‌ای با دوسویگی واقعی و نه در مبادله‌ای منصفانه. گفت‌وگویی وجود ندارد و کاربران نمی‌توانند مذاکره کنند چه مقدار داده فراهم می‌کنند و در برابر آن چه خدمتی می‌گیرند: رابطهٔ سرویس و کاربر بسیار نامتقارن و یک‌سویه است. شرایط را سرویس تعیین می‌کند، نه کاربر [۹۹].

برای کاربری که به نظارت رضایت نمی‌دهد، تنها گزینهٔ واقعی این است که از سرویس استفاده نکند. اما این انتخاب نیز آزادانه نیست: اگر سرویسی چنان محبوب باشد که «بیشتر مردم آن را برای مشارکت اجتماعی پایه ضروری بدانند» [۹۹]، انتظار انصراف افراد از آن معقول نیست؛ استفاده از آن عملاً اجباری است. مثلاً در بیشتر جوامع اجتماعی غربی، حمل گوشی هوشمند، استفاده از Facebook برای معاشرت و استفاده از Google برای یافتن اطلاعات به هنجار تبدیل شده است. به‌ویژه وقتی سرویس اثر شبکه‌ای دارد، استفاده نکردن از آن برای افراد هزینهٔ اجتماعی دارد.

خودداری از استفادهٔ سرویس به‌دلیل ردیابی کاربران فقط برای شمار اندکی از افراد ممتاز ممکن است که زمان و دانش لازم برای درک سیاست حریم خصوصی را دارند و می‌توانند احتمال از دست دادن مشارکت اجتماعی یا فرصت‌های حرفه‌ای حاصل از حضور در سرویس را بپذیرند. برای افراد با موقعیت کمتر ممتاز، آزادی انتخاب معناداری وجود ندارد: نظارت گریزناپذیر می‌شود.

حریم خصوصی و استفاده از داده

گاهی افراد بر این اساس که بعضی کاربران حاضرند انواع چیزهای روزمره یا عمیقاً شخصی را دربارهٔ زندگی‌شان در رسانه‌های اجتماعی منتشر کنند ادعا می‌کنند «حریم خصوصی مرده است». اما این ادعا نادرست و بر سوءبرداشت از واژهٔ حریم خصوصی استوار است.

داشتن حریم خصوصی به‌معنای مخفی نگه داشتن همه‌چیز نیست؛ یعنی آزادی انتخاب اینکه چه چیزی را برای چه کسی آشکار کنید، چه چیزی را عمومی و چه چیزی را محرمانه نگه دارید. حق حریم خصوصی، حق تصمیم‌گیری است: هر فرد را قادر می‌کند در هر موقعیت تصمیم بگیرد کجای طیف محرمانگی تا شفافیت قرار داشته باشد [۹۹]. این جنبه‌ای مهم از آزادی و خودمختاری فرد است.

وقتی داده از طریق زیرساخت نظارت از افراد استخراج می‌شود، حقوق حریم خصوصی لزوماً از بین نمی‌روند، بلکه به گردآورندهٔ داده منتقل می‌شوند. شرکت‌های به‌دست‌آورندهٔ داده اساساً می‌گویند: «به ما اعتماد کنید تا با دادهٔ شما کار درست را انجام دهیم»؛ یعنی حق تصمیم‌گیری دربارهٔ آشکار یا محرمانه نگه داشتن اطلاعات از فرد به شرکت منتقل می‌شود.

شرکت‌ها نیز تصمیم می‌گیرند بخش بزرگی از نتیجهٔ این نظارت را محرمانه نگه دارند، زیرا آشکار کردن آن ترسناک تلقی می‌شود و به مدل کسب‌وکارشان — که به دانستن اطلاعات بیشتر از سایر شرکت‌ها دربارهٔ مردم متکی است — آسیب می‌زند. اطلاعات خصوصی کاربران فقط غیرمستقیم آشکار می‌شود، مثلاً در قالب ابزارهایی برای هدف‌گیری تبلیغات به گروه‌های خاصی از مردم، مانند مبتلایان به بیماری مشخص.

حتی اگر کاربران مشخص را نتوان از میان گروه هدف تبلیغ دوباره شناسایی کرد، آن‌ها اختیار خود را دربارهٔ افشای اطلاعات خصوصی‌ای مانند ابتلا به بیماری از دست داده‌اند. این کاربر نیست که بر اساس ترجیحات شخصی‌اش تصمیم می‌گیرد چه چیزی برای چه کسی آشکار شود؛ شرکت است که با هدف بیشینه کردن سود خود حق حریم خصوصی را اعمال می‌کند.

هدف بسیاری از شرکت‌ها این است که ترسناک به نظر نرسند؛ آن‌ها از پرسش دربارهٔ میزان واقعی مداخله‌گری جمع‌آوری داده دوری و در عوض بر مدیریت برداشت کاربران تمرکز می‌کنند. حتی این برداشت‌ها نیز اغلب بد مدیریت می‌شوند: مثلاً چیزی شاید از نظر واقعی درست باشد، اما اگر خاطرات دردناکی را تحریک کند کاربر ممکن است نخواهد آن را به یاد بیاورد [۱۰۰]. دربارهٔ هر نوع داده باید احتمال دهیم به‌شکلی اشتباه، نامطلوب یا نامناسب باشد و برای مدیریت آن شکست‌ها مکانیزم بسازیم. «نامطلوب» یا «نامناسب» بودن چیزی البته به قضاوت انسانی بستگی دارد؛ الگوریتم‌ها از چنین مفاهیمی بی‌خبرند، مگر اینکه صریحاً آن‌ها را برای احترام به نیازهای انسانی برنامه‌ریزی کنیم. ما به‌عنوان مهندسان این سیستم‌ها باید فروتن باشیم و چنین شکست‌هایی را بپذیریم و برایشان برنامه‌ریزی کنیم.

تنظیمات حریم خصوصی که به کاربر سرویس آنلاین اجازه می‌دهد کنترل کند سایر کاربران کدام جنبه‌های داده‌اش را ببینند، نقطهٔ شروعی برای بازگرداندن بخشی از کنترل به کاربران است. با این حال، صرف‌نظر از تنظیمات، خود سرویس همچنان دسترسی نامحدودی به داده دارد و آزاد است آن را به هر شکلی که سیاست حریم خصوصی اجازه دهد استفاده کند. حتی اگر سرویس وعده دهد داده را به اشخاص ثالث نفروشد، معمولاً حقوق نامحدودی برای پردازش و تحلیل داخلی داده به خود می‌دهد که اغلب بسیار فراتر از چیزی است که آشکارا برای کاربران قابل مشاهده است.

چنین انتقال بزرگ‌مقیاسی از حقوق حریم خصوصی افراد به شرکت‌ها در تاریخ بی‌سابقه است [۹۹]. نظارت همیشه وجود داشته، اما قبلاً پرهزینه و دستی بود، نه مقیاس‌پذیر و خودکار. روابط مبتنی بر اعتماد نیز همیشه وجود داشته‌اند، مثلاً میان بیمار و پزشک یا متهم و وکیل؛ اما در این موارد استفاده از داده به‌شدت زیر قیدهای اخلاقی، حقوقی و نظارتی بوده است. سرویس‌های اینترنتی انباشت حجم عظیمی از اطلاعات حساس بدون رضایت معنادار و استفاده از آن در مقیاسی عظیم بدون درک کاربران از آنچه بر سر دادهٔ خصوصی‌شان می‌آید را بسیار آسان کرده‌اند.

داده به‌عنوان دارایی و قدرت

چون دادهٔ رفتاری محصول جانبی تعامل کاربران با سرویس است، گاهی «دود خروجی داده» نامیده می‌شود؛ عبارتی که القا می‌کند داده مادهٔ زائد و بی‌ارزشی است. از این منظر، تحلیل رفتاری و پیش‌بینی‌کننده را می‌توان نوعی بازیافت دانست که از داده‌ای که در غیر این صورت دور ریخته می‌شد ارزش استخراج می‌کند.

درست‌تر است برعکس به موضوع نگاه کنیم: از دید اقتصادی، اگر تبلیغات هدفمند هزینهٔ سرویس را می‌پردازد، دادهٔ رفتاری افراد دارایی اصلی سرویس است. در این حالت، برنامه‌ای که کاربر با آن تعامل می‌کند فقط وسیله‌ای برای فریب کاربران است تا اطلاعات شخصی بیشتر و بیشتری را وارد زیرساخت نظارت کنند [۹۹]. خلاقیت انسانی دلپذیر و روابط اجتماعی‌ای که اغلب در سرویس‌های آنلاین بروز می‌یابند، بدبینانه توسط ماشین استخراج داده استثمار می‌شوند.

وجود data brokerها، صنعتی پنهان و مشکوک که داده‌های شخصی مداخله‌گرانه را عمدتاً برای اهداف بازاریابی می‌خرد، تجمیع، تحلیل و استنتاج می‌کند و دوباره می‌فروشد، ادعای ارزشمند بودن دادهٔ شخصی را تأیید می‌کند [۹۰]. ارزش‌گذاری startupها بر اساس تعداد کاربران و «تعداد چشم‌ها» — یعنی قابلیت نظارتشان — انجام می‌شود.

چون داده ارزشمند است، افراد زیادی آن را می‌خواهند. شرکت‌ها البته آن را می‌خواهند؛ اصلاً به همین دلیل از ابتدا جمع‌آوری‌اش می‌کنند. اما دولت‌ها نیز می‌خواهند آن را به دست آورند: با معامله‌های محرمانه، اجبار، الزام قانونی یا صرفاً دزدیدن [۱۰۱]. وقتی شرکتی ورشکست می‌شود، دادهٔ شخصی جمع‌آوری‌شده یکی از دارایی‌هایی است که فروخته می‌شود. افزون بر این، ایمن کردن داده دشوار است و رخنه‌ها با تناوبی نگران‌کننده اتفاق می‌افتند [۱۰۲].

این مشاهدات باعث شده منتقدان داده را نه فقط دارایی، بلکه «دارایی سمی» [۱۰۱] یا دست‌کم «مادهٔ خطرناک» [۱۰۳] بنامند. حتی اگر فکر کنیم توان جلوگیری از سوءاستفاده از داده را داریم، هرگاه داده جمع‌آوری می‌کنیم باید منافع را با ریسک افتادن آن در دست افراد نادرست بسنجیم: سیستم‌های رایانه‌ای ممکن است توسط مجرمان یا سرویس‌های اطلاعاتی متخاصم خارجی compromise شوند، افراد داخل سازمان ممکن است داده را افشا کنند، شرکت ممکن است به دست مدیران بی‌وجدانی بیفتد که ارزش‌های ما را ندارند یا کشور را حکومتی تصاحب کند که برای واداشتن ما به تحویل داده تردیدی ندارد.

هنگام جمع‌آوری داده نه‌فقط باید محیط سیاسی امروز، بلکه همهٔ دولت‌های احتمالی آینده را در نظر بگیریم. تضمینی نیست هر دولتی که در آینده انتخاب می‌شود به حقوق بشر و آزادی‌های مدنی احترام بگذارد؛ بنابراین «نصب فناوری‌هایی که شاید روزی ایجاد حکومت پلیسی را تسهیل کنند، بهداشت مدنی ضعیفی است» [۱۰۴].

ضرب‌المثل قدیمی می‌گوید: «دانش قدرت است.» افزون بر آن، «زیر نظر گرفتن دیگران و در عین حال دور ماندن از نظارت، یکی از مهم‌ترین شکل‌های قدرت است» [۱۰۵]. به همین دلیل حکومت‌های تمامیت‌خواه خواهان نظارت‌اند: نظارت قدرت کنترل جمعیت را به آن‌ها می‌دهد. اگرچه شرکت‌های فناوری امروز آشکارا در پی قدرت سیاسی نیستند، داده و دانش انباشته‌شده‌شان با این حال قدرت زیادی بر زندگی ما به آن‌ها می‌دهد که بخش بزرگی از آن مخفیانه و خارج از نظارت عمومی است [۱۰۶].

به یاد آوردن انقلاب صنعتی

داده ویژگی تعریف‌کنندهٔ عصر اطلاعات است. اینترنت، ذخیره‌سازی و پردازش داده و خودکارسازی نرم‌افزارمحور اثری بزرگ بر اقتصاد جهانی و جامعهٔ انسانی دارند. با تغییر زندگی روزمره و سازمان اجتماعی ما در دههٔ گذشته و احتمالاً ادامهٔ تغییرات بنیادی در دهه‌های آینده، مقایسه با انقلاب صنعتی به ذهن می‌رسد [۸۷، ۹۶].

انقلاب صنعتی با پیشرفت‌های بزرگ فناوری و کشاورزی رخ داد و در بلندمدت رشد اقتصادی پایدار و بهبود چشمگیر استانداردهای زندگی را به همراه آورد. با این حال، مشکلات بزرگی نیز داشت: آلودگی هوا — بر اثر دود و فرایندهای شیمیایی — و آب — بر اثر پسماند صنعتی و انسانی — وحشتناک بود. صاحبان کارخانه‌ها در تجمل زندگی می‌کردند، در حالی که کارگران شهری اغلب مسکن بسیار نامناسبی داشتند و ساعت‌های طولانی در شرایط سخت کار می‌کردند. کار کودکان رایج بود و کارهای خطرناک و کم‌درآمد در معدن را نیز شامل می‌شد.

زمان زیادی گذشت تا حفاظ‌هایی مانند مقررات حفاظت محیط زیست، پروتکل‌های ایمنی محل کار، ممنوعیت کار کودکان و بازرسی بهداشتی مواد غذایی برقرار شوند. بی‌تردید وقتی کارخانه‌ها دیگر نمی‌توانستند پسماند خود را در رودخانه بریزند، غذای آلوده بفروشند یا از کارگران سوءاستفاده کنند، هزینهٔ کسب‌وکار افزایش یافت. اما کل جامعه سود عظیمی برد و تعداد کمی از ما خواهان بازگشت به دوران پیش از آن مقررات‌اند [۸۷].

همان‌طور که انقلاب صنعتی روی تاریکی داشت که باید مدیریت می‌شد، گذار ما به عصر اطلاعات نیز مشکلات بزرگی دارد که باید با آن‌ها روبه‌رو شویم و حلشان کنیم. باور دارم جمع‌آوری و استفاده از داده یکی از آن مشکلات است. به گفتهٔ Bruce Schneier‏ [۹۶]:

داده مسئلهٔ آلودگی عصر اطلاعات و حفاظت از حریم خصوصی چالش زیست‌محیطی آن است. تقریباً همهٔ رایانه‌ها اطلاعات تولید می‌کنند. این اطلاعات باقی می‌ماند و می‌گندد. چگونگی برخورد ما با آن — چگونه مهارش می‌کنیم و چگونه دورش می‌ریزیم — برای سلامت اقتصاد اطلاعاتی ما حیاتی است. همان‌طور که امروز به دهه‌های آغازین عصر صنعتی نگاه می‌کنیم و در شگفتیم که نیاکانمان چگونه در شتاب ساخت جهانی صنعتی آلودگی را نادیده گرفتند، نوه‌هایمان نیز به ما در این دهه‌های آغازین عصر اطلاعات نگاه و دربارهٔ نحوهٔ مواجهه‌مان با چالش جمع‌آوری و سوءاستفاده از داده قضاوت خواهند کرد.

باید تلاش کنیم باعث افتخارشان باشیم.

قانون‌گذاری و خودتنظیمی

قوانین حفاظت از داده شاید بتوانند به حفظ حقوق افراد کمک کنند. مثلاً دستورالعمل اروپایی حفاظت از داده مصوب ۱۹۹۵ می‌گوید دادهٔ شخصی باید «برای اهداف مشخص، صریح و مشروع جمع‌آوری شود و سپس به‌شکلی ناسازگار با آن اهداف پردازش نشود» و همچنین داده باید «نسبت به اهدافی که برای آن جمع‌آوری می‌شود کافی، مرتبط و نه بیش از حد باشد» [۱۰۷].

با این حال، تردید وجود دارد که این قانون در فضای اینترنت امروز مؤثر باشد [۱۰۸]. این قواعد مستقیماً مخالف فلسفهٔ Big Data هستند که می‌خواهد جمع‌آوری داده را بیشینه کند، آن را با مجموعه‌داده‌های دیگر ترکیب کند و برای تولید بینش‌های تازه دست به آزمایش و کاوش بزند. کاوش یعنی استفاده از داده برای اهداف پیش‌بینی‌نشده که خلاف اهداف «مشخص و صریحی» است که کاربر برای آن رضایت داده — اگر اصلاً بتوانیم به‌شکلی معنادار از رضایت صحبت کنیم [۱۰۹]. مقررات به‌روزشده اکنون در حال توسعه‌اند [۸۹].

شرکت‌هایی که دادهٔ زیادی دربارهٔ انسان‌ها جمع‌آوری می‌کنند با مقررات مخالفت می‌کنند و آن را بار و مانعی بر سر نوآوری می‌دانند. این مخالفت تا حدی موجه است. مثلاً اشتراک‌گذاری دادهٔ پزشکی خطرهای روشنی برای حریم خصوصی دارد، اما فرصت‌های بالقوه‌ای نیز وجود دارد: اگر تحلیل داده بتواند به تشخیص بهتر یا یافتن درمان‌های بهتر کمک کند، چند مرگ قابل پیشگیری خواهد بود [۱۱۰]؟ مقررات بیش از حد ممکن است مانع چنین پیشرفت‌هایی شود. ایجاد تعادل میان چنین فرصت‌های بالقوه‌ای و ریسک‌ها دشوار است [۱۰۵].

در بنیادی‌ترین سطح، فکر می‌کنم صنعت فناوری دربارهٔ دادهٔ شخصی به تغییر فرهنگی نیاز دارد. باید کاربران را متریک‌هایی برای بهینه‌سازی ندانیم و به یاد آوریم آن‌ها انسان‌هایی شایستهٔ احترام، کرامت و اختیارند. باید شیوه‌های جمع‌آوری و پردازش داده را خودتنظیم کنیم تا اعتماد افرادی را که به نرم‌افزار ما وابسته‌اند ایجاد و حفظ کنیم [۱۱۱]. همچنین باید مسئولیت آموزش کاربران نهایی دربارهٔ چگونگی استفاده از داده‌شان را بپذیریم، نه اینکه آن‌ها را در تاریکی نگه داریم.

باید به هر فرد اجازه دهیم حریم خصوصی خود — یعنی کنترل بر دادهٔ خودش — را حفظ کند و این کنترل را با نظارت از او ندزدیم. حق فردی ما برای کنترل داده‌مان مانند محیط طبیعی پارک ملی است: اگر صریحاً از آن محافظت و مراقبت نکنیم، نابود خواهد شد. این تراژدی منابع مشترک خواهد بود و همهٔ ما از آن زیان می‌بینیم. نظارت فراگیر اجتناب‌ناپذیر نیست؛ هنوز می‌توانیم آن را متوقف کنیم.

اینکه دقیقاً چگونه به این هدف برسیم پرسشی باز است. برای شروع، نباید داده را برای همیشه نگه داریم؛ باید به‌محض آنکه دیگر لازم نیست پاک شود [۱۱۱، ۱۱۲]. پاک‌سازی داده با ایدهٔ تغییرناپذیری در تضاد است (به «محدودیت‌های تغییرناپذیری» در صفحه ۴۶۳ مراجعه کنید)، اما این مسئله قابل حل است. رویکرد امیدوارکننده‌ای که می‌بینم، اعمال کنترل دسترسی با پروتکل‌های رمزنگاری به‌جای تکیهٔ صرف بر سیاست است [۱۱۳، ۱۱۴]. در مجموع، تغییر فرهنگ و نگرش ضروری خواهد بود.

خلاصه

در این فصل رویکردهای جدید طراحی سیستم‌های داده را بررسی کردم و نظرات شخصی و حدس‌هایم دربارهٔ آینده را نیز گنجاندم. با این مشاهده آغاز کردیم که هیچ ابزار واحدی نمی‌تواند همهٔ موارد استفادهٔ ممکن را با کارایی پوشش دهد، بنابراین برنامه‌ها ناگزیر باید چند قطعه نرم‌افزار مختلف را برای رسیدن به اهدافشان ترکیب کنند. بررسی کردیم چگونه می‌توان مسئلهٔ یکپارچه‌سازی داده را با استفاده از پردازش دسته‌ای و جریان رویداد حل کرد تا تغییرات داده میان سیستم‌های مختلف جریان یابد.

در این رویکرد، بعضی سیستم‌ها به‌عنوان سیستم ثبت تعیین می‌شوند و داده‌های دیگر از طریق تبدیل‌ها از آن‌ها مشتق می‌شوند. به این شکل می‌توانیم ایندکس‌ها، نماهای مادی‌شده، مدل‌های یادگیری ماشین، خلاصه‌های آماری و موارد دیگر را نگه داریم. با ناهمگام و loosely coupled کردن این مشتق‌سازی‌ها و تبدیل‌ها، از گسترش مشکل یک بخش به قسمت‌های نامرتبط سیستم جلوگیری و استحکام و تحمل خطای کل سیستم افزایش می‌یابد.

بیان dataflowها به‌عنوان تبدیل یک مجموعه‌داده به مجموعه‌داده‌ای دیگر به تکامل برنامه نیز کمک می‌کند: اگر بخواهید یکی از مراحل پردازش را تغییر دهید — مثلاً ساختار ایندکس یا cache را عوض کنید — می‌توانید کد تبدیل جدید را روی کل مجموعه‌دادهٔ ورودی دوباره اجرا کنید تا خروجی دوباره مشتق شود. به‌همین ترتیب، اگر مشکلی پیش بیاید، می‌توانید کد را اصلاح و داده را برای بازیابی دوباره پردازش کنید.

این فرایندها بسیار شبیه کارهایی‌اند که پایگاه‌های داده از قبل در داخل انجام می‌دهند؛ بنابراین ایدهٔ برنامه‌های dataflow را به‌صورت باز کردن بستهٔ اجزای پایگاه داده و ساخت برنامه با ترکیب این اجزای loosely coupled بازتعریف کردیم.

State مشتق را می‌توان با مشاهدهٔ تغییرات دادهٔ زیرین به‌روز کرد. افزون بر آن، خود state مشتق نیز می‌تواند توسط مصرف‌کنندگان downstream مشاهده شود. حتی می‌توانیم این dataflow را تا دستگاه کاربر نهایی که داده را نمایش می‌دهد ادامه دهیم و به این ترتیب رابط‌های کاربری‌ای بسازیم که به‌شکل پویا برای بازتاب تغییرات داده به‌روز می‌شوند و آفلاین نیز به کار ادامه می‌دهند.

سپس بررسی کردیم چگونه تضمین کنیم همهٔ این پردازش‌ها در حضور خطا درست باقی بمانند. دیدیم تضمین‌های یکپارچگی قوی را می‌توان با پردازش ناهمگام رویداد و در مقیاس پیاده‌سازی کرد: با استفاده از شناسه‌های سرتاسری عملیات برای idempotent کردن عملیات و بررسی ناهمگام قیدها. کلاینت‌ها می‌توانند تا گذشتن بررسی منتظر بمانند، یا بدون انتظار ادامه دهند و ریسک نیاز به عذرخواهی بابت نقض قید را بپذیرند. این رویکرد بسیار مقیاس‌پذیرتر و مستحکم‌تر از روش سنتی تراکنش‌های توزیع‌شده است و با شیوهٔ عملی بسیاری از فرایندهای کسب‌وکار سازگاری دارد.

با ساختاربندی برنامه‌ها حول dataflow و بررسی ناهمگام قیدها می‌توانیم از بیشتر هماهنگی‌ها اجتناب کنیم و سیستم‌هایی بسازیم که یکپارچگی را حفظ می‌کنند، اما حتی در سناریوهای توزیع‌شدهٔ جغرافیایی و حضور خطا عملکرد خوبی دارند. سپس کمی دربارهٔ استفاده از ممیزی برای بررسی یکپارچگی داده و تشخیص خرابی صحبت کردیم.

در پایان یک گام عقب رفتیم و بعضی جنبه‌های اخلاقی ساخت برنامه‌های داده‌محور را بررسی کردیم. دیدیم اگرچه می‌توان از داده برای کار خیر استفاده کرد، می‌تواند آسیب قابل توجهی نیز وارد کند: گرفتن تصمیم‌های به‌ظاهر موجهی که به‌شدت بر زندگی افراد اثر می‌گذارند و اعتراض به آن‌ها دشوار است، ایجاد تبعیض و استثمار، عادی‌سازی نظارت و افشای اطلاعات خصوصی. همچنین با ریسک رخنهٔ داده روبه‌رو هستیم و ممکن است استفادهٔ خیرخواهانه از داده پیامدهای ناخواسته داشته باشد.

از آنجا که نرم‌افزار و داده چنین اثر بزرگی بر جهان دارند، ما مهندسان باید به یاد داشته باشیم مسئولیت داریم برای ساخت جهانی تلاش کنیم که می‌خواهیم در آن زندگی کنیم؛ جهانی که با انسان‌ها با انسانیت و احترام رفتار می‌کند. امیدوارم بتوانیم با همکاری یکدیگر در راه این هدف گام برداریم.

منابع

[1] Rachid Belaid: “Postgres Full-Text Search is Good Enough!,” rachbelaid.com, July 13, 2015.

[2] Philippe Ajoux, Nathan Bronson, Sanjeev Kumar, et al.: “Challenges to Adopting Stronger Consistency at Scale,” at 15th USENIX Workshop on Hot Topics in Operating Systems (HotOS), May 2015.

[3] Pat Helland and Dave Campbell: “Building on Quicksand,” at 4th Biennial Conference on Innovative Data Systems Research (CIDR), January 2009.

[4] Jessica Kerr: “Provenance and Causality in Distributed Systems,” blog.jessitron.com, September 25, 2016.

[5] Kostas Tzoumas: “Batch Is a Special Case of Streaming,” data-artisans.com, September 15, 2015.

[6] Shinji Kim and Robert Blafford: “Stream Windowing Performance Analysis: Concord and Spark Streaming,” concord.io, July 6, 2016.

[7] Jay Kreps: “The Log: What Every Software Engineer Should Know About Real-Time Data’s Unifying Abstraction,” engineering.linkedin.com, December 16, 2013.

[8] Pat Helland: “Life Beyond Distributed Transactions: An Apostate’s Opinion,” at 3rd Biennial Conference on Innovative Data Systems Research (CIDR), January 2007.

[9] “Great Western Railway (1835–1948),” Network Rail Virtual Archive, networkrail.co.uk.

[10] Jacqueline Xu: “Online Migrations at Scale,” stripe.com, February 2, 2017.

[11] Molly Bartlett Dishman and Martin Fowler: “Agile Architecture,” at O’Reilly Software Architecture Conference, March 2015.

[12] Nathan Marz and James Warren: Big Data: Principles and Best Practices of Scalable Real-Time Data Systems. Manning, 2015. ISBN: 978-1-617-29034-3

[13] Oscar Boykin, Sam Ritchie, Ian O’Connell, and Jimmy Lin: “Summingbird: A Framework for Integrating Batch and Online MapReduce Computations,” at 40th International Conference on Very Large Data Bases (VLDB), September 2014.

[14] Jay Kreps: “Questioning the Lambda Architecture,” oreilly.com, July 2, 2014.

[15] Raul Castro Fernandez, Peter Pietzuch, Jay Kreps, et al.: “Liquid: Unifying Nearline and Offline Big Data Integration,” at 7th Biennial Conference on Innovative Data Systems Research (CIDR), January 2015.

[16] Dennis M. Ritchie and Ken Thompson: “The UNIX Time-Sharing System,” Communications of the ACM, volume 17, number 7, pages 365–375, July 1974. doi:10.1145/361011.361061

[17] Eric A. Brewer and Joseph M. Hellerstein: “CS262a: Advanced Topics in Computer Systems,” lecture notes, University of California, Berkeley, cs.berkeley.edu, August 2011.

[18] Michael Stonebraker: “The Case for Polystores,” wp.sigmod.org, July 13, 2015.

[19] Jennie Duggan, Aaron J. Elmore, Michael Stonebraker, et al.: “The BigDAWG Polystore System,” ACM SIGMOD Record, volume 44, number 2, pages 11–16, June 2015. doi:10.1145/2814710.2814713

[20] Patrycja Dybka: “Foreign Data Wrappers for PostgreSQL,” vertabelo.com, March 24, 2015.

[21] David B. Lomet, Alan Fekete, Gerhard Weikum, and Mike Zwilling: “Unbundling Transaction Services in the Cloud,” at 4th Biennial Conference on Innovative Data Systems Research (CIDR), January 2009.

[22] Martin Kleppmann and Jay Kreps: “Kafka, Samza and the Unix Philosophy of Distributed Data,” IEEE Data Engineering Bulletin, volume 38, number 4, pages 4–14, December 2015.

[23] John Hugg: “Winning Now and in the Future: Where VoltDB Shines,” voltdb.com, March 23, 2016.

[24] Frank McSherry, Derek G. Murray, Rebecca Isaacs, and Michael Isard: “Differential Dataflow,” at 6th Biennial Conference on Innovative Data Systems Research (CIDR), January 2013.

[25] Derek G Murray, Frank McSherry, Rebecca Isaacs, et al.: “Naiad: A Timely Dataflow System,” at 24th ACM Symposium on Operating Systems Principles (SOSP), pages 439–455, November 2013. doi:10.1145/2517349.2522738

[26] Gwen Shapira: “We have a bunch of customers who are implementing ‘database inside-out’ concept and they all ask ‘is anyone else doing it? are we crazy?’” twitter.com, July 28, 2016.

[27] Martin Kleppmann: “Turning the Database Inside-out with Apache Samza,” at Strange Loop, September 2014.

[28] Peter Van Roy and Seif Haridi: Concepts, Techniques, and Models of Computer Programming. MIT Press, 2004. ISBN: 978-0-262-22069-9

[29] “Juttle Documentation,” juttle.github.io, 2016.

[30] Evan Czaplicki and Stephen Chong: “Asynchronous Functional Reactive Programming for GUIs,” at 34th ACM SIGPLAN Conference on Programming Language Design and Implementation (PLDI), June 2013. doi:10.1145/2491956.2462161

[31] Engineer Bainomugisha, Andoni Lombide Carreton, Tom van Cutsem, Stijn Mostinckx, and Wolfgang de Meuter: “A Survey on Reactive Programming,” ACM Computing Surveys, volume 45, number 4, pages 1–34, August 2013. doi:10.1145/2501654.2501666

[32] Peter Alvaro, Neil Conway, Joseph M. Hellerstein, and William R. Marczak: “Consistency Analysis in Bloom: A CALM and Collected Approach,” at 5th Biennial Conference on Innovative Data Systems Research (CIDR), January 2011.

[33] Felienne Hermans: “Spreadsheets Are Code,” at Code Mesh, November 2015.

[34] Dan Bricklin and Bob Frankston: “VisiCalc: Information from Its Creators,” danbricklin.com.

[35] D. Sculley, Gary Holt, Daniel Golovin, et al.: “Machine Learning: The High-Interest Credit Card of Technical Debt,” at NIPS Workshop on Software Engineering for Machine Learning (SE4ML), December 2014.

[36] Peter Bailis, Alan Fekete, Michael J Franklin, et al.: “Feral Concurrency Control: An Empirical Investigation of Modern Application Integrity,” at ACM International Conference on Management of Data (SIGMOD), June 2015. doi:10.1145/2723372.2737784

[37] Guy Steele: “Re: Need for Macros (Was Re: Icon),” email to ll1-discuss mailing list, people.csail.mit.edu, December 24, 2001.

[38] David Gelernter: “Generative Communication in Linda,” ACM Transactions on Programming Languages and Systems (TOPLAS), volume 7, number 1, pages 80–112, January 1985. doi:10.1145/2363.2433

[39] Patrick Th. Eugster, Pascal A. Felber, Rachid Guerraoui, and Anne-Marie Kermarrec: “The Many Faces of Publish/Subscribe,” ACM Computing Surveys, volume 35, number 2, pages 114–131, June 2003. doi:10.1145/857076.857078

[40] Ben Stopford: “Microservices in a Streaming World,” at QCon London, March 2016.

[41] Christian Posta: “Why Microservices Should Be Event Driven: Autonomy vs Authority,” blog.christianposta.com, May 27, 2016.

[42] Alex Feyerke: “Say Hello to Offline First,” hood.ie, November 5, 2013.

[43] Sebastian Burckhardt, Daan Leijen, Jonathan Protzenko, and Manuel Fähndrich: “Global Sequence Protocol: A Robust Abstraction for Replicated Shared State,” at 29th European Conference on Object-Oriented Programming (ECOOP), July 2015. doi:10.4230/LIPIcs.ECOOP.2015.568

[44] Mark Soper: “Clearing Up React Data Management Confusion with Flux, Redux, and Relay,” medium.com, December 3, 2015.

[45] Eno Thereska, Damian Guy, Michael Noll, and Neha Narkhede: “Unifying Stream Processing and Interactive Queries in Apache Kafka,” confluent.io, October 26, 2016.

[46] Frank McSherry: “Dataflow as Database,” github.com, July 17, 2016.

[47] Peter Alvaro: “I See What You Mean,” at Strange Loop, September 2015.

[48] Nathan Marz: “Trident: A High-Level Abstraction for Realtime Computation,” blog.twitter.com, August 2, 2012.

[49] Edi Bice: “Low Latency Web Scale Fraud Prevention with Apache Samza, Kafka and Friends,” at Merchant Risk Council MRC Vegas Conference, March 2016.

[50] Charity Majors: “The Accidental DBA,” charity.wtf, October 2, 2016.

[51] Arthur J. Bernstein, Philip M. Lewis, and Shiyong Lu: “Semantic Conditions for Correctness at Different Isolation Levels,” at 16th International Conference on Data Engineering (ICDE), February 2000. doi:10.1109/ICDE.2000.839387

[52] Sudhir Jorwekar, Alan Fekete, Krithi Ramamritham, and S. Sudarshan: “Automating the Detection of Snapshot Isolation Anomalies,” at 33rd International Conference on Very Large Data Bases (VLDB), September 2007.

[53] Kyle Kingsbury: Jepsen blog post series, aphyr.com, 2013–2016.

[54] Michael Jouravlev: “Redirect After Post,” theserverside.com, August 1, 2004.

[55] Jerome H. Saltzer, David P. Reed, and David D. Clark: “End-to-End Arguments in System Design,” ACM Transactions on Computer Systems, volume 2, number 4, pages 277–288, November 1984. doi:10.1145/357401.357402

[56] Peter Bailis, Alan Fekete, Michael J. Franklin, et al.: “Coordination-Avoiding Database Systems,” Proceedings of the VLDB Endowment, volume 8, number 3, pages 185–196, November 2014.

[57] Alex Yarmula: “Strong Consistency in Manhattan,” blog.twitter.com, March 17, 2016.

[58] Douglas B Terry, Marvin M Theimer, Karin Petersen, et al.: “Managing Update Conflicts in Bayou, a Weakly Connected Replicated Storage System,” at 15th ACM Symposium on Operating Systems Principles (SOSP), pages 172–182, December 1995. doi:10.1145/224056.224070

[59] Jim Gray: “The Transaction Concept: Virtues and Limitations,” at 7th International Conference on Very Large Data Bases (VLDB), September 1981.

[60] Hector Garcia-Molina and Kenneth Salem: “Sagas,” at ACM International Conference on Management of Data (SIGMOD), May 1987. doi:10.1145/38713.38742

[61] Pat Helland: “Memories, Guesses, and Apologies,” blogs.msdn.com, May 15, 2007.

[62] Yoongu Kim, Ross Daly, Jeremie Kim, et al.: “Flipping Bits in Memory Without Accessing Them: An Experimental Study of DRAM Disturbance Errors,” at 41st Annual International Symposium on Computer Architecture (ISCA), June 2014. doi:10.1145/2678373.2665726

[63] Mark Seaborn and Thomas Dullien: “Exploiting the DRAM Rowhammer Bug to Gain Kernel Privileges,” googleprojectzero.blogspot.co.uk, March 9, 2015.

[64] Jim N. Gray and Catharine van Ingen: “Empirical Measurements of Disk Failure Rates and Error Rates,” Microsoft Research, MSR-TR-2005-166, December 2005.

[65] Annamalai Gurusami and Daniel Price: “Bug #73170: Duplicates in Unique Secondary Index Because of Fix of Bug#68021,” bugs.mysql.com, July 2014.

[66] Gary Fredericks: “Postgres Serializability Bug,” github.com, September 2015.

[67] Xiao Chen: “HDFS DataNode Scanners and Disk Checker Explained,” blog.cloudera.com, December 20, 2016.

[68] Jay Kreps: “Getting Real About Distributed System Reliability,” blog.empathybox.com, March 19, 2012.

[69] Martin Fowler: “The LMAX Architecture,” martinfowler.com, July 12, 2011.

[70] Sam Stokes: “Move Fast with Confidence,” blog.samstokes.co.uk, July 11, 2016.

[71] “Sawtooth Lake Documentation,” Intel Corporation, intelledger.github.io, 2016.

[72] Richard Gendal Brown: “Introducing R3 Corda™: A Distributed Ledger Designed for Financial Services,” gendal.me, April 5, 2016.

[73] Trent McConaghy, Rodolphe Marques, Andreas Müller, et al.: “BigchainDB: A Scalable Blockchain Database,” bigchaindb.com, June 8, 2016.

[74] Ralph C. Merkle: “A Digital Signature Based on a Conventional Encryption Function,” at CRYPTO ’87, August 1987. doi:10.1007/3-540-48184-2_32

[75] Ben Laurie: “Certificate Transparency,” ACM Queue, volume 12, number 8, pages 10-19, August 2014. doi:10.1145/2668152.2668154

[76] Mark D. Ryan: “Enhanced Certificate Transparency and End-to-End Encrypted Mail,” at Network and Distributed System Security Symposium (NDSS), February 2014. doi:10.14722/ndss.2014.23379

[77] “Software Engineering Code of Ethics and Professional Practice,” Association for Computing Machinery, acm.org, 1999.

[78] François Chollet: “Software development is starting to involve important ethical choices,” twitter.com, October 30, 2016.

[79] Igor Perisic: “Making Hard Choices: The Quest for Ethics in Machine Learning,” engineering.linkedin.com, November 2016.

[80] John Naughton: “Algorithm Writers Need a Code of Conduct,” theguardian.com, December 6, 2015.

[81] Logan Kugler: “What Happens When Big Data Blunders?,” Communications of the ACM, volume 59, number 6, pages 15–16, June 2016. doi:10.1145/2911975

[82] Bill Davidow: “Welcome to Algorithmic Prison,” theatlantic.com, February 20, 2014.

[83] Don Peck: “They’re Watching You at Work,” theatlantic.com, December 2013.

[84] Leigh Alexander: “Is an Algorithm Any Less Racist Than a Human?” theguardian.com, August 3, 2016.

[85] Jesse Emspak: “How a Machine Learns Prejudice,” scientificamerican.com, December 29, 2016.

[86] Maciej Cegłowski: “The Moral Economy of Tech,” idlewords.com, June 2016.

[87] Cathy O’Neil: Weapons of Math Destruction: How Big Data Increases Inequality and Threatens Democracy. Crown Publishing, 2016. ISBN: 978-0-553-41881-1

[88] Julia Angwin: “Make Algorithms Accountable,” nytimes.com, August 1, 2016.

[89] Bryce Goodman and Seth Flaxman: “European Union Regulations on Algorithmic Decision-Making and a ‘Right to Explanation’,” arXiv:1606.08813, August 31, 2016.

[90] “A Review of the Data Broker Industry: Collection, Use, and Sale of Consumer Data for Marketing Purposes,” Staff Report, United States Senate Committee on Commerce, Science, and Transportation, commerce.senate.gov, December 2013.

[91] Olivia Solon: “Facebook’s Failure: Did Fake News and Polarized Politics Get Trump Elected?” theguardian.com, November 10, 2016.

[92] Donella H. Meadows and Diana Wright: Thinking in Systems: A Primer. Chelsea Green Publishing, 2008. ISBN: 978-1-603-58055-7

[93] Daniel J. Bernstein: “Listening to a ‘big data’/‘data science’ talk,” twitter.com, May 12, 2015.

[94] Marc Andreessen: “Why Software Is Eating the World,” The Wall Street Journal, 20 August 2011.

[95] J. M. Porup: “‘Internet of Things’ Security Is Hilariously Broken and Getting Worse,” arstechnica.com, January 23, 2016.

[96] Bruce Schneier: Data and Goliath: The Hidden Battles to Collect Your Data and Control Your World. W. W. Norton, 2015. ISBN: 978-0-393-35217-7

[97] The Grugq: “Nothing to Hide,” grugq.tumblr.com, April 15, 2016.

[98] Tony Beltramelli: “Deep-Spying: Spying Using Smartwatch and Deep Learning,” Masters Thesis, IT University of Copenhagen, December 2015. Available at arxiv.org/abs/1512.05616

[99] Shoshana Zuboff: “Big Other: Surveillance Capitalism and the Prospects of an Information Civilization,” Journal of Information Technology, volume 30, number 1, pages 75–89, April 2015. doi:10.1057/jit.2015.5

[100] Carina C. Zona: “Consequences of an Insightful Algorithm,” at GOTO Berlin, November 2016.

[101] Bruce Schneier: “Data Is a Toxic Asset, So Why Not Throw It Out?,” schneier.com, March 1, 2016.

[102] John E. Dunn: “The UK’s 15 Most Infamous Data Breaches,” techworld.com, November 18, 2016.

[103] Cory Scott: “Data is not toxic - which implies no benefit - but rather hazardous material, where we must balance need vs. want,” twitter.com, March 6, 2016.

[104] Bruce Schneier: “Mission Creep: When Everything Is Terrorism,” schneier.com, July 16, 2013.

[105] Lena Ulbricht and Maximilian von Grafenstein: “Big Data: Big Power Shifts?,” Internet Policy Review, volume 5, number 1, March 2016. doi:10.14763/2016.1.406

[106] Ellen P. Goodman and Julia Powles: “Facebook and Google: Most Powerful and Secretive Empires We’ve Ever Known,” theguardian.com, September 28, 2016.

[107] Directive 95/46/EC on the protection of individuals with regard to the processing of personal data and on the free movement of such data, Official Journal of the European Communities No. L 281/31, eur-lex.europa.eu, November 1995.

[108] Brendan Van Alsenoy: “Regulating Data Protection: The Allocation of Responsibility and Risk Among Actors Involved in Personal Data Processing,” Thesis, KU Leuven Centre for IT and IP Law, August 2016.

[109] Michiel Rhoen: “Beyond Consent: Improving Data Protection Through Consumer Protection Law,” Internet Policy Review, volume 5, number 1, March 2016. doi:10.14763/2016.1.404

[110] Jessica Leber: “Your Data Footprint Is Affecting Your Life in Ways You Can’t Even Imagine,” fastcoexist.com, March 15, 2016.

[111] Maciej Cegłowski: “Haunted by Data,” idlewords.com, October 2015.

[112] Sam Thielman: “You Are Not What You Read: Librarians Purge User Data to Protect Privacy,” theguardian.com, January 13, 2016.

[113] Conor Friedersdorf: “Edward Snowden’s Other Motive for Leaking,” theatlantic.com, May 13, 2014.

[114] Phillip Rogaway: “The Moral Character of Cryptographic Work,” Cryptology ePrint 2015/1162, December 2015.

واژه‌نامه

توجه داشته باشید که تعریف‌های این واژه‌نامه کوتاه و ساده‌اند و هدفشان انتقال ایدهٔ اصلی است، نه بیان همهٔ ظرافت‌های یک اصطلاح. برای جزئیات بیشتر، ارجاع‌های متن اصلی را دنبال کنید.

ناهمگام (asynchronous)
منتظر نماندن برای تکمیل کاری—برای مثال، ارسال داده از طریق شبکه به گره‌ای دیگر—و فرض نکردن هیچ مدت مشخصی برای انجام آن. بنگرید به «همانندسازی همگام در برابر ناهمگام» در صفحهٔ 153، «شبکه‌های همگام در برابر ناهمگام» در صفحهٔ 284 و «مدل سیستم و واقعیت» در صفحهٔ 306.

اتمی (atomic)

  1. در زمینهٔ عملیات هم‌روند: عملیاتی که گویی در یک لحظهٔ واحد اثر می‌گذارد؛ بنابراین فرایند هم‌روند دیگری هرگز آن را در وضعیت «نیمه‌تمام» نمی‌بیند. همچنین بنگرید به جداسازی.
  2. در زمینهٔ تراکنش‌ها: گروه‌بندی مجموعه‌ای از نوشتن‌ها که حتی در صورت بروز خطا باید یا همگی ثبت شوند یا همگی بازگردانده شوند. بنگرید به «اتمی‌بودن» در صفحهٔ 223 و «ثبت اتمی و ثبت دوفازی (2PC)» در صفحهٔ 354.

پس‌فشار (backpressure)
واداشتن فرستندهٔ داده به کاهش سرعت، زیرا گیرنده نمی‌تواند هم‌پای آن پیش برود. با نام کنترل جریان نیز شناخته می‌شود. بنگرید به «سامانه‌های پیام‌رسانی» در صفحهٔ 441.

پردازش دسته‌ای (batch process)
محاسبه‌ای که مجموعه‌ای ثابت و معمولاً بزرگ از داده را به‌عنوان ورودی می‌گیرد و بدون تغییر ورودی، داده‌ای دیگر به‌عنوان خروجی تولید می‌کند. بنگرید به فصل 10.

کران‌دار (bounded)
دارای حد بالا یا اندازه‌ای معلوم. برای نمونه دربارهٔ تأخیر شبکه (بنگرید به «مهلت‌های زمانی و تأخیرهای بی‌کران» در صفحهٔ 281) و مجموعه‌داده‌ها (بنگرید به مقدمهٔ فصل 11) به‌کار می‌رود.

خطای بیزانسی (Byzantine fault)
گره‌ای که به شیوه‌ای دلخواه و نادرست رفتار می‌کند؛ برای مثال، پیام‌های متناقض یا مخرب برای گره‌های دیگر می‌فرستد. بنگرید به «خطاهای بیزانسی» در صفحهٔ 304.

حافظهٔ نهان (cache)
مؤلفه‌ای که داده‌های اخیراً استفاده‌شده را برای سریع‌تر کردن خواندن‌های بعدی همان داده نگه می‌دارد. معمولاً کامل نیست؛ بنابراین داده‌ای که در حافظهٔ نهان وجود ندارد باید از ذخیره‌گاه زیربنایی و کندتری واکشی شود که نسخهٔ کاملی از داده را دارد.

قضیهٔ CAP (CAP theorem)
نتیجه‌ای نظری که به‌طور گسترده بد فهمیده شده و در عمل سودمند نیست. بنگرید به «قضیهٔ CAP» در صفحهٔ 336.

علیت (causality)
وابستگی میان رویدادهایی که وقتی چیزی در سامانه «پیش از» چیز دیگری رخ می‌دهد پدید می‌آید؛ برای مثال، رویداد بعدی پاسخی به رویداد قبلی است، بر آن بنا می‌شود یا باید با توجه به آن فهمیده شود. بنگرید به «رابطهٔ پیش‌از و هم‌روندی» در صفحهٔ 186 و «ترتیب‌دهی و علیت» در صفحهٔ 339.

اجماع (consensus)
مسئله‌ای بنیادی در رایانش توزیع‌شده که در آن چند گره باید بر چیزی توافق کنند؛ برای مثال، اینکه کدام گره رهبر خوشهٔ پایگاه داده باشد. این مسئله بسیار دشوارتر از چیزی است که در نگاه نخست به نظر می‌رسد. بنگرید به «اجماع تحمل‌پذیر در برابر خطا» در صفحهٔ 364.

انبار داده (data warehouse)
پایگاه داده‌ای که داده‌های چند سامانهٔ OLTP متفاوت در آن ترکیب و برای تحلیل آماده شده‌اند. بنگرید به «انبارش داده» در صفحهٔ 91.

اعلانی (declarative)
توصیف ویژگی‌هایی که چیزی باید داشته باشد، بدون تعیین گام‌های دقیق دستیابی به آن. در پرس‌وجوها، بهینه‌ساز پرس‌وجو یک پرس‌وجوی اعلانی را می‌گیرد و بهترین شیوهٔ اجرای آن را تعیین می‌کند. بنگرید به «زبان‌های پرس‌وجوی داده» در صفحهٔ 42.

ناهنجارسازی (denormalize)
افزودن مقداری افزونگی یا تکرار به مجموعه‌داده‌ای نرمال‌شده—معمولاً به‌شکل حافظهٔ نهان یا نمایه—برای سریع‌تر کردن خواندن‌ها. مقدار ناهنجارشده نوعی نتیجهٔ ازپیش‌محاسبه‌شدهٔ پرس‌وجو، شبیه نمای مادی‌شده است. بنگرید به «عملیات تک‌شیئی و چندشیئی» در صفحهٔ 228 و «استخراج چند نما از یک گزارش رویداد» در صفحهٔ 461.

دادهٔ مشتق‌شده (derived data)
مجموعه‌داده‌ای که با فرایندی تکرارپذیر از داده‌ای دیگر ساخته می‌شود و در صورت لزوم می‌توان آن فرایند را دوباره اجرا کرد. دادهٔ مشتق‌شده معمولاً برای سریع‌تر کردن نوع خاصی از دسترسی خواندنی لازم است. نمایه‌ها، حافظه‌های نهان و نماهای مادی‌شده نمونه‌های آن‌اند. بنگرید به مقدمهٔ بخش سوم.

قطعی (deterministic)
صفت تابعی که با ورودی یکسان همیشه خروجی یکسان تولید می‌کند؛ بنابراین نمی‌تواند به اعداد تصادفی، ساعت روز، ارتباط شبکه‌ای یا عوامل پیش‌بینی‌ناپذیر دیگر وابسته باشد.

توزیع‌شده (distributed)
در حال اجرا روی چند گره متصل از طریق شبکه. ویژگی آن شکست‌های جزئی است: ممکن است بخشی از سامانه خراب باشد و بخش‌های دیگر همچنان کار کنند، و نرم‌افزار اغلب نمی‌تواند دقیقاً تشخیص دهد چه چیزی خراب شده است. بنگرید به «خطاها و شکست‌های جزئی» در صفحهٔ 274.

ماندگار (durable)
ذخیره‌سازی داده به‌گونه‌ای که باور دارید حتی با بروز خطاهای گوناگون از دست نخواهد رفت. بنگرید به «ماندگاری» در صفحهٔ 226.

ETL
استخراج–تبدیل–بارگذاری. فرایند استخراج داده از پایگاه دادهٔ مبدأ، تبدیل آن به قالبی مناسب‌تر برای پرس‌وجوهای تحلیلی و بارگذاری آن در انبار داده یا سامانهٔ پردازش دسته‌ای. بنگرید به «انبارش داده» در صفحهٔ 91.

جابجایی پس از خرابی (failover)
در سامانه‌های تک‌رهبر، فرایند انتقال نقش رهبری از یک گره به گره‌ای دیگر. بنگرید به «رسیدگی به قطعی گره‌ها» در صفحهٔ 156.

تحمل‌پذیر در برابر خطا (fault-tolerant)
توانایی بازیابی خودکار هنگام بروز مشکل، مانند از کار افتادن ماشین یا پیوند شبکه. بنگرید به «قابلیت اطمینان» در صفحهٔ 6.

کنترل جریان (flow control)
بنگرید به پس‌فشار.

پیرو (follower)
همتایی که نوشتن‌های مشتریان را مستقیماً نمی‌پذیرد و فقط تغییرات دادهٔ دریافتی از رهبر را پردازش می‌کند. با نام‌های ثانویه، برده، همتای خواندنی یا آماده‌به‌کار گرم نیز شناخته می‌شود. بنگرید به «رهبران و پیروان» در صفحهٔ 152.

جست‌وجوی تمام‌متن (full-text search)
جست‌وجوی متن با کلیدواژه‌های دلخواه، اغلب همراه با قابلیت‌هایی مانند تطبیق واژه‌های با املای مشابه یا مترادف‌ها. نمایهٔ تمام‌متن نوعی نمایهٔ ثانویه برای پشتیبانی از چنین پرس‌وجوهایی است. بنگرید به «جست‌وجوی تمام‌متن و نمایه‌های فازی» در صفحهٔ 88.

گراف (graph)
ساختار داده‌ای شامل رأس‌ها—چیزهایی که می‌توان به آن‌ها ارجاع داد و گره یا موجودیت نیز نامیده می‌شوند—و یال‌ها—اتصال‌های میان رأس‌ها که رابطه یا کمان نیز نام دارند. بنگرید به «مدل‌های دادهٔ گراف‌مانند» در صفحهٔ 49.

هش (hash)
تابعی که ورودی را به عددی ظاهراً تصادفی تبدیل می‌کند. ورودی یکسان همیشه همان عدد را می‌دهد. دو ورودی متفاوت به احتمال زیاد خروجی‌های متفاوت دارند، هرچند ممکن است خروجی یکسانی تولید کنند که برخورد نامیده می‌شود. بنگرید به «پارتیشن‌بندی بر اساس هش کلید» در صفحهٔ 203.

ایدِمپوتنت (idempotent)
صفت عملیاتی که می‌توان آن را با ایمنی تکرار کرد؛ اجرای چندبارهٔ آن همان اثری را دارد که اجرای یک‌باره‌اش. بنگرید به «ایدِمپوتنس» در صفحهٔ 478.

نمایه (index)
ساختار داده‌ای که امکان جست‌وجوی کارآمد همهٔ رکوردهای دارای مقداری مشخص در فیلدی معین را فراهم می‌کند. بنگرید به «ساختارهای داده‌ای که به پایگاه دادهٔ شما نیرو می‌دهند» در صفحهٔ 70.

جداسازی (isolation)
در زمینهٔ تراکنش‌ها، میزان دخالت تراکنش‌های هم‌زمان در یکدیگر را توصیف می‌کند. جداسازی سریال‌پذیر قوی‌ترین تضمین‌ها را فراهم می‌کند، اما سطوح ضعیف‌تر نیز استفاده می‌شوند. بنگرید به «جداسازی» در صفحهٔ 225.

پیوند (join)
کنار هم آوردن رکوردهایی که وجه مشترکی دارند. رایج‌ترین حالت زمانی است که رکوردی به رکوردی دیگر ارجاع دارد—کلید خارجی، ارجاع سند یا یال گراف—و پرس‌وجو باید رکورد مقصد را دریافت کند. بنگرید به «روابط چندبه‌یک و چندبه‌چند» در صفحهٔ 33 و «پیوندها و گروه‌بندی سمت کاهش» در صفحهٔ 403.

رهبر (leader)
وقتی داده یا خدمتی روی چند گره همانندسازی شده است، رهبر همتای تعیین‌شده‌ای است که اجازهٔ ایجاد تغییر دارد. ممکن است با پروتکل انتخاب شود یا مدیر آن را دستی برگزیند. با نام اصلی یا master نیز شناخته می‌شود. بنگرید به «رهبران و پیروان» در صفحهٔ 152.

خطی‌پذیر (linearizable)
رفتار کردن چنان‌که گویی فقط یک نسخه از داده در سامانه وجود دارد و با عملیات اتمی به‌روزرسانی می‌شود. بنگرید به «خطی‌پذیری» در صفحهٔ 324.

محلی‌بودن (locality)
بهینه‌سازی کارایی با قرار دادن چند قطعه داده در یک محل، هنگامی که اغلب هم‌زمان لازم می‌شوند. بنگرید به «محلی‌بودن داده برای پرس‌وجوها» در صفحهٔ 41.

قفل (lock)
سازوکاری برای اطمینان از اینکه تنها یک رشته، گره یا تراکنش می‌تواند به چیزی دسترسی داشته باشد و دیگر متقاضیان باید تا آزاد شدن قفل منتظر بمانند. بنگرید به «قفل‌گذاری دوفازی (2PL)» در صفحهٔ 257 و «رهبر و قفل» در صفحهٔ 301.

گزارش (log)
فایلی فقط‌افزودنی برای ذخیرهٔ داده. گزارش پیش‌نوشتن موتور ذخیره‌سازی را در برابر خرابی مقاوم می‌کند («قابل‌اعتماد کردن B-treeها»، صفحهٔ 82)، موتور گزارش‌ساخت‌یافته از گزارش به‌عنوان قالب اصلی ذخیره‌سازی استفاده می‌کند («SSTableها و LSM-treeها»، صفحهٔ 76)، گزارش همانندسازی نوشتن‌ها را از رهبر به پیروان کپی می‌کند («رهبران و پیروان»، صفحهٔ 152) و گزارش رویداد می‌تواند جریان داده را نمایش دهد («گزارش‌های پارتیشن‌بندی‌شده»، صفحهٔ 446).

مادی‌سازی (materialize)
اجرای مشتاقانهٔ محاسبه و نوشتن نتیجهٔ آن، برخلاف محاسبهٔ درخواستی. بنگرید به «تجمیع: مکعب‌های داده و نماهای مادی‌شده» در صفحهٔ 101 و «مادی‌سازی وضعیت میانی» در صفحهٔ 419.

گره (node)
نمونه‌ای از نرم‌افزار در حال اجرا روی یک رایانه که برای انجام کاری از طریق شبکه با گره‌های دیگر ارتباط برقرار می‌کند.

نرمال‌شده (normalized)
ساختاریافته به‌گونه‌ای که افزونگی یا تکرار وجود نداشته باشد. در پایگاه دادهٔ نرمال‌شده، با تغییر داده تنها لازم است یک محل را تغییر دهید، نه نسخه‌های متعدد را. بنگرید به «روابط چندبه‌یک و چندبه‌چند» در صفحهٔ 33.

OLAP
پردازش تحلیلی برخط. الگوی دسترسی‌ای که با تجمیع—مانند شمارش، جمع و میانگین—روی تعداد زیادی رکورد مشخص می‌شود. بنگرید به «پردازش تراکنش یا تحلیل؟» در صفحهٔ 90.

OLTP
پردازش تراکنش برخط. الگوی دسترسی‌ای شامل پرس‌وجوهای سریع که تعداد اندکی رکورد، معمولاً نمایه‌شده با کلید، را می‌خوانند یا می‌نویسند. بنگرید به «پردازش تراکنش یا تحلیل؟» در صفحهٔ 90.

پارتیشن‌بندی (partitioning)
تقسیم مجموعه‌داده یا محاسبه‌ای که برای یک ماشین بیش از حد بزرگ است به بخش‌های کوچک‌تر و پخش آن‌ها میان چند ماشین. شاردینگ نیز نامیده می‌شود. بنگرید به فصل 6.

صدک (percentile)
شیوه‌ای برای سنجش توزیع مقادیر با شمارش تعداد مقادیر بالاتر یا پایین‌تر از یک آستانه. برای مثال، صدک 95 زمان پاسخ در یک دوره، زمان t است که 95٪ درخواست‌ها در کمتر از t و 5٪ در زمانی بیشتر تکمیل می‌شوند. بنگرید به «توصیف کارایی» در صفحهٔ 13.

کلید اصلی (primary key)
مقداری—معمولاً عدد یا رشته—که یک رکورد را به‌طور یکتا شناسایی می‌کند. در بسیاری از برنامه‌ها هنگام ایجاد رکورد، سامانه آن را ترتیبی یا تصادفی تولید می‌کند و معمولاً کاربر تعیینش نمی‌کند. همچنین بنگرید به نمایهٔ ثانویه.

حدنصاب (quorum)
کمترین تعداد گره‌هایی که باید به عملیاتی رأی دهند تا موفق تلقی شود. بنگرید به «حدنصاب‌های خواندن و نوشتن» در صفحهٔ 179.

توازن مجدد (rebalance)
انتقال داده یا خدمات از یک گره به گره‌ای دیگر برای توزیع منصفانهٔ بار. بنگرید به «توازن مجدد پارتیشن‌ها» در صفحهٔ 209.

همانندسازی (replication)
نگهداری نسخه‌ای از دادهٔ یکسان روی چند گره یا همتا، تا در صورت دسترس‌ناپذیری یک گره، داده همچنان قابل‌دسترسی باشد. بنگرید به فصل 5.

شِما (schema)
توصیف ساختار داده، شامل فیلدها و نوع‌های دادهٔ آن. انطباق داده با شِما را می‌توان در نقاط گوناگون عمر داده بررسی کرد («انعطاف‌پذیری شِما در مدل سند»، صفحهٔ 39) و شِما می‌تواند در طول زمان تغییر کند (فصل 4).

نمایهٔ ثانویه (secondary index)
ساختار دادهٔ اضافی که در کنار ذخیره‌گاه اصلی نگهداری می‌شود و جست‌وجوی کارآمد رکوردهای منطبق با شرطی خاص را ممکن می‌کند. بنگرید به «ساختارهای نمایه‌سازی دیگر» در صفحهٔ 85 و «پارتیشن‌بندی و نمایه‌های ثانویه» در صفحهٔ 206.

سریال‌پذیر (serializable)
تضمینی که طبق آن چند تراکنش هم‌زمان چنان رفتار می‌کنند که گویی به‌ترتیبی سریالی و یکی‌یکی اجرا شده‌اند. بنگرید به «سریال‌پذیری» در صفحهٔ 251.

بی‌اشتراک (shared-nothing)
معماری‌ای که در آن گره‌های مستقل—هرکدام با CPU، حافظه و دیسک خود—از طریق شبکه‌ای معمولی متصل‌اند؛ برخلاف معماری‌های حافظه‌اشتراکی یا دیسک‌اشتراکی. بنگرید به مقدمهٔ بخش دوم.

چولگی (skew)

  1. نامتوازن بودن بار پارتیشن‌ها، به‌طوری‌که برخی درخواست یا دادهٔ بسیار و برخی بسیار کمتر دارند؛ نقاط داغ نیز نامیده می‌شود. بنگرید به «بارهای کاری چوله و کاهش نقاط داغ» در صفحهٔ 205 و «رسیدگی به چولگی» در صفحهٔ 407.
  2. ناهنجاری زمانی که رویدادها را با ترتیبی غیرمنتظره و غیرمتوالی نشان می‌دهد. بنگرید به چولگی خواندن در «جداسازی اسنپ‌شات و خواندن تکرارپذیر» صفحهٔ 237، چولگی نوشتن در «چولگی نوشتن و شبح‌ها» صفحهٔ 246 و چولگی ساعت در «مُهرهای زمانی برای ترتیب‌دهی رویدادها» صفحهٔ 291.

شکاف مغزی (split brain)
وضعیتی که دو گره هم‌زمان خود را رهبر می‌دانند و ممکن است تضمین‌های سامانه را نقض کنند. بنگرید به «رسیدگی به قطعی گره‌ها» در صفحهٔ 156 و «حقیقت را اکثریت تعریف می‌کند» در صفحهٔ 300.

رویهٔ ذخیره‌شده (stored procedure)
شیوه‌ای برای کدگذاری منطق تراکنش تا تمام آن روی سرور پایگاه داده و بدون رفت‌وبرگشت ارتباطی با مشتری اجرا شود. بنگرید به «اجرای سریالی واقعی» در صفحهٔ 252.

پردازش جریانی (stream process)
محاسبه‌ای پیوسته که جریانی پایان‌ناپذیر از رویدادها را به‌عنوان ورودی مصرف می‌کند و خروجی‌ای از آن به دست می‌آورد. بنگرید به فصل 11.

همگام (synchronous)
متضاد ناهمگام.

سامانهٔ ثبت (system of record)
سامانه‌ای که نسخهٔ اصلی و معتبر داده را نگه می‌دارد و منبع حقیقت نیز نامیده می‌شود. تغییرات ابتدا در آن نوشته می‌شوند و مجموعه‌داده‌های دیگر می‌توانند از آن مشتق شوند. بنگرید به مقدمهٔ بخش سوم.

مهلت زمانی (timeout)
یکی از ساده‌ترین روش‌های تشخیص خطا: مشاهدهٔ نبود پاسخ در مدتی معین. بااین‌حال نمی‌توان دانست مهلت زمانی ناشی از مشکل گره دوردست است یا شبکه. بنگرید به «مهلت‌های زمانی و تأخیرهای بی‌کران» در صفحهٔ 281.

ترتیب کل (total order)
شیوه‌ای برای مقایسهٔ چیزها—مانند مُهرهای زمانی—که همیشه می‌توان گفت کدام‌یک از دو چیز بزرگ‌تر و کدام کوچک‌تر است. ترتیبی که در آن بعضی چیزها مقایسه‌ناپذیرند، ترتیب جزئی نام دارد. بنگرید به «ترتیب علّی یک ترتیب کل نیست» در صفحهٔ 341.

تراکنش (transaction)
گروه‌بندی چند خواندن و نوشتن در یک واحد منطقی، برای ساده‌تر کردن رسیدگی به خطا و مسائل هم‌روندی. بنگرید به فصل 7.

ثبت دوفازی (two-phase commit یا 2PC)
الگوریتمی که تضمین می‌کند چند گره پایگاه داده یا همگی یک تراکنش را ثبت کنند یا همگی آن را لغو کنند. بنگرید به «ثبت اتمی و ثبت دوفازی (2PC)» در صفحهٔ 354.

قفل‌گذاری دوفازی (two-phase locking یا 2PL)
الگوریتمی برای دستیابی به جداسازی سریال‌پذیر که در آن تراکنش روی همهٔ داده‌هایی که می‌خواند یا می‌نویسد قفل می‌گیرد و آن‌ها را تا پایان تراکنش نگه می‌دارد. بنگرید به «قفل‌گذاری دوفازی (2PL)» در صفحهٔ 257.

بی‌کران (unbounded)
بدون حد بالا یا اندازهٔ معلوم؛ متضاد کران‌دار.

فهرست

A

لغوها (تراکنش‌ها)، 222، 224؛ در ثبت دوفازی، 356؛ کارایی کنترل هم‌روندی خوش‌بینانه، 266؛ تکرار تراکنش‌های لغوشده، 231
انتزاع، 21، 27، 222، 266، 321
مسیر دسترسی (در مدل شبکه‌ای)، 37، 60
پیچیدگی تصادفی، حذف، 21
پاسخ‌گویی، 535
ویژگی‌های ACID (تراکنش‌ها)، 90، 223؛ اتمی‌بودن، 223، 228؛ سازگاری، 224، 529؛ ماندگاری، 226؛ جداسازی، 225، 228
تأیید دریافت (پیام‌رسانی)، 445
همانندسازی active/active، بنگرید به همانندسازی چندرهبر
همانندسازی active/passive، بنگرید به همانندسازی رهبرمحور
ActiveMQ، 137، 444؛ پشتیبانی از تراکنش توزیع‌شده، 361
ActiveRecord، 30، 232
مدل کنشگر، 138؛ بنگرید نیز به پیام‌گذرانی؛ مقایسه با Pregel، 425؛ مقایسه با پردازش جریانی، 468
AMQP، 444؛ بنگرید نیز به سامانه‌های پیام‌رسانی؛ مقایسه با پیام‌رسانی گزارش‌محور، 448، 451؛ ترتیب پیام، 446
سامانه‌های هوافضا، 6، 10، 305، 372
تجمیع؛ مکعب‌های داده و نماهای مادی‌شده، 101؛ در پردازش دسته‌ای، 406؛ در پردازش جریانی، 466؛ تجمیع تأخیر دنباله، 16، 207؛ تقویت نوشتن، 84
Agile، 22؛ کمینه‌سازی برگشت‌ناپذیری، 414، 497؛ حرکت سریع‌تر با اطمینان، 532؛ فلسفهٔ Unix، 394
توافق، 365؛ بنگرید نیز به اجماع
Airflow، 402
Ajax، 131
Akka، 139
الگوریتم‌ها؛ درستی الگوریتم، 308؛ B-treeها، 79–83؛ برای سامانه‌های توزیع‌شده، 306؛ نمایه‌های هش، 72–75؛ mergesort، 76، 402، 405؛ درخت‌های سرخ‌سیاه، 78؛ SSTableها و LSM-treeها، 76–79
توپولوژی همانندسازی همه‌به‌همه، 175
AllegroGraph، 50
ALTER TABLE در SQL، 40، 111
Amazon Dynamo، 177
Amazon Web Services (AWS)، 8؛ Kinesis Streams، 448؛ قابلیت اطمینان شبکه، 279؛ پسامورتوم‌ها، 9؛ RedShift، 93؛ S3، 398؛ بررسی یکپارچگی داده، 530
تقویت؛ سوگیری، 534؛ شکست‌ها، 364، 495
تحلیل، 90؛ مقایسه با پردازش تراکنش، 91؛ انبارش داده؛ اجرای موازی پرس‌وجو در MPP، 415؛ تحلیل پیش‌بینانه؛ ارتباط با پردازش دسته‌ای، 411؛ شِماها، 93–95؛ جداسازی اسنپ‌شات، 238؛ تحلیل جریانی، 466؛ تحلیل رویدادهای فعالیت کاربر با MapReduce، 404
ضدحافظهٔ نهان، 89
ضدآنتروپی، 178
Apache: ActiveMQ، Avro، Beam، BookKeeper، Cassandra، CouchDB، Curator، Drill، Flink، Giraph، Hadoop، HAWQ، HBase، Helix، Hive، Impala، Jena، Kafka، Lucene، MADlib، Mahout، Oozie، Parquet، Qpid، Samza، Solr، Spark، Storm، Tajo، Tez، Thrift و ZooKeeper؛ بنگرید به مدخل هر محصول
Apama، 466
B-treeهای فقط‌افزودنی، 82، 242
فایل‌های فقط‌افزودنی، بنگرید به گزارش‌ها
APIها، 5، 27؛ پردازش دسته‌ای، 403؛ جریان تغییرات، 456؛ تراکنش توزیع‌شده، 361؛ پردازش گراف، 425؛ خدمات، 131–136؛ تکامل‌پذیری، 136؛ RESTful، 133؛ SOAP، 133
وضعیت برنامه، بنگرید به وضعیت
جست‌وجوی تقریبی، بنگرید به جست‌وجوی شباهت
ذخیره‌سازی بایگانی، 131
کمان‌ها، بنگرید به یال‌ها
میانگین حسابی، 14
ASCII، 119، 395
ASN.1، 127
شبکه‌های ناهمگام، 278، 553؛ مقایسه با همگام، 284؛ مدل رسمی، 307
همانندسازی ناهمگام، 154، 553؛ تشخیص تعارض، 172؛ از دست رفتن داده در جابجایی پس از خرابی، 157؛ خواندن از پیرو ناهمگام، 162
ATM، 285
انتشار اتمی، بنگرید به انتشار ترتیب کل
ساعت‌های اتمی (سزیمی)، 294، 295؛ بنگرید نیز به ساعت‌ها
اتمی‌بودن هم‌روندی، 553؛ increment-and-get اتمی، 351؛ compare-and-set، 245، 327؛ عملیات همانندسازی‌شده، 246؛ نوشتن‌ها، 243
اتمی‌بودن تراکنش، 223، 228، 553؛ ثبت اتمی، 353؛ اجتناب، 523، 528؛ مسدودکننده و نامسدودکننده، 359؛ پردازش جریانی، 360، 477؛ نگهداری دادهٔ مشتق‌شده، 453؛ تراکنش چندشیئی، 229؛ نوشتن تک‌شیئی، 230
ممیزی‌پذیری، 528–533؛ طراحی، 531؛ سامانه‌های خودممیز، 530؛ از راه تغییرناپذیری، 460؛ ابزارها، 532
دسترس‌پذیری، 8؛ بنگرید نیز به تحمل خطا؛ در CAP، 337؛ در SLA، 15
Avro، 122–127؛ تولید کد، 127؛ شِمای پویا، 126؛ فایل‌های ظرف شیء، 125، 131، 414؛ تعیین شِمای نویسنده به‌دست خواننده، 125؛ تکامل شِما، 123؛ در Hadoop، 414
awk، 391
AWS، بنگرید به Amazon Web Services
Azure، بنگرید به Microsoft

B

B-treeها، 79–83؛ گونه‌های فقط‌افزودنی/کپی‌هنگام‌نوشتن، 82، 242؛ ضریب انشعاب، 81؛ مقایسه با LSM-tree، 83–85؛ بازیابی خرابی، 82؛ رشد با شکافتن صفحه، 81؛ بهینه‌سازی، 82؛ شباهت به پارتیشن‌بندی پویا، 212
پس‌فشار، 441، 553؛ در TCP، 282
نسخه‌های پشتیبان؛ اسنپ‌شات پایگاه داده برای همانندسازی، 156؛ یکپارچگی، 530؛ جداسازی اسنپ‌شات، 238؛ در ETL، 405
سازگاری پس‌رو، 112
BASE در برابر ACID، 223
bash، 70، 395، 503
پردازش دسته‌ای، 28، 389–431، 553؛ ترکیب با جریان، معماری لامبدا، 497 و فناوری‌های یکپارچه، 498؛ مقایسه با MPP، 414–418؛ با جریان، 464؛ با Unix، 413–414؛ موتورهای جریان داده، 421–423؛ تحمل خطا، 406، 414، 422، 442؛ یکپارچه‌سازی داده، 494–498؛ گراف و تکرار، 424–426؛ APIها و زبان‌های سطح‌بالا، 403، 426–429؛ پیام‌رسانی گزارش‌محور، 451؛ نگهداری وضعیت مشتق‌شده، 495؛ MapReduce و فایل‌سیستم توزیع‌شده، 397–413؛ سنجش کارایی، 13، 390؛ خروجی‌ها، 411–413؛ ابزارهای Unix، 391–394
Bayou، 522
Beam، 498
سوگیری، 534
تودهٔ بزرگ گل‌آلود، 20
مدل دادهٔ Bigtable، 41، 99
کدگذاری دودویی، 115–128؛ Avro، 122–127؛ MessagePack، 116–117؛ Thrift و Protocol Buffers، 117–121؛ مبتنی بر شِما، 127؛ راه‌اندازهای شبکه، 128
رشته‌های دودویی در JSON و XML، 114
BinaryProtocol، 118
Bitcask، 72؛ بازیابی خرابی، 74
Bitcoin، 532؛ تحمل خطای بیزانسی، 305؛ اشکالات هم‌روندی صرافی‌ها، 233
نمایه‌های بیت‌مپ، 97
بلاک‌چین‌ها، 532؛ تحمل خطای بیزانسی، 305
ثبت اتمی مسدودکننده، 359
Bloom، 504
فیلتر Bloom، 79، 466
BookKeeper، 372
Bottled Water، 455
مجموعه‌داده‌های کران‌دار، 430، 439، 553؛ بنگرید نیز به پردازش دسته‌ای
تأخیرهای کران‌دار، 553؛ شبکه، 285؛ توقف فرایند، 298
پیوند هش انتشاری، 409
پیام‌رسانی بدون کارگزار، 442
Brubeck، 442
BTM، 356
مدل BSP، 425
ترافیک شبکهٔ انفجاری، 285
پردازش دادهٔ تجاری، 28، 90، 390
دنبالهٔ بایت، 112
خطاهای بیزانسی، 304–307، 553؛ سامانه‌های تحمل‌پذیر، 305، 532؛ مسئلهٔ ژنرال‌های بیزانسی، 304؛ الگوریتم‌های اجماع، 366

C

حافظه‌های نهان، 89، 553؛ و نماهای مادی‌شده، 101؛ دادهٔ مشتق‌شده، 386، 499–504؛ پایگاه داده به‌عنوان نهانگاه گزارش تراکنش، 460؛ CPU، 99، 338، 428؛ ابطال و نگهداری، 452، 467؛ خطی‌پذیری، 324
قضیهٔ CAP، 336–338، 554
Cascading، 419، 427؛ پیوند هش، 409؛ گردش کار، 403
شکست‌های آبشاری، 9، 214، 281
Cascalog، 60
Cassandra؛ خانواده‌ستون، 41، 99؛ فشرده‌سازی، 79؛ کلید اصلی مرکب، 204؛ gossip، 216؛ پارتیشن‌بندی هش، 203–205؛ آخرین‌نوشتن‌برنده، 186، 292؛ همانندسازی بی‌رهبر، 177؛ نبود خطی‌پذیری، 335؛ ذخیره‌سازی گزارش‌ساخت‌یافته، 78؛ چندمرکز داده، 184؛ پارتیشن‌بندی، 213؛ نمایهٔ ثانویه، 207؛ حدنصاب سهل‌گیرانه، 184
cat، 391
زمینهٔ علّی، 191؛ بنگرید نیز به وابستگی علّی
وابستگی‌های علّی، 186–191؛ ثبت، 191، 342، 494، 514؛ با ترتیب کل، 493؛ ترتیب علّی، 339؛ در تراکنش، 262؛ نمونهٔ پیام به دوستان، 494
علیت، 554؛ ترتیب علّی، 339–343؛ خطی‌پذیری، 342؛ ترتیب کل سازگار، 344، 345؛ اسنپ‌شات سازگار، 340؛ رابطهٔ پیش‌از، 186؛ تراکنش سریال‌پذیر، 262–265؛ ناسازگاری با ساعت، 292؛ ترتیب رویدادها، 493؛ نقض‌ها، 165، 176، 292، 340؛ ساعت همگام، 294
CEP، بنگرید به پردازش رویداد پیچیده
شفافیت گواهی، 532
همانندسازی زنجیره‌ای، 155؛ خواندن خطی‌پذیر، 351
ثبت تغییرات داده، 160، 454؛ API جریان تغییر، 456؛ مقایسه با منبع‌یابی رویداد، 457؛ پیاده‌سازی، 454؛ اسنپ‌شات اولیه، 455؛ فشرده‌سازی گزارش، 456
گزارش تغییرات، 460؛ ثبت تغییر داده، 454؛ وضعیت عملگر، 479؛ تریگرها، 455؛ پیوند جریانی، 474؛ فشرده‌سازی، 456؛ وضعیت مشتق‌شده، 452
Chaos Monkey، 7، 280
نقطهٔ وارسی؛ پردازش دسته‌ای، 422، 426؛ HPC، 275؛ جریان، 477، 523
مدل دادهٔ chronicle، 458
شبکهٔ مدارگزینی، 284
بافر حلقوی، 450
توپولوژی همانندسازی حلقوی، 175
دادهٔ کلیک‌استریم، 404
مشتریان؛ فراخوانی خدمات، 131؛ ارسال تغییر وضعیت، 512؛ مسیریابی درخواست، 214؛ دارای وضعیت و آفلاین، 170، 511
ساعت‌ها، 287–299؛ اتمی، 294، 295؛ فاصلهٔ اطمینان، 293–295؛ اسنپ‌شات جهانی، 294؛ منطقی؛ چولگی، 291–294، 334؛ تنظیم تدریجی، 289؛ همگام‌سازی و دقت، 289–291؛ GPS، 287، 290، 294، 295؛ ساعت روز و یکنواخت، 288؛ مُهرزنی رویداد، 471
رایانش ابری، 146، 275؛ کشف خدمت، 372؛ اختلال شبکه، 279؛ منابع اشتراکی، 284؛ قابلیت اطمینان تک‌ماشین، 8
نمایهٔ خوشه‌ای، 86
مدل CODASYL، 36؛ بنگرید نیز به مدل شبکه‌ای
تولید کد؛ Avro، 127؛ Thrift و Protocol Buffers، 118؛ WSDL، 133
ویرایش مشارکتی؛ همانندسازی چندرهبر، 170
خانوادهٔ ستون، 41، 99
ذخیره‌سازی ستونی، 95–101؛ فشرده‌سازی، 97؛ تفاوت با خانواده‌ستون، 99؛ پردازش دسته‌ای، 428؛ Parquet، 96، 131، 414؛ ترتیب مرتب‌سازی، 99–100؛ پردازش برداری، 99، 428؛ نوشتن، 101
CSV، بنگرید به مقادیر جداشده با ویرگول
CQRS، 462
فرمان‌ها در منبع‌یابی رویداد، 459
ثبت تراکنش، 222؛ ثبت اتمی، 354–355؛ جداسازی read committed، 234؛ ثبت سه‌فازی، 359؛ ثبت دوفازی، 355–359
عملیات جابجایی‌پذیر، 246
فشرده‌سازی؛ گزارش تغییرات، 456؛ وضعیت عملگر جریان، 479؛ ذخیره‌سازی گزارش‌ساخت‌یافته، 73، 79، 84
compare-and-set، 245، 327؛ قفل، 370؛ قید یکتایی، 331؛ انتشار ترتیب کل، 350؛ ارتباط با اجماع، 335، 350، 352، 374؛ تراکنش، 230
سازگاری قالب‌ها، 112، 128؛ خدمات، 136؛ پایگاه داده، 129–131؛ پیام‌گذرانی، 138
تراکنش جبرانی، 355، 461، 526
پردازش رویداد پیچیده، 465
پیچیدگی؛ مدل نظری، 310؛ پنهان‌سازی با انتزاع، 27؛ مدیریت نرم‌افزار، 20
کاربردهای محاسبه‌محور، 3، 275
نمایه‌های الحاقی، 87؛ Cassandra، 204
Concord، 466
هم‌روندی؛ مدل کنشگر، 138، 468؛ اشکالات جداسازی ضعیف، 233؛ حل تعارض، 171، 174؛ نوشتن هم‌زمان، 184–191؛ نوشتن دوگانه، 453؛ رابطهٔ پیش‌از، 186؛ سامانه‌های همانندسازی‌شده، 161–191، 324–338؛ به‌روزرسانی گم‌شده، 243؛ MVCC، 239؛ کنترل خوش‌بینانه، 261؛ ترتیب عملیات، 326، 341؛ کاهش با گزارش رویداد، 351، 462، 507؛ زمان و نسبیت، 187؛ جداسازی تراکنش، 225؛ چولگی نوشتن، 246–251
CRDTها، 174
تعارض‌ها؛ تشخیص، 172، 184؛ وابستگی علّی، 186، 342؛ اجماع، 368؛ سامانهٔ گزارش‌محور، 351، 521؛ غیرخطی‌پذیر، 343؛ SSI، 264؛ 2PC، 357، 364؛ حل خودکار، 174؛ لغو، 261؛ عذرخواهی، 527؛ همگرایی، 172–174؛ بی‌رهبر، 190؛ LWW، 186، 292؛ عملیات اتمی، 246؛ منطق سفارشی، 173؛ تعیین تعارض، 174، 522؛ چندرهبر، 171–175؛ به‌روزرسانی گم‌شده، 242–246؛ مادی‌سازی، 251؛ ترتیب عملیات، 339؛ چولگی نوشتن، 246–251
ازدحام شبکه؛ اجتناب، 282؛ دقت ساعت، 293؛ تأخیر صف، 282
اجماع، 321، 364–375، 554؛ الگوریتم‌ها، 366–368؛ جلوگیری از شکاف مغزی، 367؛ ایمنی و زنده‌بودن، 365؛ هزینه، 369؛ تراکنش توزیع‌شده، 352–375؛ 2PC، 354–359؛ XA، 361–364؛ ناممکن‌بودن، 353؛ عضویت و هماهنگی، 370–373؛ compare-and-set، 335، 350، 352، 374؛ همانندسازی، 155، 349؛ یکتایی، 521
سازگاری، 224، 524؛ میان پایگاه‌های داده، 157، 452، 462، 492؛ علّی، 339–348، 493؛ پیشوند سازگار، 165–167؛ اسنپ‌شات سازگار، 156، 237–242، 294، 455، 500؛ بازیابی خرابی، 82؛ قیدها؛ نهایی، 162، 322؛ ACID، 224، 529؛ CAP، 337؛ خطی‌پذیری، 324–338؛ خواندن یکنواخت، 164–165؛ نمایهٔ ثانویه، 231، 241، 354، 491، 500؛ تضمین ترتیب، 339–352؛ خواندن پس از نوشتن، 162–164؛ ترتیبی، 351؛ بهنگامی و یکپارچگی، 524؛ حدنصاب، 181، 334
هش سازگار، 204
خواندن پیشوند سازگار، 165
قیدهای پایگاه داده، 225، 248؛ بررسی ناهمگام، 526؛ پرهیز از هماهنگی، 527؛ ایدمپوتنس، 519؛ سامانهٔ گزارش‌محور، 521–524؛ چندپارتیشنی، 522؛ 2PC، 355، 357؛ اجماع، 374، 521؛ ترتیب رویداد، 347؛ خطی‌پذیری، 330
Consul، 372
مصرف‌کنندگان جریان پیام، 137، 440؛ پس‌فشار، 441؛ offset، 449؛ خرابی، 445، 449؛ fan-out، 11، 445، 448؛ توازن بار، 444، 448؛ عقب‌ماندن، 441، 450، 502
تعویض زمینه، 14، 297
همگرایی، 172–174، 322
هماهنگی؛ اجتناب، 527؛ میان مراکز داده، 168، 493؛ ترتیب میان پارتیشن، 256، 294، 348، 523؛ خدمات، 330، 370–373
هماهنگ‌کنندهٔ 2PC، 356؛ خرابی، 358؛ XA، 361–364؛ بازیابی، 363
کپی‌هنگام‌نوشتن، 82، 242
CORBA، 134
درستی، 6؛ ممیزی‌پذیری، 528–533؛ تحمل بیزانسی، 305، 532؛ شکست جزئی، 274؛ گزارش‌محور، 521–524؛ الگوریتم و مدل، 308؛ تراکنش جبرانی، 355؛ اجماع، 368؛ دادهٔ مشتق‌شده، 497، 531؛ دادهٔ تغییرناپذیر، 461؛ دادهٔ شخصی، 535، 540؛ زمان، 176، 289–295؛ تراکنش، 225، 515، 529؛ بهنگامی و یکپارچگی، 524–528
خرابی داده؛ تشخیص، 519، 530–533؛ حافظه، 529؛ تابش، 305؛ شکاف مغزی، 158، 302؛ جداسازی ضعیف، 233؛ اجماع، 366؛ دیسک، 227؛ WAL، 82؛ بازیابی، 414، 460
Couchbase؛ ماندگاری، 89؛ پارتیشن‌بندی هش، 203–204، 211؛ توازن مجدد، 213؛ مسیریابی، 216
CouchDB؛ B-tree، 242؛ خوراک تغییر، 456؛ مدل سند، 31؛ پیوند، 34؛ MapReduce، 46، 400؛ همانندسازی، 170، 173
نمایهٔ پوشاننده، 86
CPUها؛ انسجام نهانگاه و مانع حافظه، 338؛ نهان‌سازی و خط لوله، 99، 428؛ افزایش موازی‌سازی، 43
CREATE INDEX، 85، 500
مؤسسات رتبه‌بندی اعتباری، 535
Crunch، 419، 427؛ پیوندها، 408، 409؛ گردش کار، 403
رمزنگاری؛ دفاع در برابر مهاجم، 306؛ رمزگذاری و احراز هویت سرتاسری، 519، 543؛ اثبات یکپارچگی، 532
CSS، 44
CSV، 70، 114، 396
Curator، 330، 371
curl، 135، 397
پایداری مکان‌نما، 243
Cypher، 52؛ مقایسه با SPARQL، 59

D

خرابی داده، بنگرید به فساد داده
مکعب‌های داده، 102
قالب‌های داده، بنگرید به کدگذاری
یکپارچه‌سازی داده، 490–498، 543؛ دسته و جریان، 494–498؛ لامبدا، 497؛ وضعیت مشتق‌شده، 495؛ بازپردازش، 496؛ یکپارچه‌سازی فناوری‌ها، 498؛ جداسازی پایگاه داده، 499–515؛ مقایسه با فدراسیون، 501؛ ترکیب ابزارها، 490–494؛ دادهٔ مشتق‌شده در برابر تراکنش توزیع‌شده، 492؛ محدودیت ترتیب کل، 493؛ ثبت علیت، 493؛ استدلال دربارهٔ جریان داده، 491؛ نیاز، 385
دریاچهٔ داده، 415
مدل‌های داده، 27–64؛ گراف‌مانند، 49–63؛ Datalog، 60–63؛ گراف ویژگی، 50؛ RDF و triple-store، 55–59؛ زبان پرس‌وجو، 42–48؛ رابطه‌ای در برابر سند، 28–42
مقررات حفاظت داده، 542
سامانه‌های داده، 3؛ ملاحظات طراحی، 5؛ آینده، 489–544؛ درستی و یکپارچگی، 515–533؛ یکپارچه‌سازی، 490–498؛ جداسازی پایگاه داده، 499–515؛ ناهمگون، 452؛ نگهداشت‌پذیری، 18–22؛ خطاها، 221؛ قابلیت اطمینان، 6–10؛ مقیاس‌پذیری، 10–18؛ ساعت نامطمئن، 287–299
انبارش داده، 91–95، 554؛ در برابر دریاچه، 415؛ ETL، 92، 416، 452؛ همگام نگه‌داشتن سامانه‌ها، 452؛ شِما، 93؛ SCD، 476
برنامه‌های داده‌محور، 3
تراکنش توزیع‌شدهٔ داخلی پایگاه داده، 360، 364، 477
پایگاه‌های داده؛ بایگانی، 131؛ مقایسه با کارگزار پیام، 443؛ جریان داده، 129؛ استدلال سرتاسری، 519–520؛ بررسی یکپارچگی، 531؛ وارونه‌شده، 504؛ خروجی گردش دسته‌ای، 412؛ رابطه با جریان رویداد، 451–464؛ ثبت تغییر، 454–457؛ منبع‌یابی رویداد، 457–459؛ همگام‌سازی، 452–453؛ رویداد تغییرناپذیر، 459–464؛ جداسازی، 499–515؛ ترکیب ذخیره‌سازی، 499–504؛ طراحی حول جریان داده، 504–509؛ مشاهدهٔ وضعیت مشتق‌شده، 509–515
مراکز داده؛ جغرافیایی، 145، 164، 278، 493؛ چندمستاجری، 284؛ معماری شبکه، 276؛ خطای شبکه، 279؛ همانندسازی میان چند مرکز، 169، 184، 335
جریان داده، 128–139، 504–509؛ درستی، 525؛ تفاضلی، 504؛ پیام‌گذرانی، 136–139؛ استدلال، 491؛ پایگاه داده، 129؛ خدمات، 131–136
موتورهای جریان داده، 421–423؛ مقایسه با جریان، 464؛ DAG، 424؛ پارتیشن‌بندی، 429؛ پرس‌وجوی اعلانی، 427
Datalog، 60–63
نوع‌های داده؛ رشتهٔ دودویی JSON/XML، 114؛ بدون تعارض، 174؛ Avro، 122؛ Thrift/Protocol Buffers، 121؛ اعداد، 114
Datomic؛ B-tree، 242؛ مدل داده، 50، 57؛ Datalog، 60؛ حذف داده، 463؛ زبان تراکنش، 255؛ اجرای سریال، 253
بن‌بست؛ 2PC، 364؛ 2PL، 258
Debezium، 455
زبان اعلانی، 42، 554؛ Bloom، CSS/XSL، Cypher، Datalog، پردازش دسته‌ای، SQL بازگشتی و SPARQL
تأخیرها؛ شبکهٔ کران‌دار، 285؛ توقف کران‌دار، 298؛ شبکهٔ بی‌کران، 282؛ توقف بی‌کران، 296
حذف داده، 463
ناهنجارسازی، 34، 554؛ هزینه، 39؛ سامانهٔ مشتق‌شده، 386؛ نمای مادی، 101؛ به‌روزرسانی، 228، 231، 490؛ در برابر نرمال‌سازی، 462
دادهٔ مشتق‌شده، 386، 439، 554؛ ثبت تغییر، 454؛ منبع‌یابی رویداد، 458؛ نگهداری با گزارش، 452–463؛ مشاهده با اشتراک جریان، 512؛ خروجی دسته و جریان، 495؛ کد برنامه، 505؛ در برابر تراکنش توزیع‌شده، 492
عملیات قطعی، 255، 274، 554؛ عدم‌قطعیت تصادفی، 423؛ تحمل خطا، 423، 426؛ ایدمپوتنس، 478، 492؛ محاسبهٔ مشتق‌شده، 495، 526، 531؛ همانندسازی ماشین حالت، 349، 452، 458؛ پیوند، 476
DevOps، 394
جریان دادهٔ تفاضلی، 504
جدول بُعد، 94
مدل‌سازی بُعدی، بنگرید به شِمای ستاره‌ای
DAG، 424
خواندن کثیف، 234
نوشتن کثیف، 235
تبعیض، 534
دیسک‌ها، بنگرید به دیسک سخت
چارچوب کنشگر توزیع‌شده، 138
فایل‌سیستم توزیع‌شده، 398–399؛ جداسازی موتور پرس‌وجو، 417؛ انباشت بی‌ضابطه، 415؛ MapReduce، 402
سامانهٔ توزیع‌شده، 273–312، 554؛ بیزانسی، 304–306؛ ابر در برابر ابررایانه، 275؛ تشخیص خطای شبکه، 280؛ شکست جزئی، 274–277؛ اجماع رسمی، 365؛ نتایج ناممکن‌بودن، 338، 353؛ جابجایی پس از خرابی، 157؛ محدودیت تراکنش توزیع‌شده، 363؛ چندمرکز داده، 169، 335؛ مشکل شبکه، 277–286؛ حدنصاب، 301؛ دلایل استفاده، 145، 151؛ ساعت همگام، 291–295؛ مدل سیستم، 306–310؛ زمان، 287
Django، 232
DNS، 216، 372
Docker، 506
مدل سند، 30–42؛ مقایسه با رابطه‌ای، 38–42؛ ارجاع سند، 38، 403؛ پایگاه دادهٔ سندگرا، 31؛ چندبه‌چند و پیوند، 36؛ نیاز به تراکنش چندشیئی، 231؛ همگرایی مدل‌ها، 41؛ محلی‌بودن، 41
نمایهٔ پارتیشن‌شده بر اساس سند، 206، 217، 411
DDD، 457
DRBD، 153
رانش ساعت، 289
Drill، 93
Druid، 461
Dryad، 421
نوشتن دوگانه، 452، 507
حذف تکراری‌ها، 517؛ بنگرید نیز به ایدمپوتنس؛ شناسهٔ یکتا، 518، 522
ماندگاری، 226، 554
مدت زمان، 287؛ ساعت یکنواخت، 288
پارتیشن‌بندی پویا، 212
زبان پویا؛ شباهت به schema-on-read، 40؛ تولید کد، 127
پایگاه دادهٔ سبک Dynamo، بنگرید به همانندسازی بی‌رهبر

E

یال‌های گراف، 49، 403؛ گراف ویژگی، 50
فاصلهٔ ویرایشی، 88
معنای عملاً یک‌بار، 476، 516؛ بنگرید نیز به دقیقاً یک‌بار؛ حفظ یکپارچگی، 525
سامانهٔ کشسان، 17
Elasticsearch؛ نمایهٔ پارتیشن‌شده بر اساس سند، 207؛ توازن پارتیشن، 211؛ percolator، 467؛ نمونهٔ استفاده، 4؛ Lucene، 79
ElephantDB، 413
Elm، 504، 512
کدگذاری‌ها، 111–128؛ Avro، 122–127؛ JSON/XML دودویی، 115؛ سازگاری، 112؛ خدمات، 136؛ پایگاه داده، 129–131؛ پیام‌گذرانی، 138؛ تعریف، 113؛ JSON/XML/CSV، 114؛ وابسته به زبان، 113؛ مزیت شِما، 127؛ بازنمایی داده، 112؛ Thrift/Protocol Buffers، 117–121
استدلال سرتاسری، 277، 519–520؛ بررسی یکپارچگی، 531؛ جریان انتشار/اشتراک، 512
غنی‌سازی جریان، 473
EJB، 134
موجودیت‌ها، بنگرید به رأس‌ها
دورهٔ اجماع، 368
مبدأ زمانی Unix، 288
پیوند برابری، 403
کدگذاری پاک‌کننده، 398
Erlang OTP، 139
رسیدگی به خطا؛ شبکه، 280؛ تراکنش، 231
کد تصحیح خطا، 277، 398
Esper، 466
etcd، 370–373؛ عملیات خطی‌پذیر، 333؛ قفل و انتخاب رهبر، 330؛ خواندن حدنصابی، 351؛ کشف خدمت، 372؛ Raft، 349، 353
Ethernet، 276، 278، 285؛ checksum بسته، 306، 519
Etherpad، 170
اخلاق، 533–543؛ آیین‌نامهٔ حرفه‌ای، 533؛ قانون‌گذاری و خودتنظیمی، 542؛ تحلیل پیش‌بینانه، 533–536؛ تقویت سوگیری، 534؛ حلقهٔ بازخورد، 536؛ حریم خصوصی و ردیابی، 536–543؛ رضایت و آزادی انتخاب، 538؛ داده به‌عنوان دارایی و قدرت، 540؛ معنای حریم خصوصی، 539؛ نظارت، 537؛ احترام، کرامت و اختیار، 543، 544؛ پیامد ناخواسته، 533، 536
ETL، 92، 405، 452، 554؛ Hadoop، 416
منبع‌یابی رویداد، 457–459؛ فرمان و رویداد، 459؛ مقایسه با ثبت تغییر، 457؛ لامبدا، 497؛ استخراج وضعیت، 458؛ تغییرناپذیری و ممیزی، 459، 531؛ سامانهٔ بزرگ قابل‌اعتماد، 519، 526
Event Store، 458
جریان رویداد، بنگرید به جریان‌ها
رویدادها، 440؛ ترتیب کل، 493؛ نما از گزارش، 461؛ تفاوت با فرمان، 459؛ زمان رویداد/پردازش، 469، 477، 498؛ تغییرناپذیری، 460، 531؛ ترتیب علّی، 493؛ خواندن به‌عنوان رویداد، 513؛ دیررس، 470، 498؛ مُهر زمانی، 471
EventSource، 512
سازگاری نهایی، 152، 162، 308، 322؛ بنگرید نیز به تعارض؛ ناسازگاری دائمی، 525
تکامل‌پذیری، 21، 111؛ خدمات، 136؛ گراف، 52؛ پایگاه داده، 40، 129–131، 461، 497؛ پیام‌گذرانی، 138؛ بازپردازش، 496، 498؛ Avro، 123؛ Thrift/Protocol Buffers، 120؛ schema-on-read، 39، 111، 128
معنای دقیقاً یک‌بار، 360، 476، 516؛ هم‌ترازی با پردازش دسته‌ای، 498؛ یکپارچگی، 525
حالت انحصاری قفل، 258
تراکنش XA، بنگرید به XA
استخراج–تبدیل–بارگذاری، بنگرید به ETL

F

Facebook؛ Presto، 93؛ React/Flux/Redux، 512؛ گراف اجتماعی، 49؛ Wormhole، 455
جدول واقعیت، 93
جابجایی پس از خرابی، 157، 554؛ بنگرید نیز به همانندسازی رهبرمحور؛ نبود در بی‌رهبر، 178؛ انتخاب رهبر، 301، 348، 352؛ مشکلات، 157
شکست‌ها؛ تقویت با تراکنش توزیع‌شده، 364، 495؛ تشخیص، 280؛ توازن خودکار و شکست آبشاری، 214؛ تشخیص‌گر کامل، 359؛ مهلت و تأخیر بی‌کران، 282، 284؛ ZooKeeper، 371؛ تفاوت با خطا، 7؛ شکست جزئی، 275–277، 310؛ پیام‌رسانی fan-out، 11، 445
تحمل خطا، 6–10، 555؛ انتزاع‌ها، 321؛ اجماع، 365–369؛ همانندسازی، 367؛ تحمل خطای انسانی، 414؛ دسته‌ای، 406، 414، 422، 425؛ گزارش‌محور، 520، 524–526؛ جریانی، 476–479؛ ثبت اتمی، 477؛ ایدمپوتنس، 478؛ وضعیت مشتق‌شده، 495؛ میکروبچ و نقطهٔ وارسی، 477؛ بازسازی وضعیت، 478؛ تراکنش توزیع‌شده، 362–364؛ اتمی‌بودن، 223، 354–361
خطاها، 6؛ بیزانسی، 304–306؛ در برابر شکست، 7؛ تراکنش، 221؛ ابررایانه و ابر، 275؛ سخت‌افزار، 7؛ پردازش دسته‌ای در برابر پایگاه دادهٔ توزیع‌شده، 417؛ سامانهٔ توزیع‌شده، 274–277؛ تزریق عمدی، 7، 280؛ شبکه، 279–281؛ نامتقارن، 300؛ تشخیص، 280؛ چندرهبر، 169؛ نرم‌افزار، 8
پایگاه دادهٔ فدرال، 501
fence دستور CPU، 338
حصارگذاری، 158، 302–304؛ توکن حصار، 349، 370؛ ویژگی توکن، 308؛ نوشتن جریان‌پرداز در پایگاه داده، 478، 517
Fibre Channel، 398
برچسب فیلد، 119–121
توصیفگر فایل، 395
دادهٔ مالی، 460
Firebase، 456
Flink، 421–423؛ API جریان داده، 427؛ تحمل خطا، 422، 477، 479؛ Gelly، 425؛ یکپارچه‌سازی دسته و جریان، 495، 498؛ یادگیری ماشین، 428؛ بهینه‌ساز، 427؛ پردازش جریان، 466
کنترل جریان، 282، 441، 555
نتیجهٔ FLP، 353
FlumeJava، 403، 427
پیروان، 152، 555؛ بنگرید نیز به همانندسازی رهبرمحور
کلید خارجی، 38، 403
سازگاری پیش‌رو، 112
کاهش پیش‌رونده، 16
Fossil، 463؛ حذف داده، 463
FoundationDB؛ تراکنش سریال‌پذیر، 261، 265، 364
درخت فرکتال، 83
پویش کامل جدول، 403
جست‌وجوی تمام‌متن، 555؛ نمایهٔ فازی، 88؛ ساخت نمایه، 411؛ Lucene، 79
برنامه‌نویسی واکنشی تابعی، 504
نیازمندی کارکردی، 22
futureهای ناهمگام، 135
جست‌وجوی فازی، بنگرید به جست‌وجوی شباهت

G

جمع‌آوری زباله؛ تغییرناپذیری، 463؛ توقف فرایند، 14، 296–299، 301
تحلیل ژنوم، 63، 429
مرکز دادهٔ جغرافیایی، 145، 164، 278، 493
نمایهٔ جغرافیایی، 87
Giraph، 425
Git، 174، 342، 463
GitHub، پسامورتوم، 157، 158، 309
نمایهٔ سراسری، بنگرید به نمایهٔ پارتیشن‌شده بر اساس اصطلاح
GlusterFS، 398
GNU Coreutils، 394
GoldenGate، 161، 170، 455؛ بنگرید نیز به Oracle
Google؛ Bigtable، 78، 199، 202؛ Chubby، 370؛ Cloud Dataflow، 466، 477، 498؛ Cloud Pub/Sub، 444، 448؛ Docs، 170؛ Dremel، 93، 96؛ FlumeJava، 403، 427؛ GFS، 398؛ gRPC، 135؛ MapReduce، 390؛ ساخت نمایهٔ جست‌وجو، 411؛ پیش‌دستی وظیفه، 418؛ Pregel، 425؛ Spanner؛ TrueTime، 294
پروتکل gossip، 216
استفادهٔ دولتی از داده، 541
GPS؛ همگام‌سازی ساعت، 287، 290، 294، 295
GraphChi، 426
گراف‌ها، 555؛ مدل داده، 49–63؛ نمونه، 49؛ گراف ویژگی، 50؛ RDF و triple-store، 55–59؛ در برابر مدل شبکه‌ای، 60؛ پردازش، 424–426؛ تحمل خطا، 425؛ Pregel، 425؛ زبان‌های Cypher، Datalog، SQL بازگشتی و SPARQL
Gremlin، 50
grep، 392
GROUP BY، 406
گروه‌بندی رکورد در MapReduce، 406؛ چولگی، 407

H

Hadoop؛ مقایسه با پایگاه دادهٔ توزیع‌شده، 390؛ MPP، 414–418؛ Unix، 413–414، 499؛ مدل‌های پردازش، 417؛ HDFS؛ ابزار سطح‌بالا، 403؛ پیوندها، 403–410؛ MapReduce؛ YARN
رابطهٔ پیش‌از، 340؛ ثبت، 187؛ هم‌روندی، 186
دیسک سخت؛ الگوی دسترسی، 84؛ تشخیص فساد، 519، 530؛ خطا، 7، 227؛ توان نوشتن ترتیبی، 75، 450
خطای سخت‌افزار، 7
نمایهٔ هش، 72–75؛ پیوند انتشاری، 409؛ پیوند پارتیشن‌شده، 409
پارتیشن‌بندی هش، 203–205، 217؛ هش سازگار، 204؛ مشکل mod N، 210؛ پرس‌وجوی بازه، 204؛ تابع مناسب، 203؛ تعداد ثابت پارتیشن، 210
HAWQ، 428
HBase؛ اشکال نبود حصار، 302؛ بارگذاری انبوه، 413؛ خانواده‌ستون، 41، 99؛ پارتیشن‌بندی پویا، 212؛ بازهٔ کلید، 202؛ گزارش‌ساخت‌یافته، 78؛ مسیریابی، 216؛ فشرده‌سازی، 79؛ HDFS، 417؛ ZooKeeper، 370
HDFS، 398–399؛ یکپارچگی، 530؛ جداسازی موتور پرس‌وجو، 417؛ انباشت داده، 415؛ فراداده، 410؛ NameNode، 398؛ Flink، 479؛ HBase، 212؛ MapReduce، 402
HdrHistogram، 16
head، 392
رأس سر، 51
مسدودشدن ابتدای صف، 15
فایل heap، 86
Helix، 216
تراکنش توزیع‌شدهٔ ناهمگون، 360، 364
تصمیم ابتکاری در 2PC، 363
Hibernate، 30
مدل سلسله‌مراتبی، 36
دسترس‌پذیری بالا، بنگرید به تحمل خطا
معاملهٔ پربسامد، 290، 299
HPC، 275
تحویل راهنمایی‌شده، 183
هیستوگرام، 16
Hive، 419، 427؛ انبار داده، 93؛ HCatalog، 410؛ پیوند سمت map، 409؛ بهینه‌ساز، 427؛ پیوند چوله، 408؛ گردش کار، 403
ماشین Hollerith، 390
پنجرهٔ پرشی، 472؛ بنگرید نیز به پنجره‌ها
مقیاس‌افزایی افقی، بنگرید به scale out
HornetQ، 137، 444؛ تراکنش توزیع‌شده، 361
نقطهٔ داغ، 201؛ افراد مشهور، 205؛ سری زمانی، 203؛ دسته‌ای، 407؛ کاهش، 205
آماده‌به‌کار گرم، بنگرید به همانندسازی رهبرمحور
HTTP در API، بنگرید به خدمات
خطای انسانی، 9، 279، 414
HyperDex، 88
HyperLogLog، 466

I

عملیات I/O، انتظار، 297
IBM؛ DB2، 30، 42، 54، 242، 257، 361؛ ماشین کارت‌مرتب‌کن، 390؛ IMS، 36؛ InfoSphere Streams، 466؛ MQ، 361، 444؛ System R، 222؛ WebSphere، 137
API پرس‌وجوی دستوری، 46
ایدِمپوتنس، 134، 478، 555؛ شناسهٔ یکتا، 518، 522؛ عملیات ایدمپوتنت، 517
تغییرناپذیری؛ مزایا، 460، 531؛ استخراج وضعیت، 459–464؛ بازیابی خرابی، 75؛ B-tree، 82، 242؛ منبع‌یابی رویداد، 457؛ ورودی Unix، 397؛ محدودیت، 463
Impala؛ انبار داده، 93؛ پیوند هش، 409؛ تولید کد بومی، 428؛ HDFS، 417
عدم‌تطابق امپدانس، 29
زبان دستوری، 42؛ سبک عنصر، 45
وضعیت نامعین تراکنش، 358؛ نگهداشت قفل، 362؛ تراکنش یتیم، 363
پایگاه دادهٔ درون‌حافظه‌ای، 88؛ ماندگاری، 227؛ اجرای سریال، 253
رخدادها؛ شکست آبشاری، 9؛ ثانیهٔ کبیسه، 290؛ فساد مالی ناشی از هم‌روندی، 233؛ فساد دیسک، 227؛ LWW، 173، 292؛ دادهٔ ناخوانا، 309؛ بازظهور حذف‌شده‌ها، 174؛ افشای داده، 157؛ خطای سریال‌پذیری، 529؛ رابط گیگابیتی با 1Kb/s، 311؛ خطای شبکه، 279؛ حذف بستهٔ ورودی، 279؛ شکست مرکز داده، 275؛ رسیدگی ضعیف شبکه، 280؛ پیام به شریک سابق، 494؛ کوسه و کابل، 279؛ شکاف مغزی، 158، 279؛ لرزش رک، 14؛ نقض یکتایی، 529
نمایه‌ها، 71، 555؛ اسنپ‌شات، 241؛ دادهٔ مشتق‌شده، 386، 499–504؛ B-tree، 79–83؛ ساخت دسته‌ای، 411؛ خوشه‌ای، 86؛ B-tree در برابر LSM، 83–85؛ الحاقی، 87؛ پوشاننده، 86؛ ساخت، 500؛ تمام‌متن، 88؛ جغرافیایی، 87؛ هش، 72–75؛ قفل بازه، 260؛ چندستونی، 87؛ پارتیشن‌بندی و ثانویه، 206–209، 217؛ SSTable/LSM، 76–79؛ به‌روزرسانی، 452، 467
انقلاب صنعتی، 541
InfiniBand، 285
InfiniteGraph، 50
InnoDB؛ نمایهٔ خوشه‌ای، 86؛ به‌روزرسانی گم‌شده، 245؛ چولگی نوشتن، 248، 257؛ سریال‌پذیری، 257؛ اسنپ‌شات، 239
پایگاه دادهٔ وارونه‌شده، 504؛ بنگرید نیز به جداسازی پایگاه داده
یکپارچه‌سازی سامانه‌ها، بنگرید به یکپارچه‌سازی داده
یکپارچگی، 524؛ پرهیز از هماهنگی، 528؛ درستی جریان داده، 525؛ اجماع، 365؛ بررسی‌ها، 530؛ سرتاسری، 519، 531؛ اسنپ‌شات، 238؛ حفظ در برابر اشکال، 529
IDL، 117، 122
وضعیت میانی، مادی‌سازی، 420–423
خدمات اینترنتی، 275
ناورداها، 225؛ بنگرید نیز به قیدها
وارونگی کنترل، 396
IP؛ نامطمئن‌بودن، 277
ISDN، 284
جداسازی تراکنش، 225، 228، 555؛ درستی، 515؛ نوشتن تک‌شیئی، 230؛ سریال‌پذیری، 251–266؛ اجرای سریال، 252–256؛ SSI، 261–266؛ 2PL، 257–261؛ نقض، 228؛ سطوح ضعیف، 233–251؛ جلوگیری از به‌روزرسانی گم‌شده، 242–246؛ read committed، 234–237؛ اسنپ‌شات، 237–242
پردازش تکراری، 424–426

J–L

JDBC؛ تراکنش توزیع‌شده، 361؛ راه‌انداز شبکه، 128
Java EE، 134، 356، 361
JMS، 444؛ مقایسه با گزارش‌محور، 448، 451؛ تراکنش توزیع‌شده، 361؛ ترتیب پیام، 446
JTA، 355، 361
JVM؛ تولید bytecode، 428؛ توقف GC، 296؛ بازاستفادهٔ فرایند، 422
JavaScript؛ MapReduce، 46؛ سبک عنصر، 45؛ پرس‌وجوی پیشرفته، 48
Jena، 57
Jepsen، 515
لرزش تأخیر شبکه، 284
پیوندها، 555؛ جست‌وجوی نمایه، 403؛ عملگر رابطه‌ای، 427؛ پایگاه دادهٔ رابطه‌ای و سندی، 34؛ سمت map، 408–410؛ سمت reduce، 403–408؛ اجرای موازی، 415؛ نمایهٔ ثانویه، 85؛ جریانی، 472–476؛ پشتیبانی سندی، 42
JOTM، 356
JSON؛ شِمای Avro، 122؛ گونهٔ دودویی، 115؛ مسائل دادهٔ برنامه، 114؛ پایگاه دادهٔ رابطه‌ای، 30، 42؛ نمونهٔ رزومه، 31
Juttle، 504
k-nearest neighbors، 429
Kafka، 137، 448؛ Connect، 457، 461؛ Streams، 466، 467، 479؛ همانندسازی رهبرمحور، 153؛ فشرده‌سازی گزارش، 456، 467؛ offset، 447، 478؛ مسیریابی، 216؛ تراکنش، 477؛ نمونه، 4
Ketama، 213
ذخیره‌گاه کلید–مقدار، 70؛ خروجی دسته‌ای، 412؛ نمایهٔ هش، 72–75؛ درون‌حافظه، 89؛ پارتیشن‌بندی، 201–205، 217؛ چولگی، 205
Kryo، 113
Kubernetes، 418، 506
معماری لامبدا، 497
مُهر زمانی Lamport، 345
LHC، 64
آخرین‌نوشتن‌برنده، 173، 334؛ حذف نوشتن هم‌زمان، 186؛ مشکل‌ها، 292؛ به‌روزرسانی گم‌شده، 246
اتصال دیرهنگام، 396
تأخیر؛ 2PL، 259؛ شبکه و مصرف منبع، 286؛ در برابر زمان پاسخ، 14؛ دنباله، 15، 207
همانندسازی رهبرمحور، 152–161؛ جابجایی پس از خرابی، 157، 301؛ قطعی گره، 156؛ ثبت تغییر، 454–457؛ مبتنی بر دستور، 158؛ تریگر، 161؛ WAL، 159؛ خطی‌پذیری، 333؛ قفل و انتخاب رهبر، 330؛ شمارهٔ توالی گزارش، 156، 449؛ مقیاس خواندن، 161؛ اجماع، 367؛ پیرو جدید، 155؛ همگام/ناهمگام، 153–155
همانندسازی بی‌رهبر، 177–191؛ نوشتن هم‌زمان، 184–191؛ رابطهٔ پیش‌از، 186، 187؛ LWW، 186؛ ادغام، 190؛ بردار نسخه، 191؛ چندمرکز داده، 184؛ حدنصاب، 179–184، 334؛ ترمیم خواندن و ضدآنتروپی، 178
ثانیهٔ کبیسه، 8، 288، 290
اجاره، 295؛ ZooKeeper، 370؛ نیاز به حصار، 302
دفترکل‌ها، 460؛ فناوری دفترکل توزیع‌شده، 532
سامانهٔ میراثی، 18
less، 397
LevelDB، 78
فشرده‌سازی سطحی، 79
اتوماتای Levenshtein، 88
لنگیدن شکست جزئی، 311
خطی‌پذیری، 324–338، 555؛ هزینه، 335–338؛ CAP، 336؛ CPU چند‌هسته، 338؛ تعریف، 325–329؛ انتشار ترتیب کل، 350؛ ZooKeeper، 370؛ دادهٔ مشتق‌شده، 492، 524؛ پرهیز از هماهنگی، 527؛ روش‌های همانندسازی، 332–335؛ حدنصاب، 334؛ اتکا، 330–332؛ قید و یکتایی، 330؛ وابستگی زمانی میان کانال، 331؛ قفل و انتخاب رهبر، 330؛ قوی‌تر از سازگاری علّی، 342؛ پیاده‌سازی ترتیب کل، 351؛ در برابر سریال‌پذیری، 329
LinkedIn؛ Azkaban، 402؛ Databus، 161، 455؛ Espresso، 31، 126، 130، 153، 216؛ Helix؛ پروفایل، 30؛ ارجاع شرکت، 34؛ Rest.li، 135؛ Voldemort
Linux، اشکال ثانیهٔ کبیسه، 8، 290
ویژگی زنده‌بودن، 308
LMDB، 82، 242
بار؛ روش‌های مقابله، 17؛ توصیف، 11؛ آزمون، 16
توازن بار پیام‌رسانی، 444
نمایهٔ محلی، بنگرید به نمایهٔ پارتیشن‌شده بر اساس سند
محلی‌بودن داده، 32، 41، 555؛ دسته‌ای، 400، 405، 421؛ مشتری دارای وضعیت، 170، 511؛ جریانی، 474، 478، 508، 522
شفافیت مکان، 134؛ مدل کنشگر، 138
قفل‌ها، 556؛ بن‌بست، 258؛ قفل توزیع‌شده، 301–304، 330؛ توکن حصار، 303؛ ZooKeeper، 370؛ اجماع، 374؛ جداسازی تراکنش، 239، 257–261؛ عملیات اتمی، 243؛ کارایی، 258؛ جلوگیری از نوشتن کثیف، 236؛ شبح، 260، 265؛ حالت مشترک/انحصاری، 258؛ 2PC، 362، 364؛ مادی‌سازی تعارض، 251؛ جلوگیری از به‌روزرسانی گم‌شده، 244
شمارهٔ توالی گزارش، 156، 449
زبان برنامه‌نویسی منطقی، 504
ساعت منطقی، 293، 343، 494؛ سازگاری خواندن پس از نوشتن، 164
گزارش منطقی، 160
گزارش‌ها، 71، 556؛ تغییرناپذیری، 460؛ فشرده‌سازی، 73، 79، 456، 460، 479؛ ترتیب کل، 349؛ یکتایی، 522؛ پیام‌رسانی گزارش‌محور، 446–451؛ offset مصرف‌کننده، 449؛ فضای دیسک، 450؛ بازپخش، 451، 496، 498؛ مصرف‌کنندهٔ کند، 450؛ ذخیرهٔ پیام، 447؛ ذخیره‌سازی گزارش‌ساخت‌یافته، 71–79؛ همانندسازی، 152، 158–161؛ ثبت تغییر، 454–457؛ هماهنگی با اسنپ‌شات، 156؛ همانندسازی منطقی، 160؛ مبتنی بر دستور، 158؛ تریگر، 161؛ WAL، 159؛ محدودیت مقیاس، 493
اتصال سست، 396، 419، 502
به‌روزرسانی گم‌شده، بنگرید به به‌روزرسانی‌ها
LSM-treeها، 78–79؛ مقایسه با B-tree، 83–85
Lucene، 79؛ ساخت نمایه، 411؛ جست‌وجوی شباهت، 88
Luigi، 402
LWW، بنگرید به آخرین‌نوشتن‌برنده

M–P

یادگیری ماشین؛ اخلاق، 534؛ پردازش تکراری، 424؛ مدل مشتق از آموزش، 505؛ الگوریتم آماری، 428
MADlib، 428
سس جادویی مقیاس‌پذیری، 18
Mahout، 428
نگهداشت‌پذیری، 18–22، 489؛ تعریف، 23؛ اصول طراحی، 19؛ تکامل‌پذیری؛ عملیات‌پذیری، 19؛ سادگی، 20
روابط چندبه‌چند؛ مدل سند/رابطه‌ای، 39؛ گراف، 49؛ چندبه‌یک و چندبه‌چند، 33–36
MapReduce، 390، 399–400؛ خدمت خارجی در کار، 404، 412؛ مقایسه با پایگاه دادهٔ توزیع‌شده، 415–417؛ با جریان، 464؛ با Unix، 413–414؛ محدودیت‌ها، 419؛ تحمل خطا، 406، 414، 422؛ ابزار سطح‌بالا، 403، 426؛ Hadoop، 400–403؛ shuffle، 402؛ MongoDB، 46–48؛ یادگیری ماشین، 428؛ سمت map، 408–410؛ mapper/reducer، 399؛ وضعیت میانی، 419–423؛ خروجی، 411–413؛ سمت reduce، 403–408؛ گردش کار، 402
مارشال‌سازی، بنگرید به کدگذاری
MPP، 216؛ مقایسه با ترکیب ذخیره‌سازی، 502؛ Hadoop، 414–418، 428
همانندسازی master-master، بنگرید به چندرهبر
همانندسازی master-slave، بنگرید به رهبرمحور
مادی‌سازی، 556؛ تجمیع، 101؛ تعارض، 251؛ وضعیت میانی، 420–423؛ نمای مادی، 101، 386، 467، 475، 499–504
Maven، 428
Maxwell، 455
میانگین، 14
پایش رسانه، 467
میانه، 14
رزرو اتاق جلسه، 249، 259، 521
خدمت عضویت، 372
Memcached، 4، 89
حافظه؛ پایگاه دادهٔ درون‌حافظه، 88، 227، 253؛ بازنمایی، 112؛ وارونگی بیت، 529؛ نمایه، 72، 77؛ مانع حافظه، 338
MemSQL؛ درون‌حافظه، 89؛ read committed، 236
memtable، 78
Mercurial، 463
پیوند ادغامی، 410
ساختار پایدار ادغام‌پذیر، 174
ادغام فایل مرتب، 76، 402، 405
درخت Merkle، 532
Mesos، 418، 506
کارگزار پیام، بنگرید به سامانهٔ پیام‌رسانی
پیام‌گذرانی، 136–139؛ مزیت نسبت به RPC، 137؛ کنشگر توزیع‌شده، 138؛ تکامل‌پذیری، 138
MessagePack، 116
پیام‌ها؛ دقیقاً یک‌بار، 360، 476؛ از دست رفتن، 442؛ ترتیب کل، 348
سامانه‌های پیام‌رسانی، 440–451؛ پس‌فشار/بافر/حذف، 441؛ بدون کارگزار، 442؛ گزارش رویداد، 446–451؛ offset، 449؛ بازپخش، 451، 496، 498؛ مصرف‌کنندهٔ کند، 450؛ کارگزار، 443–446؛ تأیید و تحویل مجدد، 445؛ چند مصرف‌کننده، 444؛ قابلیت اطمینان، 442؛ یکتایی، 522
Meteor، 456
میکروبچ، 477، 495
ریزخدمت، 132؛ بنگرید نیز به خدمات؛ وابستگی علّی، 493؛ اتصال سست، 502؛ ارتباط با دسته/جریان، 389، 508
Microsoft؛ Azure Service Bus، 444؛ Azure Storage، 155، 398؛ Stream Analytics، 466؛ DCOM، 134؛ MSDTC، 356؛ Orleans؛ SQL Server
مهاجرت داده، 40، 130، 461، 497
عملگر پیمانه، 210
MongoDB؛ pipeline تجمیع، 48؛ عملیات اتمی، 243؛ BSON، 41؛ مدل سند، 31؛ شاردینگ هش، 203–204؛ بازهٔ کلید، 202؛ نبود پیوند، 34، 42؛ رهبرمحور، 153؛ MapReduce، 46، 400؛ oplog، 455، 456؛ شکافتن پارتیشن، 212؛ مسیریابی، 216؛ نمایهٔ ثانویه، 207
Mongoriver، 455
پایش، 10، 19
ساعت یکنواخت، 288
خواندن یکنواخت، 164
MSMQ، 361
نمایهٔ چندستونی، 87
همانندسازی چندرهبر، 168–177؛ تعارض نوشتن، 171–175؛ پرهیز از تعارض، 172؛ همگرایی، 172؛ منطق سفارشی، 173؛ تعیین تعارض، 174؛ نبود خطی‌پذیری، 333؛ توپولوژی، 175–177؛ مشتری آفلاین، 170؛ ویرایش مشارکتی، 170؛ چندمرکز داده، 168، 335
تراکنش چندشیئی، 228، 231
Multi-Paxos، 367
جدول خوشه‌ای چندجدولی Oracle، 41
چندمستاجری، 284
MVCC، 239، 266؛ خواندن کهنه، 263؛ نمایه و اسنپ‌شات، 241
طرد متقابل، 261؛ بنگرید نیز به قفل
MySQL؛ binlog، 156، 455؛ توپولوژی حلقوی، 175؛ اسنپ‌شات سازگار، 156؛ تراکنش توزیع‌شده، 361؛ InnoDB؛ JSON، 30، 42؛ رهبرمحور، 153؛ XA، 360؛ همانندسازی سطری، 160؛ تغییر شِما، 40؛ اسنپ‌شات، 242؛ مبتنی بر دستور، 159؛ Tungsten، 170، 177
nanomsg، 442
Narayana، 356
NATS، 137
پردازش نزدیک‌به‌بلادرنگ، 390؛ بنگرید نیز به جریان
Neo4j؛ Cypher، 52؛ مدل گراف، 50
Nephele، 421
netcat، 397
Netflix Chaos Monkey، 7، 280
NAS، 146، 398
مدل شبکه‌ای، 36؛ در برابر گراف، 60؛ API دستوری، 46
NTP، 287؛ دقت، 289، 293؛ تنظیم ساعت یکنواخت، 289؛ چند سرور، 306
شبکه‌ها؛ ازدحام و صف، 282؛ توپولوژی مرکز داده، 276؛ خطا؛ خطی‌پذیری و تأخیر، 338؛ پارتیشن شبکه، 279، 337؛ مهلت و تأخیر بی‌کران، 281
قفل next-key، 260
گره‌های گراف، بنگرید به رأس‌ها
گره‌های فرایندی، 556؛ قطعی در رهبرمحور، 156؛ مدل شکست، 307
همسایهٔ پرسر‌وصدا، 284
ثبت اتمی نامسدودکننده، 359
عملیات غیرقطعی، 275، 423
نیازمندی غیرکارکردی، 22
خواندن تکرارناپذیر، 238؛ بنگرید نیز به چولگی خواندن
نرمال‌سازی، 33، 556؛ پیوند، 39، 42، 403؛ کلید خارجی، 231؛ سامانهٔ ثبت، 386؛ در برابر ناهنجارسازی، 462
NoSQL، 29، 499؛ تراکنش، 223
Notation3، 56
npm، 428
عدد در XML/JSON، 114
ORM، 30؛ خطا و لغو، 232؛ چرخهٔ ناامن خواندن–تغییر–نوشتن، 244
عدم‌تطابق شیء–رابطه، 29
الگوی ناظر، 506
سامانهٔ آفلاین، 390؛ بنگرید نیز به دسته‌ای
برنامهٔ offline-first، 511
offset؛ مصرف‌کننده، 449؛ پیام، 447
OLAP، 91، 556؛ مکعب داده، 102
OLTP، 90، 556؛ در برابر تحلیل، 411؛ ویژگی بار، 253
رابطهٔ یک‌به‌چند، 30؛ JSON، 32
سامانهٔ برخط، 389؛ بنگرید نیز به خدمات
Oozie، 402
OpenAPI، 133
OpenStack Nova؛ ZooKeeper، 370؛ Swift، 398
عملیات‌پذیری، 19
سیستم‌عامل در برابر پایگاه داده، 499
شناسهٔ عملیات، 518، 522
تبدیل عملیاتی، 174
عملگرها، 421؛ جریان داده، 424؛ پردازش جریانی، 464
کنترل هم‌روندی خوش‌بینانه، 261
Oracle؛ تراکنش توزیع‌شده، 361؛ GoldenGate، 161، 170، 455؛ نبود سریال‌پذیری، 226؛ رهبرمحور، 153؛ جدول خوشه‌ای، 41؛ جلوگیری نکردن از چولگی، 248؛ نمایهٔ پارتیشن‌شده، 209؛ PL/SQL، 255؛ به‌روزرسانی گم‌شده، 245؛ read committed، 236؛ RAC، 330؛ پرس‌وجوی بازگشتی، 54؛ اسنپ‌شات، 239، 242؛ TimesTen، 89؛ WAL، 160؛ XML، 30
ترتیب‌دهی، 339–352؛ شمارهٔ توالی، 343–348؛ علّی، 339–343؛ ترتیب جزئی، 341؛ محدودیت ترتیب کل، 493؛ انتشار ترتیب کل، 348–352
Orleans، 139
دادهٔ پرت زمان پاسخ، 14
Oz، 504
مدیر بسته، 428، 505
تعویض بسته‌ای، 285
بسته‌ها؛ فساد، 306؛ UDP، 442
PageRank، 49، 424
صفحه‌بندی، بنگرید به حافظهٔ مجازی
ParAccel، 93
پایگاه دادهٔ موازی، بنگرید به MPP
اجرای موازی؛ الگوریتم گراف، 426؛ پرس‌وجوی MPP، 216
Parquet، 96، 131، 414
شکست جزئی، 275، 310؛ لنگیدن، 311
ترتیب جزئی، 341
پارتیشن‌بندی، 199–218، 556؛ و همانندسازی، 200؛ دسته‌ای، 429؛ عملیات چندپارتیشنی، 514، 522، 495؛ کلید–مقدار، 201–205؛ بازهٔ کلید، 202؛ چولگی، 205؛ توازن مجدد، 209–214؛ خودکار/دستی، 213؛ mod N، 210؛ پویا، 212؛ تعداد ثابت، 210، 212؛ همانندسازی، 147؛ مسیریابی، 214–216؛ نمایهٔ ثانویه، 206–209؛ سندمحور، 206؛ اصطلاح‌محور، 208؛ اجرای سریال، 255
Paxos، 366؛ شمارهٔ رأی، 368؛ Multi-Paxos، 367
صدک‌ها، 14، 556؛ محاسبهٔ کارآمد، 16؛ اهمیت صدک بالا، 16؛ SLA، 15
Percona XtraBackup، 156
کارایی، 13؛ تراکنش توزیع‌شده، 360؛ درون‌حافظه، 89؛ خطی‌پذیری، 338؛ چندرهبر، 169
ناسازگاری دائمی، 525
کنترل بدبینانه، 261
شبح‌های تراکنش، 250؛ مادی‌سازی تعارض، 251؛ جلوگیری، 259
ساعت فیزیکی، بنگرید به ساعت‌ها
pickle، 113
Pig، 419، 427؛ پیوند همانندسازی‌شده، 409؛ پیوند چوله، 407؛ گردش کار، 403
Pinball، 402
اجرای خط‌لوله‌ای، 423؛ Unix، 394
نقطه در زمان، 287
polyglot persistence، 29
polystore، 501
PostgreSQL؛ BDR، 170، 177؛ Bottled Water، 455؛ Bucardo، 161، 173؛ تراکنش توزیع‌شده، 361؛ foreign data wrapper، 501؛ تمام‌متن، 490؛ رهبرمحور، 153؛ LSN، 156؛ MVCC، 239، 241؛ PL/pgSQL، 255؛ PostGIS، 87؛ جلوگیری از به‌روزرسانی گم‌شده، 245؛ چولگی، 248، 261؛ read committed، 236؛ پرس‌وجوی بازگشتی، 54؛ گراف، 51؛ SSI، 261؛ اسنپ‌شات، 239، 242؛ WAL، 160؛ XML/JSON، 30، 42
پیش‌شکافتن، 212
PTP، 290
قفل گزاره‌ای، 259
تحلیل پیش‌بینانه، 533–536؛ تقویت سوگیری، 534؛ اخلاق؛ حلقهٔ بازخورد، 536
پیش‌دستی؛ منابع مرکز داده، 418؛ رشته، 298
Pregel، 425
کلید اصلی، 85، 556؛ کلید مرکب Cassandra، 204
همانندسازی primary-secondary، بنگرید به رهبرمحور
حریم خصوصی، 536–543؛ رضایت، 538؛ داده و قدرت، 540؛ حذف، 463؛ اخلاق؛ قانون‌گذاری، 542؛ معنا، 539؛ نظارت، 537؛ ردیابی، 536
الگوریتم احتمالاتی، 16، 466
توقف فرایند، 295–299
زمان پردازش رویداد، 469
تولیدکنندگان جریان پیام، 440
زبان برنامه‌نویسی؛ جریان داده، 504؛ رویهٔ ذخیره‌شده، 255؛ FRP، 504؛ منطقی، 504
Prolog، 61؛ بنگرید نیز به Datalog
promise ناهمگام، 135
گراف ویژگی، 50؛ Cypher، 52
Protocol Buffers، 117–121؛ برچسب فیلد و تکامل شِما، 120
منشأ داده، 531
مدل انتشار/اشتراک، 441
ناشران جریان پیام، 440
ماشین کارت پانچ، 390
تابع خالص، 48
نزدیک کردن محاسبه به داده، 400

Q–R

Qpid، 444
QoS، 285
Quantcast File System، 398
زبان پرس‌وجو، 42–48؛ pipeline تجمیع، 48؛ CSS/XSL، 44؛ Cypher، 52؛ Datalog، 60؛ Juttle، 504؛ MapReduce، 46–48؛ SQL بازگشتی، 53؛ جبر رابطه‌ای و SQL، 42؛ SPARQL، 59
بهینه‌ساز پرس‌وجو، 37، 427
تأخیر صف شبکه، 282؛ مسدودشدن ابتدای صف، 15؛ تأخیر و زمان پاسخ، 14
صف پیام، 137
حدنصاب، 179–182، 556؛ بی‌رهبر، 179؛ اجماع، 368؛ محدودیت سازگاری، 181–183، 334؛ تصمیم توزیع‌شده، 301؛ پایش کهنگی، 182؛ چندمرکز داده، 184؛ اتکا به ماندگاری، 309؛ سهل‌گیرانه، 183
R-tree، 87
RabbitMQ، 137، 444؛ رهبرمحور، 153
شرایط مسابقه، 225؛ بنگرید نیز به هم‌روندی؛ خطی‌پذیری، 331؛ نوشتن دوگانه، 452؛ نوشتن کثیف، 235؛ افزایش شمارنده، 235؛ به‌روزرسانی گم‌شده، 242–246؛ گزارش رویداد، 462، 507؛ جداسازی سریال‌پذیر، 252؛ چولگی نوشتن، 246–251
Raft، 366؛ حساسیت شبکه، 369؛ شمارهٔ دوره، 368؛ etcd، 353
RAID، 7، 398
راه‌آهن، مهاجرت شِما، 496
RAMCloud، 89
الگوریتم رتبه‌بندی، 424
RDF، 57؛ SPARQL، 59
RDMA، 276
جداسازی read committed، 234–237؛ پیاده‌سازی، 236؛ MVCC، 239؛ نبود خواندن کثیف، 234؛ نبود نوشتن کثیف، 235
مسیر خواندن دادهٔ مشتق‌شده، 509
ترمیم خواندن، 178؛ خطی‌پذیری، 335
همتای خواندنی، بنگرید به رهبرمحور
چولگی خواندن، 238، 266؛ نقض علیت، 340
سازگاری خواندن پس از نوشتن، 163، 524؛ میان دستگاه‌ها، 164
چرخهٔ خواندن–تغییر–نوشتن، 243
معماری مقیاس خواندن، 161
خواندن‌ها به‌عنوان رویداد، 513
بلادرنگ؛ ویرایش مشارکتی، 170؛ نزدیک‌به‌بلادرنگ، 390؛ جریان انتشار/اشتراک، 513؛ تضمین زمان پاسخ، 298؛ ساعت روز، 288
توازن مجدد پارتیشن، 209–214، 556؛ خودکار/دستی، 213؛ پویا، 212؛ ثابت، 210، 212؛ mod N، 210
تضمین تازگی، 324
موتور پیشنهاددهی؛ خروجی دسته‌ای، 412؛ گردش کار، 403، 420؛ پردازش تکراری، 424؛ الگوریتم آماری، 428
رکوردها، 399
RMI، 134
RPC، 134–136؛ future، 135؛ کدگذاری و تکامل، 136؛ مشکلات، 134؛ Avro، 126، 135؛ Thrift، 135؛ در برابر کارگزار پیام، 137
خواندن تکرارپذیر، 242
همتاها، 152
همانندسازی، 151–193، 556؛ ماندگاری، 227؛ زنجیره‌ای، 155؛ حل تعارض، 246؛ تضمین سازگاری، 161–167؛ پیشوند، 165؛ یکنواخت، 164؛ خواندن نوشتن خود، 162؛ فایل‌سیستم، 398؛ بی‌رهبر، 177–191؛ چندرهبر، 168–177؛ پارتیشن‌بندی، 147، 200؛ دلایل، 145، 151؛ تک‌رهبر، 152–161؛ ماشین حالت، 349، 452؛ کد پاک‌کننده، 398؛ سامانهٔ ناهمگون، 453
بازپردازش داده، 496، 498؛ بنگرید نیز به تکامل‌پذیری؛ گزارش پیام، 451
مسیریابی درخواست، 214–216
سامانهٔ تاب‌آور، 6؛ بنگرید نیز به تحمل خطا
زمان پاسخ؛ معیار خدمت، 13، 389؛ تضمین، 298؛ در برابر تأخیر، 14؛ میانگین و صدک، 14؛ تجربهٔ کاربر، 15
مسئولیت و پاسخ‌گویی، 535
REST، 133؛ بنگرید نیز به خدمات
RethinkDB؛ مدل سند، 31؛ پارتیشن‌بندی پویا، 212؛ پیوند، 34، 42؛ بازهٔ کلید، 202؛ رهبرمحور، 153؛ اشتراک تغییر، 456
Riak؛ Bitcask، 72؛ CRDT، 174، 191؛ بردار نسخه، 191؛ gossip، 216؛ هش، 203–204، 211؛ LWW، 186؛ بی‌رهبر، 177؛ LevelDB، 78؛ نبود خطی‌پذیری، 335؛ چندمرکز داده، 184؛ جلوگیری از به‌روزرسانی گم‌شده، 246؛ توازن، 213؛ جست‌وجو، 209؛ نمایهٔ ثانویه، 207؛ sibling، 190؛ حدنصاب سهل‌گیرانه، 184
بافر حلقوی، 450
Ripple، 532
موشک‌ها، 10، 36، 305
RocksDB، 78؛ فشرده‌سازی سطحی، 79
بازگردانی تراکنش، 222
ارتقای چرخشی، 8، 112
مسیریابی، بنگرید به مسیریابی درخواست
ذخیره‌سازی سطری، 96
همانندسازی سطری، 160
rowhammer، 529
RPCها، بنگرید به RPC
Rubygems، 428
قواعد Datalog، 61

S

ویژگی ایمنی و زنده‌بودن، 308؛ اجماع، 366؛ تراکنش، 222
saga، بنگرید به تراکنش جبرانی
Samza، 466، 467؛ تحمل خطا، 479؛ SQL جریانی، 466
sandbox، 9
SAP HANA، 93
مقیاس‌پذیری، 10–18، 489؛ مقابله با بار، 17؛ تعریف، 22؛ توصیف بار، 11؛ کارایی، 13؛ پارتیشن‌بندی، 199؛ همانندسازی، 161؛ scale up در برابر out، 146
scale out، 17، 146؛ بنگرید نیز به بی‌اشتراک
scale up، 17، 146
scatter/gather، 207
SCD، 476
schema-on-read، 39؛ مقایسه با شِمای تکامل‌پذیر، 128؛ فایل‌سیستم توزیع‌شده، 415
schema-on-write، 39
پایگاه دادهٔ بی‌شِما، بنگرید به schema-on-read
شِماها، 557؛ Avro، 122–127؛ شِمای نویسنده، 125؛ تکامل، 123؛ تولید پویا، 126؛ اثر بر کد، 111؛ بررسی سازگاری، 126؛ پایگاه داده، 129–131؛ پیام‌گذرانی، 138؛ خدمت، 136؛ انعطاف مدل سند، 39؛ تحلیل، 93–95؛ JSON/XML، 115؛ مزایا، 127؛ راه‌آهن، 496؛ Thrift/Protocol Buffers، 117–121؛ رویکرد سنتی و مغالطه، 462
جست‌وجوها؛ ساخت نمایه دسته‌ای، 411؛ k-nearest neighbors، 429؛ جریان، 467؛ نمایهٔ پارتیشن‌شده، 206
ثانویه‌ها، بنگرید به رهبرمحور
نمایهٔ ثانویه، 85، 557؛ پارتیشن‌بندی، 206–209، 217؛ سندمحور، 206؛ نگهداری، 495؛ اصطلاح‌محور، 208؛ نوشتن دوگانه، 452، 491؛ جداسازی، 231
مرتب‌سازی ثانویه، 405
sed، 392
فایل خودتوصیف، 127
self-join، 480
سامانهٔ خوداعتبارسنج، 530
وب معنایی، 57
همانندسازی نیمه‌همگام، 154
ترتیب شمارهٔ توالی، 343–348؛ مولد، 294، 344؛ ناکافی برای قید، 347؛ Lamport، 345؛ مُهر زمانی، 291، 295، 345
سازگاری ترتیبی، 351
سریال‌پذیری، 225، 233، 251–266، 557؛ در برابر خطی‌پذیری، 329؛ کنترل بدبینانه/خوش‌بینانه، 261؛ اجرای سریال، 252–256؛ پارتیشن‌بندی، 255؛ رویهٔ ذخیره‌شده، 253، 349؛ SSI، 261–266؛ MVCC کهنه، 263؛ نوشتن مؤثر بر خواندن، 264؛ توزیع‌شده، 265، 364؛ کارایی، 265؛ چولگی نوشتن، 262–265؛ 2PL، 257–261؛ قفل بازه، 260
Serializable در Java، 113
سریال‌سازی، 113؛ بنگرید نیز به کدگذاری
کشف خدمت، 135، 214، 372؛ DNS، 216، 372
SLA، 15
SOA، 132؛ بنگرید نیز به خدمات
خدمات، 131–136؛ ریزخدمت، 132، 493، 502؛ ارتباط با دسته/جریان، 389، 508؛ RPC، 134–136؛ شباهت به پایگاه داده، 132؛ وب‌سرویس، 132، 135
پنجرهٔ نشست، 472
sessionization، 407
شاردینگ، بنگرید به پارتیشن‌بندی
حالت مشترک قفل، 258
معماری دیسک‌اشتراکی، 146، 398
معماری حافظه‌اشتراکی، 146
معماری بی‌اشتراک، 17، 146–147، 557؛ فایل‌سیستم توزیع‌شده، 398؛ پارتیشن‌بندی، 199؛ شبکه، 277
کوسه‌ها؛ گاز گرفتن کابل، 279؛ شمارش، 46–48؛ یافتن، 42؛ وب‌سایت نمونه، 44
shredding در مدل رابطه‌ای، 38
siblingها، 190، 246؛ بنگرید نیز به تعارض
جست‌وجوی شباهت؛ فاصلهٔ ویرایش، 88؛ ژنوم، 63؛ k-nearest neighbors، 429
همانندسازی تک‌رهبر، بنگرید به رهبرمحور
اجرای تک‌رشته‌ای، 243، 252؛ دسته‌ای، 406، 421، 426؛ جریانی، 448، 463، 522
فشرده‌سازی اندازه‌لایه‌ای، 79
چولگی، 557؛ ساعت، 291–294، 334؛ خواندن، 238، 266؛ نوشتن، 246–251، 262–265؛ معانی، 238؛ بار نامتوازن، 201؛ جبران، 205؛ افراد مشهور، 205؛ سری زمانی، 203؛ دسته‌ای، 407
برده‌ها، بنگرید به رهبرمحور
پنجرهٔ لغزان، 472
حدنصاب سهل‌گیرانه، 183، 334
بُعد دیرتغییر، 476
smearing ثانیهٔ کبیسه، 290
اسنپ‌شات‌ها؛ سازگاری علّی، 340؛ دادهٔ مشتق‌شده، 500؛ ثبت تغییر، 455؛ SSI، 261–266، 329؛ همتای جدید، 156؛ جداسازی، 237–242؛ MVCC، 239؛ نمایه، 241؛ قواعد دیدپذیری، 240؛ ساعت همگام جهانی، 294
شِمای دانه‌برفی، 95
SOAP، 133؛ تکامل‌پذیری، 136
اشکال نرم‌افزار، 8؛ حفظ یکپارچگی، 529
SSD؛ الگوی دسترسی، 84؛ فساد، 519، 530؛ خطا، 227؛ توان نوشتن، 75
Solr؛ ساخت نمایه، 411؛ سندمحور، 207؛ مسیریابی، 216؛ نمونه، 4؛ Lucene، 79
sort، 392، 394، 395
پیوند sort-merge، 405
Sorted String Table، بنگرید به SSTable
مرتب‌سازی؛ ترتیب ذخیره‌سازی ستونی، 99
منبع حقیقت، بنگرید به سامانهٔ ثبت
Spanner؛ محلی‌بودن، 41؛ اسنپ‌شات با ساعت، 295؛ TrueTime، 294
Spark، 421–423؛ bytecode، 428؛ API جریان داده، 427؛ تحمل خطا، 422؛ انبار داده، 93؛ GraphX، 425؛ یادگیری ماشین، 428؛ بهینه‌ساز، 427؛ Streaming، 466، 477؛ جریان روی دسته، 495
SPARQL، 59
الگوریتم مکانی، 429
شکاف مغزی، 158، 557؛ اجماع، 352، 367؛ جلوگیری، 322، 333؛ توکن حصار، 302–304
صفحه‌گسترده، 504
SQL، 21، 28، 43؛ مزایا و محدودیت، 416؛ اجرای توزیع‌شده، 48؛ پرس‌وجوی گراف، 53؛ استاندارد جداسازی، 242؛ Hadoop، 93، 416؛ رزومه، 30؛ SQL injection، 305؛ همانندسازی مبتنی بر دستور، 158؛ رویهٔ ذخیره‌شده، 255
SQL Server؛ انبار داده، 93؛ تراکنش توزیع‌شده، 361؛ رهبرمحور، 153؛ جلوگیری از به‌روزرسانی گم‌شده، 245؛ چولگی، 248، 257؛ read committed، 236؛ پرس‌وجوی بازگشتی، 54؛ سریال‌پذیری، 257؛ اسنپ‌شات، 239؛ T-SQL، 255؛ XML، 30
SQLstream، 466
SSTable، 76–79؛ مزیت بر هش، 76؛ نمایهٔ الحاقی، 204؛ ساخت و نگهداری، 78؛ LSM-tree، 78
کهنگی داده، 162؛ وابستگی زمانی میان کانال، 331؛ بی‌رهبر، 178؛ MVCC، 263؛ پایش، 182؛ وضعیت مشتری، 512؛ در برابر خطی‌پذیری، 324؛ در برابر بهنگامی، 524
آماده‌به‌کار، بنگرید به رهبرمحور
توپولوژی ستاره‌ای همانندسازی، 175
شِمای ستاره‌ای، 93–95؛ شباهت به منبع‌یابی رویداد، 458
تشبیه Star Wars، 469
وضعیت؛ مشتق از گزارش رویداد، 458، 459؛ تعامل تغییر و کد، 507؛ نگهداری مشتق‌شده، 495؛ در پیوند جریان–جریان، 473؛ مشاهده، 509–515؛ بازسازی، 478؛ جداسازی از کد، 505
همانندسازی ماشین حالت، 349، 452
همانندسازی مبتنی بر دستور، 158
زبان ایستا؛ شباهت به schema-on-write، 40؛ تولید کد، 127
الگوریتم آماری و عددی، 428
StatsD، 442
stdin و stdout، 395، 396
Stellar، 532
خوراک بازار سهام، 442
STONITH، 158
stop-the-world، بنگرید به جمع‌آوری زباله
ذخیره‌سازی؛ ترکیب فناوری، 499–504؛ تنوع در MapReduce، 415
SAN، 146، 398
موتور ذخیره‌سازی، 69–104؛ ستونی، 95–101؛ فشرده‌سازی ستون، 97–99؛ Parquet، 96، 131؛ ترتیب، 99–100؛ نوشتن، 101؛ تراکنش در برابر تحلیل، 90–96؛ درون‌حافظه، 88، 227؛ سطری، 70–90؛ B-tree، 79–83؛ LSM، 72–85
رویهٔ ذخیره‌شده، 161، 253–255، 557؛ ترتیب کل، 349؛ مزایا و معایب، 255؛ شباهت به جریان‌پرداز، 505
Storm، 466؛ RPC توزیع‌شده، 468، 514؛ Trident، 478
رویداد دیررس، 470، 498
پردازش جریانی، 464–481، 557؛ خدمت خارجی، 474، 477، 478، 517؛ ترکیب با دسته، 497، 498؛ مقایسه با دسته، 464؛ CEP، 465؛ تحمل خطا، 476–479؛ ثبت اتمی، 477؛ ایدمپوتنس، 478؛ میکروبچ و نقطهٔ وارسی، 477؛ بازسازی وضعیت، 478؛ یکپارچه‌سازی داده، 494–498؛ وضعیت مشتق‌شده، 495؛ نما، 467؛ زمان، 468–472؛ زمان رویداد/پردازش، 469، 477، 498؛ آمادگی پنجره، 470؛ انواع پنجره، 472؛ رابطه با پایگاه داده و خدمت، 508؛ جست‌وجو، 467؛ تک‌رشته‌ای، 448، 463؛ تحلیل جریانی، 466؛ پیوند جریانی، 472–476
جریان‌ها، 440–451؛ ارسال سرتاسری به مشتری، 512؛ پیام‌رسانی؛ پردازش؛ رابطه با پایگاه داده، 451–464؛ ثبت تغییر، 454–457؛ مشتق وضعیت در زمان، 460؛ منبع‌یابی رویداد، 457–459؛ همگام‌سازی، 452–453؛ رویداد تغییرناپذیر، 459–464؛ topic، 440
سریال‌پذیری سخت‌گیرانه، 329
سازگاری قوی، بنگرید به خطی‌پذیری
سریال‌پذیری قوی تک‌نسخه‌ای، 329
subject، predicate و object، 55
مشترکان جریان پیام، 440؛ بنگرید نیز به مصرف‌کننده
ابررایانه‌ها، 275
نظارت، 537؛ بنگرید نیز به حریم خصوصی
Swagger، 133
swap به دیسک، بنگرید به حافظهٔ مجازی
شبکهٔ همگام، 285، 557؛ مقایسه با ناهمگام، 284؛ مدل رسمی، 307
همانندسازی همگام، 154، 557؛ زنجیره‌ای، 155؛ تشخیص تعارض، 172
مدل سیستم، 300، 306–310؛ فرض‌ها، 528؛ درستی الگوریتم، 308؛ واقعیت، 309؛ ایمنی و زنده‌بودن، 308
سامانهٔ ثبت، 386، 557؛ ثبت تغییر، 454، 491؛ گزارش رویداد، 460
تفکر سیستمی، 536

T–Z

t-digest، 16
پیوند جدول–جدول، 474
Tableau، 416
tail، 447
رأس دُم، 51
Tajo، 93
Tandem NonStop SQL، 200
TCP، 277؛ در برابر مدارگزینی، 285؛ در برابر UDP، 283؛ قطع اتصال، 280؛ کنترل جریان، 282، 441؛ checksum، 306، 519، 529؛ قابلیت اطمینان و حذف تکرار، 517؛ مهلت ارسال مجدد، 284؛ نشست تراکنش، 229
تله‌متری، بنگرید به پایش
Teradata، 93، 200
نمایهٔ پارتیشن‌شده بر اساس اصطلاح، 208، 217
خاتمهٔ اجماع، 365
Terrapin، 413
Tez، 421–423؛ تحمل خطا، 422؛ ابزار سطح‌بالا، 427
thrashing حافظه، 297
رشته‌ها؛ مدل کنشگر، 138، 468؛ عملیات اتمی، 223؛ پس‌زمینه، 73، 85؛ توقف اجرا، 286، 296–298؛ مانع حافظه، 338؛ پیش‌دستی، 298؛ تک‌رشته‌ای
ثبت سه‌فازی، 359
Thrift، 117–121؛ BinaryProtocol، 118؛ CompactProtocol، 119؛ برچسب فیلد و تکامل شِما، 120
توان عملیاتی، 13، 390
TIBCO؛ Enterprise Message Service، 444؛ StreamBase، 466
زمان؛ هم‌روندی، 187؛ وابستگی میان کانال، 331؛ سامانهٔ توزیع‌شده، 287–299؛ همگام‌سازی و دقت ساعت، 289؛ اتکا به ساعت همگام، 291–295؛ توقف فرایند، 295–299؛ استدلال در جریان، 468–472؛ زمان رویداد/پردازش، 469، 477، 498؛ آمادگی پنجره، 470؛ مُهر رویداد، 471؛ انواع پنجره، 472؛ مدل توزیع‌شده، 307؛ وابستگی زمانی پیوند، 475
ساعت روز، 288
بهنگامی، 524؛ پرهیز از هماهنگی، 528؛ درستی جریان داده، 525
مهلت زمانی، 279، 557؛ پیکربندی پویا، 284؛ جابجایی پس از خرابی، 158؛ طول، 281
مُهرهای زمانی، 343؛ رویداد جریان، 471؛ خواندن پس از نوشتن، 163؛ ترتیب تراکنش، 295؛ ناکافی برای قید، 347؛ بازهٔ کلید، 203؛ Lamport، 345؛ منطقی، 494؛ ترتیب رویداد، 291، 345
Titan، 50
سنگ‌قبرها، 74، 191، 456
موضوع‌های پیام‌رسانی، 137، 440
ترتیب کل، 341، 557؛ محدودیت، 493؛ شمارهٔ توالی/مُهر، 344
انتشار ترتیب کل، 348–352، 493، 522؛ اجماع، 366–368؛ ZooKeeper و etcd، 370؛ ذخیره‌سازی خطی‌پذیر، 351؛ استفاده، 349؛ پیاده‌سازی خطی‌پذیری، 350
ردیابی دادهٔ رفتاری، 536؛ بنگرید نیز به حریم خصوصی
هماهنگ‌کننده/مدیر تراکنش، بنگرید به هماهنگ‌کننده
پردازش تراکنش، 28، 90–95؛ در برابر تحلیل، 91؛ انبار داده، 93
تراکنش‌ها، 221–267، 558؛ ACID، 223–226؛ جبرانی؛ مفهوم، 222؛ توزیع‌شده، 352–364؛ اجتناب، 492، 502، 521–528؛ تقویت شکست، 364، 495؛ نامعین، 358، 362؛ 2PC، 354–359؛ XA، 361–364؛ OLTP در برابر تحلیل، 411؛ هدف، 222؛ سریال‌پذیری، 251–266؛ اجرای سریال، 252–256؛ کنترل بدبینانه/خوش‌بینانه، 261؛ SSI، 261–266؛ 2PL، 257–261؛ تک‌شیئی و چندشیئی، 228–232؛ خطا و لغو، 231؛ نوشتن تک‌شیئی، 230؛ جداسازی ضعیف، 233–251؛ جلوگیری از به‌روزرسانی گم‌شده، 242–246؛ read committed، 234–238
بستار تعدی گراف، 424
trie، 88
تریگر پایگاه داده، 161، 441؛ ثبت تغییر، 455؛ همانندسازی، 161
triple-store، 55–59؛ SPARQL، 59
پنجرهٔ غلتان، 472؛ بنگرید نیز به پنجره‌ها؛ میکروبچ، 477
فضای tuple، 507
Turtle، 56
Twitter؛ timeline خانه، 11، 462، 474، 511؛ DistributedLog، 448؛ Finagle، 135؛ Snowflake، 294؛ Summingbird، 497
ثبت دوفازی، 353، 355–359، 558؛ تفاوت با قفل‌گذاری دوفازی، 356؛ خرابی هماهنگ‌کننده، 358؛ بازیابی، 363؛ عملکرد، 357؛ مسائل عملی، 363؛ هزینه، 360؛ نگهداشت قفل، 362
قفل‌گذاری دوفازی، 257–261، 329، 558؛ تفاوت با ثبت دوفازی، 356؛ قفل بازه، 260؛ کارایی، 258
بررسی نوع پویا/ایستا، 40
UDP؛ مقایسه با TCP، 283؛ multicast، 442
مجموعه‌دادهٔ بی‌کران، 439، 558؛ بنگرید نیز به جریان‌ها
تأخیر بی‌کران، 558؛ شبکه، 282؛ توقف فرایند، 296
جداسازی پایگاه داده، 499–515؛ ترکیب فناوری ذخیره‌سازی، 499–504؛ فدراسیون در برابر جداسازی، 501؛ نیاز به زبان سطح‌بالا، 503؛ طراحی حول جریان داده، 504–509؛ مشاهدهٔ وضعیت، 509–515؛ نما و نهانگاه، 510؛ چندپارتیشنی، 514؛ ارسال تغییر به مشتری، 512
وضعیت نامطمئن تراکنش، بنگرید به نامعین
اجماع یکنواخت، 365؛ بنگرید نیز به اجماع
رابط یکنواخت، 395
نوع union در Avro، 125
uniq، 392
قید یکتایی؛ بررسی ناهمگام، 526؛ نیازمند اجماع، 521؛ خطی‌پذیری، 330؛ پیام‌رسانی گزارش‌محور، 522
فلسفهٔ Unix، 394–397؛ پردازش دسته‌ای خط فرمان، 391–394؛ pipe در برابر موتور جریان داده، 423؛ مقایسه با Hadoop، 413–414؛ پایگاه دادهٔ رابطه‌ای، 499، 501؛ جریان، 464؛ ترکیب‌پذیری و رابط یکنواخت، 395؛ اتصال سست، 396؛ pipe، 394؛ ارتباط با Hadoop، 499
UPDATE در SQL، 40
به‌روزرسانی‌ها؛ جلوگیری از گم‌شدن، 242–246؛ نوشتن اتمی، 243؛ تشخیص خودکار، 245؛ compare-and-set، 245؛ حل تعارض و همانندسازی، 246؛ قفل صریح، 244؛ جلوگیری از چولگی نوشتن، 246–251
اعتبار اجماع، 365
vBucket، 199
ساعت برداری، 191؛ بنگرید نیز به بردار نسخه
پردازش برداری، 99، 428
راستی‌آزمایی، 528–533؛ پرهیز از اعتماد کور، 530؛ فرهنگ، 530؛ طراحی ممیزی‌پذیر، 531؛ بررسی یکپارچگی سرتاسری، 531؛ ابزار، 532
کنترل نسخه و دادهٔ تغییرناپذیر، 463
بردار نسخه، 177، 191؛ وابستگی علّی، 343؛ در برابر ساعت برداری، 191
Vertica، 93؛ رسیدگی به نوشتن، 101؛ ترتیب متفاوت همتاها، 100
مقیاس‌افزایی عمودی، بنگرید به scale up
رأس‌های گراف، 49؛ گراف ویژگی، 50
Viewstamped Replication، 366؛ شمارهٔ نما، 368
ماشین مجازی، 146؛ تعویض زمینه، 297؛ شبکه، 282؛ همسایهٔ پرسر‌وصدا، 284؛ قابلیت اطمینان ابر، 8؛ ساعت مجازی، 290
حافظهٔ مجازی؛ توقف page fault، 14، 297؛ در برابر مدیریت حافظهٔ پایگاه داده، 89
VisiCalc، 504
vnode، 199
VoIP، 283
Voldemort؛ ذخیره‌گاه فقط‌خواندنی دسته‌ای، 413؛ هش، 203–204، 211؛ بی‌رهبر، 177؛ چندمرکز داده، 184؛ توازن، 213؛ ترمیم خواندن، 179؛ حدنصاب سهل‌گیرانه، 184
VoltDB؛ سریال‌پذیری میان پارتیشن، 256؛ رویهٔ قطعی، 255؛ درون‌حافظه، 89؛ جریان خروجی، 456؛ نمایهٔ ثانویه، 207؛ اجرای سریال، 253؛ همانندسازی مبتنی بر دستور، 159، 479؛ تراکنش جریانی، 477
WAL، 82
وب‌سرویس، بنگرید به خدمات
WSDL، 133
webhook، 443
webMethods، 137
WebSocket، 512
پنجره‌های پردازش جریانی، 466، 468–472؛ پنجرهٔ بی‌نهایت، 467، 474؛ رسیدن همهٔ رویدادها، 470؛ پیوند در پنجره، 473؛ انواع، 472
برندگان حل تعارض، 173
WITH RECURSIVE، 54
گردش کار MapReduce، 402؛ خروجی، 411–414؛ کلید–مقدار، 412؛ نمایهٔ جست‌وجو، 411؛ پیوند سمت map، 410
مجموعهٔ کاری، 393
تقویت نوشتن، 84
مسیر نوشتن دادهٔ مشتق‌شده، 509
چولگی نوشتن، 246–251؛ توصیف، 246–251، 262؛ نمونه‌ها، 247، 249؛ مادی‌سازی تعارض، 251؛ وقوع عملی، 529؛ شبح، 250؛ جلوگیری با اسنپ‌شات، 262–265؛ 2PL، 259–261؛ گزینه‌ها، 248
گزارش پیش‌نوشتن، 82، 159
نوشتن‌های پایگاه داده؛ اتمی، 243؛ تشخیص اثر بر خواندن قبلی، 264؛ جلوگیری از نوشتن کثیف با read committed، 235
WS-*، 133؛ بنگرید نیز به خدمات
WS-AtomicTransaction، 355
تراکنش XA، 355، 361–364؛ تصمیم ابتکاری، 363؛ محدودیت، 363
xargs، 392، 396
XML؛ گونهٔ دودویی، 115؛ RDF، 57؛ مسائل دادهٔ برنامه، 114؛ پایگاه دادهٔ رابطه‌ای، 30، 41
XSL/XPath، 45
Yahoo!؛ Pistachio، 461؛ Sherpa، 455
YARN، 416، 506؛ پیش‌دستی کار، 418؛ ZooKeeper، 370
Zab، 366؛ در ZooKeeper، 353
ZeroMQ، 442
ZooKeeper، 370–373؛ تولید توکن حصار، 303، 349، 370؛ عملیات خطی‌پذیر، 333، 351؛ قفل و انتخاب رهبر، 330؛ کشف خدمت، 372؛ تخصیص پارتیشن، 215، 371؛ الگوریتم Zab، 349، 353، 366

دربارهٔ نویسنده

مارتین کلپمن (Martin Kleppmann) پژوهشگر سیستم‌های توزیع‌شده در دانشگاه کمبریج بریتانیاست. پیش‌تر مهندس نرم‌افزار و کارآفرین در شرکت‌های اینترنتی از جمله LinkedIn و Rapportive بود و روی زیرساخت داده در مقیاس بزرگ کار کرد. در این مسیر چیزهایی را به‌سختی آموخت و امیدوار است این کتاب شما را از تکرار همان اشتباهات نجات دهد.

مارتین سخنران منظم کنفرانس، وبلاگ‌نویس و مشارکت‌کنندهٔ متن‌باز است. باور دارد ایده‌های فنی عمیق باید برای همه در دسترس باشند و درک عمیق‌تر به ما کمک می‌کند نرم‌افزار بهتری بسازیم.

کولوفون

حیوان روی جلد Designing Data-Intensive Applications گراز وحشی هندی (Sus scrofa cristatus) است؛ زیرگونه‌ای از گراز وحشی که در هند، میانمار، نپال، سری‌لانکا و تایلند یافت می‌شود. آن‌ها از گرازهای اروپایی با خارهای پشت بلندتر، بدون پوشش پشمی زیرین و جمجمهٔ بزرگ‌تر و صاف‌تر متمایزند.

گراز وحشی هندی پوششی از موی خاکستری یا سیاه دارد و خارهای سفت در امتداد ستون فقرات. نرها دندان نیش برآمده (tushes) دارند که برای جنگ با رقبا یا دفاع در برابر شکارچیان به‌کار می‌رود. نرها از ماده‌ها بزرگ‌ترند، اما این گونه به‌طور میانگین ۳۳ تا ۳۵ اینچ قد در شانه و ۲۰۰ تا ۳۰۰ پوند وزن دارد. شکارچیان طبیعی آن‌ها شامل خرس، ببر و گربه‌های بزرگ مختلف است.

این حیوانات شب‌زی و همه‌چیزخوارند — طیف وسیعی از ریشه‌ها، حشرات، لاشه، آجیل، توت‌ها و حیوانات کوچک می‌خورند. گرازهای وحشی همچنین برای غربال کردن زباله و مزارع شناخته شده‌اند و خسارت زیادی وارد می‌کنند و دشمنی کشاورزان را برمی‌انگیزند. روزانه به ۴۰۰۰ تا ۴۵۰۰ کالری نیاز دارند. گرازها حس بویایی بسیار توسعه‌یافته دارند که در جستجوی مواد گیاهی زیرزمینی و حیوانات لانه‌کننده کمک می‌کند، اما بینایی ضعیفی دارند.

گرازهای وحشی مدت‌ها در فرهنگ انسان اهمیت داشته‌اند. در اسطورهٔ هندو، گراز تجسم خدای ویشنو است. در یادبودهای تدفینی یونان باستان، نماد بازندهٔ شجاع (در تضاد با شیر پیروز) بود. به‌خاطر پرخاشگری، روی زره و سلاح جنگجویان اسکاندیناوی، ژرمنی و آنگلو-ساکسون به تصویر کشیده می‌شد. در طالع‌بینی چینی، نماد عزم و تکانش‌گری است.

بسیاری از حیوانات روی جلدهای O'Reilly در معرض انقراض‌اند؛ همهٔ آن‌ها برای جهان مهم‌اند. برای آشنایی بیشتر با نحوهٔ کمک، به animals.oreilly.com بروید.

تصویر جلد از Shaw's Zoology است. فونت‌های جلد URW Typewriter و Guardian Sans هستند. فونت متن Adobe Minion Pro است؛ فونت نمودارها Adobe Myriad Pro؛ فونت سرتیترها Adobe Myriad Condensed؛ و فونت کد Dalton Maag's Ubuntu Mono است.