Wednesday 5 February 2020

Maximo Scripting – CanDelete/CanAdd Object Launch Point

We can control whether we can add or delete a Mbo using scripting Object Launchpoint “Allow Object Deletion” and “Allow Object Creation”.

Can Add

This is an Object Launch Point – “Allow Object Creation” where you can control whether you can add a new Mbo, given the current state of the system. This point pairs up with the canAdd callback that we have in the MboSet framework.
Lets take a use case where we want to validate that a POLINE can be added to a PO only when the PO has the vendor information set.
The script code to do that is shown below:
if mboset.getOwner() is not None and mboset.getOwner().getName()=="PO" and mboset.getOwner().isNull("vendor"): 
   service.error("po", "novendor_noline") 
Note that here, there is no implicit variable called “mbo” as the launch point is invoked before the Mbo is created. At that point all you have is the MboSet (implicit variable “mboset”) for the POLINE.
If you are wondering why this cannot be done using the init Object launch point, the answer is that its little too late. At that point the Mbo has already been created and added to the set. Rejecting it at the point would have not helped.

Can Delete

Similar to the “Can Add”, this Object Launch Point helps validate whether a Mbo can be deleted or not. Going with the POLINE object, say we want to enforce a validation that the line should not be deleted, if the PO is of priority 1.
if mbo.getOwner() is not None and mbo.getOwner().getName()=="PO" and !mbo.getOwner().isNull("priority") and mbo.getOwner().getInt("priority")==1: 
   service.error("po","nolinedelete_forpriority1") 
Note that in this case, the mbo is already there (which we are trying to validate for deletion) and hence we can leverage implicit variable mbo to do the validation.

Setup Integration Framework JMS queues and buses in WebSphere



So here is a quick guide to setup the JMS queues and buses for use with the Integration Framework.  These steps are for a single instance of Maximo, if you have a second instance of Maximo running for your test environment, then you can follow these same steps, but you just need to change the names accordingly.  If you are creating this for a second instance, then you will need to change a few configuration files and rebuild EARS to point to the new set of JMS resources.

Creating the JMS buses:

1. Navigate in the left pane to Service Integration/buses
2. Click the “new” button to create a new bus
3. In the name field, enter “intjmsbus”
4. Deselect the “Bus Security” checkbox and click “next”
5. Click “finish” and save the changes
6. Navigate to the bus you just created and select it
7. Change the “high message threshold” to 500,000 messages
8. Click “apply” and save the changes

Adding members to the buses:

1. Navigate in the left pane to Service Integration/buses
2. Select the bus created above called “intjmsbus”
3. Under “Topology”, select “bus members”
4. Click the “Add” button
5. Select the “server” radio button and select the MAXIMO server from the drop down (If a single instance, choose MAXIMO, otherwise select the other instance)
6. Click “next”
7. Select the “file store” radio button
8. Accept the default file store location
9. Click “next”
10. Click “finish” and save the changes

Creating bus destinations:

cqin
1. Navigate in the left pane to Service Integration/buses
2. Select your bus intjmsbus
3. Under “destination resources” click “destinations”
4. click the “new” button
5. Set the destination type as “queue” and click “next”
6. In the identifier field, enter “cqinbd” and click “next”
7. Assign the queue in the next screen to the MAXIMO application server
8. Click “next”
9. Click “finish” and save the changes
10. Navigate back to this destination, and set the exception destination radio button to “specify” and set the value in the textbox to cqinerrbd
11. Set the “maximum failed deliveries” to 5
12. Click “apply” and save the changes

cqinerr
1. Navigate in the left pane to Service Integration/buses
2. Select your bus intjmsbus
3. Under “destination resources” click “destinations”
4. click the “new” button
5. Set the destination type as “queue” and click “next”
6. In the identifier field, enter “cqinerrbd” and click “next”
7. Assign the queue in the next screen to the MAXIMO application server
8. Click “next”
9. Click “finish” and save the changes
10. Navigate back to this destination, and set the exception destination radio button to “specify” and set the value int he textbox to cqinerrbd
11. Set the “maximum failed deliveries” to 5
12. Click “apply” and save the changes

sqin
1. Navigate in the left pane to Service Integration/buses
2. Select your bus intjmsbus
3. Under “destination resources” click “destinations”
4. click the “new” button
5. Set the destination type as “queue” and click “next”
6. In the identifier field, enter “sqinbd” and click “next”
7. Assign the queue in the next screen to the MAXIMO application server
8. Click “next”
9. Click “finish” and save the changes
10. Navigate back to this destination, and set the exception destination radio button to “none”
11. Click “apply” and save the changes

sqout
1. Navigate in the left pane to Service Integration/buses
2. Select your bus intjmsbus
3. Under “destination resources” click “destinations”
4. Click the “new” button
5. Set the destination type as “queue” and click “next”
6. In the identifier field, enter “sqoutbd”
7. Assign the queue in the next screen to the MAXIMO application server
8. Click “next”
9. Click “finish” and save the changes
10. Navigate back to this destination, and set the exception destination radio button to “none”
11. Click “apply” and save the changes

Creating the MEA connection factory:

1. Navigate in the left pane to Resources/JMS providers/default messaging
2. Select the Default Messaging Provider at the cell scope
3. Under “additional properties”, select “queue connection factories”
4. Click the “new” button
5. In the name field, enter “intjmsconfact” (If using a second instance, change this name to something like “maxtestintjmsconfact”)
6. In the JNDI name field, enter

Default instance:  jms/maximo/int/cf/intcf
Second instance:  jms/maxtest/int/cf/intcf

7. Select the bus intjmsbus from the dropdown
8. Click “apply” and save the changes

Creating JMS queues:

Continuous inbound queue:
1. Navigate to Resources/JMS providers
2. Select the default message provider at the cell scope and under “additional properties”, click “queues”
3. Click the “new” button
4. Enter the name as “cqin”
5. In the JNDI name field, enter “jms/maximo/int/queues/cqin” (Or “jms/maxtest/int/queues/cqin”)
6. Select the intjmsbus as the bus for this queue from the bus name drop down
7. Select the cqinbd queue from the queue name drop down
8. Click “apply” and save the changes

Continuous inbound error queue:
1. Navigate to Resources/JMS providers
2. Select the default message provider at the cell scope and under “additional properties”, click “queues”
3. Click the “new” button
4. Enter the name as “cqinerr”
5. In the JNDI name field, enter “jms/maximo/int/queues/cqinerr” (Or “jms/maxtest/int/queues/cqinerr”)
6. Select the intjmsbus as the bus for this queue from the bus name drop down
7. Select the cqinerrbd queue from the queue name drop down
8. Click “apply” and save the changes

Sequential inbound queue:

1. Navigate to Resources/JMS providers
2. Select the default message provider at the cell scope and under “additional properties”, click “queues”
3. Click the “new” button
4. Enter the name as “sqin”
5. In the JNDI name field, enter “jms/maximo/int/queues/sqin” (Or “jms/maxtest/int/queues/sqin”)
6. Select the intjmsbus as the bus for this queue from the bus name drop down
7. Select the sqinbd queue from the queue name drop down
8. Click “apply” and save the changes

Sequential outbound queue:

1. Navigate to Resources/JMS providers
2. Select the default message provider at the cell scope and under “additional properties”, click “queues”
3. Click the “new” button
4. Enter the name as “sqout”
5. In the JNDI name field, enter “jms/maximo/int/queues/sqout” (Or the whatever name you called it)
6. Select the intjmsbus as the bus for this queue from the bus name drop down
7. In Select the sqoutbd queue from the queue name drop down
8. Click “apply” and save the changes

Creating the JMS Activation Specifications

First activation specification:

1. Navigate to Resources/JMS providers
2. Select the default message provider at the cell scope and under “additional properties”, click “activation specifications”
3. Click the “new” button
4. In the name field, enter “intjmsact” (Or “maxtestintjmsact”)
5. In the JNDI name field, enter “intjmsact” (Or the whatever name you called it)
6. In the “Destination JNDI name” field, enter “jms/maximo/int/queues/cqin”
7. Make sure the destination type is “queue”
8. Select the bus “intjmsbus” from the bus name drop down
9. Set Maximum batch size to 10
10. Set Maximum end points to 5
11. Click OK and save the changes

Second activation specification:

1. Navigate to Resources/JMS providers
2. Select the default message provider at the cell scope and under “additional properties”, click “activation specifications”
3. Click the “new” button
4. In the name field, enter “intjmsacterr” (Or “maxtestintjmsacterr”)
5. In the JNDI name field, enter “intjmsacterr” (Or the whatever name you called it)
6. In the “Destination JNDI name” field, enter “jms/maximo/int/queues/cqinerr”
7. Make sure the destination type is “queue”
8. Select the bus “intjmsbus” from the bus name drop down
9. For this activation spec, set Maximum batch size to 1
10. For this activation spec, set Maximum end points to 10
11. Click OK and save the changes

Now that you have setup all the JMS resources, you have to enable the use the these queues by making some changes to the Maximo configuration files.

Preparing the MEA application

For the MAXIMOMEA application instance, edit the following files and uncomment the message driven beans for the continuous queues:

1. locate the file under maximo/applications/maximo/mboejb/ejbmodule/meta-inf/ejb-jar.xml

2. Edit the file and make sure the following four sections are uncommented to look like below:

<!-- MEA MDB -->
<message-driven id="MessageDriven_JMSContQueueProcessor_1">
<ejb-name>JMSContQueueProcessor-1</ejb-name>
<ejb-class>psdi.iface.jms.JMSContQueueProcessor</ejb-class>
<transaction-type>Container</transaction-type>
<message-destination-type>javax.jms.Queue</message-destination-type>
<env-entry>
<env-entry-name>MESSAGEPROCESSOR</env-entry-name>
<env-entry-type>java.lang.String </env-entry-type>
<env-entry-value>psdi.iface.jms.QueueToMaximoProcessor</env-entry-value>
</env-entry>
</message-driven>
 
<!-- MEA MDB for error queue -->
<message-driven id="MessageDriven_JMSContQueueProcessor_2">
<ejb-name>JMSContQueueProcessor-2</ejb-name>
<ejb-class>psdi.iface.jms.JMSContQueueProcessor</ejb-class>
<transaction-type>Container</transaction-type>
<message-destination-type>javax.jms.Queue</message-destination-type>
<env-entry>
<env-entry-name>MESSAGEPROCESSOR</env-entry-name>
<env-entry-type>java.lang.String </env-entry-type>
<env-entry-value>psdi.iface.jms.QueueToMaximoProcessor</env-entry-value>
</env-entry>
<env-entry>
<env-entry-name>MDBDELAY</env-entry-name>
<env-entry-type>java.lang.Long </env-entry-type>
<env-entry-value>30000</env-entry-value>
</env-entry>
</message-driven>
 
<!-- MEA MDB -->
<container-transaction>
<method>
<ejb-name>JMSContQueueProcessor-1</ejb-name>
<method-name>*</method-name>
</method>
<trans-attribute>Required</trans-attribute>
</container-transaction>
 
<!-- MEA MDB for error queue -->
<container-transaction>
<method>
<ejb-name>JMSContQueueProcessor-2</ejb-name>
<method-name>*</method-name>
</method>
<trans-attribute>Required</trans-attribute>
</container-transaction>

3. locate the file under maximo/applications/maximo/mboejb/ejbmodule/meta-inf/ibm-ejb-jar-bnd.xmi

4. Edit the file using a text editor and make sure the following 2 sections are uncommented to look like below:

<!-- MEA MDB -->
<ejbBindings xmi:type="ejbbnd:MessageDrivenBeanBinding" xmi:id="MessageDrivenBeanBinding_1" activationSpecJndiName="intjmsact">
<enterpriseBean xmi:type="ejb:MessageDriven" href="META-INF/ejb-jar.xml#MessageDriven_JMSContQueueProcessor_1"/>
</ejbBindings>
 
<!-- MEA MDB for error queue -->
<ejbBindings xmi:type="ejbbnd:MessageDrivenBeanBinding" xmi:id="MessageDrivenBeanBinding_1" activationSpecJndiName="intjmsacterr">
<enterpriseBean xmi:type="ejb:MessageDriven" href="META-INF/ejb-jar.xml#MessageDriven_JMSContQueueProcessor_2"/>
</ejbBindings>

Make sure you enter the right activation name if you are setting this up for a second instance.
5. Build and deploy the maximo.ear



Maximo Scripting – Warnings and Errors

This post is an excerpt from the Maximo 76 Scripting Features guide.
Maximo errors in 7.5 version used to get done by setting errorgroup, errorkey and params variables (full example here).
woPriority = mbo.getInt("WOPRIORITY")
if woPriority > 50:
    # Raise an error using a message
    errorgroup = "workorder"
    errorkey = "invalidPrio"
Setting those did not stop the execution, as the execution will continue till the end of that script and then it will check the flags and throw the error. Often this may not be the expected behavior. To fix this issue, in 7.6 the global “service” variable can be used to throw errors and warnings.
service.error("po", "novendor") 

Warning message

Showing a warning message can be done fairly easily with automation scripts. Say we want to show a warning to the users when they save a PO with no lines.
To do this, first we need to create a warning message from the messages dialog in the db config application. Say we name it “nolines” under the message group “po”.
We create an Object Launch Point – Save event on Add and Update. The code below (py) validates the POLINE count and uses the “service” global variable to show the warning.
if mbo.getMboSet("POLINE").count()==0 and interactive:
    service.setWarning("po", "nolines", None)

Maximo business object development

Maximo business objects (MBOs)

  • The initValue() method is the first method executed after the constructor.
    • Typically used to initialize attributes on new records and to set default values.
  • The init() method is called after initValue(). Currently used to set current attribute as read-only based on some condition. Set related attributes as read-only.
  • Setting of field flag should not be done in the init() of mbo. It should be done in the initFieldFlagsonMbo():
    • Consider performing logging, in the initFieldFlagsOnMbo() method.
      • The logic of setting the readonly and required field flags to the MBO.
      • Any setting of the field flags that require expensive operation, such as database query should be moved from init() to this method. However, those which don't require steps such as fetching Mbos can be left in init().
      • You can group the logic for a few attributes together if logic of deciding their flag is the same, or in other words, can be handled at once.
    • Don't fetch MBOs in the init() method.
    • If MBO needs to be fetched, do it in initFieldFlagsOnMbo().
    • Doing this could avoid fetching additional MBOs when this current MBO being called. This could also improve the performance.
  • Constructing a MBO is costly
    • If you have a MBO available to use, do not refetch the MBO.
    • Don't implement a cache that holds on to MBOs, use smaller data structure.
    • Use the information that already resides in memory whenever possible, avoid getting new MBO from the database.
      1. Using the same relationship from the same MBO will get you the same MBO set already created.
      2. Pass the Mbo/MboRemote as parameters to the methods.
      3. Use BulletinBoardService to communicate information among objects within the execution of the same thread if passing parameter is not possible.
  • The constructor should only be used to call super and set some instance variables.
    • Should never execute methods that could call exceptions.
  • Don't ever use a MBO or mbo set class as a event Listener, it will never get garbage collected.
  • Don't ever put a mbo/set as static anywhere.

MboSets

  • If you only need to process records that are already instantiated in the MboSet use MboSet.getSize to loop through the records instead of count.
  • Avoid using the MboSet method count. It hits the database and can take a long time.
  • To see if a MboSet is empty use the MboSet method isEmpty(), do not use count().
  • To see if there's only one record in the set, use:
    • ((set.getMbo(0) != null) && (set.getMbo(1) == null)).
  • If you want to visit every member of a set, use code like:

    *EXAMPLE

  • If you only want to check to see if a set has been modified, use Mbo method getInstanciatedMboSet.
    • If the set hasn't been read in yet, this method returns null, so you know no possible changes could have occurred:

      EXAMPLE
  • Close the MboSet if you are certain you will not use it anymore.
  • Use set NOSAVE flag to true, if MboSet obtaining is for readonly purpose. Then it won't be added to transaction.
  • If a MboSet is used for traversing forward only and not to be saved use DISCARDABLE MboSet.
    • The children of a discardable MBO is NOSAVE, change NOSAVE flag back to true.
    • Or don't use relationship, directly create set using a composed where clause.
  • SAVE - Don't call save in business logic code.
    • Exceptions are transactions that don't need to be rolled back.
    • Think Rollback when calling save.
  • The modification of a MboSet that hasn't been saved can be obtained from the same relationship from the same parent, if you use a different relationship you won't see the change.
  • If two MboSets contain MBOs pointing to the same record in the database and you modify both you will get "Record has been updated by another user". That is why you should use the same relationship to obtain the record.
  • Avoid repeating logic, reuse MboSets and MBOs
    • Pay extra attention to the logic inside a loop.
    • When implement lower level method, understand how it is to be used. Avoid expensive operations that would become repeated when the method is used.

Field validations and actions

NOTE: Even though the validation and action logic can be put in either method, by convention the validation method should only do validation and the action method should only do business logic. Do not perform validation in the action method.
  • Field validation gets invoked when tabbing out of a field, or when a field is being set from the backend.
    • When the setValue method is invoked for an attribute the framework accesses the data dictionary table MAXATTRIBUTE to determine if the attribute has a validation class associated with it.
    • Typical order of execution: initValue(), init(), validate(), action(), hasList(), getList().
    • initValue() - initializes fields for new records
    • init() - sets read-only fields
    • Once a validation class has been determined, the validate() will be executed. The validation method can be customized to verify whether the new value for the attribute is valid.
    • An action() method may then be executed. Action methods contain business logic and updates to other objects.
    • hasList() - returns true if there is a list.
    • getList() - creates MboSet for list.
  • MEA transactions also invoke field validation for all fields it sets at the very end.
  • MEA - Use getValidateOrder() to specify the attribute validate order
    • In case MEA performs delay validation, use this method to specify the validate order.
  • Register validation classes, by defining the class name in the class field.
    • If a class is already defined, then you can extend the existing class
  • Using Access Flags
    • The access flags are defined in the MboConstants interface. Implement psdi.mbo.MboConstants to use the constants without using a prefix with them.
    • Some of the more commonly used flags: NOACCESSCHECK, NOVALIDATION, DELAYVALIDATION, NOACTION, NOVALIDATION_AND_NOACTION.
NOTE: It is important to set the appropriate flags to help increase Maximo performance.
  • DOMAINS - MAXTableDomain, CrossOverDomain, ALNDomain, SynonymDomain, NumericDomain
    • Use Domains and Crossover Domains as much as possible
    • If unachievable then use FldClass
      • Understand all flags when developing field class.
      • Avoid doing validation if it's not needed.
        • Conditional Domain Value
          • ALNDomain/SynonymDomain/NumericDomain
        • Conditional Cross Over Domain (various flags - copy don't copy, etc)
        • Domain and FldClass could co-exist.
          • Consolidate Domain and FldClass if possible
            • Validation occurs for both.
            • Everything in the Field Class will be executed except for the getList(). The list will come from the domain that is defined. If a domain is not defined the list will be fetched from the field class.
        • MaxLookupmap - key mapping for lookup and goto app.
          • Better to be implemented in table and not in fldclass.
        • Avoid Yes-No-Cancel Dialog, it's memory intensive.
          • Understand User-Interactive.
  • Field Flags (ex. Readonly)
    • Do not implement costly field flags in initvalue or init of MBO.
    • Implement in mbo.initFieldFlagsonMbo
  • Non-persisistent attributes
    • Population of value should be defined in that fields initvalue, not in MBO's init.
  • When using MAXTABLEDOMAINS - getList() gets called every time.
    • Do criterias in the constructor if it's fixed and not dynamic.
  • Two classes can be associated to an attribute. MboValue and Validation Class.
    • The MboValue class contains the attribute value and all the attribute properties defined in the data dictionary.
  • Some of the more commonly used methods are listed:
    • getString(): Gets attribute value as String
    • getInt(), getFloat, getDouble(): Get value as int, float, ...
    • getColumnTitle: Gets the title of the column
    • getInitialValue(): Gets initial value
    • getMbo(): Gets the MBO associated with the attribute
    • setValue(): Sets the value of the attribute
    • setValueNull(): Sets the value null
    • isNull(): True if attribute value is null
    • isReadOnly(): True if attribute is read-only

Relationships

  • A whereclause generated by a relationship is only a portion of the whereclause generated, there are other parts to the clause such as security.
  • Make sure a relationship is not defined in the database before adding a new one.
  • Don't modify an existing relationship. Create a new one.
  • Everything obtained via a relationship is included in the same transaction. When any MboSet in the transaction is saved, all MboSets in that same transaction will be saved.
  • Don't use a relationship if you want a brand new transaction, instead get the set from MXServer.
Example: The following gets the complete Work Order table which you can further narrow the set by applying a where clause.
\\ WOSetRemote mboSet = (WOSetRemote) MXServer.getMXServer().getMboSet("WORKORDER", getUserInfo());

General

  • When using a private method, consider writing another public method to call this private method. So outside objects can extend from this private method if needed.
  • If a set is filtered from the UI using setQbe, the user should be aware that if they attempt an action using the getThisMboSet(), they will only be getting the filtered set, not the complete set.
  • When writing code, do not forget that there are possibilities that the code could be called from MEA or other objects that don't involve the UI.
  • When executing scripts, the order of execution is first DBC script and then JAVA.
  • If you want to implement an interface, you need to implement each and every of its abstract classes. Otherwise your class will remain abstract as well.
  • Do not use System.out.println
  • Avoid implementing static variable and class
  • Don't use synchronized class if not needed
  • Don't use toUpperCase() or toLowerCase() liberally, pass parameters in the right case.
  • Use StringBuilder(StringBuffer if need synchronization) instead of String += in a loop.
  • Formatter classes are expensive, directly use the object instead of converting to string and converting back.
  • Consider fetching a MBO independent of the MBO used in a while loop, which may have been fetched via a relationship. This helps release MBOs and free up memory.
  • Consider using the DISCARDABLE flag to enable the release of MBOs quicker. Garabage collection usually cleans up the left over MBOs and mboSets, but it could be slower than what you need. Use discardable when a MboSet is used for traversing forward only and is not to be saved.
  • Consider using getMXTransation().save(): If processing a large set of records, consider saving each transaction instead of waiting for the entire set in the while loop to perform transactions. This will get the currently active transaction for the object. If there is no current transaction, a new one is created for you.
  • What may also help release memory is fetching a new database connection. This helps avoid extensive instantiation of MBOs in the memory:

Keeping costs low

  • Improve the database query for the MboSet
    • A not well tuned Sql query is generally the biggest contributor to elapsed time taken by business logic
  • Don't make the table too wide
    • Using normalized tables, especially if the tables can be separated out by different functions, such as can be displayed on a different tab or dialogs on UI, can be beneficial
  • Keep MBO's init() light
    • Don't query DB in init() at all and move expensive logic of setting MBO or field flags to initFieldFlagsOnMbo()
  • In init of field A, don't initialize non-persistent field B.
    • Field B should initialize itself in its own initValue()

Common performance problems

  • Repeated queries, too many queries, and redundant queries
    • Use the existing relationship, use getSharedMboSet() if applicable
    • Consider passing parameter(s) or caching
    • Don't issue queries in Mbo.init()
    • Don't call reset/save unnecessarily
    • If the method is a method that can be called repeatedly, don't simply issue the query, think alternative solutions
  • Lacking Indexes
    • Don't assume the record set will be small, have strategies for unexpected
    • Test against large database
    • Use explain plan or other tool to help with the query and indexes
    • Analyze the possible queries to the table, create appropriate indexes
  • Not able to use indexes.
    • If the field can be UPPER, don't define it as ALN
    • Avoid using QBE in the code, use setWhere() to prevent leading %
    • Define TEXT search type on description like columns
    • Avoid using "not in" (not to be confused with not in ('abc', 'efg')) and !=
    • Avoid selects and sub-selects that only using columns with few distinct values
    • Keep in mind that result can be different on different DBs
  • Statements too complex
    • Use tools such as Optim query tuner to help with writing the query and create index
    • Consider using multiple queries
  • Query returns too many records, not enough qualifiers
    • Re-examine functionality, rewrite the query
    • Provide a default filter for the application list page and lookup
    • Find alternative app flow to use more selective columns in the query
    • Reduce the records returned in subqueries
  • Example of SQL which performs differently based on the database platform
    • Query result may be different on different DB platform, but in majority of cases, they are similar.
    • Costs shown are relative to the DB, don't literally compare between platforms.
    • The "not in" is much worse than "in".

Considerations when defining indexes

  • Do not index columns that are modified frequently (like rowstamp)
  • Consider indexing columns that are used frequently to join tables in SQL statements
  • Don't index the column that have very few distinct values, consider composite index to increase the selectivity, especially if their combined selectivity is better than the selectivity of either column individually
  • Create the index so the keys used in WHERE clauses make up a leading portion.
    • If index is on columns A, B, C in this order, query that only uses A and C will not use the index effectively; query that uses A and B will use the index effectively.
  • If all keys are used in the SQLs equally often choose the most selective columns first in the index
  • If a column in a composite index allows null, make sure a not null column precedes it in the index
  • Beware of the cost of having indexes, don't define too many of them, drop unused ones
    • This is to reduce overhead on inserts and updates
  • Start with the frequently executed queries and most expensive when creating indexes
  • Seek help from DB tools and subject experts

Database performance

  • Keep the transaction short
    • Avoid overwrite MboSet.saveTransaction()
    • Avoid using event listener after save
    • If you have to do the above, please keep the logic minimum
    • Use NOSAVE MboSet to keep the sets in MXTransaction small, thus shortens the looping time for the transaction
  • Close a MboSet immediately if you know you can close it
    • Keep the cursor open in some DB can cause locking or even dead locking
    • If isEmpty() is called, and you know it is possible that there are more than 2 records in the set, and you know you are not going to reuse the set, close it. (Maximo fetches 1 record further, and will close the set automatically if all the records are fetched.)
  • Don't design very wide tables
    • Select * is costly when the table is wide
    • Use views or sub-select with proper index defined is not slower
  • Providing default Order by only if the result set is small enough
    • Order by is very costly, especially on large result set. Even though maximo may not fetch all, the cost on DB side is high

Other practices affecting performance

  • Care should be taken when using Yes/No/Cancel dialog
    • Yes/No/Cancel is expensive because the logic repeats
    • Call the logic of deciding whether to throw yes/no/cancel as early as possible in the logic
  • Know when to use MboSet.count() and MboSet.getSize()
    • MboSet.count() will most likely hit the Database, MboSet.getSize() only give you how many has been loaded in memory
    • Use MboSet.getSize() if what you are going to process are already instantiated as Mbos in memory.
  • Don't use save() in the business logic code unless for separate transactions
    • Transaction atomicity
    • Save invokes commits, which resets the mboset. If you get child mboset from this MBO again, all its related sets will be re-instantiated by re-querying the DB.
  • When logging, always check the logging level (don't check when logging error)
if (logger.isWarnEnabled())
logger.warn();
  • There is added cost when using conditional UI and conditional sigoption, use them wisely, do not over-do it
  • When defining maximo condition, evaluate the cost of complex expression that will rely on DB query to evaluate
    • Consider using a java class if complicated logic is involved
  • Share maximo condition if possible to reduce the evaluation time
    • Condition result is cached for each MBO
    • Don't choose always evaluate if the business logic doesn't require so
  • String operations such as toUpperCase() is very expensive
    • Don't use toUpperCase() or toLowerCase() liberally, passing parameters in the right case
    • StringBuilder(StringBuffer if need synchronization) should be used instead of String for string concatenation in a looping logic.
    • Formatter classes are expensive, directly use the object instead of converting to string and converting back.

Considerations when designing

  • Consider redesigning the look and feel and the functionality to improve usability and performance, for example, show information from different tables on different tabs or dialogs
  • Consider whenever possible to introduce default filters to the list page. It is hard to add the filter after the client is accustomed to and facing performance problem when their data grows.
  • Consider providing warning when performance threshold is met. (e.g. don't wait until the user is reporting 5,000 labor reported to the same workorder.)
  • Consider providing support for using background async job when the operation is anticipated to be slow (AsyncJobHandler and AsyncJobSubmitter)
  • Offer alternative solutions such as crontasks to handle slow and resource intensive operations.
  • Weigh among non-persistent field, related field, and crossed over persistent field and using views. Each has its pros and cons, views generally offer the most benefit if can be used
  • Provide purge functionality for transactional history tables to control the size

Example

Redundant Logic
Correction of Redundant Logic which makes a big difference

Start Center best practices

Start Centers

Use the following best practices for managing start centers:
  • Maintain records in portlet tables.
    • Determine what users are inactive and delete their start center records.
    • If indexes are not present, put indexes on portlet tables. There should be an index on LAYOUTID for all start center portlet configuration tables.
  • When a user record is deleted or a user is made inactive, we do not delete that user's start center data.
    • Create a trigger that watches when users are made inactive and deletes them from corresponding tables. (See the tables in blue on the ERD figure below.)
  • A high number of queries are fired when a start center is loaded.
    • Starting with Base Services patch 7.1.1.7, start center layout and configuration of a portlet is cached when a user first logs in. Editing a portlet invalidates the cache. Editing the portlet layout invalidates portlet layout cache. Choosing a different start center also invalidates the cache. For administrators, creating a new template invalidates the cache.
      Administrators should make start center templates lean by making sure few templates are not packed with many portlets. It is recommended that only 1-2 result set portlets are configured in any template. Queries being used by the result set portlets should be reviewed. This is especially true for clients using releases prior to 7.1.1.7
      Make sure a user is not given the same template(s) multiple times. Although the user would not see multiple instances of a template on a start center, following this practice reduces some checking during log in.

Start Center Tables

The following table describes start center tables:
TablePurpose / Contains
SCTEMPLATEStart Center templates and presentation XMLs
SCCONFIGStart Centers given to a user based on the group and template. For admins, GROUPNAME and SCTEMPLATE are null when editing a template until saved into SCTEMPLATE
LAYOUTConfiguration for a given start center, i.e., which portlets, which column, what order etc.
INBXCONFIGConfiguration of a inbox portlet, i.e., all available columns, which ones are visible, custom names etc.
ACTIONCFGConfiguration of a quick insert portlet, i.e., which app, which INSERT sigoption, custom name etc.
FACONFIGConfiguration of a favorite applications portlet, i.e., which app
KPILCONFIGConfiguration of records related to KPIMAIN records, i.e., which KPIs
KPIGCONFIGConfiguration of records related to KPIMAIN records, i.e., which KPIs
RSCONFIGConfiguration of result set portlet, all available columns, which ones are visible, custom names, used in condition or for graph display etc.
REPORTLISTCFGConfiguration of a records related to REPORT, i.e., which reports belong to which app
Additional Tables
RESULTSETCOLSHolding table for persistent columns for a given object and table in maximo. Loaded when new query is selected in Result Set portlet.
EXCLUDEDACTIONSActions that are not allowed to be configured in Quick Insert portlet.
The following entity relationship diagram (ERD) shows Start Center tables.


IBM Readme for IBM Maximo Asset Management 7.6.1.3 Fix Pack

  Fix Readme Abstract This fix pack updates IBM® Maximo® Asset Management version 7.6.1, 7.6.1.1, and 7.6.1.2 Content IBM Maximo Asset Manag...