daron yöndem | Microsoft Regional Director | Silverlight MVP
Microsoft Regional Director | Nokia Developer Champion | Azure MVP
Bu yazıyı yazmam 12 dakikamı aldı. Siz normal bir okuma hızı ile ortalama 1 dakikada, göz gezdirerek ortalama 1 dakikada okuyabilirsiniz ;)

Bir süre önce sinsi bir şekilde Twitter ve Facebook'dan duyurmuştum :) Istanbul Azure Meetup adında bir buluşma grubu kurduk. Hatta ilk buluşmamızı da Aralık ayında gerçekleştirdik. Bu ay da ikinci buluşmamızı yaptık ve bu sefer konumuz "Azure ve Docker"dı. Azure Linux Ekibi'nden sevgili Ahmet Alp Balkan da bize misafir olarak katıldı.

Bundan sonraki aylarda da ayda bir buluşma olarak devam edeceğiz. Bazen konuklarımız olacak bazen de beraber oturup sohbet edeceğiz. Eğer Azure ile ilgileniyorsanız ayda bir hafta sonu beraber kahve içmek isterseniz :) bize katılın). Şubat buluşmasının konusunu, tarihini çoktan duyurduk.

Görüşmek üzere.

Bu yazıyı normal bir okuma hızı ile ortalama 1 dakikada, göz gezdirerek ortalama 1 dakikada okuyabilirsiniz ;)

Dün Microsoft'un organizasyonunda "Cloud DevCamp" gerçekleştirildi. Ben de gün boyunca üç oturum ile Compute Services, Mobile Services ve Azure Web Sites anlattım.

Malum bu bir community etkinliği değildi :) özellikle belirtiyim istiyorum çünkü kafalar karışabiliyor. Microsoft'un hafta içi sadece kurumsal katılım için organize ettiği bir etkinlikti. Ben de sizlerle twitter ve facebook üzerinden paylaşmıştım öncesinde.

Etkinlik community etkinliğinden öte Microsoft'un bir etkinliği olunca yukarıdaki gibi tweetler de kaçınılmaz oldu :) Neredeee.... community etkinliğinde açık büfe görmek pek mümkün değil tabi :)

Etkinliğe katılan herkese çok teşekkürler.

Görüşmek üzere!

Bu yazıyı normal bir okuma hızı ile ortalama 1 dakikada, göz gezdirerek ortalama 1 dakikada okuyabilirsiniz ;)

Geçen hafta sonu Eskişehir Osmangazi Üniversitesi'ndeydim. "Cloud Development 101" adını koyduğum sıfırdan Cloud Nedir gibi konseptleri anlatan ve içerisinde de Microsoft'un Azure ile yaptıklarını gösteren bir sunum yaptım. İtiraf etmek gerekirse günübirlik bir etkinlik için 10 saat yolculuk son birkaç yıldır yapmadığım bir şeydi ve yıllar sonrasında "yılların etkisini" görmemi sağladı. Sözün özü... yaşlanmışım :) Şaka zannederlere sesleniyor... "10 yıl sonra görüşürüz."

Tüm yorgunluğa rağmen :) benim için çok eğlenceli bir etkinlikti. Etkinlikte emeği geçen tüm arkadaşlarla onlarca teşekkürler. Gösterdikleri hassasiyet takdir edilesi seviyelerdeydi. Bu gibi üniversite etkinliklerine katılan öğrenci kardeşlerim :) genelde maalesef farkında olmuyorlar fakat etkinliklere gönüllü olarak katılan benim gibi konuşmacıların emeklerinin yanı sıra etkinliğin organizasyonunda çalışan arkadaşlarının da inanılmaz katkısı var. Emin olun, en basiti bile olsa, bir organizasyonu yapmak ve alnının akı ile işin içinden çıkmak hiç kolay değil.

Bir dahakine görüşmek üzere Eskişehir! ;)

Bu yazıyı normal bir okuma hızı ile ortalama 1 dakikada, göz gezdirerek ortalama 1 dakikada okuyabilirsiniz ;)

Bu hafta içi (dün) Konya, Karatay Üniversitesi'ndeydim. Her zamankinden farklı olarak teknik bir oturum yerine ilk defa aylar önce yine Konya'da sunduğum "Boş durma, boşa çalış." adındaki oturumumu sundum. Bu oturum özellikle benim "Uykudan Önce Bir Doz" ile paylaşmaya çalıştıklarımla aynı yönde bir paylaşım diyebiliriz.

Tabi ki dönüşte gençlerle bir etli ekmek yemeyi de unutmadık :) Nitekim eski performansımı gösteremiyorum artık ama olsun :) Benim için süper eğlenceliydi. Umarım katılan herkes için de faydalı olmuştur. Etkinliğin organizasyonunda görev alan tüm arkadaşlara da ayrıca çok teşekkürler. Elinize sağlık.

Görüşmek üzere!

Bu yazıyı normal bir okuma hızı ile ortalama 7 dakikada, göz gezdirerek ortalama 2 dakikada okuyabilirsiniz ;)

Azure ortamında Storage Services kapsamında yarattığımız her bir Storage account ile beraber bize bir blob, bir table, bir de queue servisi sağlandığını biliyoruz. Bugüne kadar bloblarla ilgili birçok konuya değinmiş olsak da daha Table servisi ile ilgili birşey yapmadık :) Şimdi gelin Table Services yapısına bir giriş yapalım.

Bir NOSQL hikayesi....

NoSQL genel olarak çoğu developer'ın ilgi alanı dahilinde olmamış bir konseptti. İtiraf etmek gerekirse konseptin adlandırılmasında da bazı problemler var aslında :) Özünde NoRel de denilebilir belki :) Nedenine gelirsek, Azure'taki Table Services'in belki de normal bir SQL'den en büyük farklılıklarından biri "relation" bulunmaması. Bazılarınız şu anda "nasıl yani?" derken bazılarınızın da "tamam sorun değil ben yazılım katmanına çözerim onu" gibi bir tepki verebileceğini tahmin ediyorum. Aslında burada en tehlikeli tepki ikinci tepki :) çünkü eğer ki NoSQL yapısında gidip kendi normal relational SQL sanal yapımızı oluşturmaya çalışırsak büyük hata yapmış oluruz... Neden mi? Relational yapıya ihtiyacınız varsa zaten Azure SQL Database var. Neden kastıralım ki? :)

Table servis'in nerelerde, nasıl ve hangi nedenle kullanılabileceği konusunu biraz da ileri makaleler bırakacağım. Şimdilik ipucu olarak No-SQL, Big Data and Hadoop vs diye sıralayabilir fikir vermesi için. Sonuç itibari ile table service'in çalışma yapısını, kullanımını, yapabildiklerini veya yapamadıklarını ve arkasındaki nedenleri :) bilmeden / anlamadan çıkıp da Table Services nerde kullanılmalı'nın tanımını yapmak faydasız olacaktır. İşte bu yüzden :) Gelin direk işin içine girelim ve bakalım neler oluyor.

İlk Table Service örneğine doğru...

Tüm Storage servislerinde olduğu gibi Table Services de tamamen REST API'leri ile kullanılabiliyor.

[C#]

public class Urun2
{
    public string Timestamp { get; set; }
    public string PartitionKey { get; set; }
    public string RowKey { get; set; }
    public string Adi { get; set; }
    public string Aciklama { get; set; }
}

Yukardaki kod malum bizim uygulamamızda kullanacağımız basit bir entity. Bu Entity'nin özellikle ilk üç property'si eminim ki ilginç gelecektir :) İsterseniz hemen o üçünü incelemeye başlayalım.

Timestamp : Bu property'e biz genelde dokunmuyoruz :) Bu property azure table services tarafından concurrency kontrolü için kullanılıyor. Yani biz hiçbir zaman bu arkadaşa bir değer atamıyor veya genelde oradaki değeri alıp birşeyler de yapmıyoruz. O nedenle bu property kesinlikle bulunması gereken fakat bizi pek de ilgilendirmeyen bir property.

PartitionKey : İlk olarak bu property'nin bir String olduğuna dikkat çekiyim :) PartitionKey property'si çok kritik bir property. Table servis içerisinde bir table'da bulunan entity'lerinizin gerektiğinde farklı sunucularda farklı partitionlara dağıtılması noktasında dağıtım işlemi partitionkey'e göre yapılıyor. Yani eğer ki "Arabalar" diye bir table'ınız varsa ve içinde 100 milyon araba varsa.. ve bu table'daki performans düşüşü nedeniyle Storage Services bu table'ı partitionlama kararı alırsa... bu işlemi ancak partitionkey'e göre yapar. Peki bunun tam olarak anlamı nedir?

Örnek bir
partitioning. Örnek bir partitioning.

Yukarıdaki örneği bir inceleyelim isterseniz. Aslında yukarıdaki tek bir tablomuz var. Arabalar tablosu :) PartitionKey olarak arabanın tipi kullanılmış ve arabalara partitionkey değeri olarak Sedan, Coupe gibi değerler verilmiş. Table Services performansın düştüğünü algıladığında hemen PartitionKey'e göre iki partition yaratıp tabloyu iki farklı sunucuya dağıtmış. Buraya kadar manzara süper. Peki şimdi soruyorum :) Ben kırmızı arabaları getir dersem ne olacak? Tabi ki cross-partition yani partittionlar arası / genel bir sorgu çalıştırmış olacağım. İşte can alıcı nokta da zaten burası :) Çünkü olabildiğince cross-partition sorgulardan kaçmam gerek. Ha cross-partition sorgu olamaz mı? tabi ki olabilir fakat bu durumda ben partitionlamadan kazanacağım performans kazanamam.

Bir başka örnek
partitioning. Bir başka örnek partitioning.

Oysa yukarıdaki şekilde partitionKey'lerimde renk bilgileri olsaydı "bana kırmızı arabaları getir" dediğimde çok basit bir şekilde tek bir partition (yani disk / makine) üzerinden veri gelecekti. Sorgum diğer partition'a gitmeyecekti bile. Böylece tüm partitioning konseptinden faydalanmış olacaktır. Özünde Table Service'in datamı partitionlaması bir işe yaramış olacaktır.

Şimdi eminim ki aklınıza hemen ya ben hem renge hem de tipe göre sorgu yazacaksam sorusu geliyordu :) Birincisi tabi ki bunu yapabilirsiniz ve partitionging'den faydalanmaktan vaz geçmiş olursunuz ama ikincisi ise :) belki de çok karışık sorgularınız varsa SQL'i düşünmelisiniz :) Burada "düşünmelisiniz" derken "vazgeçin buralardan" demiyorum aman dikkat. SQL ile Table Service arasındaki kararı vermek çoğu zaman birçok değişkeni olan ve zor bir karar. Daha da önemlisi %99 ihtimal ile iki servisi beraber kullanacaksınız :) bunun da kendince nedenleri olacak. Umarım bu yazıda ve ilerikilerde vereceğim bilgiler doğru kararları alabilmeniz adına işine yarar fakat buradan "sorgunuz karşıkça SQL'e gidin" gibi genel mesajlar alıp çıkmamanız çok önemli. Benim yapmaya çalışacağım şey size bilgileri vermek, ihtimalleri göstermek, düşünmeniz gerekenlere dikkat çekmek, gerisindeki kararın emin olun birçok değişkeni var.

Sanırım PartitionKey'in anlamını ve değerini artık anladık. PartitionKey property'si string olduğu için rahatlıkla istediğiniz değerleri verebilirsiniz. Tabi ufak bir uyarı daha... PartitionKey vermiş olmanız datanızın illa partitionlandığı anlamına da gelmiyor :) Performans açısından ihtiyaç olunduğunu düşünürse Storage Services'deki "Smart NLB" partitioning'i kendisi tetikleyecektir (sizin tetikleme şansınız yok).

RowKey: Bir property'miz daha var :) O da RowKey. Tahmin edebileceğiniz üzere RowKey artık bir satırı doğrudan tanımlayan key anlamına geliyor. Tam olarak Primary Key diyemem çünkü RowKey ile PartitionKey beraber PK rolü oynuyorlar. Yani bir table'da aynı RowKey'den fakat farklı PartitionKey'de bulunan iki entity olabilir. Tabi karar tamamen sizin isterseniz RowKey'i tek de tutabilirsiniz tek başına.

RowKey ile PartitionKey'in beraber ne şekillerde tanımlandıkları çok önemli. Table Service'de en yüksek performansı alabileceğiniz sorguların kesinlikle beraberlerinde RowKey ve PartitionKey getirmeleri gerekiyor. Böylece sorguyu hem bir Partition ile sınırlamış oluryorsunuz, sonra da Partition içerisinde RowKey üzerinden arama yaparak sonucu alabiliyorsunuz.

[C#]

public class Urun : TableEntity
{
    public string Adi { get; set; }
    public string Aciklama { get; set; }
}

Her sınıf tanımını yaparken bu üç property'yi de tek tek tanımlamak istemiyorsanız :) Basit bir şekilde sınıflarınızı  StorageClient ile beraber gelen TableEntity'den türeterek ilerleyebilirsiniz. Böylece tüm bu sınıflar direk üç property'ye de sahip olacaktır.

Herşeyin öncesinde TableService'a bağlanıp bir table yaratmamız gerek.

[C#]

CloudStorageAccount storageAccount =  CloudStorageAccount.DevelopmentStorageAccount;
CloudTableClient tableClient = storageAccount.CreateCloudTableClient();
CloudTable table = tableClient.GetTableReference("Urunler");
try
{
    if (await table.CreateIfNotExistsAsync())
    {
        Console.WriteLine("İşlem tamam");
    }
    else
    {
        Console.WriteLine("Bu isimde bir tablo var.");
    }
}

Blob'larla uğraştıysanız buradaki kod da epey basit gelecektir :) Bu sefer bir blobClient almak yerine CloudTableClient alıyoruz. Üzerinden de CreateTableIfNotExists gibi süper bir metod kullanıp :) tablomuzu yaratmış oluyoruz.

Gelin şimdi hızlı bir şekilde basit CRUD (Create, Read, Update, Delete) işlemlerine göz atalım.

[C#]

CloudStorageAccount storageAccount = CloudStorageAccount.DevelopmentStorageAccount;
CloudTableClient tableClient = storageAccount.CreateCloudTableClient();
CloudTable table = tableClient.GetTableReference("Urunler");
Urun2 urun = new Urun2()
{
    PartitionKey = "Musteri1",
    RowKey = (new Random().Next(1, 100)).ToString(),
    Adi = "Deneme",
    Aciklama = "Açıklama"
};
TableOperation insertOrMergeOperation = TableOperation.InsertOrMerge(urun);
TableResult result = await table.ExecuteAsync(insertOrMergeOperation);

Ben ürünleri müşterilere göre partitionlama kararı aldım. Özellikle birden çok müşterinin kendi tenant'larını kullandıkları bir yapıda bu çok anlamlı olacaktır. Sonuç itibari ile bir müşterinin diğer müşterinin verisine ulaşma gibi bir ihtimali zaten sıfır. RowKey'i ise şimdilik Random bir sayı olarak verdim ama unutmayın RowKey ve PartitionKey Table Service'deki ana iki Index'imiz. O nedenle sorgularınızı da düşünerek Index'lemek istediğiniz bilgileri bu property'lere atamak süper mantıklı olacaktır.

Kodun geri kalanının pek Azure ile bir alakası yok :)

[C#]

CloudStorageAccount storageAccount = CloudStorageAccount.DevelopmentStorageAccount;
CloudTableClient tableClient = storageAccount.CreateCloudTableClient();
CloudTable table = tableClient.GetTableReference("Urunler");
TableOperation retrieveOperation = TableOperation.Retrieve<Urun2>("Musteri1", "1");
TableResult result = await table.ExecuteAsync(retrieveOperation);
Urun2 urun = result.Result as Urun2;
if (urun != null)
{
    TableOperation deleteOperation = TableOperation.Delete(urun);
    await table.ExecuteAsync(deleteOperation);
}

Kayıt silme konusunda yukarıdaki kodu kullanabilirsiniz.

Entity Group Transactions

[C#]

TableBatchOperation batchOperation = new TableBatchOperation();

for (int i = 0; i < 100; i++)
{
    batchOperation.InsertOrMerge(new Urun2()
       {
           PartitionKey = "Musteri" + i.ToString(),
           RowKey = (new Random().Next(1, 100)).ToString(),
           Adi = "Deneme",
           Aciklama = "Açıklama"
       });
}
IList<TableResult> results = await table.ExecuteBatchAsync(batchOperation);

foreach (var res in results)
{
    var eklenenUrun = res.Result as Urun2;
}

Süper önemli noktalardan biri de "Entity Group Transaction"'lar. Elinizde birden çok işlem var ve bunları toplu olarak gönderebilmek istiyorsunuz. İşte o zaman EGT'leri kullanmanız şart. Bunun için normal TableOperation'lar yerine TableBatchOperation yaratıyorsunuz. Sonrasında kullanım batch olmayan operasyonlarla aynı şekilde. Tek farkı "ExecuteBatch" ile tüm işlemleri aynı anda REST API üzerinden gönderebilecek olmanız. Fakat burada da birkaç dikkat edilmesi gereken nokta var. Birincisi her EGT en fazla 100 işlem taşıyabilir. İkincisi ise bir EGT paketi en fazla 4MB büyüklüğünde olabilir. Bunları göz önüne alarak uygulamanızı tasarlamanız kritik. Tam "sınırlardan" bahsederken bir entity'nin 1MB'dan büyük olamayacağını (binary datalar bloblara gitsin lütfen :)) ve entity başına 255'dan fazla property olamayacağını da belirtiyim :)

EGT'lerin çalışması için ayrıca bir EGT içerisinde işlemlerin tek bir table ve tek bir partition'ı hedefliyor olması şart. Cross-Partition-Transaction yok. Tam da bu noktada eminim ki bazılarınıza "nasıl ya iki table'da aynı transaction içinde işlem yapamıyor muyuz?" diyecektir :) cevabım : "evet yapamıyorsunuz". Ama yazının başından beridir bahsetmediğim ilginç bir nokta var aslında :) Dikkat ettiyseniz Table yaratırken Field tanımlamaları yapmadık. Table'ların şemaları statik değil! Yani bir table içerisinde aslında farklı şemalarda nesneler bulunabiliyor. Ama ben şimdilik bu konuyu bir sonraki yazımıza bırakıyorum ;)

Görüşmek üzere.

Bu yazıyı normal bir okuma hızı ile ortalama 5 dakikada, göz gezdirerek ortalama 2 dakikada okuyabilirsiniz ;)

Bloblarla ilgili birçok detayı daha önceki yazılarda inceledik. Hatta PageBlob ile BlockBlob arasındaki farklılığa da değinmiştik. Şu ana kadar bildiklerimizin üzerinden geçmek gerekirse PageBlob'ların 512 byte'lık paketler (max4MB'lık paket olabilir) şekilde veri saklayabildiğini ve daha ağırlıklı olarak random read/write için uygun olduklarını BlockBlob'ların ise daha büyük paketlerle çalışabildiklerini (4MB gibi MAX 64MB Paket olabilir, Storage Client'taki maksimum destek 32MB'a kadar.) ve ufaktan da bir transaction yapılarının olduğunu biliyoruz. PageBlob'lar 1TB'a kadar veri saklayabilirken BlockBlob'lar 200GB ile sınırlı. Gelin şimdi biraz daha detaylara girerek işlemlerin daha alt seviyelerde nasıl yapılabildiğince göz atalım.

Bir BlockBlob'ın kullanımı

Normal şartlarda StorageClient'ın yüksek seviyeli diyebileceğimiz API'lerini kullandığınızda PageBlob veya BlockBlob gibi ayrımlarla uğraşmazsınız ama eğer isterseniz birazdan yapacağımız gibi daha alt seviyeli API'lere doğru ilerleyerek tüm kontrolü ele almanız da mümkün.

[C#]

CloudStorageAccount storageAccount = CloudStorageAccount.DevelopmentStorageAccount;
CloudBlobClient blobClient = storageAccount.CreateCloudBlobClient();
var anaContainer = blobClient.GetContainerReference("anacontainer");
anaContainer.CreateIfNotExists();
anaContainer.SetPermissions(new BlobContainerPermissions()
                     { PublicAccess = BlobContainerPublicAccessType.Blob });

İlk olarak yukarıdaki giriş kodumuz ile kendimize sıfırdan bir container yaratalım. Container'ımıza blob seviyesinde public access de verelim ki herkes ulaşabilsin. Şimdi ise yavaş yavaş bir BlockBlob yaratarak kullanımına bakalım.

[C#]

CloudBlockBlob blob = anaContainer.GetBlockBlobReference("birblockblob");

Blockblobları yaratırken CloudBlockBlob nesnesini kullanıyoruz. Genel mantık tek tek bloklar yüklemekten oluşuyor. Elinizdeki datayı istediğiniz boyutlarda bloklara bölerek bu blobları ayrı ayrı sunucuya gönderiyorsunuz. Her gönderdiğiniz bloğun bir de tabi ki blockID'si oluyor. İsterseniz bu blokları asenkron olarak gönderebilirsiniz, sırasız gönderebilirsiniz. Yani özetle kafanıza göre takılabilirsiniz :)

[C#]

List<string> blockIds = new List<string>();
for (int i = 0; i < 10; i++)
{
    var newId = Convert.ToBase64String(Encoding.Default.GetBytes(i.ToString()));
    blob.PutBlock(newId, new MemoryStream(Encoding.Default.GetBytes("TEST")), null);
    blockIds.Add(newId);
}

Yukarıdaki örnek kodda da görebileceğiniz üzere her bir Block'u PutBlock ile sunucuya gönderiyoruz. PutBlock'un toplam üç parametresi var, bunlardan ilki söz konusu bloğa ait ID bilgisi. ID'nin kesinlikle Base64 olması gerekiyor. İkinci parametre datamaızın ta kendisi. Ben deneme amaçlı bir çalışma için basit bir metin gönderiyorum ama siz elinizdeki büyük dosyaları da bloklara bölüp ayrı ayrı gönderebilirsiniz. Hatta multithread takılıp aynı anda birden çok blok bile gönderebilirsiniz. PutBlock'un son parametresi normalde verinin bir MD5 Hashi oluyor. Ben tembellikten eklemedim :)

Tüm bu işlemler sürerken BlockID'lerini bir listede tutuyoruz. Bunun nedeni ise aslında PutBlock dediğimizde verinin doğrudan Blob'a yansımıyor olması. Tüm blokları gönderdikten sonra gönderdiğiniz blokların ID'lerini ayrı bir liste olarak gönderip tüm block'ların commit olmasını sağlıyorsunuz.

[C#]

blob.PutBlockList(blockIds);

Böylece upload işlemi tamamlanmış oluyor. İşte bir BlockBlob'un çalışma yapısı bu şekilde. Aşağıdaki kodu bir bütün olarak çalıştırıp sonuca bir göz atın.

[C#]

protected void Page_Load(object sender, EventArgs e)
{
    CloudStorageAccount storageAccount = CloudStorageAccount.DevelopmentStorageAccount;
    CloudBlobClient blobClient = storageAccount.CreateCloudBlobClient();
    var anaContainer = blobClient.GetContainerReference("anacontainer");
    anaContainer.CreateIfNotExists();
    anaContainer.SetPermissions(new BlobContainerPermissions()
                 { PublicAccess = BlobContainerPublicAccessType.Blob });
    CloudBlockBlob blob = anaContainer.GetBlockBlobReference("birblockblob");
    List<string> blockIds = new List<string>();
    for (int i = 0; i < 10; i++)
    {
        var newId = Convert.ToBase64String(Encoding.Default.GetBytes(i.ToString()));
        blob.PutBlock(newId, new MemoryStream(Encoding.Default.GetBytes("TEST")), null);
        blockIds.Add(newId);
    }
    blob.PutBlockList(blockIds);
Response.Write(blob.Uri.ToString()); }

Ekrana yazdırılan adrese tarayıcıdan gittiğinizde doğal olarak arka arkaya yazılmış 10 tane TEST kelimesi olan bir metin dosyası ile karşılacaksınız.

Eğer isterseniz bir BlockBlob'un istediğiniz bir blokunu değiştirebilir veya üzerine yeni bloklar da ekleyebilirsiniz. Sonrasında commit ettiğiniz sürece dosyada değişikler anında gözükecektir. İşin güzel tarafı SnapShot almışsanız ve sadece tek bir block değiştirmişseniz sadece değiştirdiğiniz block'un boyutu içi para ödüyorsunuz ;)

[C#]

blockIds.AddRange(blob.DownloadBlockList(BlockListingFilter.Committed)
                      .Select(b => b.Name));
blob.PutBlock(blockIds[2], new MemoryStream(Encoding.Default.GetBytes("TEST2")), null);
blob.PutBlockList(blockIds);

Kodumuzun ilk satırında önemli bir detay var. DownloadBlockList(BlockListingFilter.Committed) dediğinizde commit edilmiş tüm blockların ID'lerini alabiliyorsunuz. Aynı şekilde UnCommitted bloklara da ulaşabilir ve commit edilmeden yine üzerlerinde değişiklik yapabilirsiniz. Bir Storage Account'ta toplam 100.000 uncommitted block olabilir ve toplam veri miktarı da 400GB'ı geçemez. Şimdilik örnek olması amacı ile ben gelen listeden ikinci block'u seçip metni değiştiriyorum. Sonunda ise tekrar blockID'leri gönderiyorum ki dosya son haline toparlansın.

Kodun son hali şu şekilde;

[C#]

protected void Page_Load(object sender, EventArgs e)
{
    CloudStorageAccount storageAccount = CloudStorageAccount.DevelopmentStorageAccount;
    CloudBlobClient blobClient = storageAccount.CreateCloudBlobClient();
    var anaContainer = blobClient.GetContainerReference("anacontainer");
    anaContainer.CreateIfNotExists();
    anaContainer.SetPermissions(new BlobContainerPermissions()
                     { PublicAccess = BlobContainerPublicAccessType.Blob });
    CloudBlockBlob blob = anaContainer.GetBlockBlobReference("birblockblob");
    List<string> blockIds = new List<string>();
    blockIds.AddRange(blob.DownloadBlockList(BlockListingFilter.Committed)
                          .Select(b => b.Name));
    blob.PutBlock(blockIds[2], new MemoryStream(Encoding.Default.GetBytes("TEST2")), null);
    blob.PutBlockList(blockIds);
    Response.Write(blob.Uri.ToString());
}

Unutmadan, eğer bir bloğu bir hafta boyunca commit etmezseniz söz konusu block otomatik olarak silinecektir. Aynı şekilde upload edilmiş bir Block'un ID'si eğer bir PutBlockList listesinde bulunmazsa hemen PutBlockList komutu sonrasında listede bulunmayan ve blob'a ait diğer havada kalan blocklar silinir.

PageBlob'lara bakış...

PageBlob'lar daha nokta atışı çalışmaya yönelik. Ayrıca önce veriyi veriyim sonra beraber commit ederim gibi bir durum da yok. Yapılan her değişiklik anında sonuca yansıyor. İşin kötü tarafı ise bloblara 512 byte'ın katları şeklinde veri sakalamak zorunda olmamız :) Yani elinizde stream varsa ona göre ilerlemeniz gerekecek.

[C#]

CloudStorageAccount storageAccount = CloudStorageAccount.DevelopmentStorageAccount;
CloudBlobClient blobClient = storageAccount.CreateCloudBlobClient();
var anaContainer = blobClient.GetContainerReference("anacontainer");
anaContainer.CreateIfNotExists();
anaContainer.SetPermissions(new BlobContainerPermissions()
                 { PublicAccess = BlobContainerPublicAccessType.Blob });

İlk olarak yukarıdaki gibi klasik işlemlerimizi yapalım ve PageBlob'umuzu yaratmaya hazırlanmış olalım.

[C#]

CloudPageBlob pageBlob = anaContainer.GetPageBlobReference("ornekpage4.txt")

Yukarıdaki kodu sanırım tahmin etmişsinizdir. Güzel bir PageBlob yaratarak yolumuza devam ediyoruz. Bundan sonrasında örnek olarak ben diskten bir TXT dosyası okuyup onu göndereceğimiz PageBlobumuza.

[C#]

CloudPageBlob pageBlob = anaContainer.GetPageBlobReference("ornekpage4.txt");

byte[] buffer;
FileStream fileStream = new FileStream(@"C:\test.txt", FileMode.Open, FileAccess.Read);
try
{
    int length = (int)fileStream.Length;  
    buffer = new byte[512];
    int count;                            
    int sum = 0;
    //BURAYA BİRŞEYLER GELECEK       
}
finally
{
    fileStream.Close();
}

İlk olarak gelin yukarıdaki koda hızlıca bir bakalım. PageBlob'umuzu yaratmışız. Basit bir şekilde diskten bir dosyayı FileStream ile açmışız. Sonra da bazı değişkenlerimizi tanımlamışız. Hazırlık belli ki bir While ile stream'i okumak için yapılmış :) Önemli noktalardan biri buffer'ımızın 512 byte olması. Sürekli 512 byte'lık paketler göndereceğiz PageBlob'a. PageBlob'a gönderebileceğiniz paketler 512 byte ile 4MB arası değişebilir ama kesinlikle 512'nin katı olmak zorundalar.

[C#]

var EnYakin512Kati = ((length + 511) / 512) * 512;
pageBlob.Create(EnYakin512Kati);

while ((count = fileStream.Read(buffer, 0, 512)) > 0)
{
    pageBlob.WritePages(new MemoryStream(buffer), sum);
    buffer = new byte[512];
    sum += count; 
}

Bir üstteki kodun orta yerine yukarıdakini alıyoruz :) İlk olarak elimizdeki dosyanın stream uzunluğuna en yakın 512'nin katını bularak o boyuta denk gelecek şekilde pageBlob'un Create metodu ile Blob'u gerçekten yaratıyoruz :) Sonra bir while ile buffer'ımızı doldurup doldurup WritePages ile de bloba aktarıyoruz.

Kodun tamamını aşağıda bulabilirsiniz;

[C#]

CloudStorageAccount storageAccount = CloudStorageAccount.DevelopmentStorageAccount;
CloudBlobClient blobClient = storageAccount.CreateCloudBlobClient();
var anaContainer = blobClient.GetContainerReference("anacontainer");
anaContainer.CreateIfNotExists();
anaContainer.SetPermissions(new BlobContainerPermissions()
                 { PublicAccess = BlobContainerPublicAccessType.Blob });

CloudPageBlob pageBlob = anaContainer.GetPageBlobReference("ornekpage4.txt");

byte[] buffer;
FileStream fileStream = new FileStream(@"C:\test.txt", FileMode.Open, FileAccess.Read);
try
{
    int length = (int)fileStream.Length;  
    buffer = new byte[512];
    int count;                            
    int sum = 0;
    var EnYakin512Kati = ((length + 511) / 512) * 512;
    pageBlob.Create(EnYakin512Kati);
    while ((count = fileStream.Read(buffer, 0, 512)) > 0)
    {
        pageBlob.WritePages(new MemoryStream(buffer), sum);
        buffer = new byte[512];
        sum += count; 
    }       
}
finally
{
    fileStream.Close();
}
Response.Redirect(pageBlob.Uri.ToString());

İtiraf etmem lazım PageBlob'larla çalışma hiç kolay değil. En azından ben BlockBlob'ları daha çok seviyorum :) PageBlob'lar ise tek tek 512bytelık aralıklara kadar müdahale sağlıyor. PageBlob'lardan snapshot aldığınızda sadece değişen 512 byte'lar için para ödeyeceğini de hatırlamakta fayda var ;)

Kendinize çok iyi bakın.

Bu yazıyı normal bir okuma hızı ile ortalama 3 dakikada, göz gezdirerek ortalama 1 dakikada okuyabilirsiniz ;)

Azure SDK'yi yüklediğinizde makinenize iki farklı emülatör yükleniyor. Bunlardan biri DevFabric de denilen Compute Emulator, yani uygulamanıza Azure ortamını yaşatan arkadaş, diğeri ise Storage Emulator, Azure Storage servislerini lokalde test etmemizi sağlayan araç. Her iki araca da Visual Studio ile ilk projenizi F5'e basarak çalıştırdıktan sonra "notification bar" dan ulaşabilirsiniz.

Azure Emülatörleri İş
Başında Azure Emülatörleri İş Başında

Storage Emulator

Storage Emulator localde sadece bir storage account sağlayabildiği için projeler arası çakışmalara neden olabilir. Özellikle aynı isimler container'lar kullanıyorsanız farklı datalar farklı projelerde kendini göstermeye başlayabilir :) Bu durumu engellemek adına Storage Emülatör'ün arayüzünü açıp tüm storage'ı resetleyebilirsiniz.

Storage Emulator'ü gidiyor... yerine command line geliyor... Storage Emulator'ü gidiyor... yerine command line geliyor...

"Herkes gider mersine biz gideriz tersine." sözünü hatırlatan kıvamda :) Azure Emulatorü'nün UI sürümünü Microsoft artık geliştirmeyeceğini söylüyor. Scripting desteği verebilmek açısından command line araçlarının gelmesi önemli fakat bu durum "UI'dan vaz geçme"yi de gerektirmez bence. Neyse, kişisel yorumlarımı bir kenara koyalım :) "Yeni command line" arayüzünü (şaka gibi) açtıktan sonra karşınıza kullanabileceğiniz komutların bir listesi geliyor.

Storage Emülatörü komutları karşımızda. Storage Emülatörü komutları karşımızda.

Storage Emulator içerisinde toplam üç storage servisi (Blob, Queue, Table) için gerekli endpointler yaratılmış durumda.

Servislerin local endpointleri. Servislerin local endpointleri.

Bu Endpoint'lere ister doğrudan REST Call'larla gidin ister StorageClient ile gidin bire bir Azure ortamındaki Storage servisleri ile aynı tepkileri vermekle yükümlüler ve itiraf etmek gerekirse şu ana kadar herhangi bir yanlışlarını görmedim :)

Arkada neler oluyor...

Esas işin ilginç tarafı şimdi başlayacak :) Storage Emulator SQL'e bağımlı. Bunun nedeni tüm datayı aslında SQL'de tutuyor olması. Burada çok önemli bir detay var; Storage Emulator'ın datayı SQL'de tutuyor olması Azure ortamında da aynı şeyin yapıldığı anlamına gelmiyor. Hatta açıklamalara göre Azure'daki Storage servislerinin arkasında SQL yok (Zaten mantıksız olurdu) ama arkada ne var diye sorarsanız :) bilmiyorum :)

Lokal yüklemede emülatörün arkasında SQL olması işimizi özellikle debuggine senaryolarında kolaylaştırabiliyor. Ne kadar ki üretkenliğinizi yükseltmek adına Storage Explorer tadında araçlarla çalışmak zorunda kalacak olsanız da :) işin dibine inmek isterseniz aslında basit bir şekilde SQL Management Studio ile bağlanıp emülatörün kullandığı veritabanını da inceleyebilirsiniz.

Storage Emülatörü arkasında SQL'e bir
bakış. Storage Emülatörü arkasında SQL'e bir bakış.

Yukarıdaki ekran görüntüsünde basit bir şekilde BlobContainer listesini görebiliyorsunuz. Bunun gibi tüm Storage verisini local emülatör SQL'de tutuyor. Acaba yolladıklarım kaydoluyor mu gibi testleri buralarda yapmak mümkün. Tabi yine tekrar etmem gerek; çok alt seviye bir testing aracı olarak düşünmek lazım bu yöntemi.

Azure Emulator

Gelelim bir de işin Azure Emulator tarafına. Azure Emulator bilgisayarınızdaki IIS Express'i kullanarak Web Role emülasyonu yapıyor. Instance'larınızı ve konsollarını tek tek emülatör arayüzünden görebiliyorsunuz. Bir anlamda local bir FC rolü oynamaya çalışıyor.

Azure Emulator
UI Azure Emulator UI

Yukarıdaki ekran görüntüsünde gördüğünüz deployment'ta sadece bir instance web role var. Buradaki rollerin her birine bağlanıp durumlarını takip edebiliyoruz. Ayrıca buradan instance'ları suspend edebilir farklı durumlara verdikleri tepkileri de ölçebilirsiniz.

Local emülatörün
parçaları... Local emülatörün parçaları...

Yukarıda kabaca local Azure Emülatörünün mimari olarak nasıl da gerçek Azure ortamına benzetilmeye çalışdığını görebilirsiniz. IISConfigurator'dan tutun WaIISHost'larımıza kadar herşey neredeyse aynı. Her Instance için bir HostBootStrapper var ve instance tipine göre de WaWorkerHost veya WaIISHost kullanılarak uygulama ayağa kaldırılıyor. Böylece Azure ortamına çok benzeyen bir debugging deneyimi yaşayabiliyoruz.

Hepinize kolay gelsin.

Bu yazıyı normal bir okuma hızı ile ortalama 3 dakikada, göz gezdirerek ortalama 1 dakikada okuyabilirsiniz ;)

Blobların en sevdiğim özelliklerinden biri blob başına snapshot alabiliyor olmamız :) Sınır yok, istediğiniz kadar snapshot alabilirsiniz :) (yeter ki parasını ödeyin) Şaka bir yana SnapShot almanın kolaylığı ve "yedekleme" amaçlı olarak süper faydalı olması geliştireceğiniz uygulamalarda eminim ki ciddi katma değer sağlayacaktır.

[C#]

CloudStorageAccount account = CloudStorageAccount.DevelopmentStorageAccount;
CloudBlobClient blobClient = account.CreateCloudBlobClient();
CloudBlobContainer container = blobClient.GetContainerReference("dosyalar");
container.CreateIfNotExists();

CloudBlockBlob blob = container.GetBlockBlobReference("istanbul_azure_logo.png");
var snapshotBlob = blob.CreateSnapshot();

Response.Write(snapshotBlob.SnapshotQualifiedUri);

Yukarıdaki kod içerisinde basit bir blob bulduktan sonra hemen üzerinden CreateSnapshot metodunu çağırıyoruz. Metodu çağırdığımız anda aslında artık blobun snapshot'ı yani bir kopyası tarihin tozlu raflarında yerini almış oluyor :) Blobun snapshot'ına direk ulaşmak isterseniz ancak SnapshotTime üzerinden snapshot'ın alındığı tarihi kullanarak ilerleyebiliyorsunuz. Local testte aldığımız URL aşağıdaki gibi oluyor.

http://127.0.0.1:10000/devstoreaccount1/dosyalar/istanbulazurelogo.png?snapshot=2014-12-2T11:33:32.5370000Z

Güzel, bir snapshot yarattık ve belli ki ona ulaşabiliyoruz. Fakat ya bir blob'un birden çok snapshot'u alınmışsa? Onlara nasıl ulaşırız? İşte bu noktada klasik blob listeleme senaryosunda geri dönüyoruz. Normal blobları listelermiş gibi ilerlerken ek olarak SnapShot'ların da listelenmesi için bir parametre göndermemiz yeterli oluyor.

[C#]

CloudStorageAccount account = CloudStorageAccount.DevelopmentStorageAccount;
CloudBlobClient blobClient = account.CreateCloudBlobClient();
CloudBlobContainer container = blobClient.GetContainerReference("dosyalar");
container.CreateIfNotExists();

CloudBlockBlob blob = container.GetBlockBlobReference("istanbul_azure_logo.png");

var snapshots = container.ListBlobs(blob.Name, true, BlobListingDetails.Snapshots);
foreach (CloudBlockBlob snap in snapshots)
{
    if(snap.IsSnapshot )
    {
        Response.Write(snap.SnapshotQualifiedUri);
    }                
}

Yukarıdaki kod içerisinde ListBlobs'a verdiğimiz BlobListingDetails işimizi görecektir. Ayrıca söz konusu metoda verdiğimiz ilk parametrede de eldeki blobun adını vererek sadece o bloba ait kayıtların dönmesini sağlamış oluyoruz. Ama burada ufak bir uyarıda da bulunmam gerek. ListBlobs'un ilk parametresi bir Prefix aslında. Yani one2one match olarak ismine göre Blob aramaz. O nedenle eğer prefix ile tutturabileceğinizden emin değilseniz içeride ayrıca BlobName kontrol yapmakta fayda var. Elimize gelen listede dolaşırken orijinal blobu dışarıdan bırakmak için IsSnapShot kontrolü yapıyor, sonrasında da SnapshotQualifiedUri'yi alıyoruz.

Doğal olarak bu noktadan sonra artık SnapShot'lar da birer blob oldukları için SnapShot silme işlemini de normal bir blob siler gibi yapabiliyoruz ama ufak bir sorun var :) O da normal şartlarda SnapShot'ı olan bir ana blobun SnapShot'ların hepsi silinmeden silinemiyor olması. Bunun için SnapShot listesi alıp for'la gezip tek tek SnapShot silmeye gerek yok ;)

[C#]

blob.Delete(DeleteSnapshotsOption.IncludeSnapshots);

Yukarıdaki gibi silme işlemine de DeleteSnapshotsOption verebilirsiniz. Burada IncludeSnapshots veya DeleteSnapshotsOnly diyebiliyorsunuz.

[C#]

birBlob.StartCopyFromBlob(snapshotBlob);

Eğer ki bir SnapShot Blob'unu alıp bir başka bloba kopyalamak isterseniz yukarıdaki kod yardımcı olabilir. Unutmayın ana blobları kopyalarken SnapShot'lar taşınmaz ve kopyalanmaz. Snapshot her zaman ilk yaratılığı bloba bağlıdır. StartCopyFromBlob metodunu bir snapshotı restore etmek için de kullanmanız gerekecek. SnapShot'tan orijinal blobun üzerine kopyalama yapmak yeterli olacaktır.

Birkaç detay...

SnapShotların çalışma şeklini bilmek ne için ne kadar para ödeyeceğinizi bilmek adına da önemli. Bir SnapShot aldığınızda eğer SnapShot yapıldıktan sonra Blob'da değişiklik olmadıysa ek bir ücret ödemiyorsunuz. Özetle aslında sadece değişen Block veya Page için SnapShot'larda para ödersiniz. Fakat ufak bir uyarı :) eğer SnapShot'lar sizin için çok önemliyse ve çok kullanacaksanız StorageClient ile beraber gelen UploadFile, UploadText, UploadStream veya UploadByteArray gibi metodları kullanmayın. Bu metodlar upload esnasından tamamen sıfırdan upload yaptıkları için tüm page'ler veya block'lar değişiyor ve bu da sıfırdan bir snapshot almış olmak anlamına geliyor. Nitekim bu şekilde de ilerlenebilir ama bilerek ilerlemekte fayda var :) Bahsettiğimiz metodların kullanımları kolay ama SnapShot'ta da ufak bir dezavantajları var. Eğer böyle kullanacaksanız tavsiyem SnapShot'larınızı doğru yönetin ki gereksiz yere binlerce SnapShot durmasın kenarlarda.

SnapShot'ları çok yoğun kullanacaksanız ideal yöntem bir alt seviyeye inip kaynaklarınızı doğrudan Page veya Block seviyesinde yönetmek. StorageClient içerisinde PutBlock ve PutBlockList gibi işinize yarayabilecek metodlar mevcut ;)

Kolay gelsin.

Bu yazıyı normal bir okuma hızı ile ortalama 4 dakikada, göz gezdirerek ortalama 1 dakikada okuyabilirsiniz ;)

Blob konusunu inceledikçe aslında adam akıllı dosya tutabileceğimiz :) süper bir yer olduğu hissiyatı eminim ki sizin de içinizi kaplamıştır. Bu düşünce yapısı ile ilerlerken aslında akla gelebilecek daha bir sürü soru var. Örneğin bir sitede kullandığımız CSS, JavaScript dosyaları acaba sitenin bir parçası mı olmalı yoksa Blob'da mı durmalı :)

Nereden çıktı bu da şimdi diyebilirsiniz ama bir düşünün. En ufak CSS değişikliğinde tüm service package'ı tekrar yaratıp upgrade süreci başlatmak ister misiniz? Aynı soru belki sitenizde kullandığınız ve site tasarımının bir parçası olan görsel dosyaları için de geçerli. Sitedeki şirket logosu bir blobda dursa daha iyi olmaz mı?

Tüm bu soruları kendi kendimize sora duralım ister bu gibi ihtiyaçlar yüzünden olsun ister eski alışkanlıkar sonuç itibari ile Blog Storage'daki tek klasörlere yapısı olarak gözüken Container'lar bize yetmeyecek galiba :) Container'ları iç içe koyamayacağımızdan bahsetmiştim değil mi? Bu konuya canınız sıkılıyorsa işte süper olmasa da işin çözümü şöyle...

File System Etkisi

Bir sunucudaki klasörlerin web ortamına yansıma şekli malum URL'de kendini gösterir. Her ne kadar artıkl URLReWriting sayesinde delikanlılığın kitabı tekrar yazılsa da :) sonuç itibari URL'deki ayrımlar çoğu zaman bizim için mantıksal ayrımlar şeklinde dosyaların yönetimi için ciddi yardımcı bir öğedir. Aynı etkiyi Azure'da Blob Storage'da yakalayabilir miyiz? sorusuna "Evet" cevabını verebilirim :) ama birazdan her ne yaparsak yapalım sakın unutmayın :) aslında böyle birşey yok :) yapacağımız herşey sanal, hala bloblarınız bir container içerisinde olacak.

[C#]

CloudStorageAccount account = CloudStorageAccount.DevelopmentStorageAccount;
CloudBlobClient blobClient = account.CreateCloudBlobClient();
CloudBlobContainer container = blobClient.GetContainerReference("dosyalar");
container.CreateIfNotExists();

BlobContainerPermissions containerPermissions = new BlobContainerPermissions();
containerPermissions.PublicAccess = BlobContainerPublicAccessType.Blob;
container.SetPermissions(containerPermissions);

var blob = container.GetBlockBlobReference("resimler/" + FileUploadControl.FileName);
blob.UploadFromStream(FileUploadControl.FileContent);

Response.Write(blob.Uri.ToString());

Çakallığın farkına vardınız mı? :) Blob ismimiz artık sadece dosya ismi değil. Yukarıdaki kod bloba isim olarak "resimler/" ile beraber dosya adını veriyor böylece blobun ismi "resimler/ornek.jpg" gibi birşey oluyor. Peki bunun URL'e yansıması nasıl olacak?

Blob adını istediğimiz gibi verince URL de istediğimiz gibi oldu
:) Blob adını istediğimiz gibi verince URL de istediğimiz gibi oldu :)

Nasıl? Güzel oldu mu :) Şimdi tabi ki diyeceksiniz ki... "tamam güzel de bu dosyaların yönetimini kolaylaştırmıyor" ve kesinlikle haklısınız :) Bu noktaya kadar sadece web sitemize girip bu adresi gören ziyaretçileri kandırmış olduk. Yoksa arka planda değişen hiçbirşey olmadı, olamaz da. Ammmaaa! :)

StorageClient diye bir wrapper kullanıyoruz ya :) İşte onun içindeki bazı ek ilginç hareketler Container'lar içerisinde sanal klasörler varmış gibi listelemeler yapabilmemizi sağlıyor. Böylece normal bir dosya sisteminden beklediğimiz gibi klasörleme mantığını kodda da kullanabiliyoruz.

[C#]

CloudStorageAccount account = CloudStorageAccount.DevelopmentStorageAccount;
CloudBlobClient blobClient = account.CreateCloudBlobClient();

CloudBlobContainer container = blobClient.GetContainerReference("dosyalar");
CloudBlobDirectory directory = container.GetDirectoryReference("resimler");
foreach (var blobItem in directory.ListBlobs())
{
    Response.Write(blobItem.Uri + "<br/>");
}

Yukarıdaki kodda Container'ımızı yakaladıktan sonra artık içinde klasörler varmış gibi "VirtualDirectory" yapısında ilerliyoruz. "resimler" adı Blob'ların adında yer alsa da bir klasörmüş gibi CloudBlobDirectory olarak alıp içindeki blobları listeleyebiliyoruz.

http://127.0.0.1:10000/devstoreaccount1/dosyalar/resimler/digerleri/
http://127.0.0.1:10000/devstoreaccount1/dosyalar/resimler/test.txt
http://127.0.0.1:10000/devstoreaccount1/dosyalar/resimler/v.jpg

Yukarıda örnek bir dosya listesi görebilirsiniz. Fark ettiyseniz yukarıda bir sorun var. Listenin ilk başkındaki nesne bir dosya değil. Deneme amaçlı olarak iç içe birden çok klasör yaratacak şekilde bloblara isim verirseniz bir CloudBlobDirectory'den alacağınız Blob listesi aslında içindeki klasörü de getirecektir. Bu durumdan kurtulmanın tek yöntemi gelen nesnelerin tipini kontrol etmek.

[C#]

CloudBlobContainer container = blobClient.GetContainerReference("dosyalar");
CloudBlobDirectory directory = container.GetDirectoryReference("resimler");
foreach (var blobItem in directory.ListBlobs())
{
    if (blobItem is CloudBlobDirectory)
    {
        foreach (var blobItemNested in ((CloudBlobDirectory)blobItem).ListBlobs())
        {
            Response.Write("Dosya:" + blobItemNested.Uri + "<br/>");
        }
    }
}

Kodun foreach ile başlayan noktasından itibaren manzaraya bakacak olursan aslında container adımız olan resimleri bir klasör olarak kabul edip içindeki blobları dönüyorum. Bu dönüş sürecinde :) gelen nesnenin bir CloudBlobDirectory olup olmadığını kontrol edip (yani klasör olup olmadığını bir anlamda anlayıp) eğer klasörse tekrar onun altından da bir ListBlobs çekerek dosyaları alabiliyoruz.

Normal bir dosya sistemi konseptine yaklaşmaya birkaç adım kaldı sanırım :) Bunlardan ilki klasör yaratbilme. Maalesef bu konuda bire bir System.IO'ya benzetilebilecek bir yapı yok. Unutmayın sonuçta sanal klasör dediğimiz şeyler aslında blobların isimleri :) Sanal klasör ve dosya ayrımını yapan StorageClient wrapper. Yani aslında şu an ne yapıyorsak bunlar sorgular şeklinde Container seviyesinde çalıştırılıyorlar.

İtiraf etmek gerekirse bu Directory yapısına Microsoft desteği ne zaman kesecek diye ben heyecanla bekliyorum. Eski SDK sürümlerinde bu gibi bir destek developerların Blob yapısına geçmeleri için güzel bir havuçtu ve klasör yapısına alışmış developerlara bir çıkış yolu sunuyordu. Fakat artık bu geçiş dönemininin az çok bittiğini de düşünürsek söz konusu geçiş araçlarına gerek var mı tartışılır. Sonuç itibari ile tüm bunlar, sanal klasörler vs doğal, organiz değil :) ve performansa etkisi olacaktır. Daha da kötüsü eğer bu yapıya çok odaklanır ve kendinizi kaptırırsanız blobların gerçek çalışma şeklini unutabilir ve bir anda kendinizi tüm blog yapısını efektif olmaktan çok uzak yöntemlerle kullanırken bulabilirsiniz. Özlete, bence sıkışmadıkça uzak durun :)

Kolay gelsin ;)

Bu yazıyı normal bir okuma hızı ile ortalama 6 dakikada, göz gezdirerek ortalama 2 dakikada okuyabilirsiniz ;)

Dün Storage Account'ları incelerken Redundancy modelinden, Replication seçeneklerinden bahsetmiş fakat detayları incelememiştik. Bu yazıda tüm Storage servisleri için geçerli olan farklı redundancy modellerine göz atacağız.

Bir Storage Account yarattığınızda size sorulan sorulardan biri de "Replication" modeli oluyor. Azure ortamında Storage için şu an dört farklı Replication modeli söz konusu.

Replication modeli fiyatlandırma için de kritik. Replication modeli fiyatlandırma için de kritik.

Locally Redundant

Farklı Fault-Domain ve Upgrade-Domain'lerde olacak şekilde storage transactionlarınızın hepsi senkron bir şekilde toplam üç ayrı storage node'una gönderilir. Böylece Azure Storage servisinin arka planında bir donanım sorunu oluşsa da veriniz ulaşılabilir durumda olur.

Geo-Redundant

Locally Redundant modelde olduğu gibi aynı datacenter içerisinde yine üç kopya tutulur. Bu üç kopyanın haricinde asenkron olarak transactionlar bir başka bölgedeki DataCenter'a daha gönderilir. Diğer DC'ye gönderilen transaction da yine söz konusu DC'de üç kopya olarak tutulur. Bu seçenek genelde failover amacı ile kullanılır ve ilk bölgedeki üç kopya bir şekilde kaybedilirse veya altyapı aşağı inerse ikinci bölgedekinin kullanımı sağlanır. İki bölge arasındaki replication asenkron olduğu için failover esnasında kopyalanmamış bir miktar data tamamen kaybedilebilir.

Read-Access Geo-Redundant

Geo-Redundant yapı ile aynı özellikleri barındıran bu yapıda ek olarak okuma operasyonları birden çok bölgeye dağıtılarak daha yüksek okuma performansı elde edilir.

Zone Redundant

Bu modelde veriniz yine üç kopya olarak tutulur ve aynı Zone içerisindeki farklı DataCenter'larda saklanır. Zone Redundant modeli Geo-Redundant modelin çoğrafi olarak aynı bölge içerisine uygulanmış hali olarak düşünebilirsiniz. Tüm diğer modellerde şu anda Storage servislerinin hepsi desteklenirken, Zone Redundant modelde sadece Block Blob'lar destekleniyor. İleriki yazılarda bahsedeceğimiz Page Blob, Table ve Queue yapıları Zone Redundant olan Storage Account'larda kullanılamıyor.

Read-only Access Geo Redundant Storage nasıl kullanılır?

Eğer RA-GRS modelinin kendi kendine arka planda çalışacağını zaennediyorsanız aldanırsınız. Aslında tam olarak da aldanmış sayılmazsınız, eğer Storage Client Library 3.0 (ve sonraki) sürümünü kullanıyorsanız sorun olmayacaktır. Her halükarda gelin biz neler oluyor bir göz atalım.

Read Only İkincil Endpointler geldi... Read Only İkincil Endpointler geldi...

Yukarıdaki ekran görüntüsünde RA-GRS replikasyon modelini kullanan bir Storage Account ile kullanmayan bir Storage Account'un endpoint farklılıklarını görebilirsiniz. Görüldüğü üzere RA-GRS'yi seçtiğimizde Secondary Endpoint denilen ikinci erişim adresi oluşturuluyor. Böylece arka planda kopyalanan storage ortamına ulaşabiliyorsunuz. Tabi diğer yandan bu şekilde iki farklı endpoint verildiğinde de akla şu soru geliyor; "Hangisine ne zaman gideceğime ben mi karar vereceğim?" Özetle, amacımız failover olmadığına göre (öyle olsaydı zaten sadece Geo-Redundant kullanırdık) ve amacımız okuma operasyonlarını dağıtmak olduğuna göre bu işi biz mi yapacağız? Cevabı; Evet.

[C#]

CloudStorageAccount account = CloudStorageAccount.Parse("DefaultEndpointsProtocol=http;AccountName=daronragrs;AccountKey=DRxxxaPXg==;");
CloudBlobClient blobClient = account.CreateCloudBlobClient();
CloudBlobContainer container = blobClient.GetContainerReference("dosyalar");
container.CreateIfNotExists();

var blob = container.GetBlockBlobReference(FileUploadControl.FileName);
blob.UploadFromStream(FileUploadControl.FileContent);
Response.Write(blob.Uri.ToString());

Yukarıdaki kodu daha önceki yazılardan hatırlayabilirsiniz. Basit bir şekilde örnek bir container yaratıp içerisine de FileUpload kontrolü ile verilen bir dosyayı blog olarak atıyoruz. Bu kodun RA-GRS'a özel bir yanı yok. Kodu çalıştırdığımızda da doğal olarak bize blob endpoint URL olarak http://daronragrs.blob.core.windows.net/dosyalar/sample.txt geliyor. Storage hesabımızın replikasyon modeli RA-GRS olduğu için "sample.txt" dosyayı ikinci endpointin arkasına da kopyalanacak ve "http://daronragrs-secondary.blob.core.windows.net/dosyalar/sample.txt" adresinden de ulaşılabilir halde olacak. Böylece normal storage SLA'i olan %99.9, %99.99'a yükselmiş olacak :) Peki dışarıdan gelmeyen ve sizin kodunuz ile yaptığınız okuma operasyonlarını nasıl dağıtacaksınız? Özetle Storage Library size nasıl yardımcı olacak?

Storage Client içerisinde Location seçenekleri. Storage Client içerisinde Location seçenekleri.

Yukarıdaki ekran görüntüsünde de görebileceğiniz üzere her Client objesinin bir LocationMode özelliği var. LocationMode'a ister doğrudan Primary veya Secondary diyebileceğiniz gibi istersenizi önceliğe göre terih seçeneği de verebiliyorsunuz. Bu noktada özellikle dikkat etmeniz gereken nokta "SecondaryOnly" seçeneği. Eğer sadece "Secondary" terseniz container yaratma gibi kodlarınız "Read" operasyonu değil "Update / Write" operasyonu olduğu için çatlayacaktır. O nedenle eğer hem read hem write yapıyorsanız benim tavsiyem en kötü ihtimalde "SecondaryThenPrimary" tercihini kullanın.

Önceliklendirmeli ayarlarda (PrimaryThenSecondary veya SecondaryThenPrimary) bilmeniz gereken birkaç nokta daha var. Bu şekilde yapılan ayarlar aslında bir anlamda "Failover" ayarı oluyor. Eğer lokasyonlardan birine ulaşılamazsa RetryPolicy'sine göre SDK doğrudan diğer lokasyona yönlenecektir. Bu senaryoda tek istisna Secondary ile alakalı. Eğer Secondary'den 404 NotFound gelirse ayrı Retry Cycle içerisinde tekrar Secondary denenmeyecektir. Bunun nedeni ise birazdan bahsedeceğimiz replikasyon gecikmesi (replication delay) ile alakalı. Malum iki ayrı lokasyon arasında replikasyon yapılırken bir gecikme de söz konusu. Bu gecikme sizin servise yönlendirdiğiniz taleplerin yoğunluğuna göre bir noktada hissedilir hale gelebilir. Bunu da engellemenin yolu var :)

[C#]

ServiceStats stats = blobClient.GetServiceStats(
    new BlobRequestOptions() { 
        LocationMode = LocationMode.SecondaryOnly 
    });
if (stats.GeoReplication.Status == GeoReplicationStatus.Live)
{
    if (stats.GeoReplication.LastSyncTime.HasValue == true)
    {
        if (stats.GeoReplication.LastSyncTime.Value.AddSeconds(30) < DateTime.UtcNow)
        {
            blobClient.DefaultRequestOptions.LocationMode = LocationMode.PrimaryThenSecondary;
        }
        else
        {
            blobClient.DefaultRequestOptions.LocationMode = LocationMode.SecondaryThenPrimary;
        }
    }
}

İşte karşınızda LastSyncTime ve GeoReplicationStatus. Gelin adım adım yukarıdaki kodu inceleyelim. BlobClient üzerinden GetServiceStats dediğimizde RA-GRS olarak ayarlı hesabımızın replikasyon durumunun bilgisini istemiş oluyoruz. Replikasyon bilgisini ancak Secondary Endpoint'ten isteyebilirsiniz. O nedenle bu metoda parametre olarak BlobRequestOptions veriyoruz. Storage Client ile operasyon başına Secondary-Primary endpoint kararı almak isterseniz bu kararı bir BlobRequestOptions içine koyup metoda iletebilirsiniz. Böylece bizim örneğimizde BlobClient'ın genel karar mekanizmasından farklı olarak sadece GetServiceStats için Endpoint kararını ezerek doğrudan Secondary Endpoint'e gitmiş oluyoruz.

Bir sonraki adımda GeoReplication'ın Status'üne bakmamız gerek. Burada üç seçenek mevcut. Eğer Unavailable gelirse replikasyon falan yok ortada, her şey dağılmış ve Secondary'ye ulaşılamıyor demektir. Bu durumda kesinlikle Primary'ye gitmeniz gerek. Bizim kod örneğimizde işin bu kısmına dair bir yapı yok. Eğer Bootstrap gelirse daha replikasyon yeni başlatılmış ve data kopyalanıyor demektir. Bu durum sadece ilk olarak bir Storage Account'un GRS'den RA-GRS'ye alındığında gerçekleşir. Son olarak da Live değeri söz konusu. Bu da replikasyonun sağlıklı bir şekilde ayakta olduğunu gösteriyor.

Replikasyonun sağlığından emin olduktan sonra LastSyncTime'a bakıyoruz. Örneğimizde eğer LastSycnTime üzerinden 30 saniyeden çok zaman geçmiş ise Primary'ye geçmemişse Secondary'e gidiyoruz. Tabi her iki seçenekte de diğer endpointi failover olarak verebilmek adına "SecondaryOnly" veya "PrimaryOnly" yerine sürekli olarak "PrimaryThenSecondary" veya "SecondaryThenPrimary" kullandık. Tabi tüm bunlar sizin ihtiyaçlarınıza göre farklı tasarlanabilir. Hatta 30 saniye kuralı dahi tamamen değiştirilmesi gereken bir kural. Bu noktada sormanız gereken soru "Ne kadarlık bir replikasyon gecikmesini hoş görebilirim?" sorusu. Tüm bunları yanı sıra bir de şöyle bir sıkıntı var, ya son 1 dakikadır zaten replikasyonun güncellenmesini gerektirecek bir operasyon olmadıysa? Bu durumda zaten LastSyncTime'ın bir dakika önceyi göstermesi normal. Özetle şu anki zaman ile LastSyncTime arasında 30 saniyeden fazla süre olması replikasyonun tamamlanmış halde olup olmadığına dair bir fikir vermiyor. LastSyncTime her servis başına (Blob, Table, Queue) en son her iki lokasyonunda da kuyrukta replike edilecek şey kalmadan eşitlenmiş olduğu anın zamanını verir. Örneğin X adındaki blobunuz LastSyncTime'dan sonra her iki lokasyona da replike edilmiş ve eşitlenmiş olabilir ama Y adındaki blobunuz daha sonradan değiştirildiği için daha replikasyon kuyrunda kalmış olabilir. Bu nedenle tüm servis genelinde bir eşitlenme anı yaşanmadığından LastSyncTime X blobunun eşitlenme anında daha eski bir zamanı gösterebilir. Tüm bu senaryoların etrafından dolaşacak kodları yazmak da size kalıyor :) Geo-Replikasyon hiçbir zaman basit olmamıştır zaten.

Merak edebilecekleriniz :)

Aklınıza gelebilecek ve cevaplamadığımız bazı sorular var.

  1. Secondary Endpoint'in Credential/Key'leri ne durumda?
    Secondary ile Primary'nin key'ler tamamen aynı. Secondary'nin Key diye bir konsepti olmadığını düşünebilirsiniz.

  2. Shared Access Signature'lere ne oluyor?
    Hiç bir şey olmuyor. İmzalama esnasında kullandığınız account ismine -secondary eklememeniz çok önemli. İmza için kullandığınız key aynı olduğundan resourceURI'yi de DNS'den vs çıkarmamanız şart. Aksi halde problem çıkacaktır. Özetle aynı SAS ile secondary'ye Endpoint'e gidebilirsiniz.

  3. Storage Analytics kullanıyorum. Secondary için ne olacak?
    Secondary endpointin storage analytics'i primary ile aynı yerde saklanıyor. Metric tablolarının adları Secondary ve Primary olarak ayrıştırılacak saklanacaktır.

Hepinize kolay gelsin.

Twitter
RSS
Youtube
RSS Blog Search
Arşiv'de tüm yazıların listesi var. Yine de blog'da arama yapmak istersen tıkla!
Instagram Instagram