MapReduce, kısaca Google’ın veri işleme, indexleme gibi işlemlerinin arka planında yer alan algoritmasıdır. Çok sayıda makina barındıran kümelerle oluşturulan dağıtık mimari ile verilerin toplanması ve analiz edilmesi sağlanır.
Dağıtık mimari dediğimiz yapıda tüm veriyi alan ana düğüm (master node), alt düğümlere (worker node) veri toplama işini dağıtır. Toplanan veriler birleştirilerek ana düğüme tekrar gönderilir. Reduce aşamasında ise veri analiz edilir. İlişkisel veritabanları mantığı ile düşünürsek; map, select ifadesini, reduce ise count, having, avg gibi ‘data-aggreagation- işlemlerine karşılık geliyor. NoSQL sistemlerinde bu işlemlerin biraz daha karmaşık olması sebebiyle MapReduce işimizi gerçekten kolaylaştırıyor. MongoDB’nin dökümantasyonundan aldığım aşağıdaki görsel durumu daha açıklayıcı hale getirebilir.

MapReduce yapısının Google’ın bir algoritması olduğunu söylemiştik. C# ile MapReduce yapısını kullanmak istiorsanız MongoDB üzerinde entegre olarak geliyor. MongoDB C# driver ı her versiyonda biraz farklılaşıyor. Aşağıdaki örnekte 1.9.2 sürümünü kullandığımı belirteyim. Siz de projede kullanmak için Package Manager Console üzerinden dahil edebilirsiniz.
PM> Install-Package mongocsharpdriver -Version 1.9.2
Örnek senaryomuzda Products adında basit bir koleksiyon üzerinde bazı işlemler yapacağız. Aşağıdaki sınıfla veritabanından çekeceğimiz veri için model oluşturuyoruz.
class Product
{
public string name { get; set; }
public string category { get; set; }
public double price { get; set; }
public DateTime enterTime { get; set; }
}
Oluşturduğumuz koleksiyon içine ürün eklemek için MongoCollection
sınıfından bir liste oluşturup Insert
ya da InsertBatch
metodunu kullanabiliriz. InsertBatch()
birden fazla verinin eklenmesini sağlar. AddProducts()
içinde localhost
‘a bağlanmak için gereken işlemlere de yer verdim. Uri
‘ye veritabanı isminizi (bu örnekte test) verdikten sonra koleksiyonlarınıza GetCollection()
metodu ile ulaşabilirsiniz.
static void AddProducts()
{
const string uri = "mongodb://localhost/test";
var client = new MongoClient(uri);
var db = client.GetServer().GetDatabase(new MongoUrl(uri).DatabaseName);
var collection = db.GetCollection<Product>("Products");
var products = new List<Product>{
new Product { name = "potato",
category = "vegetables",
price = 5.32,
enterTime = new DateTime(2015, 09, 14) },
new Product { name = "eggplant",
category = "vegetables",
price = 3,
enterTime = new DateTime(2015, 09, 14) },
};
collection.InsertBatch(products);
}
Bu kısımdan sonra asıl işlemlere yani map – reduce – finalize fonksiyonlarına geçebiliriz. Bu fonksiyonların MongoDB için Javascript ile yazıldığını hatırlatalım. Verilerin analizini price
değeri üzerinden yapmayı seçersek fonksiyonlarımız aşağıdaki gibi şekillenecektir.
string map = @"
function() {
var product = this;
emit(product.category, { count: 1, price: product.price });
}";
map işleminde, emit
fonksiyonu kendisine gelen parametreleri key - value
şeklinde toplamaktadır. Burada key, veriyi neye göre gruplayacağımıza karar verdiğimiz değerken (bu örnekte product) diğer parametre olan value, ürünün fiyatı ve sayısı (1 olmak durumunda) olmaktadır. Farklı düğümler üzerinde topladığımız veriyi ana düğüme aktardıktan sonra reduce fonksiyonuna geçebiliriz.
string reduce = @"
function(key, values) {
var result = {count: 0, price: 0 };
values.forEach(function(value){
result.count += value.count;
result.price += value.price;
});
return result;
}";
Map işleminden sonra bir bütün halinde gönderilen veri analiz edilerek tekrar toplanarak result adında tek bir veri haline getiriliyor. Burada her ürünün fiyatları toplanıyor. Buraya kadar yaptığımız işlemleri daha görsel bir örnekle açıklamaya çalışalım.
apple 3.25
orange 5
melon 2
—————
cucumber 2
eggplant 3
potato 5,32
—————
PC 4000
TV 3400
Yukarıda kategorilerine göre (fruits, vegetables, electronics
..) key – value şeklinde sıralanmış veriler görüyoruz. Bu veri yapısı aynı zamanda toplanmış verinin reduce işlemine hazır olduğu anlamına geliyor. Reduce işlemi sonrası ise şöyle bir yapı ile karşılaşıyoruz;

Görüldüğü gibi ürünler kategorilerine göre gruplanmış ve fiyatlarının toplamı da alınmış bir şekilde gözüküyor. count, price değerlerinin yanısıra average değeri de gözünüze çarpmış olabilir.
string finalize = @"
function(key, value){
value.average = value.price / value.count;
return value;
}";
finalize fonksiyonu isteğe bağlı kullanılan ve reduce işleminden geçen veri üzerinde başka işlemler yapılmak istendiğinde kullanılabilir. Burada her kategorinin fiyat ortalamasını almış oluyoruz. Map, reduce ve finalize fonksiyonlarını yazarak verilerin nasıl toplanacağını ve analiz edeceğini belirtmiş olduk. Ancak neye göre gruplanacağı sorusuna henüz cevap vermedik. Örneğin toplanan fiyat değerinin 100’den büyük olduğu verileri, sadece fruits kategorisindeki verileri ya da giriş tarihi (bu örnekte enterTime) geçen ay ve bugüne kadar olan ürünleri listelemek istediğimizde ne yapacağız?
var mapReduceQuery = Query.GT("price", 100);
var options = new MapReduceOptionsBuilder();
options.SetFinalize(finalize);
options.SetOutput(MapReduceOutput.Inline);
options.SetQuery(mapReduceQuery);
var results = collection.MapReduce(map, reduce, options);
foreach (var result in results.GetResults())
{
Console.WriteLine(result.ToJson()+"\n");
}
mapReduceQuery nesnesi ile çok çeşitli kombinasyonlar yapıp sonuçları değerlendirebilirsiniz. Bu basit uygulama dışında MapReduce işlemlerinin kullanıldığı örneklere Facebook’un kullanıcı davranışı analizlerinde, Amazon’un sunduğu web servis hizmetlerinden Amazon Elastic MapReduce‘u ve Linkedin, Twitter, Yahoo gibi çok büyük verilerle uğraşan şirketlerin yoğun olarak kullandığını verebiliriz. Daha fazla bilgi için MapReduce algoritmasını kullanan açık kaynak kodlu Hadoop platformunu inceleyebilirsiniz.