вторник, 30 сентября 2008 г.

Простая репликация в Postgresql

Задача: сделать простую репликацию в Postgresql из мастер-таблицы одной БД в слейв-таблицу другой. Запуск репликации несколько раз в сутки по крону. Для такой задачи использование инструментов типа Slony-I было бы оверхедом. Ну что ж, python нам поможет.

Идея скрипта проста и прямолинейна. Шаг первый - считываем данные из таблиц (см функцию getfromdb). Далее, сопоставляем записи на основе ключей, и синхронизируем в четыре шага:
  1. удаляем записи, которые не находятся в мастер-таблицы;
  2. удаляем изменившиеся записи, маркируя записи мастер-таблицы для добавления;
  3. маркируем записи, находящиеся в мастер-таблице, но отсутствующие в слейв-таблице;
  4. добавляем маркированные записи из мастера в слейв.
Чтобы не усложнять скрипт, приняты два ограничения. Первое - таблицы должны иметь первичный ключ, на основании которого происходит сопоставление. При этом первичный ключ должен следовать первым в списке полей. Второе - реплицируемые поля мастер и слейв таблиц должны иметь одинаковые имена.
#!/usr/bin/env python
# -*- coding: utf-8 -*-

# Usage:
# sync('db_master', 'table_master', 'db_slave', 'table_slave', fields)
# where 'fields' is list of fields in tables to syncronize
#
# NOTE: first field in field list should be a key

import pg

HOST = 'localhost'
DBUSER = 'postgres'
DBPASS = 'postgres'

def getfromdb(db_name, table_name, fields):
# Forming fields
qfields = ''
for field in fields:
if qfields:
qfields += ', ' + field
else:
qfields = field
conn = pg.connect(db_name, HOST, 5432, None, None, DBUSER, DBPASS)
query = "SELECT %s FROM %s" % (qfields, table_name)
result = conn.query(query)
table = {}
for row in result.getresult():
table[row[0]] = row[1:]
conn.close()
return table

def sync(masterdb, mastertable, slavedb, slavetable, fields):
master_data = getfromdb(masterdb, mastertable, fields)
slave_data = getfromdb(slavedb, slavetable, fields)

slave_conn = pg.connect(slavedb, HOST, 5432, None, None, DBUSER, DBPASS)

table = []

# Remove outdated rows
for key, values in slave_data.items():
if key not in master_data:
print "Delete row id=%s" % key
query = "DELETE FROM %s WHERE %s = %s" % \
(slavetable, fields[0], key)
slave_conn.query(query)
elif master_data[key] != slave_data[key]:
print "Update row id=%s" % key
query = "DELETE FROM %s WHERE %s = %s" % \
(slavetable, fields[0], key)
slave_conn.query(query)
# Mark record to update
table.append([key] + list(master_data[key]))

# Mark new records
for key, values in master_data.items():
if key not in slave_data:
table.append([key] + list(values))
print "Insert row fid=%s" % key
# Insert new records
slave_conn.inserttable(slavetable, table)
slave_conn.close()

Пример использования. Следующий код реплицирует поля fields из мастер-таблицы table1 базы db1 в таблицу table2 базы db2. В списке полей f1 - ключевое поле.

fields = ('f1', 'f2', 'f3')
sync('db1', 'table1', 'db2', 'table2', fields)

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

Комментариев нет: