انسانوں کے لیے ہاؤس ڈیٹا بیس، یا ایلین ٹیکنالوجیز پر کلک کریں۔

ایم کے بی کے ڈائریکٹوریٹ آف انفارمیشن ٹیکنالوجیز کے ریموٹ سروس چینلز کے قابلیت مرکز کے سربراہ الیکسی لیزونوف

انسانوں کے لیے ہاؤس ڈیٹا بیس، یا ایلین ٹیکنالوجیز پر کلک کریں۔

ELK اسٹیک (ElasticSearch، Logstash، Kibana) کے متبادل کے طور پر، ہم ClickHouse ڈیٹا بیس کو لاگز کے ڈیٹا اسٹور کے طور پر استعمال کرنے پر تحقیق کر رہے ہیں۔

اس مضمون میں، ہم ClickHouse ڈیٹا بیس کے استعمال کے اپنے تجربے اور پائلٹ آپریشن کے ابتدائی نتائج کے بارے میں بات کرنا چاہیں گے۔ یہ فوری طور پر غور کیا جانا چاہئے کہ نتائج متاثر کن تھے۔


انسانوں کے لیے ہاؤس ڈیٹا بیس، یا ایلین ٹیکنالوجیز پر کلک کریں۔

اگلا، ہم مزید تفصیل سے بیان کریں گے کہ ہمارا سسٹم کس طرح ترتیب دیا گیا ہے، اور یہ کن اجزاء پر مشتمل ہے۔ لیکن اب میں مجموعی طور پر اس ڈیٹا بیس کے بارے میں تھوڑی بات کرنا چاہوں گا، اور اس پر توجہ دینے کے قابل کیوں ہے۔ ClickHouse ڈیٹا بیس Yandex کی جانب سے ایک اعلیٰ کارکردگی کا تجزیاتی کالمی ڈیٹا بیس ہے۔ یہ Yandex کی خدمات میں استعمال ہوتا ہے، ابتدائی طور پر یہ Yandex.Metrica کے لیے اہم ڈیٹا اسٹوریج ہے۔ اوپن سورس سسٹم، مفت۔ ایک ڈویلپر کے نقطہ نظر سے، میں نے ہمیشہ سوچا ہے کہ انہوں نے اسے کیسے نافذ کیا، کیونکہ بہت بڑا ڈیٹا موجود ہے۔ اور Metrica کا صارف انٹرفیس خود بہت لچکدار اور تیز ہے۔ اس ڈیٹا بیس کے ساتھ پہلی واقفیت پر، تاثر یہ ہے: "ٹھیک ہے، آخر میں! لوگوں کے لیے بنایا گیا! انسٹالیشن کے عمل سے شروع ہو کر اور درخواستیں بھیجنے کے ساتھ ختم ہوتا ہے۔

اس ڈیٹا بیس میں داخلے کی حد بہت کم ہے۔ یہاں تک کہ ایک اوسط ہنر مند ڈویلپر بھی چند منٹوں میں اس ڈیٹا بیس کو انسٹال کر کے اسے استعمال کرنا شروع کر سکتا ہے۔ سب کچھ واضح طور پر کام کرتا ہے۔ یہاں تک کہ وہ لوگ جو لینکس میں نئے ہیں وہ انسٹالیشن کو جلدی سے سنبھال سکتے ہیں اور آسان ترین آپریشن کر سکتے ہیں۔ اگر پہلے بگ ڈیٹا، ہڈوپ، گوگل بگ ٹیبل، ایچ ڈی ایف ایس کے الفاظ کے ساتھ، ایک عام ڈویلپر کے پاس یہ خیال تھا کہ یہ کچھ ٹیرا بائٹس، پیٹا بائٹس کے بارے میں ہے، کہ کچھ مافوق الفطرت انسان ان نظاموں کی ترتیب اور ترقی میں ملوث ہیں، تو کلک ہاؤس کی آمد کے ساتھ۔ ڈیٹا بیس، ہمیں ایک سادہ، قابل فہم ٹول ملا ہے جس کی مدد سے آپ پہلے سے ناقابل حصول کاموں کو حل کر سکتے ہیں۔ اسے انسٹال کرنے میں صرف ایک کافی اوسط مشین اور پانچ منٹ لگتے ہیں۔ یعنی، ہمیں ایسا ڈیٹا بیس ملا ہے، مثال کے طور پر، MySql، لیکن صرف اربوں ریکارڈز کو ذخیرہ کرنے کے لیے! ایس کیو ایل زبان کے ساتھ ایک خاص سپر آرکائیور۔ یہ ایسا ہی ہے جیسے لوگوں کو غیر ملکیوں کے ہتھیار سونپے گئے تھے۔

ہمارے لاگنگ سسٹم کے بارے میں

معلومات جمع کرنے کے لیے، معیاری فارمیٹ کی ویب ایپلیکیشنز کی IIS لاگ فائلیں استعمال کی جاتی ہیں (ہم فی الحال ایپلیکیشن لاگز کو پارس کر رہے ہیں، لیکن پائلٹ اسٹیج پر بنیادی مقصد IIS لاگز کو جمع کرنا ہے)۔

مختلف وجوہات کی بنا پر، ہم ELK اسٹیک کو مکمل طور پر ترک نہیں کر سکے، اور ہم LogStash اور Filebeat اجزاء کا استعمال جاری رکھتے ہیں، جنہوں نے خود کو اچھی طرح سے ثابت کیا ہے اور کافی قابل اعتماد اور پیش گوئی کے ساتھ کام کرتے ہیں۔

عام لاگنگ اسکیم نیچے دی گئی تصویر میں دکھائی گئی ہے۔

انسانوں کے لیے ہاؤس ڈیٹا بیس، یا ایلین ٹیکنالوجیز پر کلک کریں۔

ClickHouse ڈیٹا بیس میں ڈیٹا لکھنے کی ایک خصوصیت یہ ہے کہ بڑے بیچوں میں ریکارڈز کو کبھی کبھار (ایک سیکنڈ میں ایک بار) داخل کیا جاتا ہے۔ بظاہر، یہ وہ سب سے زیادہ "مسئلہ" حصہ ہے جس کا سامنا آپ کو اس وقت ہوتا ہے جب آپ پہلی بار ClickHouse ڈیٹا بیس کے ساتھ کام کرنے کا تجربہ کرتے ہیں: اسکیم کچھ زیادہ پیچیدہ ہو جاتی ہے۔
LogStash کے لیے پلگ ان، جو براہ راست ClickHouse میں ڈیٹا داخل کرتا ہے، نے یہاں بہت مدد کی۔ یہ جزو اسی سرور پر تعینات کیا گیا ہے جیسا کہ خود ڈیٹا بیس ہے۔ لہذا، عام طور پر، یہ کرنے کی سفارش نہیں کی جاتی ہے، لیکن ایک عملی نقطہ نظر سے، تاکہ ایک ہی سرور پر تعینات ہونے کے دوران علیحدہ سرور پیدا نہ ہو. ہم نے ڈیٹا بیس کے ساتھ کسی ناکامی یا وسائل کے تنازعات کا مشاہدہ نہیں کیا۔ اس کے علاوہ، یہ بھی واضح رہے کہ پلگ ان میں غلطیوں کی صورت میں دوبارہ کوشش کرنے کا طریقہ کار موجود ہے۔ اور غلطیوں کی صورت میں، پلگ ان ڈیٹا کے ایک بیچ کو ڈسک پر لکھتا ہے جو داخل نہیں کیا جا سکتا تھا (فائل کی شکل آسان ہے: ترمیم کرنے کے بعد، آپ آسانی سے کلک ہاؤس کلائنٹ کا استعمال کرتے ہوئے درست بیچ داخل کر سکتے ہیں)۔

اسکیم میں استعمال ہونے والے سافٹ ویئر کی مکمل فہرست ٹیبل میں پیش کی گئی ہے:

استعمال شدہ سافٹ ویئر کی فہرست

نام

تفصیل

تقسیم کا لنک

این جی این ایکس

ریورس پراکسی بندرگاہوں کے ذریعے رسائی کو محدود کرنے اور اجازت کو منظم کرنے کے لیے

فی الحال اسکیم میں استعمال نہیں کیا گیا ہے۔

https://nginx.org/ru/download.html

https://nginx.org/download/nginx-1.16.0.tar.gz

فائل بیٹ

فائل لاگز کی منتقلی۔

https://www.elastic.co/downloads/beats/filebeat (ونڈوز 64 بٹ کے لیے ڈسٹری بیوشن کٹ)۔

https://artifacts.elastic.co/downloads/beats/filebeat/filebeat-7.3.0-windows-x86_64.zip

logstash

لاگ کلیکٹر۔

FileBeat سے لاگز جمع کرنے کے ساتھ ساتھ RabbitMQ قطار سے لاگز جمع کرنے کے لیے استعمال کیا جاتا ہے (سرورز کے لیے جو DMZ میں ہیں۔)

https://www.elastic.co/products/logstash

https://artifacts.elastic.co/downloads/logstash/logstash-7.0.1.rpm

Logstash-output-clickhouse

لاگ اسٹاش پلگ ان بیچوں میں کلک ہاؤس ڈیٹا بیس میں لاگز کی منتقلی کے لیے

https://github.com/mikechris/logstash-output-clickhouse

/usr/share/logstash/bin/logstash-plugin انسٹال کریں logstash-output-clickhouse

/usr/share/logstash/bin/logstash-plugin انسٹال کریں logstash-filter-prune

/usr/share/logstash/bin/logstash-plugin انسٹال کریں logstash-filter-multiline

کلک ہاؤس

لاگ اسٹوریج https://clickhouse.yandex/docs/ru/

https://packagecloud.io/Altinity/clickhouse/packages/el/7/clickhouse-server-19.5.3.8-1.el7.x86_64.rpm

https://packagecloud.io/Altinity/clickhouse/packages/el/7/clickhouse-client-19.5.3.8-1.el7.x86_64.rpm

نوٹ. اگست 2018 سے، RHEL کے لیے "نارمل" rpm کی تعمیر Yandex کے ذخیرے میں نمودار ہوئی، لہذا آپ انہیں استعمال کرنے کی کوشش کر سکتے ہیں۔ انسٹالیشن کے وقت، ہم Altinity کے بنائے ہوئے پیکجز استعمال کر رہے تھے۔

گرافانا

لاگ ویژولائزیشن۔ ڈیش بورڈز ترتیب دینا

https://grafana.com/

https://grafana.com/grafana/download

Redhat & Centos (64 بٹ) - تازہ ترین ورژن

گرافانا 4.6+ کے لیے ہاؤس ڈیٹا سورس پر کلک کریں۔

کلک ہاؤس ڈیٹا سورس کے ساتھ گرافانا کے لیے پلگ ان

https://grafana.com/plugins/vertamedia-clickhouse-datasource

https://grafana.com/api/plugins/vertamedia-clickhouse-datasource/versions/1.8.1/download

logstash

فائل بیٹ سے RabbitMQ قطار میں راؤٹر لاگ کریں۔

نوٹ. بدقسمتی سے، FileBeat میں براہ راست RabbitMQ پر آؤٹ پٹ نہیں ہے، لہذا Logstash کی شکل میں ایک انٹرمیڈیٹ لنک درکار ہے۔

https://www.elastic.co/products/logstash

https://artifacts.elastic.co/downloads/logstash/logstash-7.0.1.rpm

خرگوش

پیغام کی قطار. یہ DMZ میں لاگ بفر ہے۔

https://www.rabbitmq.com/download.html

https://github.com/rabbitmq/rabbitmq-server/releases/download/v3.7.14/rabbitmq-server-3.7.14-1.el7.noarch.rpm

Erlang رن ٹائم (RabbitMQ کے لیے درکار ہے)

ایرلنگ رن ٹائم۔ RabbitMQ کو کام کرنے کے لیے درکار ہے۔

http://www.erlang.org/download.html

https://www.rabbitmq.com/install-rpm.html#install-erlang http://www.erlang.org/downloads/21.3

کلک ہاؤس ڈیٹا بیس کے ساتھ سرور کی ترتیب درج ذیل جدول میں پیش کی گئی ہے۔

نام

ویلیو

نوٹ

تشکیل

ایچ ڈی ڈی: 40GB۔
رام: 8GB
پروسیسر: کور 2 2 گیگا ہرٹز

کلک ہاؤس ڈیٹا بیس کو چلانے کے لیے تجاویز پر توجہ دینا ضروری ہے (https://clickhouse.yandex/docs/ru/operations/tips/)

جنرل سسٹم سافٹ ویئر

OS: Red Hat Enterprise Linux سرور (Maipo)

JRE (Java 8)

 

جیسا کہ آپ دیکھ سکتے ہیں، یہ ایک عام ورک سٹیشن ہے۔

نوشتہ جات کو ذخیرہ کرنے کے لیے میز کی ساخت اس طرح ہے:

log_web.sql

CREATE TABLE log_web (
  logdate Date,
  logdatetime DateTime CODEC(Delta, LZ4HC),
   
  fld_log_file_name LowCardinality( String ),
  fld_server_name LowCardinality( String ),
  fld_app_name LowCardinality( String ),
  fld_app_module LowCardinality( String ),
  fld_website_name LowCardinality( String ),
 
  serverIP LowCardinality( String ),
  method LowCardinality( String ),
  uriStem String,
  uriQuery String,
  port UInt32,
  username LowCardinality( String ),
  clientIP String,
  clientRealIP String,
  userAgent String,
  referer String,
  response String,
  subresponse String,
  win32response String,
  timetaken UInt64
   
  , uriQuery__utm_medium String
  , uriQuery__utm_source String
  , uriQuery__utm_campaign String
  , uriQuery__utm_term String
  , uriQuery__utm_content String
  , uriQuery__yclid String
  , uriQuery__region String
 
) Engine = MergeTree()
PARTITION BY toYYYYMM(logdate)
ORDER BY (fld_app_name, fld_app_module, logdatetime)
SETTINGS index_granularity = 8192;

ہم ڈیفالٹ پارٹیشننگ (مہینے کے لحاظ سے) اور انڈیکس گرینولریٹی استعمال کرتے ہیں۔ تمام فیلڈز عملی طور پر HTTP درخواستوں کو لاگ ان کرنے کے لیے IIS لاگ اندراجات سے مطابقت رکھتے ہیں۔ الگ سے، ہم نوٹ کرتے ہیں کہ utm-tags کو ذخیرہ کرنے کے لیے الگ الگ فیلڈز ہیں (وہ استفسار کے سٹرنگ فیلڈ سے ٹیبل میں داخل کرنے کے مرحلے پر تجزیہ کیے جاتے ہیں)۔

نیز، سسٹمز، اجزاء، سرورز کے بارے میں معلومات کو ذخیرہ کرنے کے لیے ٹیبل میں کئی سسٹم فیلڈز شامل کیے گئے ہیں۔ ان شعبوں کی تفصیل کے لیے نیچے دی گئی جدول دیکھیں۔ ایک ٹیبل میں، ہم کئی سسٹمز کے لیے لاگز اسٹور کرتے ہیں۔

نام

تفصیل

مثال کے طور پر

fld_app_name

ایپلیکیشن/سسٹم کا نام
درست اقدار:

  • site1.domain.com بیرونی سائٹ 1
  • site2.domain.com بیرونی سائٹ 2
  • internal-site1.domain.local اندرونی سائٹ 1

site1.domain.com

fld_app_module

سسٹم ماڈیول
درست اقدار:

  • ویب - ویب سائٹ
  • svc - ویب سائٹ سروس
  • intgr - انٹیگریشن ویب سروس
  • bo - ایڈمن (بیک آفس)

ویب

fld_website_name

IIS میں سائٹ کا نام

ایک سرور پر کئی سسٹم تعینات کیے جا سکتے ہیں، یا ایک سسٹم ماڈیول کی کئی مثالیں بھی

ویب مین

fld_server_name

خدمت گار کا نام

web1.domain.com

fld_log_file_name

سرور پر لاگ فائل کا راستہ

C:inetpublogsLogFiles
W3SVC1u_ex190711.log

یہ آپ کو گرافانا میں مؤثر طریقے سے گراف بنانے کی اجازت دیتا ہے۔ مثال کے طور پر، کسی خاص سسٹم کے فرنٹ اینڈ سے درخواستیں دیکھیں۔ یہ Yandex.Metrica میں سائٹ کاؤنٹر کی طرح ہے۔

دو ماہ کے لیے ڈیٹا بیس کے استعمال کے کچھ اعدادوشمار یہ ہیں۔

سسٹمز اور ان کے اجزاء کے ذریعے ٹوٹے ہوئے ریکارڈز کی تعداد

SELECT
    fld_app_name,
    fld_app_module,
    count(fld_app_name) AS rows_count
FROM log_web
GROUP BY
    fld_app_name,
    fld_app_module
    WITH TOTALS
ORDER BY
    fld_app_name ASC,
    rows_count DESC
 
┌─fld_app_name─────┬─fld_app_module─┬─rows_count─┐
│ site1.domain.ru  │ web            │     131441 │
│ site2.domain.ru  │ web            │    1751081 │
│ site3.domain.ru  │ web            │  106887543 │
│ site3.domain.ru  │ svc            │   44908603 │
│ site3.domain.ru  │ intgr          │    9813911 │
│ site4.domain.ru  │ web            │     772095 │
│ site5.domain.ru  │ web            │   17037221 │
│ site5.domain.ru  │ intgr          │     838559 │
│ site5.domain.ru  │ bo             │       7404 │
│ site6.domain.ru  │ web            │     595877 │
│ site7.domain.ru  │ web            │   27778858 │
└──────────────────┴────────────────┴────────────┘
 
Totals:
┌─fld_app_name─┬─fld_app_module─┬─rows_count─┐
│              │                │  210522593 │
└──────────────┴────────────────┴────────────┘
 
11 rows in set. Elapsed: 4.874 sec. Processed 210.52 million rows, 421.67 MB (43.19 million rows/s., 86.51 MB/s.)

ڈسک پر ڈیٹا کی مقدار

SELECT
    formatReadableSize(sum(data_uncompressed_bytes)) AS uncompressed,
    formatReadableSize(sum(data_compressed_bytes)) AS compressed,
    sum(rows) AS total_rows
FROM system.parts
WHERE table = 'log_web'
 
┌─uncompressed─┬─compressed─┬─total_rows─┐
│ 54.50 GiB    │ 4.86 GiB   │  211427094 │
└──────────────┴────────────┴────────────┘
 
1 rows in set. Elapsed: 0.035 sec.

کالموں میں ڈیٹا کمپریشن کی ڈگری

SELECT
    name,
    formatReadableSize(data_uncompressed_bytes) AS uncompressed,
    formatReadableSize(data_compressed_bytes) AS compressed,
    data_uncompressed_bytes / data_compressed_bytes AS compress_ratio
FROM system.columns
WHERE table = 'log_web'
 
┌─name───────────────────┬─uncompressed─┬─compressed─┬─────compress_ratio─┐
│ logdate                │ 401.53 MiB   │ 1.80 MiB   │ 223.16665968777315 │
│ logdatetime            │ 803.06 MiB   │ 35.91 MiB  │ 22.363966401202305 │
│ fld_log_file_name      │ 220.66 MiB   │ 2.60 MiB   │  84.99905736932571 │
│ fld_server_name        │ 201.54 MiB   │ 50.63 MiB  │  3.980924816977078 │
│ fld_app_name           │ 201.17 MiB   │ 969.17 KiB │ 212.55518183686877 │
│ fld_app_module         │ 201.17 MiB   │ 968.60 KiB │ 212.67805817411906 │
│ fld_website_name       │ 201.54 MiB   │ 1.24 MiB   │  162.7204926761546 │
│ serverIP               │ 201.54 MiB   │ 50.25 MiB  │  4.010824061219731 │
│ method                 │ 201.53 MiB   │ 43.64 MiB  │  4.617721053304486 │
│ uriStem                │ 5.13 GiB     │ 832.51 MiB │  6.311522291936919 │
│ uriQuery               │ 2.58 GiB     │ 501.06 MiB │  5.269731450124478 │
│ port                   │ 803.06 MiB   │ 3.98 MiB   │ 201.91673864241824 │
│ username               │ 318.08 MiB   │ 26.93 MiB  │ 11.812513794583598 │
│ clientIP               │ 2.35 GiB     │ 82.59 MiB  │ 29.132328640073343 │
│ clientRealIP           │ 2.49 GiB     │ 465.05 MiB │  5.478382297052563 │
│ userAgent              │ 18.34 GiB    │ 764.08 MiB │  24.57905114484208 │
│ referer                │ 14.71 GiB    │ 1.37 GiB   │ 10.736792723669906 │
│ response               │ 803.06 MiB   │ 83.81 MiB  │  9.582334090987247 │
│ subresponse            │ 399.87 MiB   │ 1.83 MiB   │  218.4831068635027 │
│ win32response          │ 407.86 MiB   │ 7.41 MiB   │ 55.050315514606815 │
│ timetaken              │ 1.57 GiB     │ 402.06 MiB │ 3.9947395692010637 │
│ uriQuery__utm_medium   │ 208.17 MiB   │ 12.29 MiB  │ 16.936148912472955 │
│ uriQuery__utm_source   │ 215.18 MiB   │ 13.00 MiB  │ 16.548367623199912 │
│ uriQuery__utm_campaign │ 381.46 MiB   │ 37.94 MiB  │ 10.055156353418509 │
│ uriQuery__utm_term     │ 231.82 MiB   │ 10.78 MiB  │ 21.502540454070672 │
│ uriQuery__utm_content  │ 441.34 MiB   │ 87.60 MiB  │  5.038260760449327 │
│ uriQuery__yclid        │ 216.88 MiB   │ 16.58 MiB  │  13.07721335008116 │
│ uriQuery__region       │ 204.35 MiB   │ 9.49 MiB   │  21.52661903446796 │
└────────────────────────┴──────────────┴────────────┴────────────────────┘
 
28 rows in set. Elapsed: 0.005 sec.

استعمال شدہ اجزاء کی تفصیل

فائل بیٹ۔ فائل لاگز کو منتقل کرنا

یہ جزو ڈسک پر لاگ فائلوں میں ہونے والی تبدیلیوں کو ٹریک کرتا ہے اور معلومات کو LogStash کو منتقل کرتا ہے۔ ان تمام سرورز پر انسٹال ہے جہاں لاگ فائلیں لکھی جاتی ہیں (عام طور پر IIS)۔ ٹیل موڈ میں کام کرتا ہے (یعنی فائل میں صرف اضافی ریکارڈز کو منتقل کرتا ہے)۔ لیکن الگ سے اسے پوری فائلوں کو منتقل کرنے کے لیے ترتیب دیا جا سکتا ہے۔ جب آپ کو پچھلے مہینوں کا ڈیٹا ڈاؤن لوڈ کرنے کی ضرورت ہو تو یہ مفید ہے۔ لاگ فائل کو صرف ایک فولڈر میں رکھیں اور یہ اسے پوری طرح پڑھ لے گا۔

سروس بند ہونے پر، ڈیٹا کو مزید اسٹوریج میں منتقل نہیں کیا جاتا ہے۔

ایک مثال کی ترتیب اس طرح نظر آتی ہے:

filebeat.yml

filebeat.inputs:
- type: log
  enabled: true
  paths:
    - C:/inetpub/logs/LogFiles/W3SVC1/*.log
  exclude_files: ['.gz$','.zip$']
  tail_files: true
  ignore_older: 24h
  fields:
    fld_server_name: "site1.domain.ru"
    fld_app_name: "site1.domain.ru"
    fld_app_module: "web"
    fld_website_name: "web-main"
 
- type: log
  enabled: true
  paths:
    - C:/inetpub/logs/LogFiles/__Import/access_log-*
  exclude_files: ['.gz$','.zip$']
  tail_files: false
  fields:
    fld_server_name: "site2.domain.ru"
    fld_app_name: "site2.domain.ru"
    fld_app_module: "web"
    fld_website_name: "web-main"
    fld_logformat: "logformat__apache"
 
 
filebeat.config.modules:
  path: ${path.config}/modules.d/*.yml
  reload.enabled: false
  reload.period: 2s
 
output.logstash:
  hosts: ["log.domain.com:5044"]
 
  ssl.enabled: true
  ssl.certificate_authorities: ["C:/filebeat/certs/ca.pem", "C:/filebeat/certs/ca-issuing.pem"]
  ssl.certificate: "C:/filebeat/certs/site1.domain.ru.cer"
  ssl.key: "C:/filebeat/certs/site1.domain.ru.key"
 
#================================ Processors =====================================
 
processors:
  - add_host_metadata: ~
  - add_cloud_metadata: ~

logstash. لاگ کلیکٹر

یہ جزو FileBeat (یا RabbitMQ قطار کے ذریعے) سے لاگ اندراجات حاصل کرنے کے لیے ڈیزائن کیا گیا ہے، ClickHouse ڈیٹا بیس میں بیچز کو پارس کرنے اور داخل کرنے کے لیے۔

ClickHouse میں داخل کرنے کے لیے، Logstash-output-clickhouse پلگ ان استعمال کیا جاتا ہے۔ Logstash پلگ ان میں ایک درخواست کی دوبارہ کوشش کرنے کا طریقہ کار ہے، لیکن باقاعدہ بند ہونے کے ساتھ، بہتر ہے کہ خود سروس کو روک دیا جائے۔ جب روکا جائے گا، پیغامات RabbitMQ قطار میں جمع ہو جائیں گے، اس لیے اگر سٹاپ طویل عرصے کے لیے ہے، تو بہتر ہے کہ سرورز پر Filebeats کو روک دیا جائے۔ ایسی اسکیم میں جہاں RabbitMQ استعمال نہیں کیا جاتا ہے (مقامی نیٹ ورک پر، فائل بیٹ براہ راست لاگ اسٹاش کو لاگ بھیجتا ہے)، فائل بیٹس کافی قابل قبول اور محفوظ طریقے سے کام کرتی ہے، اس لیے ان کے لیے آؤٹ پٹ کی عدم دستیابی بغیر کسی نتیجے کے گزر جاتی ہے۔

ایک مثال کی ترتیب اس طرح نظر آتی ہے:

log_web__filebeat_clickhouse.conf

input {
 
    beats {
        port => 5044
        type => 'iis'
        ssl => true
        ssl_certificate_authorities => ["/etc/logstash/certs/ca.cer", "/etc/logstash/certs/ca-issuing.cer"]
        ssl_certificate => "/etc/logstash/certs/server.cer"
        ssl_key => "/etc/logstash/certs/server-pkcs8.key"
        ssl_verify_mode => "peer"
 
            add_field => {
                "fld_server_name" => "%{[fields][fld_server_name]}"
                "fld_app_name" => "%{[fields][fld_app_name]}"
                "fld_app_module" => "%{[fields][fld_app_module]}"
                "fld_website_name" => "%{[fields][fld_website_name]}"
                "fld_log_file_name" => "%{source}"
                "fld_logformat" => "%{[fields][fld_logformat]}"
            }
    }
 
    rabbitmq {
        host => "queue.domain.com"
        port => 5671
        user => "q-reader"
        password => "password"
        queue => "web_log"
        heartbeat => 30
        durable => true
        ssl => true
        #ssl_certificate_path => "/etc/logstash/certs/server.p12"
        #ssl_certificate_password => "password"
 
        add_field => {
            "fld_server_name" => "%{[fields][fld_server_name]}"
            "fld_app_name" => "%{[fields][fld_app_name]}"
            "fld_app_module" => "%{[fields][fld_app_module]}"
            "fld_website_name" => "%{[fields][fld_website_name]}"
            "fld_log_file_name" => "%{source}"
            "fld_logformat" => "%{[fields][fld_logformat]}"
        }
    }
 
}
 
filter { 
 
      if [message] =~ "^#" {
        drop {}
      }
 
      if [fld_logformat] == "logformat__iis_with_xrealip" {
     
          grok {
            match => ["message", "%{TIMESTAMP_ISO8601:log_timestamp} %{IP:serverIP} %{WORD:method} %{NOTSPACE:uriStem} %{NOTSPACE:uriQuery} %{NUMBER:port} %{NOTSPACE:username} %{IPORHOST:clientIP} %{NOTSPACE:userAgent} %{NOTSPACE:referer} %{NUMBER:response} %{NUMBER:subresponse} %{NUMBER:win32response} %{NUMBER:timetaken} %{NOTSPACE:xrealIP} %{NOTSPACE:xforwarderfor}"]
          }
      } else {
   
          grok {
             match => ["message", "%{TIMESTAMP_ISO8601:log_timestamp} %{IP:serverIP} %{WORD:method} %{NOTSPACE:uriStem} %{NOTSPACE:uriQuery} %{NUMBER:port} %{NOTSPACE:username} %{IPORHOST:clientIP} %{NOTSPACE:userAgent} %{NOTSPACE:referer} %{NUMBER:response} %{NUMBER:subresponse} %{NUMBER:win32response} %{NUMBER:timetaken}"]
          }
 
      }
 
      date {
        match => [ "log_timestamp", "YYYY-MM-dd HH:mm:ss" ]
          timezone => "Etc/UTC"
        remove_field => [ "log_timestamp", "@timestamp" ]
        target => [ "log_timestamp2" ]
      }
 
        ruby {
            code => "tstamp = event.get('log_timestamp2').to_i
                        event.set('logdatetime', Time.at(tstamp).strftime('%Y-%m-%d %H:%M:%S'))
                        event.set('logdate', Time.at(tstamp).strftime('%Y-%m-%d'))"
        }
 
      if [bytesSent] {
        ruby {
          code => "event['kilobytesSent'] = event['bytesSent'].to_i / 1024.0"
        }
      }
 
 
      if [bytesReceived] {
        ruby {
          code => "event['kilobytesReceived'] = event['bytesReceived'].to_i / 1024.0"
        }
      }
 
   
        ruby {
            code => "event.set('clientRealIP', event.get('clientIP'))"
        }
        if [xrealIP] {
            ruby {
                code => "event.set('clientRealIP', event.get('xrealIP'))"
            }
        }
        if [xforwarderfor] {
            ruby {
                code => "event.set('clientRealIP', event.get('xforwarderfor'))"
            }
        }
 
      mutate {
        convert => ["bytesSent", "integer"]
        convert => ["bytesReceived", "integer"]
        convert => ["timetaken", "integer"] 
        convert => ["port", "integer"]
 
        add_field => {
            "clientHostname" => "%{clientIP}"
        }
      }
 
        useragent {
            source=> "useragent"
            prefix=> "browser"
        }
 
        kv {
            source => "uriQuery"
            prefix => "uriQuery__"
            allow_duplicate_values => false
            field_split => "&"
            include_keys => [ "utm_medium", "utm_source", "utm_campaign", "utm_term", "utm_content", "yclid", "region" ]
        }
 
        mutate {
            join => { "uriQuery__utm_source" => "," }
            join => { "uriQuery__utm_medium" => "," }
            join => { "uriQuery__utm_campaign" => "," }
            join => { "uriQuery__utm_term" => "," }
            join => { "uriQuery__utm_content" => "," }
            join => { "uriQuery__yclid" => "," }
            join => { "uriQuery__region" => "," }
        }
 
}
 
output { 
  #stdout {codec => rubydebug}
    clickhouse {
      headers => ["Authorization", "Basic abcdsfks..."]
      http_hosts => ["http://127.0.0.1:8123"]
      save_dir => "/etc/logstash/tmp"
      table => "log_web"
      request_tolerance => 1
      flush_size => 10000
      idle_flush_time => 1
        mutations => {
            "fld_log_file_name" => "fld_log_file_name"
            "fld_server_name" => "fld_server_name"
            "fld_app_name" => "fld_app_name"
            "fld_app_module" => "fld_app_module"
            "fld_website_name" => "fld_website_name"
 
            "logdatetime" => "logdatetime"
            "logdate" => "logdate"
            "serverIP" => "serverIP"
            "method" => "method"
            "uriStem" => "uriStem"
            "uriQuery" => "uriQuery"
            "port" => "port"
            "username" => "username"
            "clientIP" => "clientIP"
            "clientRealIP" => "clientRealIP"
            "userAgent" => "userAgent"
            "referer" => "referer"
            "response" => "response"
            "subresponse" => "subresponse"
            "win32response" => "win32response"
            "timetaken" => "timetaken"
             
            "uriQuery__utm_medium" => "uriQuery__utm_medium"
            "uriQuery__utm_source" => "uriQuery__utm_source"
            "uriQuery__utm_campaign" => "uriQuery__utm_campaign"
            "uriQuery__utm_term" => "uriQuery__utm_term"
            "uriQuery__utm_content" => "uriQuery__utm_content"
            "uriQuery__yclid" => "uriQuery__yclid"
            "uriQuery__region" => "uriQuery__region"
        }
    }
 
}

pipelines.yml

# This file is where you define your pipelines. You can define multiple.
# For more information on multiple pipelines, see the documentation:
#   https://www.elastic.co/guide/en/logstash/current/multiple-pipelines.html
 
- pipeline.id: log_web__filebeat_clickhouse
  path.config: "/etc/logstash/log_web__filebeat_clickhouse.conf"

کلک ہاؤس لاگ اسٹوریج

تمام سسٹمز کے لاگز ایک ٹیبل میں محفوظ کیے جاتے ہیں (مضمون کے شروع میں دیکھیں)۔ اس کا مقصد درخواستوں کے بارے میں معلومات کو ذخیرہ کرنا ہے: تمام پیرامیٹرز مختلف فارمیٹس، جیسے IIS لاگز، اپاچی اور nginx لاگز کے لیے ایک جیسے ہیں۔ ایپلیکیشن لاگز کے لیے، جس میں، مثال کے طور پر، غلطیاں، معلوماتی پیغامات، انتباہات ریکارڈ کیے جاتے ہیں، مناسب ڈھانچے کے ساتھ ایک علیحدہ جدول فراہم کیا جائے گا (فی الحال ڈیزائن کے مرحلے پر)۔

ٹیبل کو ڈیزائن کرتے وقت، بنیادی کلید (جس کے ذریعے سٹوریج کے دوران ڈیٹا کو ترتیب دیا جائے گا) کا فیصلہ کرنا بہت ضروری ہے۔ ڈیٹا کمپریشن کی ڈگری اور استفسار کی رفتار اس پر منحصر ہے۔ ہماری مثال میں، کلید ہے
آرڈر بذریعہ (fld_app_name, fld_app_module, logdatetime)
یعنی نظام کے نام سے، نظام کے جزو کا نام اور واقعہ کی تاریخ۔ شروع میں تقریب کی تاریخ پہلے آئی۔ اسے آخری جگہ پر لے جانے کے بعد، سوالات نے تقریباً دوگنا تیزی سے کام کرنا شروع کیا۔ بنیادی کلید کو تبدیل کرنے کے لیے ٹیبل کو دوبارہ بنانے اور ڈیٹا کو دوبارہ لوڈ کرنے کی ضرورت ہوگی تاکہ کلک ہاؤس ڈسک پر موجود ڈیٹا کو دوبارہ ترتیب دے۔ یہ ایک بھاری آپریشن ہے، اس لیے اس بارے میں بہت سوچنا اچھا خیال ہے کہ ترتیب کی کلید میں کیا شامل کیا جانا چاہیے۔

یہ بھی واضح رہے کہ کم کارڈنالٹی ڈیٹا کی قسم نسبتاً حالیہ ورژن میں ظاہر ہوئی ہے۔ اس کا استعمال کرتے وقت، کمپریسڈ ڈیٹا کا سائز ان فیلڈز کے لیے بہت کم کر دیا جاتا ہے جن میں کارڈنلٹی کم ہوتی ہے (کچھ اختیارات)۔

ورژن 19.6 فی الحال استعمال میں ہے اور ہم تازہ ترین ورژن میں اپ ڈیٹ کرنے کی کوشش کرنے کا ارادہ رکھتے ہیں۔ ان میں ایسی شاندار خصوصیات ہیں جیسے Adaptive Granularity، Skipping indices اور DoubleDelta codec، مثال کے طور پر۔

پہلے سے طے شدہ طور پر، انسٹالیشن کے دوران، لاگنگ لیول ٹریس پر سیٹ ہوتا ہے۔ لاگز کو گھمایا جاتا ہے اور محفوظ کیا جاتا ہے، لیکن ساتھ ہی وہ ایک گیگا بائٹ تک پھیل جاتے ہیں۔ اگر کوئی ضرورت نہیں ہے، تو آپ انتباہ کی سطح کو مقرر کر سکتے ہیں، پھر لاگ کا سائز بہت کم ہو جائے گا. لاگنگ سیٹنگ config.xml فائل میں سیٹ کی گئی ہے۔

<!-- Possible levels: https://github.com/pocoproject/poco/blob/develop/Foundation/include/Poco/Logger. h#L105 -->
<level>warning</level>

کچھ مفید احکامات

Поскольку оригинальные пакеты установки собираются по Debian, то для других версий Linux необходимо использовать пакеты собранные компанией Altinity.
 
Вот по этой ссылке есть инструкции с ссылками на их репозиторий: https://www.altinity.com/blog/2017/12/18/logstash-with-clickhouse
sudo yum search clickhouse-server
sudo yum install clickhouse-server.noarch
  
1. проверка статуса
sudo systemctl status clickhouse-server
 
2. остановка сервера
sudo systemctl stop clickhouse-server
 
3. запуск сервера
sudo systemctl start clickhouse-server
 
Запуск для выполнения запросов в многострочном режиме (выполнение после знака ";")
clickhouse-client --multiline
clickhouse-client --multiline --host 127.0.0.1 --password pa55w0rd
clickhouse-client --multiline --host 127.0.0.1 --port 9440 --secure --user default --password pa55w0rd
 
Плагин кликлауза для логстеш в случае ошибки в одной строке сохраняет всю пачку в файл /tmp/log_web_failed.json
Можно вручную исправить этот файл и попробовать залить его в БД вручную:
clickhouse-client --host 127.0.0.1 --password password --query="INSERT INTO log_web FORMAT JSONEachRow" < /tmp/log_web_failed__fixed.json
 
sudo mv /etc/logstash/tmp/log_web_failed.json /etc/logstash/tmp/log_web_failed__fixed.json
sudo chown user_dev /etc/logstash/tmp/log_web_failed__fixed.json
sudo clickhouse-client --host 127.0.0.1 --password password --query="INSERT INTO log_web FORMAT JSONEachRow" < /etc/logstash/tmp/log_web_failed__fixed.json
sudo mv /etc/logstash/tmp/log_web_failed__fixed.json /etc/logstash/tmp/log_web_failed__fixed_.json
 
выход из командной строки
quit;
## Настройка TLS
https://www.altinity.com/blog/2019/3/5/clickhouse-networking-part-2
 
openssl s_client -connect log.domain.com:9440 < /dev/null

logstash. فائل بیٹ سے RabbitMQ قطار میں راؤٹر لاگ کریں۔

اس جز کو فائل بیٹ سے RabbitMQ قطار میں آنے والے لاگز کو روٹ کرنے کے لیے استعمال کیا جاتا ہے۔ یہاں دو نکات ہیں:

  1. بدقسمتی سے، FileBeat کے پاس RabbitMQ کو براہ راست لکھنے کے لیے آؤٹ پٹ پلگ ان نہیں ہے۔ اور اس طرح کی فعالیت، ان کے گیتھب پر مسئلے کو دیکھتے ہوئے، نفاذ کے لیے منصوبہ بندی نہیں کی گئی ہے۔ کافکا کے لیے ایک پلگ ان ہے، لیکن کسی وجہ سے ہم اسے گھر پر استعمال نہیں کر سکتے۔
  2. DMZ میں نوشتہ جات جمع کرنے کے تقاضے ہیں۔ ان کی بنیاد پر، لاگز کو پہلے قطار میں شامل کیا جانا چاہیے اور پھر LogStash باہر سے قطار سے اندراجات کو پڑھتا ہے۔

لہذا، یہ اس معاملے کے لئے ہے جہاں سرور DMZ میں واقع ہیں کہ کسی کو اس قدر پیچیدہ اسکیم کا استعمال کرنا پڑتا ہے۔ ایک مثال کی ترتیب اس طرح نظر آتی ہے:

iis_w3c_logs__filebeat_rabbitmq.conf

input {
 
    beats {
        port => 5044
        type => 'iis'
        ssl => true
        ssl_certificate_authorities => ["/etc/pki/tls/certs/app/ca.pem", "/etc/pki/tls/certs/app/ca-issuing.pem"]
        ssl_certificate => "/etc/pki/tls/certs/app/queue.domain.com.cer"
        ssl_key => "/etc/pki/tls/certs/app/queue.domain.com-pkcs8.key"
        ssl_verify_mode => "peer"
    }
 
}
 
output { 
  #stdout {codec => rubydebug}
 
    rabbitmq {
        host => "127.0.0.1"
        port => 5672
        exchange => "monitor.direct"
        exchange_type => "direct"
        key => "%{[fields][fld_app_name]}"
        user => "q-writer"
        password => "password"
        ssl => false
    }
}

خرگوش ایم کیو۔ پیغام کی قطار

یہ جزو DMZ میں لاگ انٹریوں کو بفر کرنے کے لیے استعمال ہوتا ہے۔ ریکارڈنگ Filebeat → LogStash کے ایک گروپ کے ذریعے کی جاتی ہے۔ پڑھنا DMZ کے باہر سے LogStash کے ذریعے کیا جاتا ہے۔ RabboitMQ کے ذریعے کام کرتے وقت، فی سیکنڈ تقریباً 4 ہزار پیغامات پر کارروائی ہوتی ہے۔

میسج روٹنگ کو سسٹم کے نام سے ترتیب دیا گیا ہے، یعنی فائل بیٹ کنفیگریشن ڈیٹا کی بنیاد پر۔ تمام پیغامات ایک قطار میں جاتے ہیں۔ اگر کسی وجہ سے قطار میں کھڑی سروس بند ہو جاتی ہے، تو یہ پیغامات کے ضائع ہونے کا باعث نہیں بنے گا: FileBeats کو کنکشن کی خرابیاں موصول ہوں گی اور بھیجنا عارضی طور پر معطل ہو جائے گا۔ اور LogStash جو قطار سے پڑھتا ہے اسے بھی نیٹ ورک کی خرابیاں موصول ہوں گی اور کنکشن کے بحال ہونے کا انتظار کیا جائے گا۔ اس صورت میں، ڈیٹا، یقینا، اب ڈیٹا بیس پر نہیں لکھا جائے گا.

قطاریں بنانے اور ترتیب دینے کے لیے درج ذیل ہدایات استعمال کی جاتی ہیں:

sudo /usr/local/bin/rabbitmqadmin/rabbitmqadmin declare exchange --vhost=/ name=monitor.direct type=direct sudo /usr/local/bin/rabbitmqadmin/rabbitmqadmin declare queue --vhost=/ name=web_log durable=true
sudo /usr/local/bin/rabbitmqadmin/rabbitmqadmin --vhost="/" declare binding source="monitor.direct" destination_type="queue" destination="web_log" routing_key="site1.domain.ru"
sudo /usr/local/bin/rabbitmqadmin/rabbitmqadmin --vhost="/" declare binding source="monitor.direct" destination_type="queue" destination="web_log" routing_key="site2.domain.ru"

گرافانا۔ ڈیش بورڈز

یہ جزو مانیٹرنگ ڈیٹا کو دیکھنے کے لیے استعمال ہوتا ہے۔ اس صورت میں، آپ کو Grafana 4.6+ پلگ ان کے لیے ClickHouse ڈیٹا سورس انسٹال کرنے کی ضرورت ہے۔ ڈیش بورڈ پر ایس کیو ایل فلٹرز کی پروسیسنگ کی کارکردگی کو بہتر بنانے کے لیے ہمیں اسے تھوڑا سا موافقت کرنا پڑا۔

مثال کے طور پر، ہم متغیرات کا استعمال کرتے ہیں، اور اگر وہ فلٹر فیلڈ میں سیٹ نہیں کیے گئے ہیں، تو ہم چاہیں گے کہ فارم کے جہاں میں کوئی شرط پیدا نہ کی جائے ( uriStem = » AND uriStem != » )۔ اس صورت میں، ClickHouse uriStem کالم پڑھے گا۔ عام طور پر، ہم نے مختلف اختیارات آزمائے اور آخر کار پلگ ان ($valueIfEmpty میکرو) کو درست کر دیا تاکہ خالی قدر کی صورت میں یہ کالم کا ذکر کیے بغیر 1 لوٹاتا ہے۔

اور اب آپ اس استفسار کو گراف کے لیے استعمال کر سکتے ہیں۔

$columns(response, count(*) c) from $table where $adhoc
and $valueIfEmpty($fld_app_name, 1, fld_app_name = '$fld_app_name')
and $valueIfEmpty($fld_app_module, 1, fld_app_module = '$fld_app_module') and $valueIfEmpty($fld_server_name, 1, fld_server_name = '$fld_server_name') and $valueIfEmpty($uriStem, 1, uriStem like '%$uriStem%')
and $valueIfEmpty($clientRealIP, 1, clientRealIP = '$clientRealIP')

جو اس SQL میں ترجمہ کرتا ہے (نوٹ کریں کہ خالی uriStem فیلڈز کو صرف 1 میں تبدیل کیا گیا ہے)

SELECT
t,
groupArray((response, c)) AS groupArr
FROM (
SELECT
(intDiv(toUInt32(logdatetime), 60) * 60) * 1000 AS t, response,
count(*) AS c FROM default.log_web
WHERE (logdate >= toDate(1565061982)) AND (logdatetime >= toDateTime(1565061982)) AND 1 AND (fld_app_name = 'site1.domain.ru') AND (fld_app_module = 'web') AND 1 AND 1 AND 1
GROUP BY
t, response
ORDER BY
t ASC,
response ASC
)
GROUP BY t ORDER BY t ASC

حاصل يہ ہوا

ClickHouse ڈیٹا بیس کی ظاہری شکل مارکیٹ میں ایک تاریخی واقعہ بن گیا ہے۔ یہ تصور کرنا مشکل تھا کہ، بالکل مفت، ایک لمحے میں ہم بڑے ڈیٹا کے ساتھ کام کرنے کے لیے ایک طاقتور اور عملی ٹول سے لیس تھے۔ بلاشبہ، بڑھتی ہوئی ضروریات کے ساتھ (مثال کے طور پر، ایک سے زیادہ سرورز کی شارڈنگ اور نقل)، اسکیم مزید پیچیدہ ہو جائے گی۔ لیکن پہلے تاثرات پر، اس ڈیٹا بیس کے ساتھ کام کرنا بہت خوشگوار ہے۔ یہ دیکھا جا سکتا ہے کہ مصنوعات کو "لوگوں کے لئے" بنایا گیا ہے.

ElasticSearch کے مقابلے میں، لاگز کو ذخیرہ کرنے اور پروسیسنگ کرنے کی لاگت میں پانچ سے دس گنا تک کمی کا تخمینہ لگایا گیا ہے۔ دوسرے الفاظ میں، اگر ڈیٹا کی موجودہ مقدار کے لیے ہمیں کئی مشینوں کا ایک کلسٹر قائم کرنا پڑے گا، تو کلک ہاؤس استعمال کرتے وقت، ہمارے لیے ایک کم طاقت والی مشین کافی ہے۔ ہاں، بلاشبہ، ElasticSearch میں آن ڈسک ڈیٹا کمپریشن میکانزم اور دیگر خصوصیات بھی ہیں جو وسائل کی کھپت کو نمایاں طور پر کم کرسکتی ہیں، لیکن ClickHouse کے مقابلے میں، یہ زیادہ مہنگا ہوگا۔

ہماری طرف سے کسی خاص اصلاح کے بغیر، ڈیفالٹ سیٹنگز پر، ڈیٹا لوڈ کرنا اور ڈیٹا بیس سے منتخب کرنا حیرت انگیز رفتار سے کام کرتا ہے۔ ہمارے پاس ابھی زیادہ ڈیٹا نہیں ہے (تقریباً 200 ملین ریکارڈ)، لیکن سرور خود کمزور ہے۔ ہم مستقبل میں اس ٹول کو دوسرے مقاصد کے لیے استعمال کر سکتے ہیں جو لاگز کو ذخیرہ کرنے سے متعلق نہیں ہیں۔ مثال کے طور پر، اینڈ ٹو اینڈ اینالیٹکس کے لیے، سیکیورٹی کے شعبے میں، مشین لرننگ۔

آخر میں، فوائد اور نقصانات کے بارے میں تھوڑا سا.

Cons

  1. بڑے بیچوں میں ریکارڈ لوڈ ہو رہا ہے۔ ایک طرف، یہ ایک خصوصیت ہے، لیکن پھر بھی آپ کو ریکارڈ بفرنگ کے لیے اضافی اجزاء استعمال کرنے ہوں گے۔ یہ کام ہمیشہ آسان نہیں ہوتا، لیکن پھر بھی حل کیا جا سکتا ہے۔ اور میں اسکیم کو آسان بنانا چاہوں گا۔
  2. کچھ غیر ملکی فعالیت یا نئی خصوصیات اکثر نئے ورژن میں ٹوٹ جاتی ہیں۔ یہ تشویش کا باعث بنتا ہے، نئے ورژن میں اپ گریڈ کرنے کی خواہش کو کم کرتا ہے۔ مثال کے طور پر، کافکا ٹیبل انجن ایک بہت مفید خصوصیت ہے جو آپ کو صارفین کو لاگو کیے بغیر، براہ راست کافکا سے واقعات پڑھنے کی اجازت دیتا ہے۔ لیکن گیتھب پر مسائل کی تعداد کو دیکھتے ہوئے، ہم اب بھی محتاط ہیں کہ اس انجن کو پیداوار میں استعمال نہ کریں۔ تاہم، اگر آپ اچانک اشارہ نہیں کرتے ہیں اور اہم فعالیت کا استعمال کرتے ہیں، تو یہ مستحکم طور پر کام کرتا ہے.

پیشہ

  1. سست نہیں ہوتا۔
  2. داخلے کی کم حد۔
  3. آزاد مصدر.
  4. مفت
  5. اچھی طرح سے پیمانہ بناتا ہے (خانہ سے باہر شارڈنگ / نقل)
  6. وزارت مواصلات کے ذریعہ تجویز کردہ روسی سافٹ ویئر کے رجسٹر میں شامل ہے۔
  7. Yandex کی طرف سے سرکاری حمایت کی موجودگی.

ماخذ: www.habr.com

نیا تبصرہ شامل کریں