пятница, 11 сентября 2009 г.

SQLJet 1.0.0 beta

Проект, которым я был практически полностью занят последнее время, SQLJet, достиг стадии beta 1.0.0. Это достаточно амбициозный проект, призванный создать pure Java реализацию механизма баз данных SQLite.

Причина, по которой такой проект имеет смысл, заключается во все более возрастающей популярности использования SQLite в качестве формата данных для приложений. Например, так поступают Firefox и Skype. Но это еще пол беды, настоящая беда в том, что так поступили и Subversion, а это уже, я бы сказал, хуже. Хуже потому, что существует фактически только одна реализация SQLite и сделана она на языке Си, со всеми вытекающими из этого обстоятельства последствиями (как хорошими так и, к сожалению, плохими). Одним из таких достаточно плохих последствий является зависимость от платформы. Иногда такая зависимость становится серьезной проблемой. Особенно остро это чувствуется в случае необходимости доступа к данным в формате SQLite из среды JVM. Да, существуют варианты (я тестировал http://www.zentus.com/sqlitejdbc/) использования SQLite через JNI или даже эмулятор NestedVM. Однако, эти варианты имеют свои недостатки. Поэтому настоящая чистая реализация формата данных SQLite в среде JVM, на мой взгляд, имеет очень серьезные перспективы.

В данный момент SQLJet имеет, как минимум, два очевидных преимущества для использования в качестве pure Java SQLite-engine:

1. Более высокая производительность. Разумеется, по сравнению только с именно pure Java реализациями, из которых в настоящий момент, кроме SQLJet, существует только основанная на NestedVM эмуляция.

Вот вывод запуска простого сравнения производительности на базе тестов JUnit, которые я использовал для оптимизации SQLjet:

[junit] Testsuite: org.tmatesoft.sqljet.benchmarks.SqlJetBenchmark
[junit] Tests run: 7, Failures: 0, Errors: 0, Time elapsed: 11,935 sec
[junit] Testcase: nothing took 0,59 sec
[junit] Testcase: updateAll took 2,509 sec
[junit] Testcase: deleteAll took 0,491 sec
[junit] Testcase: insertRandoms took 2,89 sec
[junit] Testcase: clear took 0,21 sec
[junit] Testcase: locate took 4,534 sec
[junit] Testcase: selectAll took 0,482 sec

[junit] Testsuite: org.tmatesoft.sqljet.benchmarks.NestedVmBenchmark
[junit] Tests run: 7, Failures: 0, Errors: 0, Time elapsed: 62,108 sec
[junit] Testcase: nothing took 1,612 sec
[junit] Testcase: updateAll took 4,001 sec
[junit] Testcase: deleteAll took 1,065 sec
[junit] Testcase: insertRandoms took 4,821 sec
[junit] Testcase: clear took 0,664 sec
[junit] Testcase: locate took 46,961 sec
[junit] Testcase: selectAll took 2,874 sec

2. Возможность создания расширений и специфической обработки данных в среде JVM. При использовании SQLite через JNI или даже посредством NestedVM, при необходимости создания расширений к SQLite Вам придется это делать тоже на Си что не всегда имеет смысл. Именно в этом случае SQLJet открывает полный контроль над обработкой данных - Вам ничто не мешает вызывать любой Ваш код на любой стадии работы с данными. Вы полностью контроллируете все аспекты работы Вашего приложения с Вашими данными. Я считаю, это очень серьезное преимущество, по крайней мере, в некоторых случаях.

P.S. Да, совсем забыл сказать что SQLJet уже реально используется в новом SVNKit (очень хороший продукт кстати, рекомендую).

вторник, 19 мая 2009 г.

Прозрачное перераспределение данных при добавлении нового сегмента (сервера) в шардинге

Почитал довольно интересный материал о масштабировании баз данных. Больше всего понравился метод так называемого шардинга. Это когда большую базу данных разбивают на мелкие части (части соответственно помещаются на отдельные сервера) таким образом, что всегда известно в какой из частей находится необходимая нам запись. Достигается это с помощью некой математической функции от первичного ключа - например, остаток от деления первичного ключа (или его эквивалента, например hash-функции для строк) на количество серверов. Очень хороший метод. Единственный недостаток - при добавлении нового сервера возникает необходимости полного перераспределения данных между частям.

(Пока что это был краткий пересказ прочитанной статьи, дальше идут уже мои собственные идеи)

Вот этот единственный недостаток такого замечательного метода бесконечного масштабирования баз данных меня и озадачил. Я решил попробовать устранить (хотя бы отчасти) именно этот последний недостаток шардинга - необходимость перераспределения данных между частями базы при добавлении новых серверов. И такой способ устранения необходимости перераспределения, мне кажется, существует. Разумеется, он не лишен в свою очередь своих собственных недостатков, но при разумном использовании, я думаю, может принести огромную пользу.

Суть метода очень проста. Работать такой метод может пока только на простых синетических первичных ключах типа "автоинкремент" (ну то есть каждая новая запись имеет некий порядковый номер который больше предудущей записи, это для тех кто не совсем понимает о чем идет речь). Так вот, все что необходимо сделать при подключении очередного нового сервера шардинга, это изменить функцию распределения (вычисления номера сервера от значения первичного ключа) таким образом что после известного нам (а именно последнего перед добавлением нового сервера) значения первичного ключа N и до следующего известного значения M (которое будет равно N + K, где K - количество записей в одном сегменте шардинга), функция
будет вести себя иначе, а именно - будет возвращать не остаток от деления как раньше, а просто номер нового сервера.

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

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

Конечно, это несколько усложняет алгоритм, но не настолько чтобы как то существенно снизить эффективность обработки данных. При росте количества добавленных серверов, алгоритм конечно будет еще более усложняться, но нам ничто не мешает автоматизировать функцию добавления серверов и соответсвующего изменения функции. Более того, мы можем сделать функцию динамической и менять её (функцию) паралельно с выполнением процесса перераспределения. Когда процесс перераспределения будет завершен, мы окончательно возвращаемся к классической функции "остаток от деления на количество серверов", до следующего нового сервера, когда мы опять вводим "исключение из правил" для нового сервера и запускаем процесс перераспределения. Это позволит нам добиться полностью прозрачного процесса наращивания шардинга.

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

Сам процесс перераспределения мы тоже можем разбить на три этапа:
  1. Добавление записей оттуда где они были раньше туда где они должны быть теперь (для всех сегментов) - на этом этапе у нас возникает дублирование записей но это временное явление.
  2. После завершения предыдущего этапа мы можем смело переключаться на новый алгоритм поиска записей - все необходимое у нас для этого уже есть там где нам нужно (мы как раз об этом и позаботились на первом этапе).
  3. На последнем этапе остается просто удалить уже не нужное оттуда где оно было раньше и тем самым устранить возникшее дублирование записей.
И самое интересное, мы можем разбить перераспределение на много мелких последовательных итераций из трех указанных выше стадий цикла: скопировали X записей, модифицировали алгоритм, удалили Х записей, скопировали X записей, модифицировали алгоритм, удалили X записей, ... и так до полного завершения перераспределения.

Надеюсь, я достаточно понятно объяснил свои идеи:) На самом деле, если что то еще осталось не понятно то мы можем рассмотреть базу данных как колоду карт, в которой каждая карта представляет собой запись в базе данных. Шардинг будет представлять собой раскладывание карт на какое либо известное нам количество "кучек". При этом мы раскладываем карты в строго определенном порядке, таким образом всегда имея возможность сразу найти необходимую нам карту в опредленной "кучке" карт. Теперь мы решили увеличить количество "кучек" (то есть добавляем новый сервер) - нам нужно полностью перетасовать все "кучки". На большой базе данных в нагруженной системе, которая должна работать без остановок и сбоев, такая "перетасовка" может представлять собой серьезную проблему. Именно решение этой проблемы "перетасовки" я и пытаюсь предложить.

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

Если вопросы остались, рад буду попробовать на них ответить.

четверг, 14 мая 2009 г.

Squid 2.6 и pppd

Столкнулся недавно с некоторой проблемой - заставить Squid 2.6 "подхватывать" DNS сервера провайдера. Суть проблемы в том что Squid стартует как сервис при загрузке системы, а соединение с провайдером инициируется вручную. Это означает что Squid стартует раньше чем становятся известны адреса DNS серверов.

Проявляется проблема так. Squid пишет в логи следующие сообщения:


2009/05/13 14:53:44| Warning: Could not find any nameservers. Trying to use localhost

2009/05/13 14:53:44| Please check your /etc/resolv.conf file

2009/05/13 14:53:44| or use the 'dns_nameservers' option in squid.conf.



В браузере открыть какой либо сайт соответственно тоже не получается.

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

Основательно порылся в инете и, как ни странно, никакого разумного способа решить такую, казалось бы, стандартную проблему, я не обнаружил. Порылся еще раз - результат тот же, вменяемого решения проблемы нигде не увидел.

Что ж, пришлось подумать самому :) К счастью, решение оказалось очень простым.

Под root'ом, в каталоге /etc/ppp/ip-up.d создаем файл с любым названием, отражающим его предназначение (я назвал его squid ), и следующим содержанием:


#!/bin/sh

squid -k reconfigure


Ставим файлику признак исполняемого файла командой


chmod +x squid


Вот собственно и всё. Работает как часы. Разумеется, дело происходит в Linux'е.