tags:

views:

305

answers:

7

Hello

I am the lead developer on a commercial Windows app (c#). A new requirement is to track customers who abuse the license.

For example: Let's say a customer purchases a 10 user license agreement, i.e. 10 simultaneous users at any given time.

I need to be able to report, looking back at history, all the times that customer had more than 10 users logged in at the same time.

I already have a User table with columns: userid (primary key), pw, lastLogin, lastLogout.

I was thinking about creating a a new 'logging' table in which a new row is added each time a user logs out...columns might include:

LogId, UserId, LoginDateTime, LogoutDateTime

...and then I would have a history of every time a user logs in/out of the app...

but I'm not sure if this table design will lend to efficient calculations for reporting...whether I use SQL or c# to perform the calculations does not matter to me, as long as it is reasonably fast...

Hoping someone might have a good idea about how to better design this table so that I can quickly calculate any/all points in time when the customer exceeded the license limit.

Note: I do not want to block the 11t, 12th etc. user from using the app...the requirement is to display a warning message to the user but to allow him to continue working...

A: 

The problem with having a table structure of (LogId, UserId, LoginDateTime, LogoutDateTime) is that it becomes difficult to aggregate - queries to see everyone using the system at a specified time are easy, but getting the number of users at all times is hard.

One possible approach would be to denormalise the data a bit - and record units of use, instead of spans. You could track usage in 30 minute blocks with the table structure (UserId, BlockDateTime).

If BettyR used the system from 10:05am until 11:45am, create four records in your table:

BettyR, 10:00am
BettyR, 10:30am
BettyR, 11:00am
BettyR, 11:30am

You can then use a normal SQL query with a group by clause to find the number of distinct users who made use of the system during each half hour period.

select ...
from UsageBlocks
group by BlockDateTime
having count(*) > 10

Depending on your requirements, you could then perform deeper analysis on those specific time periods using a reporting application.

Bevan
You can get the same results by logging a start and end time and simply using a table (permanent or temporary) of time blocks. That then let's you change the block of time as well for any report that you write instead of locking you into one.
Tom H.
@TomH - so, you're suggesting two tables (one with sessions, one with time blocks/periods) and doing a cross join between the two for analysis?
Bevan
+1  A: 

A different option is to create a user sessions table. When a user logs in, insert their session into the table. When they log out, delete it. That way you always have an easy way to find out how many users are connected.

Then you can create a trigger on insert (and maybe delete) and you can log from there based on the session count. If you log delete as well you have an idea of the length of time they used an excessive amount of users.

Also keep in mind that you'll have to clean up orphaned connections appropriately.

lc
+5  A: 

Think about starting and ending sessions as events that need to be recorded. You create a single table to record these events.

So, a session that starts and ends will have two entries in the table - one for the session start and one for the session end. You only ever add records to the table, never modifying previous records. The table can keep track of the number of open sessions very simply now by adding a "session count" field to the record that is incremented from the previous record's session count value when a session start event occurs and decremented when a session end event occurs.

The "session count" column now gives us a piece-wise continuous funtion over time of the number of concurrent sessions.

Example data:

    SessionId  EventType  .... your session data here ... SessionCount   
1.     1         Login         ................                 1
2.     2         Login         ................                 2
3.     3         Login         ................                 3
4.     1         Logout        ................                 2
5.     4         Login         ................                 3
6.     4         Logout        ................                 2
7.     2         Logout        ................                 1
8.     3         Logout        ................                 0
9.     5         Login         ................                 1
10.    6         Login         ................                 2

Things to worry about:

  1. You must make sure you pair begin/end events, so in the event of any failure, session end events must still be generated.
  2. Do you need the table to be tamper proof? If so, I have a technique for that also.

EDIT: please note that where I put "your session data here", I really meant "your session event data here". Information such as a time stamp would go in here. Another table should be used to track session information common to both events, such as the identity of the user that owns the session (use the same SessionId key for both tables).

Daniel Paull
This works to keep a complete log of everything. As a result, you can go back and point out exactly WHO those 10 people were that were on at a given time. Makes your argument very convincing to the client!
lc
Thinking of data as purely additive and recording events is very powerful. Some how accountants have known this for years...
Daniel Paull
The session count column is redundant and may end up causing problems. It can be calculated simply enough. Other than that though, this is a great solution.
Tom H.
This inclusion of the session count column is the whole point - reading the last record in the table gives you the current number of sessions very quickly. While it is calculated from other data in the table, the data is immutable, so persisting this is fine. What problems do you foresee?
Daniel Paull
I can't see how it could be trivial to count the number of sessions WITHOUT the session count column. You're looking at an O(n) operation to iterate through all events, and you would end up with the same thing as the column to begin with.
lc
I was focused on the requirements of "efficient calculations for reporting ... so that I can quickly calculate any/all points in time when the customer exceeded the license limit." I can not think of another strategy that meets the requirements! @Tom - I'm still interested in what problems you see
Daniel Paull
The problems that I see are the same that I see with any column that violates standard database design rules by storing redundant data - the session count WILL get out of sync at some point. It WILL be wrong eventually.
Tom H.
@Tom: I understand database schema design principals. The only reason this is acceptable is because the records are immutable and ordered. Since you emphasized WILL, I will emphasize HOW? HOW will it get "out of sync"? HOW will it be wrong eventually?
Daniel Paull
@Daniel, you are violating the principle not to persist derivable data. I guarantee that to do so will be catastrophic. If you want an academic justification, use Murphy's Law, which applies universally across all domains.
le dorfier
@le - I understand the violation. OLAP Cubes are another violation that everyone seems to accept. The derived information is calculated from immutable data, so I do not see why this is not an acceptable solution to provide fast reporting.
Daniel Paull
A: 

guys

I am very impressed with the 4 potential solutions so far...very creative and none of them I had thought of...I cant vote them up though since I am a new user....is there some way I can give you 3 guys a thumbs up ?

Daniel...tampering could be an issue as this database lives on the each customer's box...the db is in SQL Server 2005...what do you suggest?

Thumbs up noted! Regarding tampering - when records are immutable, as they are in this scenario, it is possible to store an encrypted value for a record that is calculated from not just the records values, but also the previous records values. (continued in next comment...)
Daniel Paull
If the table is ever tampered with, the encrypted values can not be verified! You must keep the encryption algorithm a secret though, otherwise someone can generated the right encrypted values to fix the table. If this not clear, I can write up a blog post the illustrates the technique.
Daniel Paull
Thanks for the kind words. Even if you can't vote up an answer, you can (and should) select the best available solution and accept it as the answer. This shows other people browsing the question what you thought was most useful - and doesn't limit other people voting for their choices.
Bevan
If you are encrypting using C# you should obfuscate the assemblies so that no one can find out the encryption algorithm by using reflector to view the code. If they can then they can also change the data in the table by entering a number smaller than the limit and encrypting that number by you algo.
Unmesh Kondolikar
A: 

I'd recommend a phone home feature, something like alerting you or logging via a web method so you can track who is doing what...but only when they exceed their limit. (research WCF for a couple ways to do this)

That way you could keep track of their usage. Charging them extra or renegotiating their contract becomes meaningful with real data. If you are good at customer service then this shouldn't ruffle their feathers much, especially if you point out that you need an infrastructure for providing service on an elavated user base. Presumably you make it quite clear to them about your licensing scheme too...

I can't recommend phoning home unless it already does so for licensing. Phoning home looks suspicious and can be stopped with a firewall. Moreover, if you demand that it phones home before allowing them to use the product, it means the customer must always have a live connection to the internet.
lc
A: 

I would be wary to implement something like license checking this way, because (as noted in Daniel's answer already, you don't get logout event in case your app crashes, or possibly even if user quits it in a specific way (End Process from Task Manager, ALT+F4, instead of going through File | Exit, etc).

So you could conceivably have a growing portion of dead sessions as users use the application. Not only you could get wrong data and impression about your users, but more importantly, they would grow frustrated by receiving warning messages they don't deserve. And if you try to tell them they are violating the license when they are actually not, they could become quite affronted. A good way to lose customers.

I would say a reasonable license checking scheme requires central server outside customer control, and for customer to have Internet access. It could go something like this:

  1. on starting application, program informs license server of a new session;
  2. during application work, program periodically (let's say 1x 5mins or so) sends a status signal to license server;
  3. on exiting program normally, it sends session end signal to license server;
  4. if no status signal is received in 10mins from program, session is considered terminated by license server anyway.
Gnudiff
You need two checks - a timeout as you note. But if the server crashes, it needs to close of open sessions upon next start up.
Daniel Paull
That's right. I kind of assumed that would be default, but it is better to explicitly say that.
Gnudiff
A: 

Note: This is a problem that's been dealt with before. The best option I've seen is to maintain an MRU stack of user activity by unlogged-out users (in this case depth 10), and derived from database transaction activity - SELECT TOP 10 DISTINCT usercode FROM atable WHERE (most recent transaction type wasn't "log off") ORDER BY timestamp DESC. As soon as someone accesses the system that isn't among the most recent 10, you have two options. You allow them to log in, and bump the oldest to logged out status (which creates hassle, but lets them continue their work); or you make them wait until one of the top 10 explicitly logs out. Somtimes the user gets to make the choice. Sometimes it requires an administrator. But that's a policy distinction.

Then you bill them for every time they log in (using the first scheme) or you just enforce the limit (with the second scheme).

le dorfier