четверг, 19 июля 2012 г.

Пост 9. ACID.

Доброго времени суток!
Итак продолжаю свое повествование об Oracle NoSQL Database. Сегодня хотел бы рассказать о таких понятиях, как ACID и BASE. Итак, начнем с ACID, я уверен, что большинство знает, что это такое, но все же хотел бы повториться.


Atomicity — Атомарность
Атомарность гарантирует, что никакая транзакция не будет зафиксирована в системе частично. Будут либо выполнены все её подоперации, либо не выполнено ни одной. 

Представьте себе простейшую банковскую операцию в которой деньги с одного счета преводятся на другой. Это мининум 2 операции БД - снять деньги с одного счета и пополнить другой. Не камильфо будет если с одного счета будут сняты деньги, а на счет 2 не поступят. Принцип атомарности в этом случае не будет выпонен.

Consistency — Согласованность
Система находится в согласованном состоянии до начала транзакции и должна остаться в согласованном состоянии после завершения транзакции.

Представьте, что у Вас есть система, состоящая их 2х и более узлов, которые реплицируют одну и туже информацию (попросту говоря копируют). Представьте себе, что вы записали некоторые данные на сервер1 и получили подтверждение успешной записи. Практически в тот же момент другой пользователь запросил данные, которые вы только что записали и не получил их... потому что балансировщик отправил его на сервер2 (куда данные еще не успели среплицироваться). Требование согласованности не выполнено.

Isolation — Изолированность
Во время выполнения транзакции другие процессы не должны видеть данные в промежуточном состоянии.


Durability — Долговечность
Независимо от проблем на нижних уровнях (к примеру, обесточивание системы или сбои в оборудовании) изменения, сделанные успешно завершённой транзакцией, должны остаться сохранёнными после возвращения системы в работу.

Другими словами если пользователь внес данные в вашу систему и почил подтверждение о том, что они успешно записаны, они не должны быть потеряны, вне зависимости ни отчего (пропало питание в ЦОД, посыпался диск...).

В противовес концепции ACID стоит концепция BASE.

BASE - Basically Available, Soft state, Eventual consistency

Базовая доступность, неустойчивое состояние, согласованность в конечном счёте
- в принципе доступна
- в принципе знаем состояние
- когда-нибудь сойдется


NoSQL DB - система хамелеон, в зависимости от Ваших пожеланий она может с разной степенью поддерживать любую концепцию парадигмы ACID. Как?
Об этом читайте в последующих постах!

Если у вас есть какие-либо вопросы по Oracle NoSQL - задавайте в комментах, постараюсь ответить, либо пишите на oracle.nosql@gmail.com
Пост 8. CRUD. Delete.

Итак, едем дальше. Создавать записи мы уже умеем. Умеем вытаскивать записи и умеем иъ изменять. Осталось только научиться удалять. Тут все предельно просто.


    public static void DeleteKey(String sKey, String store, String host, String port) {
        OraStore orastore = new OraStore(store, host, port);
        KVStore myStore = orastore.getStore();
        System.out.println("Store Opened");
        Key myKey = ParseKey.ParseKey(sKey);
        myStore.delete(myKey);
        myStore.close();
        System.out.println("Store Closed");
    }

Собственно, добавить мне нечего. От "скуки" я даже немного изменил концепцию - добавив коннект к базе кнутрь метода :) .
Ну и дабы разбавить этот достаточно неинтересный пост (будем объективны), привожу метод, который удаляет всю Вашу базу - очень удобно для разработки.


public static void ClearStore(String store, String host, String port) {
        OraStore orastore = new OraStore(store, host, port);
        KVStore myStore = orastore.getStore();
        System.out.println("Store Opened");

        System.out.println("Cleaning Store ...");

        Iterator<Key> keyIter = myStore.storeKeysIterator(Direction.UNORDERED, 0, null, null, null);
        Key key = null;
        List<String> majorPath = null;
        String majorPathStr = null;
        Hashtable<String, String> majorKeyHash = new Hashtable<String, String>();
        Integer count = 0;
        while (keyIter.hasNext()) {
            key = keyIter.next();
                  majorPath = key.getMajorPath();
            majorPathStr = majorPath.toString();
            if (!majorKeyHash.containsKey(majorPathStr)) {
                majorKeyHash.put(majorPathStr, majorPathStr);
                Key newkey = Key.createKey(majorPath);
                myStore.multiDelete(newkey, null, null);
                System.out.println(++count + " " + majorPathStr);
            }

        } //EOF while
        System.out.println("\nKVStore cleaned.");
        myStore.close();
        System.out.println("Store Closed");
    }

На сегодня наверное все.
Если у вас есть какие-либо вопросы по Oracle NoSQL - задавайте в комментах, постараюсь ответить, либо пишите на oracle.nosql@gmail.com.

суббота, 9 июня 2012 г.

Статья 7. CRUD. Retrieve part2.

Доброго времени суток!
В прошлом посте я рассказал о том как извлекать одну запись из NoSQL базы. В случае одной записи все проще простого. А как быть если я хочу вытащить несколько записей? Как сказал бы анатолий Кашперовский, если хочешь вытащить несколько записаей  - просто возьми их:)
Итак, перед тем как я буду описывать конкретные реализации инструментов извлечения, хотелось бы рассказать о концепции мультиселекта. Вытаскивать можно ключи с различной вложеностью (можно вытаскивать ключ и его детей, внуков итп...) слева на право, но не наоборот (правила как в прямом префиксном индексе).
Например дано:

major1/major2/-/minor1/
major1/major3/-/minor1/
major1/major4/-/minor1/
major2/major2/-/minor1/

Можно извлечь по запросу aka like 'major1%':

major1/major2/-/minor1/
major1/major3/-/minor1/
major1/major4/-/minor1/

Но нельзя извлечь по запросу aka like '%major2%'.
major1/major2/-/minor1/
major2/major2/-/minor1/

Надеюсь, что не очень запутал, а если запутал,то надеюсь что дальше будет понятнее.
В Oracle NoSQL DB на данный момент существует 3 способа извлечения множество строк.

multiGet() - самый прямолинейный способ извлечени данных фетчит все и сразу в память. Испоьзует структуру SortedMap, данные умеет возвразать в отсортированном порядке. Требует полного major ключа. Приведу пример.
Дано:
major1/major2/-/minor1/
major1/major2/-/minor2/
major1/major3/-/minor1/

Можно извлечь значения ключей (like 'major1/major2%'):
major1/major2/-/minor1/
major1/major2/-/minor2/
Но нельзя (like 'major1/%'):

major1/major2/-/minor1/
major1/major2/-/minor2/
major1/major3/-/minor1/

multiGetIterator() - умеет возвращать данные пачками (если достаем оч. много данных, будет не камельфо, если за одну итерацию в память у нас уйдет терабайт данных). Данные умеет возвращать в отсортированном порядке. Так же как и multiGet() требует полного major key.

storeIterator() - умеет доставать записи пачками. НЕ требуется полного major ключа достаточно куска major key. Сортировать не умеет. Мой неготивный опыт работы с этим оператором - он не возвращает строк по полному major пути. Почему не заню, скорее всего не до разобрался. Впрочем workaround прост - используем для этого multiGet().

Для упрощения своей работы я создал 2 метода: SelectWhere и SelectWhereFullMajor(опять же таки привет моему полу-реляционному мышлению).

public static void SelectWhere(String sKey,KVStore myStore) {
        Key myKey = ParseKey.ParseKey(sKey);
        try {
            Iterator<KeyValueVersion> myrecords =
                    myStore.storeIterator(Direction.UNORDERED, 0, myKey, null, Depth.PARENT_AND_DESCENDANTS);
            while (myrecords.hasNext()) {
                Key key = myrecords.next().getKey();
                ValueVersion vv = myStore.get(key);
                Value v = vv.getValue();
                data = new String(v.getValue());
                List<String> majorPath1 = key.getMajorPath();
                List<String> minorPath1 = key.getMinorPath();
                System.out.println(majorPath1 + " - " + minorPath1 + ":" + data);
            }
        } catch (RequestTimeoutException re) {
            System.out.println(re.getTimeoutMs());
        } catch (FaultException fe) {
        } finally {
        }
    }


    public static void SelectWhereFullMajor(String sKey,KVStore myStore) {
        Key myKey = ParseKey.ParseKey(sKey);
        try {
            // Initialize Matrix
            SortedMap<Key, ValueVersion> myrecords = null;
            myrecords = myStore.multiGet(myKey, null, Depth.PARENT_AND_DESCENDANTS, Consistency.ABSOLUTE, 1, TimeUnit.DAYS);      
            for (SortedMap.Entry<Key, ValueVersion> entry : myrecords.entrySet()) {
                ValueVersion vv = entry.getValue();
                Value v = vv.getValue();
                Key myKeyOut = entry.getKey();
                String data = new String(v.getValue());
                List<String> majorPath1 = myKeyOut.getMajorPath();
                List<String> minorPath1 = myKeyOut.getMinorPath();
                System.out.println(majorPath1 + " - " + minorPath1 + ":" + data);
            }
        } catch (RequestTimeoutException re) {
            System.out.println(re.getTimeoutMs());
        } catch (FaultException fe) {
        } finally {
        }
    }


при описании метода мы определяем глубину чтения (только указанные ключи, ключи и дети...) 

Если у вас есть какие-либо вопросы по Oracle NoSQL - задавайте в комментах, постараюсь ответить, либо пишите на oracle.nosql@gmail.com.
Пост 6. CRUD. Retrieve.
Доброго времени суток!
Итак едем дальше! Записывать данные в базу мы воде бы как научились. Теперь хочется научиться читать данные из нее. Ну что же это проще простого:)
Начнем с простого получения одной записи. Используя уже созданные пакеты пишем что то вроде того(прости те мое RDBMS-овское прошлое, не смог удержаться от select =)  ):

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.SortedMap;
import java.util.concurrent.TimeUnit;
import oracle.kv.*;

public class RetrieveNoConnect {

    public static String SelectRow(String sKey, KVStore myStore) {
        data = null;
        Key myKey = ParseKey.ParseKey(sKey);
        try {
            ValueVersion vv = myStore.get(myKey);
            Value v = vv.getValue();
            data = new String(v.getValue());
        } catch (RequestTimeoutException re) {
            System.out.println(re.getTimeoutMs());
        } catch (FaultException fe) {
            System.out.println("Unknown error");
        } catch (NullPointerException ne) {
            System.out.println("Key does not exist");
        } finally {
        }
        return data;
    }
}

Тут комментировать впринципе нечего, но я все же прокомментирую:) Простите уж меня за тофтологию. Итак, сначала мы получаем на вход строчку sKey, по которой хотим получить запись, преобразуем ее в тип Key (тип NoSQL DB). Далее получаем переменную ValueVersion (собственно само значение и его версию). Затем берем только то, что нам надо в данной конктетной ситуации - значение. Результат возвращаем. Вот впринципе и все. В завершении main:

import java.io.FileNotFoundException;
import java.io.IOException;
import oracle.kv.KVStore;
import simpleoperation.*;

public class TestClass{

    static String port = "5000";
    static String host = "localhost";
    static String store = "kvstore";

    public static void main(String[] args) throws FileNotFoundException, IOException, InterruptedException {

        OraStore orastore = new OraStore(store, host, port);
        KVStore myStore = orastore.getStore();
        System.out.print(simpleoperation.RetrieveNoConnect.SelectRow("major1/major2/-/minor1/",myStore));         
        myStore.close();
}

Если у вас есть какие-либо вопросы по Oracle NoSQL - задавайте в комментах, постараюсь ответить, либо пишите на oracle.nosql@gmail.com.

пятница, 1 июня 2012 г.


Пост 5. CRUD.CREATE. BULK INSERT.

Доброго времени суток!

Продолжаем наше знакомство с Oracle NoSQL Database. В предыдущей статье мы рассматривали операции создания записей в базе. А что на счет массовой загрузки? Строго говоря ее нет. Но очень хотелось бы иметь возможность загружать данные из фала (начальное формирование базы, миграция с другой платформы). Нет проблем! Давайте напишем свой метод с маджонгом и гейшами! 
Для начала надо договорить о формате входных данных. Пусть это будет, что то вроде:
major1/major2/-/minor1/minor2/: value

Единственное что стоит заметить (а вы надеюсь это уже заметили), тип value в данном примере только String. 
Для начала дополним класс ParseKey методом:
    
public static Value ParseValue(String keysString) {
        String data = keysString.substring(keysString.indexOf(":") + 1);
        Value myValue = Value.createValue(data.getBytes());
        return myValue;
    }

Отлично! Ну и дополняем класс CreateNoConnect методом згрузки из файла:


    public static void LoadFromCSV(String filepath, KVStore myStore)
            throws FileNotFoundException, IOException {
        List<String> majorPath = new ArrayList<>();
        List<String> minorPath = new ArrayList<>();
        FileReader fr = new FileReader(filepath);
        BufferedReader br = new BufferedReader(fr);
        String s;
        while ((s = br.readLine()) != null) {
            Key myKey = ParseKey.ParseKey(s);
            Value myValue = ParseKey.ParseValue(s);
            myStore.put(myKey, myValue);
            majorPath.removeAll(majorPath);
            minorPath.removeAll(minorPath);
        }
        fr.close();
    }

Осталось только сформировать файл входных данных и выполнить main:

    static String port = "5000";
    static String host = "localhost";
    static String store = "kvstore";

    public static void main(String[] args) throws FileNotFoundException, IOException, InterruptedException {

        OraStore orastore = new OraStore(store, host, port);
        KVStore myStore = orastore.getStore();
        simpleoperation.CreateNoConnect.LoadFromFile("C:\\temp\\1.csv", myStore);
        myStore.close();
 }

Если у вас есть какие-либо вопросы по Oracle NoSQL - задавайте в комментах, постараюсь ответить, либо пишите на oracle.nosql@gmail.com.

четверг, 31 мая 2012 г.

Пост 4. CRUD.CREATE.Начинаем работу с Oracle NoSQL DB.

Пока что хватит теории. Давайте немного попрактикуемся. Oracle NoSQL DB поддерживает CRUD (создать, получить, изменить, удалить) операции (позволю себе отвлечься, лучшего ответа на вопрос,что такое база данных я не видел). Итак, хорошие новости! Для того что бы начать работать с NoSQL базой, нам не потребуется много усилий, затраченых на установку оной. Просто качаем небольшой дистрибутив и запускаем версию light. Распаковываем архив, заходим в папочку в случае винды создаем bat файл со следующим содержанием:
java -jar D:\Distr\Linux\NoSQL\kv-2.0.23\lib\kvstore-2.0.23.jar kvlite
и запускаем его.  Собственно все, база запущена! У меня под виндами это выглядит как то так:

Рис 1. Запуск базы.


Теперь нам понадобится какой-либо инструмент для разработки. В моем случае это будет NetBeans (причина выбора оного - личные предпочтения). Итак, база запущена NetBeans открыт, приступим. Прежде чем выполнять какие-либо операции в БД, надо к ней подключится. Делать это мы будем часто, так что давайте создадим отдельный класс, ответственный за это, который будет входить в пакет simpleoperation.


package simpleoperation;

import oracle.kv.KVStore;
import oracle.kv.KVStoreConfig;
import oracle.kv.KVStoreFactory;
public class OraStore {

    private final KVStore oraStore;
    public OraStore(String sname, String host, String port) {
        KVStoreConfig kconfig = new KVStoreConfig(sname, host + ":" + port);
        oraStore = KVStoreFactory.getStore(kconfig);
        System.out.println("Store Opened");
    }
    public KVStore getStore() {
        return oraStore;
    }
}

Как видно для подключения к базе нам потребуется имя хоста, порт базы и имя хранилища(все прям как в RDBMS).
А в главном методе это будет выглядеть как то так:

public class SomeMainClass{


    static String port = "5000";
    static String host = "localhost";
    static String store = "kvstore";

  public static void main(String[] args) throws FileNotFoundException, IOException, InterruptedException {


       OraStore orastore = new OraStore(store, host, port);
        KVStore myStore = orastore.getStore();
        ................... 
        ................... 
       myStore.close();
     }
}

Так подключаться к базе мы умеем. Давайте попробуем создать класс, который будет создавать записи в нашей БД. В документации приведен вот такой вот пример:


package kvstore.basicExample;
import oracle.kv.Key;
import oracle.kv.Value;
import java.util.ArrayList;
...
List<String> majorComponents = new ArrayList<String>();
List<String> minorComponents = new ArrayList<String>();
...
majorComponents.add("Smith");
majorComponents.add("Bob");

minorComponents.add("phonenumber");
Key myKey = Key.createKey(majorComponents, minorComponents);
String data = "408 555 5555";
Value myValue = Value.createValue(data.getBytes());
kvstore.put(myKey, myValue); 

Как видно в этом примере мы сначала создаем два массива для major и minor части. Затем заполняем их (в итоге получается: Smith/Bob/-/phonenumber/). Потом объявляем переменную типа String, называем ее value и присваиваем ей какое-нибудь значение, далее в переменную типа Value выдавливаем поток байтов из String (да, кстати, не забудте добавить в библиотеку kvclient-1.2.123.jar). Давайте автоматизируем процесс записи (это действительно сумашествие, каждый раз проделывать такие выкрутасы, для записи одной строки). Для этого:

1) Достигнем неких договоренностей, а именно: ключ будем записывать в виде: major1/major2/-/minor1/minor2/
2) Создадим вспомогательные классы и методы.

Начнем с метода, который парсит строчку в фомат ключа, забивает распаршенное в массив и возвращает переменную типа ключ. Это будет выглядеть как то так:


    public static Key ParseKey(String keysString2) {
        int endstring;
        if (keysString2.indexOf(":") != -1) {
            endstring = keysString2.indexOf(":") - 1;            
        } else {
            endstring = keysString2.length();
        }
        String keysString = keysString2.substring(0, endstring);
        List<String> majorComponents = new ArrayList<>();
        List<String> minorComponents = new ArrayList<>();

        String[] keysArray = keysString.split("/");
        boolean isMajor = true;
        for (int i = 0; i < keysArray.length; i++) {
            if (keysArray[i].equals("-")) {
                isMajor = false;
                continue;
            }
            if (isMajor) {
                majorComponents.add(keysArray[i]);
            } else {
                minorComponents.add(keysArray[i]);
            }
        }
        if ((majorComponents.size() > 0) && (minorComponents.size() > 0)) {
            myKey = Key.createKey(majorComponents, minorComponents);
        } else if ((majorComponents.size() > 0) & (minorComponents.size() <= 0)) {
            myKey = Key.createKey(majorComponents);
        } else {
            System.out.println("Error");
        }
        return myKey;
    }

Отлично! Давайте теперь создадим метод с помощью которого мы можем записать пару ключ-значение:


    public static void put(String sKey, String data, KVStore myStore)
            throws FileNotFoundException, IOException {
        Key myKey = ParseKey.ParseKey(sKey);
        Value myValue = Value.createValue(data.getBytes());
        myStore.put(myKey, myValue);
    }

Понятно, что в этом случае записывать возможно только переменные типа String. Пока остановимся на этом, чуть позже я приведу пример с другими типами данных(фото, видео). Да и обработки исключений здесь нет, все это мы добавим позже (слона как говорится надо есть по частям). В методе mail это будет выглядеть как то так:


public class SomeMainClass{

    static String port = "5000";
    static String host = "localhost";
    static String store = "kvstore";

  public static void main(String[] args) throws FileNotFoundException, IOException, InterruptedException {

       OraStore orastore = new OraStore(store, host, port);
        KVStore myStore = orastore.getStore();
        SimpleOperations.Create.put("first/key/-/in/db/","Oracle",myStore);
       myStore.close();
     }
}

В завершении поста хотел бы заметить, что помимо метода put поддерживаются еще 3 схожих метода. putIfAbsent запишет пару только в случае отсутствия подобного ключа в базе, putIfPresent запишет только если указанный ключ существует(аналог update) и,наконец, putIfVersion запишет, если версия пары эквивалентна указанной. Про версии мы поговорим немного позже!

Если у вас есть какие-либо вопросы по Oracle NoSQL - задавайте в комментах, постараюсь ответить, либо пишите на oracle.nosql@gmail.com.

вторник, 29 мая 2012 г.

Пост 3. Архитектура Oracle NoSQL Database.

Доброго времени суток!

В данной статье хотел бы рассказать об архитектуре Oracle NoSQL Database. Oracle NoSQL DB - кластерная база данных (информация хранится на множестве серверов, распределена по принципу партишенинга). Данные задублированы на нескольких серверах (количество копий называется Replication factor). Сервера содержащие одинаковые данные объединяются в Replication  Groups (Shard). Внутри шарда реализована мастер-слейв репликация (пишет один, читают со всех). Если Мастер умирает - кто то из слейвов становится мастером (подробнее про master - slave репликацию можно посмотреть здесь). Чем больше мастеров(читай shard-ов) - тем быстрее система работает на запись.



   Рис. 1. Пример топологии NoSQL базы.

Минимальная еденица данной системы - Storage Node (применительно к картинке выше - один из девяти серверов). Каждый  Storage Node состоит из партиций (помните в самой первой статье говорилось, что на высшем уровне абстракции можно понимать Oracle NoSQL DB как одну огомную таблицу,на самом деле сегментируемую по хэшу от ключа на несколько частей). Секционирование - способ разделения инормации на независимые сегменты. Каждая секция имеет локальный B-Tree индекс. Соответственно, если у нас будет мало секций мы получим большие локальные индексы, а как известно чем больше индекс, тем выше стоимость добавления нового элемента.
Если секций будет очень много - мы получим высокую стоимость накладных расходов на вычисление нужной секции. В общем надо искать компромисс.

Рис.2. Key - Value Store в реляционном мышлении.

Строго говоря, Storage Node - логическая еденица(процесс в ОС), то есть один сервер может включать в себя несколько Storage Node. К примеру, если у нас есть 3 сервера и мы хотим обеспечить 3 репликацию данных - без проблем. Это будет выглядеть как то так:

Рис 3. Пример топологии NoSQL базы. 

Но все же не рекомендуется держать всю базу на одном очень мощном сервере. Идея NoSQL баз в разнесении хранилища на множество дешевых серверов.

Это касаемо топологии хранилища. Теперь хотелось бы сделать одно маленькое, но весьма значительное замечание по поводу ключа. Ключ состоит из 2х частей major и minor (пример: /Smith/Bob/-/foto/, /Smith/Bob - это major составляющая ключа, а /foto/ - minor). Major part + Minor part = Key. Minor составляющей может и не быть. Major составляющая влияет на распределение ключа по партициям (и как следствие по серверам), minor - просто вспомогательная составляющая.

Пример:
Key:  /Mijatovic/foto/                               Value: здесь будет аватар
Key:  /Mijatovic/foto/-/album1/My_dog      Value: здесь будет фотография собаки
Key:  /Mijatovic/foto/-/album1/My_Саt      Value: здесь будет фотография кошки

В первом примере Minor составляющая отсутствует. У всех трех записей одинаковый Major key => все записи попадут в одну партицию (ну и конечно же в одну репликационную группу).

Key:  /Mijatovic/foto/                                 Value: здесь будет аватар
Key:  /Mijatovic/foto/album1/ - /My_dog      Value: здесь будет фотография собаки
Key:  /Mijatovic/foto/album1/ - /My_Саt      Value: здесь будет фотография кошки


В этом случае Major key у второй и третей записи будет отличаться от первой. Скорее всего 1 запись попадет в партицию отличную от партиции 2 и 3 записи. Вот как то так...

Если у вас есть какие-либо вопросы по Oracle NoSQL - задавайте в комментах, постараюсь ответить, либо пишите на oracle.nosql@gmail.com.

Продолжение следует!