Azure SQL v12, Azure Search ve PowerShell entegrasyonu

Son zamanlarda Azure Search üzerine baya bir mesai harcadım diyebilirim. Amacım Azure Search’ü bir haber sitesinin veri kaynağı olarak konumlamaktı. Bu sayede belli etiketlerdeki haberleri hızlı bir şekilde görüntülemeyi amaçlıyorumdum.

Veritabanında birden çok tablonun birleşiminden oluşan haber ve ilgili etiket verisini indeksleyerek bir seferde JSON formatında alıp, sitede görüntülemek çok mantıklı geldi. Böylece sitedeki arama sistemini de otomatik olarak geliştirmiş oluyor ve sadece haberlerin etiketlerine göre değil içeriklerine göre de arama yapıp sonuç getirebilecektik.

Bu sistemi tasarlarken her zaman aklımda veriyi nasıl veritabanı ile senkron tutacağım sorusu vardı. Sonuç olarak Azure Search sadece sitenin açılmasını hızlandıracak ara bir katman olacaktı, hiçbir şekilde SQL Server’ın yerine konumlandırmayı düşünmedim, sonuçta güvenilir bir veritabanı olma özelliklerini karşılamayan bir teknoloji.

Hazırladığım çözümü anlatmadan size kısaca Azure Search hakkında kısaca bilgi vermek isterim.

Azure Search servisi tamamen bulut üzerinde çalışan ve günümüz arama motorlarının temel özelliklerini yazılım geliştiricilerin kendi projelerine kolayca entegre etmesini sağlayan bir servis. Azure Search günümüz arama motorlarında olan yazı bazlı içerik arama, arama öneri sistemi, arama sonuç önceliklendirilmesi, sonuç gruplama gibi birçok özelliği içeriyor.

Azure Search temel olarak Index yapısı üzerinden çalışıyor. İlk olarak bir veri kaynağını indeksliyorsunuz ve oluşturulan indeks üzerinde arama yapıyorsunuz. Bir indeks yaratırken ve güncel tutarken izleyebileceğiniz birçok yöntem var.

İlk yol Indexer denilen bir yapı sayesinde Azure SQL ve Azure Document Db veri kaynaklarına bağlanıp indeksin otomatik olarak oluşturulması. Bu yönteme Pull Model deniyor. Genel mantığı durağan bir veriyi indeksin içerisine çekiyorsunuz.

İkinci yol ise REST API veya .NET SDK sayesinde indekse yeni döküman eklemek, varolan dökümanı güncellemek veya silmek. Bu yönteme Push Model deniyor. Genel mantık olan bir veriyi API veya SDK kullanarak indeksin içerisine itiyoruz.

Bu iki yolda hangisini seçeceğiniz ihtiyaçlarınız doğrultusunda değişiyor. Örnek olarak eğer kurumunuz içerisinde olan bir veritabanınız var ise Pull Model çalışmayacaktır çünkü Indexer sadece Azure üzerinde olan veri kaynaklarından veri çekebiliyor bu durumda, Push Model indeksinizi kendiniz güncellemeniz gerekiyor.

Azure Search hakkında daha detaylı bilgi almak isterseniz linkteki siteden araştırmanıza başlamanızı öneririm. Link -> http://azure.microsoft.com/en-us/documentation/services/search/

Kısaca Azure Search’ü anlattığımıza göre, artık kendi oluşturduğum sisteme geçebilirim. Bu çözümü tasarlarken ortam ve gereksinimler şu şekildeydi; Azure SQL v12 üzerinde bir veritabanımız var bu veritabanına bir haber kaydı girildiğinde en kısa zamanda Azure Search üzerindeki indeksin güncellenmesi gerekiyor. Tabi ki çözüm olabildiğince az kaynak kullanarak Azure harcamalarını dengelemesi gerekiyor.

Birkaç günlük bir çalışmanın ardından içime sinen bir çözüm çıktı. Çözümün genel yapısı aşağıdaki gibi:

Capture

Önceden bahsettiğim gibi veri kaynağım Azure SQL v12. Azure SQL olduğu için direkt olarak Indexer kullanmak mantıklı gibi gelebilir fakat burada önüme çıkan başka bir sorun oldu. Veritabanından çekeceğim haberleri etiketleriyle beraber çekmem gerekiyor ve bu iki tablo arasında Many-to-Many ilişki olduğu için bir haberi tek satırda bütün etiketleriyle beraber gösteremiyorum. Bunu göstermemin tek yolu bir SQL View oluşturmak oldu. Bu view’i oluşturduğum zamanda performans açısından pek memnun olmadığım bir sonuç oldu. View en erken 4 dakikada cevap veriyordu ve ben bunu Indexer’a bağladığımda en erken her 5 dakikada bir çalışan Indexer bir 5 dakika daha view’dan veriyi almasını bekliyorum. Yani 10 dakika bir ancak veri güncelleme yapılıyordu. Amacım bundan daha performanslı bir yöntem bulmaktı.

Bu çözümden daha hızlı bir çözüm ihtiyacım var. Bu sebeple REST API’yi kullanmaya karar verdim. Azure SQL v12’de SQL Change Tracking özelliği geldi, bu sayede CT’i aktive ettiğimiz tabloda her türlü değişikliği takip edebiliyoruz. Bu çözüm klasik tablo Trigger kullanmaktan çok daha kullanışlı ve doğru bir yöntem. SQL Change Tracking’i Haberler tablosunda aktive ettikten sonra her hangi bir değişiklik olduğunda düzenlenen haberi ve ilgili etiketleri indeksde güncellerim diye düşündüm.

Bu çözüm ne kadar olsa tamamen gerçek zamanlı bir çözüm değil fakat kısa sürelerde çalıştırılabilecek işlerle gerçek zamana yakın indeksi güncelleyebilirim. Bu çözümü gerçekleştirebilecek örnek bir çözüm olarak internette güzel bir yazı buldum ve bu yazıdan esinlendim. Yazı linki -> http://azure.microsoft.com/blog/2014/11/10/how-to-sync-sql-server-data-with-azure-search/. Bu yazıda SQL Veritabanı ile Azure Search indeks arasında güncelleme yapan bir uygulama var. Uygulama kısaca SQL Change Tracking’den aldığı farkları Azure Search indekse geçiriyor.

Yukarıda bahsettiğim yazıdaki yazılımın tek negatif tarafı Azure üzerinde çalıştırmak için bir VM yaratmak gerekiyor. Çünkü hazırlanan yazılım command prompt programı. Bu yazılımın yaptığınını daha az gereksinimle yapan başka bir yol bulmam gerekiyordu. Biraz araştırma yaptıktan sonra PowerShell bu iş için çok güzel bir çözüm olarak göründü. Bu PowerShell scriptini 1 dakikada bir Azure WebJobs ile çalıştırarak yapılan değişiklikeri Azure Search indeks üzerinde güncellemeye karar verdim.

PowerShell ile indeks güncellemesini en rahat REST API ile istek göndererek yaptım. Aşağıda SQL veritabanındaki haber tablosunda yapılan değişiklikleri (silmeleri içermiyor) indeksde REST API ile güncelleyen PowerShell scriptini bulabilirsiniz.

#Set-ExecutionPolicy Unrestricted
$executingScriptDirectory = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent
$scriptPathVariables = Join-Path $executingScriptDirectory "Variables.ps1"
#Include Variables
."$scriptPathVariables"
#Connect to the SQL database
$connection = New-Object System.Data.SqlClient.SqlConnection
$connection.ConnectionString = $AzureSQLConnectionString
$connection.Open()
#Get all of the rows that are updated and inserted from the last pull
$command = $connection.CreateCommand()
$command.CommandText = $GetAllUpdatedAndInserted 
$reader = $command.ExecuteReader()
#Populate array with returned rows
[System.Collections.ArrayList]$changedRows = @()
while ($reader.read()) 
{ 
 $row = @{ 
 "@search.action" = "mergeOrUpload"
 "id" = ([int]$reader.GetValue(0)).ToString()
 "author_id" = $reader.GetValue(1)
 "rewrite_id" = $reader.GetValue(2)
 "link" = $reader.GetValue(3)
 "title" = $reader.GetValue(4)
 "alternative_title" = $reader.GetValue(5)
 "summary" = $reader.GetValue(6)
 "body" = $reader.GetValue(7)
 "image" = $reader.GetValue(8)
 "thumbnail" = $reader.GetValue(9)
 "alternative_thumbnail" = $reader.GetValue(10)
 "video" = $reader.GetValue(11)
 "air_date" = $reader.GetValue(12).ToString("yyyy-MM-ddThh:mm:ssZ")
 "status" = $reader.GetValue(13)
 "comments_enabled" = $reader.GetValue(14)
 "comment_count" = $reader.GetValue(15)
 "hit_count" = $reader.GetValue(16)
 "on_showcase" = $reader.GetValue(17)
 "is_postponed" = $reader.GetValue(18)
 "is_mobile" = $reader.GetValue(19)
 "create_date" = $reader.GetValue(20).ToString("yyyy-MM-ddThh:mm:ssZ")
 "related_match_id" = $reader.GetValue(21)
 "AllTags" = $reader.GetValue(22)
 }
 $changedRows.Add($row) | Out-Null
}
#End Populating the array
#Close the reader and sql connection
$reader.close()
$connection.close()
#Check if any changed rows exists
if($changedRows.Count -gt 0)
{
 #For Azure Search add additional field into the JSON and set search.action to "mergeOrUpload"
 $changedRows | % { $_["@search.action"] = "mergeOrUpload" }
 #For Azure Search add external element into the JSON. This "value" element is a must
 $documents = @{ "value" = $changedRows }
#Convert the array into JSON and create binary data from it. Only by this way we can call REST Endpoint
 $json = ConvertTo-Json $documents -Depth 100
 $postData = [System.Text.Encoding]::UTF8.GetBytes($json)
#Call Update API EndPoint
 $result = Invoke-RestMethod -Uri $UpdateIndexUri -Method Post -Headers $APICallHeader -Body $postData -ContentType $RESTCallContentType
 
 #Write-Host $result.value
 #Write-Host "Updated/New rows were found and Index is updated."
}
else
{
 #Write-Host "No Updated/New rows were found."
}

Aynı yöntem ile indeksden kayıt silme işlemlerini de geliştirebilirsiniz. Bütün PowerShell scriptlerinizi yazdıktan sonra Azure WebApp hizmeti açarak içerisindeki WebJobs’a bu scriptleri zip formatına getirdiğiniz bir klasör içerisinde yükleyebilirsiniz. Web Jobs otomatik olarak bu scriptleri çalıştıracaktır. Unutmayın ilk çalıştırılmasını istediğiniz script dosyasının ismi Main olmalı ve scriptlerinizde hiçbir şekilde ekrana bilgi yazdıran bir kısım olmamalı eğer varsa benim yaptığım gibi commentleyebilirsiniz.

Bu konu hakkında merak ettiğiniz ek konular olursa yorum yapmaktan çekinmeyin.

 

Reklamlar

Bir Cevap Yazın

Aşağıya bilgilerinizi girin veya oturum açmak için bir simgeye tıklayın:

WordPress.com Logosu

WordPress.com hesabınızı kullanarak yorum yapıyorsunuz. Çıkış  Yap / Değiştir )

Twitter resmi

Twitter hesabınızı kullanarak yorum yapıyorsunuz. Çıkış  Yap / Değiştir )

Facebook fotoğrafı

Facebook hesabınızı kullanarak yorum yapıyorsunuz. Çıkış  Yap / Değiştir )

Google+ fotoğrafı

Google+ hesabınızı kullanarak yorum yapıyorsunuz. Çıkış  Yap / Değiştir )

Connecting to %s