четверг, 18 апреля 2013 г.

Пост 24. Добавление новых узлов в кластер. Увеличение колличества шардов.

Доброго времени суток!
Сегодня хотел бы рассказать как происходит процесс добавления новых узлов в кластер  Oracle NoSQL Database. А точнее как происходит наращивание шардов. Это поможет нам решить 2 задачи:
- Увеличит емкость базы данных
- Увеличит пропускную способность на запись (мы помним, что один шард - один мастер, который пишет).

Предположим у нас есть двухшардовая конфигурация состоящая из 6 серверов  (2х3), которая была раздеплоена следующим образом:


kv-> configure -name "FilDB"
kv-> plan deploy-datacenter -name "FilDC" -rf 3 -wait
kv-> plan deploy-sn -dc dc1 -host datanode1 -port 5011 -wait
kv-> plan deploy-admin -sn sn1 -port 5001 -wait
kv-> plan deploy-sn -dc dc1 -host datanode2 -port 5012 -wait
kv-> plan deploy-sn -dc dc1 -host datanode3 -port 5013 -wait
kv-> plan deploy-sn -dc dc1 -host datanode4 -port 5014 -wait
kv-> plan deploy-sn -dc dc1 -host datanode5 -port 5015 -wait
kv-> plan deploy-sn -dc dc1 -host datanode6 -port 5016 -wait
kv-> pool create -name FilPool
kv-> pool join -name FilPool -sn sn1
kv-> pool join -name FilPool -sn sn2
kv-> pool join -name FilPool -sn sn3
kv-> pool join -name FilPool -sn sn4
kv-> pool join -name FilPool -sn sn5
kv-> pool join -name FilPool -sn sn6
kv-> topology create -name topo -pool FilPool -partitions 300
kv-> plan deploy-topology -name topo -wait

Визуально это будет выглядеть как то так:


Если есть желание посмотреть подобное состояние из командной строки надо набрать команду ping из kvadmin:

[root@datanode1 ~]# kvadmin
kv-> ping
Pinging components of store FilDB based upon topology sequence #315
FilDB comprises 300 partitions and 6 Storage Nodes
Storage Node [sn1] on datanode1:5011    Datacenter: FilDC [dc1]    Status: RUNNING   Ver: 11gR2.2.0.26 2013-01-28 12:19:21 UTC  Build id: 99ef986805a3
        Rep Node [rg1-rn1]      Status: RUNNING,MASTER at sequence number: 321 haPort: 8101
Storage Node [sn2] on datanode2:5012    Datacenter: FilDC [dc1]    Status: RUNNING   Ver: 11gR2.2.0.26 2013-01-28 12:19:21 UTC  Build id: 99ef986805a3
        Rep Node [rg1-rn2]      Status: RUNNING,REPLICA at sequence number: 321 haPort: 8200
Storage Node [sn3] on datanode3:5013    Datacenter: FilDC [dc1]    Status: RUNNING   Ver: 11gR2.2.0.26 2013-01-28 12:19:21 UTC  Build id: 99ef986805a3
        Rep Node [rg1-rn3]      Status: RUNNING,REPLICA at sequence number: 321 haPort: 8300
Storage Node [sn4] on datanode4:5014    Datacenter: FilDC [dc1]    Status: RUNNING   Ver: 11gR2.2.0.26 2013-01-28 12:19:21 UTC  Build id: 99ef986805a3
        Rep Node [rg2-rn1]      Status: RUNNING,MASTER at sequence number: 321 haPort: 8400
Storage Node [sn5] on datanode5:5015    Datacenter: FilDC [dc1]    Status: RUNNING   Ver: 11gR2.2.0.26 2013-01-28 12:19:21 UTC  Build id: 99ef986805a3
        Rep Node [rg2-rn2]      Status: RUNNING,REPLICA at sequence number: 321 haPort: 8500
Storage Node [sn6] on datanode6:5016    Datacenter: FilDC [dc1]    Status: RUNNING   Ver: 11gR2.2.0.26 2013-01-28 12:19:21 UTC  Build id: 99ef986805a3
        Rep Node [rg2-rn3]      Status: RUNNING,REPLICA at sequence number: 321 haPort: 8600

kv->


Теперь возникла необходимость увеличить емкость базы данных путем добавления новых серверов.
Нет ничего проще для этого надо выполнить ряд команд в админской консоли (добавление серверов в пул и перебалансировка):

kv-> pool join -name FilPool -sn sn7
kv-> pool join -name FilPool -sn sn8
kv-> pool join -name FilPool -sn sn9
kv-> topology clone -current -name toponew
kv-> topology redistribute -name toponew  -pool FilPool
kv-> plan deploy-topology -name toponew  -wait

После чего начнется перебалансировка партиций (тех самых число которых не меняется).

То есть первоначально я создал 2 шарда и 300 партиций. Они равномерно распределились между партициями. Теперь я добавил 3 шард. Произошла перебалансировка партиций - у каждого шарда осталось по 100 партиций.

Для того что бы убедиться можно дать следующую команду:

kv-> show topology
............................
............................
  shard=[rg1] num partitions=100
    [rg1-rn1] sn=sn1
    [rg1-rn2] sn=sn2
    [rg1-rn3] sn=sn3
  shard=[rg2] num partitions=100
    [rg2-rn1] sn=sn4
    [rg2-rn2] sn=sn5
    [rg2-rn3] sn=sn6
  shard=[rg3] num partitions=100
    [rg3-rn1] sn=sn7
    [rg3-rn2] sn=sn8
    [rg3-rn3] sn=sn9

kv->

Любите технологии, не бойтесь экспериментировать и если у вас появятся какие-либо вопросы касаемо этого поста - не стеснятесь их задавать!
Пост 23. Инсталяция Oracle NoSQL Database.

Доброго времени суток! Наверное давно было пора написать о том как инсталировать NoSQL базу данных производства Oracle. Официальнцую документацию можно найти здесь. Документация написана качественно и основательно и должна являться первоисточником для проверки всех догадок и теорий касаемо процесса инсталяции Oracle NoSQL Database. Здесь же я постараюсь изложить квинтэсценцию своего опыта. Итак, для инсталяции мною были взяты 10 серверов. 9 из них были отданы под базу данных, 1 под сервер приложения и management сервер (прим. на этих серверах так же установлен hadoop - отсюда и названия: cdh, datanode1...).

На подготовительном этапе вам предстоит сделать следующее:
1) Обеспечить беспарольный ssh между серверами кластера (не обязательно - но удобно с точки зрения администрирования). В интернете оооочень много статей на данный счет я обычно делаю так:

создаю файл servers.list в который помещаю IP всех серверов с которыми я хочу установить беспарольное соединение, а затем использую нехитрые команды, представленные ниже:

# ssh-keygen -t dsa -P '' -f ~/.ssh/id_dsa 
# cat ~/.ssh/id_dsa.pub >> ~/.ssh/authorized_keys
# cat ~/servers.list | while read i ; do ssh-copy-id -i ~/.ssh/id_rsa.pub root@"$i" ; done

2) Скачать и установить pdsh.
3) Я создал для простоты работы я создал в /usr/bin/ файл pdsh_hdfs (как упоминалось ранее здесь так же установлен hadoop, отсюда и название), следующего содержания:
#!/bin/bash
pdsh -w root@192.168.1.[110-120] $*

Теперь команда pdsh_hdfs применяется ко всем серверам кластера.

Теперь мы готорвы к процессу инсталяции:

1) Скачиваем дистрибутив Oracle NoSQL Database и раскидываем его на все сервера 
2) Распаковываем его на всех серверах и создаем переменную окружения $KVHOME, которая указывает на папку с распакованным дистрибутивом (в моем случае на всех серверах это было 
/home/oracle/nosql/kv-2.0.26).
3) Если у нас была инсталяция до этого "подчищаем" ее:
for i in `seq 1 9`; do ssh datanode$i rm -rf $KVHOME/kvroot; done;
for i in `seq 1 9`; do ssh datanode$i "pkill -f 'java -jar'"; done;
4) Создаем папки на всех серверах для базы данных:
do ssh datanode$i mkdir $KVHOME/kvroot; done;
5) Oracle NoSQL Database очень чувствительна к временному расхождению внутри серверов кластера. Если расхождение больше 2 секунд - база данных не раздеплоится.
Используем команду ntpdate, для синхронизации. Я засунул в crontab временую синхронизацию:
 pdsh_hdfs "echo "*/1 * * * * ntpdate ntp.server.my.company.com" >> /var/spool/cron/root"

Прим.: ntpdate не работает поверх proxy (как было в моем случае) - поэтому я использовал ntp server моей компании. Если же есть непроксеванный интеренет - можно использовать публичные ntp сервера.

6) Далее необходимо описать начальную конфигурацию базы данных (создать bootconfig) на каждом из серверов.
Один сервер сделаем admin сервером (т.е.с него мы в последствии и будем осуществлять админские операции):

ssh datanode1 "java -jar /home/oracle/nosql/kv-2.0.26/lib/kvstore.jar makebootconfig -root /home/oracle/nosql/kv-2.0.26/kvroot -host datanode1 -port 5011 -admin 5001 -harange 8100,8101 -config conf1.xml"; done;

7) Создаем конфиги на всех остальных серверах:
for i in `seq 2 9`; do ssh datanode$i "java -jar /home/oracle/nosql/kv-2.0.26/lib/kvstore.jar makebootconfig -root /home/oracle/nosql/kv-2.0.26/kvroot -host datanode$i -port 501$i -harange 8${i}00,8${i}01 -config conf1.xml"; done;

Здесь мне следует остановится и дать некие пояснения по поводу портов указанных в конфигах:
registry port - основной порт, на который "садится" база и по которому она доступна для клиента.
Admin Console port - порт для админконсоли.
HA Range - порты для коммуникации между собой (hand for "replication) узлов кластера.

Эта команда создает конфиг, который будет использоваться на следующем шаге:
<config version="2">
  <component name="params" type="bootstrapParams" validate="true">
    <property name="adminHttpPort" value="0" type="INT"/>
    <property name="hostingAdmin" value="false" type="BOOLEAN"/>
    <property name="storeName" value="FilDB" type="STRING"/>
    <property name="storageNodeId" value="4" type="INT"/>
    <property name="memoryMB" value="2091" type="INT"/>
    <property name="hostname" value="datanode4" type="STRING"/>
    <property name="numCPUs" value="2" type="INT"/>
    <property name="rootDir" value="/home/oracle/nosql/kv-2.0.26/kvroot" type="STRING"/>
    <property name="haPortRange" value="8400,8401" type="STRING"/>
    <property name="registryPort" value="5014" type="INT"/>
  </component>
  <component name="mountPoints" type="bootstrapParams" validate="false">
  </component>
</config>

8) Стартуем на всех серверах экземпляр, используя конфиг, созданный на предыдущем шаге:
for i in `seq 1 9`; do ssh datanode$i "nohup java -jar /home/oracle/nosql/kv-2.0.26/lib/kvstore.jar start -root /home/oracle/nosql/kv-2.0.26/kvroot -config conf1.xml &" && echo $i ; done;

9) Теперь у нас есть материал для конфигурации базы данных (нечто вроде пластилина из которого мы будем лепить что то), создании топологии

Если зайти на datanode1:5001 (админский порт - можно увидеть "пустую" топологию).

Теперь приступим к конфигурации нашей БД. 
Тут у меня будет просьба к читателю пожалуйста освещите в памяти архитектуру БД Oracle NoSQL и понятийный аппарат, который будет использоваться
Из 9 серверов "так и хочется" создать базу данных состоящую из трех шардов с репликационным фактором 3 (шард будет содержать по 3 копии данных).
Сказано - селано.

10) Переходим на сервер, который был скорфигурирован как admin (в моем случае это datanode1)
[root@cdh ~]# ssh datanode1
Last login: Thu Apr 18 13:50

Здесь я создал alias, позволяющий мне одной командой запускать admin консоль:
alias kvadmin='java -jar /home/oracle/nosql/kv-2.0.26/lib/kvstore.jar runadmin -port 5011 -host datanode1'

[root@datanode1 ~]# kvadmin
kv->

11) Создаем базу данных:
kv-> configure -name "FilDB"

12) создаем дата центр, указываем репликационный фактор:
kv-> plan deploy-datacenter -name "FilDC" -rf 3 -wait

13) Создаем storage node, после чего на ней же деплоим админ сервис:
kv-> plan deploy-sn -dc dc1 -host datanode1 -port 5011 -wait
kv-> plan deploy-admin -sn sn1 -port 5001 -wait

14) Создаем остальные storage node
kv-> plan deploy-sn -dc dc1 -host datanode2 -port 5012 -wait
kv-> plan deploy-sn -dc dc1 -host datanode3 -port 5013 -wait
kv-> plan deploy-sn -dc dc1 -host datanode4 -port 5014 -wait
kv-> plan deploy-sn -dc dc1 -host datanode5 -port 5015 -wait
kv-> plan deploy-sn -dc dc1 -host datanode6 -port 5016 -wait
kv-> plan deploy-sn -dc dc1 -host datanode7 -port 5017 -wait
kv-> plan deploy-sn -dc dc1 -host datanode8 -port 5018 -wait
kv-> plan deploy-sn -dc dc1 -host datanode9 -port 5019 -wait

Заглянем после этого в нашу web админ-консоль:


Видим, что "пластелин" приобрел более конкретные очертания:)

15) Создаем пул серверов и "закидываем" туда все 9 нод

kv-> pool create -name FilPool
kv-> pool join -name FilPool -sn sn1
kv-> pool join -name FilPool -sn sn2
kv-> pool join -name FilPool -sn sn3
kv-> pool join -name FilPool -sn sn4
kv-> pool join -name FilPool -sn sn5
kv-> pool join -name FilPool -sn sn6
kv-> pool join -name FilPool -sn sn7
kv-> pool join -name FilPool -sn sn8
kv-> pool join -name FilPool -sn sn9

16) После этого создаем топологию, где указываем колличество партиций (которое не может быть изменено), а так же пул серверов, который бует использоваться.

kv-> topology create -name topo -pool FilPool -partitions 300

17) Конечная команда, котрой развертываем наш пул:

kv-> plan deploy-topology -name topo -wait

Теперь еще раз заходим в web консоль и смотрим, на топологию нашей базы:
Ура! База инсталирована. Если будут вопросы - пишите! В дальнейшем мы проведем несколько экспериментов!

среда, 17 апреля 2013 г.

Пост 22. Версионность в Oracle NoSQL Database. Тесты.

Решил я вот вернуться на 2 поста назад и написать еще про версионность. Давайте посмотрим на то как ведет себя Oracle NoSQL база при обработке версионных исключений.
Конкретный пример - инкрементальное увеличение счетчика.
Допустим у нас есть своя социальная сеть. И мы хотим знать сколько пользователей нашей соц. сети существует. Заводим специальный ключ /user/count/ и при добавлении нового пользователя инкрементально увеличиваем его на 1. Достаем старое значение, увеливаем на 1, записаваем. Сказано - сделано. Написали для этого целый класс:

package test;

import java.io.FileNotFoundException;
import java.io.IOException;
import oracle.kv.KVStore;
import CRUD.Create;
import CRUD.Delete;
import Support.OraStore;
import CRUD.Retrieve;
import oracle.kv.Durability;
import oracle.kv.Value;
import oracle.kv.ValueVersion;

public class test_version_badcode {
static String[] hhost = {"localhost:5000"};
static String store = "kvstore";
public static void main(String[] args) throws FileNotFoundException, IOException, InterruptedException {
OraStore orastore = new OraStore(store, hhost);
KVStore myStore = orastore.getStore();
for (int i = 1; i < 10001; i++) {
ValueVersion vv = CRUD.Retrieve.SelectRowVV("user/count", myStore);
Value v = vv.getValue();
String cntr = new String(v.getValue());
int num_users = Integer.parseInt(cntr) + 1;
CRUD.Create.put("user/count", Integer.toString(num_users), myStore);
}
myStore.close();
System.out.println("Store closed");
}
}


И запустили метод main доторый "добавляет" 10 000 пользователей в 2х разных сессиях, ожидая увидить 20 000 в итоге.

И о "чудо" их 15796... Думаю уже на 25 строчке кода вы подумали что в хорошем обществе так делать не принято. Дейсвительно одна сессия "затирала" результаты другой. Давайте договоримся, что так как показано в примере выше вы делать никогда не будете.
Для решения этой задачи создадим метод который вытаскивает Value и Version по ключу:

public static ValueVersion SelectRowVV(String sKey, KVStore myStore) {

Key myKey = ParseKey.ParseKey(sKey);
ValueVersion vv = null;
try {
vv = myStore.get(myKey);
} 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");
}
return vv;
}

Так же метод на основе putIfVersion. На вход мы будет передавать версию только что прочитанной пары. Метод будет пытаться сделать Update записи. Если версия поменялась (после чтения другая сессия изменила значение по этому ключу), метод выдаст ошибку (вернет строку состояния операции):
public static String UpdateIfNotchange(String sKey, String data, Version OldVersion, KVStore myStore)
 throws FileNotFoundException, IOException, InterruptedException
{
retdata = null;
Key myKey = ParseKey.ParseKey(sKey);
Value myValue = Value.createValue(data.getBytes());
try {
Version NewVersion = myStore.putIfVersion(myKey, myValue, OldVersion);
if (NewVersion == null) {
System.out.println("Operation Failed! Vesion error");
retdata = "Operation Failed";
} else {
System.out.println("Record created in the kvstore.");
retdata = "Record created in the kvstore";
}
} catch (NullPointerException np) {
System.out.println("KV pair doesn't exist!");
}
return retdata;
}

Имея все это великолепие методов мы можем написать финльный класс, которым протестируем работу версионности в NoSQL (подобно первому случаю):

package test;
import java.io.FileNotFoundException;
import java.io.IOException;
import oracle.kv.KVStore;
import CRUD.Create;
import CRUD.Delete;
import Support.OraStore;
import CRUD.Retrieve;
import oracle.kv.Durability;
import oracle.kv.Value;
import oracle.kv.ValueVersion;

public class test_version {
static String[] hhost = {"localhost:5000"};
static String store = "kvstore";

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

OraStore orastore = new OraStore(store, hhost);
KVStore myStore = orastore.getStore();
String retcode;
for (int i = 1; i < 10001; i++) {
retcode = "Operation Failed";
while (retcode.equals("Operation Failed")) {
ValueVersion vv = CRUD.Retrieve.SelectRowVV("user/count", myStore);
Value v = vv.getValue();
String cntr = new String(v.getValue());
int num_users = Integer.parseInt(cntr) + 1;
retcode = CRUD.Update.UpdateIfNotchange("user/count", Integer.toString(num_users), vv.getVersion(), myStore);
}
}
myStore.close();
System.out.println("Store closed");
}
}

Запускаем 2 параллельные сессии каждая из которых будет добавлять по 10 000 пользователей. Переодически в вывод программы будут падать ошибки:

Operation Failed! Vesion error
Record created in the kvstore.
Record created in the kvstore.
Record created in the kvstore.
Record created in the kvstore.
Operation Failed! Vesion error
Operation Failed! Vesion error
Operation Failed! Vesion error
Operation Failed! Vesion error
Record created in the kvstore.
Record created in the kvstore.

Ничего страшного - после этого exception произойдет повторная попытка чтения update.
В итоге после окончания работы этих 2х методов по ключу /user/count/ мы можем найти ожидаемые 20 000. Ура!

Если будут вопросы - пишите!

пятница, 12 апреля 2013 г.

Пост 21. Large Object в Oracle NoSQL Database (LOB).

Доброго времени суток!
В Oracle NoSQL DB версии 2 появилась поддержка Large Object.

Когда то совсем давно я писал, что Oracle NoSQL – хорошая платформа для хранения больших файлов: видео и аудио, например. Но вот как то получилось так, что я не рассказал как это делать.
Во-первых, давайте договоримся о том какой объект считать большим более 1 Мб (так рекомендует Oracle). LOB объект хранится как набор chunk-ов (сами chunk могут быть разного размера).
Во-вторых, давайте посмотрим как записывать LOB в базу. Давайте запишем некоторый файл.
По традиции создадим метод:

    public static void putLob(String sKey, String pathToFile, KVStore myStore)
            throws FileNotFoundException, IOException {
        final Key myKey = ParseKey.ParseKey(sKey);
        File lobFile = new File(pathToFile);
        FileInputStream fis = new FileInputStream(lobFile);
        Version version = myStore.putLOB(myKey, fis, Durability.COMMIT_WRITE_NO_SYNC, 5, TimeUnit.SECONDS);
    }

Прим.: префикс ключа LOB должен заканчиваться на .lob (например: test/image/1.lob).
Прим2.: файлы для тестов удобно генерить командой dd
Например, dd if=/dev/zero of=big.file bs=100M count=20 - сгенерит 2 Гб файл.

Чтение происходит по chunk (порциям). В ходе чтения может поменяться сервер чтения chunk.
Давайте возьмем программу из документации, которая читает lob файл и выводит колличество байт:

    public static int SelectLOB(String sKey, KVStore myStore) throws IOException {
        data = null;
        int byteCount = 0;
        Key myKey = ParseKey.ParseKey(sKey);
        try {
            InputStreamVersion istreamVersion =
                    myStore.getLOB(myKey, Consistency.NONE_REQUIRED, 5, TimeUnit.SECONDS);
            InputStream stream = istreamVersion.getInputStream();
            while (stream.read() != -1) {
                byteCount++;
            }
        } 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 byteCount;
    }


Запускаем чтение, при этом у нас открыта консоль мониторинга (dstat -tdn 1) всех трех машин из шарда.
Смотрим и видим:


Чтение началось с одной машины, а продолжилось с другой. Т.е. при чтении несколькими агентами данных NoSQL страхует нас от "перекоса" в пользу какого-либо из шардов.

Любите технологии, пытливого ума вам, ну а если будут вопросы по Oracle NoSQL - welcome!

Пост 20. Версионность в Oracle NoSQL Database.

В Oracle NoSQL DB есть понятие версии значения. Версия всегда возвращается методу который осуществляет запись в базу KVStore.put() (и ему подобных: putIfpresent, putIfabsent…) и методам, которые получают значение по ключу.

Когда идет операция изменения или удаления бывает очень важно выполнить ее только в том случае, если значение не изменилось. В NoSQL есть следующие методы KVStore.putIfVersion() or KVStore.deleteIfVersion(). В принципе добавить к этому больше нечего – привожу пример кода, который из меняет пару ключ – значение, только в том случае если значение не изменилось (например у нас есть некий счетчик который инкрементально увеличивается при каждом событии, а события могут идти в несколько потоков). Вот здесь метод putIfVersion придется как никогда кстати. Давайте создадим еще один медод, который будет выполнять все вышеописанное.
Метод назовем UpdateIfNotchange – изменять, если не изменилосьJ

public static void UpdateIfNotchange(String sKey, String data, KVStore myStore)
throws FileNotFoundException, IOException {

Key myKey = ParseKey.ParseKey(sKey);
Value myValue = Value.createValue(data.getBytes());
try {
ValueVersion vv = myStore.get(myKey);
Version OldVersion = vv.getVersion();
Version NewVersion = myStore.putIfVersion(myKey, myValue, OldVersion);
if (NewVersion == null) {
System.out.println("Operation Failed! Vesion error");
} else {
System.out.println("Record created in the kvstore.");
}
} catch (NullPointerException np) {
System.out.println("KV pair doesn't exist!");
}
}


Как то так. Если будут вопросы - welcome!