Codoloper

آموزش برنامه‌نویسی به فارسی

یاد بگیر، پیشرفت کن، بساز.

منبع آموزشی برنامه‌نویسی به زبان فارسی - مستندات، دوره‌ها و مطالب کاربردی برای همه سطوح.

داکیومنت ها:

JavascriptHTMLCSSReact JSبیشتر
1// برنامه‌نویسی به فارسی
2import { learn } from 'codoloper'
3
4const developer = learn({
5"lang": "فارسی"
6"level": "همه سطوح"
7free: true
8})
بلاگ

مطالب جدید

تازه‌ترین مطالب آموزشی و خبرهای مرتبط.

Elasticsearch: قدرتمند، سنگین، و نه همیشه انتخاب درست نوشته شده توسط عرفان دهقانی

Elasticsearch: قدرتمند، سنگین، و نه همیشه انتخاب درست

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

Elasticsearch واقعاً چیه

Elasticsearch یک موتور جستجو و آنالیز توزیع‌شده‌ست که روی Apache Lucene ساخته شده. کارش فقط «پیدا کردن یک رشته توی متن» نیست؛ یک سیستم کامله برای indexing متن با امتیازدهی relevance، فیلتر کردن روی فیلدهای ساختاریافته، اجرای aggregation های پیچیده (مثل «میانگین قیمت به تفکیک دسته‌بندی»)، و مقیاس‌پذیری روی چند نود همزمان. برای همینه که توی سیستم‌های لاگ‌مانیتورینگ (مثل استک ELK)، جستجوی محصولات فروشگاه‌های بزرگ، و تحلیل داده‌ی حجیم این‌قدر رایجه.

چرا سنگین حساب میشه

مشکل اصلی Elasticsearch این نیست که کند باشه؛ مشکل اینه که برای اجرا شدن، منابع قابل توجهی میخواد. چون روی JVM (جاوا) اجرا میشه، حداقل نیاز به چند صد مگابایت تا چند گیگابایت heap memory داره، حتی برای یک نود توسعه‌ای کوچیک. توصیه‌ی رسمی برای یک نود production معمولاً از ۲ تا ۴ گیگابایت رم شروع میشه و برای دیتاست‌های بزرگ‌تر به‌سرعت بالا میره. علاوه بر این، Elasticsearch برای کار درست نیازمند تنظیمات سیستم‌عاملیه (مثل افزایش vm.max_map_count روی لینوکس)، و راه‌اندازی یک کلاستر واقعی (با نودهای master و data جدا) خودش یک پروژه‌ی مجزاست، نه یک نصب پنج‌دقیقه‌ای.

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

Meilisearch و رقبای سبک‌تر

Meilisearch یک موتور جستجوی متن‌باز است که با Rust نوشته شده و از ابتدا برای سادگی و سرعت راه‌اندازی طراحی شده، نه برای مقیاس عظیم. تفاوتش با Elasticsearch از همون لحظه‌ی نصب معلومه: یک باینری تک‌فایلی که بدون JVM، بدون تنظیمات پیچیده، و با مصرف رم بسیار پایین‌تر (معمولاً چند ده مگابایت برای دیتاست‌های متوسط) اجرا میشه. جستجوش هم به‌صورت پیش‌فرض typo-tolerant است — یعنی حتی اگه کاربر کلمه رو غلط تایپ کنه، نتیجه‌ی درست رو پیدا میکنه — بدون این‌که نیاز به تنظیم دستی داشته باشه.

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

اما این سادگی رایگان نیست. تریدآف اصلی اینه که Meilisearch و Typesense قابلیت‌های aggregation پیچیده‌ای که Elasticsearch داره رو ندارن — یعنی اگه نیازت فقط جستجوی متنیه، عالی جواب میدن، ولی اگه میخوای همزمان تحلیل آماری روی میلیون‌ها رکورد لاگ انجام بدی یا dashboard های پیچیده‌ی مانیتورینگ بسازی (کاری که Kibana روی Elasticsearch انجام میده)، این ابزارهای سبک‌تر برات کافی نیستن. خلاصه‌اش اینه: اگه مسئله‌ات «جستجو»ست، سبک‌ترها رو انتخاب کن؛ اگه مسئله‌ات «جستجو + آنالیز حجم عظیم داده»ست، سراغ Elasticsearch برو.

نصب Elasticsearch با Docker

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

docker run -d --name elasticsearch \
  -p 9200:9200 -p 9300:9300 \
  -e "discovery.type=single-node" \
  -e "xpack.security.enabled=false" \
  -e "ES_JAVA_OPTS=-Xms1g -Xmx1g" \
  docker.elastic.co/elasticsearch/elasticsearch:8.15.0

اگه بخوای همراهش Kibana هم بالا بیاری (برای مشاهده‌ی گرافیکی داده‌ها)، بهتره از docker-compose استفاده کنی:

version: "3.8"
services:
  elasticsearch:
    image: docker.elastic.co/elasticsearch/elasticsearch:8.15.0
    environment:
      - discovery.type=single-node
      - xpack.security.enabled=false
      - ES_JAVA_OPTS=-Xms1g -Xmx1g
    ports:
      - "9200:9200"
    volumes:
      - es_data:/usr/share/elasticsearch/data

  kibana:
    image: docker.elastic.co/kibana/kibana:8.15.0
    ports:
      - "5601:5601"
    depends_on:
      - elasticsearch

volumes:
  es_data:

بعد از اجرای docker compose up -d، میتونی با curl localhost:9200 مطمئن بشی سرویس بالا اومده.

نصب بدون Docker

اگه بخوای مستقیم روی یک سرور لینوکسی (مثلاً Ubuntu) نصبش کنی، مراحل کمی بیشتره چون باید repository رسمی Elastic رو اضافه کنی:

# اضافه‌کردن کلید و ریپازیتوری رسمی
wget -qO - https://artifacts.elastic.co/GPG-KEY-elasticsearch | sudo gpg --dearmor -o /usr/share/keyrings/elasticsearch-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/elasticsearch-keyring.gpg] https://artifacts.elastic.co/packages/8.x/apt stable main" | sudo tee /etc/apt/sources.list.d/elastic-8.x.list

sudo apt update
sudo apt install elasticsearch

# تنظیم حداقل حافظه‌ی JVM (توصیه: نصف رم سرور، حداکثر ۳۱ گیگ)
sudo nano /etc/elasticsearch/jvm.options.d/heap.options
# داخلش: -Xms2g و -Xmx2g

sudo systemctl enable elasticsearch
sudo systemctl start elasticsearch

همچنین لازمه تنظیم vm.max_map_count رو دستی انجام بدی، چون Elasticsearch برای مدیریت حافظه‌ی داخلیش به مقدار بالاتری از پیش‌فرض لینوکس نیاز داره:

sudo sysctl -w vm.max_map_count=262144

کدوم روش رو انتخاب کنیم

برای توسعه، تست محلی، یا حتی خیلی از سناریوهای production کوچیک تا متوسط، Docker (یا بهتر، docker-compose همراه با volume برای persistence) گزینه‌ی پیشنهادیه. مزیتش اینه که آپدیت نسخه، ری‌استارت، و پاک‌سازی کامل محیط، یک دستور بیشتر نیست. نصب مستقیم روی سیستم‌عامل رو معمولاً وقتی توصیه میکنن که تیم زیرساخت از قبل روی مدیریت سرویس‌های systemd و مانیتورینگ سطح OS تجربه داره، یا محدودیت سازمانی برای استفاده از Docker وجود داره. برای اکثر تیم‌ها، مسیر Docker هم سریع‌تره هم قابل‌تکرارتر (reproducible)، پس معمولاً انتخاب اول همینه.

نوشتن Query در Elasticsearch

Elasticsearch با یک زبان JSON-based به اسم Query DSL کار میکنه. ساده‌ترین حالت، جستجوی متنی روی یک فیلد است:

GET /products/_search
{
  "query": {
    "match": {
      "title": "کفش ورزشی"
    }
  }
}

اگه بخوای همزمان فیلتر دقیق (مثل بازه‌ی قیمت) رو هم اعمال کنی، از bool query استفاده میکنی که چند شرط رو با هم ترکیب میکنه:

GET /products/_search
{
  "query": {
    "bool": {
      "must": [
        { "match": { "title": "کفش ورزشی" } }
      ],
      "filter": [
        { "range": { "price": { "gte": 500000, "lte": 2000000 } } }
      ]
    }
  }
}

نکته‌ی مهم اینجا فرق must و filter است: بخش must روی امتیاز relevance تأثیر میذاره (یعنی نتایج بر اساس میزان تطابق رتبه‌بندی میشن)، ولی filter فقط true/false هست و روی امتیاز اثر نداره — و چون کش میشه، سریع‌تر هم اجرا میشه. برای شرط‌هایی مثل بازه‌ی قیمت یا وضعیت موجودی که فقط باید فیلتر کنن نه رتبه‌بندی، همیشه از filter استفاده کن.

برای aggregation، یعنی محاسبات آماری روی نتایج، مثلاً میانگین قیمت به تفکیک برند:

GET /products/_search
{
  "size": 0,
  "aggs": {
    "avg_price_by_brand": {
      "terms": { "field": "brand.keyword" },
      "aggs": {
        "avg_price": { "avg": { "field": "price" } }
      }
    }
  }
}

این همون قابلیتیه که Elasticsearch رو از یک موتور جستجوی ساده فاصله میده و بهش اجازه میده هم‌زمان نقش یک ابزار آنالیز داده رو هم بازی کنه.

جستجو روی چند فیلد همزمان (multi_match)

وقتی میخوای یک عبارت رو همزمان توی چند فیلد بگردی — مثلاً هم توی عنوان و هم توی توضیحات محصول — به‌جای چند تا match جدا، از multi_match استفاده کن:

GET /products/_search
{
  "query": {
    "multi_match": {
      "query": "کفش دویدن",
      "fields": ["title^2", "description"]
    }
  }
}

عدد ^2 کنار title یعنی تطابق توی این فیلد دو برابر وزن بیشتری توی محاسبه‌ی relevance داره — یعنی اگه کلمه توی عنوان پیدا بشه، نسبت به پیدا شدن توی توضیحات، امتیاز بالاتری میگیره.

جستجوی مقاوم به غلط تایپی (fuzzy)

اگه بخوای حتی وقتی کاربر کلمه رو اشتباه تایپ کرده جواب پیدا کنی:

GET /products/_search
{
  "query": {
    "match": {
      "title": {
        "query": "کتونی",
        "fuzziness": "AUTO"
      }
    }
  }
}

fuzziness: AUTO به Elasticsearch اجازه میده بر اساس طول کلمه، تا یک یا دو حرف اختلاف رو نادیده بگیره.

جستجوی wildcard و prefix

برای موقعیت‌هایی مثل autocomplete که فقط بخشی از کلمه رو داری:

GET /products/_search
{
  "query": {
    "prefix": {
      "sku.keyword": "SHOE-2024"
    }
  }
}

wildcard انعطاف بیشتری میده (میتونی از * و ? استفاده کنی) ولی روی دیتاست بزرگ کندتره، پس فقط وقتی لازمه که prefix یا match جواب نمیده:

GET /products/_search
{
  "query": {
    "wildcard": {
      "sku.keyword": "SHOE-*-RED"
    }
  }
}

ترکیب چند شرط با bool کامل (must, should, must_not, filter)

یک مثال واقعی‌تر که همه‌ی بخش‌های bool رو با هم نشون میده: محصولاتی که «کفش» توی عنوانشونه، ترجیحاً برند «نایک» یا «آدیداس» باشن (امتیاز بیشتر ولی اجباری نیست)، جنسشون «چرم» نباشه، و موجود باشن:

GET /products/_search
{
  "query": {
    "bool": {
      "must": [
        { "match": { "title": "کفش" } }
      ],
      "should": [
        { "term": { "brand.keyword": "Nike" } },
        { "term": { "brand.keyword": "Adidas" } }
      ],
      "must_not": [
        { "term": { "material.keyword": "leather" } }
      ],
      "filter": [
        { "term": { "in_stock": true } }
      ]
    }
  }
}

چک کردن وجود یک فیلد (exists)

مفید برای پیدا کردن رکوردهایی که یک فیلد خاص توشون پر شده یا خالیه — مثلاً محصولاتی که تخفیف دارن:

GET /products/_search
{
  "query": {
    "exists": {
      "field": "discount_percent"
    }
  }
}

مرتب‌سازی و صفحه‌بندی (sort و pagination)

GET /products/_search
{
  "query": { "match": { "title": "کفش" } },
  "sort": [
    { "price": "asc" },
    "_score"
  ],
  "from": 20,
  "size": 10
}

اینجا نتایج اول بر اساس قیمت صعودی مرتب میشن و در صورت تساوی قیمت، بر اساس امتیاز relevance. from و size هم صفحه‌ی سوم رو برمیگردونن (فرض بر ۱۰ آیتم در هر صفحه).

هایلایت کردن کلمه‌ی جستجوشده در نتیجه

وقتی میخوای مثل گوگل، بخشی از متن که با جستجو مطابقت داشته رو بولد نشون بدی:

GET /articles/_search
{
  "query": { "match": { "content": "ریپلیکیشن دیتابیس" } },
  "highlight": {
    "fields": {
      "content": {
        "pre_tags": ["<mark>"],
        "post_tags": ["</mark>"]
      }
    }
  }
}

جستجوی جغرافیایی (geo_distance)

پیدا کردن همه‌ی موارد نزدیک به یک نقطه‌ی مشخص، مثلاً رستوران‌های نزدیک یک مختصات توی شعاع ۲ کیلومتر:

GET /restaurants/_search
{
  "query": {
    "bool": {
      "filter": {
        "geo_distance": {
          "distance": "2km",
          "location": {
            "lat": 35.6892,
            "lon": 51.3890
          }
        }
      }
    }
  }
}

اگریگیشن بر اساس بازه‌ی زمانی (date_histogram)

مفید برای dashboard هایی که میخوان نشون بدن مثلاً تعداد سفارش‌ها به تفکیک روز چطور تغییر کرده:

GET /orders/_search
{
  "size": 0,
  "aggs": {
    "orders_per_day": {
      "date_histogram": {
        "field": "created_at",
        "calendar_interval": "day"
      }
    }
  }
}

جستجوی برداری ساده (kNN)

برای سیستم‌هایی که روی embedding کار میکنن (مثلاً جستجوی معنایی به‌جای جستجوی کلمه‌به‌کلمه):

GET /articles/_search
{
  "knn": {
    "field": "content_vector",
    "query_vector": [0.021, -0.13, 0.045, "..."],
    "k": 5,
    "num_candidates": 50
  }
}

اینجا k تعداد نزدیک‌ترین نتایجی که میخوای برگرده رو مشخص میکنه، و num_candidates تعداد کاندیدهایی که Elasticsearch قبل از انتخاب k تای نهایی بررسی میکنه — عدد بزرگ‌تر یعنی دقت بالاتر ولی سرعت پایین‌تر.

قابلیت‌های اضافه‌ای که کمتر دیده میشن

فراتر از جستجوی متنی معمولی، Elasticsearch چند قابلیت داره که ارزش دونستن دارن.

geo queries به تو اجازه میدن جستجوی مکانی انجام بدی — مثلاً «همه‌ی رستوران‌های نزدیک این مختصات جغرافیایی توی شعاع ۲ کیلومتر». این برای اپلیکیشن‌های مبتنی بر موقعیت مکانی حیاتیه و بدون ابزار جانبی مستقیم پشتیبانی میشه.

kNN search یا جستجوی برداری، قابلیتیه که این چند سال با رشد هوش مصنوعی خیلی مهم شده: میتونی به‌جای جستجوی متنی، بردارهای embedding (خروجی مدل‌های زبانی) رو ایندکس کنی و نزدیک‌ترین بردارها به یک query رو پیدا کنی — دقیقاً همون چیزی که پشت سیستم‌های RAG (retrieval-augmented generation) قرار داره.

percolate query برعکس جستجوی معمولیه: به‌جای این‌که یک query رو روی مجموعه‌ای از داده اجرا کنی، یک داده‌ی جدید رو روی مجموعه‌ای از query های از قبل ذخیره‌شده تست میکنی — مفید برای سیستم‌های هشدار و فیلترینگ که باید بفهمن یک رکورد جدید با کدوم شرط‌های ازپیش‌تعریف‌شده مطابقت داره.

و در نهایت، ecosystem کامل ELK/Elastic Stack — یعنی Kibana برای visualization، Logstash و Beats برای جمع‌آوری و انتقال داده — چیزیه که Elasticsearch رو از یک دیتابیس جستجو به یک پلتفرم کامل observability تبدیل میکنه، که این یکی از دلایل اصلیه که با وجود سنگین بودنش، توی محیط‌های enterprise همچنان انتخاب اول میمونه.

جمع‌بندی

Elasticsearch ابزار درستیه وقتی هم جستجوی متنی پیچیده و هم آنالیز حجم بالای داده رو همزمان نیاز داری. ولی اگه فقط دنبال یک جستجوی سریع و typo-tolerant برای سایت یا اپلیکیشنت هستی، قبل از این‌که وقت بذاری برای راه‌اندازی یک کلاستر Elasticsearch، حتماً Meilisearch یا Typesense رو هم امتحان کن — احتمالاً همون کاری که میخوای رو با یک‌دهم پیچیدگی انجام میدن.

ریپلیکیشن دیتابیس: یک کپی زنده از داده ها، همیشه آماده نوشته شده توسط عرفان دهقانی

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

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

مهمه که این رو از شاردینگ جدا کنی. توی شاردینگ، هر سرور فقط بخشی از داده رو داره. توی ریپلیکیشن، هر سرور (یا حداقل هر replica) یک کپی کامل از کل داده رو نگه میداره. این دو تکنیک هدف متفاوتی دارن و معمولاً با هم ترکیب میشن، نه جایگزین همدیگه.

چرا ریپلیکیشن میخوایم

دو دلیل اصلی برای ریپلیکیشن وجود داره، و هر کدوم مسئله‌ی متفاوتی رو حل میکنن.

اول، دسترس‌پذیری بالا (high availability). اگه سرور اصلی (که بهش primary یا master میگن) از کار بیفته، یکی از replica ها میتونه جاش رو بگیره و نقش primary جدید رو بازی کنه، بدون این‌که سرویس کامل قطع بشه. به این فرآیند failover میگن.

دوم، توزیع بار خواندن. توی خیلی از اپلیکیشن‌ها، تعداد خواندن (SELECT) خیلی بیشتر از نوشتن (INSERT/UPDATE) است. اگه همه‌ی این خواندن‌ها روی همون یک سرور primary انجام بشه، اون سرور زیر فشار میمونه. با ریپلیکیشن میتونی خواندن‌ها رو بین چند replica پخش کنی و فقط نوشتن‌ها رو روی primary نگه داری — الگویی که بهش read/write splitting میگن.

چطور داده از primary به replica میرسه

مکانیزم رایج این است که primary هر تغییری که روی داده‌اش اعمال میشه رو توی یک لاگ ترتیبی ثبت میکنه — توی PostgreSQL بهش WAL (Write-Ahead Log) میگن، توی MySQL بهش binlog. هر replica این لاگ رو از primary میخونه و همون تغییرات رو دقیقاً به همون ترتیب روی نسخه‌ی خودش اعمال میکنه. یعنی replica عملاً داره یک تاریخچه‌ی کامل از هر اتفاقی که روی primary افتاده رو دنبال میکنه و بازش تولید میکنه.

sync در مقابل async: معامله‌ای که باید آگاهانه انتخابش کنی

اینجا یکی از مهم‌ترین تصمیم‌های طراحی توی ریپلیکیشن پیش میاد: وقتی یک نوشتن روی primary اتفاق می‌افته، آیا باید صبر کنه تا replica هم همون نوشتن رو تأیید کنه، یا میتونه بدون منتظر موندن به کلاینت جواب «موفق» بده؟

توی replication همزمان (synchronous)، primary صبر میکنه تا حداقل یک replica تغییر رو دریافت و تأیید کنه، بعد به کلاینت میگه عملیات موفق بوده. این تضمین میده که هیچ‌وقت داده‌ای که کلاینت فکر میکنه ذخیره شده، گم نشه — حتی اگه دقیقاً یک لحظه بعد primary از کار بیفته. هزینه‌اش این‌جاست که هر نوشتن باید منتظر شبکه و replica هم بمونه، پس تأخیر (latency) بالاتر میره.

توی replication ناهمزمان (asynchronous)، primary بلافاصله بعد از نوشتن محلی به کلاینت جواب موفق میده، و تغییر رو در پس‌زمینه به replica ها میفرسته. سریع‌تره، ولی یک ریسک واقعی داره: اگه primary دقیقاً بین «جواب‌دادن به کلاینت» و «رسوندن تغییر به replica» از کار بیفته، اون تغییر ممکنه هیچ‌وقت به replica نرسه و گم بشه. این حالت رو replication lag میگن — فاصله‌ی زمانی بین اتفاق‌افتادن یک تغییر روی primary و رسیدنش به replica.

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

مشکلی که replication lag واقعاً ایجاد میکنه

فرض کن یک کاربر همین الان یک پست جدید توی اپلیکیشنت ثبت کرده. درخواست نوشتن روی primary انجام شده و جواب موفق برگشته. حالا همون کاربر بلافاصله صفحه رو رفرش میکنه، و این دفعه درخواست خواندن به یک replica هدایت میشه که هنوز اون تغییر رو دریافت نکرده. نتیجه؟ کاربر پست خودش رو نمیبینه و فکر میکنه ثبت نشده. این پدیده رو read-after-write inconsistency میگن و یکی از رایج‌ترین سردرگمی‌هایی‌یه که موقع استفاده از read replica ها پیش میاد.

راه‌حل رایجش اینه که برای عملیات‌های حساس به این مشکل (مثل نمایش بلافاصله‌ی داده‌ای که کاربر خودش همین الان ثبت کرده)، خواندن رو موقتاً از primary انجام بدی، نه از replica؛ یا این‌که یک مکانیزم «sticky session» بذاری که هر کاربر برای مدتی بعد از نوشتن، به همون primary وصل بمونه.

failover: چه کسی primary جدید میشه

وقتی primary از کار می‌افته، سیستم باید تصمیم بگیره کدوم replica جای اون رو بگیره. این کار میتونه دستی باشه (یک مهندس وارد میشه و خودش replica رو ترفیع میده) یا خودکار، با ابزارهایی مثل Patroni برای PostgreSQL یا Orchestrator برای MySQL (رپو اصلی دیگه مینتین نمیشه من جدید رو هم لینک میکنم) که وضعیت سلامت primary رو مانیتور میکنن و در صورت خرابی، خودشون یکی از replica ها رو (معمولاً همونی که کمترین لگ رو داره) به primary جدید ترفیع میدن.

نکته‌ای که این‌جا باید بهش دقت کرد: اگه دو replica هم‌زمان فکر کنن primary هستن (که بهش پدیده‌ی split-brain میگن)، ممکنه هر دو به‌طور مستقل نوشتن قبول کنن و داده از هم واگرا بشه. سیستم‌های failover خوب معمولاً یک مکانیزم consensus (مثل الگوریتم Raft) دارن که تضمین میکنه در هر لحظه فقط یک primary واقعی وجود داره.

multi-master: وقتی هر جا میتونه بنویسه

توی مدل primary-replica معمولی، فقط primary اجازه‌ی نوشتن داره. یک مدل جایگزین multi-master (یا multi-leader) است، جایی که چند سرور همزمان اجازه‌ی نوشتن دارن و تغییراتشون رو با هم sync میکنن. این برای سیستم‌های توزیع‌شده‌ی جغرافیایی جذابه — مثلاً یک سرور توی اروپا و یکی توی آسیا، هرکدوم بدون نیاز به رفت‌وبرگشت روی اقیانوس، مستقیم نوشتن رو قبول کنن. ولی هزینه‌ی این مدل، مواجهه‌ست با تعارض (conflict): اگه دو کاربر همزمان روی دو سرور مختلف یک ردیف رو ویرایش کنن، سیستم باید تصمیم بگیره کدوم نسخه برنده‌ست، که معمولاً با استراتژی‌هایی مثل «آخرین نوشتن برنده‌ست» یا ساختارهای داده‌ی خاص به اسم CRDT حل میشه.

جمع‌بندی

ریپلیکیشن معامله‌ی همیشگی سیستم‌های توزیع‌شده رو دوباره جلوی چشمت میذاره: بین سرعت، سازگاری، و دسترس‌پذیری باید انتخاب کنی، چون هر سه‌تاش رو همزمان و کامل نمیشه داشت. برای اکثر اپلیکیشن‌ها، یک primary با چند async replica برای read scaling و disaster recovery کافیه. فقط وقتی واقعاً نیاز به نوشتن هم‌زمان از چند نقطه‌ی جغرافیایی داری، ارزششو داره که پیچیدگی multi-master رو به جون بخری.

Grok حالا برات اپلیکیشن کامل میسازه؛ آشنایی با Build Mode و grok.me نوشته شده توسط عرفان دهقانی

Grok حالا برات اپلیکیشن کامل میسازه؛ آشنایی با Build Mode و grok.me

خیلی وقت‌ها یک ایده‌ی ساده توی ذهن آدم شکل میگیره — یک ماشین‌حساب مخصوص یک کار خاص، یک صفحه‌ی landing برای یک پروژه‌ی جانبی، یک داشبورد کوچیک برای دنبال کردن یک سری عدد — ولی چون کدنویسی بلد نیستی یا وقت راه‌اندازی محیط توسعه رو نداری، ایده همون‌جا میمونه. این دقیقاً همون شکافیه که SpaceXAI (همون شرکتی که قبلاً به اسم xAI میشناختیمش) با قابلیت جدید Grok به اسم Build Mode میخواد پرش کنه.

Build Mode چیه

Build Mode یک قابلیت تازه‌ست که همین چند روز پیش، ۲۸ ژوئیه ۲۰۲۶، برای مشترکین SuperGrok Heavy به‌صورت early beta فعال شد. ایده‌ش اینه که به‌جای این‌که Grok فقط یک تکه کد بهت بده که خودت باید کپی کنی و جایی اجرا کنی، مستقیم توی همون چت یک پیش‌نمایش زنده و کاملاً کاربردی از چیزی که خواستی میسازه. بعد میتونی با زبان طبیعی — نه با نوشتن کد — بگی چیدمان عوض بشه، رنگ‌ها تغییر کنه، یک ویژگی اضافه بشه، یا منطق برنامه فرق کنه.

این قابلیت روی وب (grok.com)، اپ iOS و اپ اندروید در دسترسه، یعنی محدود به یک پلتفرم خاص نیست.

چی میشه باهاش ساخت

طبق اعلام رسمی SpaceXAI، دامنه‌ی چیزهایی که Build Mode ازش پشتیبانی میکنه نسبتاً گسترده‌ست:

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

انتشار: لینک grok.me یا دامنه‌ی خودت

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

رقابتی که Grok تازه واردش شده

این حرکت SpaceXAI رو مستقیم وارد بازاری میکنه که این چند وقته اسمش شده «vibe coding» — رویکردی که در اون به‌جای نوشتن کد، با پرامپت و زبان طبیعی نرم‌افزار میسازی. رقبای این میدون کم نیستن: Google AI Studio که امکان تبدیل پرامپت به اپلیکیشن‌های native اندروید رو داره، Claude Artifacts خود آنتروپیک، و ChatGPT Codex از OpenAI. تفاوتی که Grok روش تأکید کرده اینه که میخواد این تجربه رو تا حد امکان غیرتخصصی نگه داره — بدون نصب محلی، بدون تنظیمات پیچیده، صرفاً پرامپت، پیش‌نمایش، و ویرایش.

نکته‌ای که نباید نادیده گرفت

با همه‌ی این هیجان، این ابزار فعلاً پشت یک پولیوار جدیه: فقط مشترکین SuperGrok Heavy بهش دسترسی دارن، که رده‌ی بالای اشتراک Grok محسوب میشه. یعنی برخلاف تصوری که ممکنه از عبارت «بدون کدنویسی» به ذهن برسه، هنوز یک مانع (البته این‌بار مالی، نه فنی) سر راهه. همچنین چون این قابلیت هنوز در فاز early beta است، طبیعیه که محدودیت‌ها و باگ‌های اولیه‌ای هم داشته باشه که با گذر زمان رفع میشن.

جمع‌بندی

Build Mode نشون میده مسیر ابزارهای هوش مصنوعی داره از «چت‌کردن درباره‌ی کد» به‌سمت «تولید مستقیم محصول قابل‌استفاده» حرکت میکنه. برای کسی که ایده داره ولی مهارت فنی نداره، این یعنی فاصله‌ی بین فکر و محصول واقعی کوتاه‌تر شده. ولی برای کسی که دنبال یک ابزار توسعه‌ی جدی و قابل اتکاست، هنوز باید صبر کرد ببینه این نسخه‌ی beta، وقتی از پشت پولیوار بیرون بیاد و در دسترس عموم قرار بگیره، چقدر توی سناریوهای واقعی و پیچیده‌تر جواب میده.

شاردینگ دیتابیس: وقتی یک سرور دیگه جایی برای داده نداره نوشته شده توسط عرفان دهقانی

شاردینگ دیتابیس: وقتی یک سرور دیگه جایی برای داده نداره

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

شاردینگ یعنی چی

شاردینگ یعنی داده‌ی یک جدول بزرگ رو به چند بخش (shard) تقسیم کنی و هر بخش رو روی یک سرور جدا نگه داری. برخلاف replication که هر سرور یک کپی کامل از کل داده رو داره، توی شاردینگ هر سرور فقط بخشی از داده رو داره. مثلاً اگه یک جدول کاربر با ۱۰۰ میلیون ردیف داری، میتونی کاربرهای با ID زوج رو روی یک سرور بذاری و کاربرهای با ID فرد رو روی سرور دیگه. حالا هر سرور فقط نصف داده رو مدیریت میکنه، و هم فضا و هم بار پردازشی بین دو ماشین تقسیم شده.

نکته‌ی مهم اینه که شاردینگ و replication دو مسئله‌ی متفاوت رو حل میکنن و معمولاً با هم ترکیب میشن: هر شارد خودش میتونه چند replica داشته باشه برای availability، و همزمان داده هم بین شاردهای مختلف تقسیم شده برای مقیاس‌پذیری.

چطور تصمیم بگیریم کدوم رکورد بره کدوم شارد

قلب هر سیستم شاردینگ، یک چیز به اسم shard key یا partition key است — ستونی که تعیین میکنه هر ردیف روی کدوم شارد قرار بگیره. چند استراتژی رایج برای این تصمیم وجود داره.

اولین و ساده‌ترین، range-based sharding است: بازه‌های مقدار رو بین شاردها تقسیم میکنی، مثلاً کاربرهایی با ID بین ۱ تا ۱ میلیون روی شارد اول، بین ۱ تا ۲ میلیون روی شارد دوم. مزیتش اینه که query هایی که روی یک بازه کار میکنن (مثل «همه‌ی سفارش‌های هفته‌ی گذشته») ممکنه فقط به یک شارد نیاز داشته باشن. مشکلش اینه که اگه داده به‌طور نامتوازن رشد کنه (مثلاً کاربرهای جدید همیشه ID بزرگ‌تر میگیرن)، شارد آخر همیشه شلوغ‌تر از بقیه میشه — به این پدیده hot shard میگن.

دومی hash-based sharding است: روی مقدار shard key یک تابع هش اجرا میکنی و نتیجه تعیین میکنه کدوم شارد. این روش توزیع خیلی یکنواخت‌تری میده و مشکل hot shard رو تا حد زیادی حل میکنه، ولی query هایی که روی بازه کار میکنن رو سخت‌تر میکنه، چون داده‌ی مرتبط ممکنه پخش بشه روی همه‌ی شاردها.

سومی directory-based sharding است، جایی که یک جدول جدا (یا سرویس جدا) نگه میداری که میگه هر کلید دقیقاً کدوم شارده. انعطاف بیشتری میده — میتونی هر موقع خواستی داده رو جابه‌جا کنی بدون این‌که فرمول هش عوض بشه — ولی خودش یک نقطه‌ی اضافه‌ست که باید مدیریتش کنی و مراقب باشی bottleneck نشه.

چیزی که واقعاً سخته: query هایی که چند شارد رو لمس میکنن

توی یک دیتابیس تک‌سروری، یک JOIN بین دو جدول یا یک عملیات تراکنشی روی چند ردیف، کار همیشگی و بی‌دردسریه. توی یک سیستم شارد شده، این همون کاریه که واقعاً سخت میشه. اگه دو ردیفی که میخوای join کنی روی دو شارد مختلف باشن، دیگه نمیتونی از یک JOIN ساده‌ی SQL استفاده کنی؛ باید یا داده رو توی اپلیکیشن خودت از دو شارد بخونی و دستی join کنی، یا از یک لایه‌ی query routing استفاده کنی که این کار رو برات مخفی کنه.

همین مسئله برای تراکنش‌ها هم صدق میکنه. یک تراکنش که باید همزمان روی دو ردیف از دو شارد مختلف atomic عمل کنه (یا هر دو موفق بشن یا هیچ‌کدوم)، به یک پروتکل به اسم two-phase commit یا الگوهای پیچیده‌تری مثل Saga نیاز داره. این دقیقاً همون چیزیه که شاردینگ رو از یک تصمیم ساده‌ی زیرساختی به یک تصمیم معماری تبدیل میکنه؛ وقتی شارد میکنی، عملاً داری از دنیای تراکنش‌های ساده‌ی ACID فاصله میگیری، مگر این‌که خیلی هوشمندانه shard key رو طوری انتخاب کنی که داده‌های مرتبط همیشه کنار هم بمونن.

re-sharding: کابوس واقعی

فرض کن با hash-based sharding شروع کردی و چهار شارد داری. حالا نیاز داری شاردهای بیشتری اضافه کنی. مشکل اینجاست که اگه فرمول هش‌ت چیزی ساده مثل hash(key) % 4 باشه، وقتی این عدد رو به ۵ تغییر بدی، تقریباً تمام mapping ها به‌هم میریزه — چون باقیمانده‌ی تقسیم برای اکثر کلیدها عوض میشه. یعنی باید تقریباً کل داده رو جابه‌جا کنی، درست همون چیزی که موقع طراحی سیستم میخواستی ازش فرار کنی.

راه‌حل رایج برای این مشکل consistent hashing است — همون تکنیکی که توی مقاله‌ی لود بالانسینگ درباره‌ی Maglev گوگل ازش گفتیم. با consistent hashing، اضافه‌کردن یک شارد جدید فقط باعث میشه بخش کوچیکی از داده جابه‌جا بشه، نه همه‌چیز. برای همین سیستم‌های بزرگ‌مقیاسی مثل Cassandra و DynamoDB این تکنیک رو به‌عنوان پایه‌ی مکانیزم شاردینگشون انتخاب کردن.

مثال‌های واقعی

Vitess، که اصلش رو یوتیوب برای مقیاس‌دادن MySQL ساخت (و بعداً Slack و GitHub هم ازش استفاده کردن)، دقیقاً یک لایه‌ی میانی روی MySQL میذاره که شاردینگ رو تا حد زیادی از دید اپلیکیشن مخفی میکنه. Citus هم همین کار رو برای PostgreSQL انجام میده — جدول‌ها رو به‌صورت شفاف بین چند نود پخش میکنه و کوئری‌ها رو به‌صورت خودکار روت میکنه. MongoDB هم شاردینگ رو به‌عنوان یک قابلیت داخلی و نه یک ابزار جانبی ارائه میده، که همین یکی از دلایلیه که خیلی از تیم‌ها برای داده‌ی خیلی بزرگ سراغش میرن.

جمع‌بندی

شاردینگ آخرین ابزاریه که باید بهش فکر کنی، نه اولین. قبل از رسیدن به این مرحله معمولاً ایندکس‌گذاری بهتر، کش، و read replica ها میتونن مدت زیادی جوابگو باشن. شاردینگ رو وقتی به سراغش میری که واقعاً داده یا نرخ نوشتن از ظرفیت یک ماشین رد شده، چون همراهش پیچیدگی‌های واقعی میاد: query های چندشارده سخت میشن، تراکنش‌ها پیچیده میشن، و re-sharding میتونه یک پروژه‌ی چندهفته‌ای بشه اگه از اول درست طراحی نشده باشه.