Skip to content

Commit 01b9562

Browse files
committed
Fixes apache#1225 - Created a single ZooNode that tracks changes for all table configs
1 parent 58dca69 commit 01b9562

File tree

4 files changed

+209
-3
lines changed

4 files changed

+209
-3
lines changed

core/src/main/java/org/apache/accumulo/core/Constants.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ public class Constants {
3535
public static final String ZTABLE_COMPACT_ID = "/compact-id";
3636
public static final String ZTABLE_COMPACT_CANCEL_ID = "/compact-cancel-id";
3737
public static final String ZTABLE_NAMESPACE = "/namespace";
38+
public static final String ZTABLE_CONFIG_VERSION = "/table-config-version";
3839

3940
public static final String ZNAMESPACES = "/namespaces";
4041
public static final String ZNAMESPACE_NAME = "/name";

core/src/main/java/org/apache/accumulo/fate/zookeeper/ZooCache.java

Lines changed: 176 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,10 @@
2828
import java.util.concurrent.locks.LockSupport;
2929
import java.util.concurrent.locks.ReadWriteLock;
3030
import java.util.concurrent.locks.ReentrantReadWriteLock;
31+
import java.util.regex.Matcher;
32+
import java.util.regex.Pattern;
3133

34+
import org.apache.accumulo.core.Constants;
3235
import org.apache.zookeeper.KeeperException;
3336
import org.apache.zookeeper.KeeperException.Code;
3437
import org.apache.zookeeper.WatchedEvent;
@@ -47,6 +50,12 @@
4750
public class ZooCache {
4851
private static final Logger log = LoggerFactory.getLogger(ZooCache.class);
4952

53+
public final static Pattern TABLE_SETTING_CONFIG_PATTERN =
54+
Pattern.compile("(/accumulo/[0-9a-z-]+)(" + Constants.ZTABLES + ")(/.*)("
55+
+ Constants.ZTABLE_CONF + ")/(table.*|tserver.*)");
56+
57+
public final static Pattern ZNODE_PATTERN = Pattern.compile("(/accumulo/[0-9a-z-]+)/.*");
58+
5059
private final ZCacheWatcher watcher = new ZCacheWatcher();
5160
private final Watcher externalWatcher;
5261

@@ -62,6 +71,8 @@ public class ZooCache {
6271
private final SecureRandom secureRandom = new SecureRandom();
6372

6473
private volatile boolean closed = false;
74+
private static boolean tableConfigWatcherSet = false;
75+
private static int lastTableConfigVersion = 0;
6576

6677
public static class ZcStat {
6778
private long ephemeralOwner;
@@ -148,14 +159,20 @@ private ZooKeeper getZooKeeper() {
148159
}
149160

150161
private class ZCacheWatcher implements Watcher {
162+
151163
@Override
152164
public void process(WatchedEvent event) {
153165
if (log.isTraceEnabled()) {
154166
log.trace("{}", event);
155167
}
168+
setWatcherForTableConfigVersion(event);
156169

157170
switch (event.getType()) {
158171
case NodeDataChanged:
172+
if (event.getPath().endsWith(Constants.ZTABLE_CONFIG_VERSION)) {
173+
processTableConfigurationItem(event);
174+
break;
175+
}
159176
case NodeChildrenChanged:
160177
case NodeCreated:
161178
case NodeDeleted:
@@ -191,6 +208,158 @@ public void process(WatchedEvent event) {
191208
externalWatcher.process(event);
192209
}
193210
}
211+
/*
212+
* The processTableConfigurationItem function is only called when there is a NodeDataChanged
213+
* event on the "table_config_version" znode. It resets the watcher on the
214+
* "table_config_version" znode and extracts the last modified table config path from the
215+
* table_config_version znode. If the version of the table_config_version znode is what this
216+
* process expects to be the next one we simply emulate what we used to do and remove that
217+
* actual table configuration property's zpath from the ZooCache. If the version is not what we
218+
* expect to be the next then we remove all the table config properties from the ZooCache as
219+
* specified in Accumulo Issue #1225. We gain a little efficiency in ZooKeeper by trying to keep
220+
* track of the version of the table_config_version znode which Zookeeper generates on its own.
221+
* It would be unwise to try to calculate the version on our own since many processes on
222+
* different machines will be setting table configuration properties.
223+
*/
224+
225+
private synchronized void processTableConfigurationItem(WatchedEvent event) {
226+
227+
if (event.getPath() == null || event.getPath().isEmpty())
228+
return;
229+
230+
try {
231+
Stat versionStat = getZooKeeper().exists(event.getPath(), watcher);
232+
if (versionStat == null) {
233+
return;
234+
}
235+
236+
byte[] configToRefresh = getZooKeeper().getData(event.getPath(), true, versionStat);
237+
238+
if (configToRefresh == null) {
239+
return;
240+
}
241+
242+
if ((versionStat != null) && (versionStat.getVersion() - lastTableConfigVersion == 1)) {
243+
lastTableConfigVersion = versionStat.getVersion();
244+
refreshTableConfig(configToRefresh);
245+
if (log.isTraceEnabled()) {
246+
log.trace(
247+
"Successfully refreshed table single table config: " + new String(configToRefresh));
248+
}
249+
} else {
250+
251+
if (log.isTraceEnabled()) {
252+
log.trace("We have to update all table configs.");
253+
}
254+
255+
if (versionStat != null) {
256+
lastTableConfigVersion = versionStat.getVersion();
257+
updateAllTableConfigurations();
258+
}
259+
}
260+
261+
} catch (Exception e) {
262+
log.error("Error getting data from TABLE_CONFIGS zoonode " + e.getMessage());
263+
}
264+
265+
return;
266+
}
267+
268+
/**
269+
* Remove all table configuration items for all tables from the cache.
270+
*/
271+
272+
private synchronized void updateAllTableConfigurations() {
273+
274+
for (String cachedItem : cache.keySet()) {
275+
if (cachedItem == null || cachedItem.isEmpty())
276+
continue;
277+
278+
Matcher tableConfigMatcher = TABLE_SETTING_CONFIG_PATTERN.matcher(cachedItem);
279+
if (tableConfigMatcher.matches()) {
280+
remove(cachedItem);
281+
}
282+
}
283+
284+
}
285+
286+
/***
287+
* Create the first watcher for the /accumulo/{InstanceID/table-config-version znode. It only
288+
* operates on the table-config-version znode. Watchers need to be set on a node if a watch
289+
* event is ever to be triggered. Watch events are triggered by a data change, addition or
290+
* deletion of a znode. Watches, once triggered need to be reset. (The reset of the watcher set
291+
* here will be done in the ZCacheWatcher.processTableConfigurationItem function.) The change to
292+
* the table-config-version znode occurs during setTableProperty and removeTableProperty calls
293+
* in the TablePropUtil class. During setTableProperty and removeTableProperty calls, the data
294+
* value of the table-config-version znode will also be changed in addition to the actual table
295+
* configuration property (that will no longer be watched). Everytime the data in the
296+
* table-config-version znode changes the internal "data version" tracked by Zookeeper will be
297+
* automatically incremented. Accumulo does not have to compute what that version is. Zookeeper
298+
* does that by itself. The ZooCache object will track this version and use it to maintain the
299+
* cache. Many processes will be using it so when the "lastTableConfigVersion member" does not
300+
* match the actual version of the table-config-version znode we just remove all the table
301+
* configurations (in the particular ZooCache instance) as was specified in the Accumulo Issue
302+
* #1225. What I found is that we can usually rely on events coming to the ZCacheWatcher in
303+
* order and we only need to remove one table configuration from the ZooCache. The one that just
304+
* got changed, added, or deleted. This should lead to far few calls to getData on Zookeeper
305+
* during runtime. This is accomplished by setting the data of the table-config-version Znode to
306+
* the ZooPath of the table configuration that was modified. This zPath with be removed from the
307+
* ZooCache in the ZCacheWathcer.process(). * It will be restored in the ZooCache the next time
308+
* ZooCache.get(String) is called for that table configuration path. This was how ZooCached
309+
* worked when we watched all table configurations and we are just emulating it now using only
310+
* one watched node for all the configuration items - the "table-config-version node".
311+
*/
312+
private synchronized void setWatcherForTableConfigVersion(WatchedEvent event) {
313+
314+
if (event.getPath() == null || event.getPath().isEmpty())
315+
return;
316+
317+
if (!tableConfigWatcherSet) {
318+
Matcher znodeMatcher = ZNODE_PATTERN.matcher(event.getPath());
319+
if (znodeMatcher.matches()) {
320+
String pathPrefix = znodeMatcher.group(1);
321+
try {
322+
323+
Stat versionStat =
324+
getZooKeeper().exists(pathPrefix + Constants.ZTABLE_CONFIG_VERSION, watcher);
325+
if (versionStat != null) {
326+
lastTableConfigVersion = versionStat.getVersion();
327+
tableConfigWatcherSet = true;
328+
if (log.isTraceEnabled())
329+
log.trace("Successfully set table_config watcher");
330+
}
331+
332+
} catch (KeeperException ke) {
333+
log.error("Could not set watcher on " + pathPrefix + Constants.ZTABLE_CONFIG_VERSION
334+
+ " will retry later " + ke.getMessage());
335+
} catch (InterruptedException ie) {
336+
log.error("Could not set watcher on " + pathPrefix + Constants.ZTABLE_CONFIG_VERSION
337+
+ " will retry later " + ie.getMessage());
338+
}
339+
}
340+
}
341+
}
342+
}
343+
344+
private void refreshTableConfig(byte[] configToRefresh) {
345+
if (configToRefresh == null)
346+
return;
347+
348+
String strConfigToRefresh = new String(configToRefresh);
349+
if (strConfigToRefresh.isEmpty())
350+
return;
351+
352+
Matcher tableConfigMatcher = TABLE_SETTING_CONFIG_PATTERN.matcher(strConfigToRefresh);
353+
if (!tableConfigMatcher.matches())
354+
return;
355+
356+
if (log.isTraceEnabled()) {
357+
log.trace("NodeDataChanged called refreshTableConfig and refreshed table config: "
358+
+ new String(configToRefresh));
359+
}
360+
361+
remove(strConfigToRefresh);
362+
194363
}
195364

196365
/**
@@ -402,18 +571,22 @@ public byte[] run() throws KeeperException, InterruptedException {
402571
* a special case that looks for Code.NONODE in the KeeperException, then non-existence can
403572
* not be cached.
404573
*/
574+
575+
Matcher configMatcher = TABLE_SETTING_CONFIG_PATTERN.matcher(zPath);
576+
boolean createWatch = !configMatcher.matches();
577+
405578
cacheWriteLock.lock();
406579
try {
407580
final ZooKeeper zooKeeper = getZooKeeper();
408-
Stat stat = zooKeeper.exists(zPath, watcher);
581+
Stat stat = zooKeeper.exists(zPath, createWatch ? watcher : null);
409582
byte[] data = null;
410583
if (stat == null) {
411584
if (log.isTraceEnabled()) {
412585
log.trace("zookeeper did not contain {}", zPath);
413586
}
414587
} else {
415588
try {
416-
data = zooKeeper.getData(zPath, watcher, stat);
589+
data = zooKeeper.getData(zPath, createWatch ? watcher : null, stat);
417590
zstat = new ZcStat(stat);
418591
} catch (KeeperException.BadVersionException | KeeperException.NoNodeException e1) {
419592
throw new ConcurrentModificationException();
@@ -546,6 +719,7 @@ boolean childrenCached(String zPath) {
546719
* path of top node
547720
*/
548721
public void clear(String zPath) {
722+
549723
Preconditions.checkState(!closed);
550724
cacheWriteLock.lock();
551725
try {

server/base/src/main/java/org/apache/accumulo/server/init/Initialize.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -629,6 +629,8 @@ private static void initZooKeeper(Opts opts, String uuid, String instanceNamePat
629629
zoo.putPersistentData(zkInstanceRoot, EMPTY_BYTE_ARRAY, NodeExistsPolicy.FAIL);
630630
zoo.putPersistentData(zkInstanceRoot + Constants.ZTABLES, Constants.ZTABLES_INITIAL_ID,
631631
NodeExistsPolicy.FAIL);
632+
zoo.putPersistentData(zkInstanceRoot + Constants.ZTABLE_CONFIG_VERSION, new byte[0],
633+
NodeExistsPolicy.FAIL);
632634
zoo.putPersistentData(zkInstanceRoot + Constants.ZNAMESPACES, new byte[0],
633635
NodeExistsPolicy.FAIL);
634636
TableManager.prepareNewNamespaceState(zoo, uuid, Namespace.DEFAULT.id(),

server/base/src/main/java/org/apache/accumulo/server/util/TablePropUtil.java

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@
1717
package org.apache.accumulo.server.util;
1818

1919
import static java.nio.charset.StandardCharsets.UTF_8;
20+
import static org.apache.accumulo.fate.zookeeper.ZooCache.TABLE_SETTING_CONFIG_PATTERN;
21+
22+
import java.util.regex.Matcher;
2023

2124
import org.apache.accumulo.core.Constants;
2225
import org.apache.accumulo.core.conf.Property;
@@ -26,9 +29,13 @@
2629
import org.apache.accumulo.fate.zookeeper.ZooUtil.NodeMissingPolicy;
2730
import org.apache.accumulo.server.ServerContext;
2831
import org.apache.zookeeper.KeeperException;
32+
import org.slf4j.Logger;
33+
import org.slf4j.LoggerFactory;
2934

3035
public class TablePropUtil {
3136

37+
private static final Logger log = LoggerFactory.getLogger(TablePropUtil.class);
38+
3239
public static boolean setTableProperty(ServerContext context, TableId tableId, String property,
3340
String value) throws KeeperException, InterruptedException {
3441
return setTableProperty(context.getZooReaderWriter(), context.getZooKeeperRoot(), tableId,
@@ -47,7 +54,10 @@ public static boolean setTableProperty(ZooReaderWriter zoo, String zkRoot, Table
4754
// create the zk node for this property and set it's data to the specified value
4855
String zPath = zkTablePath + "/" + property;
4956
zoo.putPersistentData(zPath, value.getBytes(UTF_8), NodeExistsPolicy.OVERWRITE);
50-
57+
updateTableConfigTrackingZnode(zPath, zoo);
58+
if (log.isTraceEnabled()) {
59+
log.trace("updateTableConfigTrackingZnode called in setTableProperty for " + zPath);
60+
}
5161
return true;
5262
}
5363

@@ -61,9 +71,28 @@ public static void removeTableProperty(ServerContext context, TableId tableId, S
6171
throws InterruptedException, KeeperException {
6272
String zPath = getTablePath(context.getZooKeeperRoot(), tableId) + "/" + property;
6373
context.getZooReaderWriter().recursiveDelete(zPath, NodeMissingPolicy.SKIP);
74+
updateTableConfigTrackingZnode(zPath, context.getZooReaderWriter());
75+
if (log.isTraceEnabled()) {
76+
log.trace("updateTableConfigTrackingZnode called in removeTableProperty " + zPath);
77+
}
6478
}
6579

6680
private static String getTablePath(String zkRoot, TableId tableId) {
6781
return zkRoot + Constants.ZTABLES + "/" + tableId.canonical() + Constants.ZTABLE_CONF;
6882
}
83+
84+
private static void updateTableConfigTrackingZnode(String zPath, ZooReaderWriter zoo)
85+
throws KeeperException, InterruptedException {
86+
87+
String pathPrefix;
88+
Matcher configMatcher = TABLE_SETTING_CONFIG_PATTERN.matcher(zPath);
89+
if (configMatcher.matches()) {
90+
pathPrefix = configMatcher.group(1);
91+
zoo.putPersistentData(pathPrefix + Constants.ZTABLE_CONFIG_VERSION, zPath.getBytes(UTF_8),
92+
NodeExistsPolicy.OVERWRITE);
93+
94+
}
95+
96+
}
97+
6998
}

0 commit comments

Comments
 (0)