Bug #7425
Avoid directory usage in static blocks
100%
Related issues
History
#1 Updated by Alexandru Lungu about 1 year ago
After discussing with Constantin, mostly in regard to #7398, there are several places in FWD where the directory is read from static blocks. This reading is done in a quite non-deterministic fashion to us. In the backstage, the read is done when the class is loaded, but this is not quite easy to pinpoint and certainly not easy to manage.
A solution here is to refactor static blocks into static initializing methods. These methods will be called at server bootstrap, such that the directory read is done at a deterministic point. An initial effort in this sense is done in #7388, where cache sizes will be configured only once at the server bootstrap based on directory.xml
values.
This work is not trivial, as some class initialization don't make sense (i.e. TableHints.initialize
, TempTableDataSourceProvider.initialize
- these are not a static helper classes, .initialize
doesn't seem to make sense). For caches, we will intentionally create a separate static helper class whose .initialize
makes sense. I would expect to see the same for other classes (maybe move the directory.xml reads in other helper classes which already have .initialize
). We need to avoid calling tens of .initialize
at server bootstrap, but to group them logically.
#3 Updated by Alexandru Lungu about 1 year ago
- Related to Bug #7388: Create server configuration container for cache sizes added
#4 Updated by Alexandru Lungu about 1 year ago
- Status changed from New to WIP
- Assignee set to Dănuț Filimon
Danut, this is close to your work on #7388. I will do the review on caches there asap.
Please extend your research on other static initializers we have in place. For the caches in #7388, you've done some extra work which is not necessarily required for other static blocks. Consider just renaming the static blocks into initialize
static methods and call them at server bootstrap. Make the class members non-final if customizable from directory.xml
. Initialize the values with the default until the bootstrap (to cover conversion-side). Skip code in static blocks which is not related to directory.xml
Before this whole purging process, please post a list here of all such static blocks you find across FWD.
Created 7425a.
#5 Updated by Dănuț Filimon about 1 year ago
directory.xml
:
TemporaryAccountPool
WebHandler
: from the main packageDirtyShareSupport
P2JQueryLogger
Session
: from the persist.orm packageSQLStatementLogger
TempTableDataSourceProvider
: Note that the static block was removed in 7388a.DatabaseManager
DatabaseStatistics
MetafileHelper
XprHelper
JavaTypeSerializer
TableHints
EnvironmentOps
I will double check after replacing the current static blocks with initialize
methods to make sure that none are missed. If there are places where the current directory.xml
access should not be removed, tell me.
EDIT: SQLStatementLogger
doesn't have a static initializer block.
#6 Updated by Alexandru Lungu about 1 year ago
Danut, try to make the initialization calls hierarchical. That is, do not list all initialization in StandardServer
.
For example, I think P2JQueryLogger
, DatabaseStatistics
and SQLStatementLogger
can be grouped. In fact P2JQueryLogger
and SQLStatementLogger
can be initialized from DatabaseStatistics
.
#7 Updated by Dănuț Filimon about 1 year ago
initialize
method, so to avoid confusion I bootstrap
for the method name. Currently I modified classes from the main
, persist
, reporting
, serializers
, schema
and utils
and looked into initializing in a hierarchical order:
P2JQueryLogger
can be initialized fromDatabaseStatistics
, as previously mentioned by Alexandru- Any class from the
persist
package can be initialized inPersistence.initialize
- There are already server hooks defined for
TemporaryAccountPool
and supposedlyWebHandler
(correct me if I am wrong) - Created new server hooks for the rest of the
bootstrap
calls, grouped by package.
All the changes that I am going make here should be committed to 7388a instead of 7425a because the server configuration for cache initialization was changed.
I committed 7388a/rev.14620 where static initializers were replaced by methods called at server bootstrap. I tested the changes on small programs, but will continue with testing a customer application.
Constantin, could you review these changes?
#8 Updated by Alexandru Lungu about 1 year ago
- % Done changed from 0 to 50
This bootstrap is triggered only on server start-up from StandardServer
hooks. Should we consider other FWD usages when bootstrapping? My knowledge is that directory.xml
is used exclusively with SERVER mode. Therefore, conversion should stick with the default values, not configurable by any directory.xml
.
#9 Updated by Alexandru Lungu about 1 year ago
Rebased 7388a from trunk rev. 14633. 7388a is now at rev. 14636.
#10 Updated by Alexandru Lungu about 1 year ago
Review of 7388a
This also includes the review for #7388.- In
StandardServer
, you no longer needDatabaseStatistics.get
as the bootstrap is done explicitly already. You can remove the get call. Move the comment before.bootstrap
- I think we should have a more uniform way of handling directory binding. Please decide only one single pattern for all
- there are places where
Utils.getDirectoryNodeInt(null, ...);
is used straight-away (this is OK only when the target instance can be null) - there are places where
if (!dir.bind()) throw new RuntimeException("Directory bind failed");
is used to check if there is a bind directory. (this should be used) - there are places where
finally{ ds.unbind(); }
is used after usage: sometimes this doesn't happen; sometimes the unbind is not in a finally clause (this should be used) - there are places where
ds.bind()
happens in the try clause withds.unbind()
finally clause; some have thebind
outsidetry
(It doesn't make sense to attempt unbinding if binding failed) - there are places where
ds == null
is used (I think this is decent to be used)
- there are places where
- Private constructor in
private P2JQueryLogger()
was removed (?) - Adding public in
public class Persister
is something dangerous from my POV. Maybe we can bootstrap it from another class in the same package without exposingPersister
. CacheManager
has several issues:initializeCache
calls may be omitted if we return early or an exception is thrown (the directory is not bound). The initialize should be always done, no matter if we return early / an exception is thrown.- The logs are not very descriptive. Please include the name of the parameters and values to identify what is wrong. If a class could not be found, log the name of it. If a cache is misconfigured, log its name.
- I don't think it is safe to use
initialize
in a multi-session environment. Please bootstrap CacheManager only once at server start-up to ensure it is initialized only once. Remove anyif (!initialized) initialize();
code. - Please make
ClassKey.equals
return false ifo
is not an instance ofClassKey
- Move
Session.PK
after all final members, as it is no longer final. There are other cases of such mismatch (e.g.DatabaseManager
) Session.createCache
no longer includes the database name in the cache name. Consider usingdatabase.toString()
as discriminator. Change your cache size look-up to the following. This allows setting different cache sizes for each database sessions.- If you require class and discriminator: If there is a (class, discriminator) entry use it. Otherwise if there is a (class, null) entry use it. Lastly, use the default.
- If you require class: If there is a (class, null) entry use it. Lastly, use the default.
- Please fix history entry numbers: everything that comes from 7388 should share the same history number / per file (e.g.
BufferManager
). - Anything that you made non-final should be private and retrieved through getters. Otherwise, we may change it from outside of the class.
- Please do a check with a large customer application. Debug a bit to check if the values are right considering the directory.
Danut, please fix this ASAP.
#11 Updated by Dănuț Filimon about 1 year ago
- Removed the
DatabaseStatistics.get
call inStandardServer
- This will be the standard for handling directory binding:
if (ds != null) { if (!ds.bind) { throw new RuntimeException("Directory bind failed"); } try { } finally { ds.unbind(); } }
- I instantiated the
Persister
cache inSession.bootstrap()
and removed thepublic
modifier - Added better log messager in
CacheManager
- Fixed
initializeCache
calls that could be omitted if binding fails. - Removed
intialized
and it's usage inCacheManager
, even though caches can be instantiated beforeCacheManager.initialize()
is called, a default value is used for the cache size - Added an additional check for
ClassKey.equals
method - Reordered class members in some cases
- Fixed history entries
- Added
CacheManager.getCacheSize
that will be used to look for the cache size of a cache. If a discriminator is provided but there is no value found, it will use the default key (discriminator isnull
) to look again for the cache size.
Alexandru Lungu wrote:
- Private constructor in
private P2JQueryLogger()
was removed (?)
It was not removed.
- Anything that you made non-final should be private and retrieved through getters. Otherwise, we may change it from outside of the class.
It is really necessary to use getters when the cache is not used outside of the class? I think you are talking about DatabaseManager.forceNoUndoTempTables
. In this case I added getters for this member. Same goes for DirtyShareSupport
class members.
Note that if that's the case, should I still make these changes to Session.PK
and JavaTypeSerializer
members? I didn't do it yet because they are used in a lot of places and I would like to do a separate commit.
I tested a large customer application and everything worked well, but I did make a few more changes and need to retest to make sure.
Here is an example from directory.xml
on how to set up a cache size:
<node class="container" name="08"> <node class="string" name="class-name"> <node-attribute name="value" value="com.goldencode.p2j.persist.FastFindCache"/> </node> <node class="integer" name="size"> <node-attribute name="value" value="10"/> </node> <node class="string" name="discriminator"> <node-attribute name="value" value="L2"/> </node> </node>Note that the discriminator is optional.
#12 Updated by Dănuț Filimon about 1 year ago
I retested the customer application and found out that the psCache
is initialized before the CacheManager
, which means that the cache will not use the configured size. Only after the context is closed and a new one is opened, the psCache
will use the configured size. I've committed 7388a/rev.14638 which solves this issue by initializing CacheManager
before DatabaseManager
. Except for this problem, I had no other issues.
#13 Updated by Alexandru Lungu about 1 year ago
Radu, please check P2JQueryLogger
. I feel like I've mismatched something in it while rebasing. More specific, I am not sure if loggingEnabled
is still used to rule out logging. Please double-check 7388a, more specifically your logging classes.
#14 Updated by Radu Apetrii about 1 year ago
Alexandru Lungu wrote:
Radu, please check
P2JQueryLogger
. I feel like I've mismatched something in it while rebasing. More specific, I am not sure ifloggingEnabled
is still used to rule out logging. Please double-check 7388a, more specifically your logging classes.
A short answer to this would be that loggingEnabled
is still used to mark whether P2JQuery logging
is enabled or not. I don't see anything being out of place.
- You can enable/disable P2J query logging in
directory.xml
, under thepersistence
tag, by having:<node class="container" name="p2j-query-logging"> <node class="boolean" name="enabled"> <node-attribute name="value" value="TRUE"/> </node> ... </node>
P2JQueryLogger
does not support yet an output file to be set, meaning that logs will be placed in the server log, as aWARNING
. An example of aRandomAccessQuery
(without stack trace) log is:23/06/28 15:37:50.557+0300 | WARNING | com.goldencode.p2j.persist.orm.P2JQueryLogger | ThreadName:Conversation [00000002:bogus], Session:00000002, Thread:00000013, User:bogus | com.goldencode.p2j.persist.RandomAccessQuery: Records processed: 1;
- Although there is a field called
loggingEnabled
inP2JQueryLogger
, it is not actually used, becauseP2JQueryExecutor
is the one that controls the situation. TheloggingEnabled
field that should be used is the one fromP2JQueryExecutor
and not fromP2JQueryLogger
. The latter one is redundant, I forgot to delete it.
I'm not sure if this answers your question, but the point is that I don't see anything wrong.
#15 Updated by Alexandru Lungu about 1 year ago
Although there is a field called loggingEnabled in P2JQueryLogger, it is not actually used, because P2JQueryExecutor is the one that controls the situation. The loggingEnabled field that should be used is the one from P2JQueryExecutor and not from P2JQueryLogger. The latter one is redundant, I forgot to delete it.
This is what confused me in the rebase process. I thought that maybe I missed the usage of loggingEnabled
in P2JQueryLogger
. Thank you for the clarification. Please delete it here in 7388a.
#16 Updated by Radu Apetrii about 1 year ago
Alexandru Lungu wrote:
Although there is a field called loggingEnabled in P2JQueryLogger, it is not actually used, because P2JQueryExecutor is the one that controls the situation. The loggingEnabled field that should be used is the one from P2JQueryExecutor and not from P2JQueryLogger. The latter one is redundant, I forgot to delete it.
This is what confused me in the rebase process. I thought that maybe I missed the usage of
loggingEnabled
inP2JQueryLogger
. Thank you for the clarification. Please delete it here in 7388a.
Done, committed to 7388a, rev. 14639. Sorry for the confusion!
#17 Updated by Alexandru Lungu about 1 year ago
- Status changed from WIP to Review
- % Done changed from 50 to 100
#18 Updated by Alexandru Lungu 12 months ago
Danut, please let me know if there is something else to be addressed here. I will like to do a review and testing round. If everything is right, we shall commit this asap.
#19 Updated by Dănuț Filimon 12 months ago
#20 Updated by Alexandru Lungu 12 months ago
Review of 7388a/rev. 14639
TemporaryAccountPool
still has a static block that does all kind of initialization. Should we consider moving all that inbootstrap
instead of leaving them in thestatic
block? I mean,ServerStandard
was forcing this static block anyway withgetInstance
, so from my POV, these can be safely moved inbootstrap
. I am not aware if this class should be initialized outside the server. Maybe somebody with moreTemporaryAccountPool
background can advice.- This is a place where the
directory.xml
pattern is not honored: look-up fords
, check for null and bind, etc.
- This is a place where the
ds
andbind
are not used forWebHandler
- I missed checking the other files to for this pattern, please do a second check here
I am OK with other changes.
Constantin, can you do a second review here, please? I think this was supposed to reach trunk already (some time ago), but even so, I guess it will be OK if we get it now asap.
#21 Updated by Dănuț Filimon 12 months ago
Alexandru, you previously mentioned that "there are places where Utils.getDirectoryNodeInt(null, ...);
is used straight-away (this is OK only when the target instance can be null)" so I considered that the directory service change in this case is not required. In both cases, TemporaryAccountPool
and WebHandler
use null
and the directory service is called by Utils.doWithBoundsDs
. From my point of view, it looks a lot cleaner.
#22 Updated by Alexandru Lungu 12 months ago
You are right Danut, my bad on this one. It is fine to keep null
for ds
and let the directory service do the work. I am just obsessed of keeping bind/unbind
pattern balanced. For null, the balance is ok behind the scenes. For other places, I want to stress out how important is to keep bind/unbind ref count OK.
#23 Updated by Constantin Asofiei 12 months ago
Alexandru, I guess the branch is 7388a, with latest rev 14639. I'm reviewing this now.
#24 Updated by Alexandru Lungu 12 months ago
Right, 7388a was the one I meant to write. Edited now.
#25 Updated by Constantin Asofiei 12 months ago
TemporaryDatabaseManager.initialize
- unbalancedds.bind
, this is an existing issue, please fix it.- other cases which have
Utils.getDirectoryNode
in static constructor and should be initialized at server startup, if not already done implicitly by loading the class:P2JQueryExecutor
- static c'torValidation.P4GL_MAX_INDEX_SIZE
- static fieldPersistence.foreignKeysEnabled
- static field initialized, but I think this is OK, asPersistence
class gets initialized at FWD server startup AFAIK.HtmlBrowserWidget
- has static fields, but this is used only in GUI, so is OKFileSystemOps.pkgRootPath
- static fieldSecurityOps.compatible
- static field
FQLPreprocessor
- please leave only 096 number and remove 097 text.orm.Session
- same, remove 007 text from history entry
Otherwise, nice work, Danut.
#26 Updated by Dănuț Filimon 12 months ago
- Fields were made non-final and renamed if necessary (uppercase to camel case), the only exception being
P4GL_MAX_INDEX_SIZE
; - As suggested, I skipped
Persistence.foreignKeysEnabled
andHtmlBrowserWidget
; - Directory handling was brought up to the standard in
TemporaryDatabaseManager
andP2JQueryExecutor
as mentioned in #7425-11.
#27 Updated by Alexandru Lungu 12 months ago
I will do a second review asap. I am intending to do a second rebase + test + profile just to check that the performance isn't degraded. If everything is ok, can we merge this by tomorrow EOD?
#29 Updated by Alexandru Lungu 12 months ago
Review:
- I am still a bit concerned of
TemporaryAccountPool.getInstance(); TemporaryAccountPool.bootstrap();
. I feel likebootstrap
should be enough, right? That implies moving the static block inbootstrap
. Constantin, any feedback here forTemporaryAccountPool
? - I am a bit concerned of the
createLRUCache
interface that always requires a discriminator. This is usually a particularity of some caches; the common case is "no discriminator". Danut, please create a second method only with class and size, which calls this 3 parameter method under the hood with null as discriminator. Refactor the places wherenull
is used as second parameter. - Be more consistent with cache sizes. This is not related with your changes, but can be fixed now:
Persistence.Context.cache
has size1000
. Can we make this1024
for the sake of consistency with other cache sizes?
I tested the changes and got no regression. Performance is consistent with my baseline - this is expected as most changes are related to server start-up anyway.
#30 Updated by Dănuț Filimon 12 months ago
Alexandru Lungu wrote:
Danut, please create a second method only with class and size, which calls this 3 parameter method under the hood with null as discriminator. Refactor the places where
null
is used as second parameter.
Be more consistent with cache sizes. This is not related with your changes, but can be fixed now:Persistence.Context.cache
has size1000
. Can we make this1024
for the sake of consistency with other cache sizes?
I added the an additional createLRUCache
and increased the cache size of the case in Persistence.Context
to 1024
. These changes were committed to 7388a/rev.14641. Here's a list of known classes with inconsistent cache sizes: BufferManager
(10000), FastFindCache
(L2 - 10 and L3 - 100).
#31 Updated by Constantin Asofiei 12 months ago
Alexandru, TemporaryAccountPool
is OK, it gets fully initialized at server startup. I don't think even bootstrap
is needed, but you can leave it to be consistent.
#32 Updated by Alexandru Lungu 12 months ago
Danut, please do the same changes to BufferManager
and FastFindCache
. Find the smallest power of 2 bigger than the existent sizes. I don't think these are going to break something (hopefully) - I will just do a very quick check before merge.
So, after you do the changes to BufferManager
and FastFindCache
, I will rebase and commit.
#33 Updated by Dănuț Filimon 12 months ago
BufferManager
: from 10000 to 16384.FastFindCache
L2: from 10 to 16.FastFindCache
L3: from 100 to 128.
Hope that BufferManager
won't cause any problems, it's cache size went up by more than 50%.
#34 Updated by Alexandru Lungu 12 months ago
Well, BufferManager
is per-session so it may be a problem. We can go the other way around sticking to 8192
which is closer to the original value, but may cause performance draw-backs. For FFC, from my POV, they are quite right. I guess we can go with 16384
, as long as we allow decreasing its size through server options, right? On the other way, I want to get 7388a in trunk in the next hour, so I may no be able to do the proper testing. Danut, I reverted the changes from #7425-33 as they may be too bold and may require extra testing. I will consider creating another task for cache size tuning.
#35 Updated by Alexandru Lungu 12 months ago
- Status changed from Review to Test
Merged 7388a in trunk as rev. 14641 and archived.
#36 Updated by Dănuț Filimon 12 months ago
I will update the Database Configuration wiki with the proper configuration and explanation for cache sizes as suggested in #7388-5.
#37 Updated by Dănuț Filimon 12 months ago
I updated Database Configuration, this is subjected to changes since I am still working on it, but it should provide a basic explanation on how cache sizes should be configured.
#38 Updated by Alexandru Lungu 12 months ago
Danut, please make sure you get this done correctly asap. I intend to experiment with some fine-tuning using this configuration.
#39 Updated by Dănuț Filimon 12 months ago
Alexandru Lungu wrote:
Danut, please make sure you get this done correctly asap. I intend to experiment with some fine-tuning using this configuration.
I finished writing the wiki, please let me know what you think and if I missed something.
#40 Updated by Alexandru Lungu 12 months ago
I am OK with the documentation. Looks clear to me. However, I feel you can do just a bit more feedback on the "list of caches". It is useful if you can provide their full class name (including package). One can simply check the "cache key" and configure it, without the need to actually search for that class inside the FWD code. Instead of FastFindCache
, use com.goldencode.p2j.persist.FastFindCache
. Also, please let the documentation hold the default cache sizes.
#41 Updated by Dănuț Filimon 12 months ago
Alexandru Lungu wrote:
I am OK with the documentation. Looks clear to me. However, I feel you can do just a bit more feedback on the "list of caches". It is useful if you can provide their full class name (including package). One can simply check the "cache key" and configure it, without the need to actually search for that class inside the FWD code. Instead of
FastFindCache
, usecom.goldencode.p2j.persist.FastFindCache
. Also, please let the documentation hold the default cache sizes.
Done. Thank you for the feedback.
#42 Updated by Dănuț Filimon 12 months ago
The order of the server bootstrap initialization was changed in trunk/rev.14654 and the resources affected need to be investigated further and decide if the current order is correct. For further information on this investigation, please refer to #7534-7.
- There were some instances where cache instantiation was moved to other initialization methods, such as
Persister.initializeCache
which is called bySession.bootstrap
. In this case,CacheManager.initialize
should be called beforeSession.bootstrap
to make sure that the cache is created with the configured size. - From the list mentioned above, the following only read from the directory configuration:
Session
,Validation
,P2JQueryExecutor
,DirtyShareSupport
. DataTypeHelper.initialize
initializes static resources which don't make use of directory access / cache configurations / previously modified resources.TemporaryDatabaseManager.initialize
creates an instance ofPrivateTempDbManager
/SharedTempDbManager
, which doesn't require previously modified resources.- The order between
TemporaryDatabaseManager
,DatabaseManager
andP2OLookup
should not be changed.DatabaseManager
will work with the private/shared manager that was created and thenP2OLookup
requires the managed databases that are created by it.
The conclusion is that CacheManager
can be placed first, followed by Session
, DirtyShareSupport
, P2JQueryExecutor
Validation
, and DataTypeHelper
. CacheManager
also reads the configuration (with the exception of the 2 caches that are initialized by it).
I did not mention anything about SessionFactory.SessionCloseThread.initialize
since it starts a thread before any SessionFactory
is created and it worked properly.