views:

473

answers:

5

Hi!

How do people generate auto_incrementing integers for a particular user in a typical saas application?

For example, the invoice numbers for all the invoices for a particular user should be auto_incrementing and start from 1. The rails id field can't be used in this case, as it's shared amongst all the users.

Off the top of my head, I could count all the invoices a user has, and then add 1, but does anyone know of any better solution?

A: 

Not sure if this is the best solution, but you could store the last Invoice ID on the User and then use that to determine the next ID when creating a new Invoice for that User. But this simple solution may have problems with integrity, will need to be careful.

Peter
Does an Invoice ID attribute really belong on a User object? It's a pragmatic solution but not one for the purists!
John Topley
If there is a requirement that invoice numbers are sequeential for each user, then "latestInvoiceNumberForThisUser" seems to be tightly associated with the user.
djna
Yes, well argued.
John Topley
+1  A: 

You could introduce another table associated with your "users" table that tracks the most recent invoice number for a user. However, reading this value will result in a database query, so you might as well just get a count of the user's invoices and add one, as you suggested. Either way, it's a database hit.

John Topley
+2  A: 

If the invoice numbers are independent for each user/customer then it seems like having "lastInvoice" field in some persistent store (eg. DB record) associated with the user is pretty unavoidable. However this could lead to some contention for the "latest" number.

Does it really matter if we send a user invoices 1, 2, 3 and 5, and never send them invoice 4? If you can relax the requirement a bit.

If the requirement is actually "every invoice number must be unique" then we can look at all the normal id generating tricks, and these can be quite efficient.

Ensuring that the numbers are sequenctial adds to the complexity, does it add to the business benefit?

djna
yes, because it's an accounting applications, numbers need to be auto incrementing integers. So, it needs to be pretty solid too.
Asaxena
+1  A: 

Typical solution for any relation database could be a table like

user_invoice_numbers (user_id int primary key clustered, last_id int)

and a stored procedure or a SQL query like

update user_invoice_numbers set last_id = last_id + 1 where user_id = @user_id
select last_id from user_invoice_numbers where user_id = @user_id

It will work for users (if each user has a few simultaneously running transactions) but will not work for companies (for example when you need companies_invoice_numbers) because transactions from different users inside the same company may block each other and there will be a performance bottleneck in this table.

The most important functional requirement you should check is whether your system is allowed to have gaps in invoice numbering or not. When you use standard auto_increment, you allow gaps, because in most database I know, when you rollback transaction, the incremented number will not be rolled back. Having this in mind, you can improve performance using one of the following guidelines

1) Exclude the procedure that you use for getting new numbers from the long running transactions. Let's suppose that insert into invoice procedure is a long running transaction with complex server-side logic. In this case you first acquire a new id , and then, in separate transaction insert new invoice. If last transaction will be rolled back, auto-number will not decrease. But user_invoice_numbers will not be locked for long time, so a lot of simultaneous users could insert invoices at the same time

2) Do not use a traditional transactional database to store the data with last id for each user. When you need to maintain simple list of keys and values there are lot of small but fast database engines that can do that work for you. List of Key/Value databases. Probably memcached is the most popular. In the past, I saw the projects where simple key/value storages where implemented using Windows Registry or even a file system. There was a directory where each file name was the key and inside each file was the last id. And this rough solution was still better then using SQL table, because locks were issued and released very quickly and were not involved into transaction scope.

Well, if my proposal for the optimization seems to be overcomplicated for your project, forget about this now, until you will actually run into performance issues. In most projects simple method with an additional table will work pretty fast.

Bogdan_Ch
I'm not allowing editing/deleting invoices, so that simplifies the whole thing a little(no gaps). But I'm not worried about performance now.Thanks
Asaxena
memcached is not a database engine: http://code.google.com/p/memcached/wiki/FAQ#How_can_I_use_memcached_as_a_database?
John Topley
If you use any technology that may result in gaps (as it appears from this answer auto-increment will) then you can reduce contention by allowing pre-getting of chunks from the sequence. Worst case you lose the unused chunk.
djna
A: 

Do you really want to generate the invoice IDs in an incremental format? Would this not open security holes (where in, if a user can guess the invoice number generation, they can change it in the request and may lead to information disclosure). I would ideally generate the numbers randomly (and keep track of used numbers). This prevents collisions as well (Chances of collision are reduced as the numbers are allocated randomly over a range).

Chetan