PostgreSQL内核扩展之 - ElasticSearch同步插件

背景介绍

Elasticsearch 是开源搜索平台的新成员,实时数据分析的神器,发展迅猛,基于 Lucene、RESTful、分布式、面向云计算设计、实时搜索、全文搜索、稳定、高可靠、可扩展、安装+使用方便。

PostgreSQL 是起源自伯克利大学的开源数据库,历史悠久,内核扩展性极强,用户横跨各个行业。
关于PostgreSQL的内核扩展指南请参考

传统数据库与搜索引擎ES如何同步

例如用户需要将数据库中某些数据同步到ES建立索引,传统的方法需要应用来负责数据的同步。
这种方法会增加一定的开发成本,时效也不是非常的实时。

PostgreSQL与ES结合有什么好处

PostgreSQL的扩展插件pg-es-fdw,使用PostgreSQL的foreign data wrap,允许直接在数据库中读写ES,方便用户实时的在ES建立索引。
这种方法不需要额外的程序支持,时效也能得到保障。

case

安装PostgreSQL 9.5

略,需要包含 --with-python

安装 ES on CentOS 7

# yum install -y java-1.7.0-openjdk

# rpm --import https://packages.elastic.co/GPG-KEY-elasticsearch

# vi /etc/yum.repos.d/es.repo
[elasticsearch-2.x]
name=Elasticsearch repository for 2.x packages
baseurl=https://packages.elastic.co/elasticsearch/2.x/centos
gpgcheck=1
gpgkey=https://packages.elastic.co/GPG-KEY-elasticsearch
enabled=1

# yum install -y elasticsearch

# /bin/systemctl daemon-reload
# /bin/systemctl enable elasticsearch.service
# /bin/systemctl start elasticsearch.service

# python --version
Python 2.7.5

# curl -X GET 'http://localhost:9200'
{
  "name" : "Red Wolf",
  "cluster_name" : "elasticsearch",
  "version" : {
    "number" : "2.3.3",
    "build_hash" : "218bdf10790eef486ff2c41a3df5cfa32dadcfde",
    "build_timestamp" : "2016-05-17T15:40:04Z",
    "build_snapshot" : false,
    "lucene_version" : "5.5.0"
  },
  "tagline" : "You Know, for Search"
}

python client

# easy_install pip
# pip install elasticsearch

PostgreSQL 插件 multicorn

# wget http://api.pgxn.org/dist/multicorn/1.3.2/multicorn-1.3.2.zip
# unzip multicorn-1.3.2.zip
# cd multicorn-1.3.2
# export PATH=/home/digoal/pgsql9.5/bin:$PATH
# make && make install
# su - digoal
$ psql
postgres=# create extension multicorn ;
CREATE EXTENSION

PostgreSQL 插件 pg-es-fdw (foreign server基于multicorn)

# git clone https://github.com/Mikulas/pg-es-fdw /tmp/pg-es-fdw
# cd /tmp/pg-es-fdw
# export PATH=/home/digoal/pgsql9.5/bin:$PATH
# python setup.py install
# su - digoal
$ psql

使用例子

基于multicorn创建es foreign server

CREATE SERVER multicorn_es FOREIGN DATA WRAPPER multicorn
OPTIONS (
  wrapper 'dite.ElasticsearchFDW'
);

创建测试表

CREATE TABLE articles (
    id serial PRIMARY KEY,
    title text NOT NULL,
    content text NOT NULL,
    created_at timestamp
);

创建外部表

CREATE FOREIGN TABLE articles_es (
    id bigint,
    title text,
    content text
) SERVER multicorn_es OPTIONS (host '127.0.0.1', port '9200', node 'test', index 'articles');

创建触发器

对实体表,创建触发器函数,在用户对实体表插入,删除,更新时,通过触发器函数自动将数据同步到对应ES的外部表。
同步过程调用FDW的接口,对ES进行索引的建立,更新,删除。

CREATE OR REPLACE FUNCTION index_article() RETURNS trigger AS $def$
    BEGIN
        INSERT INTO articles_es (id, title, content) VALUES
            (NEW.id, NEW.title, NEW.content);
        RETURN NEW;
    END;
$def$ LANGUAGE plpgsql;

CREATE OR REPLACE FUNCTION reindex_article() RETURNS trigger AS $def$
    BEGIN
        UPDATE articles_es SET
            title = NEW.title,
            content = NEW.content
        WHERE id = NEW.id;
        RETURN NEW;
    END;
$def$ LANGUAGE plpgsql;

CREATE OR REPLACE FUNCTION delete_article() RETURNS trigger AS $def$
    BEGIN
        DELETE FROM articles_es a WHERE a.id = OLD.id;
        RETURN OLD;
    END;
$def$ LANGUAGE plpgsql;

CREATE TRIGGER es_insert_article
    AFTER INSERT ON articles
    FOR EACH ROW EXECUTE PROCEDURE index_article();

CREATE TRIGGER es_update_article
    AFTER UPDATE OF title, content ON articles
    FOR EACH ROW
    WHEN (OLD. IS DISTINCT FROM NEW.)
    EXECUTE PROCEDURE reindex_article();

CREATE TRIGGER es_delete_article
    BEFORE DELETE ON articles
    FOR EACH ROW EXECUTE PROCEDURE delete_article();

测试

curl 'localhost:9200/test/articles/_search?q=:&pretty'
psql -c 'SELECT * FROM articles'

写入实体表,自动同步到ES
psql -c "INSERT INTO articles (title, content, created_at) VALUES ('foo', 'spike', Now());"
psql -c 'SELECT * FROM articles'

查询ES,检查数据是否已同步
curl 'localhost:9200/test/articles/_search?q=:&pretty'

更新实体表,数据自动同步到ES
psql -c "UPDATE articles SET content='yeay it updates\!' WHERE title='foo'"

查询ES数据是否更新
curl 'localhost:9200/test/articles/_search?q=:&pretty'

参考

https://www.elastic.co/guide/en/elasticsearch/reference/current/setup-repositories.html
http://www.vpsee.com/2014/05/install-and-play-with-elasticsearch/
https://github.com/Mikulas/pg-es-fdw
https://wiki.postgresql.org/wiki/Fdw
http://multicorn.org/
http://pgxn.org/dist/multicorn/
http://multicorn.readthedocs.io/en/latest/index.html

小结

  1. PostgreSQL提供的FDW接口,允许用户在数据库中直接操纵外部的数据源,所以支持ES只是一个例子,还可以支持更多的数据源。
    这是已经支持的,几乎涵盖了所有的数据源。
    https://wiki.postgresql.org/wiki/Fdw
  2. multicorn在FDW接口的上层再抽象了一层,支持使用python写FDW接口,方便快速试错,如果对性能要求不是那么高,直接用multicore就可以了。
  3. 开发人员如何编写FDW? 可以参考一下如下:
    http://multicorn.readthedocs.io/en/latest/index.html

    https://www.postgresql.org/docs/9.6/static/fdwhandler.html

附录

###
### Author: Mikulas Dite
### Time-stamp: <2015-06-09 21:54:14 dwa>

from multicorn import ForeignDataWrapper
from multicorn.utils import log_to_postgres as log2pg

from functools import partial

import httplib
import json
import logging

class ElasticsearchFDW (ForeignDataWrapper):

    def __init__(self, options, columns):
        super(ElasticsearchFDW, self).__init__(options, columns)

        self.host = options.get('host', 'localhost')
        self.port = int(options.get('port', '9200'))
        self.node = options.get('node', '')
        self.index = options.get('index', '')

        self.columns = columns

    def get_rel_size(self, quals, columns):
        """Helps the planner by returning costs.
        Returns a tuple of the form (nb_row, avg width)
        """

        conn = httplib.HTTPConnection(self.host, self.port)
        conn.request("GET", "/%s/%s/_count" % (self.node, self.index))
        resp = conn.getresponse()
        if not 200 == resp.status:
            return (0, 0)

        raw = resp.read()
        data = json.loads(raw)
        # log2pg('MARK RESPONSE: >>%d<<' % data['count'], logging.DEBUG)
        return (data['count'], len(columns) * 100)

    def execute(self, quals, columns):
        conn = httplib.HTTPConnection(self.host, self.port)
        conn.request("GET", "/%s/%s/_search&size=10000" % (self.node, self.index))
        resp = conn.getresponse()
        if not 200 == resp.status:
            yield {0, 0}

        raw = resp.read()
        data = json.loads(raw)
        for hit in data['hits']['hits']:
            row = {}
            for col in columns:
                if col == 'id':
                    row[col] = hit['_id']
                elif col in hit['_source']:
                    row[col] = hit['_source'][col]
            yield row

    @property
    def rowid_column(self):
        """Returns a column name which will act as a rowid column,
        for delete/update operations. This can be either an existing column
        name, or a made-up one.
        This column name should be subsequently present in every
        returned resultset.
        """
        return 'id';

    def es_index(self, id, values):
        content = json.dumps(values)

        conn = httplib.HTTPConnection(self.host, self.port)
        conn.request("PUT", "/%s/%s/%s" % (self.node, self.index, id), content)
        resp = conn.getresponse()
        if not 200 == resp.status:
            return

        raw = resp.read()
        data = json.loads(raw)

        return data

    def insert(self, new_values):
        log2pg('MARK Insert Request - new values:  %s' % new_values, logging.DEBUG)

        if not 'id' in new_values:
             log2pg('INSERT requires "id" column.  Missing in: %s' % new_values, logging.ERROR)

        id = new_values['id']
        new_values.pop('id', None)
        return self.es_index(id, new_values)

    def update(self, id, new_values):
        new_values.pop('id', None)
        return self.es_index(id, new_values)

    def delete(self, id):
        conn = httplib.HTTPConnection(self.host, self.port)
        conn.request("DELETE", "/%s/%s/%s" % (self.node, self.index, id))
        resp = conn.getresponse()
        if not 200 == resp.status:
            log2pg('Failed to delete: %s' % resp.read(), logging.ERROR)
            return

        raw = resp.read()
        return json.loads(raw)

## Local Variables: ***
## mode:python ***
## coding: utf-8 ***
## End: ***
时间: 2024-09-11 19:13:05

PostgreSQL内核扩展之 - ElasticSearch同步插件的相关文章

找对业务G点, 体验酸爽 - PostgreSQL内核扩展指南

通用数据库就像带装修的房子一样,如果按数据库的功能划分,可以分为豪华装修.精装.简装. PostgreSQL从SQL兼容性.功能.性能.稳定性等方面综合评价的话,绝对算得上豪华装修级别的,用户拎包入住就可以. 不过通用的毕竟是通用的,如果G点不对的话,再豪华的装修你也爽不起来,这是很多通用数据库的弊病,但是今天PostgreSQL数据库会彻底颠覆你对通用数据库的看法. 基于PostgreSQL打造最好用的私人订制数据库 花了2个通宵,写了一份PostgreSQL内核扩展指南,时间有限,内容以入门

PostgreSQL 内核扩展之 - 管理十亿级3D扫描数据(基于Lidar产生的point cloud数据)

背景知识 还记得成龙演的那部<十二生肖>里用3D扫描和打印技术复制的生肖吗?3D打印是近几年兴起的一种技术,除了存储 物体表面的位置信息,还有颜色,密度等信息 .而3D扫描其实在军用领域很早以前就有了.如果使用普通的数据库来存储,得把这些属性拆开来存.而在PostgreSQL中,你完全不需要把这些属性拆开,他们本来就是一体的,用好PG的扩展接口就好了.PostgreSQL 扩展指南 :https://yq.aliyun.com/articles/55981 什么是Lidar 3D扫描的基础知识

PostgreSQL 数据库扩展语言编程 之 plpgsql - 1

标签 PostgreSQL , plpgsql , server programming , 存储过程 , 函数 , UDF , create language , plpython , pljava , plr , plperl , pltcl 背景 PostgreSQL是一个开放的数据库,开发性表现在支持自定义数据类型.索引方法.索引.操作符.聚合.窗口.服务端编程语言等等. 所以我们可以看到在PostgreSQL的生态中有很多贴近业务的用法,比如在PostgreSQL中存储和处理化学分子.

最佳Firefox书签同步插件Xmarks使用教程

Xmarks,前身是Foxmarks,作为著名的Firefox浏览器的社会化书签同步插件,目前已经被下载了超过1500万次,并提供适用于IE和Safari浏览器版本. 对于频繁折腾系统,或经常在异地使用Firefox浏览器的用户而言,安装Xmarks插件对收藏夹的书签进行同步,无疑减少了丢失书签的风险,提高了安全性和稳定性. 此外,Xmarks不仅是一款同步/恢复插件,更提供了社会化书签的发掘.分享.评价的功能,为用户寻找和选择信息提供了方便.据说目前Xmarks的系统数据库里已经收集了6亿+条

谷歌发布谷歌文档与微软Office同步插件

北京时间11月23日凌晨消息,谷歌今天在官方博客发布了一款名为Cloud Connect的微软Office同步插件,实现谷歌文档与微软Office的无缝对接. 这款插件对于谷歌文档战略意义重大.每当用户在微软Office中点击"保存",Cloud Connect都会将文档自动同步到谷歌文档账户中.该插件支持微软Word.PowerPoint和Excel,即日起接受Google Apps企业用户报名体验,并将很快对所有消费者开放. 目前,微软已经在最新的Office 2010中整合了在线

iframeWin For Easy UI. 为 Easy UI 扩展的支持IFrame插件

iframeWin For Easy UI. 为 Easy UI 扩展的支持IFrame插件 在一个项目中用了Easy UI,但是发现里面的 Dialog .Window.Messager 弹窗都不支持IFrame的方式, 于是自己动手基于Easy UI实现了一个插件. 目前Easy UI仍然使用GPL开源协议.   JS引用: <script type="text/javascript" src="jquery.min.js"></script&

Elasticsearch推荐插件篇(head,sense,marvel)

安装head head插件可以用来快速查看elasticsearch中的数据概况以及非全量的数据,也支持控件化查询和rest请求,但是体验都不是很好. 一般就用它来看各个索引的数据量以及分片的状态. [root@localnode1 bin]# ./plugin install mobz/elasticsearch-head -> Installing mobz/elasticsearch-head... Plugins directory [/usr/elk/elasticsearch-2.4

HybridDB for PostgreSQL(Greenplum)有哪些内核扩展

HybridDB for PostgreSQL 是基于 Greenplum Database 开源数据库项目开发,由阿里云数据库内核团队深度扩展及优化,到目前为止,我们已经增加了许多功能性能,许多功能走在了社区的前面. OSS_EXT OSS是阿里云对象存储产品,OSS_EXT功能打通了HybridDB for PostgreSQL和OSS数据通道,极大地方便用户将数据并行从OSS导入或导出到OSS,降低了上云门槛,并支持通过gzip进行OSS外部表文件压缩,大量节省存储空间及成本.我们测试的一

PostgreSQL 内核自带的Oracle兼容函数

标签 PostgreSQL , Oracle , 兼容函数 , Oracle compatible functions 背景 PostgreSQL功能上基本可以和Oracle对齐,只是一些语法或者函数名不一样,所以为了做到兼容,有一些兼容包和兼容函数的出现. PostgreSQL自带了一些Oracle兼容函数,如果你觉得不够意思,可以通过orafce插件继续扩展PostgreSQL与Oracle的兼容性(本文不涉及orafce包). http://pgxn.org/dist/orafce/ ht