fluent-plugin-postgres-bulk 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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