Содержание

Предыдущий раздел

Глобальные переменные для сессий и транзакций

Следующий раздел

Временные интервалы. Хранение и применение в версии PostgreSQL до 9.2

Константы в приложении

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

  • магические значение,
  • использование настроек,
  • хранение констант непосредственно в хранимых процедурах,
  • хранение констант в таблицах

1. Применение магических значений

Под этим подходом понимается использование значений, жестко закодированных в код хранимок.

Пример

...
SELECT * FROM customers WHERE state = 1;
...

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

Плюсы

Данный подход не требует каких-то дополнительных трудозатрат и интуитивно является самым быстрым решением.

Минусы

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

2. Использование существующего функционала настроек

Константы приложения при этом подходе хранятся в настройках СУБД в файле postgresql.conf. Доступ к константам осуществляется с помощью существующих стандартных функций.

...
SELECT * FROM customers WHERE state = current_setting('myapp.active_state')::int;
...

Плюсы

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

Минусы / плюсы

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

3. Хранение констант в хранимых процедурах

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

CREATE OR REPLACE FUNCTION active_state() RETURNS int AS
$$
SELECT 1::int
$$ LANGUAGE sql IMMUTABLE;
SELECT * FROM customers WHERE state = active_state();

Плюсы

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

Минусы

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

4. Хранение констант в таблицах

Для хранения констант используется простая таблица (вида ключ-значение).

CREATE TABLE constants (
  key varchar PRIMARY KEY,
  value varchar
);

а для доступа к этим константам используются хранимые процедуры.

CREATE OR REPLACE FUNCTION get_constant(_key varchar) RETURNS varchar AS
  $$
SELECT value FROM constants WHERE key = _key;
$$ LANGUAGE sql IMMUTABLE;
SELECT * FROM customers WHERE state = get_constant('active.state')::int;

Плюсы

Данный подход является самым легким в поддержке - константы изменяются, создаются и их можно все просмотреть в одном месте.

Минусы

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

Производительность всех вышеописанных решений

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

Скрипт с необходимыми тестами.

-- NULL

CREATE OR REPLACE FUNCTION test_null_performance() RETURNS void AS
  $$
DECLARE
  _i int;
  _value varchar;
  _key varchar;
BEGIN
  _key := 'myapp.var1';
  FOR _i IN SELECT generate_series(1, 10000000)
  LOOP
    NULL;
  END LOOP;
END
$$ LANGUAGE plpgsql VOLATILE;

SELECT test_null_performance();

-- Test inline constants

CREATE OR REPLACE FUNCTION test_constants_inline_performance() RETURNS void AS
$$
DECLARE
  _i int;
  _value varchar;
  _key varchar;
BEGIN
  _key := 'myapp.var1';
  FOR _i IN SELECT generate_series(1, 10000000)
  LOOP
    _value := 'value1';
  END LOOP;
END
$$ LANGUAGE plpgsql VOLATILE;

SELECT test_constants_inline_performance();


-- Test setting constants

CREATE OR REPLACE FUNCTION test_constants_settings_performance() RETURNS void AS
$$
DECLARE
  _i int;
  _value varchar;
  _key varchar;
BEGIN
  _key := 'myapp.var1';
  FOR _i IN SELECT generate_series(1, 10000000)
  LOOP
    _value := current_setting(_key);
  END LOOP;
END
$$ LANGUAGE plpgsql VOLATILE;

SELECT test_constants_settings_performance();

-- Test immutable function constants


CREATE OR REPLACE FUNCTION get_constant_value() RETURNS varchar AS
$$
SELECT 'value1'::varchar
$$ LANGUAGE sql IMMUTABLE;


CREATE OR REPLACE FUNCTION test_constants_function_performance() RETURNS void AS
$$
DECLARE
  _i int;
  _value varchar;
BEGIN
  FOR _i IN SELECT generate_series(1, 10000000)
  LOOP
    _value := get_constant_value();
  END LOOP;
END
$$ LANGUAGE plpgsql VOLATILE;

SELECT test_constants_function_performance();

-- Test stable function constants

CREATE OR REPLACE FUNCTION get_constant_stable_value() RETURNS varchar AS
  $$
SELECT 'value1'::varchar
$$ LANGUAGE sql STABLE;

CREATE OR REPLACE FUNCTION test_constants_stable_function_performance() RETURNS void AS
  $$
DECLARE
  _i int;
  _value varchar;
BEGIN
  FOR _i IN SELECT generate_series(1, 10000000)
  LOOP
    _value := get_constant_stable_value();
  END LOOP;
END
$$ LANGUAGE plpgsql VOLATILE;

SELECT test_constants_stable_function_performance();

-- Test table constants

CREATE TABLE constants (
  key varchar PRIMARY KEY,
  value varchar
);

INSERT INTO constants(key, value) VALUES('myapp.var1', 'value1');

CREATE OR REPLACE FUNCTION get_constant_value(_key varchar) RETURNS varchar AS
  $$
SELECT value FROM constants WHERE key = _key;
$$ LANGUAGE sql IMMUTABLE;


CREATE OR REPLACE FUNCTION test_constants_table_performance() RETURNS void AS
  $$
DECLARE
  _i int;
  _value varchar;
BEGIN
  FOR _i IN SELECT generate_series(1, 10000000)
  LOOP
    _value := get_constant_value('myapp.var1');
  END LOOP;
END
$$ LANGUAGE plpgsql VOLATILE;

SELECT test_constants_table_performance();

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

denis=# \timing on
Timing is on.

denis=# SELECT test_constants_inline_performance();
 test_constants_inline_performance
-----------------------------------
(1 row)
Time: 4168.470 ms

denis=# SELECT test_constants_settings_performance();
 test_constants_settings_performance
-------------------------------------
(1 row)
Time: 6251.461 ms

denis=# SELECT test_constants_function_performance();
 test_constants_function_performance
-------------------------------------
(1 row)
Time: 2733.908 ms

denis=# SELECT test_constants_stable_function_performance();
 test_constants_stable_function_performance
--------------------------------------------
(1 row)
Time: 2750.599 ms

denis=# SELECT test_constants_table_performance();
 test_constants_table_performance
----------------------------------
(1 row)
Time: 2702.855 ms

denis=# SELECT test_null_performance();
 test_null_performance
-----------------------
(1 row)
Time: 1135.609 ms

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

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

comments powered by Disqus