Bu yazıda, NoSQL’in ne zaman ve nasıl ortaya çıktığından ve SQL ile farklarının ne olduğundan bahsetmek istiyorum. Bunun yanında aşağıdaki sorulara da cevap aramaya çalışacağız;
- NoSQL’ i Neden Kullanmalıyız?
- Kullanım senaryoları nelerdir ve kimler kullanıyor?
- CAP Teoremi nedir?
Bunların yanında en çok bilinen iki NoSQL veri tabanı olan MongoDB ve Cassandra arasındaki farklarına değineceğim. Örnek bir kullanım için docker üzerinde MongoDB container’ı kurulumu ve Spring Boot üzerinde kullanımını da izah etmeye çalışacağım.
NoSQL Ne Zaman Ortaya Çıktı?
“NoSQL” sözcüğü ilk defa 1998 yılında Carlo Strozzi tarafından kullanılmış. Geliştirdiği ilişkisel veritabanının sorgulama dili olarak SQL’i kullanmadığını belirtmek isteyen Strozzi, açık kaynak kodlu veritabanı için NoSql DB ismini kullanmıştı. Sonradan NoSql ifadesi daha çok “ilişkisel olmayan” anlamında kullanılmış, Strozzi de ilişkisel olmayan veritabanlarının NoSql yerine NoRel(no reletional) şeklinde isimlendirilmesinin daha doğru olacağını belirtmiş.
Günümüzde ise bildiğimiz sql tabanlı veritabanları dışındaki tüm veritabanları için NoSql terimi kullanılır. İilişkisel olmayan, NoRDBMS, NonRel ve Not Only Sql de diğer ifade ediliş şekilleridir.
NEDEN NOSQL VERİTABANI KULLANMALIYIZ?
NoSQL veritabanları, harika kullanıcı deneyimleri sunulması amacıyla esnek, ölçeklenebilir, yüksek performanslı ve yüksek oranda işlevsel veritabanları gerektiren mobil, web ve oyun gibi birçok modern uygulama için idealdir.
Esneklik: NoSQL genellikle daha hızlı ve daha fazla yinelemeli yazılım geliştirmeyi mümkün kılan esnek şemalar sağlar. Esnek veri modeli sayesinde NoSQL veritabanları yarı yapılandırılmış ve yapılandırılmamış veriler için idealdir.
Ölçeklenebilirlik: NoSQL veritabanları genellikle pahalı ve kalıcı sunucular eklenerek ölçeği artırılabilecek şekilde değil, dağıtılmış donanım kümeleri kullanılarak ölçeği genişletilebilecek şekilde tasarlanır. Bazı bulut sağlayıcıları bu işlemleri arka planda, tam olarak yönetilen bir hizmet olarak gerçekleştirir.
Yüksek performans: NoSQL veritabanları, benzer işlevlerin ilişkisel veritabanlarıyla gerçekleştirilmesi ile karşılaştırıldığında daha yüksek performansı mümkün kılan belirli veri modelleri ve erişim desenleri için optimize edilmiştir.
Yüksek oranda işlevsel: NoSQL veritabanları, her biri ilgili veri modeli için özel olarak tasarlanmış yüksek oranda işlevsel API’ler ve veri türleri sağlar.
NoSql db’ler ayrıca yüksek bütçe gerektiren dikey büyümeye müsait ilişkisel veritabanlarının aksine, yatay büyümeye müsaittir.
Dikey büyümeyi, sunucuların cpu, ram, hdd gibi bileşenlerinin yükseltilmesi olarak tanımlayabiliriz. Bunun bir sınırı olduğu ve maliyetinin yüksek olacağı görülebilir.
Yatay büyüme ise, gerekli durumda mevcut sunucuya aynı özellikte(olmayadabilir) yeni bir sunucunun eklenerek, sunucu arttırma işlemi yapılmasıdır. Bu şekilde binlerce sunucu bir arada çalışabilir.
Kullanım Senaryoları ve Kullanan Şirketler
NoSQL veri tabanlarının yeni yeni yaygınlaştığı söylenebilir ancak, e-ticaret, internet arama motorları ve sosyal ağlar gibi büyük ölçekli internet uygulamaları için güvenilirliğini ve performansını ispatlamıştır.
İnternetin en büyüğü Google, verilerini BigTable isimli NoSQL veritbanında yönetmektedir. Amazon’un NoSQL çözümü DynamoDB, Amazon‘un yanısıra Airbnb, Samsung, Toyota vb.. pek çok dünya devi kuruluş tarafından da kullanılmaktadır. Facebook, twitter gibi aynı anda çok sayıda kullanıcının aktif olduğu sosyal ağ platformlarında da petabayt’lık nosql veritabanları kullanılmaktadır.
NOSQL VERİTABANI TÜRLERİ
Key-Value (Anahtar-Değer) : Bilgiler bir anahtar ve karşılığında bir değer şeklinde saklanır. Kullanan sistemler : MemcacheDB, Redis, Dynamo, Riak, Voldemort, WebSphere eXtreme Scale
Document Oriented (Döküman Tabanlı) : Her nesneyi (id’li bir nesneyi) ayrı bir doküman olarak XML, JSON, BSON, Binary, PDF.. gibi formatlarda saklayabilir. Kullanan sistemler : MongoDB, CouchDB, RavenDB, MarkLogic
Column Oriented (Sütun Tabanlı) : Her sütun için değerleri ayrı olarak saklayan ve erişen veri sistemleri. Kullanan sistemler :Hbase, Cassandra, Accumulo
Graph (Çizelge) : Kayıt graph (çizelge) adı verilen bir ilişki yapısında saklanmaktadır. Kullanan sistemler : Neo4J, OrientDB, Allegro, Virtuoso, Sones, Jena, Sesame
CAP TEOREMİ
Consistency(Tutarlılık): Dağıtık bir sistemde bir sunucuya x değeri olarak 5 yazdınız. Aynı sorguyu ağınızdaki başka sunucuya yaptınız ve cevap olarak 5 değerini aldınız. Bu tutarlılıktır.
Availability(Erişebilirlik): Dağıtık her zaman eriştiniz. Hem x değerini yazabildiniz hem de okuyabildiniz.
Partition Tolarance (Bölünebilme Toleransı): Ağınızdaki sunucuların arasındaki bağlantı gittiğinde de çalışmaya devam edebilme.
(CA) İSE NEDEN (P) OLAMIYOR?
Sunucularınızın arasındaki ağ bağlantısı gitmiş. Siz gittiniz birinci sunucuya yazdınız, resimde görüldüğü gibi diğer sunuculara verinin güncel halini gönderemedi. 2nci, 3ncü sunucuya veriyi sorduğunuzda verinin olmadığını veya 1nci sunucu ile aynı olamadığını göreceksiniz. Yani sistem CA olduğu zaman P(Partition Tolerance) olamıyor. Veriniz parçalanmaya toleranslı değil.
(AP) İSENEDEN (C) OLAMIYOR?
2 sunucunuz(server) olduğunu düşünelim X değeri 2 sunucuda da bulunuyor. Sunucular arası ağınız çöktü. Sunuculardan birinde X değerini güncellediniz. Sorguladığınızda iki sunucu ayrı değer vereceği için bu durumda C(Consistent) olamıyor. Bir sunucu X değeri için 5, bir diğeri 4 değerini dönebilir.
(CP) İSE NEDEN (A) OLAMIYOR?
Eğer amacınız hem tutarlılık hem de verinin parçalara bölünerek kaydedilebilmesi ise 3 sunucu arasında bağlantı koptuğu andan itibaren A (Availibility) yazma özelliğiniz ortadan kalkacaktır. Eğer ki yazarsanız tutarlılığı bozarsınız. Bu yüzden sadece okuma yapabilirsiniz bu durumda.
SQL (İLİŞKİSEL) VE NOSQL (İLİŞKİSEL OLMAYAN) VERİTABANLARININ KARŞILAŞTIRMASI
İlişkisel veritabanları | NoSQL veritabanları | |
En uygun iş yükleri | İşlemsel ve güçlü tutarlılığa sahip çevrimiçi işlem gerçekleştirme (OLTP) uygulamaları için tasarlanan ilişkisel veritabanları, çevrimiçi analitik işlem (OLAP) için uygundur. | NoSQL veritabanları, düşük gecikme süreli uygulamaları içeren çeşitli veri erişimi desenleri için tasarlanmıştır. NoSQL arama veritabanları, yarı yapılandırılmış veriler üzerinde analiz için tasarlanmıştır. |
Veri modeli | İlişkisel model, verileri satır ve sütunlardan oluşan tablolar halinde normalleştirir. Tablolar, satırlar, sütunlar, dizinler, tablolar arasındaki ilişkiler ve diğer veritabanı öğeleri bir şema tarafından kesin bir şekilde tanımlanır. Veritabanı, tablolar arasındaki ilişkilerde başvurusal bütünlük uygular. | NoSQL veritabanları, performans ve ölçek için optimize edilmiş anahtar-değer, belge ve grafik gibi çeşitli veri modelleri sağlar. |
Performans | Performans genellikle disk alt sistemine bağlıdır. En üst düzey performans için genellikle sorguların, dizinlerin ve tablo yapısının optimize edilmesi gerekir. | Performans genel olarak temel donanımın küme boyutu, ağ gecikme süresi ve çağrı yapan uygulama gibi etmenlerin birleşimine bağlıdır. |
Ölçek | İlişkisel veritabanları genellikle donanımın işlem kapasitesini artırarak ölçeği artırır veya salt okunur iş yüklerine yönelik replikalar ekleyerek ölçeği genişletir. | Erişim desenleri aktarım hızını artırmak için neredeyse sınırsız ölçekte tutarlı performans sağlayan dağıtılmış mimariyi kullanarak ölçeği genişletebildiğinden, NoSQL veritabanları genellikle bölümlendirilebilen veritabanlarıdır. |
API’ler | Veri depolama ve alma istekleri, yapılandırılmış sorgu diline (SQL) uygun sorgular kullanılarak iletilir. Bu sorgular ilişkisel veritabanı tarafından ayrıştırılır ve yürütülür. | Nesne tabanlı API’ler, uygulama geliştiricilerinin veri yapılarını kolayca depolamasına ve almasına imkan tanır. Bölüm anahtarları, uygulamaların anahtar-değer çiftlerini, sütun kümelerini veya seri hale getirilmiş uygulama nesneleri ve öznitelikleri içeren yarı yapılandırılmış belgeleri bulmasına imkan tanır. |
CASSANDRA VS MONGODB
VERİ ERİŞİLEBİLİRLİĞİ (DATA AVAİLABİLİTY)
MongoDB, birden çok bağımlı düğümü yöneten tek bir ana bilgisayara sahiptir. Ana düğümde bir problem oluşursa, ikincil düğümlerden biri rolünü üstlenir. Otomatik yük devretme stratejisi kurtarmayı sağlasa da bağımlı birimin ana cihaz olması bir dakika kadar sürebilir. Bu süre boyunca, veritabanı isteklere yanıt veremez.
Cassandra ise farklı bir model kullanıyor. Tek bir ana düğüme sahip olmak yerine, bir küme içinde birden çok ana düğüm kullanır. Birden fazla yönetici mevcut olduğunda, herhangi bir kesinti korkusu yoktur. Yedek model, her zaman yüksek kullanılabilirlik sağlar.
ÖLÇEKLENEBİLİRLİK (SCALABİLİTY)
MongoDB: Yalnızca ana düğüm giriş yazabilir ve kabul edebilir. Bu arada, bağımlı düğümler yalnızca okumalar için kullanılır. Buna göre, MongoDB tek bir ana düğüme sahip olduğundan, yazma ölçeklenebilirliği açısından sınırlıdır.
Cassandra: Birden fazla ana düğüme sahip olmak Cassandras yazma yeteneklerini artırır. Bu veritabanının aynı anda çok sayıda yazıyı koordine etmesine izin verir, hepsi kendi yöneticilerinden gelir. Bu nedenle, bir kümede ne kadar çok ana düğüm varsa, yazma hızı (ölçeklenebilirlik) o kadar iyi olur.
VERİ MODELİ (DATA MODEL)
MongoDB‘nin veri modeli, nesne ve belge odaklı olarak kategorize edilmiştir (JSON). Bu, özelliklere sahip olabilen ve hatta birden çok düzey için iç içe geçebilen her tür nesne yapısını temsil edebileceği anlamına gelir.
Cassandra‘ya gelince, daha geleneksel bir model var. Cassandra’nın satır ve sütun kullanan bir tablo yapısı vardır. Yine de her satırın aynı sütunlara sahip olması gerekmediğinden ilişkisel veritabanlarından daha esnektir. Oluşturulduktan sonra, bu sütunlara mevcut Cassandra veri türlerinden biri atanır ve sonuçta daha çok veri yapısına dayanır.
SORGU DİLİ (QUERY LANGUAGE)
MongoDB | Cassandra | |
Selection | ‘db.employee.find()’ | ‘SELECT * FROM employee;’ |
Insert | ‘db.employee.insert({ empid: ‘101’, firstname: ‘John’, lastname: ‘Doe’, gender: ‘M’, status: ‘A’})’ | ‘INSERT INTO employee (empid, firstname, lastname, gender, status) VALUES(‘101’, ‘John’, ‘Doe’, ‘M’, ‘A’);’ |
Update | ‘db.Employee.update({“empid” : 101}, {$set: { “firstname” : “James”}})’ | ‘UPDATE employee SET firstname = ‘James’ WHERE empid = ‘101’;’ |
DESTEKLEYEN PROGLAMLAMA DİLLLERİ
MongoDB: Actionscript, C, C#, C++, Clojure, ColdFusion, D, Dart, Delphi, Erlang, Go, Groovy, Haskell, Java, JavaScript, Lisp, Lua, MatLab, Perl, PHP, PowerShell, Prolog, Python, R, Ruby, Scala, Smalltalk
Cassandra: C#, C++, Clojure, Erlang, Go, Haskell, Java, JavaScript, Perl, PHP, Python, Ruby, Scala
SECONDARY INDEXES
MongoDB, yüksek kaliteli ikincil dizinlere sahiptir. Esnek veri modeli ve ikincil dizinler sayesinde, depolanan bir nesnenin herhangi bir özelliğine erişebilir (yuvalanmış olsa bile).
Alternatif olarak, Cassandra yalnızca ikincil dizin için imleç desteğine sahiptir. Sorguları tek sütun ve eşitlik karşılaştırmaları ile sınırlıdır
ÖRNEK UYGULAMA
MongoDB container kurulumu için docker-compose.yaml dosyası oluşturup, aşağıdaki şekilde yapılandırmamız gerekli.
version: '2' services: mongo: image: mongo ports: - '27017:27017' command: --serviceExecutor adaptive
Konsolda “docker pull mongo” komutunu çalıştırıyoruz.
Sonraki aşamada “docker-compose.yaml” dosyasının olduğu dizine gidip, “docker-compose -f docker-compose.yaml up” komutunu çalıştırıyoruz.
Spring Boot Uygulaması
MongoDB’yi spring içerisinde kullanabimemiz için pom.xml içerisinde dependencies kısma şu eklemeyi yapmamız gereklidir:
org.springframework.data spring-data-mongodb 3.1.0
application.properties dosyasını aşağıdaki şekilde yapılandırmamız gereklidir.
spring.data.mongodb.host=localhost spring.data.mongodb.port=27017 spring.data.mongodb.database=local spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
Class Örneği
@Document(collection = "country") public class Country { private String id; private String name; private String iso; private String phoneCode; }
Repository Örneği
@Repository public interface CountryRepository extends MongoRepository<Country, String> { }
Service Örneği
public interface CountryService { Country create (Country country); Country update (Country country); Country getById (String id); void delete (String id); List findAll(); }
@Service public class CountyServiceImpl implements CountryService { private final CountryRepository repository; public CountyServiceImpl(CountryRepository repository) { this.repository = repository; } @Override public Country create(Country country) { return repository.save(country); } @Override public Country update(Country country) { return repository.save(country); } @Override public Country getById(String id) { return repository.findById(id).get(); } @Override public void delete(String id) { repository.deleteById(id); } @Override public List findAll() { return repository.findAll(); } }
Controller Örneği
@RestController @RequestMapping(value = "/countries") public class CountryController { private final CountryService service; public CountryController(CountryService service) { this.service = service; } @PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) public ResponseEntity create(@Valid @RequestBody Country country, BindingResult bindingResult) { return new ResponseEntity<>(service.create(country), HttpStatus.CREATED); } @PutMapping(consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) public ResponseEntity update(@Valid @RequestBody Country country, BindingResult bindingResult) { return new ResponseEntity<>(service.update(country), HttpStatus.OK); } @DeleteMapping(path = "/{countryId}", produces = MediaType.APPLICATION_JSON_VALUE) public ResponseEntity delete(@PathVariable("countryId") String countryId) { service.delete(countryId); return new ResponseEntity<>(HttpStatus.NO_CONTENT); } @GetMapping(path = "/{countryId}", produces = MediaType.APPLICATION_JSON_VALUE) public ResponseEntity getById(@PathVariable("countryId") String countryId) { return new ResponseEntity<>(service.getById(countryId), HttpStatus.OK); } @GetMapping(produces = MediaType.APPLICATION_JSON_VALUE) public ResponseEntity<List> findAll() { return new ResponseEntity<>(service.findAll(), HttpStatus.OK); } }