warsawjs-nodejs-and-oracle



warsawjs-nodejs-and-oracle

0 0


warsawjs-nodejs-and-oracle

Prezentacja na WarsawJS#15 "Aplikacja backendowa w Node.js z bazą danych Oracle"

On Github marlic7 / warsawjs-nodejs-and-oracle

#15

Aplikacja backendowa w Node.js z bazą danych Oracle

Mariusz Lichota

18 listopada 2015, Warszawa

o czym będzie

o czym będzie

  • o Node.js
  • o driverach i abstrakcjach dla Oracle
  • o moich doświadczeniach z w/w technologiami
trochę o node.js dlaczego wybrałem noda głównie o driverach bazujących na interfejsie OCI (Oracle Call Interface) trochę opowiem o doświadczeniach zdobytych w 2 projektach s zapytać o: kto ma doświadczenia z Oracle? kto ma doświadczenia z Node.js? kto ma doświadczenia z Oraclem w Node.js?

czym się zajmuję

czym się zajmuję

  • utrzymanie i rozwój Hurtowni Danych
  • rozwijanie aplikacji back office (intranet/ekstranet)
  • SQL, PL/SQL, PHP, JavaScript, HTML, CSS
spagetti -> wzorce MVC -> Ajax -> SPA) PHP jako backend (ZendFramework) JS w frontendzie (SPA)

Ruby Python Java Go Erlang Scala ???

Node.js

PHP: nie zachęca do pisania bibliotek współdzielonych, dla SPA nie dość szybkie odpowiedzi, upodabnia się do Javy (ZF2) ciężkie duże biblioteki, niekonsekwentne API języka (bałagan) alternatywy ?: Ruby, Python, Java, Go, Erlang, Scala

dlaczego Node.js?

Dlaczego zainteresowałem się Node.js

dlaczego Node.js?

  • asynchroniczny (nieblokujące I/O) = wydajność
  • event driven
  • strumienie
  • natywne moduły
  • zawsze demon
  • NPM
  • JIT compiler (V8), Adaptive optimization, Inline expansion
  • JavaScript
potem można odpalić jakieś zdarzenie i wszystkie zarejestrowane słuchacze (listenery) - funkcje callback zostaną wywołane (oczywiście po przejściu event loopa i wg określonej kolejności) operowanie na chunk-ach, mniejsza alokacja pamięci, niezwłoczne rozpoczęcie działań bez czekania na cały zbiór kosztowne operacje można napisać w C++ (koszt przełączania kontekstu) teoretycznie niepotrzebny serwer www, RAM: dane kontekstowe, biblioteki kompletny przyjazny i bardzo podobny jak w Linux-ach (apt-get/dpkg czy yum/rpm) bardzo wydajny, kompilacja do kodu maszynowego przed uruchomieniem (jak JAVA, Ruby) ale z pominięciem bytecodu, PHP 7 (planowany JIT), AO i IE wydajność kosztem większegu zurzycia RAM JavaScript Everywhere!

Asynchroniczny Node.js

jeden wątek z event loop + thread pool obsługujący zlecone zadania I/O i rejestrujący wyniki w callback queue

Wrzesień 2014 - Proof of Concept

pierwsza aplikacja z backendem w Node.js i Oracle

Wrzesień 2014 - Proof of Concept

  • Node.js (Express, oracle, generic-pool, passport, itd...)
  • nodemon
  • forever
  • Nginx
  • Sencha ExtJS (frontend)
  • z głównymi modułami
  • restart procesu noda przy zmianie w dowolnym pliku projektu
  • zapewnia automatyczny start procesu w przypadku awarii
  • jako reverse proxy do backendu + serwowanie statycznego kontentu, terminowanie sesji SSL
  • programowanie w JavaScript, SPA, cała aplikacja ładowana przy pierwszym requescie

npm install oracle

Projekt w Github: joeferner/node-oracle

Joe Ferner (właściciel projektu, główny kontrybutor)
Uwaga 1! Instalacja nie jest tak prosta jak w tytule slajdu, w dalszej części opowiem trochę o instalacji. Uwaga 2! różne nazwy modułów w NPM i projektów w GitHub.

moduł oracle

  • moduł natywny C++ wymagający bibliotek Oracle
  • opublikowany 20.12.2011
  • strumieniowe odczyty/zapisy
  • brak wbudowanej puli połączeń
  • niestabilność przy odczycie CLOB-ów
  • niskopoziomowe API
moduł natywny C++ wymagający bibliotek Oracle opublikowany 20.12.2011 strumieniowe odczyty/zapisy brak wbudowanej puli połączeń niestabilność przy odczycie CLOB-ów niskopoziomowe API

Proof of Concept - wynik

pierwsza aplikacja z backendem w Node.js i Oracle

Marzec 2015 - Nowy projekt

Charakterystyka projektu.

Marzec 2015 - Nowy projekt

  • ponad 40 formularzy
  • eksport danych do Excela
  • import danych z plików txt
  • edycja danych formularzy przez przeglądarkę
  • udostępniona dla ponad 360 firm
  • ok 1000 użytkowników
  • akumulacja sesji w końcówce terminów sprawozdawczych
Put your speaker notes here. You can see them pressing 's'.

Architektura nowego systemu

nginx - static content + reverse proxy do serwisów koncepcja mikroserwisów frontend skompilowany do postaci statycznej serwis generujący Excele (na początku projektu nie było dobrej biblioteki w Node.js) powinno być ActiveDirectory

Marzec 2015 - Nowy projekt - wynik

  • powstały 3 usługi w Node.js v0.10.38 (100% JS)
  • system oddany do testów UAT 3 m-ce od startu
  • uruchomienie produkcyjne 30.06.2015
  • serwer: CentOS 6, 2GB RAM, 2 cory (maszyna VM)
  • system bardzo wydajny i stabilny
  • minimalne obciążenie systemu operacyjnego
Średni czas odpowiedzi requestów JSON z backendu poniżej 50ms Średni czas wygenerowania i odpowiedzi z plikem xlsx ok 1MB poniżej 500ms Ruch nie jest duży ok 30k req / dzień biznesowy (szczytowy)

Drivery Oracle w Node.js

  • nowy ważny projekt - wybór Node.js
  • najpierw wybór drivera - oracledb (mimo wielu braków)
  • decyzja o własnej abstrakcji

Drivery Oracle w Node.js

  • nowy ważny projekt - wybór Node.js
  • najpierw wybór drivera - oracledb (mimo wielu braków)
  • decyzja o własnej abstrakcji

Drivery Oracle w Node.js

  • nowy ważny projekt - wybór Node.js
  • najpierw wybór drivera - oracledb (mimo wielu braków)
  • decyzja o własnej abstrakcji

Instalacja natywnego drivera

  • Oracle Instant Client lub Oracle Client
  • Python (używany przez node-gyp)
  • libaio (Linux)
  • C++ Compiler (GCC, Visual Studio lub inny)
  • Zmienne środowiskowe (OCI_LIB_DIR, OCI_INCLUDE_DIR, OCI_VERSION, NLS_LANG)
Powyższe to ogólne zasady - każda biblioteka ma własną instrukcję (w zależności od systemu operacyjnego)

oracledb w Node.js v4.x.x

oficjalnie jeszcze nie wspierany (jest procesowany PR)

instalacja z forka:
npm install https://github.com/Bigous/node-oracledb.git

wczoraj tj. 17.11.2015 wydany release node-oracledb@1.4.0 Node.js 0.10, 0.12, 4.2 i 5.0

Decyzja o publikacji własnej abstrakcji

https://www.npmjs.com/package/node-dal https://github.com/marlic7/node-dal

Powody budowy abstrakcji

  • brak sensownej abstrakcji w NPM (początek 2015)
  • nie wiadomo, który driver będzie lepszy
  • niskopoziomowe API driverów
  • braki funkcjonalne driverów
  • dodatkowe testy jednostkowe dla driverów
  • pisanie abstrakcji jest fajne pozwala zrozumieć lepiej driver
  • brak - na moment startu projektu
  • w przyszłości
  • np. konieczność każdorazowego zwalniania połączenia (zwrot do puli)
  • stronicowanie, rowset bez strumieni, CLOB-y, CLOB-y bez strumieni, pula bez kolejki
  • dla komunikacji z Oracle, casy używane w aplikacji

Teraz poprzez przykłady omówię wybrane (ważniejsze) problemy, z którymi zetknąłem się realizując 2 projekty.

Przykład 1a. Pobranie pojedynczej wartości (oracledb)

pool.getConnection(function(err, connection) {
    if (err) {
        cb(new Error(err));
        return;
    }
    connection.execute('SELECT SYSDATE FROM DUAL',
                       [],
                       function(err, result) {
        if (err) {
            cb(new Error(err));
            connection.release(function(err) {
                if (err) {
                    cb(new Error(err), {only_log: true});
                }
            });
            return;
        }

        cb(null, result.rows[0][0]);

        connection.release(function(err) {
            if (err) {
                cb(new Error(err), {only_log: true});
            }
        });
    });
});
1. pobranie połączenia z puli (j. błąd to cb i return) 2. wywołanie SQL (j. błąd to cb, zwolnienie połączenia do puli (z obsługą błędu) i return) 3. zwolnienie połączenia do puli i przekazanie wyniku zapytania do głównego cb

Przykład 1b. Pobranie pojedynczej wartości (node-dal)

dal.selectOneValueSql('SELECT SYSDATE FROM DUAL', [],
                      function(err, result) {
    if(err) {
        cb(new Error(err));
        return;
    }
    cb(null, result);
});

lub

dal.selectOneValue('DUAL', 'SYSDATE', [], function(err, result) {
    if(err) {
        cb(new Error(err));
        return;
    }
    cb(null, result);
});
w tej wersji pobieranie połączenia z puli i zwracanie jest transparentne

Przykład 2a. Pobranie wielu wierszy (oracledb)

var oracledb = require('oracledb');
oracledb.maxRows = 100; // (wartość domyślna)
connection.execute("SELECT * FROM employees", [], { maxRows: 1000 },
                   function(err, result) {
    // result.rows.length <= 1000
});
maxRows - maksymalna liczba wierszy pobieranych przez execute() (gdy nie jest używany ResultSet). Wiersze powyżej tego limitu nie są pobierane. globalne zwiększenie tego limitu powoduje większą alokację pamięci na poziomie C++ dla każdego execute()

Przykład 2b. Pobranie wielu wierszy (oracledb)

var oracledb = require('oracledb');
oracledb.prefetchRows = 100; // (wartość domyślna)
connection.execute("SELECT * FROM employees", [],
                   { resultSet: true, prefetchRows: 200 },
                   function(err, result) {
    var allRows = [];

    function fetch() {
        var max = 200;
        result.resultSet.getRows(max, function(err, rows) {
            allRows = allRows.concat(rows);
            if (rows.length === max) {
                fetch();
            } else {
                result.resultSet.close(function(err) {
                    connection.release(function(err) {
                        cb(null, allRows);
                    });
                });
            }
        });
    }
    fetch();
});
przykład bez obsługi błędów - dla przejrzystości resultSet - używać gdy nie wiemy ile wierszy może być zwrócone, albo wiemy ale może się to zmieniać w czasie. Używać gdy potrzeba pobrać dużą liczbę wierszy. Wtedy optymalnie jest wykorzystać przetwarzanie strumieniowe. przykład: fetch 200 wierszy, preparacja wyjścia csv i flush chunk-a do CSV można też bez strumieni, jeżeli wierszy nie ma bardzo dużo i chcemy po prostu pobrać wszystkie (next slide)

Przykład 2b. Pobranie wielu wierszy (node-dal)

dal.selectAllRowsSql("SELECT * FROM employees", [],
                     function(err, result) {
    cb(null, allRows);
});

lub

dal.selectAllRows({
    tbl: 'employees',
    opt: { limit:10, page:5 },
    cb:  function(err, results) {
        cb(null, results);
    }
});
przykład bez obsługi błędów - dla przejrzystości resultSet - używać gdy nie wiemy ile wierszy może być zwrócone, albo wiemy ale może się to zmieniać w czasie. Używać gdy potrzeba pobrać dużą liczbę wierszy. Wtedy optymalnie jest wykorzystać przetwarzanie strumieniowe. przykład: fetch 200 wierszy, preparacja wyjścia csv i flush chunk-a do CSV można też bez strumieni, jeżeli wierszy nie ma bardzo dużo i chcemy po prostu pobrać wszystkie (next slide)

Przykład 3a. Przekroczenie puli (oracledb)

var async    = require('async'),
    oracledb = require('oracledb'),
    cfg  = {
        user:           "username",
        password:       "password",
        connectString:  "localhost/XE",
        poolMax:        10,
        poolMin:        2
    },
    sqls = [],
    i    = 15;

while(i--) { sqls.push('SELECT SYSDATE FROM DUAL'); }

oracledb.createPool(cfg, function(err, pool) {
    var executeSql = function(sql, cb) {
        pool.getConnection(function(err, connection) {
            connection.execute(sql, [], function(err, results) {
                cb(null, results.rows[0][0]);
            });
        });
    };

    async.map(sqls, executeSql, function(err, results) {
        callback(null, results);
    });
});

Wynik: ORA-24418: Nie można otworzyć kolejnych sesji.

przykłady bez obsługi błędów oraz bez zwalniania połączenia do puli - dla przejrzystości Wbudowana pula w driver natywny oracledb nie posiada aktualnie mechanizmów kolejki. Próba pobrania połączenia z puli w systuacji gdy wszystie połączenia pracują, kończy się niepowodzeniem.

Przykład 3b. Przekroczenie puli (node-dal)

var async      = require('async'),
    dalFactory = require('node-dal'),
    cfg        = {
        connection: {
            user:           "testy",
            password:       "testy123",
            connectString:  "localhost/XE",
            poolMax:        10,
            poolMin:        1
        },
        getConnMaxProbes:   100, // times
        getConnWaitMinTime: 200, // miliseconds
        getConnWaitMaxTime: 500  // miliseconds
    },
    sqls = [],
    i    = 15;

while(i--) { sqls.push('SELECT SYSDATE FROM DUAL'); }

dalFactory('oracledb', cfg, function(err, dal) {
    var executeSql = function(sql, cb) {
        dal.selectOneValueSql(sql, [], function(err, result) {
            cb(null, result);
        });
    };

    async.map(sqls, executeSql, function(err, results) {
        callback(null, results);
    });
});

Wynik: [data1, data2, ... , data15]

przykłady bez obsługi błędów - dla przejrzystości brakuje tylko obsługi błędów, w tym przypadku nie trzeba zwracać połącenia do puli

Inne abstrakcje

  • sequelize-oracle (ORM, promise-based, oracledb >= 0.6, 2015.05.21)
  • simple-oracledb (oracledb, 2015-10-15)
  • loopback-datasource-juggler + loopback-connector-oracle (ORM, strong-oracle/oracle, oracledb tylko w branch-u, 2013.04.10)
  • oracle-orm (ORM, oracle, martwy od 2014.06.02)
  • knex (query builder, oracle, 2014.08.13) + bookshelfjs (ORM)
  • sequelize-oracle: bardzo rozbudowany, dużo testów, brak nowych wersji od 25.06.2015
  • simple-oracledb - bardzo podobna filozofia do node-dal, rozszerzenie funkcjonalności oracledb i uproszczenie API
  • 2013.04.10 z oracle, od 2015.01.24 jest branch: feature/oracledb
  • martwe projekty: sails-oracle, node-odp
  • orapool = oracledb + generic-pool

Alternatywne metody - Noradle

Alternatywne metody - Noradle

  • komunikacja bez driverów bazujących na OCI
  • Node.js jako HTTP Gateway
  • Oracle tworzy pulę połączeń TCP do Node.js (UTL_TCP)
  • składowane w bazie procedury PL/SQL są uruchamiane jako servlety http
  • cała logika manipulacji danymi znajduje się po stronie bazy danych
  • brak zależności - tylko JavaScript i baza Oracle
  • spora część dokumentacji po chińsku
  • http://docs.noradle.com/readme.html https://github.com/kaven276/noradle-demo
  • Noradle - ma niezaprzeczalną zaletę: tylko Node i Oracle (nic więcej) - wada: spora część dokumentacji po chińsku
  • https://github.com/doberkofler/node_plsql

Alternatywne metody - node_plsql

  • podejście podobne do noradle
  • alternatywa typu open-source dla mod_plsql
  • wykorzystanie technik OWA i APEX do budowania aplikacji
  • kontent serwowany przez Node.js HTTP Server (express)
  • konieczność kompilacji natywnych modułów (analogicznie jak dla OCI)
  • w moich testach na Ubuntu aplikacja demo działała bardzo wolno

OWA - PL/SQL Web Toolkit APEX - Oracle Application Express

Strojenie Node.js

process.env.UV_THREADPOOL_SIZE = 4; // Node.js default value
process.env.UV_THREADPOOL_SIZE = 10;
thread pool i domyślne 4 wątki, ewentualnie o limicie stack-a

Atwood's Law

Any application that can be written in JavaScript, will eventually be written in JavaScript.

Jeff Atwood, the founder of stackoverflow.com Put your speaker notes here. You can see them pressing 's'.

linki

http://marlic7.github.io/warsawjs-nodejs-and-oracle

Dzięki!

czas na pytania ???

#15 Aplikacja backendowa w Node.js z bazą danych Oracle Mariusz Lichota 18 listopada 2015, Warszawa