четверг, 28 марта 2013 г.

Создаем свой язык в Groovy

Основная проблема императивных языков программирования - их низкая приближенность к естественным языкам.

ООП эту проблему частями решил, упорядочив данные и логику по классам объектов, но все равно это выглядит сложно для понимания. Основная проблема здесь в том, что императивные языки не приспособлены для работы с иерархиями объектов и вложенными вызовами методов.

Например, у меня есть иерархия классов работы с заказами продуктов на товары для клиентов:

// Клиент
class Customer {
    int inn
    String name
    String address
    String phone
}

// Клиенты
class Customers {
    Customer findByInn(inn)
    void add(Customer customer)
}

// Продукт
class Product {
    String article
    String name
    double price
}

// Продукты
class Products {
    Product findByArticle(article)
    void add(Product product)
}

// Заказ
class Order {
    int num
    Customer customer
    List< orderdetail> details = []

    OrderDetail findByPos(pos)
    void add(OrderDetail detail)
}

// Товар заказа
class OrderDetail {
    int pos
    Product product
    def count = 1
    def getSum() { count * product.price }
}

// Заказы
class Orders {
    Order findByNum(num)
    void add(Order order)
}

Сама бизнес логика описания работы будет выглядеть вот так:

// Списки бизнес-сущностей
def customers = new Customers()
def products = new Products()
def orders = new Orders()

// Добавление клиента
customers.add(new Customer(inn: 1234, name: "Клиент", address: "Россия", 
      phone: "+74951002030"))

// Добавление продукта
products.add(new Product(article: "a100", name: "Товар 1", price: 100.00))
products.add(new Product(article: "a200", name: "Товар 2", price: 200.00))

// Добавление заказа
def order = new Order(num: 1, customer: customers.findByInn(1234))
order.add(new OrderDetail(pos: 1, 
   product: products.findByArticle("a100"), count: 1))
order.add(new OrderDetail(pos: 2, 
   product: products.findByArticle("a200"), count: 1))
orders.add(order)

Благодаря изяществу Groovy, код достаточно простой и читабельный. Но и пример не сложный. В реальной жизни, при написании сложной бизнес логики управления объектами, код будет выглядеть громоздко и плохо читаться. Получается, я имею некий API, который управляется только из кода, сложно пишется и нелегко читается.

В Groovy есть возможность упростить свою жизнь, написав собственный декларативный язык разметки для легкого описания выполнения нужных действий. Вот как будет выглядеть на языке разметки аналог вышеописанной бизнес логики:

AddCustomer(inn: 1234, name: "Клиент", address: "Россия", phone: "+74951002030")

AddProduct(article: "a100", name: "Товар 1", price: 100.00)
AddProduct(article: "a200", name: "Товар 2", price: 200.00)

AddOrder(num: 1, customer: 1234) {
 Detail(pos: 1, product: "a100", count: 1)
 Detail(pos: 2, product: "a200", count: 1)
}

Такой код вообще не нуждается в комментариях - он имеет высокую читабельность.
Для реализации этого языка потребуется написать билдер на Groovy. У Groovy есть абстрактный класс BuilderSupport, от которого нужно наследоваться, чтобы создать свой билдер. В наследуемом классе потребуется перекрыть ряд методов, которые Groovy будет автоматически вызывать при разборе языка разметки в коде. Вот как будет выглядеть класс билдера:

public class MyBuilder extends BuilderSupport {
 public Customers customers
 public Products products
 public Orders orders

 // Назначение ноде родительской ноды
 protected void setParent(Object parent, Object child) {
 }

 // Создать ноду без параметров
 protected Object createNode(Object name) {
  if (name != "call") 
   throw new Exception("Node required parameters")
  new Node(null, name);
 }

 // Создать ноду с привязанным к ней объектом
 protected Object createNode(Object name, Object value) {
  throw new Exception("Node required parameters")
 }

 // Создать ноду с параметрами
 protected Object createNode(Object name, Map attributes) {
  // Получаем родительскую текущую ноду
  Node parent = getCurrent()
  def result
  
  // Анализируем имя ноды
  switch (name) {
   case "AddCustomer":
    result = addCustomer(attributes)
    break
   case "AddProduct":
    result = addProduct(attributes)
    break
   case "AddOrder":
    result = addOrder(attributes)
    break
   case "Detail":
    if (parent == null || 
     parent.name() != "AddOrder")
     throw new Exception(
      "Detail must be specified with only AddOrder")

    result = addOrderDetail(parent.value(), attributes)
    break
   defailt:
    throw new Exception("Unknown node ${name}")
  }
  new Node(null, name, attributes, result);
 }

 // Создать ноду с параметрами и привязанным к ней объектом
 protected Object createNode(Object name, Map attributes, Object value) {
  throw new Exception("Node ${name} can not support objects")
 }

 // Добавляем клиента
 def addCustomer(Map params) {
  def customer = new Customer(inn: params.inn, name: params.name, 
      address: params.address, phone: params.phone)
  customers.add(customer)
  println "Added customer ${customer.inn}: ${customer.name}"
  customer
 }
 
 // Добавляем продукт
 def addProduct(Map params) {
  def product = new Product(article: params.article, name: params.name, 
      price: params.price)
  products.add(product)
  println "Added product ${product.article}: ${product.name}"
  product
 }
 
 // Добавляем заказ
 def addOrder(Map params) {
  def order = new Order(num: 1, customer: customers.findByInn(params.customer))
  orders.add(order)
  println "Added order ${order.num} from customer ${order.customer.name}"
  order
 }
 
 // Добавляем строку заказа
 def addOrderDetail(Order order, Map params) {
  def count = params.count?:1
  def detail = new OrderDetail(pos: params.pos, 
      product: products.findByArticle(params.product), 
      count: count)
  order.add(detail)
  println "Added into order ${order.num} detail pos ${detail.pos} " +
    "with product ${detail.product.name}"
  detail
 }
}

В этом классе перекрыто два абстрактных метода setParent и createNode. setParent вызывается при назначении дочерней ноде родителя и в моей логике не используется. А вот в createNode как раз и вызывается на каждый элемент разметки. В зависимости от синтаксиса описания ноды разметки, вызывается один из четырех перегруженных методов createNode. Моей синтаксис предполагает, что у элементов всегда есть параметры. Поэтому я прописал необходимую функциональность в нужный метод и добавил исключения во все остальные методы createNode. Это позволит проконтролировать и исключить неправильный синтаксис описания вызова методов. Единственное исключение было сделано для рутовой метки call, которая автоматически создается первой при запуске билдера без параметров. Класс билдера я расширил конструктором, в который передаются созданные объекты списков клиентов, продуктов и заказов. Так же описал в классе методы добавления бизнес сущностей. Ничего сложного - все замечательно видно в коде и по комментариям в нем.

А вот конечный код пользования созданным языком разметки с проверкой результатов:

// Списки бизнес-сущностей
def customers = new Customers()
def products = new Products()
def orders = new Orders()

// Создать объект билдера
def myApi = new MyBuilder(customers: customers, products: products, orders: orders)

// Вызвать билдер
myApi {
 AddCustomer(inn: 1234, name: "Клиент", address: "Россия", phone: "+74951002030")
 
 AddProduct(article: "a100", name: "Товар 1", price: 100.00)
 AddProduct(article: "a200", name: "Товар 2", price: 200.00)
 
 AddOrder(num: 1, customer: 1234) {
  Detail(pos: 1, product: "a100", count: 1)
  Detail(pos: 2, product: "a200", count: 1)
 }
}

// Результаты
println "\n*** Result ***"
println "Customers:"
println customers
println "Products:"
println products
println "Orders:"
println orders

Результат:
Added customer 1234: Клиент
Added product a100: Товар 1
Added product a200: Товар 2
Added order 1 from customer Клиент
Added into order 1 detail pos 1 with product Товар 1
Added into order 1 detail pos 2 with product Товар 2

*** Result ***
Customers:
{inn=1234, name=Клиент, address=Россия, phone=+74951002030}
Products:
{article=a100, name=Товар 1, price=100.0}
{article=a200, name=Товар 2, price=200.0}
Orders:
{num=1, customer=Клиент, 
 detail={pos=1, product=Товар 1, count=1, sum=100.0};
   {pos=2, product=Товар 2, count=1, sum=200.0}}

Все работает :)

Резюмируя можно сказать, что область применения у билдеров большая. Я, например, сейчас на его основе разрабатываю язык описания трансформации данных для своего open source проекта GETL (ETL на базе Groovy). С помощью билдера можно легко разработать синтаксис, который позволит собирать SQL запросы в коде или выводить информацию в иерархическом собственном форматированном виде. Да и штатные XML/JSON маркеры думаю теперь не представляют из себя тайны. Язык разметок можно использовать не только в коде программ на Groovy, но и как блоки описания объектов и действий, которые вынесены в отдельные файлы. Блоки описания можно прямо в runtime считывать из файлов и выполнять с помощью метода EVAL. Так как блоки хорошо формализованы, то для них можно легко написать собственный GUI разработки бизнес логики обычными пользователями.

Примеров можно привести множество. Но самое главное, не стоит забывать - все вышесказанное замечательно без каких либо усилий работает на Java! Никто не мешает в Groovy обвязать любые Java классы и методы своим языком разметки, на котором писать бизнес логику, которая дальше используется в Java приложениях. Эти возможности стоят того, чтобы начать использовать в Java волшебный Groovy :)

Полный код скриптов классов можно скачать отсюда.

вторник, 26 марта 2013 г.

Выложен билд GETL версия 1.0.1 альфа


Привет.

Двенадцать ударных дней (суток) с момента старта проекта принесли победный результат: GETL перестал быть набором абстрактных идей и недо-классов и сегодня был выложен как уже собранная JAR, готовый к использованию:
https://sourceforge.net/projects/getl/files/

Пусть пока и альфа версия, пусть пока в наличии есть Extract и Load, но нет Transform, но уже в базовом функционале уже многое есть, что можно использовать.

Итак на текущий момент эта версия GETL умеет:
  • Работать с CSV файлами: получать список файлов, удалять файлы, получать схемы полей файлов, читать и сохранять записи, осуществлять поддержку всех типов полей с автоматической конвертацией из текста файла в нужные типы, указывать форматирование значений полей для десятичных типов, даты, времени и булевого типа, проверять при чтении поля на заполнение и уникальность, считывать заданное кол-во записей из источника.
  • Работать с JDBC соединениями: получать список объектов БД, получать поля таблицы, считывать записи таблицы или запроса, сохранять записи в таблицы в пакетном режиме через PreparedStatement, считывать заданное кол-во записей с источника с заданного номера записи.
  • Организовывать многопоточный запуск процессов по указанным элементам списка для организации параллельной обработки данных.
  • Копировать данные из источника в приемник: автоматически маппировать поля источника и приемника по их именам, автоматически конвертировать значений полей источника в значения с учетом типов приемника, выполнять пользовательский код для преобразования и трансформации данных из записи источника в запись приемника.
  • Организовать процессинг обработки записей источника кодом пользователя.
  • Организовать процессинг сохранения записей в приемник, генерируемых кодом пользователя.
Для облегчения и универсализации управления источниками данных был разработан класс Dataset, который позволяет описать метаданные и атрибуты источника, содержит описание полей структуры источника. Имеется возможность указать для Dataset свой код обработки  и изменения схемы данных при ее получении при соединении, а так же код проверки записи при чтении данных. Dataset не привязан к типам соединений, его параметры можно сохранять и восстанавливать в конфигурационных файлах. Для удобства использования поверх Dataset созданы классы CSVDataset, TableDataset и QueryDataset, маппирующие основные параметры, присущие этим типам данных как свойства класса.

Так же для организации соединений разработан класс Connection, который так же не привязан к типам источников данных и универсален. Сам Connection при создании требует указать драйвер типа источника данных, на текущий момент в GETL поддерживаются драйвера CSVDriver и JDBCDriver. Для большего удобства использования поверх Connection созданы классы FileConnection и JDBCConnection.

Для работы с CSV файлами был использован open-source продукт SuperCSV. Он полностью покрыл собой работу с CSV файлами, показав отличную производительность и мне осталось только в CSVDriver прописать его использование. От себя я могу только сказать автору огромное большущее спасибо за гигантскую отлично проделанную работу и сказать, что open source это здорово - я себе не представляю даже, если бы я самостоятельно решил в GETL разработать такого уровня работу с CSV файлами, сколько времени и нервов на это бы ушло.

Для работы с JDBC все конечно проще - JDBC уже полностью покрывает собой базовый функционал работы с РСУБД, поэтому разработанные на его основе драйвер JDBCDriver будет теоретически работать с любым сервером, а практически конечно же это придется все постепенно проверять. Греет мысль, что JDBC достаточно строгий и все реализации под него драйверов более менее работают так, как заявлено в спецификации. В дальнейшем будет достаточно наследоваться от этого драйвера под конкретные СУБД и расширять уже под конкретные реализации серверов такую функциональность, как пакетная загрузка файла, создание таблицы, работа с CDC и прочее. Здесь больших трудностей не видно.

Для оптимизации работы использовались такие возможности Groovy, как динамическая компиляций кода на лету, о чем я писал ранее в блоге. Это используется сейчас в копировании данных при сборке автомаппинга с автоконвертацией, а так же при сохранении в JDBC источники через PreparedStatement с использованием пакетных операций.

Таким образом, базовый функционал есть, а для чтения и записи XML и JSON файлов, с учетом того, что они хранят данные в виде иерархий, логичнее всего использовать базовые возможности Groovy, очень мощные и легкие на уровне написания кода. Ну а записать результат чтения таких файлов в CSV/JDBC или же наоборот записать в XML/JSON записи из CSV/JDBC с помощью GETL занимает пару строчек кода и не требует усилий.

Так же в Groovy есть полная поддержка ANT, на котором можно писать управляемые сценарии работы. Здесь я тоже не вижу смысла писать собственные решения по работе с файлами, FTP или архиваторами, апачевский ANT всегда будет мощнее и круче того, что я напишу. Ну а благодаря Groovy, сценарии для ANT на нем писать легко и просто, поэтому такого рода функциональность считаю полностью охваченной штатными средствами.

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

В качестве небольшой демонстрации работы с GETL, на SourgeForge в файлах выложены примеры, которые можно запустить как консольные приложения через бат-файл, если установлен Groovy.

P.S. И да - весь код исходников GETL несмотря на то, что уже многое умеет, сейчас весит ... 74 килобайта. Конечно тут есть и моя заслуга - грамотное проектирование иерархий классов и архитектуры никто не отменял. Но в первую очередь это заслуга Groovy, к слову сказать если бы я это стал реализовывать на Java, объем исходного кода был бы больше на порядок в абсолютном понимании этого термина. Groovy это круто!

среда, 20 марта 2013 г.

Vertica 24x7

Привет.

Хочу поделится впечатлениями о том, как Vertica себя чувствует при проведении технических работ по замене или добавлению оборудования.

Задача 1:
Заменить один сервер работающий в кластере Vertica на другой (новый сервер).

Решение:
Много возиться нам не пришлось. На новый сервер поставили Линукс, проверили, что все пакеты установлены, которые Vertica в документации перечисляет как обязательные для работы, запустили утилиту Vertica updateVertica, указали ей новый сервер, чтобы она его прописала как свободный для размещения хост - да и все. Запустили GUI AdminTool Vertica и остановили заменяемый сервер. В кластере стало на один сервер меньше, но кластер продолжил работу, из пользователей никто этого не заметил. Там же в GUI указали, что остановленный сервер заменить на новый из доступных хостов. Vertica самостоятельно установила на него свой дистрибутив, проверила настройки Linux, подправила где что не хватало и запустила новый сервер на работу в кластере. Сервер инициализировался, вошел в статус RECOVERY, синхронизировался с другими серверами кластера, закачав на себя все данные, которые хранил его старый предшественник и включился в работу. В итоге Vertica провела замену сервера за 12 часов, переместив на него порядка 2.5 Тб данных, занимаемых на файловой системе.

Задача 2:
Добавить в кластер два новых сервера.

Решение:
Как и в первой задаче установили Linux, через утилиту добавили новые сервера как хосты. Далее в утилите добавили ставшими хостами сервера как ноды кластера. После добавления утилита предлагает на выбор автоматически сделать балансировку данных для равномерного распределения данных между новым количеством нод в кластере, создать SQL скрипт проведения балансировки для его запуска вручную или самостоятельно  запустить балансировку. Я выбрал самостоятельно. Сервера добавились в кластер, включились в работу и стали принимать и обрабатывать запросы. Так как на них пока попадали только новые загружаемые данные, при выполнении запросов к данным, загруженным до добавления этих серверов, они не могли оказать помощь и всю работу с ними продолжали выполнять только сервера, уже стоявшие в кластере. Когда я был морально и по времени готов, запустить самостоятельно балансировку оказалось делом не сложным, было достаточно просто выполнить запрос: SELECT START_REBALANCE_CLASTER(). Процесс запустился в фоновом режиме и перебирая по очереди таблицы стал перемещать данные уже учитывая распределение по новому количеству нод в кластере. Для таблиц, которые создавались как несегментируемые, Vertica просто создавала их зеркальную копию на новых серверах. Для сегментируемых таблиц заново по каждой записи рассчитывался кэш ключа сегментирования и они перераспределялись по новой карте размещения на кластере на нужные ноды. Весь процесс балансировки занял порядка 1.5 суток, было перераспределено порядка 7.5 Тб данных, занимаемых на файловой системе.

Обе успешно выполненные задачи еще раз доказали сильные стороны Vertica - это легкое, практически нулевое администрирование с высоким уровнем автоматизации сложных действий и высокая доступность сервера при любых ситуациях. Будь то большие нагрузки, сбой аппаратной части или технические работы Vertica продолжает работать и обслуживать пользователей без требования проведения каких либо регламентных остановок.

Вот такие чудеса :)

пятница, 15 марта 2013 г.

Немного о Tableau

Привет.

Компания интегратор, которая является представителем Tableau в России, опубликовала историю успеха запуска этого BI в нашей компании. Вот ссылка на статью: https://sites.google.com/site/analytikaplus/about/sucess_history/yota-networks

Самое интересное жалко осталось за бортом: на моих глазах наши специалисты, которые ни разу не являются IT шниками и никогда не рисовали сами отчетов, сели за Tableau, которое увидели в первый раз в жизни и накатали под себя сложные аналитические отчеты, о которых давно и долго мечтали, но реализовать их средствами SAP BusinessObjects это была бы целая песня с заявкой, разработкой и прочими сопутствующими вещами. После этого вопрос о том, нужно ли покупать Tableau в нашу компанию даже не стоял, решение было принято единогласно.

четверг, 14 марта 2013 г.

ETL на Groovy - альфа на горизонте


Привет.

Хорошая новость - проект GETL уже становится похож на работающий прототип продукта. Есть скелет - иерархия классов. Есть и уже немного мышц - полностью поддерживается работа с CSV файлами, есть хранилище промежуточных CSV файлов, позволяющее быстро туда закидывать и потом использовать промежуточные данные. Есть и базовые классы процессов, позволяющие организовать многопоточную работу и настроить движение потока данных с источников в приемники.

В ходе разработки использовались многие интересные моменты Groovy. Самый впечатляющий из них - это динамическая кодогенерация с последующей компиляцией во время выполнения программного кода. Это позволило реализовать такую мощную фишку, как генерация кода трансформации данных на лету. На момент запуска потока движения данных GETL по метаданным источников данных и правилам маппинга и трансформации данных генерирует на Groovy код логики работы, который тут же на лету компилируется в байт код Java и вызывается в процессе движения и преобразования данных. Так как на выходе получается код,  оперирующий напрямую нужными полями и функциями, без проверок и дополнительных операций, это позволяет добиться отличной скорости обработки данных.

Возможность кодогенерации есть и у других ETL. Например, Talend так же генерирует по описанию job код на Java, который уже максимально ориентирован и оптимизирован под выполнение описанных задач. Однако здесь самый важный нюанс: GETL, в отличие от Talend, и других кодогенерирующих ETL является полностью динамическим. Прямо по ходу его работы можно описывать новые структуры или изменять старые и по произведенным изменениям будет и меняться генерирующийся код обработки данных. В Talend это не возможно - если структура источника данных не совпадает с описанной структурой в job, то необходимо изменять этот job в дизайнере и компилировать его перед запуском. Если же структура источника плавающая, то придется отказаться от штатной функциональности Talend и вручную писать собственный код на Java для обработки таких источников.

Ну а если сказать коротко, то GETL в отличие от других кодогенерирующих ETL, позволит создавать шаблоны для job-ов и использовать их для типовой работы над множеством источников с разной структурой данных. Это на порядки сократит разработку загрузчиков данных. И все это без потери скорости обработки данных!

На текущий момент ведется разработка поддержки GETL источников данных JDBC, XML и JSON. После этого будет собран и выложен альфа релиз продукта, а так же опубликован Road Map дальнейшего развития продукта.

До связи!

суббота, 2 марта 2013 г.

Вышел SP Vertica 6.1.1

Всем привет.

Вчера вышел сервис пак для Vertica. Посмотрел релиз, пак закрывает не только баги, но и значительно добавляет оптимизации работы хранилищу и расширяет функциональность. Будем ставить, по идее скорости работы еще должно будет прибавиться и на выполнение запросов и на загрузку данных.

Удачных выходных!

пятница, 1 марта 2013 г.

Краш тест Vertica

Привет.

Недавно в боевых условиях я получил полноценный краш тест сервера Vertica.

Начиналось все хорошо: для работающих серверов докупили и получили новые планки RAM, расширяющие объем оперативной памяти в два раза. Это для нас уже было критичным, так как изначально на серверах стояло не так много памяти. Операция была простой - остановить кластер, всем серверам поменять планки памяти на новые и запустить Vertica обратно. Однако, получилось не все так просто, как планировали: один из серверов после перезапуска и старта всего кластера Vertica, проработав 20 минут выдав множество сообщений в лог по аппаратным ошибкам и перестал работать. Оставшиеся сервера взяли на себя его объем работ и таким образом хранилище данных продолжило оставаться доступным.

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

К сожалению, "убедить" сервер работать с новой памятью не удалось и пришлось его откатить на старые планки. После запуска со старой памятью, сервер подключился к кластеру, вошел в режим Recovery, провел полную синхронизацию данных с тем, что случилось за сутки, пока на нем не работала Vertica и через час успешно стартовал в рабочем режиме, начав принимать и обрабатывать запросы. С учетом того, что за сутки у нас пишется огромный объем информации , считаю это очень хорошим показателем восстановления сервера после краш сбоя.

Новые сервера кстати продолжили работу на новом объеме памяти, хотя раньше в Vertica утверждалось, что сервер при работе будет брать минимальные память и кол-во процессоров из рабочих серверов кластера, если они отличаются по конфигурации. Видимо за прошедший год Vertica здорово шагнула вперед и сняла это ограничение. Это означает, что для апгрейта серверов нам на самом деле даже не нужно было останавливать работу всей Vertica - было достаточно по очереди останавливать один сервер кластера, апгрейтить и запускать его обратно.

Так же я просмотрел в документации информацию по замене серверов в кластере на другие. Для замены сбойного сервера будет достаточно с любого рабочего сервера Vertica указать, на какой сервер нужно инсталлировать ПО, это будет автоматически произведено и далее будет достаточно сказать Vertica стартовать этот сервер в кластер. Автоматически при запуске будет развернута база данных на замещающем сервере и переданы на него все данные, которые ранее содержались на его предшественнике. IP адрес и имя в сети замещающего сервера не обязаны быть такими же, как и у сервера, который заменяется. Все это дает мне возможность при дополнении кластера новыми серверами, один из этих серверов указать подключить вместо сервера, который привел к сбою при работе с новой памятью. Его же можно будет отключить от кластера и отправить на полную проверку аппаратной части без каких либо затрат на его замену.

Не знаю, может это у многих MPP серверов есть такие возможности не прерывая работы базы данных заменять и апгрейтить сервера, добавлять новые или удалять с кластера существующие, но насколько легко и просто это сделано в Vertica, без требования остановок на обслуживание или замедления работы, выглядит очень приятно.

Тьфу тьфу, чтобы Vertica и дальше продолжала нас радовать такими же чудесами.

До связи!