The normal transaction is not idempotent:
Repeat until success {
* start txn
* debit $5 from AccountA
* credit $5 to AccountB
* commit txn
}
If this inadvertently runs multiple times, I will have very unhappy
customers. Maybe I should do this:
Create TxnID entity instance
Repeat until success {
* start txn
* load TxnID (if null, return success immediately)
* debit $5 from AccountA
* credit $5 to AccountB
* delete TxnID
* commit txn
}
With the pain of adding a third entity to my XG transaction (and a
cron that prunes really old, abandoned TxnIDs) it seems that I this
does the trick. Or am I missing something? Is this the best way to
accomplish once-only transactions? Is there an alternative approach?
Thanks in advance,
Jeff
In some cases, when I've got revision numbers on the entities, I
improve the process by cleaning the marker up after the transaction.
But if it is valid for an account to be updated multiple times,
provided some condition is met, I leave the marker. Note that nearly
all of my processing is task-based, so tasks could run multiple times,
or rerun with a delay, etc.... That is why I leave the markers for a
while.
Robert
> --
> You received this message because you are subscribed to the Google Groups "Google App Engine" group.
> To post to this group, send email to google-a...@googlegroups.com.
> To unsubscribe from this group, send email to google-appengi...@googlegroups.com.
> For more options, visit this group at http://groups.google.com/group/google-appengine?hl=en.
>
Jeff
> --
> You received this message because you are subscribed to the Google Groups
> "Google App Engine" group.
> To view this discussion on the web visit
> https://groups.google.com/d/msg/google-appengine/-/VVq0iD0escIJ.
If it makes you feel better, Bank Of America doesn’t seem to have solved this, “auto-paid” my car payment 3 times this month because of an error and mailed off 3 checks to Capital One.
The solution to all of these problems could be what B Of A does, bounce one of the transaction, then send the user a bill for $35 * 10 for the $2.10 transactions from Google.
PS
If you solve this issue, please make the solution open source so B of A can use it.
PSS
It’d be awesome if we could get consolidated Monthly billing for GAE for less than $500 a month J
--
You received this message because you are subscribed to the Google Groups "Google App Engine" group.
To view this discussion on the web visit https://groups.google.com/d/msg/google-appengine/-/YU3N7fnt8rAJ.
Creating a txnId entity outside the transaction and deleting it in the
transaction gives me idempotence; no matter how many times the
transaction runs, only one of those transactions will complete and
delete the txnId entity and all the others will either rollback (eg
optimistic concurrency failure) or notice that the txnId has been
deleted and immediately return success.
The problem with calling to an external system (the cc processing
service example) is that there's no way to enlist it in the
transaction. You need to build up the framework of a 2pc transaction
to the cc service. All the cc processing services I know of sidestep
the issue by repeatedly posting some sort of IPN to you, which you are
expected to handle idempotently yourself. Actually, there's another
variation - some of them have separate auth/capture steps which are
kind of like a 2pc transaction.
Jeff
On Sat, Jan 21, 2012 at 6:04 PM, AndyD <goo...@adennie.e4ward.com> wrote:
> Yes, I see what you're saying. But in looking at both your and Robert's
> approaches, I still have questions.
>
> Jeff, how does your approach avoid executing the operation twice? Let's say
> there's a web page with a submit button that triggers the process, and the
> user clicks that button twice. The algorithm you outlined...
>
> Create TxnID entity instance
> Repeat until success {
> * start txn
> * load TxnID (if null, return success immediately)
> * debit $5 from AccountA
> * credit $5 to AccountB
> * delete TxnID
> * commit txn
> }
>
> ...if run serially, would do the operation twice, wouldn't it? The first
> execution would create the TxnID entity, then in the loop, it would load
> that entity successfully, perform the operation, then delete the TxnID
> entity. Then, when the 2nd button click is handled, the operation would be
> be performed again.
>
> Robert, I didn't quite follow your algorithm. Does your marker indicate
> that the operation has not yet been performed? If so, shouldn't 2b say "if
> the marker is not found, abort? Also, is there an implied retry mechanism
> (e.g. a task) encompassing step 2?
>
> It seems to me there are multiple challenges:
> 1) eliminate the potential of concurrency
> 2) retry on failure
> 3) only do the operation once
>
> If we don't eliminate concurrency, then two parallel threads, in the same or
> different instances, could find the operation not yet completed and both
> perform it. If we indicate (by the presence or absence of a marker) that
> the operation has been completed, then two serial executions would both
> perform it. And if we don't have a retry mechanism, there is a risk that
> the operation won't be performed at all.
>
> It still seems to me that the task queue approach addresses 1 (by not
> dequeing a task to two handlers simultaneously and also not accepting a
> duplicate task into the queue) and 2 (by automatically retrying on failure),
> but as Jeff pointed out, not 3. If you add an "operation complete" marker
> entity to the picture that gets written in the same transaction as the
> run-once operation, does that cover all the bases? The marker would still
> need to be cleaned up at some later point, of course.
>
> Just to make it interesting, what if the operation to be performed once was
> not a datastore operation? What if it was a call to an external system
> (like a credit card processing service)? You could successfully make the
> external call, but fail to write the "operation complete" entity.
>
> -Andy
>
>
> --
> You received this message because you are subscribed to the Google Groups
> "Google App Engine" group.
> To view this discussion on the web visit
> https://groups.google.com/d/msg/google-appengine/-/aKzPhfSgF0AJ.
Transfers to process is a list
XFERID Jeff to Brandon $100
XFERID Brian to Brandon $100
XFERID Brandon to Safeway $7
Transactions to Process is a list.
TXID 51 Transfer $100 From Jeff
TXID 52 Transfer $100 From Brian
TXID 53 Transfer $7 From Brandon
TXID 54 Transfer $100 to Brandon
TXID 55 Transfer $100 to Brandon
TXID 56 Transfer $7 to Safeway
Brandon's account is a list
BWID 1 Account Opened with $150
BWID 2 Received $78 via TXID 38
BWID 3 Receive $100 via TXID 51
BWID 4 Receive $100 via TXID 52
BWID 5 Send $7 via TXID 53
Jeffs account is a list
JSID 1 Account opened with $5000
JSID 2 Send $100 via TXID 51
You don't move Dollars from X to Y. You always move to and from general
fund.
You add and remove dollars based on Transaction IDs which must be unique.
You rectify accounts in a batch which includes which transactions were
rectified. To determine the current balance.
I apologize for the brevity, I was typing this quickly between phone calls,
but hopefully this demonstrates the solution.
This solution creates more moving parts as a total, but fewer moving parts
per type, and the unique ids means that if you have two of the same ID you
ignore them when processing.
Also note that if I were implementing this Transfer requests would be
assigned an ID. The User would enter the information. They would be given a
confirmation. And then the transfer would be added to the queue. Unconfirmed
transfers would still consume a transfer ID.
-----Original Message-----
From: google-a...@googlegroups.com
[mailto:google-a...@googlegroups.com] On Behalf Of Jeff Schnitzer
Sent: Saturday, January 21, 2012 3:42 PM
To: google-a...@googlegroups.com
Subject: Re: [google-appengine] Re: Once-only transactions
I ended up creating a little bit of glue so that I can run a once-only
transaction anywhere:
ofy.transactOnceOnly(keyOfSomeEntityGroup, new VoidWork() {
@Override
public void vrun(Objectify ofy) {
// execution transaction, it will only complete once
}
});
...where keyOfSomeEntityGroup defines an arbitrary parent for my
transaction marker entity, keeping the # of entity groups to a
minimum.
If someone else wants the code I can post it. It's Java/Objectify but
you could easily adapt it to Python since the Ofy and GAE/Python
transaction APIs are similar.
Jeff
So my approach is effectively what Jeff had outlined, the difference
is that I use the existence of a marker to indicate that the
transaction has been completed. There may or may not be an implied
retry around step 2, but yes -- I generally run things like this in a
task.
>
> It seems to me there are multiple challenges:
> 1) eliminate the potential of concurrency
> 2) retry on failure
> 3) only do the operation once
>
> If we don't eliminate concurrency, then two parallel threads, in the same or
> different instances, could find the operation not yet completed and both
> perform it. If we don't indicate (by the presence or absence of a marker)
> that the operation has been completed, then two serial executions would both
> perform it. And if we don't have a retry mechanism, there is a risk that
> the operation won't be performed at all.
>
> It still seems to me that the task queue approach addresses 1 (by not
> dequeing a task to two handlers simultaneously and also not accepting a
> duplicate task into the queue) and 2 (by automatically retrying on failure),
> but as Jeff pointed out, not 3. If you add an "operation not performed yet"
> marker entity to the picture that gets deleted in the same transaction as
> the run-once operation, does that cover all the bases?
There is a vey important note here. Tasks *do* occasionally run
multiple times. You should never assume that a task will be run
exactly once, rather the opposite; assume your task will be run twice,
either simultaneously or serially, which ever is worse for your app.
Robert
>
> Just to make it interesting, what if the operation to be performed once
> was not a datastore operation? What if it was a call to an external system
> (like a credit card processing service)? You could successfully make the
> external call, but fail to write the "operation complete" entity.
>
> -Andy
>
> --
> You received this message because you are subscribed to the Google Groups
> "Google App Engine" group.
> To view this discussion on the web visit
> https://groups.google.com/d/msg/google-appengine/-/YU3N7fnt8rAJ.