Делаем запросы в PostgreSQL из nginx кэшируя с помощью Redis

Сегодня я расскажу как можно работать с базой данных PostgreSQL с помощью nginx’a без application’a (например, PHP или любого другого). Т.е. эта технология абсолютно не зависит от языка, на котором сделан сайт/проект/система.
Мы будем использовать мощь PostgreSQL в хранимых процедурах (stored procedures/functions), а кэшировать с помощью быстрого Redis.

Для начала возьмём нашу БД PostgreSQL и создадим хранимую процедуру на языке PL/pgSQL. В принципе, вы можете обойтись и без неё, но в данном случаи SQL Injection вам обеспечен ?

Выберем последние 25 записей из какой-нибудь таблицы (public.sometable) и вернём это всё в формате JSON.

CREATE OR REPLACE FUNCTION __sometable_getlist() RETURNS text AS
$BODY$
DECLARE
	data 			record;
	return_data		text			:= '';
BEGIN
	FOR data IN
		SELECT
			id,
			title,
			content
		FROM
			public.sometable
		ORDER BY
			id DESC
		LIMIT
			25
	LOOP
		return_data := return_data || row_to_json(data)::text || ',';
	END LOOP;
	RETURN '[' || rtrim( return_data, ',' ) || ']';
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 1;

Запустить её можно так:

SELECT * FROM __sometable_getlist()

Теперь возьмём сервер с FreeBSD и добавим 5 модулей для nginx’a:

  • Postgres — модуль доступа к БД PostgreSQL
  • SRCache — модуль кэширования результатов
  • Echo — модуль, необходимый для SRCache
  • HTTP Redis — модуль Redis (необходим для SRCache)
  • Redis 2 — модуль Redis версии 2.x (необходим для SRCache)

Через командную строку это выглядит так:

cd /usr/ports/www/nginx
make config && make && make install clean

После того как nginx пересоберётся, мы готовы к настройке его конфигов.

Для начала добавим 2 upstream’a (PostgreSQL и Redis) в секции http:

upstream database {
    postgres_server
        127.0.0.1:5432
        dbname=databasename
        user=databaseuser
        password=megacoolpassword;
}
upstream redis2 {
    server
        127.0.0.1:6379;
}

Также добавим location’ы в секцию описания нашего server, что будут читать (/redis/get) и писать (/redis/set) в Redis по ключу.

location /redis/get {
    internal;
    set		            $redis_key	$arg_key;
    redis_pass 	            redis2;
}
location /redis/set {
    internal;
    set 		    $redis_key 	$arg_key;
    redis2_query 	    select		0;
    redis2_query 	    set 		$redis_key	$echo_request_body;
    redis2_query 	    expire 		$redis_key 	60;
    redis2_pass 	    redis2;
}

Обратите внимание, что эти location’ы имеют аттрибут internal, что означает что извне по этому адресу доступиться невозможно.

Теперь, когда у нас описаны сервера и есть location’ы для чтения и записи кэша, мы можем добавить адрес, по-которому нам и нужно получать наш результат в формате JSON.

Пускай, для примера, адрес будет следующим /json/sometable/list.
Тогда в конфиг nginx’a в секцию описания нашего server мы напишем:

location /json/sometable/list {
    default_type        application/json;
    set                 $key 		"sometable_getlist";
    srcache_fetch       GET 		/redis/get 		    key=$key;
    srcache_store       PUT 		/redis/set 		    key=$key;
    srcache_store_statuses          200;
    postgres_pass   	database;
    postgres_query  	"select * from __sometable_getlist()";
    postgres_output 	text;
}

В итоге на запрошенный URL мы получим JSON-ответ, что можем далее использовать как нам будет удобно.

Теперь о скорости.
Время выполнения БД сравнивать нет смысла. Следовательно, необходимо сравнивать как быстро до клиента дойдёт ответ через связку nginx->redis и nginx->php-fpm->php-redis.

У меня связка nginx->redis «выстреливает» за 3 ms,
а nginx->php-fpm->php-redis аж за 12 ms.

Что, впрочем, ожидаемо.