Transactions

SOP Transactions is designed to be scalable since SOP is a DB engine. It can accomodate and is recommended to wrap within a transaction thousands or hundreds of thousands of management actions. SOP transaction will be able to handle this amount of management actions and able to scale with no performance or resource penalties. SOP Stores will take care of its job of swapping in and out pageful of records to and from disk during heavy usage if necessary.

Scope

Transaction scope is per ObjectServer. An ObjectServer can be opened in its own transaction session that is separate than another ObjectServer instance. Multiple ObjectServers can be opened at any given time, each instance of which, having its own transaction session.

After ObjectServer transaction commit or rollback, another transaction can be started right away. In between server transaction sessions, opened File(s) and their Store(s) don't need to be closed nor disposed. In fact it is recommended to leave their management up to SOP so their buffers (MRU cache) will be kept open and reused across different transaction sessions thus, optimizing performance. Explicitly closing Store objects are only recommended when Store isn't needed anymore for the duration of the module or application.
Since StoreFactory also uses MRU algorithm in managing Store instances, you won't need to worry about accidentally keeping huge amount of instances in-memory generating a memory or resource leak. Also, System File Streams are virtualized thus, code will not need to worry about getting poor performance or experiencing out of System File Stream exceptions due to too many opened System File Streams.

NOTE: keep Store instance open in-between transactions to optimize buffer/MRU cache reuse. This behavior is a bit of a change than typical client side transactions (e.g. - ado.net), where code is encouraged to close/dispose container (connection, command, reader,...) objects when not needed. SOP framework version 4.5 is an embedded DB engine, thus doesn't support this familiar client-side transaction pattern. Future versions of SOP will support two phase commit client-side transaction sessions and employ this familiar transaction pattern.

Sample snippet to illustrate transaction management across multiple ObjectServers:
// create/open object server 1 transaction.
var server1 = ObjectServer.OpenWithTransaction(@"c:\sopbin\server1\o1.dta");
// create/open object server 2 transaction.
var server2 = ObjectServer.OpenWithTransaction(@"c:\sopbin\server2\o2.dta");


//... code to manage File Stores of server1 & server2 here... 'omitted for brevity.


// commit on server1 will finalize or commit changes done on any Store of any File object belonging to server1. Changes on server2 will not be committed.
server1.Commit();

// commit on server2 will finalize or commit changes done on any Store of any File object belonging to server2.
server2.Commit();

Sample Code

Here is a sample program originally authored by one of our community members and modified to optimize Server and ObjectStore caching. Take note of how ObjectServer and Store instances are never disposed by the code, they are intentionally left opened across different transaction sessions so buffers won't need to be re-created across different instances.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Sop;

namespace SopTest
{
    class Program
    {
        const string filename = @"E:\SopBin\store.dta";

        static void Main(string[] args)
        {
            for (int i = 0; i < 100; i++)
            {
                Populate(10000);
                if (!ReadAll())
                    break;
            }
        }

        static IObjectServer server
        {
            get
            {
                if (_server == null)
                    _server = ObjectServer.OpenWithTransaction(filename);
                return _server;
            }
        }
        static IObjectServer _server;

        static ISortedDictionary<string, int> store
        {
            get
            {
                if (_store == null)
                {
                    IStoreFactory sf = new StoreFactory();
                    _store = sf.Get<string, int>(server.SystemFile.Store, "MyStore");
                }
                return _store;
            }
        }
        static ISortedDictionary<string, int> _store;

        static void Populate(int count)
        {
            Console.Write("{0} | Write {1} items ... ", DateTime.Now, count);
            Int32 maxID = store.Count;
            for (int i = 0; i < count; i++)
            {
                Int32 id = (Int32)store.GetNextSequence();
                string name = string.Format("1111-2222-3333-4444-{0}", maxID + i);
                store.Add(name, id);
            }
            server.Commit();
            server.BeginTransaction();
            Console.WriteLine("done.");
        }

        static bool ReadAll()
        {
            Console.Write("{0} | Read items ... ", DateTime.Now);
            if (store.MoveFirst())
            {
                store.HintBatchCount = 103;
                int Ctr = 0;
                do
                {
                    Ctr++;
                    Int32 p = store.CurrentValue;
                } while (store.MoveNext());
                if (Ctr != store.Count)
                {
                    Console.WriteLine("{0} found, expected {1} [FAILED]", Ctr, store.Count);
                    return false;
                }
                else
                    Console.WriteLine("{0} found [OK]", Ctr);
            }
            return true;
        }
    }
}

Last edited Jan 12, 2013 at 8:35 AM by grecinto, version 8

Comments

No comments yet.