Skip to content

Commit 37e2ba0

Browse files
jasontedorywelsch
andcommitted
Move shared cache pre-allocation and support macOS (elastic#70285)
This commit moves the shared cache pre-allocation code out of bootstrap, and instead to the searchable snapshots code. We go out of our way to only grant permissions to a specific library used for the pre-allocation. Additionally, to ensure our interfaces are sound, we add a macOS implementation based on fcntl and ftruncate. Co-authored-by: Yannick Welsch <[email protected]>
1 parent 294c04d commit 37e2ba0

File tree

26 files changed

+435
-227
lines changed

26 files changed

+435
-227
lines changed

server/src/main/java/org/elasticsearch/bootstrap/Bootstrap.java

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@
4242
import org.elasticsearch.node.InternalSettingsPreparer;
4343
import org.elasticsearch.node.Node;
4444
import org.elasticsearch.node.NodeValidationException;
45-
import org.elasticsearch.snapshots.SnapshotsService;
4645

4746
import java.io.ByteArrayOutputStream;
4847
import java.io.IOException;
@@ -172,18 +171,6 @@ private void setup(boolean addShutdownHook, Environment environment) throws Boot
172171
BootstrapSettings.SYSTEM_CALL_FILTER_SETTING.get(settings),
173172
BootstrapSettings.CTRLHANDLER_SETTING.get(settings));
174173

175-
final long cacheSize = SnapshotsService.SNAPSHOT_CACHE_SIZE_SETTING.get(settings).getBytes();
176-
final long regionSize = SnapshotsService.SNAPSHOT_CACHE_REGION_SIZE_SETTING.get(settings).getBytes();
177-
final int numRegions = Math.toIntExact(cacheSize / regionSize);
178-
final long fileSize = numRegions * regionSize;
179-
if (fileSize > 0) {
180-
try {
181-
Natives.tryCreateCacheFile(environment, fileSize);
182-
} catch (Exception e) {
183-
throw new BootstrapException(e);
184-
}
185-
}
186-
187174
// initialize probes before the security manager is installed
188175
initializeProbes();
189176

server/src/main/java/org/elasticsearch/bootstrap/JNAFalloc.java

Lines changed: 0 additions & 57 deletions
This file was deleted.

server/src/main/java/org/elasticsearch/bootstrap/JNANatives.java

Lines changed: 0 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -11,20 +11,11 @@
1111
import com.sun.jna.Native;
1212
import com.sun.jna.Pointer;
1313
import com.sun.jna.WString;
14-
1514
import org.apache.logging.log4j.LogManager;
1615
import org.apache.logging.log4j.Logger;
17-
import org.apache.logging.log4j.message.ParameterizedMessage;
1816
import org.apache.lucene.util.Constants;
19-
import org.elasticsearch.common.SuppressForbidden;
20-
import org.elasticsearch.env.Environment;
2117
import org.elasticsearch.monitor.jvm.JvmInfo;
22-
import org.elasticsearch.snapshots.SnapshotUtils;
2318

24-
import java.io.FileOutputStream;
25-
import java.io.IOException;
26-
import java.lang.reflect.Field;
27-
import java.nio.file.Files;
2819
import java.nio.file.Path;
2920

3021
import static org.elasticsearch.bootstrap.JNAKernel32Library.SizeT;
@@ -269,39 +260,4 @@ static void tryInstallSystemCallFilter(Path tmpFile) {
269260
}
270261
}
271262

272-
@SuppressForbidden(reason = "need access to fd on FileOutputStream")
273-
static void fallocateSnapshotCacheFile(Environment environment, long fileSize) throws IOException {
274-
final JNAFalloc falloc = JNAFalloc.falloc();
275-
if (falloc == null) {
276-
logger.debug("not trying to create a shared cache file using fallocate because native fallocate library could not be loaded.");
277-
return;
278-
}
279-
280-
Path cacheFile = SnapshotUtils.findCacheSnapshotCacheFilePath(environment, fileSize);
281-
if (cacheFile == null) {
282-
throw new IOException("could not find a directory with adequate free space for cache file");
283-
}
284-
boolean success = false;
285-
try (FileOutputStream fileChannel = new FileOutputStream(cacheFile.toFile())) {
286-
long currentSize = fileChannel.getChannel().size();
287-
if (currentSize < fileSize) {
288-
final Field field = fileChannel.getFD().getClass().getDeclaredField("fd");
289-
field.setAccessible(true);
290-
final int result = falloc.fallocate((int) field.get(fileChannel.getFD()), currentSize, fileSize - currentSize);
291-
if (result == 0) {
292-
success = true;
293-
logger.info("allocated cache file [{}] using fallocate", cacheFile);
294-
} else {
295-
logger.warn("failed to initialize cache file [{}] using fallocate errno [{}]", cacheFile, result);
296-
}
297-
}
298-
} catch (Exception e) {
299-
logger.warn(new ParameterizedMessage("failed to initialize cache file [{}] using fallocate", cacheFile), e);
300-
} finally {
301-
if (success == false) {
302-
// if anything goes wrong, delete the potentially created file to not waste disk space
303-
Files.deleteIfExists(cacheFile);
304-
}
305-
}
306-
}
307263
}

server/src/main/java/org/elasticsearch/bootstrap/Natives.java

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,7 @@
1010

1111
import org.apache.logging.log4j.LogManager;
1212
import org.apache.logging.log4j.Logger;
13-
import org.elasticsearch.env.Environment;
1413

15-
import java.io.IOException;
1614
import java.nio.file.Path;
1715

1816
/**
@@ -135,19 +133,4 @@ static boolean isSystemCallFilterInstalled() {
135133
return JNANatives.LOCAL_SYSTEM_CALL_FILTER;
136134
}
137135

138-
/**
139-
* On Linux, this method tries to create the searchable snapshot frozen cache file using fallocate if JNA is available. This enables
140-
* a much faster creation of the file than the fallback mechanism in the searchable snapshots plugin that will pre-allocate the cache
141-
* file by writing zeros to the file.
142-
*
143-
* @throws IOException on failure to determine free disk space for a data path
144-
*/
145-
public static void tryCreateCacheFile(Environment environment, long fileSize) throws IOException {
146-
if (JNA_AVAILABLE == false) {
147-
logger.warn("cannot use fallocate to create cache file because JNA is not available");
148-
return;
149-
}
150-
JNANatives.fallocateSnapshotCacheFile(environment, fileSize);
151-
}
152-
153136
}

server/src/main/java/org/elasticsearch/snapshots/SnapshotUtils.java

Lines changed: 3 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,16 @@
99

1010
import org.elasticsearch.action.support.IndicesOptions;
1111
import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
12-
import org.elasticsearch.common.Nullable;
1312
import org.elasticsearch.common.regex.Regex;
14-
import org.elasticsearch.env.Environment;
1513
import org.elasticsearch.index.IndexNotFoundException;
1614

15+
<<<<<<< HEAD
1716
import java.util.ArrayList;
1817
import java.io.IOException;
1918
import java.nio.file.Files;
2019
import java.nio.file.Path;
20+
=======
21+
>>>>>>> 36683a4581a... Move shared cache pre-allocation and support macOS (#70285)
2122
import java.util.Arrays;
2223
import java.util.Collections;
2324
import java.util.HashSet;
@@ -115,28 +116,4 @@ public static List<String> filterIndices(List<String> availableIndices, String[]
115116
return Collections.unmodifiableList(new ArrayList<>(result));
116117
}
117118

118-
/**
119-
* Tries to find a suitable path to a searchable snapshots shared cache file in the data paths founds in the environment.
120-
*
121-
* @return path for the cache file or {@code null} if none could be found
122-
*/
123-
@Nullable
124-
public static Path findCacheSnapshotCacheFilePath(Environment environment, long fileSize) throws IOException {
125-
Path cacheFile = null;
126-
for (Path path : environment.dataFiles()) {
127-
Files.createDirectories(path);
128-
// TODO: be resilient to this check failing and try next path?
129-
long usableSpace = Environment.getUsableSpace(path);
130-
Path p = path.resolve(SnapshotsService.CACHE_FILE_NAME);
131-
if (Files.exists(p)) {
132-
usableSpace += Files.size(p);
133-
}
134-
// TODO: leave some margin for error here
135-
if (usableSpace > fileSize) {
136-
cacheFile = p;
137-
break;
138-
}
139-
}
140-
return cacheFile;
141-
}
142119
}

server/src/main/java/org/elasticsearch/snapshots/SnapshotsService.java

Lines changed: 5 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,6 @@
3737
import org.elasticsearch.cluster.RestoreInProgress;
3838
import org.elasticsearch.cluster.SnapshotDeletionsInProgress;
3939
import org.elasticsearch.cluster.SnapshotsInProgress;
40-
import org.elasticsearch.common.unit.ByteSizeValue;
41-
import org.elasticsearch.common.util.CollectionUtils;
42-
import org.elasticsearch.repositories.RepositoryShardId;
4340
import org.elasticsearch.cluster.SnapshotsInProgress.ShardSnapshotStatus;
4441
import org.elasticsearch.cluster.SnapshotsInProgress.ShardState;
4542
import org.elasticsearch.cluster.SnapshotsInProgress.State;
@@ -67,7 +64,11 @@
6764
import org.elasticsearch.common.regex.Regex;
6865
import org.elasticsearch.common.settings.Setting;
6966
import org.elasticsearch.common.settings.Settings;
67+
<<<<<<< HEAD
7068
import org.elasticsearch.common.util.concurrent.AbstractRunnable;
69+
=======
70+
import org.elasticsearch.common.util.CollectionUtils;
71+
>>>>>>> 36683a4581a... Move shared cache pre-allocation and support macOS (#70285)
7172
import org.elasticsearch.index.Index;
7273
import org.elasticsearch.index.shard.ShardId;
7374
import org.elasticsearch.indices.SystemIndices;
@@ -77,6 +78,7 @@
7778
import org.elasticsearch.repositories.RepositoryData;
7879
import org.elasticsearch.repositories.RepositoryException;
7980
import org.elasticsearch.repositories.RepositoryMissingException;
81+
import org.elasticsearch.repositories.RepositoryShardId;
8082
import org.elasticsearch.repositories.ShardGenerations;
8183
import org.elasticsearch.threadpool.ThreadPool;
8284
import org.elasticsearch.transport.TransportService;
@@ -143,26 +145,6 @@ public class SnapshotsService extends AbstractLifecycleComponent implements Clus
143145

144146
public static final String UPDATE_SNAPSHOT_STATUS_ACTION_NAME = "internal:cluster/snapshot/update_snapshot_status";
145147

146-
public static final String SHARED_CACHE_SETTINGS_PREFIX = "xpack.searchable.snapshot.shared_cache.";
147-
148-
public static final Setting<ByteSizeValue> SHARED_CACHE_RANGE_SIZE_SETTING = Setting.byteSizeSetting(
149-
SHARED_CACHE_SETTINGS_PREFIX + "range_size",
150-
ByteSizeValue.ofMb(16), // default
151-
Setting.Property.NodeScope
152-
);
153-
public static final Setting<ByteSizeValue> SNAPSHOT_CACHE_REGION_SIZE_SETTING = Setting.byteSizeSetting(
154-
SHARED_CACHE_SETTINGS_PREFIX + "region_size",
155-
SHARED_CACHE_RANGE_SIZE_SETTING,
156-
Setting.Property.NodeScope
157-
);
158-
public static final Setting<ByteSizeValue> SNAPSHOT_CACHE_SIZE_SETTING = Setting.byteSizeSetting(
159-
SHARED_CACHE_SETTINGS_PREFIX + "size",
160-
ByteSizeValue.ZERO,
161-
Setting.Property.NodeScope
162-
);
163-
164-
public static final String CACHE_FILE_NAME = "shared_snapshot_cache";
165-
166148
public static final String NO_FEATURE_STATES_VALUE = "none";
167149

168150
private final ClusterService clusterService;

x-pack/plugin/searchable-snapshots/build.gradle

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ archivesBaseName = 'x-pack-searchable-snapshots'
1212

1313
dependencies {
1414
compileOnly project(path: xpackModule('core'))
15+
implementation project(path: 'preallocate')
1516
internalClusterTestImplementation(testArtifact(project(xpackModule('core'))))
1617
}
1718

18-
addQaCheckDependencies()
19+
addQaCheckDependencies()
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
apply plugin: 'elasticsearch.build'
9+
10+
archivesBaseName = 'preallocate'
11+
12+
dependencies {
13+
compileOnly project(":server")
14+
}
15+
16+
tasks.named("testingConventions").configure { enabled = false }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
package org.elasticsearch.xpack.searchablesnapshots.preallocate;
9+
10+
import com.sun.jna.Native;
11+
import com.sun.jna.Platform;
12+
13+
import java.security.AccessController;
14+
import java.security.PrivilegedAction;
15+
16+
final class LinuxPreallocator implements Preallocator {
17+
18+
@Override
19+
public boolean available() {
20+
return Natives.NATIVES_AVAILABLE;
21+
}
22+
23+
@Override
24+
public int preallocate(final int fd, final long currentSize, final long fileSize) {
25+
final int rc = Natives.fallocate(fd, 0, currentSize, fileSize - currentSize);
26+
return rc == 0 ? 0 : Native.getLastError();
27+
}
28+
29+
@Override
30+
public String error(int errno) {
31+
return Natives.strerror(errno);
32+
}
33+
34+
private static class Natives {
35+
36+
public static final boolean NATIVES_AVAILABLE;
37+
38+
static {
39+
NATIVES_AVAILABLE = AccessController.doPrivileged((PrivilegedAction<Boolean>) () -> {
40+
try {
41+
Native.register(Natives.class, Platform.C_LIBRARY_NAME);
42+
} catch (final UnsatisfiedLinkError e) {
43+
return false;
44+
}
45+
return true;
46+
});
47+
}
48+
49+
static native int fallocate(int fd, int mode, long offset, long length);
50+
51+
static native String strerror(int errno);
52+
53+
}
54+
55+
}

0 commit comments

Comments
 (0)