views:

181

answers:

6

We have the sitation that several servers are inserting chunks of rows into a table in a relational database, and one server reads the new data once in a while from the table. (The table is conceptually some kind of logfile - data is only inserted but never modified, and the reading server shows a tail of the log.) Is there a way to have the reading server only read the new data? We are free to structure the table(s) as we want to.

Some ideas that crossed my mind but do not work are:

  • Marking the rows as read does not fit our application: the reading server should not change the database. (Writing to the database for displaying things is not a good thing to do, and there might be several sessions displaying the stuff.)

  • We could insert a timestamp in each row that is filled with the database system time. The problem is that this is not the timestamp of the commit time, but of the insert time. If you ask the database "give me all values between now-5 minutes and now" you cannot rely on all values being present, since there might be transactions in progress. You'll have to ask again later for the values in this interval, which is what I wanted to avoid.

  • We could insert a running row count filled from a sequence. The same problem with running transactions occurs as when using timestamps.

Is there any solution to the problem, or do I have to apply some heuristics like assuming a maximum transaction time and always asking for values written after "now - maximum transaction time" and reading some data twice?

In case it matters: we use Oracle for this. But I assume answers that work only with other databases, are of general interest as well.

A: 

Create another table LOG_REVISION. It contains a single row (an INTEGER).

The logging process should read this table and add the number it finds there to each log record. Lock the row until you commit the transaction.

The reading process should first update the LOG_REVISION by incrementing the number and then read all rows which have the old LOG_REVISION.

[EDIT] There are two more ways around this:

  • have another table where you record the rows which you have processed.
  • The writer puts the data into an intermediate table and the reader copies the rows to the final place and deletes the processed rows.
Aaron Digulla
A nice idea, but this blocks concurrent writing of the several servers to the database. It depends on the application whether this is acceptable, and I would not do it here.
hstoerr
I've added two more solutions.
Aaron Digulla
Thank you, but for both of them I would need to write to the database, which I don't want to since I'm just displaying the data with incremental updates. Modifying the database is not appropriate for such a task, and might not work at all if you are displaying the data for several users at the same time, for instance in a clustered web application.
hstoerr
A: 

I would say your idea for timestamps is valid, but rather than ask for a range, just ask for all values after a certain time. You should get all available values in the database in the most recent time period you selected. Obviously, it won't work for any transactions still in progress that haven't been logged... but you only have to do a single, simple query.

Edit:
You must ensure the timestamp values are unique per row. In which case you only need to keep track of the latest timestamp value you read from the database. The next query to the database is of all values after that. You won't miss any data, nor will you read duplicates. Any ongoing transactions will not have been stored in the database during a query for you to miss, and you are guaranteed to get it the next time you query the database.

Ioan
That was my original plan. But in the next query: if I just ask for the values of the next timeperiod I'll never get the values whose transaction was still in progress. I'll need to ask for at least a part of the last timeperiod as well, and will read duplicates I'll have to filter out.
hstoerr
@hstoerr This depends on the database you are using. Some databases (such as Oracle) can maintain this multitemporal data, namely transaction time vs valid time.
charstar
@loan: the point where your idea fails is the same as in my comment to Arthur Thomas' answer - except if you actually can insert the transaction time into the column, as charstar seems to imply.
hstoerr
@hstoerr: Perhaps you could make the differences in meaning and execution of "commit time" vs "transaction time" a little more clear. Mainly, which order is important and when it's counted. I'm particularly confused by your example of inserting entries out of order, yet expecting to see them in order (I don't understand what "time" you're using).
Ioan
Another example: You want to order by "time transaction started", not finished. In this case, you could have entries that get *inserted* out of order as you describe. This, to me, is a slightly different problem than what you described in your question.
Ioan
In my comment above you can read "transaction time" as "commit time". Sorry, here I was just using charstars terminology, but in the question I was using it as "transaction duration".The ordering is not too important. My primary concern is: each time I hit the database I want to read just the rows that have been committed since the last time I was here.
hstoerr
A: 

This is a possible solution , depending on your situation etc.

Have a column called "read_timestamp" which is null , once a row is read, the reading process will update it to a non-null timestamp.

The reader queries this table with "where read_timestamp is null".

An easier solution would be to go with an estimate (i.e. this row may have been viewed already kind of caveat). Hence you would at any time display the "last 50 rows" or the 'rows that arrived in the last 10 minutes" (with the inaccuracy that another log-viewer may have already pulled those).

A third solution would be to feed these rows using a back-end process to a queue : Each read of a row makes the row disappear from the queue (because its a 'pop' operation). Hence a row can only be viewed once (first-come-first-served).

blispr
The OP specified that the reading application cannot modify the database.
charstar
A: 

create an id sequence for the log table so that each log has a unique id. then when the reader reads the logs it will record somewhere the highest id read. Next time it runs it will get all ids after the last recorded id. Any ongoing transaction won't be a problem since you don't get them in the resultset. They will be gathered in the next run.

so if you have:

id | log
1  | blah
2  | blah again
3  | more blah
* transaction to insert row '4' in progress

then you will have fetched all those logs and recorded 3 as last id found. And on the next run:

select id, log from logs where id > last_recorded id order by id #id will be 3

4  | yet again some blah
5  | does this blah never end
6  | omg blah

and record 6 as your new last recorded id. I still think it is good to keep the dates of when the log was made as well.

EDIT ok to catch every thing like that you will have to keep a set of all records read in an alternate location and then get the difference of the read set against the active log table. If you can't touch the log table then you are just dealing with sets and finding what is not in one of the sets.

Arthur Thomas
-1 Same problem as the timestamp: If a transaction hasn't been committed, the sequence will already have increased but the reader will miss the row. If another transaction gets committed, the first row won't be processed.
Aaron Digulla
To go with your example: the point where your answer does not work is when transaction for 3 is in progress, but 4 is read. The problem is that the IDs are created on insertion, not on commit. If a database would allow this, the problem would be solved.
hstoerr
@Aaron: May be MS SQL specific but, when both reader and writer use ReadCommitted transaction isolation level, the reader executing anything like 'SELECT * WHERE ID > 2' while insertion of the row with id '4' is still in progress it will won't get any results until committed (and generally it will wait until all insert transactions are committed).
Regent
+2  A: 

The database being used wasn't specified so it's not clear as to whether the solution has to be hammered into an existing deployment or not. There are some queue engines that can be plugged into MySQL that could potentially work. One of them is Q4M. Some commercial databases like Oracle have temporal database functionality that allow for determining transaction time vs valid time vs real time.

When using Oracle, either the pseudo-column ora_rowscn or the useful combination scn_to_timestamp(ora_rowscn) can effectively provide the timestamp for when a row was committed (the SCN in which it took place). Alternatively, Oracle Workspace Manager provides version-enable tables, basically it goes like this: You enable versioning on a table with DBMS_WM.EnableVersioning(...), rows are inserted with an aditional WMSYS.WM_PERIOD(...) field specifying a valid time range, set a valid range for the workspace is set on the reader DBMS_WM.SetValidTime(...).

You could also fake this functionality to a certain degree by meshing your timestamp idea with the commit time heuristic. The idea is simply to store the "valid time" as a column along with the data instead of using an arbitrary delta from now(). In other words a secondary timestamp column that would specify some future date (the "valid time") based on a heuristic of commit time + some acceptable window of delay (perhaps the mean commit time + twice the standard deviation). Alternatively, using some ceil()ing of mean commit time ("at least the commit time but rounding up to, say, 30 second intervals"). The latter would effectively quantize (coalesce?) the time log records would be read. It doesn't seem too different but this way would save you from reading redundant rows. It also solves the problem that the reading application cannot accurately know the commit times of the writing application without writing a lot more code.

charstar
Very interesting ideas. :-) Since we are using oracle anyway: how can I get the transaction time into the rows? I failed to find something in the docs.
hstoerr
Look into the pseudo-column called ora_rowscn and the useful combination scn_to_timestamp(ora_rowscn). This is the row timestamp for when a row was committed (the SCN in which it took place). Alternatively, Oracle Workspace Manager provides version-enable tables, basically it goes like this: You enable versioning on a table EXECUTE DBMS_WM.EnableVersioning(...), rows are inserted with an aditional WMSYS.WM_PERIOD(...) field specifying valid time, set a valid range for the workspace is set on the reader DBMS_WM.SetValidTime(...).
charstar
Thanks! Neat. An interesting question here is whether a query like select * from ... where ora_rowscn > {lastscn} needs to do a full table scan - if so you would want to avoid this. But perhaps a combination with an index over timestamp and querying over ora_rowscn *and* the timestamp minus maxtransactiontime is fast enough. As I gather from http://www.dba-oracle.com/oracle_tips_ora_rowscn_10g_pseudo_column.htm you'll need to declare ROWDEPENDENCIES for the table as well.
hstoerr
I think its overkill to use database constructs at a low-level of abstraction such as SCNs to track this. Same with Oracle Workspace Manager for what seems to be a simple problem. Both the features are kind of 'exotic' in the Oracle world - especially for a problem of this nature, which would have any # of straightforward, low-tech-no-tech kind of solutions.
blispr
@babyLisper I tend to agree. IMHO relational databases just shouldn't be used as queues but it happens all of the time. There are plenty of purpose-built message queue, temporal queue, and publisher/subscriber systems out there.
charstar
We are keeping an eventlog in the database. Thus, this is not really a queue; you only have a queue like access pattern when you are displaying the latest events / messages.
hstoerr
+2  A: 

MS SQL have its specific solution:

You can add a column of rowversion data type to table. This column will be automatically updated on related rows by the engine on any update/insert statement.

If writer use ReadCommitted isolation level then reader can use ReadUncommitted isolation level (so it don't need to wait for all transactions to end before returning any results) but with the query like this:

SELECT * FROM [Log]
WHERE Version > @LastKnownVersion
    AND Version < MIN_ACTIVE_ROWVERSION()

Where @LastKnownVersion is the maximum row version processed by the reader and MIN_ACTIVE_ROWVERSION() is a built-in MS SQL function which returns minimum row version number which is still in transaction.

So with this solution even if you have ID=4 committed but ID=3 not yet it will return only rows changed before ID=3 because its version will be exactly MIN_ACTIVE_ROWVERSION().

Advantage of this method is that there is no need for reader to wait for transaction to be committed before getting any results what can be crucial if there is a lot of writers. (Reader could be locked forever.)

Regent