Now we are going to do some serious multi user testing. Having introduced transactions and complex locking to Database abstraction with google apps script in Working with transactions, testing all that in a multi user environment was always going to be tough. Using the lessons learned in Simulating multiuser testing, here’s how.
The tests
I’m adding new backends to the test as I complete their implementation of transactions., locking and rollbacks. The test will run serveral of instances of the same back end along side others. The objective is to make sure there is no leakage of locking across or between backends and that cache voiding and so on were working in this complex environment.
Here are the profiles.
Most of this is recycled from previous posts, so I won’t repeat the details of the profile functions. Here they are all put together though.
function dbMassiveProfiles() { var CHUNKS = 4; var profile = []; // clear the db var profileClear = getTheMassiveClearProfile(); // get the test data var profileTest = [dbAsyncTestData ()]; // reduce the test data var profileReduction = [dbAsyncReduce()]; // get and process all the messages var profileSheets = getTheMassiveExecuteProfile(CHUNKS); //Log the results var profileLog= [dbAsyncLog ('massive')]; profile.push( profileClear, profileTest, profileReduction, profileSheets, profileReduction, profileLog ); return profile; } function getTheMassiveExecuteProfile (chunks) { return [ dbSheetParameters, dbDriveParameters, dbMemoryParameters, dbParseParameters, dbMongoLabParameters ].reduce (function (p,c) { for (var i =0; i <chunks;i++ ) { p.push(dbAsyncExecute (c().parameters, c().driver,i,chunks)); } return p; },[]); } function getTheMassiveClearProfile (chunks) { return [ dbSheetParameters, dbDriveParameters, dbMemoryParameters, dbParseParameters, dbMongoLabParameters ].map (function (f) { return dbAsyncClear (f().parameters, f().driver); }); }
Page Content
hide
And the results
The tests
These are the standard tests I use against all databases, except now I’ve updated them to include transactions
/** * execute test cases against database * @param {DbAbstraction} handler a handler * @param {string} accessToken if ouath2 is needed * @param {number} cycles sclae factor. not required if testData is already present * @param {Array.object} testData the test data * @param {string} strain filters all queries if present * @return {object} the results of the test */ function testCases (handler, accessToken, cycles,testData,strain,small) { // for constraints var dhc = cDbAbstraction.dhConstants; var c = dhc.CONSTRAINTS; // duplicate it up to test scalability cycles = cycles || 1; var what = 'datbase test' var whatall ='mixed-'+cycles // get some test data if not passed here already testData = testData ? cUseful.clone(testData) : getSomeTestData (cycles); var sample = testData.length; // use this for paralell running stream identification strain = strain || handler.getDBName(); testData.forEach(function(d) { d.strain = strain; }); var thenall = new Date().getTime(); // clean set of data var r2 = handler.transaction ( function (db) { // delete everything var result = handler.remove({strain:strain}); assert (result.handleCode >= 0 ,result,'remove'); //query everything var result = handler.query({strain:strain}); assert ([result.handleCode >= 0,result.data.length === 0],result, 'querypostdelete'); //save the test data var result = handler.save (testData); assert (result.handleCode >= 0 ,result,'save'); return result; }); assert ([r2.transaction.code >=0], r2, 'transaction clean set'); var then = new Date().getTime(); //query everything var result = handler.query({ strain:strain }); assert ([result.handleCode >= 0 ,result.data.length === testData.length], result, 'query1'); // make sure we're getting the right id with complex constaints var result = handler.query ( { strain:strain, 'stuff.age': handler.constraints([[c.GT,25],[c.LTE,60]]) }); assert ([result.handleCode >= 0, testData.filter( function(d) { return (d.stuff.age <= 60 && d.stuff.age > 25) ; }).length === result.data.length],result, "first test easy"); // query everything with limit var result = handler.query( {strain:strain}, {limit:1} ); assert ([result.handleCode >= 0, result.data.length === 1],result,'querylimit'); // sort reverse var result = handler.query( {strain:strain}, {sort:'-name'} ); assert ([result.handleCode >= 0, result.data.length === testData.length], result, 'querysort- data length was ' + result.data.length); // sort reverse, skip 1 var result = handler.query( {strain:strain}, {skip:9,sort:'-name'} ); assert ([result.handleCode >= 0,result.data.length === testData.length -9],result,'querysort+skip'); // flattten query everything var result = handler.query({ "name":'ethel', strain:strain }); assert ([result.handleCode >= 0,testData.filter(function(d) { return d.name ==='ethel'; }).length === result.data.length ],result,'filterdot0'); // flatten query everything var result = handler.query({ strain:strain, stuff:{sex:'female'} }); assert ([result.handleCode >= 0,testData.filter(function(d) { return d.stuff.sex ==='female'; }).length === result.data.length], result,'filter'); // check a single constraint works var result = handler.query({ strain:strain, "stuff.age":handler.constraints([[c.GT,25]]) }); assert ([result.handleCode >= 0,testData.filter(function(d) { return d.stuff.age > 25; }).length === result.data.length],result,'filterdotc1'); // two constraints on same property var result = handler.query ( { strain:strain, 'stuff.age': handler.constraints([[c.LT,60],[c.GT,25]]) }); assert ([result.handleCode >= 0,testData.filter(function(d) { return d.stuff.age > 25 && d.stuff.age < 60; }).length === result.data.length],result,'filterdotc2'); // check two constraints plus a different property var result = handler.query ( { strain:strain, 'stuff.age': handler.constraints([[c.LT,60],[c.GTE,25]]), 'stuff.sex': 'male' },undefined,1,true); assert ([result.handleCode>= 0,testData.filter( function(d) { return d.stuff.age >= 25 && d.stuff.age < 60 && d.stuff.sex === 'male'; }).length === result.data.length],result,'filterdotc3'); var result = handler.query ( { strain:strain, name:handler.constraints([[c.IN,['ethel','fred']]]) }); assert ([ result.handleCode >= 0, testData.filter(function(d) { return ['ethel','fred'].indexOf(d.name) != -1; }).length === result.data.length],result,'filterdotc4'); // check text constraints var result = handler.query ( { strain:strain, 'stuff.age': handler.constraints([[c.GT,25]]), 'stuff.sex': 'male', 'name':handler.constraints([[c.IN,['john','mary']]]) }); assert ([result.handleCode >= 0, testData.filter(function(d) { return d.stuff.age > 25 && d.stuff.sex === 'male' && ['john','mary'].indexOf(d.name) != -1; }).length === result.data.length], result,'filterdotc5'); // this transaction is testing gets var t = handler.transaction( function (db) { var result = handler.query ( { strain:strain, 'stuff.age': handler.constraints([[c.GT,25]]) },{limit:1}, 1, true ); assert ([result.handleCode >= 0,result.data.length ===1, result.handleKeys.length ===1], result, "limitkeycheck1"); // testing Get var r2 = handler.get(result.handleKeys); assert ([r2.handleCode >= 0,r2.data.length === result.data.length,r2.data.every (function(d) { return d.stuff.age > 25 ; })], r2, "getcheck1"); // retest constraint var result = handler.query ( { strain:strain, 'stuff.age': handler.constraints([[c.GT,60]]) }); assert ([result.handleCode >= 0,testData.filter(function(d) { return ( d.stuff.age > 60) ; }).length === result.data.length], result, "repeat test easy"); return result; }); assert ([t.transaction.code >=0], t,' transaction 99'); var t = handler.transaction ( function (db) { var result = db.query ( { strain:strain, 'stuff.sex': 'male' },undefined,1,true); assert ([ result.handleCode >= 0, testData.filter(function(d) { return (d.stuff.sex === 'male') ; }).length === result.handleKeys.length ], result, "does male work"); var k = db.remove ( { strain:strain, 'stuff.sex': 'male' },undefined,1,true); assert ([k.handleCode >= 0], k, "delete the males"); var k = db.save (result.data); assert ([k.handleCode >= 0], k, "insert the males"); var result = db.query ( { strain:strain, 'stuff.sex': 'male' },undefined,1,true); assert ([ result.handleCode >= 0, testData.filter(function(d) { return (d.stuff.sex === 'male') ; }).length === result.handleKeys.length ], result, "redo male save"); var result = db.query ( { strain:strain, 'stuff.sex': 'female' },undefined,1,true); assert ([ result.handleCode >= 0, testData.filter(function(d) { return (d.stuff.sex === 'female') ; }).length === result.handleKeys.length ], result, "does female work"); var r2 = db.update( result.handleKeys, result.data.map (function(d) { d.stuff.female = d.stuff.sex === 'female'; d.timeStamp = new Date().getTime(); return d; })); assert ([r2.handleCode >= 0], r2, "update the females"); var result = db.query ({ strain:strain, 'stuff.female': true }); assert ([ result.handleCode >= 0, testData.filter(function(d) { return (d.stuff.sex === 'female') ; }).length === result.data.length ], result, "female test"); return result; }); assert ([t.transaction.code >=0], t,' transaction 100'); // check previous query still works var result = handler.query ( { strain:strain, 'stuff.age': handler.constraints([[c.LTE,60],[c.GT,25]]) }); assert ([ result.handleCode >= 0, testData.filter(function(d) { return ( d.stuff.age <= 60 && d.stuff.age > 25) ; } ).length === result.data.length ],result, "repeat test after update"); //query post update var result = handler.query({ strain:strain }); assert (result.handleCode >= 0,result, 'querypostupdate'); // lets try counting var result = handler.count ({ strain:strain }); assert ([ result.handleCode >= 0, testData.length === result.data[0].count ],result,'count 1'); // check count var result = handler.count ( { strain:strain, 'stuff.age': handler.constraints([[c.GT,25]]), 'stuff.sex': 'male', 'name':handler.constraints([[c.IN,['fred','john']]]) }); assert ([ result.handleCode >= 0, testData.filter(function(d) { return d.stuff.age > 25 && d.stuff.sex === 'male' && ['fred','john'].indexOf(d.name) != -1; }).length === result.data[0].count ],result,'count 2'); handler.transaction ( function (db) { // get readu for OR var result = handler.query ( { strain:strain, 'stuff.age': handler.constraints([[c.GTE,60]]), 'stuff.sex': 'male' }); assert ([ result.handleCode >= 0, testData.filter(function(d) { return ( d.stuff.age >= 60 && d.stuff.sex === 'male') ; }).length === result.data.length ],result,'normal 0'); // make sure we're getting the right id with complex constaints var result = handler.query ( { strain:strain, 'stuff.age': handler.constraints([[c.GT,25],[c.LTE,60]]) }); assert ([ result.handleCode >= 0, testData.filter(function(d) { return (d.stuff.age <= 60 && d.stuff.age > 25) ; }).length === result.data.length ],result, "repeat test constraint"); // try OR var result = handler.query ( [ {'stuff.age': handler.constraints([[c.LT,26]]), 'stuff.sex':'male',strain:strain }, {'stuff.age': handler.constraints([[c.GTE,60]]), 'stuff.sex':'male',strain:strain } ]); assert ([ result.handleCode >= 0, testData.filter(function(d) { return (d.stuff.age < 26 && d.stuff.sex === 'male') || (d.stuff.age >= 60 && d.stuff.sex === 'male')}).length === result.data.length ],result,'OR 1'); return result; }); // show all the males var result = handler.query ( { strain:strain, 'stuff.sex':'male' },undefined,1,true); assert ([ result.handleCode >= 0 , result.data.every (function(d) { return d.stuff.sex === 'male'; }) ],result , "show the males"); var t = handler.transaction (function(db) { // remove all the males var result = handler.remove ( { 'stuff.sex':'male', strain:strain }); assert (result.handleCode >= 0 , result , "remove the males"); // check they've all gone var result = handler.query ({ strain:strain }); assert ([ result.handleCode >=0, result.data.every (function(d) {return !d.stuff.man; }), result.data.length === testData.filter (function(d) { return d.stuff.sex !== 'male'}).length ], result,'check after delete males'); // add them back in var result = handler.save( testData.filter(function (d) { return d.stuff.sex === 'male' })); assert (result.handleCode >= 0 , result , "add them in again"); // check what we haveassertion var result = handler.count({ stuff:{sex:'female'}, strain:strain }); assert ([ result.handleCode >= 0, result.data[0].count === testData.filter (function(d) { return d.stuff.sex === 'female'}).length ], result,'check count female'); // check what we have var result = handler.count({stuff:{sex:'male'},strain:strain}); assert ([ result.handleCode >= 0, result.data[0].count === testData.filter (function(d) { return d.stuff.sex === 'male'}).length ], result,'check count male'); // sort and save var result = handler.query( {strain:strain}, {sort:'-serial'} ); assert ([ result.handleCode >= 0, result.data.length === testData.length ],result,'check sort count'); // mark as good and save var r2 = handler.save (result.data.map(function(d){ d.good=true; return d; })); assert (r2.handleCode >= 0 , r2 , "wrote back the data"); // check we have twice the records var result = handler.query({ strain:strain }); assert ([ result.handleCode >= 0, result.data.length === testData.length *2 ],result,'check sort count'); // delete the ones we added var r2 = handler.remove({ good: true, strain:strain }); assert (r2.handleCode >= 0 , r2 , "removed the data"); return result; }); assert ([t.transaction.code >=0],t,'transaction big'); // check the original length var result = handler.query({ strain:strain }); assert ([ result.handleCode >= 0, result.data.length === testData.length ], result,'check final count'); var now = new Date().getTime(); return {end:now,start:thenall,what:what,where:handler.getDBName(),code:result.handleCode,elapsed:now-thenall,volume:testData.length}; }
For more snippets like this see Google Apps Scripts snippets