Transaction has been aborted

Posted by weickdev:

Following the document, I operation transaction like this:

Transaction txn = dgraphClient.newTransaction();
  try {
    // Do something here
    // ...
  } finally {
    txn.discard();
  }

It works well if I run single thread.

But if I run this in multi-threads, it throws below exception:

Exception in thread "pool-2-thread-5" io.dgraph.TxnConflictException: Transaction has been aborted. Please retry.
	at io.dgraph.DgraphClient$Transaction.checkAndThrowException(DgraphClient.java:315)
	at io.dgraph.DgraphClient$Transaction.commit(DgraphClient.java:256)

This is my code:

private void saveVertices(List<Map<String, Object>> itemList) {
        long startTime = System.currentTimeMillis();
        DgraphClient.Transaction transaction = dgraphClient.newTransaction();
        try {
            try {
                String json = JSON.toJSONString(itemList);
                DgraphProto.Mutation mutation = DgraphProto.Mutation.newBuilder().setSetJson(ByteString.copyFromUtf8(json)).build();
                transaction.mutate(mutation);
            } catch (Exception e) {
                logger.error("Node create error: ", e);
            }
            transaction.commit();
        } finally {
            transaction.discard();
        }
        logger.info("save {} items costs {}ms", itemList.size(), System.currentTimeMillis() - startTime);
    }

private void ansySaveVertex(List<Map<String, Object>> dataList) {
        List<Map<String, Object>> dataListCopy = new ArrayList<>();
        dataListCopy.addAll(dataList);
        executorService.execute(() -> {
            saveVertices(dataListCopy);
        });
        dataList.clear();
    }

public void init() {
        ManagedChannel channel = ManagedChannelBuilder.forAddress("192.168.1.74", 9080).usePlaintext(true).build();
        DgraphGrpc.DgraphBlockingStub blockingStub = DgraphGrpc.newBlockingStub(channel);
        dgraphClient = new DgraphClient(Collections.singletonList(blockingStub));
        executorService = new ThreadPoolExecutor(
                threadNum, threadNum, 0, TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(threadNum),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.CallerRunsPolicy());
    }

deepakjois commented :

I don’t see the data you are trying to mutate. If your code is mutating the same nodes in Dgraph concurrently, then it is possible that a TxnConflictException is thrown. This is to prevent the data in Dgraph from getting inconsistent. You have a couple of options:

  • Check for TxnConflictException and retry the transaction with some sort of backoff. Eventually it will succeed once other conflicting transactions have been committed.

  • Make sure concurrent threads are never operating on the same nodes, so that there is no chance of a conflict.

weickdev commented :

@deepakjois every node is a new one, and don’t have uid. I create node like this:

private Map<String, Object> makeVertex(long index, String type) {
        Map<String, Object> data = new HashMap<>();
        data.put("sequence", index);
        data.put("ctime", new Date());
        data.put("_key", type + "/" + DigestUtils.md5Hex(String.valueOf(index).getBytes()));
        data.put("name", StringUtils.substring(UUID.randomUUID().toString(), 0, random.nextInt(32)));
        return data;
    }

and data is provided in main thread like this:

List<Map<String, Object>> dataList = new ArrayList<>();
        for (long i = 0; i < count; ) {
            dataList.add(makeVertex(i, type));
            if (++i % perCount == 0) {
                logger.info("submit {} batch {} data", i / perCount, type);
                ansySaveVertex(dataList);
                dataList.clear();
            }
        }

deepakjois commented :

Ok, in that case I am not really sure under what conditions the server can issue a transaction conflict exception. It could be a problem in the Java client, but it’s unlikely. I will ask the folks working on the server to take a look here, and respond before I investigate the client further.

Meanwhile, you could still try retrying the transaction with backoff, and see how it goes.

weickdev commented :

@deepakjois I try retrying the transaction, It can be successful after many retry times. Some times, I get this exception:

[pool-2-thread-1] ERROR com.haizhi.graph.lab.dgraph.datamaker.DgraphDataMaker - Node create error: 
io.dgraph.TxnConflictException: Conflicts with pending transaction. Please abort.
	at io.dgraph.DgraphClient$Transaction.checkAndThrowException(DgraphClient.java:315)
	at io.dgraph.DgraphClient$Transaction.mutate(DgraphClient.java:228)
	at com.haizhi.graph.lab.dgraph.datamaker.DgraphDataMaker.saveVertices(DgraphDataMaker.java:127)

mpires commented :

Something similar happened to me.
With many mutations to the same indexed predicate, the predicate is being indexed and thus conflicts occur.
This can safely be ignored with the Mutation.setIgnoreIndexConflict(true) if you’re doing updates (as opposed to upserts)

weickdev commented :

@mpires Thanks for you help, It works. But why predicate index will cause conflict exception?

mpires commented :

I believe the rationale is, you made two concurrent mutations to the same predicate, mutation #1 created a new node and the index is being reindexed. Mutation #2 might perform a query beforehand (within its transaction) to ascertain if there’s a particular node. Since the index is being refreshed, the query might/will not yield the result created in #1.

weickdev commented :

@mpires yes, it sounds reasonable, thanks so much!