flex-admin 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +20 -0
- data/README.md +24 -0
- data/VERSION +1 -0
- data/bin/flex-admin +112 -0
- data/flex-admin.gemspec +20 -0
- data/lib/flex-admin.rb +7 -0
- data/lib/flex/admin.rb +114 -0
- data/lib/flex/live_reindex.rb +248 -0
- data/lib/tasks.rake +17 -0
- metadata +73 -0
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2012-2013 by Domizio Demichelis
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# Flex-admin
|
2
|
+
|
3
|
+
Simple tool to dump/load one or more elasticsearch indices and types.
|
4
|
+
|
5
|
+
## Links
|
6
|
+
|
7
|
+
* [Flex Repository](https://github.com/ddnexus/flex)
|
8
|
+
* [Flex Project (Global Documentation)](http://ddnexus.github.io/flex/doc/)
|
9
|
+
* [flex-admin Gem (Specific Documentation)](http://ddnexus.github.io/flex/doc/6-flex-admin)
|
10
|
+
* [Issues](https://github.com/ddnexus/flex-admin/issues)
|
11
|
+
* [Pull Requests](https://github.com/ddnexus/flex-admin/pulls)
|
12
|
+
|
13
|
+
## Branches
|
14
|
+
|
15
|
+
The master branch reflects the last published gem. Then you may find a next-version branch (named after the version string), with the commits that will be merged in master just before publishing the next gem version. The next-version branch may get rebased or force pushed.
|
16
|
+
|
17
|
+
## Credits
|
18
|
+
|
19
|
+
Special thanks for their sponsorship to [Escalate Media](http://www.escalatemedia.com) and [Barquin International](http://www.barquin.com).
|
20
|
+
|
21
|
+
## Copyright
|
22
|
+
|
23
|
+
Copyright (c) 2012-2013 by [Domizio Demichelis](mailto://dd.nexus@gmail.com)<br>
|
24
|
+
See [LICENSE](https://github.com/ddnexus/flex-admin/blob/master/LICENSE) for details.
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
1.0.1
|
data/bin/flex-admin
ADDED
@@ -0,0 +1,112 @@
|
|
1
|
+
#! /usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'optparse'
|
4
|
+
require 'flex-admin'
|
5
|
+
|
6
|
+
options = Flex::Admin::Tasks.new.default_options
|
7
|
+
|
8
|
+
version = File.read(File.expand_path('../../VERSION', __FILE__)).strip
|
9
|
+
copy = "flex-admin #{version} (c) 2012-2013 by Domizio Demichelis"
|
10
|
+
optparse = OptionParser.new do |opts|
|
11
|
+
|
12
|
+
opts.banner = <<-banner
|
13
|
+
|
14
|
+
flex-admin:
|
15
|
+
Generic binary tool to dump/load data from/to any elasticsearch index (no app needed).
|
16
|
+
If you need to migrate data, use the flex live-reindexing.
|
17
|
+
|
18
|
+
Usage:
|
19
|
+
flex-admin <command> [options]
|
20
|
+
<command>:
|
21
|
+
dump dumps the data from one or more elasticsearch indices
|
22
|
+
load loads a dumpfile
|
23
|
+
stats prints the full elasticsearch stats
|
24
|
+
|
25
|
+
Notice: The load command will load the dump-file into elasticsearch without removing any pre-existent data.
|
26
|
+
If you need fresh indices, use the flex:index:delete and flex:index:create rake tasks from your
|
27
|
+
application, which will also recreate the mapping.
|
28
|
+
banner
|
29
|
+
|
30
|
+
|
31
|
+
opts.separator ''
|
32
|
+
opts.separator 'Common options:'
|
33
|
+
|
34
|
+
opts.on( '-f', '--file [FILE]', "The path of the dumpfile (default: '#{options[:file]}')" ) do |f|
|
35
|
+
options[:file] = f
|
36
|
+
end
|
37
|
+
|
38
|
+
opts.on( '-r', '--[no-]verbose', "Run verbosely (default: '#{options[:verbose]}')" ) do |q|
|
39
|
+
options[:verbose] = q
|
40
|
+
end
|
41
|
+
|
42
|
+
opts.separator ''
|
43
|
+
opts.separator 'Dump options:'
|
44
|
+
|
45
|
+
opts.on( '-i', '--index [INDEX_OR_INDICES]', Array, 'The index or comma separated indices to dump (default: all indices)' ) do |i|
|
46
|
+
options[:index] = i
|
47
|
+
end
|
48
|
+
|
49
|
+
opts.on( '-t', '--type [TYPE_OR_TYPES]', Array, 'The type or comma separated types to dump (default: all types)' ) do |t|
|
50
|
+
options[:type] = t
|
51
|
+
end
|
52
|
+
|
53
|
+
opts.on( '-s', '--scroll [TIME]', Integer, "The ElasticSearch scroll time (default: #{options[:scroll]})" ) do |s|
|
54
|
+
options[:scroll] = s
|
55
|
+
end
|
56
|
+
|
57
|
+
opts.on( '-z', '--size [SIZE]', Integer, "The chunk size to dump per shard (default: #{options[:size]} * number of shards)" ) do |z|
|
58
|
+
options[:size] = z
|
59
|
+
end
|
60
|
+
|
61
|
+
opts.separator ''
|
62
|
+
opts.separator 'Load options:'
|
63
|
+
|
64
|
+
opts.on( '-m', '--index-map [INDEX_MAP]', 'The index rename map (example: -m=dumped_index_name:loaded_index_name,a:b)') do |m|
|
65
|
+
options[:index_map] = Hash[m.split(',').map{|i|i.split(':')}]
|
66
|
+
end
|
67
|
+
|
68
|
+
opts.on( '-o', '--timeout [SECONDS]', Integer, "The http_client timeout for bulk loading (default: #{options[:timeout]} seconds)" ) do |o|
|
69
|
+
options[:timeout] = o
|
70
|
+
end
|
71
|
+
|
72
|
+
opts.on( '-b', '--batch-size [BATCH_SIZE]', Integer, "The batch size to load (default: #{options[:batch_size]})" ) do |z|
|
73
|
+
options[:batch_size] = z
|
74
|
+
end
|
75
|
+
|
76
|
+
opts.separator ''
|
77
|
+
opts.separator 'Other options:'
|
78
|
+
|
79
|
+
opts.on( '-v', '--version', 'Shows the version and exits' ) do
|
80
|
+
puts version
|
81
|
+
exit
|
82
|
+
end
|
83
|
+
|
84
|
+
opts.on_tail( '-h', '--help', 'Displays this screen' ) do
|
85
|
+
puts copy
|
86
|
+
puts opts
|
87
|
+
exit
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
91
|
+
|
92
|
+
optparse.parse!
|
93
|
+
command = ARGV.first
|
94
|
+
exec "#{$0} -h" if command.nil?
|
95
|
+
puts copy
|
96
|
+
|
97
|
+
case command
|
98
|
+
|
99
|
+
when 'dump'
|
100
|
+
Flex::Admin::Tasks.new(options).dump_to_file(true)
|
101
|
+
|
102
|
+
when 'load'
|
103
|
+
Flex::Admin::Tasks.new(options).load_from_file
|
104
|
+
|
105
|
+
when 'stats'
|
106
|
+
puts '>> puts Flex.index_stats.to_yaml'
|
107
|
+
puts Flex.index_stats.to_yaml
|
108
|
+
|
109
|
+
else
|
110
|
+
puts "unknown command: #{command.inspect}"
|
111
|
+
|
112
|
+
end
|
data/flex-admin.gemspec
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'date'
|
2
|
+
version = File.read(File.expand_path('../VERSION', __FILE__)).strip
|
3
|
+
|
4
|
+
Gem::Specification.new do |s|
|
5
|
+
s.name = 'flex-admin'
|
6
|
+
s.summary = 'Dump/load/rename/live-redindex one or more elasticsearch indices and types.'
|
7
|
+
s.description = 'Provides binary and rake tasks to dump, load and optionally rename indices. Implements live-reindex with hot-swap of old code/index with new code/index.'
|
8
|
+
s.homepage = 'http://github.com/ddnexus/flex-admin'
|
9
|
+
s.authors = ["Domizio Demichelis"]
|
10
|
+
s.email = 'dd.nexus@gmail.com'
|
11
|
+
s.executables = %w[flex-admin]
|
12
|
+
s.extra_rdoc_files = %w[README.md]
|
13
|
+
s.files = `git ls-files -z`.split("\0")
|
14
|
+
s.version = version
|
15
|
+
s.date = Date.today.to_s
|
16
|
+
s.required_rubygems_version = ">= 1.3.6"
|
17
|
+
s.rdoc_options = %w[--charset=UTF-8]
|
18
|
+
|
19
|
+
s.add_runtime_dependency 'flex', version
|
20
|
+
end
|
data/lib/flex-admin.rb
ADDED
data/lib/flex/admin.rb
ADDED
@@ -0,0 +1,114 @@
|
|
1
|
+
module Flex
|
2
|
+
module Admin
|
3
|
+
|
4
|
+
class Tasks
|
5
|
+
|
6
|
+
attr_reader :options
|
7
|
+
|
8
|
+
def initialize(overrides={})
|
9
|
+
options = Flex::Utils.env2options *default_options.keys
|
10
|
+
|
11
|
+
options[:size] = options[:size].to_i if options[:size]
|
12
|
+
options[:timeout] = options[:timeout].to_i if options[:timeout]
|
13
|
+
options[:batch_size] = options[:batch_size].to_i if options[:batch_size]
|
14
|
+
options[:index_map] = Hash[options[:index_map].split(',').map{|i|i.split(':')}] if options[:index_map] && options[:index_map].is_a?(String)
|
15
|
+
|
16
|
+
@options = default_options.merge(options).merge(overrides)
|
17
|
+
end
|
18
|
+
|
19
|
+
def default_options
|
20
|
+
@default_options ||= { :file => './flex.dump',
|
21
|
+
:index => Conf.variables[:index],
|
22
|
+
:type => Conf.variables[:type],
|
23
|
+
:scroll => '5m',
|
24
|
+
:size => 50,
|
25
|
+
:timeout => 20,
|
26
|
+
:batch_size => 1000,
|
27
|
+
:verbose => true,
|
28
|
+
:index_map => nil }
|
29
|
+
end
|
30
|
+
|
31
|
+
def dump_to_file(cli=false)
|
32
|
+
vars = { :index => cli ? options[:index] : (options[:index] || Flex::Tasks.new.config_hash.keys),
|
33
|
+
:type => options[:type] }
|
34
|
+
if options[:verbose]
|
35
|
+
total_hits = Flex.count(vars)['count'].to_i
|
36
|
+
total_count = 0
|
37
|
+
pbar = ProgBar.new(total_hits)
|
38
|
+
dump_stats = Hash.new { |hash, key| hash[key] = Hash.new { |h, k| h[k] = 0 } }
|
39
|
+
file_size = 0
|
40
|
+
end
|
41
|
+
vars.merge! :params => { :scroll => options[:scroll],
|
42
|
+
:size => options[:size],
|
43
|
+
:fields => '_source,*' }
|
44
|
+
|
45
|
+
file = options[:file].is_a?(String) ? File.open(options[:file], 'wb') : options[:file]
|
46
|
+
path = file.path
|
47
|
+
|
48
|
+
Flex.dump_all(vars) do |batch|
|
49
|
+
bulk_string = ''
|
50
|
+
batch.each do |document|
|
51
|
+
dump_stats[document['_index']][document['_type']] += 1 if options[:verbose]
|
52
|
+
bulk_string << Flex.build_bulk_string(document)
|
53
|
+
end
|
54
|
+
file.puts bulk_string
|
55
|
+
if options[:verbose]
|
56
|
+
total_count += batch.size
|
57
|
+
pbar.pbar.inc(batch.size)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
file_size = file.size if options[:verbose]
|
61
|
+
file.close
|
62
|
+
|
63
|
+
if options[:verbose]
|
64
|
+
formatted_file_size = file_size.to_s.reverse.gsub(/...(?=.)/, '\&,').reverse
|
65
|
+
pbar.pbar.finish
|
66
|
+
puts "\n***** WARNING: Expected document to dump: #{total_hits}, dumped: #{total_count}. *****" \
|
67
|
+
unless total_hits == total_count
|
68
|
+
puts "\nDumped #{total_count} documents to #{path} (size: #{formatted_file_size} bytes)"
|
69
|
+
puts dump_stats.to_yaml
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def load_from_file
|
74
|
+
Configuration.http_client.options[:timeout] = options[:timeout]
|
75
|
+
chunk_size = options[:batch_size] * 2 # 2 lines per doc
|
76
|
+
bulk_string = ''
|
77
|
+
file = options[:file].is_a?(String) ? File.open(options[:file]) : options[:file]
|
78
|
+
path = file.path
|
79
|
+
if options[:verbose]
|
80
|
+
line_count = 0
|
81
|
+
file.lines { line_count += 1 }
|
82
|
+
file.rewind
|
83
|
+
puts "\nLoading from #{path}...\n"
|
84
|
+
pbar = ProgBar.new(line_count / 2, options[:batch_size])
|
85
|
+
end
|
86
|
+
file.lines do |line|
|
87
|
+
bulk_string << (options[:index_map] ? map_index(line) : line)
|
88
|
+
if (file.lineno % chunk_size) == 0
|
89
|
+
result = Flex.post_bulk_string :bulk_string => bulk_string
|
90
|
+
bulk_string = ''
|
91
|
+
pbar.process_result(result, options[:batch_size]) if options[:verbose]
|
92
|
+
end
|
93
|
+
end
|
94
|
+
# last chunk
|
95
|
+
unless bulk_string == ''
|
96
|
+
result = Flex.post_bulk_string :bulk_string => bulk_string
|
97
|
+
pbar.process_result(result, (file.lineno % chunk_size) / 2) if options[:verbose]
|
98
|
+
end
|
99
|
+
file.close
|
100
|
+
pbar.finish if options[:verbose]
|
101
|
+
end
|
102
|
+
|
103
|
+
private
|
104
|
+
|
105
|
+
def map_index(line)
|
106
|
+
joined_keys = options[:index_map].keys.join('|')
|
107
|
+
line.sub(/"_index":"(#{joined_keys})"/) do |match_string|
|
108
|
+
options[:index_map].has_key?($1) ? %("_index":"#{options[:index_map][$1]}") : match_string
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
@@ -0,0 +1,248 @@
|
|
1
|
+
module Flex
|
2
|
+
# private module
|
3
|
+
module LiveReindex
|
4
|
+
|
5
|
+
class MissingRedisError < StandardError; end
|
6
|
+
class LiveReindexInProgressError < StandardError; end
|
7
|
+
class MissingAppIdError < StandardError; end
|
8
|
+
class MissingStopIndexingProcError < StandardError; end
|
9
|
+
class MissingEnsureIndicesError < StandardError; end
|
10
|
+
class MissingOnReindexBlockError < StandardError; end
|
11
|
+
class ExtraIndexError < StandardError; end
|
12
|
+
class MultipleReindexError < StandardError; end
|
13
|
+
|
14
|
+
# private module
|
15
|
+
module Redis
|
16
|
+
|
17
|
+
KEYS = { :pid => 'flex-reindexing-pid',
|
18
|
+
:changes => 'flex-reindexing-changes' }
|
19
|
+
|
20
|
+
extend self
|
21
|
+
|
22
|
+
def method_missing(command, key, *args)
|
23
|
+
return unless Conf.redis
|
24
|
+
Conf.redis.send(command, "#{KEYS[key]}-#{Conf.app_id}", *args)
|
25
|
+
end
|
26
|
+
|
27
|
+
def reset_keys
|
28
|
+
KEYS.keys.each { |k| del k }
|
29
|
+
end
|
30
|
+
|
31
|
+
def init
|
32
|
+
begin
|
33
|
+
require 'redis'
|
34
|
+
rescue LoadError
|
35
|
+
raise MissingRedisError, 'The live-reindex feature rely on redis. Please, install redis and the "redis" gem.'
|
36
|
+
end
|
37
|
+
raise MissingAppIdError, 'You must set the Flex::Configuration.app_id, and be sure you deploy it before live-reindexing.' \
|
38
|
+
if Conf.app_id.nil? || Conf.app_id.empty?
|
39
|
+
raise LiveReindexInProgressError, %(It looks like a live-reindex is in progress (PID #{get(:pid)}). If you are sure that there is no live-reindex in progress, please run the "flex:reset_redis_keys" rake task and retry.) \
|
40
|
+
if get(:pid)
|
41
|
+
reset_keys # just in case
|
42
|
+
set(:pid, $$)
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
extend self
|
48
|
+
|
49
|
+
def on_reindex(&block)
|
50
|
+
@reindex = block
|
51
|
+
end
|
52
|
+
|
53
|
+
def on_each_change(&block)
|
54
|
+
@each_change = block
|
55
|
+
end
|
56
|
+
attr_reader :each_change
|
57
|
+
|
58
|
+
def on_stop_indexing(&block)
|
59
|
+
@stop_indexing = block
|
60
|
+
end
|
61
|
+
|
62
|
+
def reindex(opts={})
|
63
|
+
yield self
|
64
|
+
perform(opts)
|
65
|
+
end
|
66
|
+
|
67
|
+
def reindex_indices(opts={})
|
68
|
+
yield self if block_given?
|
69
|
+
|
70
|
+
opts[:verbose] = true unless opts.has_key?(:verbose)
|
71
|
+
opts[:index] ||= opts.delete(:indices) || config_hash.keys
|
72
|
+
|
73
|
+
# we override the on_reindex eventually set
|
74
|
+
on_reindex do
|
75
|
+
migrate_indices(opts)
|
76
|
+
end
|
77
|
+
|
78
|
+
perform(opts)
|
79
|
+
end
|
80
|
+
|
81
|
+
def should_prefix_index?
|
82
|
+
Redis.get(:pid) == $$.to_s
|
83
|
+
end
|
84
|
+
|
85
|
+
def should_track_change?
|
86
|
+
pid = Redis.get(:pid)
|
87
|
+
!!pid && !(pid == $$.to_s)
|
88
|
+
end
|
89
|
+
|
90
|
+
def track_change(action, document)
|
91
|
+
Redis.rpush(:changes, MultiJson.encode([action, document]))
|
92
|
+
end
|
93
|
+
|
94
|
+
# use this method when you are tracking the change of another app
|
95
|
+
# you must pass the app_id of the app being affected by the change
|
96
|
+
def track_external_change(app_id, action, document)
|
97
|
+
return unless Conf.redis
|
98
|
+
Conf.redis.rpush("#{KEYS[:changes]}-#{app_id}", MultiJson.encode([action, document]))
|
99
|
+
end
|
100
|
+
|
101
|
+
def prefix_index(index)
|
102
|
+
base = unprefix_index(index)
|
103
|
+
# raise if base is not included in @ensure_indices
|
104
|
+
raise ExtraIndexError, "The index #{base} is missing from the :ensure_indices option. Reindexing aborted." \
|
105
|
+
if @ensure_indices && !@ensure_indices.include?(base)
|
106
|
+
prefixed = @timestamp + base
|
107
|
+
unless @indices.include?(base)
|
108
|
+
unless Flex.exist?(:index => prefixed)
|
109
|
+
config_hash[base] = {} unless config_hash.has_key?(base)
|
110
|
+
Flex.POST "/#{prefixed}", config_hash[base]
|
111
|
+
end
|
112
|
+
@indices |= [base]
|
113
|
+
end
|
114
|
+
prefixed
|
115
|
+
end
|
116
|
+
|
117
|
+
# remove the (eventual) prefix
|
118
|
+
def unprefix_index(index)
|
119
|
+
index.sub(/^\d{14}_/, '')
|
120
|
+
end
|
121
|
+
|
122
|
+
private
|
123
|
+
|
124
|
+
def config_hash
|
125
|
+
@config_hash ||= ModelTasks.new.config_hash
|
126
|
+
end
|
127
|
+
|
128
|
+
def perform(opts={})
|
129
|
+
Conf.logger.warn 'Safe reindex is disabled!' if opts[:safe_reindex] == false
|
130
|
+
Redis.init
|
131
|
+
@indices = []
|
132
|
+
@timestamp = Time.now.strftime('%Y%m%d%H%M%S_')
|
133
|
+
@ensure_indices = nil
|
134
|
+
|
135
|
+
unless opts[:on_stop_indexing] == false || Conf.on_stop_indexing == false
|
136
|
+
@stop_indexing ||= Conf.on_stop_indexing || raise(MissingStopIndexingProcError, 'The on_stop_indexing block is not set.')
|
137
|
+
end
|
138
|
+
|
139
|
+
raise MissingOnReindexBlockError, 'You must configure an on_reindex block.' \
|
140
|
+
unless @reindex
|
141
|
+
|
142
|
+
raise MissingEnsureIndicesError, 'You must pass the :ensure_indices option when you pass the :models option.' \
|
143
|
+
if opts.has_key?(:models) && !opts.has_key?(:ensure_indices)
|
144
|
+
if opts[:ensure_indices]
|
145
|
+
@ensure_indices = opts.delete(:ensure_indices)
|
146
|
+
@ensure_indices = @ensure_indices.split(',') unless @ensure_indices.is_a?(Array)
|
147
|
+
each_change = @each_change
|
148
|
+
@each_change = nil
|
149
|
+
migrate_indices(:index => @ensure_indices)
|
150
|
+
@each_change = each_change
|
151
|
+
end
|
152
|
+
|
153
|
+
@reindex.call
|
154
|
+
|
155
|
+
# when the reindexing is finished we try to empty the changes list a few times
|
156
|
+
tries = 0
|
157
|
+
bulk_string = ''
|
158
|
+
until (count = Redis.llen(:changes)) == 0 || tries > 9
|
159
|
+
count.times { bulk_string << build_bulk_string_from_change(Redis.lpop(:changes))}
|
160
|
+
Flex.post_bulk_string(:bulk_string => bulk_string)
|
161
|
+
bulk_string = ''
|
162
|
+
tries += 1
|
163
|
+
end
|
164
|
+
# at this point the changes list should be empty or contain the minimum number of changes we could achieve live
|
165
|
+
# the @stop_indexing should ensure to stop/suspend all the actions that would produce changes in the indices being reindexed
|
166
|
+
@stop_indexing.call if @stop_indexing
|
167
|
+
# if we have still changes, we can index them (until the list will be empty)
|
168
|
+
bulk_string = ''
|
169
|
+
while (change = Redis.lpop(:changes))
|
170
|
+
bulk_string << build_bulk_string_from_change(change)
|
171
|
+
end
|
172
|
+
Flex.post_bulk_string(:bulk_string => bulk_string)
|
173
|
+
|
174
|
+
# deletes the old indices and create the aliases to the new
|
175
|
+
@indices.each do |index|
|
176
|
+
Flex.delete_index :index => index
|
177
|
+
Flex.put_index_alias :alias => index,
|
178
|
+
:index => @timestamp + index
|
179
|
+
end
|
180
|
+
# after the execution of this method the user should deploy the new code and then resume the regular app processing
|
181
|
+
|
182
|
+
# we redefine this method so it will raise an error if any new live-reindex is attempted during this session.
|
183
|
+
unless opts[:safe_reindex] == false
|
184
|
+
class_eval <<-ruby, __FILE__, __LINE__
|
185
|
+
def perform(*)
|
186
|
+
raise MultipleReindexError, "Multiple live-reindex attempted! You cannot use any reindexing method multiple times in the same session or you may corrupt your index/indices! The previous reindexing in this session successfully reindexed and swapped the new index/indices: #{@indices.map{|i| @timestamp + i}.join(', ')}. You must deploy now, and run the other reindexing in single successive deploys ASAP. Notice that if the code-changes that you are about to deploy rely on the successive reindexings that have been aborted, your app may fail. If you are working in development mode you must restart the session now. The next time you can silence this error by passing :safe_reindex => false"
|
187
|
+
end
|
188
|
+
ruby
|
189
|
+
end
|
190
|
+
|
191
|
+
rescue Exception
|
192
|
+
# delete all the created indices
|
193
|
+
@indices ||=[]
|
194
|
+
@indices.each do |index|
|
195
|
+
Flex.delete_index :index => @timestamp + index
|
196
|
+
end
|
197
|
+
raise
|
198
|
+
|
199
|
+
ensure
|
200
|
+
Redis.reset_keys
|
201
|
+
end
|
202
|
+
|
203
|
+
|
204
|
+
def migrate_indices(opts)
|
205
|
+
opts[:verbose] = true unless opts.has_key?(:verbose)
|
206
|
+
pbar = ProgBar.new(Flex.count(opts)['count'], nil, "index #{opts[:index].inspect}: ") if opts[:verbose]
|
207
|
+
|
208
|
+
Flex.dump_all(opts) do |batch|
|
209
|
+
result = process_and_post_batch(batch)
|
210
|
+
pbar.process_result(result, batch.size) if opts[:verbose]
|
211
|
+
end
|
212
|
+
|
213
|
+
pbar.finish if opts[:verbose]
|
214
|
+
end
|
215
|
+
|
216
|
+
def process_and_post_batch(batch)
|
217
|
+
bulk_string = ''
|
218
|
+
batch.each do |document|
|
219
|
+
bulk_string << build_bulk_string('index', document)
|
220
|
+
end
|
221
|
+
Flex.post_bulk_string(:bulk_string => bulk_string)
|
222
|
+
end
|
223
|
+
|
224
|
+
def build_bulk_string_from_change(change)
|
225
|
+
action, document = MultiJson.decode(change)
|
226
|
+
return '' unless @indices.include?(unprefix_index(document['_index']))
|
227
|
+
build_bulk_string(action, document)
|
228
|
+
end
|
229
|
+
|
230
|
+
def build_bulk_string(action, document)
|
231
|
+
result = if @each_change
|
232
|
+
document.extend(Result::Document)
|
233
|
+
document.extend(Result::DocumentLoader)
|
234
|
+
@each_change.call(action, document)
|
235
|
+
else
|
236
|
+
[{ action => document }]
|
237
|
+
end
|
238
|
+
result = [result] unless result.is_a?(Array)
|
239
|
+
bulk_string = ''
|
240
|
+
result.compact.each do |hash|
|
241
|
+
act, doc = hash.to_a.flatten
|
242
|
+
bulk_string << Flex.build_bulk_string(doc, :action => act)
|
243
|
+
end
|
244
|
+
bulk_string
|
245
|
+
end
|
246
|
+
|
247
|
+
end
|
248
|
+
end
|
data/lib/tasks.rake
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'flex'
|
2
|
+
require 'flex-admin'
|
3
|
+
|
4
|
+
env = defined?(Rails) ? :environment : []
|
5
|
+
|
6
|
+
namespace :flex do
|
7
|
+
namespace :admin do
|
8
|
+
|
9
|
+
desc 'Dumps the data from one or more ElasticSearch indices to a file'
|
10
|
+
task(:dump => env) { Flex::Admin::Tasks.new.dump_to_file }
|
11
|
+
|
12
|
+
desc 'Loads a dumpfile into ElasticSearch'
|
13
|
+
task(:load => env) { Flex::Admin::Tasks.new.load_from_file }
|
14
|
+
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
metadata
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: flex-admin
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Domizio Demichelis
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-08-12 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: flex
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - '='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 1.0.1
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - '='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 1.0.1
|
30
|
+
description: Provides binary and rake tasks to dump, load and optionally rename indices.
|
31
|
+
Implements live-reindex with hot-swap of old code/index with new code/index.
|
32
|
+
email: dd.nexus@gmail.com
|
33
|
+
executables:
|
34
|
+
- flex-admin
|
35
|
+
extensions: []
|
36
|
+
extra_rdoc_files:
|
37
|
+
- README.md
|
38
|
+
files:
|
39
|
+
- LICENSE
|
40
|
+
- README.md
|
41
|
+
- VERSION
|
42
|
+
- bin/flex-admin
|
43
|
+
- flex-admin.gemspec
|
44
|
+
- lib/flex-admin.rb
|
45
|
+
- lib/flex/admin.rb
|
46
|
+
- lib/flex/live_reindex.rb
|
47
|
+
- lib/tasks.rake
|
48
|
+
homepage: http://github.com/ddnexus/flex-admin
|
49
|
+
licenses: []
|
50
|
+
post_install_message:
|
51
|
+
rdoc_options:
|
52
|
+
- --charset=UTF-8
|
53
|
+
require_paths:
|
54
|
+
- lib
|
55
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
56
|
+
none: false
|
57
|
+
requirements:
|
58
|
+
- - ! '>='
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '0'
|
61
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
62
|
+
none: false
|
63
|
+
requirements:
|
64
|
+
- - ! '>='
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: 1.3.6
|
67
|
+
requirements: []
|
68
|
+
rubyforge_project:
|
69
|
+
rubygems_version: 1.8.25
|
70
|
+
signing_key:
|
71
|
+
specification_version: 3
|
72
|
+
summary: Dump/load/rename/live-redindex one or more elasticsearch indices and types.
|
73
|
+
test_files: []
|