|
5 | 5 | import java.util.concurrent.TimeUnit;
|
6 | 6 | import java.util.concurrent.TimeoutException;
|
7 | 7 |
|
8 |
| -import org.slf4j.Logger; |
9 |
| -import org.slf4j.LoggerFactory; |
10 | 8 | import tech.ydb.common.transaction.TxMode;
|
11 | 9 | import tech.ydb.core.Result;
|
12 | 10 | import tech.ydb.core.Status;
|
|
21 | 19 | import tech.ydb.table.result.ResultSetReader;
|
22 | 20 | import tech.ydb.table.values.PrimitiveValue;
|
23 | 21 | import tech.ydb.topic.TopicClient;
|
24 |
| -import tech.ydb.topic.description.Codec; |
25 | 22 | import tech.ydb.topic.settings.SendSettings;
|
26 | 23 | import tech.ydb.topic.settings.WriterSettings;
|
27 | 24 | import tech.ydb.topic.write.AsyncWriter;
|
28 | 25 | import tech.ydb.topic.write.Message;
|
29 | 26 | import tech.ydb.topic.write.QueueOverflowException;
|
30 |
| -import tech.ydb.topic.write.WriteAck; |
31 | 27 |
|
32 | 28 | /**
|
33 | 29 | * @author Nikolay Perfilov
|
34 | 30 | */
|
35 | 31 | public class TransactionWriteAsync extends SimpleTopicExample {
|
36 |
| - private static final Logger logger = LoggerFactory.getLogger(TransactionWriteAsync.class); |
37 |
| - private static final String PRODUCER_ID = "messageGroup1"; |
38 |
| - private static final String MESSAGE_GROUP_ID = "messageGroup1"; |
39 | 32 | private static final long SHUTDOWN_TIMEOUT_SECONDS = 10;
|
40 | 33 |
|
41 | 34 | @Override
|
42 | 35 | protected void run(GrpcTransport transport) {
|
43 |
| - |
| 36 | + // WARNING: Working with transactions in Java Topic SDK is currently experimental. Interfaces may change |
44 | 37 | try (TopicClient topicClient = TopicClient.newClient(transport).build()) {
|
45 | 38 | try (QueryClient queryClient = QueryClient.newClient(transport).build()) {
|
| 39 | + long id = 2; |
| 40 | + String randomProducerId = "randomProducerId"; // Different for writers with different transactions |
46 | 41 | WriterSettings writerSettings = WriterSettings.newBuilder()
|
47 | 42 | .setTopicPath(TOPIC_NAME)
|
48 |
| - .setProducerId(PRODUCER_ID) |
49 |
| - .setMessageGroupId(MESSAGE_GROUP_ID) |
50 |
| - .setCodec(Codec.ZSTD) |
| 43 | + .setProducerId(randomProducerId) |
| 44 | + .setMessageGroupId(randomProducerId) |
51 | 45 | .build();
|
52 | 46 |
|
53 |
| - writeFromTableToTopic(topicClient, queryClient, writerSettings, 1); |
54 |
| - writeFromTableToTopic(topicClient, queryClient, writerSettings, 2); |
| 47 | + SessionRetryContext retryCtx = SessionRetryContext.create(queryClient).build(); |
| 48 | + retryCtx.supplyStatus(querySession -> { |
| 49 | + QueryTransaction transaction = querySession.beginTransaction(TxMode.SERIALIZABLE_RW) |
| 50 | + .join().getValue(); |
| 51 | + |
| 52 | + QueryStream queryStream = transaction.createQuery( |
| 53 | + "DECLARE $id AS Uint64;\n" + |
| 54 | + "SELECT value FROM table WHERE id=$id", |
| 55 | + Params.of("$id", PrimitiveValue.newUint64(id))); |
| 56 | + QueryReader queryReader = QueryReader.readFrom(queryStream).join().getValue(); |
| 57 | + ResultSetReader resultSet = queryReader.getResultSet(0); |
| 58 | + if (!resultSet.next()) { |
| 59 | + throw new RuntimeException("Value for id=" + id + " not found"); |
| 60 | + } |
| 61 | + String value = resultSet.getColumn("value").getText(); |
| 62 | + |
| 63 | + // Current implementation requires creating a writer for every transaction: |
| 64 | + AsyncWriter writer = topicClient.createAsyncWriter(writerSettings); |
| 65 | + writer.init(); |
| 66 | + System.err.println("writer initialized, value: " + value); |
| 67 | + try { |
| 68 | + writer.send(Message.of(value.getBytes()), |
| 69 | + SendSettings.newBuilder() |
| 70 | + .setTransaction(transaction) |
| 71 | + .build()) |
| 72 | + .join(); // Waiting for WriteAck before committing transaction |
| 73 | + } catch (QueueOverflowException exception) { |
| 74 | + // Send queue is full. Need to retry with backoff or skip |
| 75 | + throw new RuntimeException("Couldn't add message to SDK buffer", exception); |
| 76 | + } |
| 77 | + CompletableFuture<Status> commitStatus = transaction.commit().thenApply(Result::getStatus); |
| 78 | + commitStatus.join(); |
| 79 | + try { |
| 80 | + System.err.println("Commit status: " + commitStatus.get()); |
| 81 | + } catch (InterruptedException | ExecutionException e) { |
| 82 | + throw new RuntimeException(e); |
| 83 | + } |
| 84 | + |
| 85 | + try { |
| 86 | + writer.shutdown().get(SHUTDOWN_TIMEOUT_SECONDS, TimeUnit.SECONDS); |
| 87 | + } catch (TimeoutException exception) { |
| 88 | + throw new RuntimeException("Shutdown not finished within " + SHUTDOWN_TIMEOUT_SECONDS + |
| 89 | + " seconds"); |
| 90 | + } catch (InterruptedException | ExecutionException exception) { |
| 91 | + throw new RuntimeException("Shutdown not finished due to exception: " + exception); |
| 92 | + } |
| 93 | + |
| 94 | + return commitStatus; |
| 95 | + }).join().expectSuccess("Couldn't read from table and write to topic in transaction"); |
55 | 96 | }
|
56 | 97 | }
|
57 | 98 | }
|
58 | 99 |
|
59 |
| - private void writeFromTableToTopic(TopicClient topicClient, QueryClient queryClient, |
60 |
| - WriterSettings writerSettings, long id) { |
61 |
| - SessionRetryContext retryCtx = SessionRetryContext.create(queryClient).build(); |
62 |
| - retryCtx.supplyStatus(querySession -> { |
63 |
| - // Create new transaction object. It is not yet started on server |
64 |
| - QueryTransaction transaction = querySession.createNewTransaction(TxMode.SERIALIZABLE_RW); |
65 |
| - |
66 |
| - // Execute a query to start a new transaction |
67 |
| - QueryStream queryStream = transaction.createQuery( |
68 |
| - "DECLARE $id AS Uint64; " + |
69 |
| - "SELECT value FROM table WHERE id=$id", |
70 |
| - Params.of("$id", PrimitiveValue.newUint64(id))); |
71 |
| - |
72 |
| - // Get query result |
73 |
| - QueryReader queryReader = QueryReader.readFrom(queryStream).join().getValue(); |
74 |
| - ResultSetReader resultSet = queryReader.getResultSet(0); |
75 |
| - if (!resultSet.next()) { |
76 |
| - throw new RuntimeException("Value for id=" + id + " not found"); |
77 |
| - } |
78 |
| - String value = resultSet.getColumn("value").getText(); |
79 |
| - |
80 |
| - // Create a writer |
81 |
| - AsyncWriter writer = topicClient.createAsyncWriter(writerSettings); |
82 |
| - |
83 |
| - // Init in background |
84 |
| - writer.init(); |
85 |
| - |
86 |
| - // Write a message |
87 |
| - while (true) { |
88 |
| - try { |
89 |
| - // Blocks until the message is put into sending buffer |
90 |
| - writer.send(Message.of(value.getBytes()), |
91 |
| - SendSettings.newBuilder() |
92 |
| - .setTransaction(transaction) |
93 |
| - .build()) |
94 |
| - .whenComplete((result, ex) -> { |
95 |
| - if (ex != null) { |
96 |
| - logger.error("Exception while sending a message: ", ex); |
97 |
| - } else { |
98 |
| - logger.info("Message ack received"); |
99 |
| - |
100 |
| - switch (result.getState()) { |
101 |
| - case WRITTEN: |
102 |
| - WriteAck.Details details = result.getDetails(); |
103 |
| - logger.info("Message was written successfully, offset: " + |
104 |
| - details.getOffset()); |
105 |
| - break; |
106 |
| - case ALREADY_WRITTEN: |
107 |
| - logger.warn("Message was already written"); |
108 |
| - break; |
109 |
| - default: |
110 |
| - throw new RuntimeException("Unknown WriteAck state: " + result.getState()); |
111 |
| - } |
112 |
| - } |
113 |
| - }) |
114 |
| - // Waiting for the message to reach the server before committing the transaction |
115 |
| - .join(); |
116 |
| - logger.info("Message is sent"); |
117 |
| - break; |
118 |
| - } catch (QueueOverflowException exception) { |
119 |
| - logger.error("Queue overflow exception while sending a message"); |
120 |
| - // Send queue is full. Need to retry with backoff or skip |
121 |
| - } |
122 |
| - } |
123 |
| - |
124 |
| - // Commit transaction |
125 |
| - CompletableFuture<Status> commitStatus = transaction.commit().thenApply(Result::getStatus); |
126 |
| - |
127 |
| - // Shutdown writer |
128 |
| - try { |
129 |
| - writer.shutdown().get(SHUTDOWN_TIMEOUT_SECONDS, TimeUnit.SECONDS); |
130 |
| - } catch (TimeoutException exception) { |
131 |
| - logger.error("Timeout exception during writer termination ({} seconds): ", SHUTDOWN_TIMEOUT_SECONDS, exception); |
132 |
| - } catch (ExecutionException exception) { |
133 |
| - logger.error("Execution exception during writer termination: ", exception); |
134 |
| - } catch (InterruptedException exception) { |
135 |
| - logger.error("Writer termination was interrupted: ", exception); |
136 |
| - } |
137 |
| - |
138 |
| - // Return commit status to SessionRetryContext function |
139 |
| - return commitStatus; |
140 |
| - }).join().expectSuccess("Couldn't read from table and write to topic in transaction"); |
141 |
| - } |
142 |
| - |
143 | 100 | public static void main(String[] args) {
|
144 | 101 | new TransactionWriteAsync().doMain(args);
|
145 | 102 | }
|
|
0 commit comments