Использование партиций PostgreSQL c Django
Партицирование в Postgresql(и в MySQL :)) очень полезно при хранении больших масcивов данных, особенно если нам нужно с ними работать разделяя по конкретному критерию. В моем случае у меня есть логи работы над проектами и эти логи хранятся в партициях, по одной партиции на каждый проект.
Использование партицирования в Django не сложно, но есть один подводный камень. Django выполняет запрос на вставку с конструкцией “returning id” которая должна вернуть id вставленной записи. Но так как мы используем партицирование — у нас есть функция котороя делает вставку данных и возвращает тригер. Для того что бы это обойти мы просто будем просить django не использовать “returning id” и все.
Сейчас разберем как использовать партицирование шаг за шагом.
шаг 1: создаем модели
class Project(models.Model):
title = models.CharField(max_length=150)
class ProjectLog(models.Model):
project = models.ForeignKey(Project)
user = models.ForeignKey(User)
content = models.CharField(max_length=500)
шаг 2: создаем миграции, что бы добавить в базу данных логику партицирования у нас будет две функции: первая для создания новой таблици для каждого проекта и вторая для вставки данных в нужную таблицу
def make_partitions_operants(apps, schema_editor):
Project = apps.get_model('project', 'Project')
ProjectLog = apps.get_model('project', 'ProjectLog')
sql = '''
create or replace function insert_into_projectlog() returns trigger as $$
begin
execute 'insert into %(projectlog_db_table)s_' || NEW.project_id || ' values(($1).*)' using NEW;
return NULL;
end $$ language 'plpgsql';
create or replace function create_projectlog_partition() returns trigger as $$
begin
execute 'create table %(projectlog_db_table)s_' || NEW.id || ' (
check (project_id = ' || NEW.id || ')
) inherits (%(projectlog_db_table)s);';
return NEW;
end $$ language 'plpgsql';
create trigger trigger_create_projectlog_partition
after insert on %(project_db_table)s
for each row execute procedure create_projectlog_partition();
create trigger trigger_insert_into_projectlog
before insert on %(projectlog_db_table)s
for each row execute procedure insert_into_projectlog();
''' % {
'projectlog_db_table': ProjectLog._meta.db_table,
'project_db_table': Project._meta.db_table,
'rowtype': '%%ROWTYPE',
}
schema_editor.execute(sql)
class Migration(migrations.Migration):
dependencies = [
('project', '0001_initial'),
]
operations = [
migrations.RunPython(make_partitions_operants),
]
я использую родные миграции для Django 1.7
шаг 3: нам нужно переопределить метод save у ProjectLog
def save(self, *args, **kwargs):
from django.db.backends.postgresql_psycopg2.base import DatabaseFeatures
if not self.pk:
DatabaseFeatures.can_return_id_from_insert = False
super(ProjectLog, self).save(*args, **kwargs)
DatabaseFeatures.can_return_id_from_insert = True
здесь мы переписываем значение can_return_id_from_insert
, чтобы в запросе не использовался “returning id”
Вот и все что нам потребовалось сделать.
Теперь каждый раз когда будет создаваться запись о новом проекте, в базе дыннх будет появляться новая таблица и все логи будут писаться по разным таблицам.
Пример как это работает:
test=# \dt
List of relations
Schema | Name | Type | Owner
--------+--------------------+-------+-------
public | project_project | table | quard
public | project_projectlog | table | quard
(2 rows)
test=# INSERT INTO project_project(title) VALUES('blog');
INSERT 0 1
test=# \dt
List of relations
Schema | Name | Type | Owner
--------+----------------------+-------+----------------
public | project_project | table | quard
public | project_projectlog | table | quard
public | project_projectlog_1 | table | vadymzakovinko
(3 rows)
test=# INSERT INTO project_project(title) VALUES('education');
INSERT 0 1
test=# \dt
List of relations
Schema | Name | Type | Owner
--------+----------------------+-------+----------------
public | project_project | table | quard
public | project_projectlog | table | quard
public | project_projectlog_1 | table | vadymzakovinko
public | project_projectlog_2 | table | vadymzakovinko
(4 rows)
test=# INSERT INTO project_projectlog(user_id, project_id, content) VALUES(1, 1, 'new article');
INSERT 0 0
test=# SELECT * FROM project_projectlog;
id | user_id | project_id | content
----+---------+------------+-------------
1 | 1 | 1 | new article
(1 row)
test=# SELECT * FROM project_projectlog_1;
id | user_id | project_id | content
----+---------+------------+-------------
1 | 1 | 1 | new article
(1 row)
test=# SELECT * FROM project_projectlog_2;
id | user_id | project_id | content
----+---------+------------+---------
(0 rows)
test=# INSERT INTO project_projectlog(user_id, project_id, content) VALUES(1, 2, 'django 1.7 migrations');
INSERT 0 0
test=# SELECT * FROM project_projectlog_2;
id | user_id | project_id | content
----+---------+------------+-----------------------
2 | 1 | 2 | django 1.7 migrations
(1 row)
test=# SELECT * FROM project_projectlog_1;
id | user_id | project_id | content
----+---------+------------+-------------
1 | 1 | 1 | new article
(1 row)
test=# SELECT * FROM project_projectlog;
id | user_id | project_id | content
----+---------+------------+-----------------------
1 | 1 | 1 | new article
2 | 1 | 2 | django 1.7 migrations
(2 rows)