private <N extends InternalTitanVertex> void persist(
      ListMultimap<N, InternalRelation> mutatedEdges,
      Map<TitanType, TypeSignature> signatures,
      InternalTitanTransaction tx,
      StoreMutator mutator)
      throws StorageException {
    assert mutatedEdges != null && !mutatedEdges.isEmpty();

    Collection<N> vertices = mutatedEdges.keySet();
    //		if (sortNodes) {
    //			List<N> sortedvertices = new ArrayList<N>(vertices);
    //			Collections.sort(sortedvertices, new Comparator<N>(){
    //
    //				@Override
    //				public int compare(N o1, N o2) {
    //					assert o1.getID()!=o2.getID();
    //					if (o1.getID()<o2.getID()) return -1;
    //					else return 1;
    //				}
    //
    //			});
    //			vertices=sortedvertices;
    //		}

    for (N node : vertices) {
      List<InternalRelation> edges = mutatedEdges.get(node);
      List<Entry> additions = new ArrayList<Entry>(edges.size());
      List<ByteBuffer> deletions = new ArrayList<ByteBuffer>(Math.max(10, edges.size() / 10));
      List<TitanProperty> properties = new ArrayList<TitanProperty>();
      for (InternalRelation edge : edges) {
        if (edge.isRemoved()) {
          if (edge.isProperty()) {
            deleteIndexEntry((TitanProperty) edge, mutator);
          }
          deletions.add(getEntry(tx, edge, node, signatures, true).getColumn());
        } else {
          assert edge.isNew();
          if (edge.isProperty()) properties.add((TitanProperty) edge);
          additions.add(getEntry(tx, edge, node, signatures));
        }
      }
      mutator.mutateEdges(IDHandler.getKey(node.getID()), additions, deletions);
      // Persist property index for retrieval
      for (TitanProperty prop : properties) {
        addIndexEntry(prop, mutator);
      }
    }
  }
  private void assignIDs(Collection<InternalRelation> addedEdges, InternalTitanTransaction tx) {
    for (InternalRelation edge : addedEdges) {
      if (edge.isRemoved()) continue;
      if (edge.hasID()) continue;

      for (int i = 0; i < edge.getArity(); i++) {
        InternalTitanVertex node = edge.getVertex(i);
        if (!node.hasID()) {
          assert node.isNew();
          assignID(node);
        }
      }
      assert !edge.hasID();
      assignID(edge);
    }
  }
  @Override
  public void save(
      final Collection<InternalRelation> addedRelations,
      final Collection<InternalRelation> deletedRelations,
      final InternalTitanTransaction tx)
      throws StorageException {
    // Setup
    log.debug(
        "Saving transaction. Added {}, removed {}", addedRelations.size(), deletedRelations.size());
    final Map<TitanType, TypeSignature> signatures = new HashMap<TitanType, TypeSignature>();
    final TransactionHandle txh = tx.getTxHandle();

    final StoreMutator mutator = getStoreMutator(txh);
    final boolean acquireLocks = tx.getTxConfiguration().hasAcquireLocks();

    // 1. Assign TitanVertex IDs
    assignIDs(addedRelations, tx);

    for (int saveAttempt = 0; saveAttempt < maxWriteRetryAttempts; saveAttempt++) {
      //        while (true) { //Indefinite loop, broken if no exception occurs, otherwise retried
      // or failed immediately
      try {
        // 2. Collect deleted edges
        ListMultimap<InternalTitanVertex, InternalRelation> mutations = ArrayListMultimap.create();
        if (deletedRelations != null && !deletedRelations.isEmpty()) {
          for (InternalRelation del : deletedRelations) {
            assert del.isRemoved();
            for (int pos = 0; pos < del.getArity(); pos++) {
              InternalTitanVertex node = del.getVertex(pos);
              if (pos == 0 || !del.isUnidirected()) {
                mutations.put(node, del);
              }
              if (pos == 0
                  && acquireLocks
                  && del.getType().isFunctional()
                  && ((InternalTitanType) del.getType()).isFunctionalLocking()) {
                Entry entry = getEntry(tx, del, node, signatures);
                mutator.acquireEdgeLock(
                    IDHandler.getKey(node.getID()), entry.getColumn(), entry.getValue());
              }
            }
            if (acquireLocks && del.isProperty()) {
              lockKeyedProperty((TitanProperty) del, mutator);
            }
          }
        }

        ListMultimap<InternalTitanType, InternalRelation> simpleEdgeTypes = null;
        ListMultimap<InternalTitanType, InternalRelation> otherEdgeTypes = null;

        // 3. Sort Added Edges
        for (InternalRelation edge : addedRelations) {
          if (edge.isRemoved()) continue;
          assert edge.isNew();

          TitanType et = edge.getType();

          // Give special treatment to edge type definitions
          if (SystemTypeManager.prepersistedSystemTypes.contains(et)) {
            assert edge.getVertex(0) instanceof InternalTitanType;
            InternalTitanType node = (InternalTitanType) edge.getVertex(0);
            assert node.hasID();
            if (node.isSimple()) {
              if (simpleEdgeTypes == null) simpleEdgeTypes = ArrayListMultimap.create();
              simpleEdgeTypes.put(node, edge);
            } else {
              if (otherEdgeTypes == null) otherEdgeTypes = ArrayListMultimap.create();
              otherEdgeTypes.put(node, edge);
            }
          } else { // STANDARD TitanRelation
            assert (edge.getArity() == 1 && edge.isProperty())
                || (edge.getArity() == 2 && edge.isEdge());
            for (int pos = 0; pos < edge.getArity(); pos++) {
              InternalTitanVertex node = edge.getVertex(pos);
              assert node.hasID();
              if (pos == 0 || !edge.isUnidirected()) {
                mutations.put(node, edge);
              }
              if (pos == 0
                  && acquireLocks
                  && edge.getType().isFunctional()
                  && !node.isNew()
                  && ((InternalTitanType) edge.getType()).isFunctionalLocking()) {
                Entry entry = getEntry(tx, edge, node, signatures, true);
                mutator.acquireEdgeLock(IDHandler.getKey(node.getID()), entry.getColumn(), null);
              }
            }
          }
          if (acquireLocks && edge.isProperty()) {
            lockKeyedProperty((TitanProperty) edge, mutator);
          }
        }

        // 3. Persist
        if (simpleEdgeTypes != null) persist(simpleEdgeTypes, signatures, tx, mutator);
        if (otherEdgeTypes != null) persist(otherEdgeTypes, signatures, tx, mutator);
        mutator.flush();

        // Commit saved EdgeTypes to TypeManager
        if (simpleEdgeTypes != null) commitEdgeTypes(simpleEdgeTypes.keySet());
        if (otherEdgeTypes != null) commitEdgeTypes(otherEdgeTypes.keySet());

        if (!mutations.isEmpty()) persist(mutations, signatures, tx, mutator);
        mutator.flush();

        // Successfully completed - return to break out of loop
        break;
      } catch (Throwable e) {
        if (e instanceof TemporaryStorageException) {
          if (saveAttempt < maxWriteRetryAttempts - 1) temporaryStorageException(e);
          else
            throw new PermanentStorageException(
                "Tried committing "
                    + maxWriteRetryAttempts
                    + " times on temporary exception without success",
                e);
        } else if (e instanceof StorageException) {
          throw (StorageException) e;
        } else {
          throw new PermanentStorageException(
              "Unidentified exception occurred during persistence", e);
        }
      }
    }
  }