Elasticsearch (اختیاری)
مقدمه
در این فاز با موتور جستجوی Elasticsearch و نحوهی کار کردن با آن آشنا میشوید.
Elasticsearch چیست؟
در سالهای اخیر با توجه به رشد روزافزون خدمات مبتنی بر نرمافزار و تولید مقادیر زیادی داده، امکان جستجو در این دادهها اهمیت فوقالعادهای پیدا کرده است. همچنین علاوه بر دادههایی که مربوط به فعالیت کاربران در بسترهای مجازی است، خود سیستمهای نرمافزاری مقادیر زیادی log تولید میکنند که امکان جستجو در آنها میتواند کار را برای مدیران سیستم و توسعهدهندگان بسیار سادهتر کند. در هر محصول نرمافزاری به احتمال قوی کاربر به امکان جستجو نیاز دارد که برای ایجاد این امکان میتوان از موتورهای جستجو استفاده کرد. محصولات متفاوتی در این حوزه وجود دارند که در حال حاضر معروفترین و محبوبترین آنها Elasticsearch نام دارد.
Elasticsearch یک موتور جستجوی Full-text است که به طور متنباز توسعه داده میشود. این محصول به کاربران این امکان را میدهد که در تعداد زیادی سند متنی جستجو کنند. این موتور جستجو توسط بسیاری از شرکتهای نرمافزاری دنیا مورد استفاده قرار گرفته است. همچنین در محبوبیت آن همین بس که در github بیش از 60هزار ستاره دریافت کرده است. این موتور جستجو کاربردهای فراوانی در حوزههای مختلف دارد که از این کاربردها میتوان به امکان ارائهی جستجو در محصولات نرمافزاری و جمعآوری و تحلیل logهای سیستمهای نرمافزاری اشاره کرد.
علاوه بر خود Elasticsearch چندین محصول دیگر به عنوان Elastic Stack نیز وجود دارند که از مهمترین آنها میتوان به Kibana، Logstash و APM اشاره کرد.
Kibana
این محصول امکان مدیریت کلاستر Elasticsearch و Visualize کردن دادهها در داشبوردها و نمودارهای مختلف را به مدیر سیستم میدهد.
Logstash
با این محصول میتوان Logهای سیستم را جمعآوری کرد که در ادامه بتوان راحتتر آنها را مورد تحلیل و بررسی قرار داد.
APM (Application Performance Monitoring)
این محصول به ایجادکنندگان نرمافزار این امکان را میدهد تا نرمافزار خود را از نظر کارایی مورد نظارت قرار دهند به این صورت که میتوانند زمانی که صرف هر یک از قسمتهای نرمافزار و یا فراخوانی هر یک از توابع شده است را اندازهگیری کنند و متوجه گلوگاههای سیستم شوند.
Elasticsearch کمی عمیقتر
برای آشنایی با موتور جستجوی Elasticsearch مطالعهی «What is Elasticsearch» مفید است.
شما در ابتدا، جستجو در اسناد متنی را به وسیلهی Inverted Index پیادهسازی کردید. موتور جستجوی Elasticsearch نیز قابلیتی مشابه پروژهی شما ارائه میدهد. این قابلیت شامل index کردن اسناد متنی و سپس جستجو در آنها میشود که البته امکانات بسیار پیشرفتهتری را نیز در بر دارد که در ادامه برخی از آنها را ذکر میکنیم:
امکانات معماری
توزیعشدگی
شما میتوانید Elasticsearch را روی چندین سرور نصب کنید و آنها را به یک دیگر متصل کنید و این سرورها با همکاری یکدیگر قدرت و سرعت بیشتری را در اختیار شما قرار دهند.
مقیاسپذیری
با افزودن تعداد سرورها میتوانید مقادیر بیشتری داده در Elasticsearch بریزید و کماکان سرعت بالای index کردن و جستجو حفظ شود.
توضیحات بیشتر را در Scalability and resilience: clusters, nodes, and shards مطالعه کنید.
امکانات کارکردی
Dynamic Mapping
سندی که میخواهید در Elasticsearch بریزید میتواند هر ساختاری داشته باشد و لازم نیست از قبل ساختار آن را در Elasticsearch تعیین کنید چرا که به طور خودکار آن را تشخیص میدهد و ساختار مناسب را تعریف میکند.
برای مطالعه بیشتر از Dynamic mapping استفاده کنید.
Normalization
Elasticsearch امکانات نرمالسازی متن پیشرفته از جمله Analyzerها و Tokenizerهای مختلفی دارد که کار کاربر را بسیار ساده میکند.
پشتیبانی از انواع مختلف داده
به غیر از مقادیر متنی میتوان دادههای عددی، تاریخی و یا مختصات مکانی و ... را در Elasticsearch ریخت و روی آنها جستجو کرد. برای مثال فقط در سندهای یک بازهی زمانی خاص و یا سندهای یک محدودهی جغرافیایی خاص جستجو کرد.
پشتیبانی از جستجوی Fuzzy و زیررشته
در بسیاری مواقع کاربران ممکن است مقداری از عبارت را به اشتباه تایپ کنند یا فقط زیررشتهای از یک عبارت را جستجو کنند. Elasticsearch از این نوع جستجوها نیز پشتیبانی میکند.
آشنایی با مفاهیم
در Elasticsesarch چندین مفهوم مطرح میشود که در زیر توضیح داده شدهاند:
Cluster
همانطور که گفته شد Elasticsearch توزیعشده است. به تعدادی از سرورها که به یکدیگر متصل هستند و سرویس Elasticsearch را ارائه میدهند Elasticsearch Cluster گفته میشود.
Node
به هر یک از سرورهای Elasticsearch در یک Cluster یک Node گفته میشود که Nodeها نقشهای مختلفی بر عهده دارند که با توجه به نقش آنها به چند دسته تقسیم میشوند که مهمترین آنها Master Node و Data Node هستند.
برای مطالعهی بیشتر میتوانید به اینجا مراجعه کنید.
Document
به هر یک از سندهای متنی که در Elasticsearch بارگذاری میشود Document گفته میشود. این مفهوم، مفهومی فراتر از Elastcisearch است و کلاً در حوزهی بازیابی اطلاعات مورد استفاده قرار میگیرد. برای مثال فرض کنید بخواهیم در غزلهای حافظ جستجو کنیم در این صورت بسته به کاربرد ممکن است هر یک از ابیات به عنوان یک Document در نظر گرفته شوند و یا هر یک از غزلها یک Document به حساب بیایند.
Index
در Elasticsearch مفهوم Index به هر یک از مخازنی گفته میشود که تعدادی Document با قالب مشابه در آن قرار گرفتهاند برای مثال میتوان آن را با یک جدول در پایگاه دادههای رابطهای مقایسه کرد که البته این دو مفهوم یکسان نیستند و تفاوتهایی دارند که فعلاً از آنها چشمپوشی میکنیم.
علاوه بر مفهوم گفتهشده از عبارت Index به صورت فعلی به معنای ریختن داده در Elasticsearch استفاده میشود.
Mapping
در هر Index سندها قالب مشخصی دارند که به این قالب Mapping گفته میشود. برای مثال اگر بخواهیم مشخصات تعدادی دانشجو را در Elasticsearch بریزیم و هر دانشجو را یک Document در نظر بگیریم، هر دانشجو نام، نام خانوادگی، رشته، تاریخ ورود، سن، معدل و ... دارد که از هر کدام از این موارد در Elasticsearch با عنوان Field یاد میشود. هر کدام از این Fieldها نوع مشخصی دارد برای مثال نام، نام خانوادگی و رشته از نوع text هستند و مثلاً سن از نوع عدد طبیعی و معدل از جنس عدد اعشاری است و تاریخ ورود میتواند از جنس تاریخ در نظر گرفته شود. به این قالب که Fieldهای مختلف و جنس هر کدام را مشخص میکند Mapping گفته میشود.
Shard
همانطور که پیشتر اشاره شد Elasticsearch به صورت توزیعشده است که میتوان داده را در چندین سرور توزیع کرد. نحوهی توزیع به این صورت است که هر Index دارای تعدادی Shard است که این Shardها به طور متوازن بین سرورها توزیع میشوند. برای مثال اگر 5 سرور داشته باشیم و یک Index دارای 10 شارد باشد سهم هر سرور 2 شارد خواهد بود یعنی 2 شارد روی آن سرور نگهداری میشود. حالا فرض کنید که یکی از سرورها دچار مشکل شود که در این صورت آن قسمت از داده که در اختیار آن سرور بوده است از دسترس خارج میشود. Elasticsearch برای این موضوع نیز راهحلی اندیشیده است به این صورت که با توجه به میزان اهمیت داده میتوان از هر یک از Shardها تعدادی کپی داشت که در صورت از دسترس خارج شدن دادهی اصلی، داده همچنان از طریق آن کپیها در دسترس باشد. به Shardهای اصلی Primary Shard و به کپیها Replica Shard گفته میشود.
Segment
هر یک از Shardها دارای تعدادی Segment است. علت وجود این Segmentها این است که Elasticsearch بر پایهی Apache Lucene ایجاد شده است. به اختصار میتوان گفت که Apache Lucene سرویس جستجو را ارائه میدهد اما تنها روی یک ماشین بدون قابلیتهای توزیعشدگی که Elasticsearch قابلیت توزیعشدگی را به آن اضافه میکند. در حقیقت هر یک از Shardها یک Instance از نرمافزار Lucene است.
به طور خلاصه میتوان گفت در Elasticsearch هر Cluster تعدادی Node دارد. هر Node میزبان تعدادی Shard است که هر Shard از تعدادی Segment تشکیل شده است. هر Index از تعدادی Shard تشکیل شده است.
برای مطالعهی بیشتر دربارهی Indexها و Documentها از Data in: documents and indices استفاده کنید.
کار با Elasticsearch
نصب و راهاندازی Elasticsearch و Kibana
ابتدا Elasticsearch متناسب با سیستمعامل خود را از اینجا دریافت و نصب کنید.
سپس Kibana متناسب با سیستمعامل خود را از اینجا دریافت و نصب کنید.
لینکهای داده شده تحریم هستند که برای رفع تحریم باید از VPN و یا سرویس شکن استفاده کنید.
اگر از یکی از توزیعهای سیستمعامل لینوکس استفاده میکنید میتوانید برای نصب این دو برنامه از Package Managerها نیز استفاده کنید.
Elasticsearch و Kibana را اجرا کنید.
به طور پیشفرض Elasticsearch روی پورت 9200 و Kibana روی پورت 5601 راهاندازی میشود. برای اطمینان از درستی اجرای آنها میتوانید در مرورگر خود آدرسهای زیر را تست کنید و مطمئن شوید که این دو برنامه به درستی کار میکنند:
ایجاد index
برای اجرای queryها از قسمت Dev Tools در Kibana استفاده کنید.
ابتدا با اجرای Query زیر یک index به نام
people-simple
در Elasticsearch ایجاد کنید:PUT /people-simple
در این لینک توضیحات بیشتری دربارهی ایجاد index داده شده است.
بارگذاری اسناد متنی
فایل people-simple.json را دریافت کنید که در آن مشخصات چند شخص در قالب JSON آورده شده است.
این اشخاص را در index ایجاد شده بریزید. برای این کار میتوانید از این لینک کمک بگیرید.
مثالی نیز در زیر آمده است:
POST /people-simple/_doc/
{
"name": "Ali",
"last_name": "Mohammad",
"age": 50
}با استفاده از Query زیر از درستی کار خود مطمئن شوید:
GET /people-simple/_search
جستجو در اسناد متنی بارگذاری شده
انواع مختلف Query به ویژه Queryهای زیر را امتحان کنید و امکانات هر یک از آنها و تفاوت آنها را با توجه به نتایج به دستآمده دریابید.
Match Query
GET /people-simple/_search
{
"query":{
"match": {
"name": "mohammad"
}
}
}
Fuzzy Query
GET /people-simple/_search
{
"query": {
"match": {
"name": {
"query": "mohammad",
"fuzziness": 1
}
}
}
}
Term Query
GET /people-simple/_search
{
"query": {
"term": {
"name": {
"value": "mohammad"
}
}
}
}
Range Query
GET /people-simple/_search
{
"query": {
"range": {
"age": {
"gte": 24,
"lte": 35
}
}
}
}
Multi-match Query
GET /people-simple/_search
{
"query": {
"multi_match": {
"query": "mohammad",
"fields": ["name", "last_name"],
"fuzziness": 1
}
}
}
Bool Query
GET /people-simple/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"name": {
"query": "mohammad",
"fuzziness": 1
}
}
}
],
"should": [
{
"range": {
"age": {
"gte": 10,
"lte": 30
}
}
}
]
}
}
}
GET /people-simple/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"name": {
"query": "mohammad",
"fuzziness": 1
}
}
}
],
"must_not": [
{
"match": {
"last_name": "mostmand"
}
}
]
}
}
}
GET /people-simple/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"name": {
"query": "mohammad",
"fuzziness": 1
}
}
},
{
"match": {
"last_name": "mostmand"
}
}
]
}
}
}
برای آشنایی بیشتر با Queryهای مختلف که Elasticsearch در اختیار کاربر میگذارد میتوانید از Query DSL کمک بگیرید.
Text Analysis
برای این که بتوان در اسناد متنی جستجو کرد نیاز است تا این اسناد نرمالسازی شوند. برای مثال باید حروف کلمات lowercase شوند و یا کلاً این که یک متن به کلمات شکسته شود و هم هنگام index کردن و هم هنگام جستجو این اتفاق بیفتد.
Elasticsearch در این زمینه امکانات زیادی ارائه میدهد. برای استفاده از این امکانات باید هنگام تعریف index از Analyzerها و Tokenizerهای مناسب استفاده کرد. ابتدا به توضیحی از این مفاهیم میپردازیم:
Analyzer
مجموعهای از Tokenizerها و Normalizerها که برای هر Field در Mapping تعریف میشود و نحوهی برخورد با مقادیر آن Field را مشخص میکند.
Tokenizer
وقتی میخواهیم یک عبارت متنی را index کنیم برای این که قابل جستجو باشد باید آن را به بخشهای کوچکتر به نام Token تجزیه کنیم. Tokenizer این وظیفه را بر عهده دارد. انواع Tokenizer وجود دارد که سادهترین و متداولترین نوع آن شکستن عبارت به کلمات تشکیلدهندهی آن است.
Normalizer
انتظار داریم وقتی کلمهی 'quick' را جستجو میکنیم سندهایی که دارای کلمهی 'Quick' هستند نیز در نتایج ظاهر شوند بنابراین نیاز است تا همهی کلمات را نرمال کنیم که در اینجا lowercase کردن کاراکترها میتواند مناسب باشد.
تفاوت Normalizer با Tokenizer در این است که Normalizer یک Token ورودی میگیرد و یک Token خروجی میدهد اما Tokenizer یک عبارت ورودی میگیرد و تعدادی Token خروجی میدهد.
برای مطالعهی بیشتر میتوانید از این لینک استفاده کنید.
استفاده از Analyzerها
در قسمت «بارگذاری اسناد متنی» ما Analyzerای تعیین نکردیم و Elasticsearch با توجه به ساختار سندهایی که در آن بارگذاری کردیم از Analyzerهای پیشفرض استفاده کرد. با اجرای Query زیر میتوانید Mapping پیشفرض ایجاد شده برای هر Field را مشاهده کنید:
GET people-simple/_mapping
حال میخواهیم Mapping و Analyzerها را خودمان تعیین کنیم.
- ابتدا با استفاده از Analyze API نتیجهی استفاده از چند مورد از Analyzerها را مشاهده میکنیم. در زیر چند Query آورده شده است که اجرای آنها به روشن کردن قضیه کمک میکند:
POST _analyze
{
"analyzer": "whitespace",
"text": "The quick brown fox."
}
موارد استفاده شده:
POST _analyze
{
"analyzer": "standard",
"text": "The quick brown fox."
}
موارد استفاده شده:
POST _analyze
{
"tokenizer": "standard",
"filter": [ "lowercase", "asciifolding" ],
"text": "Is this déja vu?"
}
موارد استفاده شده:
حالا میخواهیم یک Analyzer مناسب خودمان ایجاد کنیم که Query زیر مثالی از آن است:
PUT people-simple2
{
"settings": {
"index": {
"max_ngram_diff": 7
},
"analysis": {
"analyzer": {
"my_ngram_analyzer": {
"type": "custom",
"tokenizer": "standard",
"filter": [
"lowercase",
"my_ngram_filter"
]
}
},
"filter": {
"my_ngram_filter": {
"type": "ngram",
"min_gram": 3,
"max_gram": 10
}
}
}
}
}
موارد استفاده شده:
در این Query
ما یک index
به نام people-simple2
ساختیم که یک Custom Analyzer
به اسم my_ngram_analyzer
دارد که این Analyzer
از یک Standard Tokenizer
همراه با Lowercase Token Filter
و my_ngram_filter
استفاده کرده است که my_ngram_filter
خود یک N-gram Token Filter
است که زیررشتههای به طول 3 تا 10 از کلمات ایجاد میکند. در ابتدای کار نیز در تنظیمات این index
تعیین شده است که اختلاف min_gram
و max_gram
میتواند حداکثر 7 باشد که پیشفرض آن 1 است.
حالا با اجرای کوئری زیر نتیجهی استفاده از این Custom Analyzer مندرآوردی را ببینید!
POST people-simple2/_analyze
{
"analyzer": "my_ngram_analyzer",
"text": "Mohammad Reza"
}
چه مشاهده میکنید؟ به نظر شما این Analyzer چه کاربردی دارد؟
هشدار لوثسازی (Spoiler Alert)
برای آشنایی با انواع مختلف Tokenizerها و Token Filterها میتوانید از این لینک استفاده کنید.
انجام جستجوی زیررشته
حالا از Analyzer ساختهشده در قسمت قبل استفاده میکنیم:
PUT people-simple2/_mapping
{
"properties":{
"last_name": {
"type":"text",
"analyzer":"my_ngram_analyzer"
}
}
}مشخص است که یک Field به نام last_name با Analyzer خودمان تعریف کردیم.
Query زیر را اجرا کنید و نتیجه را ببینید:
GET people-simple2/_search
{
"query": {
"match": {
"last_name": "hamm"
}
}
}
البته ایجاد Analyzer و تعریف Fieldهای index را میتوان در یک Query انجام داد که البته به شرطی است که index با آن نام قبلاً وجود نداشته باشد:
PUT people-simple3
{
"settings": {
"index": {
"max_ngram_diff": 7
},
"analysis": {
"analyzer": {
"my_ngram_analyzer": {
"type": "custom",
"tokenizer": "standard",
"filter": [
"lowercase",
"my_ngram_filter"
]
}
},
"filter": {
"my_ngram_filter": {
"type": "ngram",
"min_gram": 3,
"max_gram": 10
}
}
}
},
"mappings": {
"properties":{
"last_name": {
"type":"text",
"analyzer":"my_ngram_analyzer"
}
}
}
}
Typeهای دیگر
علاوه بر جنس text میتوان Fieldها را از جنسهای دیگر نیز تعریف کرد که مثالی از آن را در مشخصهی age از دادههای نمونه دیدید.
حالا تلاش کنید که با استفاده از مطالب قسمت قبل Mapping مناسب برای این دادههای نمونه را در یک index جدید تعریف کنید و تعدادی از این دادهها را در آن index بریزید.
در این لینک میتوانید انواع مختلف داده در Elasticsearch را مشاهده کنید و دربارهی آنها اطلاعات بیشتری کسب کنید.
مشخصههای latitude و longitude در دادههای نمونه به طور جداگانه آورده شدهاند که برای ریخته شدن در Elasticsearch باید به یک Field تبدیل شوند که این کار را به طور دستی و یا با نوشتن یک کد به زبان دلخواه میتوانید انجام دهید.
دادههای از جنس تاریخ و زمان ممکن است در فرمتهای مختلفی باشند لذا در هنگام تعریف Mapping علاوه بر تعریف type، فرمت متناسب با دادههای نمونه را منظور کنید.
آشنایی با Bulk API
اگر بخواهید تعداد زیادی سند در Elasticsearch بارگذاری کنید این که برای هر کدام یک بار Query بزنید چندان بهینه نیست چرا که سربار زیادی روی شبکه دارد و این مسئله کار را بسیار کند میکند. برای حل این مشکل باید چندین سند را به طور یکجا برای بارگذاری به Elasticsearch فرستاد.
از این لینک کمک بگیرید و با Bulk API آشنا شوید. با استفاده از آن ابیاتی از شعرای گرانقدر پارسیزبان را به طور یکجا در Elasticsearch بریزید.
برای ساختن Bulk Query نیاز دارید تا تغییراتی را در فایل داده شده اعمال کنید که به دلیل تعداد زیاد سندها به طور دستی امکانپذیر نیست که میتوانید برای آن به زبان دلخواه کد بزنید.
Aggregations
از هر کدام از شاعرها چند بیت در دادههای بارگذاری شده در قسمت قبل وجود دارد؟
برای پاسخ به این سؤال میتوانید از Terms Aggregation استفاده کنید.
با اجرای Query زیر به پاسخ پرسش بالا برسید:
GET poems/_search
{
"aggs": {
"poets": {
"terms": { "field": "poet.keyword" }
}
},
"size": 0
}
Queryهای Aggregation در Elasticsearch خود دنیایی دارد که بیان همهی انواع آن در این مستند نمیگنجد. برای آشنایی میتوانید به Aggregations مراجعه کنید.
پینوشت
بهترین منبع برای آشنایی بیشتر با Elasticsearch خود Doumentation آن است که در زیر لینک آن آمده است:
اشعار از API وبسایت گنجور به دست آمده است.