fluent-plugin-postgres-bulk 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: e9beccd75e6813dc745d12c56a8f097aad875bbca6811ec6f93bd8d8246d0538
4
+ data.tar.gz: 7fc02ae6abea7c575d9acbc9482f84e5f2d45470ce84a46386cd0480568d0283
5
+ SHA512:
6
+ metadata.gz: '0716585370c79fe0e3fd09e2448d7e12814ebc7d2043354a5786c5e209efa01045fa974a159abd0148ddb0389607152d5c4c67da1fdac70c78dcf64b378ede15'
7
+ data.tar.gz: 7d3581fb539b80944d4e4d82b7f6f36d01f3b161b49c2375d97383a7aef176e3300e0de2b01711487f23c6e49b3923aa1f2e5f003e294dc57cedef05b753a798
@@ -0,0 +1,3 @@
1
+ Gemfile.lock
2
+ test/plugin/docker/plugins/
3
+ pkg
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
@@ -0,0 +1,102 @@
1
+ fluent-plugin-postgres-bulk
2
+ =====
3
+
4
+ [Fluentd](https://www.fluentd.org/) output plugin to postgres.
5
+
6
+ # Usage
7
+
8
+ ```
9
+ <match **>
10
+ @type postgres_bulk
11
+ host my-host-name.local
12
+ port 5432
13
+ username ksss
14
+ password password
15
+ table any
16
+ column_names id,col1,col2,col3
17
+ </match>
18
+ ```
19
+
20
+ Plugin build and exec query to postgres like this
21
+
22
+ ```sql
23
+ INSERT INTO #{@table} (#{column_names}) VALUES (...),(...),(...);
24
+ ```
25
+
26
+ # Configuration
27
+
28
+ * See also: [Output Plugin Overview](https://docs.fluentd.org/v1.0/articles/output-plugin-overview)
29
+
30
+ ## Fluent::Plugin::PostgresBulkOutput
31
+
32
+ ### host (string) (optional)
33
+
34
+ Database host.
35
+
36
+ Default value: `127.0.0.1`.
37
+
38
+ ### port (integer) (optional)
39
+
40
+ Database port.
41
+
42
+ Default value: `5432`.
43
+
44
+ ### database (string) (required)
45
+
46
+ Database name.
47
+
48
+ ### username (string) (required)
49
+
50
+ Database user.
51
+
52
+ ### password (string) (optional) (secret)
53
+
54
+ Database password.
55
+
56
+ Default value: `(empty)`.
57
+
58
+ ### table (string) (required)
59
+
60
+ Bulk insert table.
61
+
62
+ ### column_names (array) (required)
63
+
64
+ Bulk insert column.
65
+
66
+ # Installation
67
+
68
+ ## RubyGems
69
+
70
+ ```
71
+ $ gem install fluent-plugin-postgres-bulk
72
+ ```
73
+
74
+ ## Bundler
75
+
76
+ Add following line to your Gemfile:
77
+
78
+ ```ruby
79
+ gem "fluent-plugin-postgres-bulk"
80
+ ```
81
+
82
+ And then execute:
83
+
84
+ ```
85
+ $ bundle
86
+ ```
87
+
88
+ # Require
89
+
90
+ - libpq(build pg gem)
91
+
92
+ # Contributing
93
+
94
+ I need your help.
95
+
96
+ # Copyright
97
+
98
+ Copyright (c) 2018 Yuki Kurihara.
99
+
100
+ # LISENCE
101
+
102
+ MIT
@@ -0,0 +1,15 @@
1
+ require "bundler/gem_tasks"
2
+ require 'fileutils'
3
+ require 'rake/testtask'
4
+ Rake::TestTask.new(:test) do |test|
5
+ test.libs << 'lib' << 'test'
6
+ test.pattern = 'test/**/test_*.rb'
7
+ test.verbose = true
8
+
9
+ if ENV["WITH_DOCKER"]
10
+ file "test/plugin/docker/plugins/out_postgres_bulk.rb" => "lib/fluent/plugin/out_postgres_bulk.rb"
11
+ FileUtils.cp "lib/fluent/plugin/out_postgres_bulk.rb", "test/plugin/docker/plugins/out_postgres_bulk.rb"
12
+ end
13
+ end
14
+
15
+ task :default => :test
@@ -0,0 +1,21 @@
1
+ Gem::Specification.new do |gem|
2
+ gem.name = "fluent-plugin-postgres-bulk"
3
+ gem.version = "0.1.0"
4
+ gem.authors = ["ksss"]
5
+ gem.email = ["co000ri@gmail.com"]
6
+ gem.description = %q{fluent plugin for bulk insert to postgres}
7
+ gem.summary = %q{fluent plugin for bulk insert to postgres}
8
+ gem.homepage = "https://github.com/ksss/fluent-plugin-postgres-bulk"
9
+ gem.license = "MIT"
10
+
11
+ gem.files = `git ls-files`.split($\)
12
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
13
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
14
+ gem.require_paths = ["lib"]
15
+
16
+ gem.add_runtime_dependency "fluentd", ['>= 0.14.8', '< 2']
17
+ gem.add_runtime_dependency "pg"
18
+ gem.add_development_dependency "rake"
19
+ gem.add_development_dependency "test-unit"
20
+ gem.add_development_dependency "fluent-logger"
21
+ end
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+ require 'fluent/plugin/output'
3
+ require 'benchmark'
4
+
5
+ module Fluent::Plugin
6
+ class PostgresBulkOutput < Output
7
+ # number of parameters must be between 0 and 65535
8
+ N_PARAMETER_MAX = 65535
9
+ Fluent::Plugin.register_output('postgres_bulk', self)
10
+
11
+ config_param :host, :string, default: '127.0.0.1',
12
+ desc: "Database host."
13
+ config_param :port, :integer, default: 5432,
14
+ desc: "Database port."
15
+ config_param :database, :string,
16
+ desc: "Database name."
17
+ config_param :username, :string,
18
+ desc: "Database user."
19
+ config_param :password, :string, default: '', secret: true,
20
+ desc: "Database password."
21
+ config_param :table, :string,
22
+ desc: "Bulk insert table."
23
+ config_param :column_names, :array,
24
+ desc: "Bulk insert column."
25
+
26
+ def initialize
27
+ super
28
+ require 'pg'
29
+ end
30
+
31
+ def client
32
+ PG.connect(
33
+ host: @host,
34
+ port: @port,
35
+ dbname: @database,
36
+ user: @username,
37
+ password: @password,
38
+ )
39
+ end
40
+
41
+ def multi_workers_ready?
42
+ true
43
+ end
44
+
45
+ def write(chunk)
46
+ handler = client()
47
+ values = build_values(chunk)
48
+ max_slice = N_PARAMETER_MAX / @column_names.length * @column_names.length
49
+ values.each_slice(max_slice) do |slice|
50
+ place_holders = build_place_holders(slice)
51
+ query = "INSERT INTO #{@table} (#{@column_names.join(',')}) VALUES #{place_holders}"
52
+ t = Benchmark.realtime {
53
+ handler.exec_params(query, slice)
54
+ }
55
+ @log.debug("inserted #{values.length / @column_names.length} records in #{(t * 1000).to_i} ms")
56
+ end
57
+ ensure
58
+ handler.close
59
+ end
60
+
61
+ private
62
+
63
+ def build_values(chunk)
64
+ values = []
65
+ chunk.each { |time, record|
66
+ v = @column_names.map { |k|
67
+ record[k]
68
+ }
69
+ values.push(*v)
70
+ }
71
+ values
72
+ end
73
+
74
+ def build_place_holders(values)
75
+ values.each_slice(@column_names.length)
76
+ .map
77
+ .with_index { |cols, i|
78
+ params = cols.map.with_index { |c, j|
79
+ "$#{i * cols.length + j + 1}"
80
+ }
81
+ "(#{params.join(',')})"
82
+ }.join(',')
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,8 @@
1
+ $LOAD_PATH.unshift(File.expand_path("../../", __FILE__))
2
+ require "test-unit"
3
+ require "fluent/test"
4
+ require "fluent/test/driver/output"
5
+ require "fluent/test/helpers"
6
+
7
+ Test::Unit::TestCase.include(Fluent::Test::Helpers)
8
+ Test::Unit::TestCase.extend(Fluent::Test::Helpers)
@@ -0,0 +1,11 @@
1
+ FROM fluent/fluentd:v1.2
2
+
3
+ RUN apk add --update --virtual .build-deps sudo build-base ruby-dev \
4
+ ruby-bundler postgresql-dev
5
+ COPY Gemfile* /fluentd/etc/
6
+ RUN cd /fluentd/etc \
7
+ && bundle install -j4 \
8
+ --deployment \
9
+ --path /fluent/etc/vender/bundle
10
+ COPY fluent.conf /fluentd/etc/fluent.conf
11
+ COPY plugins /fluentd/plugins/
@@ -0,0 +1,3 @@
1
+ FROM postgres:9.6
2
+
3
+ COPY init.sql /docker-entrypoint-initdb.d/
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+
3
+ gem "fluentd", "1.2.0"
4
+ gem "fluent-plugin-record-modifier"
5
+ gem "json"
6
+ gem "pg"
@@ -0,0 +1,23 @@
1
+ version: "3"
2
+ services:
3
+
4
+ postgres:
5
+ build:
6
+ context: .
7
+ dockerfile: Dockerfile-postgres
8
+ restart: always
9
+ environment:
10
+ POSTGRES_DB: test
11
+ POSTGRES_USER: postgres
12
+ POSTGRES_PASSWORD: password
13
+ ports:
14
+ - '5432:5432'
15
+
16
+ fluentd:
17
+ build:
18
+ context: .
19
+ dockerfile: Dockerfile-fluentd
20
+ environment:
21
+ FLUENTD_OPT: -v --gemfile /fluentd/etc/Gemfile
22
+ ports:
23
+ - '24224:24224'
@@ -0,0 +1,33 @@
1
+ <source>
2
+ @type forward
3
+ @id input1
4
+ @label @mainstream
5
+ port 24224
6
+ </source>
7
+
8
+ <label @mainstream>
9
+ <match **>
10
+ @type copy
11
+ <store>
12
+ @type postgres_bulk
13
+ @id output1
14
+ host postgres
15
+ port 5432
16
+ username postgres
17
+ password password
18
+ database test
19
+ table bulk
20
+ column_names col4,col2,col3,col1
21
+ <buffer tag>
22
+ @type file
23
+ path /fluentd/log/bulk
24
+ flush_thread_count 2
25
+ flush_at_shutdown true
26
+ flush_interval 0.01s
27
+ chunk_limit_size 8m
28
+ retry_timeout 1s
29
+ retry_max_interval 1s
30
+ </buffer>
31
+ </store>
32
+ </match>
33
+ </label>
@@ -0,0 +1,7 @@
1
+ DROP TABLE IF EXISTS bulk;
2
+ CREATE TABLE bulk (
3
+ col1 integer,
4
+ col2 text,
5
+ col3 float,
6
+ col4 jsonb
7
+ );
@@ -0,0 +1,75 @@
1
+ require "helper"
2
+ require "fluent/plugin/out_postgres_bulk.rb"
3
+
4
+ class PostgresBulkOutputTest < Test::Unit::TestCase
5
+ setup do
6
+ Fluent::Test.setup
7
+ end
8
+
9
+ def create_driver(conf)
10
+ $execed_params = []
11
+ Fluent::Test::Driver::Output.new(Fluent::Plugin::PostgresBulkOutput) do
12
+ def client
13
+ Object.new.tap do |o|
14
+ def o.exec_params(*args)
15
+ $execed_params << args
16
+ end
17
+
18
+ def o.close
19
+ end
20
+ end
21
+ end
22
+ end.configure(conf)
23
+ end
24
+
25
+ def test_write
26
+ driver = create_driver %[
27
+ database db
28
+ username ksss
29
+ table public.test
30
+ column_names id,col1
31
+ ]
32
+ time = event_time
33
+ driver.run do
34
+ driver.feed("tag", time, {"id" => 1, "col1" => "foo"})
35
+ driver.feed("tag", time, {"id" => 1, "col1" => "bar"})
36
+ driver.feed("tag", time, {"id" => 1, "col1" => "baz"})
37
+ end
38
+ assert_equal($execed_params,
39
+ [
40
+ ["INSERT INTO public.test (id,col1) VALUES ($1,$2),($3,$4),($5,$6)",
41
+ [1, "foo", 1, "bar", 1, "baz"]]
42
+ ]
43
+ )
44
+ end
45
+
46
+ def test_write_huge
47
+ driver = create_driver %[
48
+ database db
49
+ username ksss
50
+ table public.test
51
+ column_names id,col1
52
+ ]
53
+ begin
54
+ orig = Fluent::Plugin::PostgresBulkOutput::N_PARAMETER_MAX
55
+ Fluent::Plugin::PostgresBulkOutput.__send__(:remove_const, :N_PARAMETER_MAX)
56
+ Fluent::Plugin::PostgresBulkOutput.__send__(:const_set, :N_PARAMETER_MAX, 10)
57
+ driver.run do
58
+ 7.times do |i|
59
+ driver.feed("tag", Time.now.to_i, {"col1" => "foo", "id" => i})
60
+ end
61
+ end
62
+ ensure
63
+ Fluent::Plugin::PostgresBulkOutput.__send__(:remove_const, :N_PARAMETER_MAX)
64
+ Fluent::Plugin::PostgresBulkOutput.__send__(:const_set, :N_PARAMETER_MAX, orig)
65
+ end
66
+ assert_equal($execed_params,
67
+ [
68
+ ["INSERT INTO public.test (id,col1) VALUES ($1,$2),($3,$4),($5,$6),($7,$8),($9,$10)",
69
+ [0, "foo", 1, "foo", 2, "foo", 3, "foo", 4, "foo"]],
70
+ ["INSERT INTO public.test (id,col1) VALUES ($1,$2),($3,$4)",
71
+ [5, "foo", 6, "foo"]]
72
+ ]
73
+ )
74
+ end
75
+ end
@@ -0,0 +1,89 @@
1
+ return true unless ENV["WITH_DOCKER"]
2
+
3
+ require "json"
4
+ require "helper"
5
+ require "fluent/plugin/out_postgres_bulk.rb"
6
+ require 'fluent-logger'
7
+ require 'pg'
8
+
9
+ class WithDockerTest < Test::Unit::TestCase
10
+ def connect_pg(count = 1, limit = 10)
11
+ PG.connect(
12
+ host: URI.parse(ENV["DOCKER_HOST"]).hostname,
13
+ port: 5432,
14
+ dbname: "test",
15
+ user: "postgres",
16
+ password: "password",
17
+ ).tap { puts "connect_pg => ok" }
18
+ rescue PG::ConnectionBad
19
+ if count <= limit
20
+ puts "connect_pg => retry"
21
+ sleep 1
22
+ connect_pg(count + 1, limit)
23
+ else
24
+ raise
25
+ end
26
+ end
27
+
28
+ def connect_fluentd(count = 1, limit = 10)
29
+ Fluent::Logger.open(
30
+ Fluent::Logger::FluentLogger,
31
+ "test",
32
+ host: URI.parse(ENV["DOCKER_HOST"]).hostname,
33
+ port: 24224
34
+ )
35
+ if Fluent::Logger.default.connect?
36
+ puts "connect_fluentd => ok"
37
+ elsif count <= limit
38
+ puts "connect_fluentd => retry"
39
+ sleep 1
40
+ connect_fluentd(count + 1, limit)
41
+ else
42
+ raise "can not connect to fluent-logger"
43
+ end
44
+ end
45
+
46
+ setup do
47
+ system("cd #{__dir__}/docker; docker-compose up --build --detach")
48
+ at_exit { system("cd #{__dir__}/docker; docker-compose down") }
49
+ @client = connect_pg
50
+ @client.exec("truncate bulk")
51
+ connect_fluentd
52
+ end
53
+
54
+ teardown do
55
+ at_exit { @client.close }
56
+ end
57
+
58
+ def test_write
59
+ data = 3.times.map do |i|
60
+ {
61
+ "col1" => i,
62
+ "col2" => "$$$aaa$$$",
63
+ "col3" => nil,
64
+ "col4" => JSON.dump({"hello" => "world's end --","value" => i}),
65
+ }
66
+ end
67
+ data.each do |payload|
68
+ ret = Fluent::Logger.post_with_time(
69
+ "tag1",
70
+ payload,
71
+ Time.now.to_i
72
+ )
73
+ unless ret
74
+ raise "can not post to fluentd"
75
+ end
76
+ end
77
+ sleep 2 # wait flush
78
+ @client.exec("select * from bulk") do |r|
79
+ assert_equal(
80
+ r.to_a,
81
+ [
82
+ {"col1"=>"0", "col2"=>"$$$aaa$$$", "col3"=>nil, "col4"=>"{\"hello\": \"world's end --\", \"value\": 0}"},
83
+ {"col1"=>"1", "col2"=>"$$$aaa$$$", "col3"=>nil, "col4"=>"{\"hello\": \"world's end --\", \"value\": 1}"},
84
+ {"col1"=>"2", "col2"=>"$$$aaa$$$", "col3"=>nil, "col4"=>"{\"hello\": \"world's end --\", \"value\": 2}"}
85
+ ]
86
+ )
87
+ end
88
+ end
89
+ end
metadata ADDED
@@ -0,0 +1,148 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: fluent-plugin-postgres-bulk
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - ksss
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2018-05-24 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: fluentd
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 0.14.8
20
+ - - "<"
21
+ - !ruby/object:Gem::Version
22
+ version: '2'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ version: 0.14.8
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: '2'
33
+ - !ruby/object:Gem::Dependency
34
+ name: pg
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
40
+ type: :runtime
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ - !ruby/object:Gem::Dependency
48
+ name: rake
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ - !ruby/object:Gem::Dependency
62
+ name: test-unit
63
+ requirement: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
68
+ type: :development
69
+ prerelease: false
70
+ version_requirements: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ - !ruby/object:Gem::Dependency
76
+ name: fluent-logger
77
+ requirement: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
82
+ type: :development
83
+ prerelease: false
84
+ version_requirements: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: '0'
89
+ description: fluent plugin for bulk insert to postgres
90
+ email:
91
+ - co000ri@gmail.com
92
+ executables: []
93
+ extensions: []
94
+ extra_rdoc_files: []
95
+ files:
96
+ - ".gitignore"
97
+ - Gemfile
98
+ - README.md
99
+ - Rakefile
100
+ - fluent-plugin-postgres-bulk.gemspec
101
+ - lib/fluent/plugin/out_postgres_bulk.rb
102
+ - test/helper.rb
103
+ - test/plugin/docker/Dockerfile-fluentd
104
+ - test/plugin/docker/Dockerfile-postgres
105
+ - test/plugin/docker/Gemfile
106
+ - test/plugin/docker/Gemfile.lock
107
+ - test/plugin/docker/docker-compose.yml
108
+ - test/plugin/docker/fluent.conf
109
+ - test/plugin/docker/init.sql
110
+ - test/plugin/docker/plugins/.gitkeep
111
+ - test/plugin/test_out_postgres_bulk.rb
112
+ - test/plugin/test_with_docker.rb
113
+ homepage: https://github.com/ksss/fluent-plugin-postgres-bulk
114
+ licenses:
115
+ - MIT
116
+ metadata: {}
117
+ post_install_message:
118
+ rdoc_options: []
119
+ require_paths:
120
+ - lib
121
+ required_ruby_version: !ruby/object:Gem::Requirement
122
+ requirements:
123
+ - - ">="
124
+ - !ruby/object:Gem::Version
125
+ version: '0'
126
+ required_rubygems_version: !ruby/object:Gem::Requirement
127
+ requirements:
128
+ - - ">="
129
+ - !ruby/object:Gem::Version
130
+ version: '0'
131
+ requirements: []
132
+ rubyforge_project:
133
+ rubygems_version: 2.7.6
134
+ signing_key:
135
+ specification_version: 4
136
+ summary: fluent plugin for bulk insert to postgres
137
+ test_files:
138
+ - test/helper.rb
139
+ - test/plugin/docker/Dockerfile-fluentd
140
+ - test/plugin/docker/Dockerfile-postgres
141
+ - test/plugin/docker/Gemfile
142
+ - test/plugin/docker/Gemfile.lock
143
+ - test/plugin/docker/docker-compose.yml
144
+ - test/plugin/docker/fluent.conf
145
+ - test/plugin/docker/init.sql
146
+ - test/plugin/docker/plugins/.gitkeep
147
+ - test/plugin/test_out_postgres_bulk.rb
148
+ - test/plugin/test_with_docker.rb