۱1 مدل رابطهای در برابر مدل سندیRelational vs document model
در مدل رابطهای، داده در جدولها و ارتباط میان آنها نگه داشته میشود. در مدل سندی، اطلاعات مرتبط میتواند داخل یک سندِ تودرتو قرار بگیرد.
مثال فنی: دو نمایش یک سابقهٔ شغلی
وضعیت اولیه: مریم با person_id=17 در شرکت 7 کار کرده است.
اجرای مرحلهبهمرحله: رابطهای: ردیف در people و ردیف در jobs با کلید خارجی؛ سندی: آرایهٔ jobs داخل سند مریم.
نتیجه و دلیل: سند خواندن یکجای رزومه را ساده میکند؛ گزارش همهٔ کارمندان شرکت ممکن است ایندکس و پردازش متفاوتی بخواهد.
The relational model (Codd, 1970) keeps data in relations (in SQL: tables) with unordered rows. It dominated by the mid-80s
and still rules business analytics. Challengers came and went: network and hierarchical models, object databases, XML databases;
each made noise and never stuck. Instead SQL absorbed them (XML, JSON, graph).
The 2010s NoSQL wave popularized document models, schema flexibility and scale-out, while
NewSQL chased NoSQL scalability with relational transactional guarantees.
ناهماهنگی شیء-رابطهای (Impedance Mismatch) و ORMObject-relational impedance mismatch and ORMs
ORM واسطهای است که اشیای برنامه را به جدولها و ردیفها تبدیل میکند. این واسطه نوشتن کد را آسانتر میکند، اما تفاوتِ شکل اشیا و جدولها را از بین نمیبرد.
مثال فنی: N+1 در صفحهٔ رزومهها
وضعیت اولیه: ORM ابتدا ۱۰۰ متقاضی را با یک SQL میخواند.
اجرای مرحلهبهمرحله: دسترسی lazy به person.jobs برای هر متقاضی یک SQL جدا میزند: مجموعاً ۱۰۱ پرسوجو؛ preload میتواند رابطهها را دستهای بخواند.
نتیجه و دلیل: کد کوتاه لزوماً مسیر کمهزینه نیست؛ تعداد query و دادهٔ برگرداندهشده باید دیده شود.
Since most development is object-oriented, storing data in tables needs an awkward translation layer; the gap is called
impedance mismatch (a term borrowed from electronics). ORM frameworks
(like Hibernate and ActiveRecord) slim this layer down, but the criticisms are serious:
- پیچیدهاند و نمیتوانند تفاوت دو مدل را کامل پنهان کنند.
- فقط برای OLTPاند؛ مهندس داده همچنان با نمایش رابطهایِ زیرین کار دارد، پس طراحی اسکیما مهم میماند.
- در سیستمهای متنوع (موتور جستوجو، گراف، NoSQL) معمولاً پشتیبانی ندارند.
- اسکیمای تولیدشان میتواند هم برای کاربرِ مستقیمِ داده ناخوشایند باشد و هم ناکارآمد.
- بهسادگی کوئری ناکارآمد میسازند؛ مثال معروف: مسئلهٔ N+1 — یک کوئری برای N نظر، و بعد N کوئری جدا برای نویسندهٔ هر نظر،
در حالی که یک JOIN این کار را یکجا میکرد.
- They are complex and cannot fully hide the two models’ differences.
- They serve OLTP only; the data engineer still works with the underlying relational representation, so schema design stays important.
- They usually lack support across diverse systems (search engines, graphs, NoSQL).
- Their generated schemas can be both unpleasant for direct data users and inefficient.
- They easily generate inefficient queries; the famous case: the N+1 problem — one query for N comments, then N separate queries for each comment’s author,
where one JOIN would have done it at once.
مدل سندی: رزومه بهصورت یک درختThe document model: a resume as a tree
یک سند برای دادهای مناسب است که بهشکل یک مجموعهٔ تودرتو خوانده میشود. این سادگی زمانی کمتر میشود که بخشهای سند میان افراد زیادی مشترک باشند.
مثال فنی: Embedding تا کجا مناسب است؟
وضعیت اولیه: رزومه دو سابقهٔ شغلی دارد و هر دو در آرایهٔ jobs ذخیرهاند.
اجرای مرحلهبهمرحله: خواندن سند همهٔ رزومه را میآورد؛ اگر نام شرکت در ۱۰ هزار رزومه کپی شده باشد، تغییر نام به اصلاح همان ۱۰ هزار نسخه نیاز دارد.
نتیجه و دلیل: بخشهای متعلق به یک رزومه برای embedding مناسباند؛ هویت مشترک شرکت معمولاً مرجع مستقل میخواهد.
Not all data is relational. The book’s example: a resume (a career-network profile). Fields like name appear once,
but “job history”, “education” and “contact info” each hold several items. Two fixes:
- Shredding (تکهتکه کردن): هر بخش در جدول جدا با کلید خارجی به جدول
users.
- Embedding (توکار کردن): همه در یک سند JSON — یک درختِ یکبهچند که دقیقاً با ساختار شیء در کد یکی است.
- Shredding: each section in its own table with a foreign key to
users.
- Embedding: everything in one JSON document — a one-to-many tree identical to the in-code object structure.
{
"user_id": 251,
"first_name": "Barack",
"last_name": "Obama",
"positions": [
{"job_title": "President", "organization": "United States of America"},
{"job_title": "US Senator (D-IL)", "organization": "United States Senate"}
],
"education": [
{"school_name": "Harvard University", "start": 1988, "end": 1991}
],
"contact_info": {"website": "https://barackobama.com"}
}
مزیت سند: locality — هرچه برای نمایش یک پروفایل لازم است یکجاست و یک خواندن کافی است؛
در مدل رابطهای یا باید چند کوئری بزنی یا یک join چندطرفهٔ درهموبرهم.
اما اگر واقعاً آیتمهای مرتبط «تعدادشان زیاد» باشد (مثلاً هزاران نظر زیر یک پست)، توکار کردن در سند ناخوشایند میشود.
The document’s edge: locality — everything a profile page needs sits together, so one read suffices;
in the relational model you either issue several queries or one tangled multi-way join.
But when related items truly run “large” (e.g. thousands of comments under a post), embedding in a document turns unpleasant.
روایت مدرس از نمودارTeacher narration
مثال فنی برای خواندنِ نمودار: Embedding تا کجا مناسب است؟
وضعیت اولیه: رزومه دو سابقهٔ شغلی دارد و هر دو در آرایهٔ jobs ذخیرهاند.
اجرای مرحلهبهمرحله: خواندن سند همهٔ رزومه را میآورد؛ اگر نام شرکت در ۱۰ هزار رزومه کپی شده باشد، تغییر نام به اصلاح همان ۱۰ هزار نسخه نیاز دارد.
نتیجه و دلیل: بخشهای متعلق به یک رزومه برای embedding مناسباند؛ هویت مشترک شرکت معمولاً مرجع مستقل میخواهد.
دو کارت را کنار هم بخوانید. چپ (Shredding): سه جدولِ کوچکِ users و positions و education که با کلید خارجی به هم گره خوردهاند؛ خواندن یعنی JOIN در هر بار، نوشتن یعنی یک ردیف در هر بار. راست (Embedding): یک سند JSON که همهچیز را در خود دارد؛ خواندن یعنی یک جستوجو، نوشتن یعنی بازنویسی و کپیها. جملهٔ پایین حکم انتخاب است: درختِ یکبهچند را توکار کن، وبِ چندبهچند را ارجاع بده.
Read the two cards side by side. Left (Shredding): three small tables — users, positions, education — tied by foreign keys; reading means a JOIN every time, writing means one row at a time. Right (Embedding): one JSON document holding everything; reading means a single lookup, writing means rewrites and copies. The bottom line is the selection rule: embed a one-to-few tree, reference a many-to-many web.
نرمالسازی در برابر نرمالزداییNormalization vs denormalization
ایدهٔ کلیدیKey idea
نرمالسازی یعنی اطلاعات مشترک را یکجا نگه داریم و با شناسه به آن ارجاع بدهیم. نرمالزدایی یعنی برای آسانتر شدن خواندن، بخشی از داده را تکرار کنیم.
مثال فنی: نشانیِ فعلی و نشانیِ تاریخی
وضعیت اولیه: نشانی فعلی مشتری در جدول customers است؛ سفارش ۴۲ به نشانی زمان خرید نیاز دارد.
اجرای مرحلهبهمرحله: سفارش یک snapshot از نشانی ارسال نگه میدارد؛ تغییر نشانی فعلی، سفارش تحویلشدهٔ قدیمی را بازنویسی نمیکند.
نتیجه و دلیل: این تکرار داده عمدی است؛ هر کپی معنای زمانی جدا دارد و نباید بیفکر با مرجع فعلی همگام شود.
If you keep region_id (not the text “Washington, DC”), your data is normalized:
human-meaningful information is stored exactly once, and everything else references it by ID.
Copying the text everywhere is denormalized. An ID means nothing to humans, so it never changes;
but anything meaningful may change tomorrow — and then every copy must be updated.
| نرمالشده (ID + join) | دنرمالشده (کپی متن) |
| نوشتن | ارزانتر: فقط یک نسخه | گرانتر: چند نسخه برای بهروزرسانی |
| خواندن | کندتر: نیاز به join | سریعتر: join کمتر |
| سازگاری | خودکار با تراکنش اتمی | خطر ناسازگاری اگر فرآیند وسط کار بمیرد |
| نمونه | OLTP (هر دو سریع باشند) | سیستمهای تحلیلی (بهروزرسانی دستهای) |
| Normalized (ID + join) | Denormalized (copied text) |
| Writes | Cheaper: a single copy | Pricier: several copies to update |
| Reads | Slower: needs a join | Faster: fewer joins |
| Consistency | Automatic with atomic transactions | Inconsistency risk if a process dies mid-way |
| Example | OLTP (both stay fast) | Analytical systems (batch updates) |
نکتههای ظریفSubtleties
- نرمالزدایی را مثل دادهٔ مشتقشده ببین (مثل فصل ۱): یعنی باید فرآیندی برای بهروزرسانیِ کپیهای اضافی بسازی.
- درسِ شبکهٔ اجتماعی (فصل ۲): تایملاینِ متریالایزشده در X فقط ID پست و فرستنده را نگه میدارد، نه متن و تعداد لایک را —
چون آنها سریع عوض میشوند. هنگام خواندن، این IDها را «آبرسانی» میکنند (hydrating IDs) که عملاً یک join در کد برنامه است.
- نتیجهٔ مهم: «join هنگام خواندن» ذاتاً مانع مقیاسپذیری نیست؛ آبرسانیِ IDها بهخوبی موازی میشود.
- تصمیمِ نرمالزدایی همیشه بدیهی نیست: بعضی چیزها را دنرمال کن و بعضی را نرمال نگه دار؛ بستگی به تناوبِ تغییر و هزینهٔ خواندن/نوشتن دارد.
- See denormalization as derived data (like Chapter 1): you must build a process that updates the extra copies.
- The social-network lesson (Chapter 2): X’s materialized timeline keeps only post and sender IDs, not text or like counts —
because those change fast. At read time these IDs get “hydrated” (hydrating IDs), effectively a join in application code.
- Key result: “join at read time” is not inherently a scalability blocker; hydrating IDs parallelizes well.
- The denormalization call is rarely obvious: denormalize some things, keep others normalized; it depends on change frequency and read/write cost.
یکبهچند، چندبهیک و چندبهچندOne-to-many, many-to-one and many-to-many
نوع رابطه مشخص میکند چند رکورد به چند رکورد دیگر وصل میشوند. بهتر است آن را با نیاز واقعی بشناسیم، نه فقط با شکل نمودار.
مثال فنی: کلید خارجی و جدول واسط
وضعیت اولیه: مشتری 17 دو سفارش 42 و 43 دارد؛ هر سفارش چند کالا دارد.
اجرای مرحلهبهمرحله: orders.customer_id رابطهٔ چندبهیک را میسازد؛ order_items(order_id, product_id, qty) سفارش و کالا را چندبهچند میکند.
نتیجه و دلیل: هر قلم میتواند qty مخصوص همان رابطه داشته باشد؛ گذاشتن آن فقط روی مشتری یا کالا معنای درستی ندارد.
- یکبهچند (one-to-few): رزومه چند موقعیت شغلی دارد — درختِ مناسبِ مدل سندی.
- چندبهیک: افراد زیادی در یک منطقه زندگی میکنند → منطقه را با ID ارجاع بده.
- چندبهچند: یک نفر در چند سازمان کار کرده و هر سازمان چند نفر دارد → در رابطهای با جدول انجمنی (join table)؛
در مدل سندی یا ارجاع به سند دیگر یا نگهداشتن ID در هر دو طرف (که خودش دنرمالشده و ناسازگاریساز است).
- راهحل نرمال: رابطه را یکجا نگه دار و با ایندکس ثانویه روی هر دو ستون، جستوجو در هر دو جهت را سریع کن.
- محدودیت سند: نمیتوانی به یک آیتمِ توکار مستقیماً ارجاع بدهی (باید بگویی «آیتم دومِ لیستِ positions کاربر ۲۵۱»)؛
و برای لیستهایی که کاربر جابهجاشان میکند (مثل to-do)، آرایهٔ JSON بسیار طبیعیتر از ترفندهای رابطهای است.
- One-to-few: a resume holds a few jobs — a tree fit for the document model.
- Many-to-one: many people live in one region → reference the region by ID.
- Many-to-many: one person worked at several organizations and each organization has many people → in relational, an association table (join table);
in documents, either reference another document or keep IDs on both sides (itself denormalized and inconsistency-prone).
- The normalized fix: keep the relationship in one place and speed up both directions with a secondary index on each column.
- Document limits: you cannot reference an embedded item directly (you must say “second item of user 251’s positions list”);
and for lists users reorder (like to-dos), a JSON array is far more natural than relational tricks.
نمودار ER — اسکیمای رابطهای رزومهER diagram — the resume's relational schema
روایت مدرس از نمودارTeacher narration
مثال فنی برای خواندنِ نمودار: کلید خارجی و جدول واسط
وضعیت اولیه: مشتری 17 دو سفارش 42 و 43 دارد؛ هر سفارش چند کالا دارد.
اجرای مرحلهبهمرحله: orders.customer_id رابطهٔ چندبهیک را میسازد؛ order_items(order_id, product_id, qty) سفارش و کالا را چندبهچند میکند.
نتیجه و دلیل: هر قلم میتواند qty مخصوص همان رابطه داشته باشد؛ گذاشتن آن فقط روی مشتری یا کالا معنای درستی ندارد.
این نمودار ER، همان رزومهٔ نمودار قبلی را در مدل رابطهای نشان میدهد. از جعبهٔ وسطِ سمت چپ شروع کنید:
«users» با زیرنویسِ «PK user_id, name, FK region_id». سه فلشِ «1..N» از آن بیرون میآید:
به «positions» بالا، «education» وسط و
«contact_info» پایین. یعنی هر کاربر در هرکدام چند ردیف دارد — همان درختِ یکبهچند که در JSON توکار میشد.
حالا فلشِ چینچینِ «N..1» را از users دنبال کنید که به «regions» میرود (برچسبِ «normalized»):
بسیاری از افراد در یک منطقهاند، اما نامِ منطقه فقط یکجا ذخیره میشود. بهجای متنِ تکراری، ID میگذاریم و هنگام نمایش،
یک lookup یا join اضافه میپردازیم.
سمت راست، «positions» با فلشِ «N..1» به «organizations» وصل است
(برچسبِ «entity»، زیرنویسِ «PK org_id, name, logo_url»). این همان تصمیمِ نرمالسازی است: اگر لوگوی سازمان عوض شود،
فقط یک ردیف تغییر میکند. و اگر دقیقتر نگاه کنید، بین users و organizations عملاً رابطهٔ چندبهچند داریم
که همان جدولِ positions آن را میسازد: هر ردیفِ positions یک user_id را به یک org_id گره میزند.
This ER diagram shows the previous diagram’s resume in the relational model. Start from the mid-left box:
“users” (“PK user_id, name, FK region_id”). Three “1..N” arrows leave it:
to “positions” above, “education” in the middle and
“contact_info” below. Each user owns several rows in each — the same one-to-many tree that JSON would embed.
Now follow the dashed “N..1” arrow from users to “regions” (tagged “normalized”):
many people share one region, but the region’s name is stored exactly once. Instead of repeated text we keep an ID,
paying one extra lookup or join at display time.
On the right, “positions” connects with “N..1” to “organizations”
(“entity”, “PK org_id, name, logo_url”). That is the normalization call: if an organization’s logo changes,
only one row changes. And look closer: users and organizations effectively hold a many-to-many relationship,
built by that same positions table: each positions row ties one user_id to one org_id.
اسکیمای تحلیلی: Star، Snowflake و OBTAnalytic schemas: star, snowflake and OBT
در مدل تحلیلی، جدول واقعیت رویدادها و مقدارها را نگه میدارد و جدولهای بُعد ویژگیهای توصیفی را. میزان جدا کردن یا کنار هم گذاشتن این اطلاعات، شکل اسکیما را تعیین میکند.
مثال فنی: Fact و dimension روی یک فروش
وضعیت اولیه: قلم فروش شامل product_id=7, store_id=3, amount=200 است.
اجرای مرحلهبهمرحله: جدول fact مبلغ و کلیدها را دارد؛ dimension محصول نام و دستهبندی را میدهد؛ join برای جمع فروش به تفکیک دسته اجرا میشود.
نتیجه و دلیل: در OBT نام دسته در ردیفهای فروش تکرار میشود؛ خواندن سادهتر میشود، ولی تغییر و معنای تاریخچه باید مدیریت شود.
Warehouses are usually relational with well-known conventions. In a star schema, the center holds a
fact table (e.g. fact_sales) where each row is one event at a point in time (a purchase).
Its columns are either attributes (sale price, cost) or foreign keys to dimension tables:
the who/what/where/when/how/why dimensions. E.g. dim_product (SKU, brand, category), dim_store, even dim_date
(so public holidays stay analyzable). The “star” name comes from the rays around the central table.
- Snowflake: بعدها به زیربعد شکسته میشوند (برند و دسته جدول جدا) — نرمالتر، ولی برای تحلیلگر کار با star سادهتر است.
- OBT (One Big Table): حذف کامل جدولهای بعد و تا کردن اطلاعاتشان در ستونهای دنرمالشده — فضای بیشتر، گاهی کوئری سریعتر.
- دنرمالسازی در تحلیل بیدردسر است: داده تاریخچهٔ تغییرناپذیر است و نگرانیِ سازگاریِ نوشتنِ OLTP را ندارد.
- جدول واقعیه میتواند پتابایتی شود؛ چون هر رویداد را جدا نگه میدارند، بعداً انعطاف تحلیلی بیشتر است.
- Snowflake: dimensions split into sub-dimensions (brand and category in separate tables) — more normalized, but analysts find star simpler.
- OBT (One Big Table): dimension tables dropped entirely, their data folded into denormalized columns — more space, sometimes faster queries.
- Denormalization is painless in analytics: data is immutable history with none of OLTP’s write-consistency worries.
- Fact tables can reach petabytes; keeping each event separate preserves future analytical flexibility.
Schema-on-Read در برابر Schema-on-Write + نمودار ۱Schema-on-read vs schema-on-write + Diagram 1
در schema-on-write، داده پیش از ذخیره با ساختار موردانتظار بررسی میشود. در schema-on-read، بخش بیشتری از تفسیر و بررسی ساختار هنگام خواندن انجام میشود؛ دادهٔ سندی هم الزاماً بیساختار نیست.
مثال فنی: اعتبارسنجی در نوشتن یا خواندن
وضعیت اولیه: منبعی age="unknown" میفرستد، اما گزارش میانگین سن عدد میخواهد.
اجرای مرحلهبهمرحله: schema-on-write میتواند رکورد نامعتبر را همان ورود رد کند؛ schema-on-read فایل خام را میپذیرد و هنگام تحلیل خطا یا مقدار نامعلوم را مدیریت میکند.
نتیجه و دلیل: نبود schema در ذخیره، نیاز به تفسیر و اعتبارسنجی را حذف نمیکند؛ فقط محل آن جابهجا میشود.
- schema-on-write (روابطهای سنتی): اسکیما صریح است و پایگاه داده هنگام نوشتن تضمین میکند داده مطابق آن باشد — مثل type checking ایستا.
- schema-on-read (سند): هیچ اسکیمایی اجرا نمیشود و ساختار فقط هنگام خواندن تفسیر میشود — مثل type checking پویا. واژهٔ «schemaless» گمراهکننده است چون کدِ خواننده همیشه ساختاری را فرض میکند.
- هنگام تغییر قالب (مثلاً جدا کردن نام کامل به نام و نام خانوادگی): در سند، کدِ برنامه باید با اسناد قدیمی کنار بیاید؛
در رابطهای، مهاجرت (ALTER TABLE + UPDATE) که روی جدول بزرگ کند است.
- schema-on-read وقتی مفید است که ساختار داده ناهمگون باشد یا سیستمهای بیرونی آن را تعیین کنند.
- Schema-on-write (traditional relational): the schema is explicit and the database enforces it at write time — like static type checking.
- Schema-on-read (documents): no schema is enforced and structure is only interpreted at read time — like dynamic type checking. “Schemaless” misleads, since reader code always assumes some structure.
- When the format changes (e.g. splitting a full name into first and last): in documents, application code must cope with old documents;
in relational, a migration (ALTER TABLE + UPDATE) that runs slowly on big tables.
- Schema-on-read pays off when data structure is heterogeneous or dictated by outside systems.
روایت مدرس از نمودار ۱Teacher narration of Diagram 1
مثال فنی برای خواندنِ نمودار: دو نمایش یک سابقهٔ شغلی
وضعیت اولیه: مریم با person_id=17 در شرکت 7 کار کرده است.
اجرای مرحلهبهمرحله: رابطهای: ردیف در people و ردیف در jobs با کلید خارجی؛ سندی: آرایهٔ jobs داخل سند مریم.
نتیجه و دلیل: سند خواندن یکجای رزومه را ساده میکند؛ گزارش همهٔ کارمندان شرکت ممکن است ایندکس و پردازش متفاوتی بخواهد.
بیایید این نمودار را با هم، قدمبهقدم، بخوانیم. اول چشمتان به نیمهٔ بالای شکل میافتد:
جعبهٔ «Profile Page (SQL)» با توضیحِ «fetch full resume». از کنارهٔ راست همین جعبه سه فلش بیرون آمده است.
فلش اول، پررنگ، با برچسب «query + join» به جدول users میرسد؛
دو فلش بعدی هر دو برچسب یکسان «join on user_id» دارند و به جدولهای
positions و education میروند. یعنی برای ساختنِ یک صفحهٔ رزومه باید سه جدول را کنار هم بیاوریم.
حالا فلش چینچینِ «resolve ID» را دنبال کنید: از جدول users به جدولِ
regions میرود که برچسبِ کنارش normalized است.
این همان نرمالسازی است: مقدار region_id که برای انسان بیمعنی است، اینجا به نامِ قابلخواندن تبدیل میشود.
به نیمهٔ پایینِ شکل بروید. اینجا جعبهٔ «Profile Page (JSON)» فقط یک فلش پررنگ دارد:
«single read» که مستقیم به «Profile Document» میرسد؛
سندی که زیرنویسش «positions + education inside» است و برچسبش «one read».
تنها فلش چینچینِ این نیمه، «by region_id» است که به regions collection میرود.
درسِ شکل در همین تضاد است: بالا چند پرسوجو و join برای بازسازی یک درخت، پایین یک خواندن —
اما بهای پایین، تکرار داده و مسئلهٔ بهروزرسانیِ کپیهاست.
Let’s read this diagram together, step by step. First your eye lands on the top half:
the “Profile Page (SQL)” box (“fetch full resume”). Three arrows leave its right edge.
The first, bold arrow, labelled “query + join”, reaches the users table;
the next two share one label, “join on user_id”, and reach the
positions and education tables. Building one resume page means joining three tables.
Now follow the dashed “resolve ID” arrow: from the users table to the
regions table, tagged normalized.
That is normalization: the human-meaningless region_id becomes a readable name here.
Move to the bottom half. Here the “Profile Page (JSON)” box has a single bold arrow:
“single read”, straight into the “Profile Document”;
a document subtitled “positions + education inside” and tagged “one read”.
This half’s only dashed arrow, “by region_id”, goes to the regions collection.
The lesson is in this contrast: above, several queries and joins to rebuild one tree; below, a single read —
but the price below is data duplication and the copy-update problem.
نمودار کلاس/ER — یک رزومه، سه شکلClass/ER diagram — one resume, three shapes
تا اینجا هر مدل را جدا دیدیم. حالا هر سه را با زبانِ نمودارِ کلاس/ER کنار هم بگذار تا تفاوتِ شکلِ داده
یکجا معلوم شود: در مدلِ رابطهای با «موجودیت + کلید خارجی + کاردینالیتی» طرفیم، در مدلِ سندی با «یک درختِ تو در تو»،
و در مدلِ گرافی با «رأس + یالِ دارای خاصیت». همان اطلاعات، سه شکل — و هر شکل، دستهای از پرسوجوها را ارزان میکند.
So far we saw each model alone. Now put all three side by side in class/ER notation so the shape of data
shows at once: in relational, “entity + foreign key + cardinality”; in documents, “one nested tree”;
in graphs, “node + attributed edge”. Same information, three shapes — and each shape cheapens one family of queries.
روایت مدرس از نمودارTeacher narration
مثال فنی برای خواندنِ نمودار: دو نمایش یک سابقهٔ شغلی
وضعیت اولیه: مریم با person_id=17 در شرکت 7 کار کرده است.
اجرای مرحلهبهمرحله: رابطهای: ردیف در people و ردیف در jobs با کلید خارجی؛ سندی: آرایهٔ jobs داخل سند مریم.
نتیجه و دلیل: سند خواندن یکجای رزومه را ساده میکند؛ گزارش همهٔ کارمندان شرکت ممکن است ایندکس و پردازش متفاوتی بخواهد.
شکل سه پنل دارد و عنوانِ هر پنل بالایش آمده. در پنلِ چپ — «A · Relational: tables + foreign keys» —
دو جعبهٔ کلاسمانند میبینی. جعبهٔ بالا «person» است با سه ویژگی:
«person_id PK»، «first_name» و «last_name».
جعبهٔ پایین «job» است با «job_id PK»،
«person_id FK»، «title» و «start_date».
بین این دو، خطی عمودی با لوزیِ ترکیب در سرِ آن میبینی و دو برچسبِ کاردینالیتی:
«1» کنارِ person و «0..*» کنارِ job — یعنی هر شخص صفر یا چند شغل دارد و هر شغل مالِ یک شخص است؛
برچسبِ «person_id» روی خط، همان کلیدِ خارجی است.
پنلِ وسط — «B · Document: one self-contained tree» — یک جعبهٔ بزرگ دارد:
«resume (one JSON document)» با ویژگیهای «_id»،
«name» و سپس آرایههای تو در تو: «jobs: [ { title, start_date }, … ]»،
«education: [ ... ]» و «skills: [ ... ]».
زیر آن، جعبهٔ کوچکِ «job (embedded, not a table)» با ویژگیهای
«title» و «start_date» نشسته و لوزیِ ترکیب با برچسبِ
«1» و «0..* embedded» آن را به سند وصل میکند.
یعنی اینجا job جدولِ جدا نیست؛ داخلِ همان سند جا گرفته و خواندنِ رزومه فقط یک خواندن است.
پنلِ راست — «C · Graph: nodes and relationships are first-class» — سه جعبهٔ رأس داری:
«(:Person)» با ویژگیهای «name» و «born»،
«(:Company)» با «name» و «industry»،
و «(:City)» با «name» و «country».
سه فلشِ سبز بینشان هست: از Person به Company با برچسبِ «[:WORKS_AT {since}]»،
از Person به City با «[:LIVES_IN]» و از Company به City با «[:LOCATED_IN]».
دقت کن برچسبِ اول یک ویژگی هم دارد: «{since}» — یال خودش میتواند داده داشته باشد،
چیزی که در کلیدِ خارجیِ ساده جایی ندارد.
حالا زیرِ هر پنل، سه خط توضیحِ کوتاه را بخوان. پنلِ A: «هر ردیف یک چیز است؛ ارجاعها باید معتبر بمانند» و
«نمایشِ رزومه یعنی JOIN در لحظهٔ خواندن». پنلِ B: «همهٔ فیلدهای یک کاربر کنار هماند — بدونِ join» اما
«denormalization یعنی تکرارِ داده در چند جا». پنلِ C: «روابط دادهٔ خودشان را دارند» و
«هزینهٔ پرسوجو به جستوجو بستگی دارد، نه به schema». و خطِ پایینِ کلِ شکل نتیجه را میگیرد:
همان اطلاعات، سه شکل — مدل تعیین میکند کدام دسترسی ارزان است و کدامیک به تراکنش یا تکرارِ داده نیاز دارد.
درسِ شکل: «ناهماهنگی شیء-رابطهای» دقیقاً همین شکافِ بین جعبههای پنلِ A و شیءِ درونِ کدِ برنامه است.
مدلِ سندی آن را با جا دادنِ کلِ درخت در یک سند حل میکند — اما بهایش را هنگامِ بهروزرسانیِ دادههای تکرارشده میپردازی.
مدلِ گرافی هم وقتی برنده است که سوالِ تو اساساً دربارهٔ «چه کسی به چه چیزی وصل است» باشد.
The figure has three panels, each titled above. In the left panel — “A · Relational: tables + foreign keys” —
you see two class-like boxes. The top box is “person” with three attributes:
“person_id PK”, “first_name” and “last_name”.
The bottom box is “job” with “job_id PK”,
“person_id FK”, “title” and “start_date”.
Between them a vertical line with a composition diamond carries two cardinality labels:
“1” by person and “0..*” by job — each person has zero or more jobs, each job belongs to one person;
the “person_id” label on the line is the foreign key.
The middle panel — “B · Document: one self-contained tree” — holds one big box:
“resume (one JSON document)” with “_id”,
“name” and nested arrays: “jobs: [ { title, start_date }, … ]”,
“education: [ ... ]” and “skills: [ ... ]”.
Below sits the small “job (embedded, not a table)” box with
“title” and “start_date”, joined to the document by a composition diamond labelled
“1” and “0..* embedded”.
Here job is no separate table; it lives inside the same document, so reading the resume is a single read.
The right panel — “C · Graph: nodes and relationships are first-class” — has three node boxes:
“(:Person)” with “name” and “born”,
“(:Company)” with “name” and “industry”,
and “(:City)” with “name” and “country”.
Three green arrows run between them: Person to Company labelled “[:WORKS_AT {since}]”,
Person to City “[:LIVES_IN]”, Company to City “[:LOCATED_IN]”.
Note the first label carries an attribute: “{since}” — an edge can hold data itself,
something a plain foreign key has no room for.
Now read the three short notes under each panel. Panel A: “each row is one thing; references must stay valid” and
“showing the resume means JOIN at read time”. Panel B: “all of one user’s fields sit together — no join” but
“denormalization means duplicated data in several places”. Panel C: “relationships carry their own data” and
“query cost depends on traversal, not schema”. The bottom line of the whole figure concludes:
same information, three shapes — the model decides which access is cheap and which needs transactions or duplicated data.
The lesson: “object-relational mismatch” is exactly this gap between panel A’s boxes and the in-code object.
The document model closes it by fitting the whole tree in one document — but pays at duplicated-data update time.
And the graph model wins when your question is fundamentally “who connects to what”.
۷7 تمرینهای مصاحبه و آزمایشگاهInterview exercises and hands-on labs
این تمرینها برای مسیر Backend / Distributed Systems در ۱۴ اکتبر ۲۰۲۶ بازبینی شدند. سطح مربوط به نسخهٔ مصاحبهای است؛ زمان آزمایشگاه جداست.Reviewed on 14 October 2026 for Backend / Distributed Systems. Level refers to the interview brief; full-lab time is separate. روش ارزیابی و مسیر مطالعهMethod and study path
تمرین ۱ — مدلسازی کاتالوگ فروشگاهExercise 1 — modelling a shop catalog
L2 · SeniorL2 · Seniorاولویت اصلیCore priority40 دقیقه بحث40 min discussion
مدلسازی بر اساس query و constraint ارزشمند است؛ پاسخ «SQL یا NoSQL» بدون workload ضعیف است.Model choices should follow queries, cardinality and constraints.
نسخهٔ بازبینیشده برای مصاحبهReviewed interview brief
کاتالوگ محصول، برند، دستهبندی چندگانه و نظر کاربران را با مدل رابطهای و سندی طراحی کن. سه query پرتکرار، pagination، رشد نظرات، uniqueness و تغییر برند را مشخص کن. schema و index را رسم کن و یک انتخاب متناسب با بار داشته باش.Model products, brands, multiple categories and unbounded reviews in relational and document forms. Specify three queries, pagination, indexes and uniqueness; justify a workload-specific choice.
معیار پاسخ خوبWhat a good answer demonstrates
- cardinality، FK و index روشناندCardinality, keys and indexes
- آرایهٔ بیحد نظرات را در سند جا نمیدهدBounded document growth
- انتخاب را با query و consistency توجیه میکندQuery-driven consistency choices
پیگیری مصاحبه:Interview follow-up: اگر محصول در چند دسته باشد و نظرات میلیونتایی شوند مدل چگونه تغییر میکند؟How does the model change when a product has multiple categories and millions of reviews?
آزمایشگاه تکمیلی · زمان تقریبی ۱–۲ ساعتExtended hands-on laboratory · estimated 1–2 hours
یک فروشگاه اینترنتی با محصول، دستهبندی، برند و نظر کاربران را در نظر بگیر. دو مدلسازی بنویس: یکی رابطهای (با جدولهای لازم و کلیدها)
و یکی سندی (JSON). برای هرکدام بگو: چه کوئریهایی سادهاند، چه کوئریهایی نیاز به join یا کد اضافه دارند، و کدام را انتخاب میکنی — چرا؟
Take an online shop with products, categories, brands and user reviews. Write two modellings: one relational (with tables and keys)
and one document-based (JSON). For each, say: which queries are simple, which need joins or extra code, and which you would pick — why?
برای یادگیریِ عمیق است؛ انجام کاملش در زمان بحثِ مصاحبه انتظار نمیرود.The full laboratory is optional depth work and is not expected within the discussion time.
ارزیابی، سطح و شواهد این تمرینAssessment, level and evidence · حل آموزشی این تمرینWorked solution · Amazon · SDE III interview preparation · Atlassian · engineering interview handbook
تمرین ۲ — تصمیم نرمالسازیExercise 2 — the normalization call
L1 · پایهL1 · Foundationsاولویت اصلیCore priority20 دقیقه بحث20 min discussion
نمونهٔ خوبی برای normalization است؛ نسخهٔ فعلی برای لول بالا باید به update propagation و cache invalidation برسد.Useful foundations; propagation failures provide the senior follow-up.
نسخهٔ بازبینیشده برای مصاحبهReviewed interview brief
لوگوی یک برند روی صد هزار محصول دیده میشود. reference، embedding و read model مشتق را مقایسه کن. هزینهٔ خواندن، fan-out تغییر، stale data و failure میان batch update را بیان کن؛ انتخابت را با حد تازگی قابل قبول توجیه کن.Compare references, embedding and a derived read model for one brand logo across 100,000 products. Analyze read cost, update fan-out, partial failure, staleness and versioning.
معیار پاسخ خوبWhat a good answer demonstrates
- کپی را بدون مسیر update پیشنهاد نمیدهدCopy propagation
- خواندن سریع را با هزینه نوشتن مبادله میکندRead/write cost trade-off
- منبع حقیقت و نسخهٔ logo را مشخص میکندAuthority and versioning
پیگیری مصاحبه:Interview follow-up: با شکست بهروزرسانی در نیمهٔ محصولات چه چیزی را retry میکنی؟What do you retry when a logo update fails halfway through the products?
آزمایشگاه تکمیلی · زمان تقریبی ۳۰–۶۰ دقیقهExtended hands-on laboratory · estimated 30–60 minutes
در همان فروشگاه، لوگوی برند در صفحهٔ محصول نمایش داده میشود. اگر لوگو عوض شود چه اتفاقی میافتد؟
دو طرح بده (کپی لوگو در هر محصول، در برابر جدول برند با ارجاع) و هر کدام را با هزینهٔ خواندن/نوشتن و ریسک ناسازگاری بسنج.
In the same shop, the brand logo shows on the product page. What happens if the logo changes?
Sketch two designs (logo copied into each product vs a referenced brand table) and weigh each by read/write cost and inconsistency risk.
برای یادگیریِ عمیق است؛ انجام کاملش در زمان بحثِ مصاحبه انتظار نمیرود.The full laboratory is optional depth work and is not expected within the discussion time.
ارزیابی، سطح و شواهد این تمرینAssessment, level and evidence · حل آموزشی این تمرینWorked solution · Atlassian · engineering interview handbook · Debezium · outbox event router
تمرین ۳ — event sourcing برای رزرو سینماExercise 3 — event sourcing for cinema booking
L2 · SeniorL2 · Seniorاولویت اصلیCore priority40 دقیقه بحث40 min discussion
رزرو و replay سناریوی قویای است؛ نامگذاری event بهتنهایی کافی نیست و concurrency قید صندلی مهمتر است.Reservation invariants matter more than naming events.
نسخهٔ بازبینیشده برای مصاحبهReviewed interview brief
رزرو سینما را با event log و سه projection وضعیت صندلی، فروش و بلیت طراحی کن. دو درخواست همزمان برای یک صندلی، انقضای hold، لغو و اصلاح رزرو را توضیح بده. invariant را در write path اعمال کن و replay را از ارسال دوبارهٔ بلیت جدا کن.Design cinema reservations with a log and seat, sales and ticket projections. Handle concurrent requests, hold expiry, cancellation and corrections. Enforce seat exclusivity at the write boundary and prevent replayed external effects.
معیار پاسخ خوبWhat a good answer demonstrates
- یک صندلی دو رزرو تأییدشده نداردSeat exclusivity
- command و event و compensating event را جدا میکندCommands versus events
- projection و اثر بیرونی idempotent هستندIdempotent projections and side effects
پیگیری مصاحبه:Interview follow-up: رویداد SeatHeld دیر برسد ولی SeatReleased قبلاً پردازش شده باشد چه میکنی؟What happens if SeatHeld arrives after SeatReleased has already been processed?
آزمایشگاه تکمیلی · زمان تقریبی ۲–۴ ساعتExtended hands-on laboratory · estimated 2–4 hours
برای سیستم رزرو صندلی سینما: فهرست رویدادها را بنویس (نامها به زمان گذشته)، سه projection تعریف کن (وضعیت صندلیها، گزارش فروش، چاپ بلیت)،
و بگو اگر کاربری اشتباه رزرو کرد چطور با یک رویداد تازه اصلاحش میکنی. یک حالت هم پیدا کن که پردازشِ دوباره در آن خطرناک باشد.
For a cinema seat-booking system: list the events (past-tense names), define three projections (seat status, sales report, ticket printing),
and say how a mistaken booking is fixed with a fresh event. Also find one case where replaying is dangerous.
برای یادگیریِ عمیق است؛ انجام کاملش در زمان بحثِ مصاحبه انتظار نمیرود.The full laboratory is optional depth work and is not expected within the discussion time.
ارزیابی، سطح و شواهد این تمرینAssessment, level and evidence · حل آموزشی این تمرینWorked solution · AWS · safe retries with idempotent APIs · Amazon · SDE III interview preparation