Skip to content

Commit 3646ca3

Browse files
Merge pull request #17 from Sage/feature_index_alias
Feature - Index Alias
2 parents 76d4b98 + abd5c5c commit 3646ca3

File tree

13 files changed

+424
-13
lines changed

13 files changed

+424
-13
lines changed

Gemfile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
source 'https://rubygems.org'
22

3+
gem 'json', '2.1.0'
4+
35
# Specify your gem's dependencies in elastic_search_framework.gemspec
46
gemspec
57

README.md

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,60 @@ This is used to set the port that should be used for all connection actions.
5151

5252
> DEFAULT = 9200
5353
54+
# IndexAlias
55+
To define an index alias within elasticsearch, create an index alias class that extends from the `ElasticSearchFramework::IndexAlias` module.
56+
57+
Example:
58+
59+
60+
```ruby
61+
class ExampleIndexAlias
62+
extend ElasticSearchFramework::IndexAlias
63+
64+
index ExampleIndex, active: true
65+
index ExampleIndex2, active: false
66+
67+
name :example
68+
end
69+
```
70+
71+
**attributes**
72+
73+
- **index** [Hash] [Required] [Multi] This is used to specify the indexes associated with this alias and which index is the current active index for the alias to point to.
74+
- **name** [Hash] [Required] [Single] This is used to specify the unique name of the index alias.
75+
76+
---
77+
78+
Index Aliases are required to decouple your application from a specific index and allow you to handle index updates without downtime.
79+
80+
To change the mapping of an existing index Create a new version of the index, then associate the new index with the index alias as an inactive index `active: false`. This will allow index writes and deletes to be performed on both indexes so no new data is lost while you perform a `_reindex` operation to move existing data from the old index into the new index.
81+
82+
Once you have `_reindexed` into your new index you can then de-activate the old index `active: false` and activate the new index `active: true` in your index alias. This will swap all requests to the new index.
83+
84+
Doing the above steps should enable you to seamlessly transition between 1 index and another when mapping/analyzer changes are required.
85+
86+
## #create
87+
This method is called to create the index alias within an elastic search instance.
88+
> This method is idempotent and will modify the index alias if it already exists.
89+
90+
> All associated indexes must exist before this method is called.
91+
92+
ExampleIndexAlias.create
93+
94+
## Index operation methods
95+
The following index operation methods are available for an index alias:
96+
97+
- `#get_item`
98+
- `#put_item`
99+
- `#delete_item`
100+
- `#query`
101+
102+
> `#put_item` calls will be performed against all indexes associated with the alias.
103+
104+
> `#delete_item` calls will be performed against all indexes associated with the alias.
105+
106+
> Details for how to use the above index operation methods can be found below.
107+
54108
# Index
55109
To define an index within elasticsearch, create an index definition class that extends from the `ElasticSearchFramework::Index` module.
56110

lib/elastic_search_framework.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
require_relative 'elastic_search_framework/exceptions'
99
require_relative 'elastic_search_framework/repository'
1010
require_relative 'elastic_search_framework/index'
11+
require_relative 'elastic_search_framework/index_alias'
1112
require_relative 'elastic_search_framework/query'
1213

1314
module ElasticSearchFramework

lib/elastic_search_framework/index.rb

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,19 @@ module ElasticSearchFramework
22
module Index
33
attr_accessor :index_settings
44

5-
def index(name:)
5+
def index(name:, version: nil)
66
unless instance_variable_defined?(:@elastic_search_index_def)
7-
instance_variable_set(:@elastic_search_index_def, name: "#{name}")
7+
instance_variable_set(:@elastic_search_index_def, name: "#{name}#{version}")
8+
instance_variable_set(:@elastic_search_index_version, version: version) unless version.nil?
89
else
910
raise ElasticSearchFramework::Exceptions::IndexError.new("[#{self.class}] - Duplicate index description. Name: #{name}.")
1011
end
1112
end
1213

14+
def version
15+
instance_variable_defined?(:@elastic_search_index_version) ? instance_variable_get(:@elastic_search_index_version) : 0
16+
end
17+
1318
def id(field)
1419
unless instance_variable_defined?(:@elastic_search_index_id)
1520
instance_variable_set(:@elastic_search_index_id, field)
@@ -160,7 +165,7 @@ def host
160165
end
161166

162167
def repository
163-
ElasticSearchFramework::Repository.new
168+
@repository ||= ElasticSearchFramework::Repository.new
164169
end
165170

166171
def get_item(id:, type: 'default')
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
module ElasticSearchFramework
2+
module IndexAlias
3+
def index(klass, active:)
4+
unless instance_variable_defined?(:@elastic_search_indexes)
5+
instance_variable_set(:@elastic_search_indexes, [])
6+
end
7+
indexes = self.instance_variable_get(:@elastic_search_indexes)
8+
indexes << {klass: klass, active: active}
9+
instance_variable_set(:@elastic_search_indexes, indexes)
10+
end
11+
12+
def indexes
13+
self.instance_variable_get(:@elastic_search_indexes)
14+
end
15+
16+
def name(name)
17+
unless instance_variable_defined?(:@elastic_search_index_alias_name)
18+
instance_variable_set(:@elastic_search_index_alias_name, "#{name}")
19+
else
20+
raise ElasticSearchFramework::Exceptions::IndexError.new("[#{self.class}] - Duplicate index alias name: #{name}.")
21+
end
22+
end
23+
24+
def valid?
25+
indexes.select { |i| i[:active] == true }.length == 1
26+
end
27+
28+
def create
29+
if !valid?
30+
raise ElasticSearchFramework::Exceptions::IndexError.new("[#{self.class}] - Invalid Index alias.")
31+
end
32+
33+
uri = URI("#{host}/_aliases")
34+
35+
payload = {
36+
actions: [],
37+
}
38+
39+
indexes.each do |index|
40+
action = nil
41+
if exists?(index: index[:klass])
42+
action = "remove" if !index[:active]
43+
else
44+
action = "add" if index[:active]
45+
end
46+
next if action.nil?
47+
48+
payload[:actions] << {action => {index: index[:klass].full_name, alias: self.full_name}}
49+
end
50+
51+
request = Net::HTTP::Post.new(uri.request_uri)
52+
request.body = JSON.dump(payload)
53+
request.content_type = "application/json"
54+
55+
response = repository.with_client do |client|
56+
client.request(request)
57+
end
58+
59+
is_valid_response?(response.code) || Integer(response.code) == 404
60+
end
61+
62+
def delete
63+
uri = URI("#{host}/_all/_aliases/#{full_name}")
64+
65+
request = Net::HTTP::Delete.new(uri.request_uri)
66+
67+
response = repository.with_client do |client|
68+
client.request(request)
69+
end
70+
71+
is_valid_response?(response.code) || Integer(response.code) == 404
72+
end
73+
74+
def exists?(index:)
75+
uri = URI("#{host}/#{index.full_name}/_alias/#{full_name}")
76+
77+
request = Net::HTTP::Get.new(uri.request_uri)
78+
79+
response = repository.with_client do |client|
80+
client.request(request)
81+
end
82+
83+
return false if response.code == "404"
84+
85+
result = nil
86+
if is_valid_response?(response.code)
87+
result = JSON.parse(response.body)
88+
end
89+
90+
return true if !result.nil? && result[index.full_name]["aliases"] != nil
91+
return false
92+
end
93+
94+
def is_valid_response?(code)
95+
[200, 201, 202].include?(Integer(code))
96+
end
97+
98+
def full_name
99+
name = instance_variable_get(:@elastic_search_index_alias_name)
100+
if ElasticSearchFramework.namespace != nil
101+
"#{ElasticSearchFramework.namespace}#{ElasticSearchFramework.namespace_delimiter}#{name.downcase}"
102+
else
103+
name.downcase
104+
end
105+
end
106+
107+
def description
108+
index = indexes.last[:klass]
109+
hash = index.instance_variable_get(:@elastic_search_index_def)
110+
if index.instance_variable_defined?(:@elastic_search_index_id)
111+
hash[:id] = index.instance_variable_get(:@elastic_search_index_id)
112+
else
113+
hash[:id] = :id
114+
end
115+
hash
116+
end
117+
118+
def host
119+
"#{ElasticSearchFramework.host}:#{ElasticSearchFramework.port}"
120+
end
121+
122+
def repository
123+
@repository ||= ElasticSearchFramework::Repository.new
124+
end
125+
126+
def get_item(id:, type: "default")
127+
repository.get(index: self, id: id, type: type)
128+
end
129+
130+
def put_item(type: "default", item:)
131+
indexes.each do |index|
132+
repository.set(entity: item, index: index[:klass], type: type)
133+
end
134+
end
135+
136+
def delete_item(id:, type: "default")
137+
indexes.each do |index|
138+
repository.drop(index: index[:klass], id: id, type: type)
139+
end
140+
end
141+
142+
def query
143+
ElasticSearchFramework::Query.new(index: self)
144+
end
145+
end
146+
end
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
module ElasticSearchFramework
2-
VERSION = '2.1.0'
2+
VERSION = '2.2.0'
33
end

script/docker-compose.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ version: '2.1'
22

33
services:
44
elasticsearch:
5-
image: elasticsearch:alpine
5+
image: elasticsearch:5.6-alpine
66
container_name: elasticsearch
77
environment:
88
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"

0 commit comments

Comments
 (0)