вторник, 25 декабря 2012 г.

Firefox: пишем расширение для браузера

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

Внедерение своего скрипта в тело страницы
//защищенная часть скрипта - оборачиваем в анонимную функцию, чтобы не было повторов имен переменных
(function() {
    var YT_global_doc = undefined;

    var YT_loader = new function(){};
    YT_loader.run = function(e) {
        //проверяем адрес сайта
	var unsafeWin = e.target.defaultView;
	var unsafeLoc = new XPCNativeWrapper(unsafeWin, 'location').location;
	var href = new XPCNativeWrapper(unsafeLoc, 'href').href;
	if (!href.match(/^http:\/\/www\.xxxx\.com\/watch(.*)?$/i) )
	      return;

        //обращаемся к dom сайта
	YT_global_doc = e.target.defaultView.document;

	if(YT_global_doc) {

          //подгружаем наш скрипт
	  var script = YT_global_doc.createElement( 'script' );
	  script.type = 'text/javascript';
	  script.src = 'chrome://xxx/content/yyy.js';
	  YT_global_doc.body.appendChild(script);

	}
    };

    var yt_load = function() {
        //событие загрузки вкладки с сайтом, выполняется каждый раз при загрузке страницы
        window.document.addEventListener("DOMContentLoaded", YT_loader.run, true);
    };
    //событие загрузки приватной части - выполняется один раз
    window.addEventListener("load", yt_load, false);

})();


Правильное создание новых узлов в DOM
Внутренними правилами firefox запрещено обращаться и работать с dom через innerHtml. Взамен этого необходимо пользоваться шаблонами. В своих аддонах я пользуюсь JQuery Templating.
Вот пример:
$("#pv_actions").prepend (
      $("<a>", {
	href: 	"#",
	target: "_blank",
	text:	"Загрузить оригинал",
	id:	"pv_down_link"
      })
      .css("font-weight", "bold")
      .bind("click", function (o) {
	//
      })
);


Взаимодействие с защищенной частью скрипта
Аддон состоит из 2 частей: защищенной и внедренной (небезопасной части). В безопасной части можно работать с апи браузера: сохранять файл, делать ajax запросы и т.д.
Взаимодействовать эти две изолированные части могут только по средствам событий.
Рассмотрим пример: узнать размер файла.
В незащищённой части создаем событие с нашими параметрами.
  //yt_getFileSizeEvent - название события
  var element = document.createElement("yt_getFileSizeEvent");
  element.setAttribute("attribute_url", "адрес до файла" );
  element.setAttribute("attribute_id",  "индекс файла" );
  document.documentElement.appendChild(element);
  var evt = document.createEvent("Events");
  evt.initEvent("yt_getFileSizeEvent", true, false);
  element.dispatchEvent(evt);
В защищенной части ловим событие:
document.addEventListener("yt_getFileSizeEvent", function(e) { YT_secureEvent.getFileSize(e); }, false, true);
После этого обрабатываем и повторяем все в обратном порядке, чтобы передать данные в незащищённую часть скрипта.

суббота, 1 декабря 2012 г.

Советы по оптимизации SQL запросов

Поделюсь опытом, который получил за несколько лет оптимизации sql запросов. Большая часть советов касается субд ORACLE.
Если кому статья покажется слишком очевидной, то считайте это заметкой чисто для себя, чтобы не забыть.

Другие статьи по оптимизации SQL

1. Ни каких подзапросов, только JOIN
Как я уже писал ранее, если выборка 1 к 1 или надо что-то просуммировать, то ни каких подзапросов, только join.
Стоит заметить, что в большинстве случаев оптимизатор сможет развернуть подзапрос в join, но это может случиться не всегда.

2. Выбор IN или EXISTS ?
На самом деле это сложный выбор и правильное решение можно получить только опытным путем.
Я дам только несколько советов:
* Если в основной выборке много строк, а в подзапросе мало, то ваш выбор IN. Т.к. в этом случае запрос в in выполнится один раз и сразу ограничит большую основную таблицу.
* Если в подзапросе сложный запрос, а в основной выборке относительно мало строк, то ваш выбор EXISTS. В этом случае сложный запрос выполнится не так часто.
* Если и там и там сложно, то это повод изменить логику на джойны.

3. Не забывайте про индексы
Совет для совсем новичков: вешайте индексы на столбцы по которым джойните таблицы.

4. По возможности не используйте OR.
Проведите тесты, возможно UNION выглядит не так элегантно, за то запрос может выполнится значительно быстрей. Причина в том, что в случае OR индексы почти не используются в join.

5. По возможности не используйте WITH в oracle.
Значительно облегчает жизнь, если запрос в with необходимо использовать несколько раз ( с хинтом materialize ) в основной выборке или если число строк в подзапросе не значительно.
Во всех других случаях необходимо использовать прямые подзапросы в from или взаранее подготовленную таблицу с нужными индексами и данными из WITH.
Причина плохой работы WITH в том, что при его джойне не используются ни какие индексы и если данных в нем много, то все встанет. Вторая причина в том, что оптимизатору сложно определить сколько данных нам вернет with и оптимизатор не может построить правильный план запроса.
В большинстве случаев WITH без +materialize все равно будет развернут в основной запрос.


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

7. Используйте KEEP взамен корреляционных подзапросов.
В ORACLE есть очень полезные аналитические функции, которые упростят ваши запросы. Один из них - это KEEP.
KEEP позволит сделать вам сортировку или группировку основной выборки без дополнительно запроса.
Пример: отобрать контрагента для номенклатуры, который раньше остальных был к ней подвязан. У одной номенклатуры может быть несколько поставщиков.
SELECT n.ID, MIN(c.ID) KEEP (DENSE_RANK FIRST ORDER BY c.date ASC) as cnt_id
FROM nmcl n, cnt c
WHERE n.cnt_id = c.id
GROUP BY n.ID
При обычном бы подходе пришлось бы делать корреляционный подзапрос для каждой номенклатуры с выбором минимальной даты.
Но не злоупотребляйте большим числом аналитических функций, особенно если они имеют разные сортировки. Каждая разная сортировка - это новое сканирование окна.

8. Гуляние по выборке вверх-вниз
Менее популярная функция, но не менее полезная. Позволяет смещать текущую строку выборки на N элементов вверх или вниз. Бывает полезно, если необходимо сравнить показатели рядом стоящих строк.
Следующий пример отбирает продажи департаментов отсортированных по дате. К основной выборке добавляются столбцы со следующим и предыдущим значением выручки. Второй параметр - это на сколько строк сместиться, третьи - параметр по-умолчанию, если данные соседа не нашлись.
SELECT deptno, empno, sal,
LEAD(sal, 1, 0) OVER (PARTITION BY dept ORDER BY date) NEXT_LOWER_SAL,
LAG(sal, 1, 0) OVER (PARTITION BY dept ORDER BY date) PREV_HIGHER_SAL
FROM emp;
ORDER BY deptno, date DESC;
При обычном подходе бы пришлось это делать через логику приложения.

9. Direct Path Read
Установка этой настройки (настройкой или параллельным запросом) - чтение данных напрямую в PGA, минуя буферный кэш. Что укоряет последующие этапы запроса, т.к. не используется UNDO и защелки совместного доступа.

10. Direct IO
Использование прямой записи/чтения с диска без использования буфера файловой системы (файловая система конкретно для СУБД).
* В случае чтения преимущество в использовании буферного кэша БД, замен кэша ФС (кэш бд лучше заточен на работу с sql)
* В случае записи, прямая запись гарантирует, что данные не потеряются в буфере ФС в случае выключения электричества (для redolog всегда использует fsync, в не зависимости от типа ФС)

11. Оптимизация параллельных запросов
12. Оценка стоимости запроса и построение правильного плана
13. Оптимизация работы секционированных таблиц
14. Индексный поиск
15. Оптимизация запросов вставки
16. Ускорение pl/sql циклов
17. И другое...

четверг, 22 ноября 2012 г.

ORACLE: логирование всех DDL операций.

Вот такой небольшой триггер позволит сохранить все операции DDL, происходящие во всех схемах БД. DDL (Data Definition Language) - описание структур объектов базы.
Триггер выполняется после DDL операции и сохраняет: тип объекта (ora_dict_obj_type), схему (ora_dict_obj_owner), имя объекта (ora_dict_obj_name), имя пользователя , дата операции, тип DDL операции (ora_sysevent), имя компьютера и CLOB с текстом DDL операции (ora_sql_txt).
create or replace TRIGGER bi_sa_psk.trg_ddl_trig_hist
 AFTER DDL
 ON DATABASE
declare
  PRAGMA AUTONOMOUS_TRANSACTION;

  v_ddl CLOB := EMPTY_CLOB;
  li ora_name_list_t;
  l_n number;
BEGIN
  /* sys создает системные временные таблицы для sql запросов */
  IF ora_dict_obj_owner = 'SYS' THEN
    return;
  END IF;

  /* Получим текст DDL */
  l_n := ora_sql_txt(li);
  for i in 1 .. l_n loop
    v_ddl := v_ddl || TO_CLOB(TO_CHAR(li(i)));
  end loop;

  /* запишем историю */
  INSERT INTO bi_sa_psk.ddl_hist(object_type, owner, object_name, USER_NAME, DDL_DATE, DDL_TYPE, COMP_NAME, DDL_TXT, stack)
  VALUES(UPPER(ora_dict_obj_type), UPPER(ora_dict_obj_owner), UPPER(ora_dict_obj_name), ora_login_user, SYSDATE, ora_sysevent, SYS_CONTEXT ('USERENV', 'OS_USER'), v_ddl, dbms_utility.format_call_stack);
  commit;

  /* любые ошибки игнорируем, что бы не завалить БД целиком */
  EXCEPTION WHEN OTHERS THEN NULL;
END trg_ddl_trig_hist;
/
Такая штука будет очень полезна, чтобы вернуть утерянные изменения или отыскать виновного в баге :) .

воскресенье, 9 сентября 2012 г.

Yandex Map: Открытие балуна метки, объединенной в кластер

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

Существует несколько способов обхода этого ограничения:
1. Во время клика, вынести точку из кластера, создать на карте, открыть, после этого вернуть в кластер.
2. Подменять каждый раз балун кластера, при попытке открытия метки.
3. Позиционироваться на кластере, увеличивать карту до максимума, чтобы кластер разложился на точки, после этого открывать балун стандартными средствами.

Я расскажу, как реализовать 3ий вариант, как самый простой.

среда, 30 мая 2012 г.

Хуки на SVN: pre-commit хук на VBS

Хук - это набор команд на определенное действие с репозиторием SVN.

Хуки срабатывают на разные события Subversion, вот некоторые из них:
  • start-commit — запускается до начала транзакции, может быть использован для проверки прав.
  • pre-commit — запускается в конце транзакции, но до commit, часто используется для валидации данных, например для проверки не пустых лог-собщений.
  • post-commit — запускается после транзакции, может быть использовано для отправки e-mail или для резервирования хранилища.
  • pre-revprop-change — запускается до изменений в ревизии, могут быть использованы для проверки доступа.
  • post-revprop-change — запускается после изменений в ревизии, могут быть использованы для отправки e-mail или для резервирования изменений.
  • Есть еще «post-lock», «post-unlock», «pre-lock» и «pre-unlock», как видно из названий он срабатывают при блокировке.

Самый интересный из всех - это конечно "pre-commit". Именно в этот момент мы имеем весь текст транзакции, можем ее обработать или даже изменить.
Хук должен находится на сервере svn в папке "hooks", файл прекоммита называется "pre-commit.bat". Т.е. предполагается, что хук будет на батниках, к которым у меня хроническая нелюбовь. Так что мой батник будет вызывать vbs, а вся логика будет в вбсине.

Часть 1: батник, вызывающий VBS:
SETLOCAL
SET PATH=C:\Windows;C:\Windows\system32;C:\Program Files\VisualSVN Server\bin;
cscript.exe //NoLogo D:\Repositories\TradingSystem\hooks\pre-commit.wsf %1 %2
IF %ERRORLEVEL% EQU 1 GOTO fail
IF %ERRORLEVEL% EQU 2 GOTO fail2

:success
EXIT 0

:fail
echo Введите описание к commit! 1>&2
EXIT 1

:fail2
echo Обнаружено не закрытое подключение к базе! 1>&2
EXIT 1
Где "C:\Program Files\VisualSVN Server\bin" - путь до исполняемых файлов сервера SVN
"D:\Repositories\TradingSystem\hooks\pre-commit.wsf" - путь до запускаемого скрипта VBS
Т.е. роль нашего bat файла, вызвать VBS с параметрами и получить код возврата.

Сам VBS под катом:

воскресенье, 15 апреля 2012 г.

Демон и клиент Gearman

Сегодня пойдет речь о расширении к PHP Gearman.
Исходный код проекта: GearmanDaemon


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

Т.е. у нас есть задача не на 15 секунд, допустим сделать несколько ресайзнутых копии загруженных пользователем фотографии в наилучшем качестве. Если это делать синхронно, то это может занять до нескольких минут. Для устранения проблемы ожидания создано это расширение.

Принцип работы:
  1. Клиент: Получаем данные от клиента (набор фотографии или другое.)
  2. Клиент: Разбиваем данные на группы по какому то признаку
  3. Клиент: Ставим задачи серверам: каждому серверу или разных потокам сервера своя задача
  4. Сервер: Демон сервера, на любом языке, получает новое задание, стартует поток и выполняет задание
В случае асинхронного выполнения, ответ обратно не отсылается. Но если задача выполняется синхронно, просто с разбивкой по потокам или серверам, то можно отправить ответ.

Более подробное описание реализации под катом.