ermouth: (ang)

Написал за ночь сегодня логгер для следующеего релиза CloudWall, сейчас потестил – оч шустро вышло. Написал заметочку в корпоративной Стене и решил сюда кросспостнуть.

Под катом инженерные соображения про логгер для браузерной системы.

ExpandКлик чтобы раскрыть... )

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

Справочно: скорость записи в localStorage небольших фрагментов (десятки килобайт) колоссальна: на моём i7 – сотни мегабайт в секунду в любом браузере. Это многократно превосходит среднюю скорость сериализации в JSON, например.

Коллеги, что я забыл про важные фичи лога?

ermouth: (Default)

У Гугла есть отличный сервис – PageSpeed Insights. Сегодня посравнивали dvinanews.ru – новостной ресурс местного правительства – с флагманами типа rbc.ru, lenta.ru, meduza.io и с несколькими менее значимыми.

Моя прекрасная FEED CMS уделывает по десктопной версии их всех )

Снимок экрана 2015-06-25 в 22.12.26

По мобильной версии – всех, кроме Медузы. У Медузы – 100 из 100 по удобству и чуть больше, чем у Двинаньюса, по коду.

Снимок экрана 2015-06-25 в 22.12.42

Сравнение с новостными сайтами правительств других субъектов РФ можно даже не приводить – их Двинаньюс опережает с бооольшим отрывом. Включая регионы с дикими бюджетами, типа Татарстана. И даже kremlin.ru – правда, всего на один балл.

От гордости просто разрывает ) Редчайший так то случай, когда правительственный ресурс опережает коммерческие проекты по качеству инженерного исполнения. Про архитектуру, админку и всякое такое я писал вот тут ermouth.livejournal.com/604509.html

Скоро кста откроем новостной ресурс ещё для одного субъекта, красивенький.

И да, если у уважаемых читателей из других регионов РФ есть на примете СМИ или пресс-центры с хорошей посещаемостью, в которых работают нормальные ребята, а инженерное/дизайнерское исполнение убогое – дайте им ссылочку на этот пост.

Месяца за три можно любой пресс-центр превратить в конфетку – причём мы не только в инженерном плане сделаем по-хорошему, но и редакцию круто подтянем по уровню.

И это не зарубежной выделки платформа, FEED сделан в России )

UPD. КДПВ вот придумалась )

feedvsother

ermouth: (ang)
Последнюю неделю я практически рыл носом землю, чтобы понять точно, что же вызвало потерю данных. Нашёл и зафайлил кучу багов разной степени тяжести в PouchDB, но ручное прикрытие каждого так и не давало ответа, в чём же в самом деле причина.

Апофеоз истории вот тут https://github.com/pouchdb/pouchdb/issues/3961. В результате я написал тест, который имитирует, что происходит в cloudwall без запуска cloudwall – но оказалось, что этот тест никто кроме меня не может воспроизвести. А у меня он железно воспроизводился на двух моих Маках в 100% случаев – но только для двух доменов, для остальных доменов всё было ок. Я даже стал подозревать в какой-то момент, что зелёные человечки – не выдумка (шучу :)

Ключик в решению проблемы увиделся, когда я слил свои файлы IndexedDB из Хрома разработчику PouchDB, и он не смог воспроизвести баг даже с ними. Просто ничего больше не осталось, как грешить на файловую систему – всё остальное было к тому моменту тщательнейшим образом проверено и отброшено.

Так и оказалось – на обоих Маках у меня были сбои на FS. На эйре, видимо, деградировал SSD, а на iMac – HDD, который часть Fusion Drive. Именно поэтому баг и не проявлялся, пока не закроешь вкладку в Хроме – браузер закрывал файл, а файловая система его обнуляла из-за ошибок в бинарном дереве каталога.

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

Из этого надо вынести несколько уроков:

  1. БД ни при каких обстоятельствах не должна считать нижележащую технологию надёжной. А так считают и PouchDB, и IndexedDB и, увы мне, CloudWall.

  2. При обнаружении багов с потерей данных идти надо начинать снизу вверх, а не сверху вниз. Это тот-же ровно принцип, как и в случае с проверкой розетки, питания и предохранителей в первую очередь, когда «компьютер сломался». Я как-то не соотносил эту народную мудрость с высокими технологиями, а напрасно.

  3. Баг – лучший учитель. Я за последнюю неделю досконально разобрался, как устроен PouchDB  в части репликации, внешних ajаx-запросов и взаимодействия с IndexedDB. Попутно разобрался со схемой запросов к IDB по-хорошему (ну и гадость, доложу я вам), с производительностью IDB, с тем, как устроены файлы каталогов в HFS+, и много ещё чего попутного по мелочи.

Такие дела. Извинился перед чуваками за наезд – хотя он бесспорно был небесполезен. Я ясно вижу, что они меняют и тесты, и сам подход.

FEED CMS

May. 20th, 2015 09:42 pm
ermouth: (Default)

Оформили в первом приближении нашу новостную CMS в продукт. Вот уж не думал, не гадал. Клик по картинке – на сайт в первой итерации.

Снимок экрана 2015-05-20 в 21.16.31

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

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

FEED специально заточен под такие задачи – организацию высоконагруженного непрерывного потока информации лентами. Под статические сайты он не подходит – будет очень ограничивать и дорого стОит для статики.

Самое старое боевое внедрение FEED за два года отдало 5+Тб страничек и ни разу не упало, чихнуло разве что пару раз. Есть ещё одно внедрение, на котором в течение полугода менялись один за другим студенты и кому ни попадя права админов раздавались. Там такое с системой вытворялось, что даже я был удивлён, как её можно выкрутить – и тоже не разу не упала, но крепко, правда, спотыкалась.

Всё целиком, от первой до последней строчки, javascript. Весь бэкэнд и мобильная версия – jQuery.my-приложения. Исторический эскизик вот, с которого всё началось:

image

Всё немного посложней в жизни вышло, но примерно принцип понятен, как оно работает.

С Днём Рождения, FEED )

ermouth: (Default)

Отличный материал про промисы, от самого активного контрибутора PouchDB.

http://pouchdb.com/2015/05/18/we-have-a-problem-with-promises.html

Там хороший разбор, особенно для тех, кто до сих пор по-старинке коллбэками пишет. Куча примеров, ошибки разобраны – в том числе достаточно частые:

Снимок экрана 2015-05-19 в 19.09.24

Вообще, систематическое применение промисов порождает вот такой, например, код:

Снимок экрана 2015-05-18 в 19.08.45

Снимок экрана 2015-05-18 в 19.09.49

Это реальный код из cloudwall.me, слева – старт приложения, справа – старт системы. По читаемости с коллбэками не идёт ни в какое сравнение, конечно. Нельзя сказать то же о скорости и прожорливости, но всё не так плохо.

http://spion.github.io/posts/why-i-am-switching-to-promises.html

Так что да, главная проблема с промисами – что их готовить не умеют. С функционалом всё более-менее устаканилось.

ermouth: (Default)

C целью “потрогать” arrow-функции в ES6 написал вот парсер выражений в польской нотации, простенький. Работает в консоли FireFox или в io.js, больше нигде не работает.

var polish = (function () {
  var ops = "+-/*".split("").reduce((a,b)=>(a[b]=Function("x","return x[0]=x.shift()"+b+"x[0],x"),a),{});
  return (s)=>s.split(/\s+/).reduce((a,b)=>(ops[b]?ops[b](a):!isNaN(b)&&b!=''?(a.unshift(+b),a):a),[]);
})();

polish ("10 1 2 3 + + *") напишет [60].

Пожалуй, я уже хочу стрелки в js. До этого как-то они мне не родными для js казались – ну и зря.

ermouth: (Default)

Наткнулся только что в кейноте по новым предложениям для javascript.

Снимок экрана 2015-04-22 в 3.10.50

Это значит, что мы можем расшаривать данные между workers, со всеми плюшками. То-есть теперь в js есть threads, ну, или совсем скоро будут.

Многопоточность в C++ понимании в JS-мире нужна очень редко (да и в остальных мирах, по-хорошему, тоже) – но зато когда она нужна, без неё туго.

Это, например, вещание потоковых стримов из воркера нескольким (торрент)-слушателям через WebRTC или аналогичные применения. Это разделяемые мемкэши в веб-серверах. Это навороченные игры.

Игры от меня далеко, а вот два других применения – очень даже мне близки.

Обходные манёвры на чистом JS сейчас превращаются или в медленные, или жутко прожорливые решения (а обычно и медленные, и прожорливые). Ну, кажется, теперь всё станет по-другому.

---

Вот хорошо бы ещё пионэры JS-комьюнити смотрели и в сторону Erlang, а не только Emscripten/C++.

Atomic CSS

Apr. 20th, 2015 10:00 pm
ermouth: (Default)

21 января 2013 я написал про технику мнемонических CSS-правил, которую использую достаточно давно и нахожу исключительно удобной.

Оказывается, эту же технику использует Yahoo под названием Atomic CSS. Там прекрасный флейм в комментах кста.

Техника эта – когда мы пишем кучу правил типа .mt10 {margin-top:10px} или .fl {float:left} – особенно удобна, если сделать набор генерализованных правил большим, с частой сеточкой.

В CloudWall и системах, на нём построенных (общий объём выдачи с таких систем – 600К страниц в месяц есличо) объём таких правил в CSS-файлах – примерно 30Кбайт. Это 10Кб гзипом, меньше маленькой картиночки.

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

В самом деле наилучший вариант – обе техники сочетать.

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

То-есть, даже очень сложные приложения требуют относительно скромных кастомных CSS-стайлшитов в стиле БЭМ – если подставить под БЭМ более “низкоуровневую” технику типа Atomic CSS.

В реальности это даёт вкусные каврижки. Скажем, для довольно сложного мобильного приложения весь CSS-код укладывается в 80 строк:

Снимок экрана 2015-04-20 в 21.45.51

Типичный объём CSS для таких приложений – полтысячи строк и больше, и конечно при БЭМ подходе чистоганом хрен их встроишь куда-то. А тут оно что на мобиле, что во всплывайке, что виджетом на странице одинаково работает и выглядит.

ermouth: (Default)

Мы все привыкли, что в javascript-коде, особенно клиентском, можно особо насчёт RAM не париться. Автоматические сборщики мусора уже очень хороши во всех браузерах и прекрасно переваривают даже существенно корявый код и сложно зацикленные указатели.

Но есть одно исключение, когда памятью в js нужно управлять прямо – то-есть делать allocate-deallocate кодом, а не полагаться на автомат. В полный рост это исключение проявляется в noBackend приложениях.

Жизнь без пермалинков

Дело в том, что для бинари-ресурсов, хранимых локально в браузере, не существует пермалинков. То-есть, если надо вставить картинку, то src="http://url.com/image.jpg" написать не получится – нет у картинок урла.

ExpandRead more... )
ermouth: (Default)

Перепозиционировал это всё как noBackend OS, с известной долей иронии каэш. Теперь и на гитхабе – https://github.com/ermouth/cloudwall

Снимок-экрана-2015-04-08-в-1.37.50 

Главные фичи

Оно теперь опенсорц. Установить можно на любой статический хостинг прямо из .tgz, там только html + js + json + css.

Исправлено куча багов, апгрейд библиотек сделан, примерчики добавлены, приложения обновлены. Ещё я там на заглавной выложил унылый шестиминутный скринкаст как за час сделать, отладить и задеплоить приложение для организации распределённых дискуссий. Целых 4кБ длиной приложение, ога.

И – тадам – это всё можно форкать и править/пересобирать форк самого CloudWall прямо в браузере. То-есть можно скачать себе в браузер исходники, это примерно так выглядит:

Снимок экрана 2015-04-08 в 1.38.45

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

Important notice

Если кто-то юзал или смотрел cloudwall.me до этого, не забудьте обновить системные приложения. Как войдёте – серая кнопка Check updates в левой панели внизу.

И да, информация о документе и всякие copy/delete теперь – через клик по иконке документа.

Сделано в CloudWall

CloudWall в текущей версии полностью написан и собран в CloudWall, только иконки не в окошке браузера нарисованы. Сам сайт cloudwall.me тоже управляется прямо из CloudWall.

Вообще, у меня лично внутрибраузерная боевая БД перевалила за 400 мегов. Полёт прекрасный.

---

Хвалите, поздравляйте, ставьте, пробуйте. На планшетах кста норм работает, да.

ermouth: (ang)

Год назад я запустил первую версию cloudwall.me. Жэжэшечка у меня тогда была закрыта, так что написал я об этом только в мае и в Medium.

clouds-09

В тот момент cloudwall.me работал на PouchDB 1.4 (это такая локальная NoSQL БД в браузере). Тогда она была дико глючная и медленная. За год PouchDB стараниями комьюнити доросла до 3.3 и стала вполне стабильной. Среда исполнения приложений на jquerymy за год тоже здорово подросла и в плане скорости, и в плане надёжности.

Плюс добавился IDE, плюс ещё всякие редакторы и приложения, плюс я сайт jquerymy переделал.

Короче, сейчас cloudwall.me – простейшая браузерная ОС, которая для работы не требует инет-соединения. Год назад я говорил (и думал), что ОС эта совсем игрушечная – но, похоже, что не такая уж и игрушечная.

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

И контракт этот – на разработку софта на платформе cloudwall.me. Offlline-ready, все дела. Интересно, что контракт сам меня нашёл.

Что дальше

CloudWall система уникальная в том плане, что её исходники не существуют в виде файлов.

Текущая версия CloudWall (платформы) создана внутри него самого, исходники – это JSON-документы в локальной браузерной БД. Синхронизированной, конечно, с удалёнными репликами CouchDB.

Сам сайт cloudwall.me – статически слинкованный – это один документ CouchDB с 32 аттачами. То-есть, чтобы запустить CloudWall у себя, как свой сайт, достаточно поставить CouchDB и скопировать в неё один-единственный документ. Больше ничего делать не надо – оно сразу заработает само.

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

А дальше у меня есть соображения, откуда могут проявиться интересные деньги. Потому что если к этому прикрутить CoverCouch и сделать централизованные профили пользователей, получается распределённая ОС с безопасностью, пригодной для корпоративного сектора.

Что-то типа MS Active directory + DFS получается, только в браузере и для запуска js-приложений. Ну и без дикого сопутствующего гимора как в Винде. И клиенты по определению однопользовательские.

Но сначала я раскрою cloudwall. Скоро на гитхабе, да.

ermouth: (Default)

Новое модное веяние, во многом – просто красивое название для концепции distributed render, которая у меня оформилась в середине 2012. Дополнительно к distributed render это ещё и быстрая синхронизация локальных данных клиента и сервера, максимальная согласованность того, что в UI и того, что в главной БД.

Ну и главная фишка – более-менее единый код для клиента и для сервера.

Я с этими штуками за 3 последних года вдоволь наэкспериментировался вдоль и поперёк – просто не называл это isomorphic javascript. В этой связи у меня оформилось несколько стойких соображений.

Изоморфность, когерентность и диффы

Изоморфные веб-приложения – это, конечно, такая серебряная пуля. В идеале – отображение удалённой БД на UI, работающее в обе стороны в реальном времени.

Самая главная беда у нас по пути – канал связи. Мы заранее не знаем какой он у нас будет – и это главное соображение, почему серебряной пули не случится. Когда приложение исполняется локально, скорость отображения изменений UI на подлежащий документ известна и она очень высока.

Когда мы отображаем UI на remote документ, мы рискуем получить либо невероятные задержки в UI, либо ссылочную некогерентность данных UI и сервера.

Простой пример. У нас есть чатик, который дописывает узлы в какой-то док по мере появления реплик. Пусть он их передаёт diff-ами, например.

Мы начинаем передавать в чатик 10 картинок по 10 мегабайт каждая. Добавляем их в UI – и хотим написать реплику со ссылкой на одну из картинок.

Если мы требуем ссылочной когерентности – реплика не сможет быть отправлена в БД до того, как туда попадут картинки. Это, конечно, так себе чатик будет.

То-есть требование ссылочной когерентности отвалилось на простейшем юз-кейсе. Ну или нам надо сделать такую БД в которой два произвольных апдейта всегда коммутируют – что-то не видать таких, ога.

Итого: изоморфность – наверное, когерентность – видимо нет.

Декомпозиция задачи

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

Мы, в идеале, хотим иметь такую среду исполнения, приложения которой:

  1. можно использовать на клиенте как UI для модификации подлежащего документа,
  2. можно использовать на сервере как валидатор,
  3. можно использовать на сервере как параметризуемый шаблон, генерящий HTML,
  4. не должны заботиться, где вообще находится документ и его реплики.

Ну и мы подразумеваем, что у нас приложение – это одинаковая сущность и для клиента, и для сервера.

Условие №1, клиентский UI – чисто здравый смысл. Приложения – они для людей.

Условие №2, валидатор из приложения – самое главное. Валидация приложением – это когда мы можем взять документ, взять код приложения и не выполняя его в части UI определить, мог ли документ быть получен нормальной работой этого аппа в соответствующем окружении.

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

С точки зрения математики каждый валидный документ приложения образует неподвижную точку для функции, которая “вычисляет” приложение над документом.

С точки зрения пользователя это эквивалент операций Открыть >> Сохранить, после которого документ не должен измениться, и по пути не должно возникнуть никаких ошибок. Вот только всё это надо иметь возможность делать без UI.

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

Условие №3. Приложение как параметризуемый шаблон. Это считается главной серебряной пулей, чуть не самой желанной фичей. Дескать чтобы вот передать на сервер URL – а он тебе HTML-снапшот приложения в нужном состоянии.

Или не передавать – а попросить с сервера данные и отрендерить приложение на клиенте, но чтобы то же самое получилось.

Это серебряной пулей считается напрасно, хотя усилия по реализации предприняты в целом колоссальные. Это примерно так же переоценено, как responsive layouts. Да, иногда нужно, и даже удобно – но если всё так делать, то это очень ограничивает.

Я вот тут развиваю мысль, почему это в общем случае делать незачем. Главная мысль – потребитель всех этих усилий в итоге гугл, а не люди. А гуглу нужны данные и ссылки, а не состояния вашего приложения.

То-есть не надо смешивать две задачи:

  1. Максимально эффективно скормить гуглу вашу базы.
  2. Иметь на сервере функцию, которая может вам прислать HTML-снапшот вашего приложения в любом его состоянии.

Первая задача стОит, чтобы её решать, вторая – ну её нафиг.

Условие №4. Среда исполнения должна сама выбирать стратегию распространения изменений по уровням кэша (локальная БД >> удалённая БД или несколько удалённых БД, пиринговая доставка другим юзерам, дайджесты на емэйлы и тп).

Ну или как минимум давать максимально удобно конфигурировать параметры стратегии.

Для приложения все эти детали должны быть скрыты, у него одна операция – сэйв, и один ивент – внешний апдейт документа.

Я такую конструкцию в части API к БД смог довольно быстро собрать – но в целом оно довольно неудобно. С одной напрягает бардак с promise. С другой – с памятью.

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

Серверные API, напротив, просто обязаны давать возможность обрабатывать данные экономно с точки зрения монопольного занятия CPU и памяти надолго.

Это, вообще говоря, требования противоречивые (ситуация кста касается и пункта №2 про валидатор).

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

Потому что эти 5 мегов придётся загружать в каждую серверную сессию при валидации данных этого приложения, пришедших удалённо. Можно какие угодно предпринимать усилия по дедупликации, и всё равно найдётся такая задача, где стратегия дедупликации поломается и сервер начнёт жрать память и размножать данные по сессиям.

Итого

Изоморфные в полный рост приложения – это довольно нишевый продукт, потому что сейчас делать и владеть ими дорого, поддерживать сложно и все имеющиеся решения очень громоздки.

Что-то простое из них можно собрать просто и коротко, а вот сложное – получается непременно хромота с какой-нибудь стороны.

Конечно, кто-то непременно решит всю задачу целиком и красиво, но это всё равно не будет серебряной пулей – есть определённые области, где подход точно не применим.

Вообще, думаю, тут не обойдётся без появления нового языка (или следующей итерации JS) – и он будет ближе к Erlang по компоновке кода и соглашениям, чем к JS.

ermouth: (Default)

Интерпретатор Лиспа на яваскрипте, объемом в 1Кб. Он не совсем Лисп на входе кушает – там некоторое очень близкое подобие Лиспа (AST в нативном JS формате), но всё равно нереально круто.

https://github.com/kanaka/miniMAL/blob/gh-pages/stepB_web.js – исходник. После упаковки чем-то типа jscrush оно превращается вот в такое:

Снимок экрана 2015-03-02 в 0.16.35

У меня получилось через jscompress+jscrush ужать полный исходник (с макросами и обработкой ошибок) до 1.5Кб с первой же попытки, так что думаю эту реализацию можно сделать ещё короче.

Яваскрипт великий язык, да.

ermouth: (ang)

Мне сегодня на Stackoverflow попался изумительной красоты вопрос про javascript. Я, как обычно, невнимательно его прочитал и ответил немного не в кассу, ну, меня быстро поправили.

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

Задача: нужно сконструировать такую функцию add, которая делает вот так

add(1) >> 1
add(1)(2) >> 3
add(2)(3)(4) >> 9

Цепочка вызовов может быть любой длины, в скобках только числа.

Сначала может показаться, что задачка нерешаема. Потом – что нам нужно локально расширить объект Number какой-то конструкцией, которая позволит вызывать Number как функцию. Это можно сделать – но получается очень громоздко. В самом деле надо идти с другой стороны – делать такую функцию, которая при попытке её с чем нибудь сложить кастится в число.

Любопытно, что если переделывать Number получается практически то-же самое, просто значительно длиннее и от самого намбера там ничего не остаётся.

Сделать функцию, которая при попытке её с чем-нибудь сложить “прикидывается” числом совсем просто.

Дело в том, что любая функция в js – объект. У неё есть метод toString, который и управляет кастингом. Собственно, он и вызывается неявно, когда вычисляемое выражение требует привдения к примитивному типу. Если toString наследуется от прототипа – он вернёт исходный текст функции. Но мы можем его подменить – и он станет возвращать число.

Хак целиком выглядит вот так:


var add = (function() {
    var factory = function(value) {
        var fn = function(num) { return factory(value + num) };
        fn.toString = function() {return value};
        return fn;
    };
    return factory(0);
})();

Офигенно, по-моему, с эстетической точки зрения. На каждом вызове функции возвращается новая функция, которая скрыто каррирована значением предыдущего шага и которая прикидывается числом, если её закастить в примитивный тип.

Такой очень хитрый итератор.

Использовать в жизни я бы такое, правда, не посоветовал – оно mind-blowing, медленное и хрупкое. Как и всё, что красивое и бесполезное )

ermouth: (ang)
Задачка с эмуляцией JS-энджина CouchDB внутри моей волшебной библиотечки решилась в одно соображение. Там суть задачи – воссоздание для JS-функции довольно необычного scope, такого же, как внутри CouchDB. Сложность в том, что часть этого scope – это «системные» функции CouchDB, а часть – итераторы, зависящие от параметров конкретного запроса и результата работы map-функции.

«В лоб» задача решается просто – в каждом запросе к reduce делаем кусок исходника со всеми этими функциями и превращаем это все в исполняемый код рефлектором. Это очень просто, но совсем плохо по скорости и памяти, да и вообще довольно стрёмно. Первый же подход к снаряду показал, что это тупик – это примерно полгода назад было, и я задачку отложил.

А потом я как-то вспомнил про curried constructor в Scala. Сам по себе аналог в js никак специально не называется, но мне подумалось, что если этот концепт немного дополнить, то выходит как раз нужный мне функционал.

В результате получилось, что на каждую reduce-функцию у меня есть наполовину инстанцированный конструктор, в некотором смысле sсope-полуфабрикат. При каждом запросе reduce клиентом я этот полуфабрикат инжектирую итераторы, зависимые от параметров запроса, и исполняю. Такие конструкции V8 прекрасно оптимизирует – сама reduce-функция получается инстанцированной однократно, вызывается часто и оптимизируется в этой связи компилятором по-полной.

Выложил вчера на гитхаб, а сегодня ночью тесты гонял. Летает )
ermouth: (ang)

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

var app = require('express')(), request = require('request');
app.use("/proxy",function(req,res){req.pipe(request(req.query.url)).pipe(res)});
app.listen(80);

Обожаю за такие штуки javascript.

Появился этот код в процессе работы над прокси-сервером для CouchDB, который обеспечит per-document read ACL. Ну и попутно кучу других всяких рестрикшинов, которых в CouchDB сейчас нет, а очень бы хотелось.

Это всё небесплатно в плане производительности, но у меня получилось на бумажке построить схему, как это сделать всеобъемлющим, без щелей, и даже для reduce, а не только для map или выборки по ключам.

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

“Не всеобъемлющее” означает, что API CouchDB где-то немного изменена, а где-то обрезана. А “громоздкое” – потому что много сложно связанного кода, и требует дополнительно Redis (это memcache) для кэширования и синхронизации ACL и сессий между потоками.

Это оказался подход тупиковый и меня недавно осенило, как можно обойтись без межпотоковых взаимодействий через временную БД. Картиночка цепей обработки раутером КучДБ-шного API в части GET-запросов в результате выглядит примерно так:

Снимок экрана 2015-01-22 в 19.42.31

То-есть это полное накрытие REST API, при этом получается не так и много разновидностей обработчиков (это которые зелёные и через запятую). Каждый обработчик строк по 30-50, может. Короткие довольно.

Попутно эта штука будет ограничивать к-во запросов, gzip-ить ответы и вести лог. Во всём остальном прикидываясь CouchDB.

Такой подход (per-document read ACL), если брать в общем, разрушает возможность построения колец из реплик, то-есть допустима только звезда. Скажем, есть три базы – главная и две у юзеров. Реплики у юзеров будут различаться – и если они реплицируются друг в друга минуя главную базу, то оба увидят документы друг друга.

Те не менее, жизнь показывает, что колец репликации надо избегать совершенно по другим причинам и в реальных сценариях их возникновение маловероятно. Например, две реплики “главной” БД, помещённые в два разных браузера, напрямую друг с другом соединиться уже не смогут.

Интересно, что общий подход к этой задаче я в обдумывал года два, пока не придумалось. В общем, скоро “смотрите на гитхабе” )

ermouth: (Default)

За каникулы случился minor version upgrade – добавилось немножко вкусных фич. Заодно оказалось, что это 10-й юбилейный релиз, да.

В jQuery.my теперь есть внутрисистемный pub/sub и управляемое наследование.

Radio/ listen

Pub/sub организован по принципу радио – каналы и подписчики. Подписчики – контролы в приложениях, они могут апдейтиться на сообщение в канале.

В отличие от типичных чисто js-реализаций pub/sub, я заюзал DOM. Просто мне надо иногда ограничивать вещание, примерно вот так:

unnamed

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

С учётом того, что $.my это loosely coupled система, такую функциональность – что по границам каждого приложения может проходить radio-relay c ретранслятором/обработчиком/глушилкой – оказалось не так просто сделать по канонам.

Любое $.my приложение может позаимствовать у соседа, например, какую-то всплывайку, чуток её декорировать и использовать как свою. Или даже не декорировать. И этот позаимствованный манифест должен вести себя, как родной, в том числе и в плане цензуры радио.

В общем, скомбинированные события jQuery и обычный pub/sub вполне решают проблему без создания каналов с частными именами. Также не нужен контроль за тем, жив ли ещё подписчик или его уже прибило по пути и это последние коллбэки в темноте дёргаются – то-есть можно не отписываться от канала в деструкторе, тебя автоматом отпишут по выбытию.

Expose / inherit

Пример с заимствованием кода у соседних приложений во время рантайма имеет ещё одну сторону – принимаемый сниппет по идее должен получать какие-то методы или данные от принимающей стороны.

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

Было бы удобно ей сразу передавать кукую-то ветку манифеста вызывающего приложения, отвечающую за БД. Это и раньше можно было делать, но надо было код писать – а он всегда исполняется после require. Теперь можно сразу фэйлить старт, если вызывающая форма не expose’ит какие-то нужные фичи.

В общем то, недавний редактор JSON написан как раз отчасти чтобы это всё потестить.

Картинки

Когда рисовал схему паб/саба, внезапно нашлось три картинки. Кажется, не публиковал – и напрочь забыл, по какому случаю вообще их рисовал. Интересно, двигательная активность в сочетании с провалами в памяти ггг.

unnamed1 unnamed (1) unnamed (2)

Каникулы кончились, на дворе адище, русский зима, –30. А я дома в трусах и у меня внезапно ядовитый паслён Solanum pseudocapsicum мало того, что зацвёл, но ещё и ягодки будут.

В общем, сдержанно-оптимистичный пост, да.

ermouth: (ang)

Прочитал на Хабре про успехи npm и решил написать пост. npm – это пакетный менеджер и публичный репозиторий для node.js, и успехи реально очень впечатляющие.

Снимок экрана 2015-01-10 в 7.04.56

Фишка в том, что этот репозиторий – база CouchDB. Не “веб-сервер плюс БД”, а именно просто БД. Кластер там, с обвесами – но основные функции выполняет CouchDB, вот на её мету прямой выход. И именно CouchDB там используется неспроста.

Доступность

CouchDB имеет сразу после установки уникальный набор фич, связанных с доступностью. По-отдельности они в других БД есть, а вот разом – нет.

По-хорошему, CouchDB сразу после установки становится веб-сервисом. Доступ к БД – только через http(s)-запросы, через REST-интерфейс, то-есть веб-сервер уже встроен в БД. Веб-приложение админки тоже встроено в БД, аж в двух версиях.

Система контроля доступа – простая, но совершенно железобетонная – тоже встроена в БД, как и механика авторизации.

БД умеет синхронизироваться в непрерывном режиме с другими экземплярами через http(s), в тч в режиме “мастер-мастер”. Протокол репликации хорошо документирован и основан на согласовании деревьев ревизий.

Последняя фича, например, значит, что можно иметь полную локальную “живую” копию npm. Можно даже в браузере, без установки CouchDB.

Хранение и запись

Сама по себе, как БД, CouchDB представляет из себя хранилище JSON-документов, но тут тоже есть целый ряд уникальных фич.

Операция записи/обновления – просто POST запрос, например, аяксом. Запись неблокирующая, это называется MVCC, и тут он честный, а не как в табличных БД.

У каждого дока есть ревизия, которая состоит из номера версии и случайного значения (типа 15-12efdab). При каждой записи в док версия инкрементится, а значение меняется. Записать в док можно только отправив значение предыдущей ревизии, причём если сохранённая ревизия не равна отправляемой, запись отменяется.

Запись идёт в режиме “append only”, ничего не пишется поверх. Это значит, что база помнит все ревизии документов до тех пор пока не будет выполнена операция очистки/оптимизации. Также это значит, что база выжимает из SSD-дисков всё, на что они способны – и при этом их бережёт.

И самое главное – к JSON-документам возможны файловые аттачи, примерно как к емэйлам. То-есть это не просто блобы, это блобы с именем и mime-типом.

Выборка по ключу

Нет ничего проще – GET-запрос типа domain/dbname/doc_id – например https://ermouth.couchappy.com/cwmanual/cw-Demo-Controls-4vx1 – сразу отдаст JSON-документ.

В этом документе есть приаттаченный файл – картинка. Она тоже доступна по прямой ссылке https://ermouth.couchappy.com/cwmanual/cw-Demo-Controls-4vx1/turing.jpg. Вот она, отображается прямо из CouchDB.

Выборка запросами

Любая выборка запросом из CouchDB – это выполнение map/reduce и выдача запрошенного диапазона ключей.

Именованные пары map/reduce функций, к которым выполняются запросы, хранятся в самой БД в специальных документах. Документ выглядит примерно так ermouth.couchappy.com/cloudwall/_design/cloudwall. Видно, что функция – javascript.

Снимок экрана 2015-01-10 в 8.50.45

Запрос к этой map/reduce паре (в которой reduce, правда, нет) выглядит примерно так:
ermouth.couchappy.com/cloudwall/_design/cloudwall/_view/info?startkey="cw"&endkey="cwz"

На выходе – краткая информация о документах в базе, подготовленная map-функцией. В диапазоне ключей cw…cwz.

Важнейшее отличие CouchDB от других БД – результаты вычислений map/reduce кэшируются и повторно map-функции не вычисляются, если документ не обновился.

То-есть map/reduce не требует фуллскана каждый раз, как, например, это происходит в Mongo. Фактически map-функции используются для построения индексов.

Валидация записи и частичное обновление

POST-запросы на запись могут проверяться в БД функциями-валидаторами. Они тоже js и тоже хранятся прямо в БД как специальные документы. Например, вот эта функция не даст писать в БД, если вы не авторизованы:

Снимок экрана 2015-01-10 в 9.03.51

Примерно такие же хранимые функции могут применяться для частичного обвновления документов.

Ну то-есть например надо обновить в документе только таймстамп. Чтоб не гонять весь док по сети, можно вызвать на сервере сохранённую функцию, которая это сделает “не отходя от кассы”.

Применимость CouchDB

Везде, где reads>>writes и структура хранимых данных – более-менее сложная. Также в силу специфики http и сериализации как читать, так и писать лучше сразу помногу.

Табличка вот по кейсам, 0 – совсем не подходит, 5 – лучше не придумаешь.

Версионированные хранилища доков 5
Распределённые синхронизированные хранилища 4
Хранилища частично нормализованных связанных данных 1-4
Полностью нормализованные данные 0
Быстрые логи 2
Медленные логи / Агрегаторы логов для анализа 5
Вообще большие наборы данных для анализа 5
Необходимость транзакционной целостности 0
Сложные повторяющиеся “фигурные” выборки 4
Выборки сабсетов узлов документов (частей документов) 5
Подключенные клиенты хотят уведомлений, что база обновилась 4
Хранение файлов (типизированных блобов) 4
SSD диски как хранилище 5+++
Синхронизация / репликация по каналам с потерями и обрывами 5+++

CouchDB вместо сервера приложений

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

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

Такая архитектура проста и надёжна, как железный лом – если в ней что-то и ломается (что почти невероятно), то мгновенно понятно что.

Единственное, что приходится делать вне CouchDB – это ограничивать доступ на чтение. Система доступов в самой CouchDB такая, что дать права на чтение пользователю можно только к целому бакету, но не к отдельным документам.

----

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

Есчо, на Винде тоже прекрасно работает.

JSON editor

Jan. 9th, 2015 07:44 am
ermouth: (ang)

У меня тут в качестве побочного инструмента разработки возникла потребность в нормальном JSON-редакторе. Это то-есть такого, который:


  • распознаёт таймстампы и показывает их как даты

  • понимает стрингифаенные функции

  • определяет, что строка – base64 и её можно показать/скачать

  • прощает ошибки набора JSON (пропуск кавычек), но генерит корректный JSON

  • понимает JS-выражения, которые сразу вычисляет

  • позволяет размножать/переставлять ветки

  • позволяет каждую ветку поправить сорцом

  • даёт экстендить один JSON другим.

Ещё два важных момента – это должен быть компонент, который можно как контрол инициализировать и он должен уметь показать переменную в памяти – при этом напрямую мутировать-редактировать её, не портя и не разрывая связи.

У меня такой редактор написался внезапно, и такой хорошенький, что я вот его обвесил всякими сэмпл-доками и выложил как online tool. Он умеет доки хранить в локалсторидже есчо, так что всё помнит.

Снимок экрана 2015-01-09 в 7.26.06

http://cloudwall.me/etc/json-editor.html

Любопытно, что эта штука – “рекурсивное” приложение. Оно при раскрытии веток юзером инстанцирует само себя как дочки. Я сначала думал что это довольно дорого в плане памяти и CPU, но неожиданно оно норм даже на айпаде полетело. Хотя прожорливое, конечно.

ermouth: (ang)

Полгода назад morfizm написал хороший пост про то, как работают технологии map/reduce для распараллеливания обработки больших объёмов данных.

Мне есть что к той истории добавить – все системы, что я построил за последний год, используют map/reduce для сортированной/группированной выборки данных из NoSQL БД. В силу целого ряда вкусностей мы используем CouchDB и примеры будут ближе к ней, но в остальных NoSQL БД принцип примерно такой же.

С помощью применения map/reduce практически любая сложная выборка по набору JSON-документов (деревья, не таблица) успешно приводится к простой выборке (цепочке выборок) диапазона ключей. В общем, для этого достаточно только map, про reduce я в следующий раз напишу.

Работает это так.

Пусть у нас есть БД с постами и комментами, то-есть с набором документов примерно такой структуры:


{type:"post", _id:"", author:"", title:"", stamp:0, content""}
{type:"comment", parentId:"", _id:"", author:"", title:"", stamp:0, content""}

Например, мы хотим выбирать сразу посты и комменты к ним за диапазон дат. Для такой выборки достаточно одной map-функции.


function Feed (doc) {
  if (doc.type==="post") {
    emit ([doc.id, 0], doc    );
    emit ( doc.stamp , doc._id);
    //       _↑_        __↑__
    //       key        value
  }
  if (doc.type==="comment") emit ([doc.parentId, 1], doc)
}

Наша map-функция, в зависимости от типа документа, эмитит пары ключ-значение. Для поста мы эмитим сразу две пары, а для коммента – одну.

Заметим, что разные комменты к одному конкретному посту эмитят себя по одинаковому ключу. Также заметим, что ключ может быть не только string, но и array.

Если пройтись этой функцией по нашей БД, мы на выходе получим набор пар ключ-значение со следующими свойствами:


  1. Выбор из этого набора диапазона ключей startStamp … endStamp даст на выходе айдишники постов в этом диапазоне дат.

  2. Выбор потом ключей вида
    [postId1, 0], [postId1, 1], [postId2, 0], [postId2, 1], [postId3, 0], [postId3, 1]…
    отдаст нам сразу и посты, и все комменты к ним.

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

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

CouchDB именно так и делает. Над каждой базой можно создать набор именованных индексов, в которых хранятся результаты вычисления различных предопределённых map-функций. Выбирать из БД можно как по ID документов, так и по ключам результатов работы этих функций.

Индексы хранятся как B-tree, соответственно с рождения заточены под выбор диапазонов.

Чуток усложнив ключи с таймстампом – скажем, до [doc.author, doc.stamp], мы сможем выбирать посты конкретного автора. Диапазоны выборки ключей для первого запроса будут выглядеть тогда примерно так:  ["ermouth", 1420000000000] … ["ermouth", 1420014425025].

К такой схеме выборки после SQL пришлось некоторое время привыкать – но когда к ней приспособишься, оказывается, что она куда богаче SQL по возможностям одного только map.

На этой прекрасной ноте заканчиваю 2014 год.

Всем счастья, радости и приятных ништячков в 2015! Ушёл готовить оливье.

Profile

ermouth: (Default)
ermouth

November 2021

S M T W T F S
 123456
78910111213
14151617181920
21 222324252627
282930    

Syndicate

RSS Atom

Most Popular Tags

Style Credit

Expand Cut Tags

Expand All Cut TagsCollapse All Cut Tags
Page generated Jul. 3rd, 2025 08:14 am
Powered by Dreamwidth Studios