Как стать автором
Обновить

Комментарии 23

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


А если пул акторов (через роутер) в специальном ExecutionContext (количество потоков == количество акторов) каждый со своим коннектом? При заполнении запроса параметрами — запоминать их и отправлять вместе с текстом запроса акторам для исполнения.
Выходные параметры получать через `Future[Map[String, Any]]` (если извратиться, то, полагаю и типизированно через HList можно).
Для обработки курсоров — дополнительно передавать, например, `Iteratee`.

Должно получится вполне реактивно.
Так драйвер Oracle всё равно блокирует. Будут эти потоки (по количеству акторов) висеть и ждать, а контексты их занимать память сервера. Чтобы этого не было, нужно, чтобы драйвер был неблокирующий изначально.
Все так, но это в общем-то копейки по ресурсам. Зато код можно получить в духе Reactive Manifesto. Без блокировки «основного» потока. С агрегацией Future и параллельностью.
Ну как сказать, один раз в практике сталкивался с таким, что копеек было 2,5 Гб. По акторам — да, можно так сделать, у нас даже есть пробная версия. Подумываем в следующем году перейти на акторов. А вот, чтобы типизировать то, что возвращается из БД нужны Type Providers.
Можно, конечно, и с ними, но совершенно не обязательно. Достаточно чтобы вот это:

sql"""
begin
  ${output[String]()} := ${"abc"};
  ${output[Int]()} := ${1};
end;
"""


Возвращало:

SqlExpr[String :: Int :: HNil](
  sql = "begin ? = ?; ? = ?; end;",
  params = Seq( OutputString, InputString("abc"), OutputInt, InputInt(1))
)


Сделать это не сложно и при этом полностью строго типизированно.

Вот чтобы типизировать курсоры — тут да, нужны Type Providers. Но они, по сути, уже есть. Правда придется парсить sql чтоб разобрать какие поля из каких таблиц, но это осуществимо.
Кто из них поддерживает pl/sql блоки, out-параметры хранимок и тому подобное?
Хм, да, с этим я думаю посложнее. Как-то я не внимательно прочитал первый абзац. :)

scalikejdbc ближе всего к тому, что описано в данном посте. Я думаю его query DSL можно было бы дописать до необходимого уровня поддержки этих фич.

Спасибо, заценим `scalikejdbc`, хотя есть мысли на MongoDB сползать сильнее в будущем.
ScalikeJDBC-Async provides non-blocking APIs to talk with PostgreSQL and MySQL in the JDBC way.


sad.
Ну-ну, сидите дальше на своём Oracle. Настоящие базы данных давно имеют асинхронные драйверы, и даже по несколько разных реализаций. Чтобы не быть голословным, вот вам ссылка на postgresql-async.
сидите дальше на своём Oracle

Чтоб я так жил, и мне за это ничего не было.
:-D

Если бы речь шла только про базюльку нашего приложения — без вопросов, мало того на NoSQL даже.
Я че-т не понял — вы после каждого запроса возвращаете коннект в пул? И его может после этого захватить любой другой поток?
Вроде это обычная практика, нет? Позволяет сделать так, чтобы количество соединений с базой не зависело от количества клиентов. Можно нагрузку на базу регулировать.

Честно сказать, я по другому видел только в однопользовательских приложениях.
Ну SQL то он вроде как statefull. Опять-же транзакции там.
Условно говоря (с синтаксисом не знаком, так что псевдокод):

database execute query ("BEGIN")
database execute query (sql"select balance from users where id=${one}")
if balance >= amount:
    database execute query (sql"update users set balance=balance - ${amount} where id=${one}")
    database execute query (sql"update users set balance=balance + ${amount} where id=${two}")
database execute query ("COMMIT")

Такое провернуть не удастся, т.к. каждый запрос может выполняться в совершенно разных соединениях. Правильно понял?
Посмотрите пример ниже. Все sqlи выполняются с одним соединением в `execute`. BEGIN-END не нужны.
А зачем 2 отдельных метода database.execute() и database.query()? разве нельзя connection получать/освобождать внутри query?
Или вы еще и batch query используете?
Иногда хочется композировать запросы:

database.execute { connection => val result = database.query(sql"select 1 from dual")(connection) database.update(sql"update users set result = ${result}")(connection) }

или даже так можно:

database.execute { connection => database.update(sql"update table1 set field1 = 1 where key1 = 1") ~ database.update(sql"update table2 set field2 = 2 where key2 = 2") }

batch update тоже есть, да.
Интересно было почитать. Смешно, когда я прочитал слово Oracle, который вы любите и умеете готовить, я в первую очередь подумал о джаве, а совсем не о СУБД.

Скальный паттерн освобождения ресурсов, который вы использовали в execute, называется Loan: wiki.scala-lang.org/display/SYGN/Loan. Необходимая вещь, конечно же.

Еще очень правильным решением было отказаться от ORM, вот и ребята из Слика считают, что ORM — большая ошибка для крупных проектов, что реляционные абстракции и абстракции ОО плохо соответствуют друг другу, и поэтому не стоит маскировать реляционные таблички и кортежи под объекты: slick.typesafe.com/talks/scalax2012/Slick_ScalaExchange_2012.pdf, слайд 11.

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

Я сильно подозреваю, что Идея поняла вашу строковую интерполяцию для sql потому, что в других движках тоже есть похожие с такими же названиями. В Слике есть. Вообще, я остаюсь большим поклонником Слика, жалко, что Оракл-плагин у него платный, но для «Тинькова» наверно это не так важно. И для Монго они тоже активно разрабатывают плагинчик.

Реактивность для jdbc обычно достигается выделением фиксированого количества тредов под соединения с БД, а реактивный код к этим тредам обращается. Это позволяет снять пиковую нагрузку на БД, когда количество замороженных тредов начинает зашкаливать. Этим активно пользуются play-разработчики, например.
Вот страничка Slick-плагина для Play, на которой написано как авторы плагина решили эту проблему в своем случае: github.com/freekh/play-slick/wiki/ScalaSlickThreads. А вот код для DBAction, который там описывается, но сам я этот код не стал читал github.com/freekh/play-slick/blob/master/src/main/scala/play/api/db/slick/DBAction.scala
Спасибо за идею про неявный параметр. Пока не вкручивал, но попробую обязательно, жаль что вылезает слово implicit в прикладном коде…

От ORM не отказывались, его у нас не было ;)

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

Я так понимаю, что реактивность не просто в пуле тредов, а еще в том, как с ним взаимодействовать (можно использовать акторы например, или просто фьючи). А еще я не знал, но к некоторым СУБД разрабатываются асинхронные коннекторы (Postgres и MySQL)
Зарегистрируйтесь на Хабре, чтобы оставить комментарий