ویرگول
ورودثبت نام
Setare Behzadi
Setare Behzadiمهندسی نرم افزار و توسعه دهنده وب | نکاتی در مورد وب که فکر میکنم میتونه واسه خیلی ها مناسب باشد رو منتشر میکنم.
Setare Behzadi
Setare Behzadi
خواندن ۸ دقیقه·۳ روز پیش

خلاصه کتاب Designing Data-Intensive Applications

فصل ۲- Query Languages for Data- قسمت۴

پرس‌وجو با MapReduce

MapReduce یک مدل برنامه‌نویسی برای پردازش حجمیِ مقدار زیادی داده به‌صورت دسته‌ای (bulk) روی تعداد زیادی ماشین است که توسط گوگل محبوب شد. شکل محدودی از MapReduce توسط برخی دیتابیس‌های NoSQL از جمله MongoDB و CouchDB پشتیبانی می‌شود، به‌عنوان مکانیزمی برای انجام پرس‌وجوهای فقط‌خواندنی (read-only) روی تعداد زیادی سند.

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

MapReduce نه یک زبان پرس‌وجوی کاملاً اعلانی (declarative) است و نه یک API پرس‌وجوی کاملاً امری (imperative)، بلکه جایی بین این دو قرار می‌گیرد: منطق پرس‌وجو با تکه‌کدهایی بیان می‌شود که توسط چارچوب پردازش به‌صورت تکرارشونده فراخوانی می‌شوند. این مدل بر پایه‌ی توابع map (که collect هم نامیده می‌شود) و reduce (که fold یا inject نیز گفته می‌شود) است؛ توابعی که در بسیاری از زبان‌های برنامه‌نویسی تابعی وجود دارند.

برای مثال، تصور کنید یک زیست‌شناس دریایی هستید و هر بار که حیوانی را در اقیانوس مشاهده می‌کنید، یک رکورد مشاهده (observation) به دیتابیس خود اضافه می‌کنید. حالا می‌خواهید گزارشی تولید کنید که نشان دهد در هر ماه چند کوسه دیده‌اید.

در PostgreSQL می‌توانید این پرس‌وجو را به این شکل بنویسید:

SELECT date_trunc('month', observation_timestamp) AS observation_month, sum(num_animals) AS total_animals FROM observations WHERE family = 'Sharks' GROUP BY observation_month;

تابع date_trunc('month', timestamp) ماه تقویمی‌ای را که timestamp در آن قرار دارد مشخص می‌کند و timestamp دیگری را که نشان‌دهنده‌ی ابتدای آن ماه است برمی‌گرداند. به عبارت دیگر، این تابع یک timestamp را به نزدیک‌ترین ماهِ پایین‌تر گرد می‌کند.

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

همین کار را می‌توان با قابلیت MapReduce در MongoDB به شکل زیر بیان کرد:

db.observations.mapReduce( function map() { var year = this.observationTimestamp.getFullYear(); var month = this.observationTimestamp.getMonth() + 1; emit(year + "-" + month, this.numAnimals); }, function reduce(key, values) { return Array.sum(values); }, { query: { family: "Sharks" }, out: "monthlySharkReport" } );

فیلتر مربوط به در نظر گرفتن فقط گونه‌های کوسه می‌تواند به‌صورت اعلانی مشخص شود (این یک افزونه‌ی مخصوص MongoDB به MapReduce است).

تابع JavaScriptِ map یک بار برای هر سندی که با query مطابقت دارد فراخوانی می‌شود، به‌طوری که this به شیء سند اشاره می‌کند.

تابع map یک کلید (رشته‌ای شامل سال و ماه، مثل "2013-12" یا "2014-1") و یک مقدار (تعداد حیوانات در آن مشاهده) را emit می‌کند.

جفت‌های کلید–مقدارِ تولیدشده توسط map بر اساس کلید گروه‌بندی می‌شوند. برای تمام جفت‌های کلید–مقداری که کلید یکسانی دارند (یعنی همان ماه و سال)، تابع reduce یک بار فراخوانی می‌شود.

تابع reduce تعداد حیوانات مربوط به همه‌ی مشاهده‌های یک ماه مشخص را با هم جمع می‌کند.

خروجی نهایی در کالکشنی به نام monthlySharkReport نوشته می‌شود.

برای مثال، فرض کنید کالکشن observations شامل این دو سند باشد:

{ observationTimestamp: Date.parse("Mon, 25 Dec 1995 12:34:56 GMT"), family: "Sharks", species: "Carcharodon carcharias", numAnimals: 3 } { observationTimestamp: Date.parse("Tue, 12 Dec 1995 16:17:18 GMT"), family: "Sharks", species: "Carcharias taurus", numAnimals: 4 }

در این حالت، تابع map یک بار برای هر سند فراخوانی می‌شود و در نتیجه emit("1995-12", 3) و emit("1995-12", 4) تولید می‌شود. سپس تابع reduce با reduce("1995-12", [3, 4]) فراخوانی شده و مقدار ۷ را برمی‌گرداند.

توابع map و reduce تا حدی در کارهایی که اجازه دارند انجام دهند محدود هستند. آن‌ها باید توابعی خالص (pure functions) باشند؛ یعنی فقط از داده‌ای که به‌عنوان ورودی به آن‌ها داده می‌شود استفاده کنند، نمی‌توانند پرس‌وجوی دیگری به دیتابیس بزنند و نباید هیچ اثر جانبی (side effect) داشته باشند. این محدودیت‌ها به دیتابیس اجازه می‌دهد این توابع را در هر جایی، با هر ترتیبی اجرا کند و در صورت بروز خطا دوباره آن‌ها را اجرا کند. با این حال، این توابع همچنان قدرتمند هستند: می‌توانند رشته‌ها را پردازش کنند، توابع کتابخانه‌ای را فراخوانی کنند، محاسبات انجام دهند و کارهای بیشتری انجام دهند.

MapReduce یک مدل برنامه‌نویسی نسبتاً سطح پایین برای اجرای توزیع‌شده روی خوشه‌ای از ماشین‌ها است. زبان‌های پرس‌وجوی سطح بالاتر مانند SQL را می‌توان به‌صورت یک خط لوله (pipeline) از عملیات MapReduce پیاده‌سازی کرد (به فصل ۱۰ مراجعه کنید)، اما پیاده‌سازی‌های توزیع‌شده‌ی زیادی از SQL وجود دارند که از MapReduce استفاده نمی‌کنند. توجه کنید که در خود SQL هیچ چیزی وجود ندارد که آن را محدود به اجرا روی یک ماشین واحد کند، و MapReduce هم انحصار اجرای پرس‌وجوهای توزیع‌شده را در اختیار ندارد.

امکان استفاده از کد JavaScript در میانه‌ی یک پرس‌وجو ویژگی بسیار خوبی برای پرس‌وجوهای پیشرفته است، اما این قابلیت فقط محدود به MapReduce نیست—برخی دیتابیس‌های SQL نیز می‌توانند با توابع JavaScript توسعه داده شوند.

یکی از مشکلات کاربری MapReduce این است که باید دو تابع JavaScript که با دقت با هم هماهنگ شده‌اند بنویسید، که اغلب سخت‌تر از نوشتن یک پرس‌وجوی واحد است. علاوه بر این، یک زبان پرس‌وجوی اعلانی فرصت‌های بیشتری به بهینه‌ساز پرس‌وجو می‌دهد تا عملکرد پرس‌وجو را بهبود دهد. به همین دلایل، MongoDB در نسخه‌ی 2.2 پشتیبانی از یک زبان پرس‌وجوی اعلانی به نام aggregation pipeline را اضافه کرد.

در این زبان، همان پرس‌وجوی شمارش کوسه‌ها به شکل زیر نوشته می‌شود:

db.observations.aggregate([

{ $match: { family: "Sharks" } },

{ $group: {

_id: {

year: { $year: "$observationTimestamp" },

month: { $month: "$observationTimestamp" }

},

totalAnimals: { $sum: "$numAnimals" }

}}

]);

زبان aggregation pipeline از نظر قدرت بیان شبیه به زیرمجموعه‌ای از SQL است، اما به‌جای نحوِ جمله‌مانند SQL از نحو مبتنی بر JSON استفاده می‌کند؛ تفاوتی که شاید بیشتر به سلیقه برگردد. پیام نهایی داستان این است که یک سیستم NoSQL ممکن است ناخواسته در حال بازآفرینی SQL باشد، هرچند در لباسی مبدل.

Graph-Like Data Models

این بخش دقیقاً جاییه که انتخاب مدل داده از «کدنویسی» می‌ره توی «تفکر معماری».

ایده‌ی اصلی در یک جمله

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

1️⃣ اول مسئله رو درست بفهمیم

سه نوع رابطه‌ی رایج بین داده‌ها

1. بدون رابطه یا خیلی کم

مثلاً:

  • لاگ‌ها

  • eventها

  • پیام‌ها

📌 Document DB عالیه (Mongo)


2. One-to-Many (درختی)

مثلاً:

  • بلاگ → پست‌ها → کامنت‌ها

  • سفارش → آیتم‌ها

📌 Document Model هنوز خوبه

{ "post": "Graph DB", "comments": [ { "text": "good" }, { "text": "nice" } ] }

3. Many-to-Many (اینجا درد شروع می‌شه)

مثلاً:

  • کاربر ↔ گروه

  • دانشجو ↔ درس

  • آدم‌ها ↔ آدم‌ها (دوستی، ازدواج، فالو)

📌 اینجا Relational سخت می‌شه
📌 Document تقریباً شکست می‌خوره
📌 Graph می‌درخشه

2️⃣ چرا Many-to-Many با Document خوب نیست؟

مثال:

  • یک کاربر در ۱۰۰ گروه

  • هر گروه ۱۰۰۰ کاربر

اگر embed کنی:

  • دیتا تکراری

  • آپدیت کابوس

  • consistency مشکل

اگر reference بدی:

  • query پیچیده

  • join دستی

  • performance بد


3️⃣ Relational چقدر جواب می‌ده؟

Relational می‌گه:

users groups user_groups

تا اینجا اوکیه 👌
اما وقتی:

  • depth زیاد می‌شه

  • رابطه روی رابطه می‌خوای

  • traversal می‌خوای

مثلاً:

دوستِ دوستِ دوستِ من کیه؟

SQL شروع می‌کنه به زجر دادن.


4️⃣ اینجا گراف وارد می‌شه 🔥

گراف از چی تشکیل شده؟

Vertex (Node)

  • آدم

  • شهر

  • پست

  • رویداد

Edge (Relation)

  • دوستِ

  • ازدواج با

  • زندگی می‌کند در

  • شرکت کرده در

5️⃣ مثال ساده از گراف

(Lucy) --[married_to]-- (Alain) | | [lives_in] [lives_in] | | (London) (London)

همه چیز رابطه‌محوره.

6️⃣ چرا گراف طبیعی‌تره؟

چون سؤال‌هامون رابطه‌ایه:

  • لوسی کجا زندگی می‌کنه؟

  • همسرش کیه؟

  • دوستان همسرش کیان؟

  • کیا توی لندن هستن و متأهلن؟

در گراف:

اینا traversal هستن، نه join


7️⃣ مثال Query (حسی، نه سینتکس)

SQL:

JOIN JOIN JOIN

Graph:

Lucy → married_to → Alain → lives_in → London

خوانا
طبیعی
نزدیک به مدل ذهنی انسان


8️⃣ کاربردهای واقعی Graph DB

1️⃣ شبکه‌های اجتماعی

  • دوست

  • فالو

  • لایک

  • کامنت

📌 Facebook, LinkedIn


2️⃣ Recommendation

  • اینو دیدی → اونا رو هم دیدن

  • دوستات چی دوست دارن؟

📌 Netflix, Spotify


3️⃣ Fraud Detection

  • ارتباط مشکوک بین حساب‌ها

  • پول از کجا به کجا می‌چرخه؟

📌 بانک‌ها


4️⃣ مسیر‌یابی

  • shortest path

  • cheapest route

📌 Google Maps


5️⃣ دانش‌نامه‌ها

  • Wiki

  • Knowledge Graph

📌 Google Search


9️⃣ نکته‌ی خیلی مهم

Graph DB برای «data with rich relationships» است، نه برای همه‌چیز

❌ جایگزین همه دیتابیس‌ها نیست
✔ کنار بقیه می‌درخشه

جمله‌ی طلایی مصاحبه 🎯

«وقتی پرسش‌های اصلی سیستم حول traversal رابطه‌ها می‌چرخد، Graph DB طبیعی‌ترین انتخاب است.»

1️⃣ دو مدل اصلی گراف دیتا

1️⃣ Property Graph Model

(Neo4j، Titan، InfiniteGraph)

تعریف ساده

در Property Graph:

  • Node (Vertex) داریم

  • Edge (Relationship) داریم

  • هر دو می‌تونن Property داشته باشن (key-value)


مثال ذهنی

(:Person {name: "Lucy", from: "Idaho"}) -[:MARRIED_TO {since: 2015}]-> (:Person {name: "Alain", from: "France"})

ویژگی مهم

  • Node و Edge هر دو دیتا دارن

  • Edge فقط لینک نیست، خودش معنا داره

📌 خیلی نزدیک به مدل ذهنی انسان
📌 برای شبکه‌های اجتماعی عالی


کاربرد واقعی

  • Social Network

  • Recommendation

  • Fraud detection


2️⃣ Triple-Store Model

(Datomic، AllegroGraph، RDF)

تعریف ساده

همه‌چیز می‌شه سه‌تایی:

(subject, predicate, object)

مثال

(Lucy, marriedTo, Alain) (Lucy, livesIn, London)

حتی ویژگی‌ها:

(Lucy, age, 30)

ویژگی مهم

  • ساختار خیلی عمومی

  • Schema آزاد

  • مناسب knowledge graph

📌 رسمی‌تر
📌 semantic web
📌 استاندارد محور

2️⃣ زبان‌های Query گراف (Declarative)

1️⃣ Cypher (برای Property Graph)

حس و حال

Cypher طوری طراحی شده که:

Query مثل نقاشی گراف نوشته بشه


مثال

MATCH (p:Person)-[:MARRIED_TO]->(s:Person) RETURN p.name, s.name

تقریباً شبیه جمله‌ی انگلیسی 😄

📌 خیلی خوانا
📌 محبوب برای Neo4j


2️⃣ SPARQL (برای Triple Store)

حس و حال

SPARQL رسمی و استاندارده (W3C)

SELECT ?person ?city WHERE { ?person :livesIn ?city . }

📌 شبیه SQL
📌 مناسب knowledge graph و وب معنایی


3️⃣ Datalog

تعریف ساده

Datalog یه زبان Rule-based است

ancestor(X, Y) :- parent(X, Y). ancestor(X, Y) :- parent(X, Z), ancestor(Z, Y).

📌 فکر کردن منطقی
📌 مناسب inference و تحلیل عمیق


3️⃣ Declarative بودن این زبان‌ها چرا مهمه؟

همون حرف‌های قبلی ولی در گراف:

✔ فقط می‌گی «چی می‌خوای»
✔ traversal رو engine انجام می‌ده
✔ optimizer مسیر رو انتخاب می‌کنه
✔ parallelization ممکنه


4️⃣ زبان‌های Imperative گراف (برای مقایسه)

Gremlin

g.V().has("name","Lucy").out("marriedTo").out("livesIn")

اینجا:

  • step به step می‌گی کجا بره

  • traversal مشخصه

📌 انعطاف‌پذیر
❌ سخت بهینه‌سازی
❌ وابسته به ترتیب


Prege (مثل Pregel)

  • پردازش گراف‌های خیلی بزرگ

  • الگوریتم محور (PageRank)

📌 analytics
❌ query روزمره نیست


5️⃣ چه موقع کدومو انتخاب کنیم؟ (خیلی مهم)

Property Graph + Cypher

✔ اپلیکیشن
✔ query تعاملی
✔ business logic


Triple Store + SPARQL

✔ knowledge graph
✔ ontology
✔ semantic data


Gremlin / Pregel

✔ تحلیل سنگین
✔ batch processing
✔ الگوریتم‌های گرافی


6️⃣ جمع‌بندی سینیوری نهایی

Graph DB خودش یک دنیا است:
مدل داده + زبان پرس‌وجو + فلسفه‌ی فکر کردن.
برای query روزمره، declarative؛ برای الگوریتم، imperative.


جمله‌ی طلایی مصاحبه 🎯

«Property graph برای اپلیکیشن‌ها طبیعی‌تر است، در حالی که triple-store برای داده‌های معنایی و inference مناسب‌تر است.»

گرافمدل ذهنی
۰
۰
Setare Behzadi
Setare Behzadi
مهندسی نرم افزار و توسعه دهنده وب | نکاتی در مورد وب که فکر میکنم میتونه واسه خیلی ها مناسب باشد رو منتشر میکنم.
شاید از این پست‌ها خوشتان بیاید