421 lines
13 KiB
Markdown
421 lines
13 KiB
Markdown
|
|
# عملیات: اجرا، مانیتورینگ و عیبیابی
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## ۱. اجرا
|
|||
|
|
|
|||
|
|
### ۱.۱ حالت کامل (پیشفرض)
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
cp .env.example .env
|
|||
|
|
docker compose up --build # یا: make up
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
<div dir="rtl">
|
|||
|
|
|
|||
|
|
ترتیب بالا آمدن سرویسها با `depends_on: condition: service_healthy` کنترل میشود:
|
|||
|
|
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
postgres ──┐
|
|||
|
|
redis ─────┼──▶ backend ──▶ frontend
|
|||
|
|
elasticsearch ─┴──▶ logstash
|
|||
|
|
└──▶ kibana
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
<div dir="rtl">
|
|||
|
|
|
|||
|
|
اولین اجرا ۳ تا ۵ دقیقه طول میکشد. `entrypoint.sh` در کانتینر بکاند:
|
|||
|
|
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
منتظر 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 ...
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
<div dir="rtl">
|
|||
|
|
|
|||
|
|
ایمپورت **idempotent** است و فقط وقتی اجرا میشود که جدول پروفایل خالی باشد؛ ریاستارت کانتینر داده را دوباره وارد نمیکند.
|
|||
|
|
|
|||
|
|
### ۱.۲ حالت توسعه (hot reload)
|
|||
|
|
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
make dev
|
|||
|
|
# SPA (Vite) → http://localhost:5173
|
|||
|
|
# API → http://localhost:8000/api/docs/
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
<div dir="rtl">
|
|||
|
|
|
|||
|
|
سورسها bind-mount میشوند: تغییر در پایتون باعث ریاستارت `runserver` و تغییر در TSX باعث HMR میشود.
|
|||
|
|
|
|||
|
|
### ۱.۳ حالت پروداکشن
|
|||
|
|
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
make prod
|
|||
|
|
# = docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
<div dir="rtl">
|
|||
|
|
|
|||
|
|
تفاوتها: احراز هویت Elasticsearch روشن، Kibana منتشر نمیشود، seed خاموش، محدودیت CPU/RAM، دو نمونه بکاند.
|
|||
|
|
|
|||
|
|
### ۱.۴ اجرا بدون Elasticsearch
|
|||
|
|
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
echo "SEARCH_BACKEND=postgres" >> .env
|
|||
|
|
docker compose up -d postgres redis backend frontend
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
<div dir="rtl">
|
|||
|
|
|
|||
|
|
کل API و فرانتاند بدون تغییر کار میکنند؛ فیلد `backend` در پاسخ سرچ `"postgres"` میشود.
|
|||
|
|
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## ۲. لاگ ساختیافته
|
|||
|
|
|
|||
|
|
### ۲.۱ شکل رکوردها
|
|||
|
|
|
|||
|
|
```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}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
<div dir="rtl">
|
|||
|
|
|
|||
|
|
| لاگر | رویدادها |
|
|||
|
|
|---|---|
|
|||
|
|
| `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) |
|
|||
|
|
|
|||
|
|
### ۲.۲ مسیر انتقال
|
|||
|
|
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
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
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
<div dir="rtl">
|
|||
|
|
|
|||
|
|
**چرا async؟** اگر Logstash از دسترس خارج شود، یک هندلر همگام ترد درخواست را بلاک میکند و کل API کند میشود. با `python-logstash-async` رکوردها در حافظه صف میشوند و یک ترد جدا آنها را میفرستد. این رفتار عملاً آزموده شده: با Logstash خاموش، تنها اثرش یک warning در stdout است.
|
|||
|
|
|
|||
|
|
### ۲.۳ مسیردهی به ایندکسها
|
|||
|
|
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
| ایندکس | محتوا |
|
|||
|
|
|---|---|
|
|||
|
|
| `app-logs-search-*` | رویدادهای `search_executed` |
|
|||
|
|
| `app-logs-audit-*` | ورود، خروج، ثبتنام |
|
|||
|
|
| `app-logs-error-*` | `ERROR` و `CRITICAL` |
|
|||
|
|
| `app-logs-app-*` | بقیه |
|
|||
|
|
|
|||
|
|
<div dir="rtl">
|
|||
|
|
|
|||
|
|
یک ایندکس در روز برای هر جریان. نتیجه: «همه لاگهای قدیمیتر از ۳۰ روز را پاک کن» یک حذف ایندکس است، نه یک `delete_by_query` سنگین.
|
|||
|
|
|
|||
|
|
**برچسبهای خودکار:** `slow_search` (بیش از ۵۰۰ms) و `zero_results`.
|
|||
|
|
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## ۳. 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/"
|
|||
|
|
→ درخواستهای ناموفق سرچ
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
<div dir="rtl">
|
|||
|
|
|
|||
|
|
**چطور `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 |
|
|||
|
|
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## ۴. عملیات روزمره
|
|||
|
|
|
|||
|
|
```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
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## ۵. عیبیابی
|
|||
|
|
|
|||
|
|
<div dir="rtl">
|
|||
|
|
|
|||
|
|
### Elasticsearch بالا نمیآید
|
|||
|
|
|
|||
|
|
**علامت:** کانتینر `elasticsearch` مدام ریاستارت میشود؛ در لاگ: `max virtual memory areas vm.max_map_count [65530] is too low`.
|
|||
|
|
|
|||
|
|
**علت:** Elasticsearch به `vm.max_map_count ≥ 262144` نیاز دارد.
|
|||
|
|
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
sudo sysctl -w vm.max_map_count=262144 # موقت
|
|||
|
|
echo 'vm.max_map_count=262144' | sudo tee -a /etc/sysctl.conf # دائمی
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
<div dir="rtl">
|
|||
|
|
|
|||
|
|
روی Docker Desktop (مک/ویندوز) معمولاً از قبل تنظیم است.
|
|||
|
|
|
|||
|
|
### Elasticsearch بهخاطر حافظه کشته میشود (exit 137)
|
|||
|
|
|
|||
|
|
`ES_JAVA_OPTS` را در `.env` کم کنید:
|
|||
|
|
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
```env
|
|||
|
|
ES_JAVA_OPTS=-Xms256m -Xmx256m
|
|||
|
|
LS_JAVA_OPTS=-Xms192m -Xmx192m
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
<div dir="rtl">
|
|||
|
|
|
|||
|
|
یا اگر رم کم است، Kibana و Logstash را بالا نیاورید:
|
|||
|
|
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
docker compose up -d postgres redis elasticsearch backend frontend
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
<div dir="rtl">
|
|||
|
|
|
|||
|
|
### بکاند با «DJANGO_SECRET_KEY is required» بالا نمیآید
|
|||
|
|
|
|||
|
|
`.env` ساخته نشده یا کلید خالی است:
|
|||
|
|
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
cp .env.example .env
|
|||
|
|
python -c "import secrets; print(secrets.token_urlsafe(64))" # مقدار را در .env بگذارید
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
<div dir="rtl">
|
|||
|
|
|
|||
|
|
### سرچ ۵۰۳ میدهد
|
|||
|
|
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
curl -s localhost:8080/api/v1/health/ready/ | jq # کدام وابستگی خراب است؟
|
|||
|
|
docker compose logs elasticsearch | tail -50
|
|||
|
|
make index-status # آیا ایندکس اصلاً وجود دارد؟
|
|||
|
|
make reindex # بازسازی
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
<div dir="rtl">
|
|||
|
|
|
|||
|
|
راهحل موقت: `SEARCH_BACKEND=postgres` در `.env` و `docker compose restart backend`.
|
|||
|
|
|
|||
|
|
### سرچ نتیجه نمیدهد ولی داده هست
|
|||
|
|
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
make index-status
|
|||
|
|
# {"alias": "linkedin-profiles", "exists": true, "documents": 284}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
<div dir="rtl">
|
|||
|
|
|
|||
|
|
اگر `documents: 0` بود، ایمپورت انجام شده ولی ایندکس ساخته نشده: `make reindex`.
|
|||
|
|
|
|||
|
|
### لاگها در Kibana دیده نمیشوند
|
|||
|
|
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
```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 ساخته شده؟
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
<div dir="rtl">
|
|||
|
|
|
|||
|
|
Logstash حدود ۶۰ ثانیه طول میکشد تا آماده شود؛ لاگهای قبل از آن در بافر میمانند و بعد ارسال میشوند.
|
|||
|
|
|
|||
|
|
### فرانتاند «Network Error» میدهد
|
|||
|
|
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
docker compose ps backend # سالم است؟
|
|||
|
|
docker compose exec frontend wget -qO- http://backend:8000/api/v1/health/live/
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
<div dir="rtl">
|
|||
|
|
|
|||
|
|
اگر با `npm run dev` بیرون از داکر کار میکنید، مطمئن شوید مبدأ `http://localhost:5173` در `CORS_ALLOWED_ORIGINS` هست.
|
|||
|
|
|
|||
|
|
### مایگریشن روی `pg_trgm` شکست میخورد
|
|||
|
|
|
|||
|
|
مایگریشن `0002_postgres_extensions` نیاز به دسترسی ساخت extension دارد. در ایمیج رسمی Postgres کاربر پیشفرض superuser است؛ در دیتابیس مدیریتشده باید ادمین یک بار اجرا کند:
|
|||
|
|
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
```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 بازسازی میشود
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
<div dir="rtl">
|
|||
|
|
|
|||
|
|
از Elasticsearch **پشتیبان نمیگیریم**. ایندکس یک projection دورریختنی است و همیشه از Postgres قابل بازسازی است — این دقیقاً دلیل انتخاب آن معماری بود.
|
|||
|
|
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## ۷. سنجههای عملکرد (دیتاست ۲۸۴ رکوردی)
|
|||
|
|
|
|||
|
|
<div dir="rtl">
|
|||
|
|
|
|||
|
|
| عملیات | زمان |
|
|||
|
|
|---|---|
|
|||
|
|
| ایمپورت کامل (خواندن + ترمیم + نوشتن) | ~۱۵ ثانیه |
|
|||
|
|
| بازسازی ایندکس | ~۲ ثانیه |
|
|||
|
|
| سرچ با Elasticsearch | ~۵–۱۵ms |
|
|||
|
|
| سرچ با PostgreSQL | ~۱۵–۳۵ms |
|
|||
|
|
| جزئیات پروفایل | ~۲۰ms |
|
|||
|
|
| بیلد ایمیج بکاند (بدون کش) | ~۹۰ ثانیه |
|
|||
|
|
| بیلد ایمیج فرانتاند (بدون کش) | ~۶۰ ثانیه |
|
|||
|
|
|
|||
|
|
اعداد سرچ روی همین دیتاست و ماشین توسعه اندازهگیری شدهاند؛ فیلد `took_ms` در هر پاسخ همین را گزارش میکند و در Kibana قابل نمودار کردن است.
|
|||
|
|
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## ۸. چکلیست پیش از استقرار
|
|||
|
|
|
|||
|
|
<div dir="rtl">
|
|||
|
|
|
|||
|
|
- [ ] `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` بدون خطا
|
|||
|
|
|
|||
|
|
</div>
|