Mappings: Remove ability to set path for _id and _routing on 2.0+ indexes

_id and _routing now no longer support the 'path' setting on indexes
created with 2.0.  Indexes created before 2.0 still support this
setting for backcompat.

closes #6730
This commit is contained in:
Ryan Ernst 2015-02-09 21:50:55 -08:00
parent 6544890e14
commit b3474f6b25
15 changed files with 106 additions and 134 deletions

View File

@ -175,6 +175,15 @@ def generate_index(client, version):
}
}
}
mappings['meta_fields'] = {
'_id': {
'path': 'myid'
},
'_routing': {
'path': 'myrouting'
}
}
client.indices.create(index='test', body={
'settings': {

View File

@ -25,31 +25,3 @@ using the appropriate mapping attributes:
}
--------------------------------------------------
The `_id` mapping can also be associated with a `path` that will be used
to extract the id from a different location in the source document. For
example, having the following mapping:
[source,js]
--------------------------------------------------
{
"tweet" : {
"_id" : {
"path" : "post_id"
}
}
}
--------------------------------------------------
Will cause `1` to be used as the id for:
[source,js]
--------------------------------------------------
{
"message" : "You know, for Search",
"post_id" : "1"
}
--------------------------------------------------
This does require an additional lightweight parsing step while indexing,
in order to extract the id to decide which shard the index operation
will be executed on.

View File

@ -24,42 +24,6 @@ has been provided (or derived from the doc). A delete operation will be
broadcasted to all shards if no routing value is provided and `_routing`
is required.
[float]
==== path
The routing value can be provided as an external value when indexing
(and still stored as part of the document, in much the same way
`_source` is stored). But, it can also be automatically extracted from
the index doc based on a `path`. For example, having the following
mapping:
[source,js]
--------------------------------------------------
{
"comment" : {
"_routing" : {
"required" : true,
"path" : "blog.post_id"
}
}
}
--------------------------------------------------
Will cause the following doc to be routed based on the `111222` value:
[source,js]
--------------------------------------------------
{
"text" : "the comment text"
"blog" : {
"post_id" : "111222"
}
}
--------------------------------------------------
Note, using `path` without explicit routing value provided required an
additional (though quite fast) parsing phase.
[float]
==== id uniqueness

View File

@ -183,19 +183,11 @@ public class DocumentMapper implements ToXContent {
this.indexSettings = indexSettings;
this.builderContext = new Mapper.BuilderContext(indexSettings, new ContentPath(1));
this.rootObjectMapper = builder.build(builderContext);
IdFieldMapper idFieldMapper = new IdFieldMapper();
if (indexSettings != null) {
String idIndexed = indexSettings.get("index.mapping._id.indexed");
if (idIndexed != null && Booleans.parseBoolean(idIndexed, false)) {
FieldType fieldType = new FieldType(IdFieldMapper.Defaults.FIELD_TYPE);
fieldType.setTokenized(false);
idFieldMapper = new IdFieldMapper(fieldType);
}
}
// UID first so it will be the first stored field to load (so will benefit from "fields: []" early termination
this.rootMappers.put(UidFieldMapper.class, new UidFieldMapper());
this.rootMappers.put(IdFieldMapper.class, idFieldMapper);
this.rootMappers.put(RoutingFieldMapper.class, new RoutingFieldMapper());
this.rootMappers.put(IdFieldMapper.class, new IdFieldMapper(indexSettings));
this.rootMappers.put(RoutingFieldMapper.class, new RoutingFieldMapper(indexSettings));
// add default mappers, order is important (for example analyzer should come before the rest to set context.analyzer)
this.rootMappers.put(SizeFieldMapper.class, new SizeFieldMapper(indexSettings));
this.rootMappers.put(IndexFieldMapper.class, new IndexFieldMapper());

View File

@ -29,6 +29,7 @@ import org.apache.lucene.index.Term;
import org.apache.lucene.queries.TermsFilter;
import org.apache.lucene.search.*;
import org.apache.lucene.util.BytesRef;
import org.elasticsearch.Version;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.lucene.BytesRefs;
@ -58,7 +59,7 @@ import static org.elasticsearch.index.mapper.MapperBuilders.id;
import static org.elasticsearch.index.mapper.core.TypeParsers.parseField;
/**
*
*
*/
public class IdFieldMapper extends AbstractFieldMapper<String> implements InternalMapper, RootMapper {
@ -115,7 +116,7 @@ public class IdFieldMapper extends AbstractFieldMapper<String> implements Intern
Map.Entry<String, Object> entry = iterator.next();
String fieldName = Strings.toUnderscoreCase(entry.getKey());
Object fieldNode = entry.getValue();
if (fieldName.equals("path")) {
if (fieldName.equals("path") && parserContext.indexVersionCreated().before(Version.V_2_0_0)) {
builder.path(fieldNode.toString());
iterator.remove();
}
@ -125,17 +126,10 @@ public class IdFieldMapper extends AbstractFieldMapper<String> implements Intern
}
private final String path;
private final boolean writePre20Settings;
public IdFieldMapper() {
this(new FieldType(Defaults.FIELD_TYPE));
}
public IdFieldMapper(FieldType fieldType) {
this(Defaults.NAME, Defaults.INDEX_NAME, fieldType, null);
}
protected IdFieldMapper(String name, String indexName, FieldType fieldType, Boolean docValues) {
this(name, indexName, Defaults.BOOST, fieldType, docValues, Defaults.PATH, null, null, null, ImmutableSettings.EMPTY);
public IdFieldMapper(Settings indexSettings) {
this(Defaults.NAME, Defaults.INDEX_NAME, Defaults.BOOST, idFieldType(indexSettings), null, Defaults.PATH, null, null, null, indexSettings);
}
protected IdFieldMapper(String name, String indexName, float boost, FieldType fieldType, Boolean docValues, String path,
@ -144,6 +138,15 @@ public class IdFieldMapper extends AbstractFieldMapper<String> implements Intern
super(new Names(name, indexName, indexName, name), boost, fieldType, docValues, Lucene.KEYWORD_ANALYZER,
Lucene.KEYWORD_ANALYZER, postingsProvider, docValuesProvider, null, null, fieldDataSettings, indexSettings);
this.path = path;
this.writePre20Settings = Version.indexCreated(indexSettings).before(Version.V_2_0_0);
}
private static FieldType idFieldType(Settings indexSettings) {
FieldType fieldType = new FieldType(Defaults.FIELD_TYPE);
if (indexSettings.getAsBoolean("index.mapping._id.indexed", true) == false) {
fieldType.setTokenized(false);
}
return fieldType;
}
public String path() {
@ -351,7 +354,7 @@ public class IdFieldMapper extends AbstractFieldMapper<String> implements Intern
if (includeDefaults || fieldType.indexOptions() != Defaults.FIELD_TYPE.indexOptions()) {
builder.field("index", indexTokenizeOptionToString(fieldType.indexOptions() != IndexOptions.NONE, fieldType.tokenized()));
}
if (includeDefaults || path != Defaults.PATH) {
if (writePre20Settings && (includeDefaults || path != Defaults.PATH)) {
builder.field("path", path);
}

View File

@ -23,6 +23,7 @@ import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.FieldType;
import org.apache.lucene.index.IndexOptions;
import org.elasticsearch.Version;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.lucene.Lucene;
@ -107,7 +108,7 @@ public class RoutingFieldMapper extends AbstractFieldMapper<String> implements I
if (fieldName.equals("required")) {
builder.required(nodeBooleanValue(fieldNode));
iterator.remove();
} else if (fieldName.equals("path")) {
} else if (fieldName.equals("path") && parserContext.indexVersionCreated().before(Version.V_2_0_0)) {
builder.path(fieldNode.toString());
iterator.remove();
}
@ -118,11 +119,11 @@ public class RoutingFieldMapper extends AbstractFieldMapper<String> implements I
private boolean required;
private final String path;
private final boolean writePre20Settings;
public RoutingFieldMapper() {
this(new FieldType(Defaults.FIELD_TYPE), Defaults.REQUIRED, Defaults.PATH, null, null, null, ImmutableSettings.EMPTY);
public RoutingFieldMapper(Settings indexSettings) {
this(Defaults.FIELD_TYPE, Defaults.REQUIRED, Defaults.PATH, null, null, null, indexSettings);
}
protected RoutingFieldMapper(FieldType fieldType, boolean required, String path, PostingsFormatProvider postingsProvider,
@ -131,6 +132,7 @@ public class RoutingFieldMapper extends AbstractFieldMapper<String> implements I
Lucene.KEYWORD_ANALYZER, postingsProvider, docValuesProvider, null, null, fieldDataSettings, indexSettings);
this.required = required;
this.path = path;
this.writePre20Settings = Version.indexCreated(indexSettings).before(Version.V_2_0_0);
}
@Override
@ -234,7 +236,7 @@ public class RoutingFieldMapper extends AbstractFieldMapper<String> implements I
if (includeDefaults || required != Defaults.REQUIRED) {
builder.field("required", required);
}
if (includeDefaults || path != Defaults.PATH) {
if (writePre20Settings && (includeDefaults || path != Defaults.PATH)) {
builder.field("path", path);
}
builder.endObject();

View File

@ -21,6 +21,7 @@ package org.elasticsearch.document;
import com.google.common.base.Charsets;
import org.elasticsearch.Version;
import org.elasticsearch.action.admin.indices.alias.Alias;
import org.elasticsearch.action.bulk.BulkItemResponse;
import org.elasticsearch.action.bulk.BulkRequest;
@ -35,6 +36,7 @@ import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.action.update.UpdateRequestBuilder;
import org.elasticsearch.action.update.UpdateResponse;
import org.elasticsearch.cluster.metadata.IndexMetaData;
import org.elasticsearch.common.bytes.BytesArray;
import org.elasticsearch.common.settings.ImmutableSettings;
import org.elasticsearch.common.xcontent.XContentBuilder;
@ -594,7 +596,8 @@ public class BulkTests extends ElasticsearchIntegrationTest {
.endObject()
.endObject()
.endObject();
assertAcked(prepareCreate("test").addMapping("type", builder));
assertAcked(prepareCreate("test").addMapping("type", builder)
.setSettings(IndexMetaData.SETTING_VERSION_CREATED, Version.V_1_4_2_ID));
ensureYellow("test");
String brokenBuildRequestData = "{\"index\": {} }\n" +
@ -620,7 +623,8 @@ public class BulkTests extends ElasticsearchIntegrationTest {
.endObject()
.endObject()
.endObject();
assertAcked(prepareCreate("test").addMapping("type", builder));
assertAcked(prepareCreate("test").addMapping("type", builder)
.setSettings(IndexMetaData.SETTING_VERSION_CREATED, Version.V_1_4_2_ID));
ensureYellow("test");
String brokenBuildRequestData = "{\"index\": {} }\n" +

View File

@ -19,6 +19,10 @@
package org.elasticsearch.index.mapper.id;
import org.elasticsearch.Version;
import org.elasticsearch.cluster.metadata.IndexMetaData;
import org.elasticsearch.common.settings.ImmutableSettings;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
@ -28,15 +32,13 @@ import org.elasticsearch.index.mapper.ParsedDocument;
import org.elasticsearch.index.mapper.internal.IdFieldMapper;
import org.elasticsearch.index.mapper.internal.UidFieldMapper;
import org.elasticsearch.test.ElasticsearchSingleNodeTest;
import org.junit.Test;
import static org.hamcrest.Matchers.*;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.nullValue;
/**
*/
public class IdMappingTests extends ElasticsearchSingleNodeTest {
@Test
public void simpleIdTests() throws Exception {
String mapping = XContentFactory.jsonBuilder().startObject().startObject("type")
.endObject().endObject().string();
@ -68,8 +70,7 @@ public class IdMappingTests extends ElasticsearchSingleNodeTest {
assertThat(doc.rootDoc().get(UidFieldMapper.NAME), notNullValue());
assertThat(doc.rootDoc().get(IdFieldMapper.NAME), nullValue());
}
@Test
public void testIdIndexed() throws Exception {
String mapping = XContentFactory.jsonBuilder().startObject().startObject("type")
.startObject("_id").field("index", "not_analyzed").endObject()
@ -93,13 +94,13 @@ public class IdMappingTests extends ElasticsearchSingleNodeTest {
assertThat(doc.rootDoc().get(UidFieldMapper.NAME), notNullValue());
assertThat(doc.rootDoc().get(IdFieldMapper.NAME), notNullValue());
}
@Test
public void testIdPath() throws Exception {
String mapping = XContentFactory.jsonBuilder().startObject().startObject("type")
.startObject("_id").field("path", "my_path").endObject()
.endObject().endObject().string();
DocumentMapper docMapper = createIndex("test").mapperService().documentMapperParser().parse(mapping);
Settings settings = ImmutableSettings.builder().put(IndexMetaData.SETTING_VERSION_CREATED, Version.V_1_4_2_ID).build();
DocumentMapper docMapper = createIndex("test", settings).mapperService().documentMapperParser().parse(mapping);
// serialize the id mapping
XContentBuilder builder = XContentFactory.jsonBuilder().startObject();

View File

@ -61,13 +61,11 @@ public class RoutingTypeMapperTests extends ElasticsearchSingleNodeTest {
.startObject("_routing")
.field("store", "no")
.field("index", "no")
.field("path", "route")
.endObject()
.endObject().endObject().string();
DocumentMapper docMapper = createIndex("test").mapperService().documentMapperParser().parse(mapping);
assertThat(docMapper.routingFieldMapper().fieldType().stored(), equalTo(false));
assertEquals(IndexOptions.NONE, docMapper.routingFieldMapper().fieldType().indexOptions());
assertThat(docMapper.routingFieldMapper().path(), equalTo("route"));
}
@Test

View File

@ -20,6 +20,7 @@
package org.elasticsearch.routing;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.Version;
import org.elasticsearch.action.RoutingMissingException;
import org.elasticsearch.action.admin.indices.alias.Alias;
import org.elasticsearch.action.explain.ExplainResponse;
@ -30,6 +31,9 @@ import org.elasticsearch.action.termvectors.TermVectorsRequest;
import org.elasticsearch.action.termvectors.TermVectorsResponse;
import org.elasticsearch.action.update.UpdateResponse;
import org.elasticsearch.client.Requests;
import org.elasticsearch.cluster.metadata.IndexMetaData;
import org.elasticsearch.common.settings.ImmutableSettings;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.index.mapper.MapperParsingException;
import org.elasticsearch.index.query.QueryBuilders;
@ -40,17 +44,13 @@ import org.junit.Test;
import static org.elasticsearch.index.query.QueryBuilders.matchAllQuery;
import static org.hamcrest.Matchers.*;
/**
*
*/
public class SimpleRoutingTests extends ElasticsearchIntegrationTest {
@Override
protected int minimumNumberOfShards() {
return 2;
}
@Test
public void testSimpleCrudRouting() throws Exception {
createIndex("test");
ensureGreen();
@ -107,8 +107,7 @@ public class SimpleRoutingTests extends ElasticsearchIntegrationTest {
assertThat(client().prepareGet("test", "type1", "1").setRouting("0").execute().actionGet().isExists(), equalTo(false));
}
}
@Test
public void testSimpleSearchRouting() {
createIndex("test");
ensureGreen();
@ -174,8 +173,7 @@ public class SimpleRoutingTests extends ElasticsearchIntegrationTest {
assertThat(client().prepareCount().setRouting("0", "1", "0").setQuery(QueryBuilders.matchAllQuery()).execute().actionGet().getCount(), equalTo(2l));
}
}
@Test
public void testRequiredRoutingMapping() throws Exception {
client().admin().indices().prepareCreate("test").addAlias(new Alias("alias"))
.addMapping("type1", XContentFactory.jsonBuilder().startObject().startObject("type1").startObject("_routing").field("required", true).endObject().endObject().endObject())
@ -230,8 +228,7 @@ public class SimpleRoutingTests extends ElasticsearchIntegrationTest {
assertThat(client().prepareGet(indexOrAlias(), "type1", "1").setRouting("0").execute().actionGet().isExists(), equalTo(false));
}
}
@Test
public void testRequiredRoutingWithPathMapping() throws Exception {
client().admin().indices().prepareCreate("test")
.addAlias(new Alias("alias"))
@ -239,6 +236,7 @@ public class SimpleRoutingTests extends ElasticsearchIntegrationTest {
.startObject("_routing").field("required", true).field("path", "routing_field").endObject().startObject("properties")
.startObject("routing_field").field("type", "string").field("index", randomBoolean() ? "no" : "not_analyzed").field("doc_values", randomBoolean() ? "yes" : "no").endObject().endObject()
.endObject().endObject())
.setSettings(IndexMetaData.SETTING_VERSION_CREATED, Version.V_1_4_2_ID)
.execute().actionGet();
ensureGreen();
@ -269,14 +267,14 @@ public class SimpleRoutingTests extends ElasticsearchIntegrationTest {
assertThat(client().prepareGet(indexOrAlias(), "type1", "1").setRouting("0").execute().actionGet().isExists(), equalTo(true));
}
}
@Test
public void testRequiredRoutingWithPathMappingBulk() throws Exception {
client().admin().indices().prepareCreate("test")
.addAlias(new Alias("alias"))
.addMapping("type1", XContentFactory.jsonBuilder().startObject().startObject("type1")
.startObject("_routing").field("required", true).field("path", "routing_field").endObject()
.endObject().endObject())
.setSettings(IndexMetaData.SETTING_VERSION_CREATED, Version.V_1_4_2_ID)
.execute().actionGet();
ensureGreen();
@ -301,14 +299,44 @@ public class SimpleRoutingTests extends ElasticsearchIntegrationTest {
}
}
@Test
public void testRequiredRoutingWithPathNumericType() throws Exception {
public void testRequiredRoutingBulk() throws Exception {
client().admin().indices().prepareCreate("test")
.addAlias(new Alias("alias"))
.addMapping("type1", XContentFactory.jsonBuilder().startObject().startObject("type1")
.startObject("_routing").field("required", true).endObject()
.endObject().endObject())
.execute().actionGet();
ensureGreen();
logger.info("--> indexing with id [1], and routing [0]");
client().prepareBulk().add(
client().prepareIndex(indexOrAlias(), "type1", "1").setRouting("0").setSource("field", "value1")).execute().actionGet();
client().admin().indices().prepareRefresh().execute().actionGet();
logger.info("--> verifying get with no routing, should fail");
for (int i = 0; i < 5; i++) {
try {
client().prepareGet(indexOrAlias(), "type1", "1").execute().actionGet().isExists();
fail();
} catch (RoutingMissingException e) {
assertThat(e.status(), equalTo(RestStatus.BAD_REQUEST));
assertThat(e.getMessage(), equalTo("routing is required for [test]/[type1]/[1]"));
}
}
logger.info("--> verifying get with routing, should find");
for (int i = 0; i < 5; i++) {
assertThat(client().prepareGet(indexOrAlias(), "type1", "1").setRouting("0").execute().actionGet().isExists(), equalTo(true));
}
}
public void testRequiredRoutingWithPathNumericType() throws Exception {
client().admin().indices().prepareCreate("test")
.addAlias(new Alias("alias"))
.addMapping("type1", XContentFactory.jsonBuilder().startObject().startObject("type1")
.startObject("_routing").field("required", true).field("path", "routing_field").endObject()
.endObject().endObject())
.setSettings(IndexMetaData.SETTING_VERSION_CREATED, Version.V_1_4_2_ID)
.execute().actionGet();
ensureGreen();
@ -331,8 +359,7 @@ public class SimpleRoutingTests extends ElasticsearchIntegrationTest {
assertThat(client().prepareGet(indexOrAlias(), "type1", "1").setRouting("0").execute().actionGet().isExists(), equalTo(true));
}
}
@Test
public void testRequiredRoutingMapping_variousAPIs() throws Exception {
client().admin().indices().prepareCreate("test").addAlias(new Alias("alias"))
.addMapping("type1", XContentFactory.jsonBuilder().startObject().startObject("type1").startObject("_routing").field("required", true).endObject().endObject().endObject())

View File

@ -75,7 +75,7 @@ public class SignificantTermsTests extends ElasticsearchIntegrationTest {
@Override
public void setupSuiteScopeCluster() throws Exception {
assertAcked(prepareCreate("test").setSettings(SETTING_NUMBER_OF_SHARDS, 5, SETTING_NUMBER_OF_REPLICAS, 0).addMapping("fact",
"_routing", "required=true,path=routing_id", "routing_id", "type=string,index=not_analyzed", "fact_category",
"_routing", "required=true", "routing_id", "type=string,index=not_analyzed", "fact_category",
"type=integer,index=not_analyzed", "description", "type=string,index=analyzed"));
createIndex("idx_unmapped");
@ -105,7 +105,8 @@ public class SignificantTermsTests extends ElasticsearchIntegrationTest {
for (int i = 0; i < data.length; i++) {
String[] parts = data[i].split("\t");
client().prepareIndex("test", "fact", "" + i)
.setSource("routing_id", parts[0], "fact_category", parts[1], "description", parts[2]).get();
.setRouting(parts[0])
.setSource("fact_category", parts[1], "description", parts[2]).get();
}
client().admin().indices().refresh(new RefreshRequest("test")).get();
}

View File

@ -52,7 +52,6 @@ public class TermsDocCountErrorTests extends ElasticsearchIntegrationTest{
private static final String STRING_FIELD_NAME = "s_value";
private static final String LONG_FIELD_NAME = "l_value";
private static final String DOUBLE_FIELD_NAME = "d_value";
private static final String ROUTING_FIELD_NAME = "route";
public static String randomExecutionHint() {
return randomBoolean() ? null : randomFrom(ExecutionMode.values()).toString();
@ -84,14 +83,15 @@ public class TermsDocCountErrorTests extends ElasticsearchIntegrationTest{
.endObject()));
}
numRoutingValues = between(1,40);
assertAcked(prepareCreate("idx_with_routing").addMapping("type", "{ \"type\" : { \"_routing\" : { \"required\" : true, \"path\" : \"" + ROUTING_FIELD_NAME + "\" } } }"));
assertAcked(prepareCreate("idx_with_routing").addMapping("type", "{ \"type\" : { \"_routing\" : { \"required\" : true } } }"));
for (int i = 0; i < numDocs; i++) {
builders.add(client().prepareIndex("idx_single_shard", "type", ""+i).setSource(jsonBuilder()
builders.add(client().prepareIndex("idx_single_shard", "type", "" + i)
.setRouting(String.valueOf(randomInt(numRoutingValues)))
.setSource(jsonBuilder()
.startObject()
.field(STRING_FIELD_NAME, "val" + randomInt(numUniqueTerms))
.field(LONG_FIELD_NAME, randomInt(numUniqueTerms))
.field(DOUBLE_FIELD_NAME, 1.0 * randomInt(numUniqueTerms))
.field(ROUTING_FIELD_NAME, String.valueOf(randomInt(numRoutingValues)))
.endObject()));
}
indexRandom(true, builders);

View File

@ -1580,7 +1580,6 @@ public class SearchQueryTests extends ElasticsearchIntegrationTest {
.startObject("s")
.startObject("_routing")
.field("required", true)
.field("path", "bs")
.endObject()
.startObject("properties")
.startObject("online")
@ -1601,8 +1600,8 @@ public class SearchQueryTests extends ElasticsearchIntegrationTest {
.addMapping("bs", "online", "type=boolean", "ts", "type=date,ignore_malformed=false,format=dateOptionalTime"));
ensureGreen();
client().prepareIndex("test", "s", "1").setSource("online", false, "bs", "Y", "ts", System.currentTimeMillis() - 100).get();
client().prepareIndex("test", "s", "2").setSource("online", true, "bs", "X", "ts", System.currentTimeMillis() - 10000000).get();
client().prepareIndex("test", "s", "1").setRouting("Y").setSource("online", false, "bs", "Y", "ts", System.currentTimeMillis() - 100).get();
client().prepareIndex("test", "s", "2").setRouting("X").setSource("online", true, "bs", "X", "ts", System.currentTimeMillis() - 10000000).get();
client().prepareIndex("test", "bs", "3").setSource("online", false, "ts", System.currentTimeMillis() - 100).get();
client().prepareIndex("test", "bs", "4").setSource("online", true, "ts", System.currentTimeMillis() - 123123).get();
refresh();