Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move metadata storage to Lucene #50907

Merged
merged 23 commits into from
Jan 13, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
2a08dd1
Introduce Lucene-based metadata persistence (#48733)
DaveCTurner Nov 13, 2019
c3a49dc
Merge branch 'master' into reduce-metadata-writes-master
DaveCTurner Nov 18, 2019
5a471f1
Remove per-index metadata without assigned shards (#49234)
DaveCTurner Nov 18, 2019
1dd0f6b
Merge remote-tracking branch 'elastic/master' into reduce-metadata-wr…
ywelsch Dec 11, 2019
1606804
Merge remote-tracking branch 'elastic/master' into reduce-metadata-wr…
ywelsch Dec 12, 2019
3498a47
Use Lucene exclusively for metadata storage (#50144)
ywelsch Dec 13, 2019
8b73715
Merge remote-tracking branch 'elastic/master' into reduce-metadata-wr…
ywelsch Dec 18, 2019
c6e1ea8
Add command-line tool support for Lucene-based metadata storage (#50179)
ywelsch Dec 19, 2019
f9d7546
Merge remote-tracking branch 'elastic/master' into reduce-metadata-wr…
ywelsch Dec 19, 2019
fec602e
Merge branch 'master' into reduce-metadata-writes-master
DaveCTurner Jan 2, 2020
09b95f4
Merge remote-tracking branch 'elastic/master' into reduce-metadata-wr…
ywelsch Jan 7, 2020
0830b7d
Use single directory for metadata (#50639)
ywelsch Jan 8, 2020
1a9f88f
Add async dangling indices support (#50642)
ywelsch Jan 8, 2020
63a9238
Merge remote-tracking branch 'elastic/master' into reduce-metadata-wr…
ywelsch Jan 8, 2020
c8bfe3d
Fold node metadata into new node storage (#50741)
ywelsch Jan 8, 2020
df40aec
Merge remote-tracking branch 'elastic/master' into reduce-metadata-wr…
ywelsch Jan 9, 2020
65366e0
Write CS asynchronously on data-only nodes (#50782)
ywelsch Jan 9, 2020
af12a00
Merge remote-tracking branch 'elastic/master' into reduce-metadata-wr…
ywelsch Jan 9, 2020
431cdb0
Remove persistent cluster settings tool (#50694)
ywelsch Jan 9, 2020
bfb878a
Make cluster state writer resilient to disk issues (#50805)
ywelsch Jan 10, 2020
7c0b64a
Merge remote-tracking branch 'elastic/master' into reduce-metadata-wr…
ywelsch Jan 13, 2020
9b805b0
Omit writing global metadata if no change (#50901)
ywelsch Jan 13, 2020
66a5675
DanglingIndicesIT should ensure node removed first (#50896)
DaveCTurner Jan 13, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 67 additions & 6 deletions docs/reference/commands/node-tool.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@

The `elasticsearch-node` command enables you to perform certain unsafe
operations on a node that are only possible while it is shut down. This command
allows you to adjust the <<modules-node,role>> of a node and may be able to
recover some data after a disaster or start a node even if it is incompatible
with the data on disk.
allows you to adjust the <<modules-node,role>> of a node, unsafely edit cluster
settings and may be able to recover some data after a disaster or start a node
even if it is incompatible with the data on disk.

[float]
=== Synopsis
Expand All @@ -20,13 +20,17 @@ bin/elasticsearch-node repurpose|unsafe-bootstrap|detach-cluster|override-versio
[float]
=== Description

This tool has four modes:
This tool has five modes:

* `elasticsearch-node repurpose` can be used to delete unwanted data from a
node if it used to be a <<data-node,data node>> or a
<<master-node,master-eligible node>> but has been repurposed not to have one
or other of these roles.

* `elasticsearch-node remove-settings` can be used to remove persistent settings
from the cluster state in case where it contains incompatible settings that
prevent the cluster from forming.

* `elasticsearch-node unsafe-bootstrap` can be used to perform _unsafe cluster
bootstrapping_. It forces one of the nodes to form a brand-new cluster on
its own, using its local copy of the cluster metadata.
Expand Down Expand Up @@ -76,6 +80,26 @@ The tool provides a summary of the data to be deleted and asks for confirmation
before making any changes. You can get detailed information about the affected
indices and shards by passing the verbose (`-v`) option.

[float]
==== Removing persistent cluster settings

There may be situations where a node contains persistent cluster
settings that prevent the cluster from forming. Since the cluster cannot form,
it is not possible to remove these settings using the
<<cluster-update-settings>> API.

The `elasticsearch-node remove-settings` tool allows you to forcefully remove
those persistent settings from the on-disk cluster state. The tool takes a
list of settings as parameters that should be removed, and also supports
wildcard patterns.

The intended use is:

* Stop the node
* Run `elasticsearch-node remove-settings name-of-setting-to-remove` on the node
* Repeat for all other master-eligible nodes
* Start the nodes

[float]
==== Recovering data after a disaster

Expand Down Expand Up @@ -143,9 +167,9 @@ If there is at least one remaining master-eligible node, but it is not possible
to restart a majority of them, then the `elasticsearch-node unsafe-bootstrap`
command will unsafely override the cluster's <<modules-discovery-voting,voting
configuration>> as if performing another
<<modules-discovery-bootstrap-cluster,cluster bootstrapping process>>.
<<modules-discovery-bootstrap-cluster,cluster bootstrapping process>>.
The target node can then form a new cluster on its own by using
the cluster metadata held locally on the target node.
the cluster metadata held locally on the target node.

[WARNING]
These steps can lead to arbitrary data loss since the target node may not hold the latest cluster
Expand Down Expand Up @@ -290,6 +314,9 @@ it can join a different cluster.
`override-version`:: Overwrites the version number stored in the data path so
that a node can start despite being incompatible with the on-disk data.

`remove-settings`:: Forcefully removes the provided persistent cluster settings
from the on-disk cluster state.

`-E <KeyValuePair>`:: Configures a setting.

`-h, --help`:: Returns all of the command parameters.
Expand Down Expand Up @@ -346,6 +373,40 @@ Confirm [y/N] y
Node successfully repurposed to no-master and no-data.
----

[float]
==== Removing persistent cluster settings

If your nodes contain persistent cluster settings that prevent the cluster
from forming, i.e., can't be removed using the <<cluster-update-settings>> API,
you can run the following commands to remove one or more cluster settings.

[source,txt]
----
node$ ./bin/elasticsearch-node remove-settings xpack.monitoring.exporters.my_exporter.host

WARNING: Elasticsearch MUST be stopped before running this tool.

The following settings will be removed:
xpack.monitoring.exporters.my_exporter.host: "10.1.2.3"

You should only run this tool if you have incompatible settings in the
cluster state that prevent the cluster from forming.
This tool can cause data loss and its use should be your last resort.

Do you want to proceed?

Confirm [y/N] y

Settings were successfully removed from the cluster state
----

You can also use wildcards to remove multiple settings, for example using

[source,txt]
----
node$ ./bin/elasticsearch-node remove-settings xpack.monitoring.*
----

[float]
==== Unsafe cluster bootstrapping

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
import org.elasticsearch.cluster.metadata.MetaData;
import org.elasticsearch.cluster.node.DiscoveryNode;

import java.io.Closeable;
import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
Expand Down Expand Up @@ -432,15 +434,14 @@ public void invariant() {
assert publishVotes.isEmpty() || electionWon();
}

public void close() {
public void close() throws IOException {
persistedState.close();
}

/**
* Pluggable persistence layer for {@link CoordinationState}.
*
*/
public interface PersistedState {
public interface PersistedState extends Closeable {

/**
* Returns the current term
Expand Down Expand Up @@ -497,7 +498,8 @@ default void markLastAcceptedStateAsCommitted() {
}
}

default void close() {}
default void close() throws IOException {
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@
import org.elasticsearch.transport.TransportResponse.Empty;
import org.elasticsearch.transport.TransportService;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
Expand Down Expand Up @@ -702,7 +703,7 @@ protected void doStop() {
}

@Override
protected void doClose() {
protected void doClose() throws IOException {
final CoordinationState coordinationState = this.coordinationState.get();
if (coordinationState != null) {
// This looks like a race that might leak an unclosed CoordinationState if it's created while execution is here, but this method
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,12 @@
*/
package org.elasticsearch.cluster.coordination;

import joptsimple.OptionSet;
import org.elasticsearch.cli.Terminal;
import org.elasticsearch.cluster.metadata.Manifest;
import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.cluster.metadata.MetaData;
import org.elasticsearch.common.collect.Tuple;
import org.elasticsearch.env.Environment;
import org.elasticsearch.gateway.PersistedClusterStateService;

import java.io.IOException;
import java.nio.file.Path;
Expand All @@ -48,14 +49,21 @@ public DetachClusterCommand() {


@Override
protected void processNodePaths(Terminal terminal, Path[] dataPaths, Environment env) throws IOException {
final Tuple<Manifest, MetaData> manifestMetaDataTuple = loadMetaData(terminal, dataPaths);
final Manifest manifest = manifestMetaDataTuple.v1();
final MetaData metaData = manifestMetaDataTuple.v2();
protected void processNodePaths(Terminal terminal, Path[] dataPaths, OptionSet options, Environment env) throws IOException {
final PersistedClusterStateService persistedClusterStateService = createPersistedClusterStateService(dataPaths);

terminal.println(Terminal.Verbosity.VERBOSE, "Loading cluster state");
final ClusterState oldClusterState = loadTermAndClusterState(persistedClusterStateService, env).v2();
final ClusterState newClusterState = ClusterState.builder(oldClusterState)
.metaData(updateMetaData(oldClusterState.metaData())).build();
terminal.println(Terminal.Verbosity.VERBOSE,
"[old cluster state = " + oldClusterState + ", new cluster state = " + newClusterState + "]");

confirm(terminal, CONFIRMATION_MSG);

writeNewMetaData(terminal, manifest, updateCurrentTerm(), metaData, updateMetaData(metaData), dataPaths);
try (PersistedClusterStateService.Writer writer = persistedClusterStateService.createWriter()) {
writer.writeFullStateAndCommit(updateCurrentTerm(), newClusterState);
}

terminal.println(NODE_DETACHED_MSG);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,75 +26,92 @@
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.cli.EnvironmentAwareCommand;
import org.elasticsearch.cli.Terminal;
import org.elasticsearch.cluster.metadata.Manifest;
import org.elasticsearch.cluster.metadata.MetaData;
import org.elasticsearch.cli.UserException;
import org.elasticsearch.cluster.ClusterModule;
import org.elasticsearch.cluster.ClusterName;
import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.common.collect.Tuple;
import org.elasticsearch.common.util.BigArrays;
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
import org.elasticsearch.env.Environment;
import org.elasticsearch.env.NodeEnvironment;
import org.elasticsearch.env.NodeMetaData;
import org.elasticsearch.gateway.PersistedClusterStateService;
import org.elasticsearch.indices.IndicesModule;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Objects;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public abstract class ElasticsearchNodeCommand extends EnvironmentAwareCommand {
private static final Logger logger = LogManager.getLogger(ElasticsearchNodeCommand.class);
protected static final String DELIMITER = "------------------------------------------------------------------------\n";

static final String STOP_WARNING_MSG =
DELIMITER +
"\n" +
" WARNING: Elasticsearch MUST be stopped before running this tool." +
"\n";
protected static final String FAILED_TO_OBTAIN_NODE_LOCK_MSG = "failed to lock node's directory, is Elasticsearch still running?";
static final String NO_NODE_FOLDER_FOUND_MSG = "no node folder is found in data folder(s), node has not been started yet?";
static final String NO_MANIFEST_FILE_FOUND_MSG = "no manifest file is found, do you run pre 7.0 Elasticsearch?";
protected static final String GLOBAL_GENERATION_MISSING_MSG =
"no metadata is referenced from the manifest file, cluster has never been bootstrapped?";
static final String NO_GLOBAL_METADATA_MSG = "failed to find global metadata, metadata corrupted?";
static final String WRITE_METADATA_EXCEPTION_MSG = "exception occurred when writing new metadata to disk";
protected static final String ABORTED_BY_USER_MSG = "aborted by user";
static final String NO_NODE_FOLDER_FOUND_MSG = "no node folder is found in data folder(s), node has not been started yet?";
static final String NO_NODE_METADATA_FOUND_MSG = "no node meta data is found, node has not been started yet?";
protected static final String CS_MISSING_MSG =
"cluster state is empty, cluster has never been bootstrapped?";

protected static final NamedXContentRegistry namedXContentRegistry = new NamedXContentRegistry(
Stream.of(ClusterModule.getNamedXWriteables().stream(), IndicesModule.getNamedXContents().stream())
.flatMap(Function.identity())
.collect(Collectors.toList()));

public ElasticsearchNodeCommand(String description) {
super(description);
}

protected void processNodePaths(Terminal terminal, OptionSet options, Environment env) throws IOException {
public static PersistedClusterStateService createPersistedClusterStateService(Path[] dataPaths) throws IOException {
final NodeMetaData nodeMetaData = PersistedClusterStateService.nodeMetaData(dataPaths);
if (nodeMetaData == null) {
throw new ElasticsearchException(NO_NODE_METADATA_FOUND_MSG);
}

String nodeId = nodeMetaData.nodeId();
return new PersistedClusterStateService(dataPaths, nodeId, namedXContentRegistry, BigArrays.NON_RECYCLING_INSTANCE, true);
}

public static ClusterState clusterState(Environment environment, PersistedClusterStateService.OnDiskState onDiskState) {
return ClusterState.builder(ClusterName.CLUSTER_NAME_SETTING.get(environment.settings()))
.version(onDiskState.lastAcceptedVersion)
.metaData(onDiskState.metaData)
.build();
}

public static Tuple<Long, ClusterState> loadTermAndClusterState(PersistedClusterStateService psf,
Environment env) throws IOException {
final PersistedClusterStateService.OnDiskState bestOnDiskState = psf.loadBestOnDiskState();
if (bestOnDiskState.empty()) {
throw new ElasticsearchException(CS_MISSING_MSG);
}
return Tuple.tuple(bestOnDiskState.currentTerm, clusterState(env, bestOnDiskState));
}

protected void processNodePaths(Terminal terminal, OptionSet options, Environment env) throws IOException, UserException {
terminal.println(Terminal.Verbosity.VERBOSE, "Obtaining lock for node");
try (NodeEnvironment.NodeLock lock = new NodeEnvironment.NodeLock(logger, env, Files::exists)) {
final Path[] dataPaths =
Arrays.stream(lock.getNodePaths()).filter(Objects::nonNull).map(p -> p.path).toArray(Path[]::new);
if (dataPaths.length == 0) {
throw new ElasticsearchException(NO_NODE_FOLDER_FOUND_MSG);
}
processNodePaths(terminal, dataPaths, env);
processNodePaths(terminal, dataPaths, options, env);
} catch (LockObtainFailedException e) {
throw new ElasticsearchException(FAILED_TO_OBTAIN_NODE_LOCK_MSG, e);
}
}

protected Tuple<Manifest, MetaData> loadMetaData(Terminal terminal, Path[] dataPaths) throws IOException {
terminal.println(Terminal.Verbosity.VERBOSE, "Loading manifest file");
final Manifest manifest = Manifest.FORMAT.loadLatestState(logger, NamedXContentRegistry.EMPTY, dataPaths);

if (manifest == null) {
throw new ElasticsearchException(NO_MANIFEST_FILE_FOUND_MSG);
}
if (manifest.isGlobalGenerationMissing()) {
throw new ElasticsearchException(GLOBAL_GENERATION_MISSING_MSG);
}
terminal.println(Terminal.Verbosity.VERBOSE, "Loading global metadata file");
final MetaData metaData = MetaData.FORMAT_PRESERVE_CUSTOMS.loadGeneration(
logger, NamedXContentRegistry.EMPTY, manifest.getGlobalGeneration(), dataPaths);
if (metaData == null) {
throw new ElasticsearchException(NO_GLOBAL_METADATA_MSG + " [generation = " + manifest.getGlobalGeneration() + "]");
}

return Tuple.tuple(manifest, metaData);
}

protected void confirm(Terminal terminal, String msg) {
terminal.println(msg);
String text = terminal.readText("Confirm [y/N] ");
Expand All @@ -104,7 +121,7 @@ protected void confirm(Terminal terminal, String msg) {
}

@Override
protected final void execute(Terminal terminal, OptionSet options, Environment env) throws Exception {
public final void execute(Terminal terminal, OptionSet options, Environment env) throws Exception {
terminal.println(STOP_WARNING_MSG);
if (validateBeforeLock(terminal, env)) {
processNodePaths(terminal, options, env);
Expand All @@ -126,44 +143,11 @@ protected boolean validateBeforeLock(Terminal terminal, Environment env) {
* Process the paths. Locks for the paths is held during this method invocation.
* @param terminal the terminal to use for messages
* @param dataPaths the paths of the node to process
* @param options the command line options
* @param env the env of the node to process
*/
protected abstract void processNodePaths(Terminal terminal, Path[] dataPaths, Environment env) throws IOException;


protected void writeNewMetaData(Terminal terminal, Manifest oldManifest, long newCurrentTerm,
MetaData oldMetaData, MetaData newMetaData, Path[] dataPaths) {
long newGeneration;
try {
terminal.println(Terminal.Verbosity.VERBOSE,
"[clusterUUID = " + oldMetaData.clusterUUID() + ", committed = " + oldMetaData.clusterUUIDCommitted() + "] => " +
"[clusterUUID = " + newMetaData.clusterUUID() + ", committed = " + newMetaData.clusterUUIDCommitted() + "]");
terminal.println(Terminal.Verbosity.VERBOSE, "New coordination metadata is " + newMetaData.coordinationMetaData());
terminal.println(Terminal.Verbosity.VERBOSE, "Writing new global metadata to disk");
newGeneration = MetaData.FORMAT.write(newMetaData, dataPaths);
Manifest newManifest = new Manifest(newCurrentTerm, oldManifest.getClusterStateVersion(), newGeneration,
oldManifest.getIndexGenerations());
terminal.println(Terminal.Verbosity.VERBOSE, "New manifest is " + newManifest);
terminal.println(Terminal.Verbosity.VERBOSE, "Writing new manifest file to disk");
Manifest.FORMAT.writeAndCleanup(newManifest, dataPaths);
} catch (Exception e) {
terminal.println(Terminal.Verbosity.VERBOSE, "Cleaning up new metadata");
MetaData.FORMAT.cleanupOldFiles(oldManifest.getGlobalGeneration(), dataPaths);
throw new ElasticsearchException(WRITE_METADATA_EXCEPTION_MSG, e);
}
// if cleaning old files fail, we still succeeded.
try {
cleanUpOldMetaData(terminal, dataPaths, newGeneration);
} catch (Exception e) {
terminal.println(Terminal.Verbosity.SILENT,
"Warning: Cleaning up old metadata failed, but operation was otherwise successful (message: " + e.getMessage() + ")");
}
}

protected void cleanUpOldMetaData(Terminal terminal, Path[] dataPaths, long newGeneration) {
terminal.println(Terminal.Verbosity.VERBOSE, "Cleaning up old metadata");
MetaData.FORMAT.cleanupOldFiles(newGeneration, dataPaths);
}
protected abstract void processNodePaths(Terminal terminal, Path[] dataPaths, OptionSet options, Environment env)
throws IOException, UserException;

protected NodeEnvironment.NodePath[] toNodePaths(Path[] dataPaths) {
return Arrays.stream(dataPaths).map(ElasticsearchNodeCommand::createNodePath).toArray(NodeEnvironment.NodePath[]::new);
Expand Down
Loading