This commit is contained in:
@@ -67,7 +67,7 @@ SELECT indexrelname,cast(idx_tup_read AS numeric) / idx_scan AS avg_tuples,idx_s
|
|||||||
|
|
||||||
WORK IN PROGRESS:
|
WORK IN PROGRESS:
|
||||||
|
|
||||||
Search result list:
|
Search result list NAME FETCHER in :
|
||||||
|
|
||||||
//Before attempt to optimize name fetcher (unknown number of results)
|
//Before attempt to optimize name fetcher (unknown number of results)
|
||||||
//22548, 21187, 20462, 22336, 20094 - AVG = 21325
|
//22548, 21187, 20462, 22336, 20094 - AVG = 21325
|
||||||
@@ -92,3 +92,13 @@ Now I'm going to try it with the index put back in and data regenerated
|
|||||||
Now fresh test but without index being crated
|
Now fresh test but without index being crated
|
||||||
### 14244 results with index in place (after a restart of server, and bypassing EF entirely with a direct query OPTIMIZED TO REUSE CONNECTION): 14270 results - 13176, 12688, 13179, 12994, 12272 AVG: 12,861 = .90 per result
|
### 14244 results with index in place (after a restart of server, and bypassing EF entirely with a direct query OPTIMIZED TO REUSE CONNECTION): 14270 results - 13176, 12688, 13179, 12994, 12272 AVG: 12,861 = .90 per result
|
||||||
|
|
||||||
|
index put back in and data regenerated
|
||||||
|
### 14255 results with index in place (after a restart of server and bypassing EF entirely with a direct query OPTIMIZED TO REUSE CONNECTION): 12461, 12040, 11171, 11141, 11214 AVG: 11605 = .81 per result
|
||||||
|
OK, this tells me that it's faster with the index in place and intuitively that just makes sense.
|
||||||
|
Also verified it's actually using the index scan instead of table scan.
|
||||||
|
I'm going to enact a policy to index id,name in all objects that have many columns, if they only have a name and id and not much else then there seems little benefit
|
||||||
|
|
||||||
|
### results ("final" id,name indexes on user table and widget table, freshly generated data), 14202 RESULTS: 13295, 14502, 11774, 12521, 12101, 13169 AVG: 12,893 = 1.15
|
||||||
|
|
||||||
|
Ok, it just makes logical sense to keep the indexes even if slightly slower, I can revisit this later, the difference is miniscule. I suspect with a bigger database there would definitely be better peformance.
|
||||||
|
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ Current unbounded search for "et*" in huge dataset takes avg 21 seconds to proce
|
|||||||
- see if any other callers to name fetcher are in tight loops and could benefit from using the new Direct version
|
- see if any other callers to name fetcher are in tight loops and could benefit from using the new Direct version
|
||||||
- Update all the other routes to include search indexing (attachments, tags etc, anything with text in it)
|
- Update all the other routes to include search indexing (attachments, tags etc, anything with text in it)
|
||||||
|
|
||||||
|
- Schema: clean up all the LOOKAT items and verify the indexes are being used
|
||||||
|
|
||||||
- EventLogProcessor.AddEntry: CHANGE this to save the context itself and then change all callers to handle that (remove save)
|
- EventLogProcessor.AddEntry: CHANGE this to save the context itself and then change all callers to handle that (remove save)
|
||||||
- I originally didn't have the save in there because I thought subsequent code might all share in the single context save,
|
- I originally didn't have the save in there because I thought subsequent code might all share in the single context save,
|
||||||
|
|||||||
@@ -363,7 +363,7 @@ namespace AyaNova
|
|||||||
// ******************** TESTING WIPE DB *****************************
|
// ******************** TESTING WIPE DB *****************************
|
||||||
//
|
//
|
||||||
//Set this to true to wipe the db and reinstall a trial license and re-seed the data
|
//Set this to true to wipe the db and reinstall a trial license and re-seed the data
|
||||||
var TESTING_REFRESH_DB = true;
|
var TESTING_REFRESH_DB = false;
|
||||||
|
|
||||||
#if (DEBUG)
|
#if (DEBUG)
|
||||||
//TESTING
|
//TESTING
|
||||||
|
|||||||
@@ -13,21 +13,8 @@ using AyaNova.Models;
|
|||||||
namespace AyaNova.Biz
|
namespace AyaNova.Biz
|
||||||
{
|
{
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
using (var command = ct.Database.GetDbConnection().CreateCommand())
|
|
||||||
{
|
|
||||||
command.CommandText = $"SELECT m.name FROM awidget AS m WHERE m.id = {id} LIMIT 1";
|
|
||||||
ct.Database.OpenConnection();
|
|
||||||
using (var dr = command.ExecuteReader())
|
|
||||||
{
|
|
||||||
|
|
||||||
// do something with result
|
|
||||||
return dr.Read() ? dr.GetString(0) : "UNKNOWN";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
//Turn a type and ID into a displayable name
|
//Turn a type and ID into a displayable name
|
||||||
|
//this version uses a direct DataReader for performance in tight loops (search)
|
||||||
internal static class BizObjectNameFetcherDirect
|
internal static class BizObjectNameFetcherDirect
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|||||||
@@ -375,32 +375,7 @@ namespace AyaNova.Biz
|
|||||||
watch.Stop();//###################### PROFILING
|
watch.Stop();//###################### PROFILING
|
||||||
var TimeToBuildSearchResultReturnList = watch.ElapsedMilliseconds;//###################### PROFILING
|
var TimeToBuildSearchResultReturnList = watch.ElapsedMilliseconds;//###################### PROFILING
|
||||||
|
|
||||||
//Before attempt to optimize name fetcher
|
|
||||||
//22548, 21187, 20462, 22336, 20094 - AVG = 21325
|
|
||||||
|
|
||||||
/*
|
|
||||||
explain analyze SELECT m.name
|
|
||||||
FROM awidget AS m
|
|
||||||
WHERE m.id = 12989
|
|
||||||
LIMIT 1
|
|
||||||
|
|
||||||
|
|
||||||
"Limit (cost=0.29..8.30 rows=1 width=27) (actual time=0.079..0.080 rows=1 loops=1)"
|
|
||||||
" -> Index Scan using awidget_pkey on awidget m (cost=0.29..8.30 rows=1 width=27) (actual time=0.077..0.077 rows=1 loops=1)"
|
|
||||||
" Index Cond: (id = 12989)"
|
|
||||||
"Planning time: 0.098 ms"
|
|
||||||
"Execution time: 0.102 ms"
|
|
||||||
|
|
||||||
|
|
||||||
//All index data
|
|
||||||
select * from pg_stat_user_indexes
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
return ResultList;
|
return ResultList;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,12 +23,26 @@ namespace AyaNova.Util
|
|||||||
private const int DESIRED_SCHEMA_LEVEL = 9;
|
private const int DESIRED_SCHEMA_LEVEL = 9;
|
||||||
|
|
||||||
internal const long EXPECTED_COLUMN_COUNT = 99;
|
internal const long EXPECTED_COLUMN_COUNT = 99;
|
||||||
internal const long EXPECTED_INDEX_COUNT = 22;
|
internal const long EXPECTED_INDEX_COUNT = 23;
|
||||||
|
|
||||||
//!!!!WARNING: BE SURE TO UPDATE THE DbUtil::PrepareDatabaseForSeeding WHEN NEW TABLES ADDED!!!!
|
//!!!!WARNING: BE SURE TO UPDATE THE DbUtil::PrepareDatabaseForSeeding WHEN NEW TABLES ADDED!!!!
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
MAXIMUM POSTGRES OBJECT NAME LENGTH: 63 CHARACTERS
|
||||||
|
|
||||||
|
HOW TO INDEX
|
||||||
|
|
||||||
|
AyaNova does a lot of name fetching so any tables that contain a lot of columns in addition to the name will benefit from a compound index on (id,name)
|
||||||
|
|
||||||
|
Other indexes should be created with care and after a huge load and integration test periodically look for unused indexes and see how they are performing
|
||||||
|
see core-performance.txt for the relevant queries to view this info
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
static int startingSchema = -1;
|
static int startingSchema = -1;
|
||||||
public static int currentSchema = -1;
|
public static int currentSchema = -1;
|
||||||
|
|
||||||
@@ -125,37 +139,30 @@ namespace AyaNova.Util
|
|||||||
exec("CREATE TABLE aevent (id BIGSERIAL PRIMARY KEY, created timestamp not null, ownerid bigint not null," +
|
exec("CREATE TABLE aevent (id BIGSERIAL PRIMARY KEY, created timestamp not null, ownerid bigint not null," +
|
||||||
"ayid bigint not null, aytype integer not null, ayevent integer not null, textra varchar(255))");
|
"ayid bigint not null, aytype integer not null, ayevent integer not null, textra varchar(255))");
|
||||||
|
|
||||||
//LOOKAT: do I *really* need these or do they bloat unnecessarily? Need to test with big dataset
|
//SEARCH TABLES
|
||||||
//index for quick searching
|
|
||||||
// exec("CREATE INDEX ayid_idx ON aevent (ayid);");
|
|
||||||
// exec("CREATE INDEX aytype_idx ON aevent (aytype);");
|
|
||||||
|
|
||||||
//Add the search key and dictionary tables
|
|
||||||
//TODO: Indexes determined through load testing and experimentation
|
|
||||||
//Too many indexes or unnecessary ones would be bad because this table is hit on every save / update of an object and would slow those ops
|
|
||||||
//too little is bad if search takes a dogs age to find anything
|
|
||||||
|
|
||||||
exec("CREATE TABLE asearchdictionary (id BIGSERIAL PRIMARY KEY, word varchar(255) not null)");
|
exec("CREATE TABLE asearchdictionary (id BIGSERIAL PRIMARY KEY, word varchar(255) not null)");
|
||||||
exec("CREATE UNIQUE INDEX searchdictword_idx ON asearchdictionary (word);");
|
exec("CREATE UNIQUE INDEX asearchdictionary_word_idx ON asearchdictionary (word);");
|
||||||
exec("CREATE TABLE asearchkey (id BIGSERIAL PRIMARY KEY, wordid bigint not null REFERENCES asearchdictionary (id), objectid bigint not null, objecttype integer not null, inname bool not null)");
|
exec("CREATE TABLE asearchkey (id BIGSERIAL PRIMARY KEY, wordid bigint not null REFERENCES asearchdictionary (id), objectid bigint not null, objecttype integer not null, inname bool not null)");
|
||||||
|
|
||||||
//create locale text tables
|
//create locale text tables
|
||||||
exec("CREATE TABLE alocale (id BIGSERIAL PRIMARY KEY, ownerid bigint not null, name varchar(255) not null, stock bool, cjkindex bool default false)");
|
exec("CREATE TABLE alocale (id BIGSERIAL PRIMARY KEY, ownerid bigint not null, name varchar(255) not null, stock bool, cjkindex bool default false)");
|
||||||
exec("CREATE UNIQUE INDEX localename_idx ON alocale (name)");
|
exec("CREATE UNIQUE INDEX alocale_name_idx ON alocale (name)");
|
||||||
|
|
||||||
exec("CREATE TABLE alocaleitem (id BIGSERIAL PRIMARY KEY, localeid bigint not null REFERENCES alocale (id), key text not null, display text not null)");
|
exec("CREATE TABLE alocaleitem (id BIGSERIAL PRIMARY KEY, localeid bigint not null REFERENCES alocale (id), key text not null, display text not null)");
|
||||||
exec("CREATE INDEX localeitemlid_key_idx ON alocaleitem (localeid,key)");
|
exec("CREATE INDEX alocaleitem_localeid_key_idx ON alocaleitem (localeid,key)");
|
||||||
|
|
||||||
//Load the default LOCALES
|
//Load the default LOCALES
|
||||||
AyaNova.Biz.PrimeData.PrimeLocales(ct);
|
AyaNova.Biz.PrimeData.PrimeLocales(ct);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//Add user table
|
//Add user table
|
||||||
exec("CREATE TABLE auser (id BIGSERIAL PRIMARY KEY, ownerid bigint not null, active bool not null, name varchar(255) not null, " +
|
exec("CREATE TABLE auser (id BIGSERIAL PRIMARY KEY, ownerid bigint not null, active bool not null, name varchar(255) not null, " +
|
||||||
"login text not null, password text not null, salt text not null, roles integer not null, localeid bigint not null REFERENCES alocale (id), " +
|
"login text not null, password text not null, salt text not null, roles integer not null, localeid bigint not null REFERENCES alocale (id), " +
|
||||||
"dlkey text, dlkeyexpire timestamp, usertype integer not null, employeenumber varchar(255), notes text, clientid bigint, headofficeid bigint, subvendorid bigint)");
|
"dlkey text, dlkeyexpire timestamp, usertype integer not null, employeenumber varchar(255), notes text, clientid bigint, headofficeid bigint, subvendorid bigint)");
|
||||||
|
|
||||||
|
//Index for name fetching
|
||||||
|
exec("CREATE UNIQUE INDEX auser_name_id_idx ON auser (id, name);");
|
||||||
|
|
||||||
//Add user options table
|
//Add user options table
|
||||||
exec("CREATE TABLE auseroptions (id BIGSERIAL PRIMARY KEY, ownerid bigint not null, " +
|
exec("CREATE TABLE auseroptions (id BIGSERIAL PRIMARY KEY, ownerid bigint not null, " +
|
||||||
"userid bigint not null, timezoneoffset decimal(19,5) not null default 0, emailaddress text, uicolor int not null default 0)");
|
"userid bigint not null, timezoneoffset decimal(19,5) not null default 0, emailaddress text, uicolor int not null default 0)");
|
||||||
@@ -192,9 +199,8 @@ namespace AyaNova.Util
|
|||||||
exec("CREATE TABLE awidget (id BIGSERIAL PRIMARY KEY, ownerid bigint not null, name varchar(255) not null, " +
|
exec("CREATE TABLE awidget (id BIGSERIAL PRIMARY KEY, ownerid bigint not null, name varchar(255) not null, " +
|
||||||
"startdate timestamp, enddate timestamp, dollaramount decimal(19,5), active bool, roles int4, notes text)");
|
"startdate timestamp, enddate timestamp, dollaramount decimal(19,5), active bool, roles int4, notes text)");
|
||||||
|
|
||||||
//PERF TESTING INDEX ############################
|
//Compound index for name fetching
|
||||||
//Weirdly, it's always slower when it uses the index?? memory issue maybe?
|
exec("CREATE UNIQUE INDEX awidget_name_id_idx ON awidget (id, name);");
|
||||||
exec("CREATE UNIQUE INDEX widget_idx_name_id ON public.awidget USING btree (id, name COLLATE pg_catalog.\"default\") TABLESPACE pg_default;");
|
|
||||||
|
|
||||||
setSchemaLevel(++currentSchema);
|
setSchemaLevel(++currentSchema);
|
||||||
}
|
}
|
||||||
@@ -211,7 +217,8 @@ namespace AyaNova.Util
|
|||||||
"storedfilename text not null, displayfilename text not null, contenttype text, notes text)");
|
"storedfilename text not null, displayfilename text not null, contenttype text, notes text)");
|
||||||
|
|
||||||
//index required for ops that need to check if file already in db (delete, count refs etc)
|
//index required for ops that need to check if file already in db (delete, count refs etc)
|
||||||
exec("CREATE INDEX storedfilename_idx ON afileattachment (storedfilename);");
|
//LOOKAT: isn't this useless without the ID as well or is that not fetched?
|
||||||
|
exec("CREATE INDEX afileattachment_storedfilename_idx ON afileattachment (storedfilename);");
|
||||||
|
|
||||||
setSchemaLevel(++currentSchema);
|
setSchemaLevel(++currentSchema);
|
||||||
}
|
}
|
||||||
@@ -224,7 +231,10 @@ namespace AyaNova.Util
|
|||||||
LogUpdateMessage(log);
|
LogUpdateMessage(log);
|
||||||
// LOOKAT: Should taggroupmap have an index that enforces no taggroup can have the same tag more than once? Same for objects being tagged?
|
// LOOKAT: Should taggroupmap have an index that enforces no taggroup can have the same tag more than once? Same for objects being tagged?
|
||||||
exec("CREATE TABLE atag (id BIGSERIAL PRIMARY KEY, ownerid bigint not null, name varchar(255) not null)");
|
exec("CREATE TABLE atag (id BIGSERIAL PRIMARY KEY, ownerid bigint not null, name varchar(255) not null)");
|
||||||
exec("CREATE UNIQUE INDEX tagname_idx ON atag (name);");
|
|
||||||
|
//LOOKAT: isn't this useless without the ID? Need to see if it's being used after unit testing
|
||||||
|
exec("CREATE UNIQUE INDEX atag_name_idx ON atag (name);");
|
||||||
|
|
||||||
exec("CREATE TABLE atagmap (id BIGSERIAL PRIMARY KEY, ownerid bigint not null," +
|
exec("CREATE TABLE atagmap (id BIGSERIAL PRIMARY KEY, ownerid bigint not null," +
|
||||||
"tagid bigint not null REFERENCES atag (id), tagtoobjectid bigint not null, tagtoobjecttype integer not null)");
|
"tagid bigint not null REFERENCES atag (id), tagtoobjectid bigint not null, tagtoobjecttype integer not null)");
|
||||||
|
|
||||||
@@ -253,6 +263,7 @@ namespace AyaNova.Util
|
|||||||
//////////////////////////////////////////////////
|
//////////////////////////////////////////////////
|
||||||
//LICENSE table new columns
|
//LICENSE table new columns
|
||||||
//LOOKAT: DO I need this anymore???
|
//LOOKAT: DO I need this anymore???
|
||||||
|
//answer: no because it relates to ops stuff in other tables and logging, not to the license itself (except maybe dbid?)
|
||||||
if (currentSchema < 8)
|
if (currentSchema < 8)
|
||||||
{
|
{
|
||||||
LogUpdateMessage(log);
|
LogUpdateMessage(log);
|
||||||
|
|||||||
Reference in New Issue
Block a user