Skip to content

Commit de7faf9

Browse files
committed
Soft automatic schema reload
Now client keeps actual schema metadata and sends schemaId header to be checked against current Tarantool schema version. If client version mismatches DB version client does schema reloading in the background. Client operation interface was reworked in scope of support not only number identifiers for spaces and indexes but also their string names. Closes: #7, #137
1 parent 56c28a3 commit de7faf9

40 files changed

+2527
-443
lines changed

Diff for: README.md

+50-1
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ Feel free to override any method of `TarantoolClientImpl`. For example, to hook
131131
all the results, you could override this:
132132

133133
```java
134-
protected void complete(TarantoolPacket packet, TarantoolOp<?> future);
134+
protected void complete(TarantoolPacket packet, CompletableFuture<?> future);
135135
```
136136

137137
### Client config options
@@ -181,6 +181,55 @@ Supported options are follow:
181181
14. `operationExpiryTimeMillis` is a default request timeout in ms.
182182
Default value is `1000` (1 second).
183183

184+
## String space/index resolution
185+
186+
Each operation that requires space or index to be executed, can work with
187+
number ID as well as string name of a space or an index.
188+
Assume, we have `my_space` space with space ID `512` and its primary index
189+
`primary` with index ID `0`. Then, for instance, `select` operations can be
190+
performed using their names:
191+
192+
```java
193+
client.syncOps().select(512, 0, Collections.singletonList(1), 0, 1, Iterator.EQ);
194+
// or using more convenient way
195+
client.syncOps().select("my_space", "primary", Collections.singletonList(1), 0, 1, Iterator.EQ);
196+
```
197+
198+
Because _iproto_ has not yet supported string spaces and indexes, a client caches current server
199+
schema in memory. The client relies on protocol SCHEMA_ID and sends each request with respect to
200+
cached schema version. The schema is used primarily to resolve string names of spaces or indexes
201+
against its integer IDs.
202+
203+
### Schema update
204+
205+
1. Just after a (re-)connection to the Tarantool instance.
206+
The client cannot guarantee that new instance is the same and has same schema,
207+
thus, the client drops the cached schema and fetches new one.
208+
2. Receiving a schema version error as a response to our request.
209+
It's possible some request can be rejected by server because of schema
210+
mismatching between client and server. In this case the schema will be
211+
reloaded and the refused request will be resent using the updated schema
212+
version.
213+
3. Sending a DDL request and receiving a new version in a response.
214+
4. Sending a request against a non-existent space/index name.
215+
The client cannot exactly know whether name was not found because of
216+
it does not exist or it has not the latest schema version. A ping request
217+
is sent in the case to check a schema version and then a client will reload
218+
it if needed. The original request will be retried if a space / an index
219+
name will be found in a new schema.
220+
221+
### Schema support caveats
222+
223+
1. Each schema reloading requires at least two extra requests to fetch spaces and
224+
indexes metadata respectively. There is also a ping request followed by reloading
225+
of the schema to check whether the client has outdated version (see point 4 in
226+
[Schema update](#schema-update)).
227+
2. In some circumstance, requests can be rejected several times until both client's
228+
and server's versions matches. It may take significant amount of time or even be
229+
a cause of request timeout.
230+
3. The client guarantees an order of synchronous requests per thread. Other cases such
231+
as asynchronous or multi-threaded requests may be out of order before the execution.
232+
184233
## Spring NamedParameterJdbcTemplate usage example
185234

186235
The JDBC driver uses `TarantoolClient` implementation to provide a communication with server.
+167-25
Original file line numberDiff line numberDiff line change
@@ -1,62 +1,204 @@
11
package org.tarantool;
22

3+
import static org.tarantool.TarantoolRequestArgumentFactory.cacheLookupValue;
4+
import static org.tarantool.TarantoolRequestArgumentFactory.value;
35

4-
public abstract class AbstractTarantoolOps<Space, Tuple, Operation, Result>
5-
implements TarantoolClientOps<Space, Tuple, Operation, Result> {
6+
import org.tarantool.schema.TarantoolSchemaMeta;
7+
8+
import java.util.List;
9+
10+
public abstract class AbstractTarantoolOps<Result>
11+
implements TarantoolClientOps<Integer, List<?>, Object, Result> {
612

713
private Code callCode = Code.CALL;
814

9-
protected abstract Result exec(Code code, Object... args);
15+
protected abstract Result exec(TarantoolRequest request);
16+
17+
protected abstract TarantoolSchemaMeta getSchemaMeta();
18+
19+
public Result select(Integer space, Integer index, List<?> key, int offset, int limit, Iterator iterator) {
20+
return select(space, index, key, offset, limit, iterator.getValue());
21+
}
1022

11-
public Result select(Space space, Space index, Tuple key, int offset, int limit, Iterator iterator) {
23+
@Override
24+
public Result select(String space, String index, List<?> key, int offset, int limit, Iterator iterator) {
1225
return select(space, index, key, offset, limit, iterator.getValue());
1326
}
1427

15-
public Result select(Space space, Space index, Tuple key, int offset, int limit, int iterator) {
28+
@Override
29+
public Result select(Integer space, Integer index, List<?> key, int offset, int limit, int iterator) {
30+
return exec(
31+
new TarantoolRequest(
32+
Code.SELECT,
33+
value(Key.SPACE), value(space),
34+
value(Key.INDEX), value(index),
35+
value(Key.KEY), value(key),
36+
value(Key.ITERATOR), value(iterator),
37+
value(Key.LIMIT), value(limit),
38+
value(Key.OFFSET), value(offset)
39+
)
40+
);
41+
}
42+
43+
@Override
44+
public Result select(String space, String index, List<?> key, int offset, int limit, int iterator) {
45+
return exec(
46+
new TarantoolRequest(
47+
Code.SELECT,
48+
value(Key.SPACE), cacheLookupValue(() -> getSchemaMeta().getSpace(space).getId()),
49+
value(Key.INDEX), cacheLookupValue(() -> getSchemaMeta().getSpaceIndex(space, index).getId()),
50+
value(Key.KEY), value(key),
51+
value(Key.ITERATOR), value(iterator),
52+
value(Key.LIMIT), value(limit),
53+
value(Key.OFFSET), value(offset)
54+
)
55+
);
56+
}
57+
58+
@Override
59+
public Result insert(Integer space, List<?> tuple) {
60+
return exec(new TarantoolRequest(
61+
Code.INSERT,
62+
value(Key.SPACE), value(space),
63+
value(Key.TUPLE), value(tuple)
64+
)
65+
);
66+
}
67+
68+
@Override
69+
public Result insert(String space, List<?> tuple) {
70+
return exec(
71+
new TarantoolRequest(
72+
Code.INSERT,
73+
value(Key.SPACE), cacheLookupValue(() -> getSchemaMeta().getSpace(space).getId()),
74+
value(Key.TUPLE), value(tuple)
75+
)
76+
);
77+
}
78+
79+
@Override
80+
public Result replace(Integer space, List<?> tuple) {
81+
return exec(
82+
new TarantoolRequest(
83+
Code.REPLACE,
84+
value(Key.SPACE), value(space),
85+
value(Key.TUPLE), value(tuple)
86+
)
87+
);
88+
}
89+
90+
@Override
91+
public Result replace(String space, List<?> tuple) {
1692
return exec(
17-
Code.SELECT,
18-
Key.SPACE, space,
19-
Key.INDEX, index,
20-
Key.KEY, key,
21-
Key.ITERATOR, iterator,
22-
Key.LIMIT, limit,
23-
Key.OFFSET, offset
93+
new TarantoolRequest(
94+
Code.REPLACE,
95+
value(Key.SPACE), cacheLookupValue(() -> getSchemaMeta().getSpace(space).getId()),
96+
value(Key.TUPLE), value(tuple)
97+
)
2498
);
2599
}
26100

27-
public Result insert(Space space, Tuple tuple) {
28-
return exec(Code.INSERT, Key.SPACE, space, Key.TUPLE, tuple);
101+
@Override
102+
public Result update(Integer space, List<?> key, Object... operations) {
103+
return exec(
104+
new TarantoolRequest(
105+
Code.UPDATE,
106+
value(Key.SPACE), value(space),
107+
value(Key.KEY), value(key),
108+
value(Key.TUPLE), value(operations)
109+
)
110+
);
29111
}
30112

31-
public Result replace(Space space, Tuple tuple) {
32-
return exec(Code.REPLACE, Key.SPACE, space, Key.TUPLE, tuple);
113+
@Override
114+
public Result update(String space, List<?> key, Object... operations) {
115+
return exec(
116+
new TarantoolRequest(
117+
Code.UPDATE,
118+
value(Key.SPACE), cacheLookupValue(() -> getSchemaMeta().getSpace(space).getId()),
119+
value(Key.KEY), value(key),
120+
value(Key.TUPLE), value(operations)
121+
)
122+
);
123+
}
124+
125+
@Override
126+
public Result upsert(Integer space, List<?> key, List<?> defTuple, Object... operations) {
127+
return exec(
128+
new TarantoolRequest(
129+
Code.UPSERT,
130+
value(Key.SPACE), value(space),
131+
value(Key.KEY), value(key),
132+
value(Key.TUPLE), value(defTuple),
133+
value(Key.UPSERT_OPS), value(operations)
134+
)
135+
);
33136
}
34137

35-
public Result update(Space space, Tuple key, Operation... args) {
36-
return exec(Code.UPDATE, Key.SPACE, space, Key.KEY, key, Key.TUPLE, args);
138+
@Override
139+
public Result upsert(String space, List<?> key, List<?> defTuple, Object... operations) {
140+
return exec(
141+
new TarantoolRequest(
142+
Code.UPSERT,
143+
value(Key.SPACE), cacheLookupValue(() -> getSchemaMeta().getSpace(space).getId()),
144+
value(Key.KEY), value(key),
145+
value(Key.TUPLE), value(defTuple),
146+
value(Key.UPSERT_OPS), value(operations)
147+
)
148+
);
37149
}
38150

39-
public Result upsert(Space space, Tuple key, Tuple def, Operation... args) {
40-
return exec(Code.UPSERT, Key.SPACE, space, Key.KEY, key, Key.TUPLE, def, Key.UPSERT_OPS, args);
151+
@Override
152+
public Result delete(Integer space, List<?> key) {
153+
return exec(
154+
new TarantoolRequest(
155+
Code.DELETE,
156+
value(Key.SPACE), value(space),
157+
value(Key.KEY), value(key)
158+
)
159+
);
41160
}
42161

43-
public Result delete(Space space, Tuple key) {
44-
return exec(Code.DELETE, Key.SPACE, space, Key.KEY, key);
162+
@Override
163+
public Result delete(String space, List<?> key) {
164+
return exec(
165+
new TarantoolRequest(
166+
Code.DELETE,
167+
value(Key.SPACE), cacheLookupValue(() -> getSchemaMeta().getSpace(space).getId()),
168+
value(Key.KEY), value(key)
169+
)
170+
);
45171
}
46172

173+
@Override
47174
public Result call(String function, Object... args) {
48-
return exec(callCode, Key.FUNCTION, function, Key.TUPLE, args);
175+
return exec(
176+
new TarantoolRequest(
177+
callCode,
178+
value(Key.FUNCTION), value(function),
179+
value(Key.TUPLE), value(args)
180+
)
181+
);
49182
}
50183

184+
@Override
51185
public Result eval(String expression, Object... args) {
52-
return exec(Code.EVAL, Key.EXPRESSION, expression, Key.TUPLE, args);
186+
return exec(
187+
new TarantoolRequest(
188+
Code.EVAL,
189+
value(Key.EXPRESSION), value(expression),
190+
value(Key.TUPLE), value(args)
191+
)
192+
);
53193
}
54194

195+
@Override
55196
public void ping() {
56-
exec(Code.PING);
197+
exec(new TarantoolRequest(Code.PING));
57198
}
58199

59200
public void setCallCode(Code callCode) {
60201
this.callCode = callCode;
61202
}
203+
62204
}

Diff for: src/main/java/org/tarantool/Iterator.java

+10
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package org.tarantool;
22

3+
import java.util.Arrays;
4+
35
// Iterator info was taken from here https://github.com/tarantool/tarantool/blob/f66584c3bcdffe61d6d99a4868a9b72d62338a11/src/box/iterator_type.h#L62
46
public enum Iterator {
57
EQ(0), // key == x ASC order
@@ -24,4 +26,12 @@ public enum Iterator {
2426
public int getValue() {
2527
return value;
2628
}
29+
30+
public static Iterator valueOf(int value) {
31+
return Arrays.stream(Iterator.values())
32+
.filter(v -> value == v.getValue())
33+
.findFirst()
34+
.orElseThrow(IllegalArgumentException::new);
35+
}
36+
2737
}

Diff for: src/main/java/org/tarantool/TarantoolBase.java

+1-12
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,9 @@
66
import java.io.IOException;
77
import java.net.Socket;
88
import java.nio.channels.SocketChannel;
9-
import java.util.List;
109
import java.util.concurrent.atomic.AtomicLong;
1110

12-
public abstract class TarantoolBase<Result> extends AbstractTarantoolOps<Integer, List<?>, Object, Result> {
11+
public abstract class TarantoolBase<Result> extends AbstractTarantoolOps<Result> {
1312
protected String serverVersion;
1413
protected MsgPackLite msgPackLite = MsgPackLite.INSTANCE;
1514
protected AtomicLong syncId = new AtomicLong();
@@ -42,16 +41,6 @@ protected void closeChannel(SocketChannel channel) {
4241
}
4342
}
4443

45-
protected void validateArgs(Object[] args) {
46-
if (args != null) {
47-
for (int i = 0; i < args.length; i += 2) {
48-
if (args[i + 1] == null) {
49-
throw new NullPointerException(((Key) args[i]).name() + " should not be null");
50-
}
51-
}
52-
}
53-
}
54-
5544
public void setInitialRequestSize(int initialRequestSize) {
5645
this.initialRequestSize = initialRequestSize;
5746
}

Diff for: src/main/java/org/tarantool/TarantoolClient.java

+4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package org.tarantool;
22

3+
import org.tarantool.schema.TarantoolSchemaMeta;
4+
35
import java.util.List;
46
import java.util.Map;
57
import java.util.concurrent.CompletionStage;
@@ -29,4 +31,6 @@ public interface TarantoolClient {
2931

3032
boolean waitAlive(long timeout, TimeUnit unit) throws InterruptedException;
3133

34+
TarantoolSchemaMeta getSchemaMeta();
35+
3236
}

0 commit comments

Comments
 (0)