[ 
https://issues.apache.org/jira/browse/CASSANDRA-5062?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=13590210#comment-13590210
 ] 

Jonathan Ellis commented on CASSANDRA-5062:
-------------------------------------------

A more-complete sketch of the coordinator side:

{code}
.   /**
     * Apply @param updates if and only if the current values in the row for 
@param key
     * match the ones given by @param old.  The algorithm is "raw" Paxos: that 
is, Paxos
     * minus leader election -- any node in the cluster may propose changes for 
any row,
     * which (that is, the row) is the unit of values being proposes, not 
single columns.
     *
     * The Paxos cohort is only the replicas for the given key, not the entire 
cluster.
     * So we expect performance to be reasonable, but CAS is still intended to 
be used
     * "when you really need it," not for all your updates.
     *
     * There are three phases to Paxos:
     *  1. Prepare: the coordinator generates a ballot (timeUUID in our case) 
and asks replicas to (a) promise
     *     not to accept updates from older ballots and (b) tell us about the 
most recent update it has already
     *     accepted.
     *  2. Accept: if a majority of replicas reply, the coordinator asks 
replicas to accept the value of the
     *     highest proposal ballot it heard about, or a new value if no 
in-progress proposals were reported.
     *  3. Commit (Learn): if a majority of replicas acknowledge the accept 
request, we can commit the new
     *     value.
     *
     *  Commit procedure is not covered in "Paxos Made Simple," and only 
briefly mentioned in "Paxos Made Live,"
     *  so here is our approach:
     *   3a. The coordinator sends a commit message to all replicas with the 
ballot and value.
     *   3b. Because of 1-2, this will be the highest-seen commit ballot.  The 
replicas will note that,
     *       and send it with subsequent promise replies.  This allows us to 
discard acceptance records
     *       for successfully committed replicas, without allowing incomplete 
proposals to commit erroneously
     *       later on.
     *
     *  Note that since we are performing a CAS rather than a simple update, we 
perform a read (of committed
     *  values) between the prepare and accept phases.  This gives us a 
slightly longer window for another
     *  coordinator to come along and trump our own promise with a newer one 
but is otherwise safe.
     *
     * @return true if the operation succeeds in updating the row
     */
    public static boolean cas(String table, ByteBuffer key, ColumnFamily 
expected, ColumnFamily updates)
    {
        // begin a paxos round
        UUID paxosBallot = UUIDGen.getTimeUUID();
        Token tk = StorageService.getPartitioner().getToken(key);
        List<InetAddress> naturalEndpoints = 
StorageService.instance.getNaturalEndpoints(table, tk);
        Collection<InetAddress> pendingEndpoints = 
StorageService.instance.getTokenMetadata().pendingEndpointsFor(tk, table);
        Iterable<InetAddress> allEndpoints = Iterables.concat(naturalEndpoints, 
pendingEndpoints);
        Map<InetAddress, PaxosPrepareResponse> inProgressProposals = 
preparePaxos(paxosBallot, allEndpoints);

        // if we didn't hear from a majority of replicas, we have to bail for 
now
        int quorum = 1 + Iterables.size(allEndpoints) / 2;
        if (inProgressProposals.size() < quorum)
            throw new UnavailableException(ConsistencyLevel.SERIAL, quorum, 
inProgressProposals.size());

        // summarize the responses
        UUID mostRecentCommitted = UUIDGen.minTimeUUID(0);
        UUID inProgressBallot = UUIDGen.minTimeUUID(0);
        ColumnFamily inProgressUpdates;
        for (PaxosPrepareResponse response : inProgressProposals.values())
        {
            if (!response.promised)
            {
                // technically, we can proceed if we get a majority of 
successful promises,
                // but if a higher proposal number already exists even on one 
replica,
                // chances are it will be on more by the time we send a value 
to accept
                return false;
            }

            if (timeComparator.compare(response.mostRecentCommitted, 
mostRecentCommitted) > 0)
                mostRecentCommitted = response.mostRecentCommitted;
            if (timeComparator.compare(response.inProgressBallot, 
inProgressBallot) > 0)
            {
                inProgressBallot = response.inProgressBallot;
                inProgressUpdates = response.inProgressUpdates;
            }
        }

        // complete earlier, in-progress rounds if necessary
        if (inProgressUpdates != null && 
timeComparator.compare(inProgressBallot, mostRecentCommitted) >= 0)
        {
            RowMutation rm = new RowMutation(table, key, inProgressUpdates);
            if (acceptPaxos(paxosBallot, allEndpoints, rm))
                commitPaxos(paxosBallot, allEndpoints, rm);
            return false;
        }

        // read the current value
        ReadCommand readCommand = new SliceByNamesReadCommand(filter for 
expected);
        List<Row> rows = read(readCommand, ConsistencyLevel.QUORUM);

        // compare current value with expected
        if (expected does not match current)
            return false;

        // finish the paxos round w/ the desired updates
        RowMutation rm = new RowMutation(table, key, updates);
        RowMutation rm = new RowMutation(table, key, inProgressUpdates);
        if (acceptPaxos(paxosBallot, allEndpoints, rm))
        {
            commitPaxos(paxosBallot, allEndpoints, rm);
            return true;
        }
        return false;
    }

    private static class PaxosPrepareResponse
    {
        public final boolean promised;
        public final UUID mostRecentCommitted;
        public final UUID inProgressBallot;
        public final ColumnFamily inProgressUpdates;
    }
{code}
                
> Support CAS
> -----------
>
>                 Key: CASSANDRA-5062
>                 URL: https://issues.apache.org/jira/browse/CASSANDRA-5062
>             Project: Cassandra
>          Issue Type: New Feature
>          Components: API, Core
>            Reporter: Jonathan Ellis
>             Fix For: 2.0
>
>
> "Strong" consistency is not enough to prevent race conditions.  The classic 
> example is user account creation: we want to ensure usernames are unique, so 
> we only want to signal account creation success if nobody else has created 
> the account yet.  But naive read-then-write allows clients to race and both 
> think they have a green light to create.

--
This message is automatically generated by JIRA.
If you think it was sent incorrectly, please contact your JIRA administrators
For more information on JIRA, see: http://www.atlassian.com/software/jira

Reply via email to