# عملیات: اجرا، مانیتورینگ و عیب‌یابی --- ## ۱. اجرا ### ۱.۱ حالت کامل (پیش‌فرض) ```bash cp .env.example .env docker compose up --build # یا: make up ```
ترتیب بالا آمدن سرویس‌ها با `depends_on: condition: service_healthy` کنترل می‌شود:
``` postgres ──┐ redis ─────┼──▶ backend ──▶ frontend elasticsearch ─┴──▶ logstash └──▶ kibana ```
اولین اجرا ۳ تا ۵ دقیقه طول می‌کشد. `entrypoint.sh` در کانتینر بک‌اند:
``` منتظر postgres (حداکثر ۱۲۰ ثانیه) منتظر elasticsearch (حداکثر ۱۸۰ ثانیه) python manage.py migrate --noinput python manage.py seed_users # اگر SEED_USERS=true python manage.py import_profiles # اگر SEED_DATASET=true و دیتابیس خالی باشد python manage.py search_index --rebuild exec gunicorn ... ```
ایمپورت **idempotent** است و فقط وقتی اجرا می‌شود که جدول پروفایل خالی باشد؛ ری‌استارت کانتینر داده را دوباره وارد نمی‌کند. ### ۱.۲ حالت توسعه (hot reload)
```bash make dev # SPA (Vite) → http://localhost:5173 # API → http://localhost:8000/api/docs/ ```
سورس‌ها bind-mount می‌شوند: تغییر در پایتون باعث ری‌استارت `runserver` و تغییر در TSX باعث HMR می‌شود. ### ۱.۳ حالت پروداکشن
```bash make prod # = docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d ```
تفاوت‌ها: احراز هویت Elasticsearch روشن، Kibana منتشر نمی‌شود، seed خاموش، محدودیت CPU/RAM، دو نمونه بک‌اند. ### ۱.۴ اجرا بدون Elasticsearch
```bash echo "SEARCH_BACKEND=postgres" >> .env docker compose up -d postgres redis backend frontend ```
کل API و فرانت‌اند بدون تغییر کار می‌کنند؛ فیلد `backend` در پاسخ سرچ `"postgres"` می‌شود.
--- ## ۲. لاگ ساخت‌یافته ### ۲.۱ شکل رکوردها ```json {"@timestamp":"2026-09-03T04:00:08.574Z","level":"INFO","logger":"audit.search", "message":"search_executed","service":"linkedin-search-api","environment":"production", "request_id":"bb3c21c555a54ae0a6dafcd12ea1a98c","event":"search_executed", "user_id":2,"backend":"elasticsearch","result_count":80,"took_ms":31, "query":"leadership","filters":{"skills":["management"],"countries":["united states"], "roles":[],"seniority":[],"industries":[],"companies":[], "min_experience":null,"max_experience":null}, "sort":"relevance","page":1,"page_size":3} ```
| لاگر | رویدادها | |---|---| | `apps.access` | `http_request` — متد، مسیر، وضعیت، `duration_ms`، IP، user id | | `audit.search` | `search_executed` — کوئری، فیلترها، تعداد نتیجه، زمان، موتور | | `audit.auth` | `login_succeeded`، `logout`، `user_registered` | | `apps.errors` | `unhandled_exception` با stack trace | | `gunicorn.access` | خط دسترسی وب‌سرور (JSON) | ### ۲.۲ مسیر انتقال
``` Django logger │ JSONFormatter → یک شیء JSON در هر رکورد │ RequestIDFilter → افزودن request_id از ContextVar ├──▶ stdout (همیشه؛ قابل دیدن با docker compose logs) └──▶ AsynchronousLogstashHandler │ بافر درون‌حافظه‌ای + ترد جدا ▼ Logstash :5959 (codec: json_lines) │ غنی‌سازی، برچسب‌گذاری، حذف فیلدهای حساس ▼ Elasticsearch app-logs-{stream}-{YYYY.MM.dd} ▼ Kibana ```
**چرا async؟** اگر Logstash از دسترس خارج شود، یک هندلر همگام ترد درخواست را بلاک می‌کند و کل API کند می‌شود. با `python-logstash-async` رکوردها در حافظه صف می‌شوند و یک ترد جدا آن‌ها را می‌فرستد. این رفتار عملاً آزموده شده: با Logstash خاموش، تنها اثرش یک warning در stdout است. ### ۲.۳ مسیردهی به ایندکس‌ها
| ایندکس | محتوا | |---|---| | `app-logs-search-*` | رویدادهای `search_executed` | | `app-logs-audit-*` | ورود، خروج، ثبت‌نام | | `app-logs-error-*` | `ERROR` و `CRITICAL` | | `app-logs-app-*` | بقیه |
یک ایندکس در روز برای هر جریان. نتیجه: «همه لاگ‌های قدیمی‌تر از ۳۰ روز را پاک کن» یک حذف ایندکس است، نه یک `delete_by_query` سنگین. **برچسب‌های خودکار:** `slow_search` (بیش از ۵۰۰ms) و `zero_results`.
--- ## ۳. Kibana ```bash make kibana-setup # ساخت data view «Application logs» open http://localhost:5601 ``` ### کوئری‌های مفید در Discover ``` event: "search_executed" and result_count: 0 → کاربران چه چیزی می‌خواستند که پیدا نکردند؟ (بهترین سیگنال برای بهبود سرچ) event: "search_executed" and took_ms > 200 → کدام ترکیب فیلترها کند است؟ event: "search_executed" and filters.skills: * → کدام مهارت‌ها بیشترین جستجو را دارند؟ level: "ERROR" → خطاها با stack trace کامل request_id: "bb3c21c555a54ae0a6dafcd12ea1a98c" → کل ردپای یک درخواست خاص (شامل خط دسترسی و رویداد ممیزی) http_status >= 400 and http_path: "/api/v1/search/" → درخواست‌های ناموفق سرچ ```
**چطور `request_id` را پیدا کنم؟** هدر `X-Request-ID` در تب Network مرورگر، یا فیلد `request_id` داخل بدنه هر پاسخ خطا. ### داشبوردی که ارزش ساختن دارد | ویجت | کوئری | |---|---| | نرخ سرچ | `event: "search_executed"` — Date histogram | | صدک ۹۵ زمان پاسخ | `took_ms` — Percentiles | | نرخ نتیجه‌ی صفر | `result_count: 0` تقسیم بر کل | | پرجستجوترین مهارت‌ها | `filters.skills` — Terms | | نرخ خطا | `http_status >= 500` | | کاربران فعال | `user_id` — Unique count |
--- ## ۴. عملیات روزمره ```bash make ps # وضعیت کانتینرها + healthcheck make logs # لاگ زنده همه سرویس‌ها docker compose logs -f backend # فقط API make seed # ایمپورت مجدد کامل + بازسازی ایندکس make reindex # فقط بازسازی ایندکس از روی Postgres make index-status # وضعیت alias و تعداد داکیومنت make users # ساخت/به‌روزرسانی کاربران نمونه make superuser # ساخت superuser تعاملی make shell # شل جنگو make test # تست‌ها داخل کانتینر make lint # lint + type-check make audit # اسکن امنیتی وابستگی‌ها make schema # نوشتن docs/openapi.yaml ``` ### بررسی سلامت ```bash curl -s localhost:8080/api/v1/health/live/ | jq curl -s localhost:8080/api/v1/health/ready/ | jq ``` --- ## ۵. عیب‌یابی
### Elasticsearch بالا نمی‌آید **علامت:** کانتینر `elasticsearch` مدام ری‌استارت می‌شود؛ در لاگ: `max virtual memory areas vm.max_map_count [65530] is too low`. **علت:** Elasticsearch به `vm.max_map_count ≥ 262144` نیاز دارد.
```bash sudo sysctl -w vm.max_map_count=262144 # موقت echo 'vm.max_map_count=262144' | sudo tee -a /etc/sysctl.conf # دائمی ```
روی Docker Desktop (مک/ویندوز) معمولاً از قبل تنظیم است. ### Elasticsearch به‌خاطر حافظه کشته می‌شود (exit 137) `ES_JAVA_OPTS` را در `.env` کم کنید:
```env ES_JAVA_OPTS=-Xms256m -Xmx256m LS_JAVA_OPTS=-Xms192m -Xmx192m ```
یا اگر رم کم است، Kibana و Logstash را بالا نیاورید:
```bash docker compose up -d postgres redis elasticsearch backend frontend ```
### بک‌اند با «DJANGO_SECRET_KEY is required» بالا نمی‌آید `.env` ساخته نشده یا کلید خالی است:
```bash cp .env.example .env python -c "import secrets; print(secrets.token_urlsafe(64))" # مقدار را در .env بگذارید ```
### سرچ ۵۰۳ می‌دهد
```bash curl -s localhost:8080/api/v1/health/ready/ | jq # کدام وابستگی خراب است؟ docker compose logs elasticsearch | tail -50 make index-status # آیا ایندکس اصلاً وجود دارد؟ make reindex # بازسازی ```
راه‌حل موقت: `SEARCH_BACKEND=postgres` در `.env` و `docker compose restart backend`. ### سرچ نتیجه نمی‌دهد ولی داده هست
```bash make index-status # {"alias": "linkedin-profiles", "exists": true, "documents": 284} ```
اگر `documents: 0` بود، ایمپورت انجام شده ولی ایندکس ساخته نشده: `make reindex`. ### لاگ‌ها در Kibana دیده نمی‌شوند
```bash docker compose logs logstash | tail -30 # آیا pipeline بالا آمده؟ docker compose exec backend env | grep LOGSTASH # LOGSTASH_ENABLED=true ؟ curl -s localhost:5601/api/status | jq .status.overall ./scripts/kibana_setup.sh # آیا data view ساخته شده؟ ```
Logstash حدود ۶۰ ثانیه طول می‌کشد تا آماده شود؛ لاگ‌های قبل از آن در بافر می‌مانند و بعد ارسال می‌شوند. ### فرانت‌اند «Network Error» می‌دهد
```bash docker compose ps backend # سالم است؟ docker compose exec frontend wget -qO- http://backend:8000/api/v1/health/live/ ```
اگر با `npm run dev` بیرون از داکر کار می‌کنید، مطمئن شوید مبدأ `http://localhost:5173` در `CORS_ALLOWED_ORIGINS` هست. ### مایگریشن روی `pg_trgm` شکست می‌خورد مایگریشن `0002_postgres_extensions` نیاز به دسترسی ساخت extension دارد. در ایمیج رسمی Postgres کاربر پیش‌فرض superuser است؛ در دیتابیس مدیریت‌شده باید ادمین یک بار اجرا کند:
```sql CREATE EXTENSION IF NOT EXISTS pg_trgm; CREATE EXTENSION IF NOT EXISTS unaccent; ``` --- ## ۶. پشتیبان‌گیری و بازیابی ```bash # پشتیبان docker compose exec -T postgres pg_dump -U linkedin linkedin | gzip > backup-$(date +%F).sql.gz # بازیابی gunzip -c backup-2026-09-03.sql.gz | docker compose exec -T postgres psql -U linkedin linkedin make reindex # ایندکس از Postgres بازسازی می‌شود ```
از Elasticsearch **پشتیبان نمی‌گیریم**. ایندکس یک projection دورریختنی است و همیشه از Postgres قابل بازسازی است — این دقیقاً دلیل انتخاب آن معماری بود.
--- ## ۷. سنجه‌های عملکرد (دیتاست ۲۸۴ رکوردی)
| عملیات | زمان | |---|---| | ایمپورت کامل (خواندن + ترمیم + نوشتن) | ‎~۱۵ ثانیه | | بازسازی ایندکس | ‎~۲ ثانیه | | سرچ با Elasticsearch | ‎~۵–۱۵ms | | سرچ با PostgreSQL | ‎~۱۵–۳۵ms | | جزئیات پروفایل | ‎~۲۰ms | | بیلد ایمیج بک‌اند (بدون کش) | ‎~۹۰ ثانیه | | بیلد ایمیج فرانت‌اند (بدون کش) | ‎~۶۰ ثانیه | اعداد سرچ روی همین دیتاست و ماشین توسعه اندازه‌گیری شده‌اند؛ فیلد `took_ms` در هر پاسخ همین را گزارش می‌کند و در Kibana قابل نمودار کردن است.
--- ## ۸. چک‌لیست پیش از استقرار
- [ ] `DJANGO_SECRET_KEY` تصادفی و ≥۵۰ کاراکتر - [ ] `POSTGRES_PASSWORD` و `ELASTIC_PASSWORD` عوض شده - [ ] رمزهای کاربران نمونه عوض یا `SEED_USERS=false` - [ ] `DJANGO_DEBUG=false` و `DJANGO_SETTINGS_MODULE=config.settings.prod` - [ ] `DJANGO_ALLOWED_HOSTS` روی دامنه واقعی - [ ] `DJANGO_ADMIN_URL` روی یک مسیر غیرقابل‌حدس - [ ] HTTPS در لبه + `SECURE_SSL_REDIRECT=true` - [ ] `SEED_DATASET=false` - [ ] overlay پروداکشن فعال (احراز هویت ES) - [ ] پشتیبان‌گیری خودکار Postgres - [ ] سیاست نگهداری لاگ (ILM) روی `app-logs-*` - [ ] `docker compose exec backend python manage.py check --deploy` بدون خطا