Комментарии 25
Спасибо, интересная идея и продукт. Я как раз из стана людей готовых «браться за любую задачу на системе разработки общего назначения», но уже наелся полной ложной этого подхода.

Вопросы:

1. Почему для хранения времени используется только TIMESTAMP? Единственный тип (по крайней мере, в Postgres), который позволяет безопасно хранить время в моменты переводов стрелок часов назад это timestamp with timezone.

2. В PostgreSQL тоже существует MATERIALIZED VIEW — чем он не подошёл?

3.
Чем плохо использование представления для получения агрегированных данных? Этот подход вполне работоспособен, но в действительности он подкладывает бомбу замедленного действия под всю нашу систему: ведь представление, которое является SQL-запросом, выполняется все медленнее и медленнее по мере накопления данных в системе.

Тут что-то не то! Насколько я понимаю, если правильно расставить индексы то такого происходить не должно, потому что движок БД группирует запрос с тем, который записан в VIEW, а не парсит полностью весь VIEW целиком внешним SQL-запросом. То есть, тормозить не должно…
Конечно, движок БД работает именно так, как Вы пишите, и получает агрегаты максимально быстро с учетом всех индексов и его понимания требуемого порядка выполнения операций над данными. Но получение агрегатов — это в любом случае затратная операция. Как ни крути, цифры (пусть даже только те, что нужно и без лишних отягощений) надо считать и сложить.
1. Вы не первый, кто указывает на эту проблему. С хранением времени у нас пока костыльно. Сейчас всё время мы при записи в БД сводим к московскому, в т. ч. для систем, работающих по всей России. Мы однако ж постоянно улучшаемся, поэтому скоро, думаю, этот вопрос будет проработан.

2. :-) Тем, что для обновления своего содержимого он требует REFRESH MATERIALIZED VIEW. Рефрешить нельзя после каждой операции на зависимой таблице, т. к. это дорого обойдётся. Рефрешить периодически раз в сутки? — мы имеем неактуальные данные в сводном представлении.

3. То, что Вы описали, действительно имеет место в Indexed Views для SQL Server, и тем они хороши. Не знаю, как обстоит дело в Materialized views для PostgreSQL сегодня, но кажется ещё несколько лет назад у них этого не было и там то ли собирались внедрить это, то ли это существовало с какими-то ограничениями. Если говорить об обычном, не материализованном view вида select sum..group by, то он будет деградировать по мере роста количества записей.
Ок, ещё вопрос: Celesta внутри себя содержит свои собственные реализации триггеров, курсоров, serializable транзакций?
Про триггеры. Если говорить о триггерах, выполняемых внутри БД, то Celesta генерирует триггеры на таблицах базы данных, чтобы решать свои внутренние задачи (например, контроль потерянных обновлений, materialized views). «Пользовательский» код на уровне БД она не даёт выполнять, но на челеста-таблицы можно вешать триггеры, реализуемые на Python, через статические поля курсоров соответствующих таблиц, как описано здесь. Естественно, это будет код, выполняемый на сервере приложений, и если вы будете вставлять записи в таблицу напрямую, он не выполнится. Но это плата за кроссбазданческость.

Про курсоры. Курсоры в терминологии Celesta — это классы доступа к данным, которые генерирует Celesta, и причём любой доступ к данным в Celesta может осуществляться только через них. На каждую таблицу и представление кодогенерируется класс-курсор на языке Python. Курсор «под капотом» из себя представляет набор JDBC PreparedStatement-ов для CRUD-операций над данными БД и SELECT-ов с различными фильтрами. По сути, там происходит генерация SQL-ек, их всевозможное кэширование и выполнение. Всё работает через классический JDBC.

Про транзакции и уровень изоляции. Перед началом celesta-процедуры, когда формируется CallContext, с ним связывается JDBC Connection, который берётся из пула. Таким образом, один CallContext всегда должен использоваться в одном треде, и в каждом есть свой connection. В вашем коде вы можете достать Connection из свойства CallContext и поменять в нём isolation level, хотя на практике я не встречал, чтобы кто-то таким пользовался. Всегда default-уровня достаточно. Connection создаётся со свойством autocommit=false и коммитится, если Celesta-процедура добежала до конца без ошибок. Если с ошибками — то откатывается. Если вам надо закоммитить транзакцию в середине celesta-процедуры — context.commit.

Если Вы в Москве, приходите на джуг: jugmsk.timepad.ru/event/569994
То есть, на мой вопрос ответ «да, да, да»?

Но ради чего это всё? Только ради возможности писать код бизнеслогики на питоне?

Не думали над изобретением собственного языка для бизнеслогики? Эдакого Celesta1cLikeLanguage?

Тогда вместо повторения уже реализованного во всех СУБД кода достаточно было бы написать хорошо тестируемый транслятор из вашего языка в платформо-зависимые языки типа PLSQL, а триггеры, курсоры и транзакции использовать уже готовые. Раз уж в создание своего транслятора для SQL смогли, то и язык смогли бы…

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

Если Вы в Москве, приходите на джуг: jugmsk.timepad.ru/event/569994

Спасибо за приглашение, но я далеко от Москвы
> Не думали над изобретением собственного языка для бизнеслогики?

Да думали, конечно же думали! Питон вообще и его реализация Jython в частности нас очень и очень много в чём не устраивают. Про это много чего могу рассказать ) Сляпать свой язык программирования — задача стандартная и, в общем, вполне выполнимая. Но ЯП — это не только транслятор, это же ещё и tooling: IDE, code completion, пошаговая отладка, вот это всё… на это у нас сил нет, но более того: на это нет сил ни у 1С, ни у Microsoft. Вы знаете, какое убожество — отладчики Microsoft Dynamics систем?? Это врагу не пожелаешь!

Да и вообще: сляпать какой-никакой ЯП — задача нехитрая. А сделать изящный ЯП, с красивым синтаксисом, читаемый, без неочевидных каких-то случаев — очень хитрая задача. Python очень красивый язык и ему легко научаются. Хотя, повторюсь, мы сейчас ищем и другие варианты.
Лучше не сляпывать, а взять готовый (подмножество готового) — тогда IDE и всякие там подсветки синтаксиса будут работать.

Насчёт отладки: а как в БД сейчас триггеры и хранимки отлаживаются? Ответ: да никак, в общем-то, только вставкой дебажного вывода. И все живы)
Живы-то живы, конечно. Но не сказать, чтоб очень счастливы ) Я несколько лет потратил на работу с системами Microsoft Dynamics. Они прекрасны в своём роде. Но я совсем не хочу туда возвращаться — именно из-за отношения к разработчику. Хорошо, когда у девелопера лучший IDE, лучшая система контроля версий, лучшая система для тестирования — всё это сейчас доступно только в языках общего назначения…
Хорошо, когда у девелопера лучший IDE, лучшая система контроля версий, лучшая система для тестирования — всё это сейчас доступно только в языках общего назначения…


Мне кажется, сейчас тренд как раз на создание таких языков, которым IDE не нужна. Это происходит потому что идей языков много, а ресурсов на создание IDE под каждый из них у разработчиков нет.
(В дополнение к предыдущему комменту)
Всегда default-уровня достаточно.

Поясняю свою мысль про транзакции: в СУБД есть serializable транзакции, которые делают ровно то же самое, для чего существует ваше внутреннее поле recversion.
Теперь про recversion.

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

Во-вторых, recversion решает другую задачу. Даже если мы работаем с serializable-транзакциями, но с помощью курсоров, даже в пределах одного треда можно «выстрелить себе в ногу», как описано здесь. Serializable транзакция не поможет, ведь код, приведённый там в качестве примера, бежит в одном треде и в одной транзакции. Это плата за идеологию взаимодействия с данными базы через «курсоры», которые являются по сути буферами данных одной записи в таблице.

Кстати, встречаются и таблицы, записи в которых не обновляются, а только добавляются (книги операций, финансовые и товарные транзакции). Для них пляски с recversion — лишние. Такие таблицы можно объявить в CelestaSQL с постфиксом WITH NO VERSION CHECK и поле recversion создаваться на них не будет.
Во-первых, serializable-транзакции генерируют больше блокировок

Потому что RDBMS работает со строками, а у вас часто происходит работа с полями в строках?

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

Ммм? Там же как раз описано как два пользователя работают и это, соответственно, в разных транзакциях происходит?

Кстати, встречаются и таблицы, записи в которых не обновляются, а только добавляются (книги операций, финансовые и товарные транзакции). Для них пляски с recversion — лишние. Такие таблицы можно объявить в CelestaSQL с постфиксом WITH NO VERSION CHECK и поле recversion создаваться на них не будет.

Это решалось бы настройкой грайна. Read only таблицы же есть, будут ещё и WORM-таблицы, в которых открывалась бы транзакция обычного типа.
Кстати, встречаются и таблицы, записи в которых не обновляются, а только добавляются (книги операций, финансовые и товарные транзакции). Для них пляски с recversion — лишние. Такие таблицы можно объявить в CelestaSQL с постфиксом WITH NO VERSION CHECK и поле recversion создаваться на них не будет.

Это решалось бы настройкой грайна. Read only таблицы же есть, будут ещё и WORM-таблицы, в которых открывалась бы транзакция обычного типа.


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

Потому что RDBMS работает со строками, а у вас часто происходит работа с полями в строках?

Сообразил что написал ерунду и понял что причина излишних блокировок в чём-то другом.
В чём?
На этот вопрос ответьте?

Очень уж странно что вам пришлось повторить готовый функционал. Так что, объяснение про излишние блокировки важно.
Я вопрос не очень понял. Вы спрашиваете, почему бы не применять всюду serializable транзакции? Почему у serializable транзакций ниже производительность? Ну более низкий performance — это расплата за то, что они serializable. Поэтому по дефолту JDBC поднимает repeatable read.

Ещё Вы спрашиваете, почему контроль версий, а не select for update, не excplicit locking таблиц. Вообще, возможность сделать select for update надо бы встроить в API Celesta-курсоров, это правда. Но это всё равно не отменяет recversion, он же «optimistic locking» подход. На практике он быстр и безопасен. Если вы залочили таблицу и отвалились/ушли в бесконечный цикл, приехали: вся система, зависящая от этой таблицы, встала из-за одного процесса. Не буду скрывать: идея с курсорами и recversion позаимствована из MS Dynamics ERP, там это именно так работает.
Я вопрос не очень понял. Вы спрашиваете, почему бы не применять всюду serializable транзакции? Почему у serializable транзакций ниже производительность? Ну более низкий performance — это расплата за то, что они serializable.

Предположу, ваш подход с recversion устроен ровно так, как реализованы внутри СУБД serializable-транзакции. (Но я не знаком со всеми БД, которые вы поддерживаете.) Соответственно, скорость получается одинаковая.

Не буду скрывать: идея с курсорами и recversion позаимствована из MS Dynamics ERP, там это именно так работает.

Ваши курсоры — суть курсоры, которые также предоставляет любая СУБД. Но работать они, конечно, будут при условии что вы используете встроенные в СУБД возможности для блокировок, а не свои самописные.

Короче говоря, я клоню к тому что вы зачем-то повторяете код, который уже реализован во всех СУБД.
> Предположу, ваш подход с recversion устроен ровно так, как реализованы внутри СУБД serializable-транзакции.

Нет, конечно, нет! Наш код реализует optimistic lock. СУБД реализуют serializable транзакции за счёт pessimistic locks.

> Ваши курсоры — суть курсоры, которые также предоставляет любая СУБД.

Опять конечно нет. Потому что те курсоры доступны из СУБД-specific-языков, наши доступны на уровне Application Server и кросс-базданческие. А зачем мы с этим морочаемся — кажется, довольно подробно описано в статье.
Нет, конечно, нет! Наш код реализует optimistic lock. СУБД реализуют serializable транзакции за счёт pessimistic locks.


Опс, прощу прощения, забыл про то что нужен optimitic

Опять конечно нет. Потому что те курсоры доступны из СУБД-specific-языков, наши доступны на уровне Application Server и кросс-базданческие.


Из любых языков доступны же, есть SQL-команда для этого: DECLARE CURSOR или CREATE CURSOR обычно.
… но для блокировок «вручную» в чистом виде не годятся.

Всё понято, вопрос больше нет! Спасибо за разъяснения!
> Там же как раз описано как два пользователя работают и это, соответственно, в разных транзакциях происходит?

Почитайте чуть дальше первого абзаца))

> будут ещё и WORM-таблицы, в которых открывалась бы транзакция обычного типа.

Эм, не очень понимаю, что значит «транзакция открывалась бы в таблице».

Таблицы WITH NO VERSION CHECK это и есть у нас, по сути, WORM-таблицы. Но в нынешней реализации, кроме отключения механизма recversion, это ни на что не влияет.
> Там же как раз описано как два пользователя работают и это, соответственно, в разных транзакциях происходит?

Почитайте чуть дальше первого абзаца))

Мне кажется, проблема внутри одной транзакции решается трансляцией вашего SQL-выражения в конструкцию типа «SELECT блаблабла FOR UPDATE». (Речь о синтаксисе Postgresql.)
Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.