Data Center Readiness

Database: Additional tables

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 issue table, user table. A user may create a reminder on an issue, and after that both the user and the issue may be deleted while the add-on is disabled temporarily. So we always need to verify data we read. We verify whether the user exist, issue exist. Adding a  @NonNull constraint on issue id or user key will not save us. 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. 

Scheduled Tasks & Locking

We use following code to send pending reminders. This code is triggered at 5 minutes intervals and at least 5 minutes later the add-on received onStart event. We use register task with RunMode.RUN_ONCE_PER_CLUSTER setting. In addition to this we also use cluster locks (line 622) to ensure that only one instance of this task is running and if a new tasks is triggered before the previous task finished we simply skip new task. 

We don't know whether the cluster locks are reentrant or not. So we also have an additional guard with an instance level "sendingReminders" flag (line 614). If lock is reentrant and the host holding lock can re-enter the lock block this instance level flag will prevent this.

Worst case scenario is sending a reminder will be delayed 5 more minutes. 

    if (this.sendingReminders) {
      log.warn("The add-on received a send pending reminders request while already processing reminders. You may be sending too many reminders, for example you may sending a reminder to large jira user group");
      return;
    }

    log.debug("Acquiring cluster lock for sending reminders");
    ClusterLock lock = clusterLockService.getLockForName(ReminderServiceImp.class.getName() + ".SendPendingReminders");
    boolean lockAcquired = lock.tryLock();
    if (!lockAcquired) {
      log.warn("Previous instance still running");
      return;
    }
    log.debug("Acquired cluster lock for sending reminders");
    try {
      sendPendingRemindersImp();
    } catch (Exception e) {
      log.error("Can't send pending reminders", e);
    } finally {
      this.sendingReminders = false;
      log.debug("Releasing cluster lock for sending reminders");
      lock.unlock();
    }

Event Handling

We introduce ReminderCreated and ReminderDeleted events. We publish them using eventPublisher.publish(event) method. We don't perform have any guard for limiting memory usage. Do you have any guideline on this issue?

We also listen for IssueEvent and IssuePreDeleteEvent. If type of IssueEvent is ISSUE_UPDATED we reset "global due date reminder sent" flag of issue (1 DB query and delete DB record if record exist). For IssuePreDeleteEvent, we perform the same operation with IssueEvent but also delete all reminders for the issue (1 DB query and delete found reminders from DB). 

Transaction Management & Resource Usage

The add-on sends two type of reminders, user created reminders for issues and due date reminders for issues.

User Created Reminders: We only retrieve active reminders (which are not triggered yet) when querying reminders to be sent Although users may create more than one reminder for a single issue, most issues don’t require any reminder. In a normal usage, number of active reminders should be very small compared to the number of issues or users. We have also considered to use activeObjects.stream instead of following code segment but we need to modify Reminder object as not active and stream is returning read-only instances.

    Query query = Query.select().where("ACTIVE = ?", Boolean.TRUE);
    Reminder[] reminders = activeObjects.find(Reminder.class, query);

For Global Due Date Reminders: We first query issues due within specified hours. We do this using JQL using PagerFilter.getUnlimitedFilter. We use unlimited query because our JQL is already very restricted, it only returns issues due within specific hours. There is no option other than using unlimited filter here, because we need to send reminder for all issues matching the criteria (due data < # hours).


Functionality: Application Components

Our add-on adds a web panel to issue right context. Thanks to Jira, even if there is an exception in code path that provides this web panel Jira still displays rest of the issue and only an error is displayed inside the web panel. We use Velocity Templates to render this web panel, it is not loaded with Ajax. Our Reminder active object is annotated with @Preload annotation and has index on issue id field. When an issue is viewed we try to load reminders of the issue. So in sort, we add one more database call to get reminders of an issue.


Security

All panels for the add-on are rendered using Velocity Template Engine and we use #enable_html_escaping() directive to prevent injection. We also use @RequiresXsrfCheck annotation and ${atl_token} inside forms to prevent request forgery.