Parse script on storage instead of on retrieval

Parsing a script on retrieval causes it to be re-parsed on every single script call, which can be very expensive for large frequently called scripts. This change switches to parsing scripts only once during store operation.
This commit is contained in:
Igor Motov 2016-09-06 22:11:19 -04:00
parent e77f4710e4
commit d34fdaac5e
3 changed files with 25 additions and 22 deletions

View File

@ -24,6 +24,7 @@ import org.elasticsearch.cluster.Diff;
import org.elasticsearch.cluster.DiffableUtils; import org.elasticsearch.cluster.DiffableUtils;
import org.elasticsearch.cluster.metadata.MetaData; import org.elasticsearch.cluster.metadata.MetaData;
import org.elasticsearch.common.ParsingException; import org.elasticsearch.common.ParsingException;
import org.elasticsearch.common.bytes.BytesArray;
import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.io.stream.StreamOutput;
@ -65,7 +66,7 @@ public final class ScriptMetaData implements MetaData.Custom {
if (scriptAsBytes == null) { if (scriptAsBytes == null) {
return null; return null;
} }
return parseStoredScript(scriptAsBytes); return scriptAsBytes.utf8ToString();
} }
public static String parseStoredScript(BytesReference scriptAsBytes) { public static String parseStoredScript(BytesReference scriptAsBytes) {
@ -78,6 +79,9 @@ public final class ScriptMetaData implements MetaData.Custom {
XContentBuilder builder = XContentFactory.contentBuilder(XContentType.JSON)) { XContentBuilder builder = XContentFactory.contentBuilder(XContentType.JSON)) {
parser.nextToken(); parser.nextToken();
parser.nextToken(); parser.nextToken();
if (parser.currentToken() == Token.END_OBJECT) {
throw new IllegalArgumentException("Empty script");
}
switch (parser.currentName()) { switch (parser.currentName()) {
case "script": case "script":
case "template": case "template":
@ -115,10 +119,8 @@ public final class ScriptMetaData implements MetaData.Custom {
case FIELD_NAME: case FIELD_NAME:
key = parser.currentName(); key = parser.currentName();
break; break;
case START_OBJECT: case VALUE_STRING:
XContentBuilder contentBuilder = XContentBuilder.builder(parser.contentType().xContent()); scripts.put(key, new ScriptAsBytes(new BytesArray(parser.text())));
contentBuilder.copyCurrentStructure(parser);
scripts.put(key, new ScriptAsBytes(contentBuilder.bytes()));
break; break;
default: default:
throw new ParsingException(parser.getTokenLocation(), "Unexpected token [" + token + "]"); throw new ParsingException(parser.getTokenLocation(), "Unexpected token [" + token + "]");
@ -147,7 +149,7 @@ public final class ScriptMetaData implements MetaData.Custom {
@Override @Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
for (Map.Entry<String, ScriptAsBytes> entry : scripts.entrySet()) { for (Map.Entry<String, ScriptAsBytes> entry : scripts.entrySet()) {
builder.rawField(entry.getKey(), entry.getValue().script); builder.field(entry.getKey(), entry.getValue().script.utf8ToString());
} }
return builder; return builder;
} }
@ -188,8 +190,8 @@ public final class ScriptMetaData implements MetaData.Custom {
@Override @Override
public String toString() { public String toString() {
return "ScriptMetaData{" + return "ScriptMetaData{" +
"scripts=" + scripts + "scripts=" + scripts +
'}'; '}';
} }
static String toKey(String language, String id) { static String toKey(String language, String id) {
@ -216,7 +218,8 @@ public final class ScriptMetaData implements MetaData.Custom {
} }
public Builder storeScript(String lang, String id, BytesReference script) { public Builder storeScript(String lang, String id, BytesReference script) {
scripts.put(toKey(lang, id), new ScriptAsBytes(script)); BytesReference scriptBytest = new BytesArray(parseStoredScript(script));
scripts.put(toKey(lang, id), new ScriptAsBytes(scriptBytest));
return this; return this;
} }

View File

@ -98,15 +98,15 @@ public class ScriptMetaDataTests extends ESTestCase {
public void testDiff() throws Exception { public void testDiff() throws Exception {
ScriptMetaData.Builder builder = new ScriptMetaData.Builder(null); ScriptMetaData.Builder builder = new ScriptMetaData.Builder(null);
builder.storeScript("lang", "1", new BytesArray("abc")); builder.storeScript("lang", "1", new BytesArray("{\"foo\":\"abc\"}"));
builder.storeScript("lang", "2", new BytesArray("def")); builder.storeScript("lang", "2", new BytesArray("{\"foo\":\"def\"}"));
builder.storeScript("lang", "3", new BytesArray("ghi")); builder.storeScript("lang", "3", new BytesArray("{\"foo\":\"ghi\"}"));
ScriptMetaData scriptMetaData1 = builder.build(); ScriptMetaData scriptMetaData1 = builder.build();
builder = new ScriptMetaData.Builder(scriptMetaData1); builder = new ScriptMetaData.Builder(scriptMetaData1);
builder.storeScript("lang", "2", new BytesArray("changed")); builder.storeScript("lang", "2", new BytesArray("{\"foo\":\"changed\"}"));
builder.deleteScript("lang", "3"); builder.deleteScript("lang", "3");
builder.storeScript("lang", "4", new BytesArray("jkl")); builder.storeScript("lang", "4", new BytesArray("{\"foo\":\"jkl\"}"));
ScriptMetaData scriptMetaData2 = builder.build(); ScriptMetaData scriptMetaData2 = builder.build();
ScriptMetaData.ScriptMetadataDiff diff = (ScriptMetaData.ScriptMetadataDiff) scriptMetaData2.diff(scriptMetaData1); ScriptMetaData.ScriptMetadataDiff diff = (ScriptMetaData.ScriptMetadataDiff) scriptMetaData2.diff(scriptMetaData1);
@ -118,19 +118,19 @@ public class ScriptMetaDataTests extends ESTestCase {
assertNotNull(((DiffableUtils.MapDiff) diff.pipelines).getUpserts().get("lang#4")); assertNotNull(((DiffableUtils.MapDiff) diff.pipelines).getUpserts().get("lang#4"));
ScriptMetaData result = (ScriptMetaData) diff.apply(scriptMetaData1); ScriptMetaData result = (ScriptMetaData) diff.apply(scriptMetaData1);
assertEquals(new BytesArray("abc"), result.getScriptAsBytes("lang", "1")); assertEquals(new BytesArray("{\"foo\":\"abc\"}"), result.getScriptAsBytes("lang", "1"));
assertEquals(new BytesArray("changed"), result.getScriptAsBytes("lang", "2")); assertEquals(new BytesArray("{\"foo\":\"changed\"}"), result.getScriptAsBytes("lang", "2"));
assertEquals(new BytesArray("jkl"), result.getScriptAsBytes("lang", "4")); assertEquals(new BytesArray("{\"foo\":\"jkl\"}"), result.getScriptAsBytes("lang", "4"));
} }
public void testBuilder() { public void testBuilder() {
ScriptMetaData.Builder builder = new ScriptMetaData.Builder(null); ScriptMetaData.Builder builder = new ScriptMetaData.Builder(null);
builder.storeScript("_lang", "_id", new BytesArray("{\"script\":\"1 + 1\"}")); builder.storeScript("_lang", "_id", new BytesArray("{\"script\":\"1 + 1\"}"));
IllegalArgumentException e = IllegalArgumentException e = expectThrows(IllegalArgumentException.class,
expectThrows(IllegalArgumentException.class, () -> builder.storeScript("_lang#", "_id", new BytesArray("{}"))); () -> builder.storeScript("_lang#", "_id", new BytesArray("{\"foo\": \"bar\"}")));
assertEquals("stored script language can't contain: '#'", e.getMessage()); assertEquals("stored script language can't contain: '#'", e.getMessage());
e = expectThrows(IllegalArgumentException.class, () -> builder.storeScript("_lang", "_id#", new BytesArray("{}"))); e = expectThrows(IllegalArgumentException.class, () -> builder.storeScript("_lang", "_id#", new BytesArray("{\"foo\": \"bar\"}")));
assertEquals("stored script id can't contain: '#'", e.getMessage()); assertEquals("stored script id can't contain: '#'", e.getMessage());
e = expectThrows(IllegalArgumentException.class, () -> builder.deleteScript("_lang#", "_id")); e = expectThrows(IllegalArgumentException.class, () -> builder.deleteScript("_lang#", "_id"));
assertEquals("stored script language can't contain: '#'", e.getMessage()); assertEquals("stored script language can't contain: '#'", e.getMessage());

View File

@ -429,14 +429,14 @@ public class ScriptServiceTests extends ESTestCase {
ScriptMetaData scriptMetaData = result.getMetaData().custom(ScriptMetaData.TYPE); ScriptMetaData scriptMetaData = result.getMetaData().custom(ScriptMetaData.TYPE);
assertNotNull(scriptMetaData); assertNotNull(scriptMetaData);
assertEquals("abc", scriptMetaData.getScript("_lang", "_id")); assertEquals("abc", scriptMetaData.getScript("_lang", "_id"));
assertEquals(script, scriptMetaData.getScriptAsBytes("_lang", "_id"));
} }
public void testDeleteScript() throws Exception { public void testDeleteScript() throws Exception {
ClusterState cs = ClusterState.builder(new ClusterName("_name")) ClusterState cs = ClusterState.builder(new ClusterName("_name"))
.metaData(MetaData.builder() .metaData(MetaData.builder()
.putCustom(ScriptMetaData.TYPE, .putCustom(ScriptMetaData.TYPE,
new ScriptMetaData.Builder(null).storeScript("_lang", "_id", new BytesArray("abc")).build())) new ScriptMetaData.Builder(null).storeScript("_lang", "_id",
new BytesArray("{\"script\":\"abc\"}")).build()))
.build(); .build();
DeleteStoredScriptRequest request = new DeleteStoredScriptRequest("_lang", "_id"); DeleteStoredScriptRequest request = new DeleteStoredScriptRequest("_lang", "_id");