Skip to content

Commit 132bcd8

Browse files
committed
Use the same protobuf for both requests and log entries
We are now _really_ using the command-log pattern!
1 parent 1c6e9f7 commit 132bcd8

40 files changed

Lines changed: 3039 additions & 2528 deletions

cloudata-keyvalue/src/main/java/com/cloudata/keyvalue/KeyValueLog.java

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

cloudata-keyvalue/src/main/java/com/cloudata/keyvalue/KeyValueServer.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import java.net.SocketAddress;
66
import java.util.EnumSet;
77
import java.util.List;
8+
import java.util.concurrent.Executors;
89

910
import javax.servlet.DispatcherType;
1011

@@ -24,6 +25,8 @@
2425
import com.cloudata.keyvalue.web.WebModule;
2526
import com.google.common.base.Throwables;
2627
import com.google.common.collect.Lists;
28+
import com.google.common.util.concurrent.ListeningExecutorService;
29+
import com.google.common.util.concurrent.MoreExecutors;
2730
import com.google.inject.Guice;
2831
import com.google.inject.Injector;
2932
import com.google.inject.servlet.GuiceFilter;
@@ -65,7 +68,8 @@ public synchronized void start() throws Exception {
6568
logDir.mkdirs();
6669
stateDir.mkdirs();
6770

68-
KeyValueStateMachine stateMachine = new KeyValueStateMachine();
71+
ListeningExecutorService executor = MoreExecutors.listeningDecorator(Executors.newCachedThreadPool());
72+
KeyValueStateMachine stateMachine = new KeyValueStateMachine(executor);
6973

7074
ClusterConfig config = ClusterConfig.from(local, peers);
7175
this.raft = RaftService.newBuilder(config).logDir(logDir).timeout(300).build(stateMachine);

cloudata-keyvalue/src/main/java/com/cloudata/keyvalue/KeyValueStateMachine.java

Lines changed: 47 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import java.io.File;
66
import java.nio.ByteBuffer;
7+
import java.util.concurrent.Callable;
78
import java.util.concurrent.ExecutionException;
89

910
import javax.annotation.Nonnull;
@@ -19,18 +20,20 @@
1920

2021
import com.cloudata.btree.BtreeQuery;
2122
import com.cloudata.btree.Keyspace;
22-
import com.cloudata.keyvalue.KeyValueLog.KvEntry;
23-
import com.cloudata.keyvalue.operation.AppendOperation;
24-
import com.cloudata.keyvalue.operation.DeleteOperation;
25-
import com.cloudata.keyvalue.operation.IncrementOperation;
26-
import com.cloudata.keyvalue.operation.KeyOperation;
27-
import com.cloudata.keyvalue.operation.SetOperation;
23+
import com.cloudata.keyvalue.KeyValueProtocol.ActionResponse;
24+
import com.cloudata.keyvalue.KeyValueProtocol.KeyValueAction;
25+
import com.cloudata.keyvalue.operation.KeyValueOperation;
26+
import com.cloudata.keyvalue.operation.KeyValueOperations;
2827
import com.cloudata.values.Value;
28+
import com.google.common.base.Function;
29+
import com.google.common.base.Preconditions;
2930
import com.google.common.base.Throwables;
3031
import com.google.common.cache.CacheBuilder;
3132
import com.google.common.cache.CacheLoader;
3233
import com.google.common.cache.LoadingCache;
34+
import com.google.common.util.concurrent.Futures;
3335
import com.google.common.util.concurrent.ListenableFuture;
36+
import com.google.common.util.concurrent.ListeningExecutorService;
3437
import com.google.protobuf.ByteString;
3538
import com.google.protobuf.InvalidProtocolBufferException;
3639

@@ -41,7 +44,10 @@ public class KeyValueStateMachine implements StateMachine {
4144
private File baseDir;
4245
final LoadingCache<Long, KeyValueStore> keyValueStoreCache;
4346

44-
public KeyValueStateMachine() {
47+
private final ListeningExecutorService executor;
48+
49+
public KeyValueStateMachine(ListeningExecutorService executor) {
50+
this.executor = executor;
4551
KeyValueStoreCacheLoader loader = new KeyValueStoreCacheLoader();
4652
this.keyValueStoreCache = CacheBuilder.newBuilder().recordStats().build(loader);
4753
}
@@ -65,7 +71,7 @@ public void init(RaftService raft, File stateDir) {
6571
// return raft.commit(entry.toByteArray());
6672
// }
6773

68-
public <V> V doActionSync(KeyOperation<V> operation) throws InterruptedException, RaftException {
74+
public ActionResponse doActionSync(KeyValueOperation operation) throws InterruptedException, RaftException {
6975
try {
7076
return doActionAsync(operation).get();
7177
} catch (ExecutionException e) {
@@ -80,50 +86,53 @@ public <V> V doActionSync(KeyOperation<V> operation) throws InterruptedException
8086
}
8187
}
8288

83-
public <V> ListenableFuture<V> doActionAsync(KeyOperation<V> operation) throws RaftException {
84-
KvEntry entry = operation.serialize();
89+
public ListenableFuture<ActionResponse> doActionAsync(final KeyValueOperation operation) throws RaftException {
90+
if (operation.isReadOnly()) {
91+
return executor.submit(new Callable<ActionResponse>() {
92+
93+
@Override
94+
public ActionResponse call() throws Exception {
95+
// TODO: Need to check that we are the leader!!
96+
97+
long storeId = operation.getStoreId();
98+
99+
KeyValueStore keyValueStore = getKeyValueStore(storeId);
100+
101+
keyValueStore.doAction(operation);
102+
103+
return operation.getResult();
104+
}
105+
});
106+
}
107+
108+
KeyValueAction entry = operation.serialize();
85109

86110
log.debug("Proposing operation {}", entry.getAction());
87111

88-
return (ListenableFuture<V>) raft.commitAsync(entry.toByteArray());
112+
return Futures.transform(raft.commitAsync(entry.toByteArray()), new Function<Object, ActionResponse>() {
113+
114+
@Override
115+
public ActionResponse apply(Object input) {
116+
Preconditions.checkArgument(input instanceof ActionResponse);
117+
return (ActionResponse) input;
118+
}
119+
120+
});
89121
}
90122

91123
@Override
92124
public Object applyOperation(@Nonnull ByteBuffer op) {
93125
// TODO: We need to prevent repetition during replay
94126
// (we need idempotency)
95127
try {
96-
KvEntry entry = KvEntry.parseFrom(ByteString.copyFrom(op));
128+
KeyValueAction entry = KeyValueAction.parseFrom(ByteString.copyFrom(op));
97129
log.debug("Committing operation {}", entry.getAction());
98130

99131
long storeId = entry.getStoreId();
100132

101133
KeyValueStore keyValueStore = getKeyValueStore(storeId);
102134

103-
KeyOperation<?> operation;
104-
105-
switch (entry.getAction()) {
106-
107-
case APPEND:
108-
operation = new AppendOperation(entry);
109-
break;
110-
111-
case DELETE:
112-
operation = new DeleteOperation(entry);
113-
break;
114-
115-
case INCREMENT: {
116-
operation = new IncrementOperation(entry);
117-
break;
118-
}
119-
120-
case SET:
121-
operation = new SetOperation(entry);
122-
break;
123-
124-
default:
125-
throw new UnsupportedOperationException();
126-
}
135+
KeyValueOperation operation = KeyValueOperations.build(entry);
127136

128137
keyValueStore.doAction(operation);
129138

@@ -168,11 +177,13 @@ public KeyValueStore load(@Nonnull Long id) throws Exception {
168177
}
169178
}
170179

180+
// This should also really be an operation, but is special-cased for speed
171181
public Value get(long storeId, Keyspace keyspace, ByteString key) {
172182
KeyValueStore keyValueStore = getKeyValueStore(storeId);
173183
return keyValueStore.get(keyspace.mapToKey(key).asReadOnlyByteBuffer());
174184
}
175185

186+
// This function should really be an operation, but we want to support streaming
176187
public BtreeQuery scan(long storeId, Keyspace keyspace, ByteString keyPrefix) {
177188
KeyValueStore keyValueStore = getKeyValueStore(storeId);
178189
boolean stripKeyspace = true;

cloudata-keyvalue/src/main/java/com/cloudata/keyvalue/KeyValueStore.java

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
import com.cloudata.btree.PageStore;
1515
import com.cloudata.btree.ReadOnlyTransaction;
1616
import com.cloudata.btree.WriteTransaction;
17-
import com.cloudata.keyvalue.operation.KeyOperation;
17+
import com.cloudata.keyvalue.operation.KeyValueOperation;
1818
import com.cloudata.values.Value;
1919
import com.google.protobuf.ByteString;
2020

@@ -33,10 +33,16 @@ public KeyValueStore(File dir, boolean uniqueKeys) throws IOException {
3333
this.btree = new Btree(pageStore, uniqueKeys);
3434
}
3535

36-
public void doAction(KeyOperation<?> operation) {
37-
try (WriteTransaction txn = btree.beginReadWrite()) {
38-
txn.doAction(btree, operation);
39-
txn.commit();
36+
public void doAction(KeyValueOperation operation) {
37+
if (operation.isReadOnly()) {
38+
try (ReadOnlyTransaction txn = btree.beginReadOnly()) {
39+
txn.doAction(btree, operation);
40+
}
41+
} else {
42+
try (WriteTransaction txn = btree.beginReadWrite()) {
43+
txn.doAction(btree, operation);
44+
txn.commit();
45+
}
4046
}
4147
}
4248

Lines changed: 23 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,62 +1,49 @@
11
package com.cloudata.keyvalue.operation;
22

3-
import java.nio.ByteBuffer;
4-
5-
import com.cloudata.keyvalue.KeyValueLog.KvAction;
6-
import com.cloudata.keyvalue.KeyValueLog.KvEntry;
3+
import com.cloudata.btree.Keyspace;
4+
import com.cloudata.keyvalue.KeyValueProtocol.ActionType;
5+
import com.cloudata.keyvalue.KeyValueProtocol.KeyValueAction;
6+
import com.cloudata.keyvalue.KeyValueProtocol.ResponseEntry;
77
import com.cloudata.values.Value;
88
import com.google.common.base.Preconditions;
99
import com.google.protobuf.ByteString;
1010

11-
public class AppendOperation implements KeyOperation<Integer> {
11+
public class AppendOperation extends KeyValueOperationBase {
1212

13-
private int newLength;
14-
final KvEntry entry;
13+
public AppendOperation(KeyValueAction entry) {
14+
super(entry);
1515

16-
public AppendOperation(KvEntry entry) {
17-
Preconditions.checkState(entry.getAction() == KvAction.APPEND);
16+
Preconditions.checkState(entry.getAction() == ActionType.APPEND);
1817
Preconditions.checkState(!entry.getIfNotExists());
1918
Preconditions.checkState(!entry.hasIfValue());
20-
21-
this.entry = entry;
2219
}
2320

2421
@Override
2522
public Value doAction(Value oldValue) {
2623
Value appendValue = Value.deserialize(entry.getValue().asReadOnlyByteBuffer());
2724

25+
ResponseEntry.Builder eb = response.addEntryBuilder();
26+
eb.setKey(entry.getKey());
27+
28+
Value newValue;
2829
if (oldValue == null) {
29-
this.newLength = entry.getValue().size();
30-
return appendValue;
30+
newValue = appendValue;
3131
} else {
32-
Value appended = oldValue.concat(appendValue.asBytes());
33-
34-
this.newLength = appended.sizeAsBytes();
35-
return appended;
32+
newValue = oldValue.concat(appendValue.asBytes());
3633
}
37-
}
38-
39-
@Override
40-
public KvEntry serialize() {
41-
return entry;
42-
}
43-
44-
@Override
45-
public Integer getResult() {
46-
return newLength;
47-
}
34+
eb.setValue(ByteString.copyFrom(newValue.asBytes()));
35+
eb.setChanged(true);
4836

49-
@Override
50-
public ByteBuffer getKey() {
51-
return entry.getKey().asReadOnlyByteBuffer();
37+
return newValue;
5238
}
5339

54-
public static AppendOperation build(long storeId, ByteString qualifiedKey, Value value) {
55-
KvEntry.Builder b = KvEntry.newBuilder();
56-
b.setAction(KvAction.APPEND);
40+
public static AppendOperation build(long storeId, Keyspace keyspace, ByteString key, Value value) {
41+
KeyValueAction.Builder b = KeyValueAction.newBuilder();
42+
b.setAction(ActionType.APPEND);
5743
b.setStoreId(storeId);
58-
b.setKey(qualifiedKey);
59-
b.setValue(ByteString.copyFrom(value.serialize()));
44+
b.setKeyspaceId(keyspace.getKeyspaceId());
45+
b.setKey(key);
46+
b.setValue(ByteString.copyFrom(value.asBytes()));
6047
return new AppendOperation(b.build());
6148
}
6249
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package com.cloudata.keyvalue.operation;
2+
3+
import com.cloudata.btree.Btree;
4+
import com.cloudata.btree.Transaction;
5+
import com.cloudata.btree.operation.ComplexOperation;
6+
import com.cloudata.keyvalue.KeyValueProtocol.ActionResponse;
7+
import com.cloudata.keyvalue.KeyValueProtocol.ActionType;
8+
import com.cloudata.keyvalue.KeyValueProtocol.KeyValueAction;
9+
import com.google.common.base.Preconditions;
10+
11+
public class CompoundOperation implements KeyValueOperation, ComplexOperation<ActionResponse> {
12+
13+
private final KeyValueAction entry;
14+
15+
final ActionResponse.Builder response = ActionResponse.newBuilder();
16+
17+
public CompoundOperation(KeyValueAction entry) {
18+
Preconditions.checkState(entry.getAction() == ActionType.COMPOUND);
19+
Preconditions.checkArgument(entry.hasStoreId());
20+
21+
this.entry = entry;
22+
}
23+
24+
@Override
25+
public KeyValueAction serialize() {
26+
return entry;
27+
}
28+
29+
@Override
30+
public ActionResponse getResult() {
31+
return response.build();
32+
}
33+
34+
@Override
35+
public void doAction(Btree btree, Transaction txn) {
36+
for (KeyValueAction child : entry.getChildrenList()) {
37+
if (child.getStoreId() != entry.getStoreId()) {
38+
throw new IllegalArgumentException();
39+
}
40+
KeyValueOperation op = KeyValueOperations.build(child);
41+
txn.doAction(btree, op);
42+
response.addChildren(op.getResult());
43+
}
44+
}
45+
46+
@Override
47+
public boolean isReadOnly() {
48+
return KeyValueOperations.isReadOnly(entry);
49+
}
50+
51+
@Override
52+
public long getStoreId() {
53+
return entry.getStoreId();
54+
}
55+
56+
}

0 commit comments

Comments
 (0)