"Быстрее, лучше, мощнее!"
"Быстрее, лучше, мощнее!"

Привет! Меня зовут Сергей, я технический эксперт в компании Bercut. Когда-то увлекался Delphi и J2ME, издал несколько книг на эти темы, и профессионально уже около 20 лет создаю высоконагруженные приложения баз данных, используя для этого PL/SQL и SQL.

В Bercut мы занимаемся разработкой и развитием IT-продуктов и решений для операторов цифровых услуг и мобильных сервисов. Наши системы работают на различном железе, разных СУБД и обслуживают 24x7x365 в режиме онлайн сотни миллионов абонентов по всему миру. И это только кажется, что абонент один раз в день куда-то позвонил и все, больше никакой нагрузки от него нет и никто с ним не работает. На самом деле каждый смартфон каждого абонента непрерывно обменивается данными с сетью, а это все работа инфраструктуры операторов сотовой связи, а значит, нашего ПО. Как же система справляется с таким огромным объемом нагрузки?

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

Итак,

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

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

И наконец, в игру вступают разработчики. Никто лучше разработчиков не знает, как и где нужно построить индексы, какие написать запросы, чтобы система работала максимально быстро, потребляя минимальные ресурсы. А ресурсы в СУБД - это время БД, состоящее, в том числе, из времени CPU, логических и дисковых чтений, записи, защелок, блокировок и т.д. Однако не все разработчики хорошо знакомы с СУБД, для которой пишут код, а потому код получается не особо качественный. Встречаются разработчики, которые якобы могут писать код под 5 разных СУБД. По опыту такой код неэффективно работает в каждой из них.

Хороший код не только работает быстро, но и отлично масштабируется. Например, требуется выполнить расчет некоторого показателя для N клиентов. При расчете в один поток (одним джобом) система выполнит это за X секунд. Если код написан качественно, то мы можем легко распараллелить этот расчет на произвольное (но ограниченное) число M потоков (джобов). Возможное число потоков зависит от производительности сервера и нагрузки от других процессов. И тогда расчет для того же числа N клиентов в M потоков будет занимать уже не X секунд, а ненамного больше, чем X/M. Если же код некачественный, то выполнив распараллеливание процесса на M потоков, мы получим время расчета значительно больше X/M. В некоторых случаях даже гораздо больше, чем X, и нагрузку на БД >= X*M. А все почему? Блокировки, защелки, конкуренция, ошибки управления транзакциями.

Следует избегать реализации алгоритмов, степень роста которых n^2 и выше. Если на обработку n клиентов нужно время t, а на обработку 10n клиентов – 100t, то это плохой алгоритм, нужно разработать более оптимальный.

Блокировки

В Оракл ни один процесс не может блокировать чтение строк из базы данных (промолчим про частный случай с result_cash). Но любой процесс может блокировать обновление строк. Если есть два джоба, и первый из них проапдейтил строку s1 в таблице t, но не закоммитил, то второй, когда захочет обновить эту же строку, будет обязан ждать коммита или роллбека первого джоба. Получаем блокировку.

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

Взаимоблокировки

Еще хуже, если возникает взаимоблокировка (deadlock). Это когда джоб1 проапдейтил, но не закоммитил строку s1, а джоб2 проапдейтил строку s2 (и тоже не закоммитил). Затем джоб1 пытается апдейтить строку s2, а она заблокирована джобом2. Тогда джоб1 ждет, а джоб2 пытается проапдейтить строку s1, а она, в свою очередь, заблокирована джобом1. В итоге первый джоб ждет второго, а второй ждет первого. Происходит взаимоблокировка. Оракл из этих двух сессий выбирает «жертву» и в ней генерирует исключение. Неуспешная транзакция откатывается. Тогда вместо ускорения получаем замедление - нужно подождать какое-то время, а потом еще и откат выполнить, -  и нарушение логики. 

Блокировки и взаимоблокировки ухудшают масштабируемость приложений, а потому необходимо их избегать. Значит:

  • разные сессии должны работать с разными строками таблиц и не мешать друг другу (например, джоб1 обрабатывает всех четных клиентов, а джоб2 - всех нечетных);

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

Параллельная обработка

Джобов, выполняющих обработку порции данных, может быть произвольное количество. Как распределить данные между ними? Есть множество способов, которые можно применить. В случае c клиентами, у нас есть таблица клиентов customers, в ней первичный ключ - идентификатор клиента cust_id.

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

Если мы используем не очереди, а пакет DBMS_PARALLEL_EXECUTE для обработки всей таблицы, то таблица бьется на порции (чанки), которые автоматически распределяются между предназначенными для нее джобами.

А что, если нет ни очереди, ни пакета DBMS_PARALLEL_EXECUTE, и ведется просто выборка данных запросом из таблицы и обработка их в цикле?

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

procedure calc_clients_data( p_process_count in number := 1, p_process_idx   in number := 0) is
begin
  for i in (select cust_id
              from customers c
            where mod( c.cust_id, p_process_count ) = p_process_idx  ) loop
    -- тут код, выполняющий все расчеты по одному клиенту
    commit;
  end loop; 
end;

Тогда код первого джоба, если всего джобов десять, будет выглядеть:

begin
  calc_clients_data( 10, 0);
end;

Код второго джоба:

begin
  calc_clients_data( 10, 1);
end;

И так далее. Возможно распараллеливание и по строковым полям. Тогда вместо mod в запросе можно применить функцию dbms_utility.get_hash_value( name => строковое поле таблицы, base => 0, hash_size => p_process_count )

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

Не следует строить долгих циклов по запросу - чем дольше он работает, тем ему будет тяжелее. Он будет замедляться все больше и больше. Есть множество других процессов, которые апдейтят таблицу customers, а запрос обязан восстановить таблицу до того состояния, в котором она была на момент начала его выполнения. Чем больше было изменений этой строки в других процессах, тем тяжелее системе будет восстановить ее первоначальное состояние. В определенный момент может оказаться, что информации, необходимой для восстановления исходного состояния строки, уже нет (данные в журнале отката были переписаны другой информацией), и мы получим ошибку ORA-01555 "Snapshot too old". Есть еще множество других нюансов, но я не ставлю целью в этой статье рассказать все о разработке в Оракл.

СУБД поддерживает также параллельное выполнение запроса – для его использования достаточно в запросе указать хинт /*+parallel*/. Но следует помнить, что:

  1. далеко не каждому запросу параллельное выполнение идет на пользу;

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

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

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

Конкуренция

Чем меньше код создает защелок, тем меньше конкуренции.

Если джобы читают одну и ту же таблицу, то для снижения конкуренции может быть использовано партиционирование таблицы - так, чтобы каждый джоб работал со своей партицией. Но количество партиций фиксированно, задается при создании таблицы и не может быть оперативно изменено. С другой стороны, никто не мешает в одном джобе, если он только один, последовательно обрабатывать все партиции. Или завести для каждой партиции два-три джоба. Но в случае партиционирования нужно не забывать - оно не предназначено для ускорения работы системы. Хотя в случае хранилища данных, ускорение и даже значительное вполне возможно. Партиционирование в системах OLTP необходимо в первую очередь для повышения управляемости данными средствами DBA. Но если в запросах забывать указывать ключ партиционирования, либо указывать его в неочевидной для СУБД форме, а индексы локальные, то запрос будет просматривать все партиции и процесс замедлится значительно.

Оптимальность запросов

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

  • клиент активен (cust_status=1),

  • находится в филиале (cust_branch=1),

  • на тарифном плане (cust_tp_id=123),

  • у него тип (cust_type=2), 

  • подтип (cust_sub_type=3),

  • дата создания  (cust_created=01.02.2022 13:44:55).

Предположим, имеется индекс (cust_status, cust_branch,cust_tp_id,cust_type,cust_sub_type). По такому индексу будет отобрано из ста миллионов где-то сто тысяч клиентов, по каждому из них будет выполняться чтение таблицы для проверки даты создания. В итоге получим одного клиента, но это будет совершенно неэффективно. К тому же индекс содержит много столбцов, его чтение - тоже тяжелая операция. При добавлении новых строк в таблицу (или обновлении старых) системе придется проводить большую работу по изменению индекса. А теперь предположим, что у нас есть индекс (cust_created,cust_branch,cust_tp_id). Считав из индекса только cust_created = 01.02.2022 13:44:55, система уже получает всего несколько десятков или сотен клиентов, а обработав оставшиеся два столбца индекса, система получит менее десяти строк, для которых нужно обращение непосредственно к таблице. Таким образом, один и тот же запрос в случае второго индекса оказывается в тысячи раз легче для системы, чем первый.

Помимо эффективного использования индексов, также на нагрузку от запроса влияют:

  • сортировка (если ее нет, запрос легче);

  • количество возвращаемых полей (чем меньше, тем запрос легче);

  • наличие/отсутствие distinct, union. Если можно не указывать distinct, лучше этого не делать, если есть возможность вместо union использовать union all, то стоит использовать union all;

  • вызов pl/sql функций. Нужно избегать вызова функций в запросе, т.к. это лишнее переключение контекста, а в некоторых случаях (если функция в условии where) она может вызываться гораздо чаще, чем вы ожидаете. Все зависит от плана, который построит оптимизатор. Он может ее вызвать на конечной стадии выполнения запроса, когда в предварительных результатах уже почти нет ничего лишнего, так и на начальной, когда вместо одной итоговой строки у нас есть тысячи строк. Если функция в запросе необходима, можно рассмотреть возможность ее объявления с pragma UDF. Если функция на одних и тех же входных параметрах должна возвращать один и тот же результат, имеет смысл объявить ее как детерминированную, что снизит число ее вызовов системой.

Также важно для полей, которые могут быть использованы для отбора данных по индексу, избегать условия OR.

Запрос вида:

begin
  select c.cust_id
      bulk collect into v_cust_id_tab
      from customers c
    where c.cust_created = v_created
      and (     v_query_type = 1
            and c.cust_branch = v_branch
             or  v_query_type != 1
            and c.cust_tp_id = v_tp_id);
end;

работает значительно хуже, чем

begin
  if ( v_query_type = 1 ) then
    select c.cust_id
      bulk collect into v_cust_id_tab
      from customers c
    where c.cust_created = v_created
     and c.cust_branch = v_branch;
  else
    select c.cust_id
      bulk collect into v_cust_id_tab
      from customers c
    where c.cust_created = v_created
      and c.cust_tp_id = v_tp_id;
  end if;
end;

особенно, если в большинстве случаев v_query_type = 1. Оптимизатор не знает, чему равен v_query_type, и потому не может искать данные в индексе по условию c.cust_branch = v_branch. Данные будут найдены только по первому столбцу индекса (по c.cust_created = v_created), считаны и затем уже для них будет применяться фильтр c.cust_branch = v_branch.

Динамический sql

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

Если без динамического sql никак, то нужно использовать execute immediate, а не пакет dbms_sql. Если без dbms_sql никак, то стоит хотя снизить число вызовов dbms_sql.parse. Например, через переменные пакета реализовать: не вызывать parse, если в этот раз сгенерирован такой же текст запроса, как в предыдущий раз.

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

begin
  v_sql := 'select c.cust_name
              from customers c
            where c.cust_branch = :i_branch';
  if i_cust_tp_id is not null then
    v_sql := v_sql || ' and c.cust_tp_id = :i_cust_tp_id';
  end if;
  if i_cust_type is not null then
    v_sql := v_sql || ' and c.cust_type = :i_cust_type';
  end if;
  if i_cust_sub_type is not null then
    v_sql := v_sql || ' and c.cust_sub_type = :i_cust_sub_type';
  end if;
  if     i_cust_tp_id is null
     and i_cust_type  is null
     and i_cust_sub_type is null then
    execute immediate v_sql
    bulk collect into v_cust_name_tab
    using i_branch;
  elsif     i_cust_tp_id is not null
     and i_cust_type  is null
     and i_cust_sub_type is null then
    execute immediate v_sql
    bulk collect into v_cust_name_tab
    using i_branch,
          i_cust_tp_id;
   elsif     i_cust_tp_id is not null
     and i_cust_type  is not null
     and i_cust_sub_type is null then
    execute immediate v_sql
    bulk collect into v_cust_name_tab
    using i_branch,
          i_cust_tp_id,
          i_cust_type;
   elsif     i_cust_tp_id  is not null
     and i_cust_type is not null
     and i_cust_sub_type is not null then
    execute immediate v_sql
    bulk collect into v_cust_name_tab
    using i_branch,
          i_cust_tp_id,
          i_cust_type,
          i_cust_sub_type;
   elsif ...

из-за чего многие используют dbms_sql – там достаточно для каждой  переменной привязки, которая есть в запросе вызвать dbms_sql.bind_variable. Однако сам код для dbms_sql сложнее для написания и понимания. А в плане нагрузки для CPU, dbms_sql тяжелее в несколько раз.

Потому лучше генерировать запрос с постоянным числом переменных привязки, пусть некоторые условия в запросе будут избыточны, и использовать execute immediate:

begin
  v_sql := 'select c.cust_name
              from customers c
            where c.cust_branch = :i_branch';

  if i_cust_tp_id is not null then
    v_sql := v_sql || ' and c.cust_tp_id = :i_cust_tp_id';
  else
    v_sql := v_sql || ' and :i_cust_tp_id is null';
  end if;

  if i_cust_type is not null then
    v_sql := v_sql || ' and c.cust_type = :i_cust_type';
  else
    v_sql := v_sql || ' and :i_cust_type is null';
  end if;

  if i_cust_sub_type is not null then
    v_sql := v_sql || ' and c.cust_sub_type = :i_cust_sub_type';
  else
    v_sql := v_sql || ' and :i_cust_sub_type is null';
  end if;
    
  execute immediate v_sql
  bulk collect into v_cust_name_tab
  using i_branch,
        i_cust_tp_id,
        i_cust_type,
        i_cust_sub_type;
end;

Проверка что переменная привязки is null практически никак не сказывается на быстродействии запроса.

Никаких лишних запросов

Самое тяжелое, что есть в системе - это SQL-запросы, даже самые легкие. Потому если есть возможность не выполнять их, лучше не выполнять. В идеале ситуация такая: один раз в начале процесса получили все данные и дальше передаем их через параметры процедуры, не выполняя повторно никаких запросов. Вот пример медленного кода:

function get_cust_name( p_cust_id in number ) return varchar2 is
  v_name varchar2(2000);
begin
  select c.cust_name
    into v_name
    from customers c
  where c.cust_id = p_cust_id; 
  return v_name;
end;

function get_cust_crnc( p_cust_id in number ) return varchar2 is
  v_crnc varchar2(200);
begin
  select c.cust_crnc
    into v_crnc
    from customers c
  where c.cust_id = p_cust_id; 
  return v_crnc;
end;   

function get_cust_amount( p_cust_id in number ) return number is
  v_amt number;
begin
  select c.cust_amount
    into v_amt
    from customers c
  where c.cust_id = p_cust_id; 
  return v_amt;
end;

procedure process_one_cust( p_cust_id in number ) is
  v_name varchar2(2000);
  v_crnc varchar2(200);
  v_amt number;
begin
  v_name := get_cust_name(p_cust_id);
  v_crnc := get_cust_crnc(p_cust_id);
  v_amt := get_cust_amount(p_cust_id);
  …-- тут выполним необходимые действия
end;

procedure process_custs is
begin
  for i in (select c.cust_id
              from customers c
            where mod( c.cust_id, p_process_count ) = p_process_idx  ) loop
    process_one_cust((i.cust_id);
  end loop;
end;

то же самое можно написать гораздо короче, эффективнее и надежнее:

procedure process_one_cust( p_cust_id in number,
                            p_name in varchar2,
                            p_crnc in varchar2,
                            p_amt in number ) is
begin
  …-- тут выполним необходимые действия
end;

procedure process_cust is
  cursor lc_cust is
  select c.cust_id, c.cust_name, c.cust_crnc, c.cust_amount
    from customers c
  where mod( c.cust_id, p_process_count ) = p_process_idx;
  
  type lt_cust_tab is table of lc_cust%rowtype;
  v_cust_tab lt_cust_tab;
begin
  open lc_cust;
  fetch lc_cust bulk collect into v_cust_tab;
  close lc_cust;
  for i in 1..v_cust_tab.count  loop
    process_one_cust( v_cust_tab(i).cust_id,
                      v_cust_tab(i).cust_name,
                      v_cust_tab(i).cust_crnc,
                      v_cust_tab(i).cust_amount );
  end loop;
end;

Пусть есть 100 000 клиентов, которых должна обработать процедура. В первом варианте будет выполнен 1 основной запрос и по 100 000 раз еще 3 запроса - итого 300 001 выполнений запросов. Во втором варианте всего 1 выполнение запроса.

Но не следует считывать в коллекцию, особенно многомерную, миллионы строк и десятки полей - есть риск потратить всю память PGA (если в системе очень много сессий).

Если единственным источником данных запроса является коллекция, при этом в запросе нет сортировки, то смысла в таком запросе нет, лучше сделать тоже самое циклом по коллекции. Потому что цикл — это просто повторить действия n раз и все. Доступ к элементу коллекции из pl/sql-кода происходит практически мгновенно. А SQL-запрос – это сложный алгоритм. Нужно разобрать текст запроса, подставить переменные привязки, считать данные из индекса/таблицы/коллекции, отфильтровать, повторить и т.д.

Если в запросе что-то меняется, то это должны быть переменные привязки. Причем таких переменных в запросе должно быть немного (не десятки, а единицы штук). Если что-то постоянно, это должны быть литералы, а не константы, определенные в пакете. Потому что константы для оптимизатора — это те же переменные привязки, а значит их значение заранее неизвестно, потому план может получиться менее оптимальным. На этапе выполнения придется передавать значение констант в запрос, это тоже затраты времени (хоть они и незначительные, но тем не менее). Если же в SQL-запросе будут меняться литералы, то для каждого выполнения в Оракл это будет новый SQL-запрос с hard parse и всеми вытекающими из этого последствиями.

Тестирование

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

Итак, сегодня мы рассмотрели некоторые важные моменты разработки в Оракл, про которые должен помнить каждый разработчик, чтобы писать эффективный код. Тем, кого интересует данная тема, рекомендую также почитать книгу Тома Кайта "Оракл для профессионалов".

Комментарии (17)


  1. LyubavinSergey Автор
    08.09.2023 13:39
    +10

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


  1. aGGre55or
    08.09.2023 13:39
    +5

    А то что Oracle напрочь ушёл из РФ и все госзаказчики не запускают больше никаких проектов на Oracle Вас не смущает? Я про "Первое, с чего все начинается, это архитектура системы в целом". Просто любопытство)


    1. LyubavinSergey Автор
      08.09.2023 13:39
      +10

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


      1. TIEugene
        08.09.2023 13:39

        Понимаете ли, коллега... Как сисадмин сисадмину - напомню: любое резкое телодвижение вендора продукта заставляет перья на холке любого нормального сисадмина топорщиться и включать "План Б".
        Посему Oracle на сегодня - ненадежно.
        "Идея интересная, но исполнители..."

        Но так, вообще, поковыряться интересно, согласен.
        Если есть время и желание.
        Академически.
        (как c Паскалем Delphi примерно)


        1. mc2
          08.09.2023 13:39
          +4

          А разве данный ресурс исключительно для жителей одной страны? Да и сам ресурс, если память не изменяет мне, юридически оформлен на Кипре.


          1. hVostt
            08.09.2023 13:39
            +3

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


            1. mc2
              08.09.2023 13:39
              +3

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

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


              1. hVostt
                08.09.2023 13:39
                +1

                Никто не говорит про политику. А про последствия на ИТ сектор. Можно сказать прямо, что Oracle в РФ умер. Вы можете быть самым аполитичным человеком в мире, но факт от этого не изменится. Для других стран и предприятий это очень громкий звоночек.

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


                1. breninsul
                  08.09.2023 13:39
                  +1

                  Ну, есть вообще более вменяемые, политизированные варианты, чем "перестать пользоваться продуктами компаний, принявшими правильное решение" :)

                  (Хотя в целом в подавляющем числе случаев PostgreSQL будет лучшим решением вне зависимости от доступности Oracle)


                1. mc2
                  08.09.2023 13:39

                  Ещё раз повторюсь про разные страны. Доковидная статистика ресурса показывала около 20-22% посещений жителями не стран постсоветского пространства (при 30 миллионах посетителей). Вы серьезно считаете что жителям северной Америки или большей части Европы, стоит задумываться про уход компании из стран, которые, мягко говоря, не могут соблюдать ни международное, ни внутренние законодательства?


                  1. hVostt
                    08.09.2023 13:39
                    +1

                    Не хочется уходить в аналогии, но придётся. Если есть прецеденты, стоит обратить на это внимание, и принимать действия, а не засовывать голову в песок, как страус по принципу "моя хата с краю". Vendor lock, это риск, который раньше многие принимали, так как никто раньше особо не думал, что дойдёт до такого маразма и абсурда. По факту, переход с Oracle на Postrgres идёт успешно. А раз так, зачем по собственной инициативе залезать в петлю?


                    1. toxicdream
                      08.09.2023 13:39

                      Замените, пожалуйста, мысленно в статье Oracle на Postrgres и не делайте дисскуссии на ровном месте


                      1. hVostt
                        08.09.2023 13:39

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


            1. vikarti
              08.09.2023 13:39

              Ну технологии то хорошие.
              Поведение — отдельный разговор (я не только про Россию а например про историю с вымогательством денег за Java).
              Хотя статью про PostgreSQL было бы конечно интереснее.


      1. OBIEESupport
        08.09.2023 13:39
        +1

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


    1. n27051538
      08.09.2023 13:39

      Ответ очевиден, работа кипит. Просто статья еще не готова)


      1. ivankudryavtsev
        08.09.2023 13:39
        +2

        Вообще не очевиден. Если у Вас такое отношение к очевидному, у меня для Вас плохие новости…


    1. Vogul
      08.09.2023 13:39

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