The add-on has 5 main features and there are several caches to make it perform fast in each area.
To check cache sizes in bytes we have used "Retained Size" reported by JVisualVM. Although we have limited size of each cache in terms of items in the cache. Size of each item may change depending on size of used strings, whether a field is null or has a value. Also sometimes a list of objects is stored as a field. Depending on the number of items in the list, cache size may change too. We have also measured only cache entries, cache keys are very small in size mostly primitive value like Long.
Size of all of the above caches combined should be 6.3 + 26.4 + 12.9 + 25.4 = 71Mbytes. This number is just the size of actual objects we store in the cache. But Atlassian Cache stores lost of extra information for each cache entry and for cache itself. For example last access time, pointers to previous and next entries etc. This will make size of caches larger but not too much.
Not all projects requires bundle feature, only big projects with lots of independently releasable components needs this feature. Also number of bundle releases in a project is generally much less than number of component releases. Our customers combine all hardware and software component releases in a package and release this package to their customers.
There are 2 main caches:
There are 3 caches are mostly used to speed up our bundle related custom field indexing and JQL functions (deprecated), and grouping for Structure add-on. All of them are remote caches with maximum size of 1000 entries and expires after 10 minutes. Maximum size of each cache is 1000 * 147bytes = 0.15Mbytes.
Combined total size of all of these 5 caches are: 6.3Mbytes.
We have two caches.
Total size of these two caches are: 26.4Mbytes.
Used to speed up version hierarchy construction and reduce database lookup for displaying version hierarchy and JQL operations related with version hierarchy.
There are 5 different areas handled by the add-on and not every project need to use all of the functions. Also how a feature works depends on user configurable parameters. For this reason the add-on needs to get some configuration objects before it can do anything on the UI. We request subproject configuration, subcomponent configuration and component version configuration objects on every page load (where an issue can be created/displayed). There are 3 caches here, but each cache stores only one object. Replicated via invalidation. Max size of extra heap is < 10KB (there are at most 3 entries in this cache).
Subcomponents feature allows management of components as a component tree.
Total size of these two caches is around 12.9Mbytes.
Subprojects feature allows projects to be organized into a tree hierarchy and adds a picker to select a project from this hierarchy. It also provides "project in subprojectsOf(.......)" JQL.
We don't use @NonNull constraint for active objects. We need to verify the active objects record every time we use it anyway. Jira doesn't allow us to create foreign key constraints on components, projects or versions tables. So every time we read a record from database we still need to verify that it is still referencing a valid project or component. Adding a @NonNull constraint on any of these fields will not save us. We listen for component, version and project events and update our own database records accordingly but an administrator may temporarily disable our add-on delete some versions and he may re-enable the add-on. We need to recover from invalid references to components and versions. Also as soon as we add a @NonNull constraint we need to hardcode name of these columns when creating active object records, we don't like this. We don't say that adding @NonNull constraints help, it helps. For example one of our database upgrade task was written to set project_id column to correct values. It wasn't expected to be null but it did. We are just saying that there are downsides of this approach too.
Our latest and valid upgrade task belongs to 2016. We had a bug in our add-on and project_id of some records were not set correctly. We have fixed this using an upgrade task. Our other upgrade tasks where used to copy value of a column to a new column. But it turned out to be a bad idea and we have commented out body of those upgrade tasks. Today even in a large instance this task should not affect any record.
The app uses EvenPublisher.publish method to publish an application specific event, SettingsUpdatedEvent. We use this event to break a circular reference between two components and to invalidate caches of the ad-on when setting is updated. Because there are some settings which affects which versions are valid given a list of components.
We also listen a lot of Jira specific events: ProjectDeletedEvent, ProjectUpdatedEvent, ProjectComponentDeletedEvent, ProjectComponentUpdatedEvent, and events derived from AbstractVersionEvent. We mostly listen them to invalidate some caches and delete update some database records. We do not perform any long-running actions. They may trigger update/delete of a few database row and invalidation of caches. For example if a project is deleted, we delete corresponding component versions, bundle contents and subcomponent hierarchy. None of these events are too frequent to cause any problem. We don't listen any IssueEvent.
We don't use pagination because we don't think we need it. The add-on mostly operates within the context of a project. It loads subcomponent tree of a project, or bundles of a project, or component versions of a project. Even in a large instance number of components or versions within a project is way below than a few thousands.
We use following code block to manage transactions:
Transaction tx = Txn.begin(); try { .... tx.commit(); } finally { tx.finallyRollbackIfNotCommitted(); } |
All UI of the app is implemented with React and react escapes string by default. This provides some level of build-in protection against XSS attacks. But we also used OWASP's HTML Sanitizer library at server side of the app to sanitize user inputs. Our app mostly about relationship between existing components and versions rather than user input.