system-design70 min read

How to Design Atlassian - Software Collaboration Platform — A Senior+ Guide

How to Design Atlassian — Software Collaboration Platform

A Senior+ System Design Guide for Building Enterprise Collaboration at Scale

Article #240 Published: October 26, 2024 Reading Time: 45 min By Ayodhyya

1. Introduction: Atlassian at Scale

Atlassian stands as one of the most influential software collaboration platforms in the world, powering the workflows of over 300,000 customers across 190 countries. With annual revenues exceeding billion, Atlassian has evolved from a single issue tracking tool into a comprehensive suite of products that cover project management, knowledge management, code collaboration, IT service management, and enterprise-grade automation. The platform includes Jira Software, Confluence, Bitbucket, Trello, Jira Service Management, and dozens of other integrated products that together form an interconnected ecosystem for software development teams of all sizes.

The significance of Atlassian in the modern software development lifecycle cannot be overstated. From startups with five engineers to Fortune 500 enterprises with tens of thousands of developers, Atlassian provides the tooling backbone that connects planning, coding, testing, deploying, and operating software. The platform processes billions of API calls daily, manages petabytes of collaborative content, and serves as the system of record for project management decisions across the global software industry.

Designing a system like Atlassian from scratch is an extraordinarily complex engineering challenge. You must build a multi-product platform that maintains data consistency across interconnected systems, supports real-time collaboration on documents and boards, scales to serve hundreds of thousands of tenants simultaneously, and provides enterprise-grade security and compliance. The system must handle highly variable workloads � from a single developer updating a ticket to thousands of engineers running CI/CD pipelines simultaneously � while maintaining sub-second response times and near-perfect availability.

This guide walks through the complete system design of an Atlassian-like software collaboration platform. We will examine each major product area, explore the underlying architectural patterns, discuss the data models, review the API design, and cover the operational considerations necessary for running such a system at scale. By the end, you will have a thorough understanding of how to approach building a world-class collaboration platform, the tradeoffs involved in each decision, and the specific patterns that make Atlassian successful.

Key Metrics and Scale

To appreciate the engineering challenge, consider the following scale metrics that an Atlassian-like platform must handle. The system serves over 300,000 paying customers, with millions of daily active users generating continuous streams of interactions. Jira alone processes billions of issue transitions, comments, and field updates monthly. Confluence hosts tens of millions of pages that are read and edited concurrently by distributed teams. Bitbucket manages millions of Git repositories with continuous integration pipelines running around the clock. Trello boards are updated in real-time by teams coordinating across time zones.

MetricValueImplication
Paying Customers300,000+Multi-tenant isolation at scale
Annual Revenue+Enterprise reliability requirements
Products15+ integrated productsCross-product data consistency
Daily Active UsersTens of millionsReal-time sync and low latency
API CallsBillions monthlyHigh-throughput API gateway
Marketplace Apps5,000+Extensible platform architecture

Design Goals

When designing an Atlassian-like platform, the following goals must guide every architectural decision. First, the platform must be multi-tenant, supporting thousands of organizations on shared infrastructure while maintaining strict data isolation. Second, the system must be highly available, targeting 99.95% uptime or higher, because teams depend on these tools for their daily work and any downtime directly impacts productivity. Third, the platform must be extensible, providing robust APIs and frameworks that allow third-party developers to build integrations and add-ons. Fourth, the system must support real-time collaboration, enabling multiple users to work on the same content simultaneously without conflicts. Finally, the platform must provide enterprise-grade security, including encryption at rest and in transit, role-based access control, audit logging, and compliance with regulations like SOC 2, GDPR, and FedRAMP.

Each of these goals introduces significant engineering complexity. Multi-tenancy requires careful namespace isolation and resource allocation. High availability demands redundant infrastructure, graceful degradation, and sophisticated monitoring. Extensibility requires well-designed plugin architectures that sandbox third-party code. Real-time collaboration necessitates operational transformation or conflict-free replicated data types. Enterprise security requires defense-in-depth across every layer of the stack. Throughout this guide, we will examine how each of these challenges can be addressed through thoughtful system design.

Historical Context and Evolution

Understanding the evolution of Atlassian provides critical context for its current architecture. The company started in 2002 with Jira as a simple JSP-based issue tracker. Over the following two decades, Atlassian expanded its product portfolio through a combination of organic development and acquisitions. Confluence was added as a wiki-based knowledge management tool, Bitbucket was acquired to provide Git hosting, Trello was acquired for lightweight board-based project management, and Opsgenie was acquired for incident management. Each product brought its own architectural legacy, and the challenge of unifying these diverse systems into a coherent platform is one of the defining engineering problems Atlassian has solved.

The migration from Server to Data Center to Cloud represents another critical architectural transition. Atlassian's early products were designed as self-hosted applications that customers deployed on their own infrastructure. As the industry shifted toward cloud-native delivery, Atlassian invested heavily in rebuilding its products as multi-tenant cloud services. This transition required rethinking everything from data storage and tenant isolation to authentication and billing. Today, Atlassian Cloud is the primary deployment model, and the architecture we will design in this guide reflects cloud-native principles from the ground up.

2. Architecture Overview

The architecture of an Atlassian-like platform is organized as a collection of microservices, each responsible for a specific domain of functionality. These services communicate through a combination of synchronous REST APIs for request-response interactions, asynchronous message queues for event-driven workflows, and shared data stores for cross-service data access. The platform is deployed across multiple AWS regions to provide low-latency access to users worldwide and to support data residency requirements for enterprise customers.

At the highest level, the architecture consists of three layers: the presentation layer, which delivers web and mobile experiences to end users; the application layer, which contains the microservices implementing business logic for each product; and the data layer, which provides persistent storage, caching, and search capabilities. A shared platform services layer cuts across all three, providing authentication, authorization, billing, audit logging, and analytics capabilities that every product depends on.

graph TB Client[Web/Mobile Clients] --> Gateway[API Gateway] Gateway --> Auth[Identity Service] Gateway --> Jira[Jira Service] Gateway --> Confluence[Confluence Service] Gateway --> Bitbucket[Bitbucket Service] Gateway --> Trello[Trello Service] Gateway --> JSM[JSM Service] Jira --> DB[(PostgreSQL)] Confluence --> DB Bitbucket --> DB Trello --> DB JSM --> DB Jira --> Cache[(Redis Cache)] Confluence --> Cache Jira --> Search[(Elasticsearch)] Bitbucket --> ObjectStore[(S3 Object Storage)] Jira --> MQ[Message Queue - SQS] Confluence --> MQ Bitbucket --> MQ MQ --> Events[Event Processing] Events --> Analytics[Analytics Service] Events --> Automation[Automation Engine]

Cloud Platform Foundation

The cloud platform foundation provides the shared infrastructure and services that all Atlassian products build upon. This includes the identity and access management system, the tenant management system, the billing and subscription management system, and the observability platform. The identity service implements OpenID Connect and SAML for single sign-on, supports multi-factor authentication, and manages the complex permission model that spans organizations, sites, projects, and individual resources. The tenant management system tracks which customers are subscribed to which products, manages seat counts and licensing, and enforces resource quotas to ensure fair usage across the multi-tenant infrastructure.

The Connect and Forge frameworks provide the extensibility model that allows third-party developers to build integrations and add-ons. Connect uses a webhook-based architecture where the marketplace app registers callback URLs with the Atlassian platform, and the platform sends HTTP requests to those URLs when relevant events occur. Forge takes a more modern approach, allowing developers to deploy functions directly to Atlassian's cloud infrastructure, where they run in sandboxed environments with controlled access to platform APIs. Both frameworks must be designed with strict security boundaries to prevent marketplace apps from accessing data they are not authorized to see.

graph LR subgraph Platform Services IAM[Identity and Access Management] Billing[Billing and Subscriptions] Audit[Audit Logging] Analytics[Analytics and Insights] end subgraph Product Services Jira[Jira Software] Confluence[Confluence] Bitbucket[Bitbucket] Trello[Trello] JSM[Jira Service Management] end subgraph Data Layer PG[(PostgreSQL)] Redis[(Redis)] ES[(Elasticsearch)] S3[(S3)] Kafka[Kafka] end Jira --> IAM Confluence --> IAM Bitbucket --> IAM Trello --> IAM JSM --> IAM Jira --> Billing Jira --> Audit Confluence --> Audit Jira --> Kafka Confluence --> Kafka Bitbucket --> Kafka Kafka --> Analytics Jira --> PG Jira --> Redis Jira --> ES Bitbucket --> S3

Multi-Region Deployment

Atlassian Cloud operates across multiple AWS regions, including US East (N. Virginia), EU (Frankfurt), and AU (Sydney). Each region runs a complete copy of the platform stack, and customer data is pinned to a specific region based on their data residency preferences. This architecture requires careful handling of cross-region scenarios, such as when a user in the EU needs to access a project hosted in the US, or when a marketplace app needs to synchronize data across regions.

Within each region, the platform uses a cell-based architecture to provide isolation and fault tolerance. Each cell is an independently deployable unit that contains a full set of application services and its own data stores. Cells are assigned to specific customer segments, so a failure in one cell only affects the customers assigned to that cell, rather than the entire platform. This design pattern is critical for achieving the high availability targets that enterprise customers demand.

Architecture PatternPurposeImplementation
MicroservicesDomain isolation and independent deploymentKubernetes-based service mesh
Cell-Based ArchitectureFault isolation per customer segmentIndependent cell per customer cohort
CQRSSeparate read and write optimizationWrite model in PostgreSQL, read model in Elasticsearch
Event SourcingAudit trail and temporal queriesEvent store in DynamoDB with Kafka distribution
CQRS + Event SourcingRebuildable read modelsMaterialized views from event stream
Strangler FigIncremental migration from legacyAPI facade routing to old and new services

Service Communication Patterns

Services within the platform communicate using three primary patterns. Synchronous communication via gRPC is used for low-latency, request-response interactions between services that need immediate answers, such as when the Jira service needs to check permissions with the identity service before processing a request. Asynchronous communication via Apache Kafka is used for event-driven workflows where services need to react to changes in other domains, such as when a code commit in Bitbucket triggers a build pipeline or when a comment in Confluence sends a notification. Shared database reads via read replicas are used for scenarios where multiple services need access to the same data with strong consistency guarantees, such as cross-product search results.

The choice between these communication patterns has significant implications for system behavior. Synchronous calls create tight coupling between services and can cascade failures if a downstream service becomes unavailable. Asynchronous communication decouples services and provides natural buffering, but introduces eventual consistency. Shared databases simplify consistency but create scaling bottlenecks and increase coupling. The Atlassian architecture uses a deliberate mix of all three patterns, choosing the right one for each interaction based on its consistency, latency, and availability requirements.

3. Jira Software

Jira Software is the flagship product of the Atlassian suite and serves as the primary project management and issue tracking system for software development teams. At its core, Jira organizes work into projects, each of which contains a collection of issues (also called tickets or work items). Issues represent individual units of work � a feature to be built, a bug to be fixed, a task to be completed � and flow through configurable workflows that define the stages of the development process. Jira supports multiple project types including Scrum projects with sprint planning and Kanban projects with continuous flow.

The data model for Jira is centered around the concept of an issue, which is the fundamental entity that all operations act upon. An issue contains dozens of fields including summary, description, status, priority, assignee, reporter, labels, components, fix versions, and custom fields. The issue also has a changelog that records every field modification, providing a complete audit trail of how the issue has evolved over time. Issues are organized into projects, and projects define the available workflows, screens, and field configurations that govern how issues are created and modified.

classDiagram class Organization { +string orgId +string name +List~Site~ sites } class Site { +string siteId +string baseUrl +List~Project~ projects } class Project { +string projectId +string name +ProjectType type +Workflow workflow +List~Issue~ issues } class Issue { +string issueId +string key +IssueType issueType +string summary +Status status +User assignee +Priority priority } class Workflow { +string workflowId +List~Status~ statuses +List~Transition~ transitions } class Status { +string statusId +string name +StatusCategory category } class Transition { +string transitionId +string name +Status fromStatus +Status toStatus } Organization --> Site Site --> Project Project --> Issue Project --> Workflow Workflow --> Status Workflow --> Transition

Board Views and Visualization

Jira boards provide visual representations of work in progress. Kanban boards display issues as cards organized into columns that represent workflow statuses, allowing teams to see at a glance what is in progress, what is blocked, and what is ready for review. Scrum boards add the concept of sprints, showing which issues are planned for the current sprint and tracking progress against the sprint goal. Both board types support swimlanes for grouping issues by assignee, epic, or custom criteria, and support WIP (Work in Progress) limits to prevent overloading team members.

The board rendering pipeline is one of the most performance-sensitive components of Jira. When a user opens a board, the system must query all issues in the project, apply the board filter criteria, group them by status column, and render them as interactive cards. For projects with thousands of issues, this query must complete in under two seconds. The system achieves this through a combination of database indexing, Elasticsearch for complex queries, Redis caching for frequently accessed board states, and incremental rendering on the client side that shows the board structure immediately while loading card details progressively.

Workflow Engine

The Jira workflow engine is a state machine that controls how issues transition between statuses. Each workflow consists of a set of statuses (representing stages in the process), transitions (representing actions that move issues between statuses), and validators (conditions that must be met for a transition to be allowed). Workflows can be configured per project or shared across projects, and can include validators that check user permissions, require field values, or call external webhooks. The workflow engine is one of the most complex parts of Jira because it must support highly customizable configurations while maintaining performance and data integrity.

C#
public class JiraWorkflowEngine
{
    private readonly IWorkflowRepository _workflowRepo;
    private readonly ITransitionValidator _validator;
    private readonly IAuditLogger _auditLogger;
    private readonly IEventPublisher _eventPublisher;

    public JiraWorkflowEngine(
        IWorkflowRepository workflowRepo,
        ITransitionValidator validator,
        IAuditLogger auditLogger,
        IEventPublisher eventPublisher)
    {
        _workflowRepo = workflowRepo;
        _validator = validator;
        _auditLogger = auditLogger;
        _eventPublisher = eventPublisher;
    }

    public async Task<TransitionResult> ExecuteTransitionAsync(
        string issueId, string transitionId,
        User performedBy, Dictionary<string, object> fields)
    {
        var issue = await GetIssueWithLockAsync(issueId);
        var workflow = await _workflowRepo.GetWorkflowAsync(issue.ProjectId);

        var transition = workflow.GetTransition(transitionId);
        if (transition == null)
            return TransitionResult.Failure("Transition not found");

        if (transition.SourceStatus != issue.Status)
            return TransitionResult.Failure("Issue not in expected status");

        var validationContext = new ValidationContext
        {
            Issue = issue,
            Transition = transition,
            User = performedBy,
            Fields = fields
        };

        var validationResult = await _validator.ValidateAsync(validationContext);
        if (!validationResult.IsValid)
            return TransitionResult.Failure(validationResult.ErrorMessage);

        var previousStatus = issue.Status;
        issue.Status = transition.TargetStatus;

        foreach (var field in fields)
            issue.SetFieldValue(field.Key, field.Value);

        issue.Changelog.AddEntry(performedBy, previousStatus, transition.TargetStatus);

        await SaveIssueAsync(issue);

        await _auditLogger.LogTransitionAsync(new AuditEntry
        {
            IssueId = issueId,
            PerformedBy = performedBy.Id,
            Transition = transitionId,
            FromStatus = previousStatus,
            ToStatus = transition.TargetStatus,
            Timestamp = DateTime.UtcNow
        });

        await _eventPublisher.PublishAsync(new IssueTransitionedEvent
        {
            IssueId = issueId,
            FromStatus = previousStatus,
            ToStatus = transition.TargetStatus,
            PerformedBy = performedBy.Id,
            Timestamp = DateTime.UtcNow
        });

        return TransitionResult.Success(transition.TargetStatus);
    }

    private async Task<Issue> GetIssueWithLockAsync(string issueId)
    {
        return await _context.Issues
            .Include(i => i.Changelog)
            .FirstOrDefaultAsync(i => i.Id == issueId);
    }

    private async Task SaveIssueAsync(Issue issue)
    {
        _context.Issues.Update(issue);
        await _context.SaveChangesAsync();
    }
}

Automation Rules

Jira automation engine allows users to create rules that automatically perform actions when specific triggers occur. Common automation rules include automatically assigning issues to the team lead when they are moved to a particular status, sending Slack notifications when high-priority bugs are created, transitioning parent issues to Done when all child issues are completed, and automatically transitioning issues when related pull requests are merged. The automation engine evaluates rules in real-time by listening to the event stream and executing the action defined in matching rules.

The automation system must be designed to handle millions of rules across all tenants without introducing significant latency to the core issue processing pipeline. This is achieved through a rule indexing system that maintains a lookup table mapping trigger types to the rules that listen for them, so only relevant rules are evaluated for each event. Rules are also rate-limited per tenant to prevent runaway automations from consuming excessive resources, and all rule executions are logged for audit and debugging purposes.

Automation ComponentDescriptionExample
TriggerEvent that initiates rule evaluationIssue created, status changed, sprint started
ConditionFilter that narrows when the rule runsIssue type is Bug, priority is High
ActionOperation performed when conditions passAssign issue, send notification, transition issue
BranchConditional logic for complex workflowsIf-else, for-each on related issues
RuleComplete automation unit combining all componentsWhen Bug created with High priority, assign to on-call
Audit LogRecord of rule execution for debuggingRule executed at 2:30 PM, action completed successfully

JQL and Search

Jira Query Language (JQL) is a powerful domain-specific language that allows users to search for issues using structured queries. JQL supports complex conditions, logical operators, function calls, and ordering. For example, the query project = MYAPP AND status = In Progress AND assignee = currentUser() ORDER BY priority DESC finds all issues in the MYAPP project that are currently in progress and assigned to the logged-in user, sorted by priority. The JQL parser must handle the full grammar of the language, optimize queries for efficient execution, and support both Elasticsearch-backed full-text search and PostgreSQL-backed structured search.

Under the hood, JQL queries are first parsed into an abstract syntax tree (AST) and then translated into either Elasticsearch DSL queries or PostgreSQL SQL queries, depending on the nature of the query. Simple field comparisons are translated to SQL for exact matching, while full-text search and complex aggregation queries are sent to Elasticsearch. The query planner must handle this routing transparently, and must also enforce tenant isolation by injecting the appropriate project and organization filters into every query to prevent cross-tenant data leakage.

4. Confluence

Confluence is Atlassian's knowledge management and documentation platform, designed to help teams create, organize, and share project documentation, meeting notes, product requirements, and technical specifications. Content in Confluence is organized into spaces, which serve as top-level containers for related pages. Each space can have its own permissions, templates, and homepage. Within spaces, pages are organized in a hierarchical tree structure, with child pages nested under parent pages to create a navigable documentation tree.

Confluence pages are authored using a rich text editor that supports formatting, images, tables, macros, and embedded content from other Atlassian products. The editor stores content in a structured document format called ADF (Atlassian Document Format), which is a JSON-based representation that separates the document structure from its presentation. This allows the same content to be rendered differently depending on context � as a full-page view in the browser, as a snippet in a search result, or as a notification preview in an email.

graph TB Org[Organization] --> Space1[Engineering Space] Org --> Space2[Product Space] Org --> Space3[HR Space] Space1 --> Page1[Architecture Docs] Space1 --> Page2[Runbooks] Space1 --> Page3[Meeting Notes] Page1 --> Child1[System Design] Page1 --> Child2[API Reference] Page1 --> Child3[Database Schema] Page2 --> Child4[Incident Response] Page2 --> Child5[Deployment Guide] Page3 --> Child6[Sprint Retro] Page3 --> Child7[Planning Meeting]

Real-Time Collaboration

One of the most technically challenging features of Confluence is real-time collaborative editing, which allows multiple users to edit the same page simultaneously and see each other changes in real-time. This is implemented using Operational Transformation (OT), a technique that allows concurrent edits to be merged automatically without conflicts. When a user makes an edit, the change is represented as a set of operations (insert, delete, format) that are sent to the server. The server transforms the operation against any concurrent operations that have been received from other users, ensuring that all clients converge to the same document state regardless of the order in which operations arrive.

The OT algorithm must handle a wide range of editing scenarios, including simultaneous insertions at the same position, overlapping deletions, formatting changes that span multiple paragraphs, and macro-level operations that affect entire sections of the document. The server maintains a version vector for each document that tracks the state of each client's view, and uses this vector to determine how to transform incoming operations. When a new user opens a page, they receive the current document state along with the version vector, and subsequent operations are transformed against all operations that occurred after that version.

Content Storage and Retrieval

Confluence stores page content in a document database optimized for hierarchical data. Each page is stored as a separate document that contains the ADF representation of the content, metadata about the page (title, space, parent, author, timestamps), and an attachment list. Page content is versioned, with each edit creating a new version that is stored alongside the previous version. This enables features like page history, comparison between versions, and rollback to previous states.

C#
public class ConfluencePageService
{
    private readonly IPageRepository _pageRepo;
    private readonly ISpaceRepository _spaceRepo;
    private readonly IPermissionService _permissions;
    private readonly ISearchIndexer _searchIndexer;
    private readonly ICollaborationEngine _collabEngine;

    public async Task<PageResult> GetPageAsync(
        string pageId, User currentUser)
    {
        var page = await _pageRepo.GetPageAsync(pageId);
        if (page == null)
            return PageResult.NotFound();

        var hasAccess = await _permissions.CheckPageAccessAsync(
            currentUser, page, PermissionType.Read);
        if (!hasAccess)
            return PageResult.Forbidden();

        var versions = await _pageRepo.GetPageVersionsAsync(pageId);
        var attachments = await _pageRepo.GetAttachmentsAsync(pageId);
        var comments = await _pageRepo.GetCommentsAsync(pageId);
        var activeEditors = await _collabEngine
            .GetActiveEditorsAsync(pageId);

        return PageResult.Success(new PageView
        {
            Page = page,
            Content = await RenderAdfContentAsync(page.Content),
            Versions = versions,
            Attachments = attachments,
            Comments = comments,
            ActiveEditors = activeEditors,
            Breadcrumbs = await BuildBreadcrumbsAsync(page)
        });
    }

    public async Task<EditResult> SavePageAsync(
        string pageId, AdfDocument content,
        string versionMessage, User currentUser)
    {
        var page = await _pageRepo.GetPageAsync(pageId);
        if (page == null)
            return EditResult.NotFound();

        var canEdit = await _permissions.CheckPageAccessAsync(
            currentUser, page, PermissionType.Write);
        if (!canEdit)
            return EditResult.Forbidden();

        var newVersion = new PageVersion
        {
            VersionNumber = page.CurrentVersion + 1,
            Content = content,
            Author = currentUser.Id,
            Message = versionMessage,
            CreatedAt = DateTime.UtcNow
        };

        page.Content = content;
        page.CurrentVersion = newVersion.VersionNumber;
        page.LastModifiedBy = currentUser.Id;
        page.LastModifiedAt = DateTime.UtcNow;

        await _pageRepo.SaveVersionAsync(pageId, newVersion);
        await _pageRepo.UpdatePageAsync(page);
        await _searchIndexer.IndexPageAsync(page);

        return EditResult.Success(newVersion.VersionNumber);
    }

    private async Task<string> RenderAdfContentAsync(AdfDocument content)
    {
        var renderer = new AdfHtmlRenderer();
        return await renderer.RenderAsync(content);
    }

    private async Task<List<Breadcrumb>> BuildBreadcrumbsAsync(Page page)
    {
        var breadcrumbs = new List<Breadcrumb>();
        var current = page;
        while (current != null)
        {
            breadcrumbs.Insert(0, new Breadcrumb
            {
                PageId = current.Id,
                Title = current.Title
            });
            current = current.ParentId != null
                ? await _pageRepo.GetPageAsync(current.ParentId)
                : null;
        }
        return breadcrumbs;
    }
}

Templates and Blueprints

Confluence provides templates and blueprints to help users create consistent content without starting from scratch. Templates are pre-defined page layouts that include placeholder fields for common content types like meeting notes, project plans, and decision logs. Blueprints are more advanced templates that can include conditional sections, auto-populated fields, and integration with other Atlassian products. For example, the Service Retrospective blueprint automatically pulls in incident data from Jira Service Management and displays it in the page template.

The template system is designed to be extensible, allowing marketplace developers to create and publish custom templates through the Atlassian Marketplace. Custom templates are stored in the same document format as regular pages and are evaluated at page creation time to produce the initial content. The template engine must handle complex expressions, loops, and conditional logic while maintaining security by preventing template injection attacks.

FeatureImplementationScale Consideration
Page RenderingServer-side ADF to HTML conversionCDN caching, lazy loading of macros
Full-Text SearchElasticsearch with custom analyzersIndex sharding by space, incremental updates
AttachmentsS3 with CloudFront CDNMultipart upload for large files, virus scanning
Page TreeMaterialized path in PostgreSQLDenormalized tree for fast subtree queries
Collaborative EditingOperational Transformation via WebSocketConnection pooling, operation batching
Likes and ReactionsCounter table with Redis cacheWrite-through cache, eventual consistency

Spaces and Permissions

Confluence spaces provide organizational boundaries for content, with each space having its own set of pages, blog posts, attachments, and permissions. Space permissions are managed at the space level and can be granted to individual users, groups, or the entire organization. Page-level permissions can further restrict access within a space, allowing sensitive pages within an otherwise open space to be visible only to authorized users.

The permission model must be evaluated efficiently because every page load, search result, and API call requires an access check. The system uses a hierarchical permission model where space permissions are inherited by all pages within the space, but individual pages can override these inherited permissions with more restrictive settings. Permission checks are cached in Redis with a short TTL to balance security with performance, and cache invalidation is triggered whenever permissions are modified.

5. Jira Service Management

Jira Service Management (JSM) extends Jira's issue tracking capabilities into the domain of IT Service Management (ITSM), providing tools for incident management, problem management, change management, asset management, and service request fulfillment. JSM implements the ITIL (Information Technology Infrastructure Library) framework, which defines best practices for aligning IT services with business needs. The product serves both internal IT teams (help desk) and external customer support teams (customer portal), providing tailored experiences for each audience.

The core data model of JSM builds on Jira's issue model by introducing the concept of a service project, which includes additional entities like requests (customer-initiated tickets), queues (saved filters for agent workflows), SLAs (service level agreements that track response and resolution times), and customer portals (branded interfaces for external users to submit requests). Requests in JSM have a customer-facing view that hides internal details and an agent-facing view that shows the full context needed for resolution.

SLA Management

SLA management is one of the most critical features of JSM. An SLA defines a target for how quickly a request must be acknowledged (response time) and resolved (resolution time), based on criteria like the request priority, issue type, and service desk configuration. The SLA engine must track the elapsed time for each request against its target, pause the clock during non-business hours (based on configured calendars), and alert agents when requests are approaching their SLA deadlines. SLA breaches must be recorded for reporting and compliance purposes.

sequenceDiagram participant Customer participant Portal as Customer Portal participant JSM as JSM Service participant SLA as SLA Engine participant Agent participant Notify as Notification Service Customer->>Portal: Submit request Portal->>JSM: Create request JSM->>SLA: Start SLA timer SLA->>SLA: Calculate deadline based on priority JSM->>Notify: Send acknowledgment Notify->>Customer: Email confirmation JSM->>Agent: Assign to queue Agent->>JSM: Acknowledge request JSM->>SLA: Pause response timer Agent->>JSM: Investigate and resolve JSM->>SLA: Stop resolution timer SLA->>SLA: Record SLA metrics JSM->>Notify: Send resolution notice Notify->>Customer: Resolution email

Queues and Agent Workflow

Queues in JSM are saved filter configurations that organize requests into actionable worklists for agents. Unlike Jira boards, which are organized around project workflows, queues are organized around service management priorities � for example, High Priority Unassigned, Waiting for Customer, SLA Breach Risk, or My Assigned Requests. Agents work through queues in priority order, picking up the most urgent requests first. The queue system must update in real-time as requests are created, modified, or reassigned, so agents always see the current state of their workload.

C#
public class SlaManager
{
    private readonly ISlaRepository _slaRepo;
    private readonly ICalendarService _calendarService;
    private readonly ITimeProvider _timeProvider;
    private readonly INotificationService _notifications;

    public async Task<SlaStatus> EvaluateSlaAsync(Request request)
    {
        var slaConfig = await _slaRepo.GetSlaConfigAsync(
            request.ServiceDeskId,
            request.IssueType,
            request.Priority);

        if (slaConfig == null)
            return SlaStatus.NotApplicable();

        var startTime = request.CreatedAt;
        var currentTime = _timeProvider.UtcNow;

        var responseDeadline = await _calendarService.AddBusinessTimeAsync(
            startTime, slaConfig.ResponseTime);
        var resolutionDeadline = await _calendarService.AddBusinessTimeAsync(
            startTime, slaConfig.ResolutionTime);

        var responseElapsed = await _calendarService.GetBusinessTimeAsync(
            startTime, currentTime);
        var resolutionElapsed = responseElapsed;

        var responseRemaining = await _calendarService.GetBusinessTimeAsync(
            currentTime, responseDeadline);
        var resolutionRemaining = await _calendarService.GetBusinessTimeAsync(
            currentTime, resolutionDeadline);

        var responseStatus = CalculateSlaStatus(
            responseElapsed, slaConfig.ResponseTime, responseRemaining);
        var resolutionStatus = CalculateSlaStatus(
            resolutionElapsed, slaConfig.ResolutionTime, resolutionRemaining);

        if (responseStatus == SlaAlert.Breach ||
            resolutionStatus == SlaAlert.Breach)
        {
            await _notifications.SendSlaBreachAlertAsync(
                request, responseStatus, resolutionStatus);
        }
        else if (responseStatus == SlaAlert.Warning ||
                 resolutionStatus == SlaAlert.Warning)
        {
            await _notifications.SendSlaWarningAsync(
                request, responseStatus, resolutionStatus);
        }

        return new SlaStatus
        {
            ResponseGoal = slaConfig.ResponseTime,
            ResponseElapsed = responseElapsed,
            ResponseRemaining = responseRemaining,
            ResponseStatus = responseStatus,
            ResolutionGoal = slaConfig.ResolutionTime,
            ResolutionElapsed = resolutionElapsed,
            ResolutionRemaining = resolutionRemaining,
            ResolutionStatus = resolutionStatus
        };
    }

    private SlaAlert CalculateSlaStatus(
        TimeSpan elapsed, TimeSpan goal, TimeSpan remaining)
    {
        var percentComplete = elapsed.TotalSeconds / goal.TotalSeconds;
        if (remaining <= TimeSpan.Zero)
            return SlaAlert.Breach;
        if (percentComplete >= 0.8)
            return SlaAlert.Warning;
        return SlaAlert.OnTrack;
    }
}

Customer Portal

The customer portal provides a branded, simplified interface for external users to submit and track service requests. Unlike the agent interface, which shows the full Jira issue view with all fields and operations, the customer portal shows only the fields and actions that are relevant to the customer. Service desk administrators configure which request types are visible in the portal, what fields customers must fill out, and what auto-responses are sent when requests are created. The portal supports multiple languages and can be themed with the organization branding.

JSM FeatureAgent ExperienceCustomer Experience
Request ViewFull Jira issue with internal fieldsSimplified form with customer-relevant fields only
CommentsInternal notes + customer-facing commentsOnly customer-facing comments visible
WorkflowsFull workflow with all transitionsLimited transitions (e.g., Add comment, Close)
SLA DisplayDetailed SLA timers and breach alertsGeneral response time estimate only
AssignmentManual or automatic assignment rulesNot visible to customers
ReportingDashboards with SLA, volume, agent metricsPersonal request history only

Change Management and CAB

JSM change management module implements the ITIL change management process, which controls how changes to IT infrastructure are proposed, reviewed, approved, and implemented. Changes are categorized as standard, normal, or emergency, each with different approval workflows. Normal changes require approval from a Change Advisory Board (CAB) before implementation, while standard changes follow pre-approved procedures and emergency changes follow expedited approval. The change management module integrates with the CMDB (Configuration Management Database) to track the impact of changes on IT assets and services.

The CMDB is a critical component that maps the relationships between IT assets, services, and the people who manage them. When a change is proposed, the CMDB is consulted to identify which assets will be affected and what services depend on those assets. This information helps the CAB assess the risk of the change and make informed approval decisions. The CMDB is maintained through automated discovery scans, manual entry, and integration with external asset management systems.

6. Bitbucket

Bitbucket is Atlassian's Git-based code hosting platform, providing repositories, branch management, pull requests, and integrated CI/CD through Bitbucket Pipelines. Bitbucket supports both Git and Mercurial repositories (though Mercurial support was deprecated in 2020) and offers features designed specifically for professional software development teams, including branch permissions, code reviews, merge checks, and integration with Jira for traceability between code changes and work items.

The repository management system must handle the fundamental operations of Git � clone, fetch, push, pull � at scale, serving millions of repositories across hundreds of thousands of organizations. The backend stores Git repository data using a combination of object storage (S3) for pack files and a metadata database (PostgreSQL) for repository information, branch references, and access control lists. The Git protocol is handled by a specialized service that manages connections, authentication, and data transfer, while the web interface provides a higher-level view of repositories, branches, commits, and pull requests.

graph TB Developer[Developer] --> GitProtocol[Git Protocol Service] GitProtocol --> RepoService[Repository Service] RepoService --> GitStorage[(S3 - Git Objects)] RepoService --> MetaDB[(PostgreSQL - Metadata)] Developer --> WebUI[Web Interface] WebUI --> PullRequest[Pull Request Service] WebUI --> CodeReview[Code Review Service] PullRequest --> MergeEngine[Merge Engine] MergeEngine --> CI[Bitbucket Pipelines] CI --> BuildService[Build Service] BuildService --> Artifacts[(S3 - Artifacts)] CI --> DeployService[Deployment Service] PullRequest --> Jira[Jira Integration] CI --> Jira

Pull Request Workflow

Pull requests are the primary mechanism for code review in Bitbucket. A pull request represents a proposed set of changes from a source branch to a target branch, and provides a forum for team members to review the code, discuss the changes, and approve or request modifications before the changes are merged. Pull requests track the diff between the source and target branches, display inline comments on specific lines of code, and maintain a conversation thread where reviewers and authors can discuss the changes.

The pull request lifecycle includes several stages. First, the author creates a pull request by selecting the source and target branches and providing a description of the changes. Next, reviewers are assigned and begin reviewing the code, adding inline comments and general feedback. The author responds to feedback by pushing additional commits to the source branch, which automatically update the pull request diff. When all reviewers have approved the pull request and all merge checks have passed (such as passing CI builds and no unresolved conversations), the pull request can be merged using one of several merge strategies: merge commit, squash, or fast-forward.

Bitbucket Pipelines

Bitbucket Pipelines is an integrated CI/CD service that runs builds, tests, and deployments directly within Bitbucket. Pipelines are configured using a YAML file (bitbucket-pipelines.yml) in the repository root, which defines the pipeline stages, steps, and execution conditions. Each step runs in a Docker container, and steps within a stage can run in parallel for faster execution. Pipelines can be triggered automatically on push, on pull request, on a schedule, or manually.

C#
public class PipelineExecutor
{
    private readonly IPipelineRepository _pipelineRepo;
    private readonly IContainerService _containerService;
    private readonly IArtifactService _artifactService;
    private readonly IWebhookService _webhookService;
    private readonly ILogger<PipelineExecutor> _logger;

    public async Task<PipelineResult> ExecutePipelineAsync(
        string repositoryId, PipelineConfig config,
        PipelineTrigger trigger)
    {
        var execution = new PipelineExecution
        {
            Id = Guid.NewGuid().ToString(),
            RepositoryId = repositoryId,
            Config = config,
            Trigger = trigger,
            Status = PipelineStatus.Running,
            StartedAt = DateTime.UtcNow
        };

        await _pipelineRepo.SaveExecutionAsync(execution);

        try
        {
            foreach (var stage in config.Stages)
            {
                _logger.LogInformation(
                    "Executing stage: {StageName}", stage.Name);

                var stageResult = await ExecuteStageAsync(
                    execution.Id, stage);

                execution.StageResults.Add(stageResult);

                if (stageResult.Status == StepStatus.Failed &&
                    stage.FailFast)
                {
                    execution.Status = PipelineStatus.Failed;
                    break;
                }
            }

            if (execution.Status == PipelineStatus.Running)
                execution.Status = PipelineStatus.Success;
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Pipeline execution failed");
            execution.Status = PipelineStatus.Error;
            execution.ErrorMessage = ex.Message;
        }

        execution.CompletedAt = DateTime.UtcNow;
        await _pipelineRepo.SaveExecutionAsync(execution);
        await _webhookService.SendPipelineCompletedAsync(execution);

        return new PipelineResult
        {
            ExecutionId = execution.Id,
            Status = execution.Status,
            Duration = execution.CompletedAt - execution.StartedAt
        };
    }

    private async Task<StageResult> ExecuteStageAsync(
        string executionId, PipelineStage stage)
    {
        var stageResult = new StageResult { Name = stage.Name };
        var tasks = stage.Steps.Select(step =>
            ExecuteStepAsync(executionId, step));
        var results = await Task.WhenAll(tasks);

        stageResult.StepResults = results.ToList();
        stageResult.Status = results.All(r =>
            r.Status == StepStatus.Success)
            ? StepStatus.Success
            : StepStatus.Failed;

        return stageResult;
    }

    private async Task<StepResult> ExecuteStepAsync(
        string executionId, PipelineStep step)
    {
        var container = await _containerService.CreateContainerAsync(
            new ContainerRequest
            {
                Image = step.Image,
                Commands = step.Script,
                Environment = step.EnvVariables,
                MaxMemory = "2GB",
                Timeout = TimeSpan.FromMinutes(step.MaxMinutes)
            });

        var logs = await _containerService.RunAsync(container.Id);
        var artifacts = await _artifactService.CollectArtifactsAsync(
            container.Id, step.ArtifactPaths);

        return new StepResult
        {
            StepName = step.Name,
            Status = container.ExitCode == 0
                ? StepStatus.Success
                : StepStatus.Failed,
            Logs = logs,
            Artifacts = artifacts
        };
    }
}

Branch Permissions and Merge Checks

Branch permissions in Bitbucket define who can push to specific branches and who can merge pull requests into those branches. Common branch permission configurations include requiring pull request reviews before merging into the main branch, preventing direct pushes to release branches, and restricting branch deletion to administrators. Merge checks are additional conditions that must be satisfied before a pull request can be merged, such as requiring passing CI builds, minimum number of approving reviewers, and no unresolved conversations.

Bitbucket FeatureImplementationKey Considerations
Git HostingS3 for pack files, PostgreSQL for metadataRepository size limits, pack file optimization
Pull RequestsDiff engine + comment system + merge strategiesLarge diff handling, inline commenting accuracy
PipelinesDocker containers on shared build infrastructureBuild isolation, resource quotas, parallel execution
Code SearchElasticsearch index of repository contentIndex freshness, cross-repository search
Branch PermissionsRule engine evaluated on push and mergePerformance on high-velocity repositories
LFS (Large File Storage)S3-backed with Git LFS protocolBandwidth management, storage quotas

7. Trello

Trello is a lightweight, visual project management tool based on the Kanban methodology. Unlike Jira, which provides highly customizable workflows and extensive configuration options, Trello focuses on simplicity and ease of use. The core abstraction in Trello is a board, which contains lists (columns) that hold cards (individual work items). Users create boards for projects, add lists to represent stages of work, and create cards to represent tasks. Cards are dragged between lists as work progresses, providing a visual representation of the project's status.

Trello's architecture is designed for real-time responsiveness, as the board view is the primary interface and users expect immediate feedback when they drag cards between lists, add comments, or update card details. The backend uses a WebSocket-based push system to synchronize board state across all connected clients in real-time. When a user moves a card, the change is optimistically applied on the client side and sent to the server, which broadcasts the change to all other clients viewing the same board. This approach provides a responsive, collaborative experience even with high latency connections.

graph TB User[User] --> Board[Trello Board] Board --> List1[To Do] Board --> List2[In Progress] Board --> List3[Done] List1 --> Card1[Task A] List1 --> Card2[Task B] List2 --> Card3[Task C] List3 --> Card4[Task D] Card1 --> Checklist[Checklist] Card1 --> DueDate[Due Date] Card1 --> Members[Members] Card1 --> Labels[Labels] Card1 --> Attachments[Attachments] Card1 --> Comments[Comments]

Power-Ups and Butler

Power-Ups are Trello's extension mechanism, allowing third-party developers and Atlassian itself to add functionality to boards. Power-Ups can add buttons to cards, display additional information on cards, integrate with external services, and modify the board interface. Common Power-Ups include calendar views, voting, custom fields, and integrations with tools like GitHub, Slack, and Google Drive. Power-Ups are configured per board, and each board can have multiple Power-Ups active simultaneously.

Butler is Trello's built-in automation tool, which allows users to create rules that automatically perform actions on cards based on triggers. Butler rules can be created through a natural language interface � for example, When a card is moved to Done, mark the due date as complete and remove all members. Butler also supports more complex automations involving multiple conditions, scheduled actions (such as creating a card every Monday), and board-level operations (such as archiving all cards in a list when a button is clicked).

C#
public class ButlerAutomationEngine
{
    private readonly IRuleRepository _ruleRepo;
    private readonly ICardRepository _cardRepo;
    private readonly IBoardRepository _boardRepo;
    private readonly IWebhookDispatcher _webhookDispatcher;

    public async Task<ButlerResult> ProcessTriggerAsync(
        ButlerTrigger trigger, Board board)
    {
        var matchingRules = await _ruleRepo.GetRulesByTriggerAsync(
            board.Id, trigger.Type);

        var results = new List<RuleExecutionResult>();

        foreach (var rule in matchingRules)
        {
            if (!EvaluateConditions(rule.Conditions, trigger.Context))
                continue;

            var execution = new RuleExecution
            {
                RuleId = rule.Id,
                Trigger = trigger,
                ExecutedAt = DateTime.UtcNow
            };

            try
            {
                foreach (var action in rule.Actions)
                {
                    await ExecuteActionAsync(action, trigger.Context, board);
                }
                execution.Status = ExecutionStatus.Success;
            }
            catch (Exception ex)
            {
                execution.Status = ExecutionStatus.Failed;
                execution.ErrorMessage = ex.Message;
            }

            results.Add(new RuleExecutionResult
            {
                RuleId = rule.Id,
                RuleName = rule.Name,
                Status = execution.Status
            });

            await _ruleRepo.LogExecutionAsync(execution);
        }

        return new ButlerResult { Executions = results };
    }

    private bool EvaluateConditions(
        List<ButlerCondition> conditions, TriggerContext context)
    {
        return conditions.All(condition => condition.Type switch
        {
            ConditionType.CardHasLabel =>
                context.Card.Labels.Contains(condition.ExpectedValue),
            ConditionType.CardIsAssigned =>
                context.Card.Members.Any(),
            ConditionType.ListNameEquals =>
                context.List.Name == condition.ExpectedValue,
            ConditionType.DueDateWithin =>
                IsDueDateWithin(context.Card.DueDate,
                    TimeSpan.Parse(condition.ExpectedValue)),
            ConditionType.FieldMatches =>
                context.Card.CustomFields
                    .Any(f => f.Name == condition.FieldName &&
                        f.Value == condition.ExpectedValue),
            _ => true
        });
    }

    private async Task ExecuteActionAsync(
        ButlerAction action, TriggerContext context, Board board)
    {
        switch (action.Type)
        {
            case ActionType.MoveCard:
                var targetList = await _boardRepo.GetListAsync(
                    action.Parameters["listId"]);
                await _cardRepo.MoveCardAsync(
                    context.Card.Id, targetList.Id);
                break;
            case ActionType.AddMember:
                var memberId = action.Parameters["memberId"];
                await _cardRepo.AddMemberAsync(
                    context.Card.Id, memberId);
                break;
            case ActionType.SetDueDate:
                var dueDate = DateTime.Parse(
                    action.Parameters["dueDate"]);
                await _cardRepo.SetDueDateAsync(
                    context.Card.Id, dueDate);
                break;
            case ActionType.ArchiveCard:
                await _cardRepo.ArchiveCardAsync(context.Card.Id);
                break;
            case ActionType.CreateCard:
                var newCard = new Card
                {
                    Name = action.Parameters["name"],
                    ListId = context.List.Id,
                    BoardId = board.Id
                };
                await _cardRepo.CreateCardAsync(newCard);
                break;
        }

        await _webhookDispatcher.DispatchAsync(
            new ButlerActionPerformedEvent
        {
            ActionType = action.Type,
            CardId = context.Card.Id,
            Parameters = action.Parameters
        });
    }

    private bool IsDueDateWithin(DateTime? dueDate, TimeSpan window)
    {
        if (!dueDate.HasValue) return false;
        var remaining = dueDate.Value - DateTime.UtcNow;
        return remaining >= TimeSpan.Zero && remaining <= window;
    }
}

Real-Time Board Synchronization

Trello's real-time board synchronization is implemented using a combination of WebSockets for push notifications and optimistic updates for immediate user feedback. When a user performs an action on a board (moving a card, adding a comment, updating a label), the client immediately applies the change to its local state and sends the action to the server via WebSocket. The server validates the action, persists it to the database, and broadcasts the change to all other clients viewing the same board. Clients receive the broadcast and apply the change to their local state, keeping all views in sync.

This architecture must handle several edge cases, including concurrent modifications to the same card (which are resolved using last-writer-wins with conflict detection), network partitions (which queue changes locally and sync when the connection is restored), and reconnection after disconnection (which triggers a full board refresh to ensure consistency). The WebSocket connections are managed by a dedicated connection service that maintains a mapping of board IDs to connected client sessions, allowing efficient broadcast without scanning all connections.

Trello ConceptData ModelScale Consideration
BoardTop-level container with settings and membersBoard size limits (lists x cards), archival strategy
ListOrdered column within a boardSort order management, max lists per board
CardIndividual work item with metadataCard count limits, attachment storage
Power-UpThird-party extension registered per boardSandboxed execution, API rate limits
Butler RuleAutomation rule with trigger-condition-actionRule evaluation performance, execution queue
WebhookHTTP callback for external integrationsDelivery guarantees, retry logic, fan-out

8. Marketplace

The Atlassian Marketplace is a platform ecosystem that enables third-party developers to build, distribute, and sell integrations and add-ons for Atlassian products. With over 5,000 apps available, the Marketplace generates significant revenue for both Atlassian and its partner ecosystem. Apps on the Marketplace range from simple UI customizations to complex enterprise integrations, and they address use cases across every industry and team size. The Marketplace platform must support the complete lifecycle of an app, from development and testing through publication, installation, licensing, and ongoing updates.

The Marketplace supports two primary app frameworks: Connect and Forge. Connect apps are hosted by the developer and communicate with Atlassian products through webhooks and REST APIs. This model gives developers full control over their app's infrastructure but requires them to manage hosting, security, and scalability. Forge apps are hosted by Atlassian, with developers providing functions that run in a serverless environment. This model simplifies deployment and scaling for developers but limits the types of functionality they can implement. The Marketplace also supports Data Center apps for on-premises deployments, though this model is being phased out in favor of cloud-native solutions.

graph LR subgraph Connect Model DevServer[Developer Server] -->|Webhooks| Platform[Atlassian Platform] Platform -->|REST API| DevServer end subgraph Forge Model ForgeFunc[Forge Function] -->|Runs on| AtlassianInfra[Atlassian Infrastructure] AtlassianInfra -->|Events| ForgeFunc end subgraph Marketplace AppListing[App Listings] LicenseService[License Service] InstallService[Installation Service] BillingService[Billing Service] end Platform --> InstallService AtlassianInfra --> InstallService InstallService --> LicenseService LicenseService --> BillingService

App Installation and Lifecycle

When a customer installs a Marketplace app, the installation process must coordinate between the Marketplace service, the Atlassian product, and (for Connect apps) the developer's server. The process begins with the customer clicking Install on the app listing page. The Marketplace validates the customer's license and generates an installation descriptor that contains the app's configuration, including its base URL, webhook URLs, and required permissions. This descriptor is sent to the Atlassian product, which stores it and begins sending webhooks to the app's registered URLs.

The installation process must handle several important scenarios, including installing an app across multiple products (for example, a Jira app that also integrates with Confluence), upgrading an app to a new version while preserving the app's data, and uninstalling an app cleanly by removing all webhooks, data, and configuration. The Marketplace also manages licensing, tracking which customers have active licenses for which apps, and enforcing seat limits based on the number of users in the customer's Atlassian organization.

Monetization and Pricing

The Marketplace supports several monetization models for app developers. Apps can be offered free, with revenue generated through consulting or premium support. Apps can use flat-rate pricing, where customers pay a fixed price per billing period regardless of their user count. Apps can use per-user pricing, where the cost scales with the number of Atlassian users in the customer's organization. Apps can also offer tiered pricing, with different price points for different user count ranges. The Marketplace handles all billing, payment processing, and revenue sharing with developers, taking a percentage of each transaction.

Marketplace FeatureConnect AppsForge Apps
HostingDeveloper-managed infrastructureAtlassian-managed serverless
CommunicationWebhooks + REST APIEvents + Forge API
UI Extensionsiframes with full UI controlForge UI with declarative components
Data StorageDeveloper-managed databasesForge storage (key-value)
Security ReviewManual review by AtlassianAutomated + manual review
PerformanceDepends on developer infrastructureManaged by Atlassian with SLAs

Marketplace Search and Discovery

The Marketplace search and discovery system must help customers find the right apps for their needs among thousands of listings. The search engine uses Elasticsearch with custom scoring that considers relevance (text match quality), popularity (install count and ratings), recency (last updated date), and quality (review scores and support responsiveness). The search results page supports filtering by Atlassian product, app category, pricing model, and compatibility with the customer's Atlassian Cloud or Data Center deployment.

Beyond search, the Marketplace provides curated collections, editor's picks, and category pages that help customers discover apps they might not have found through search alone. The recommendation engine analyzes the customer's existing app installations, team size, and industry to suggest complementary apps. These discovery mechanisms are critical for the Marketplace revenue, as they drive app installations that might not happen through search alone.

9. Atlassian Cloud Platform

The Atlassian Cloud Platform provides the foundational infrastructure and shared services that all Atlassian products run on. The platform is built on AWS and uses a multi-region, multi-account architecture to provide isolation, scalability, and compliance. Each Atlassian customer is assigned to a specific cloud tenant, which defines their data residency region, product entitlements, and organizational settings. The platform manages the lifecycle of these tenants, from provisioning new environments to scaling existing ones based on usage patterns.

Tenant isolation is a critical concern in a multi-tenant SaaS platform. Atlassian implements isolation at multiple layers: network isolation ensures that traffic from one tenant cannot reach another tenant services; data isolation ensures that database queries are scoped to the tenant data; and compute isolation ensures that a tenant workload cannot consume resources needed by other tenants. These isolation mechanisms must be enforced consistently across all products and services, which requires a centralized policy engine that every service consults before processing a request.

Identity and Access Management

The identity service is the most critical shared service in the Atlassian Cloud Platform, as it governs authentication and authorization for every request. The identity service implements OpenID Connect for authentication, supports single sign-on via SAML 2.0 and OIDC with external identity providers (Okta, Azure AD, Google Workspace), and manages the hierarchical authorization model that spans organizations, sites, projects, and individual resources. Multi-factor authentication is supported through TOTP, WebAuthn, and SMS, and can be enforced organization-wide through security policies.

C#
public class AtlassianIdentityService
{
    private readonly IOrganisationRepository _orgRepo;
    private readonly IUserRepository _userRepo;
    private readonly ITokenService _tokenService;
    private readonly IPolicyEngine _policyEngine;
    private readonly IAuditLogger _auditLogger;

    public async Task<AuthResult> AuthenticateAsync(AuthRequest request)
    {
        var user = await _userRepo.FindByEmailAsync(request.Email);
        if (user == null)
            return AuthResult.UserNotFound();

        var org = await _orgRepo.GetOrganisationAsync(user.OrganisationId);
        var securityPolicy = await _policyEngine
            .GetSecurityPolicyAsync(org.Id);

        if (securityPolicy.RequireMfa && !user.MfaEnabled)
            return AuthResult.MfaRequired();

        if (securityPolicy.AllowedIpRanges.Any() &&
            !IsIpAllowed(request.IpAddress, securityPolicy.AllowedIpRanges))
            return AuthResult.IpBlocked();

        var passwordValid = await VerifyPasswordAsync(user, request.Password);
        if (!passwordValid)
        {
            await HandleFailedLoginAsync(user);
            return AuthResult.InvalidCredentials();
        }

        if (securityPolicy.RequireMfa)
        {
            var mfaResult = await VerifyMfaAsync(user, request.MfaToken);
            if (!mfaResult.IsValid)
                return AuthResult.MfaFailed();
        }

        var session = await _tokenService.CreateSessionAsync(
            new SessionRequest
        {
            UserId = user.Id,
            OrganisationId = org.Id,
            IpAddress = request.IpAddress,
            UserAgent = request.UserAgent,
            ExpiresAt = DateTime.UtcNow.AddHours(
                securityPolicy.SessionTimeoutHours)
        });

        await _auditLogger.LogAuthenticationAsync(new AuditEntry
        {
            UserId = user.Id,
            OrganisationId = org.Id,
            Action = "LOGIN_SUCCESS",
            IpAddress = request.IpAddress,
            Timestamp = DateTime.UtcNow
        });

        return AuthResult.Success(session);
    }

    public async Task<AuthorizationResult> AuthorizeAsync(
        string resourceId, string resourceType,
        PermissionType permission, User user)
    {
        var org = await _orgRepo.GetOrganisationAsync(user.OrganisationId);

        var hasPermission = await _policyEngine
            .CheckPermissionAsync(new PermissionRequest
            {
                UserId = user.Id,
                OrganisationId = org.Id,
                ResourceId = resourceId,
                ResourceType = resourceType,
                Permission = permission
            });

        if (!hasPermission)
        {
            await _auditLogger.LogAuthorizationFailureAsync(
                user.Id, resourceId, permission);
            return AuthorizationResult.Denied();
        }

        return AuthorizationResult.Allowed();
    }

    private async Task HandleFailedLoginAsync(User user)
    {
        user.FailedLoginCount++;
        user.LastFailedLoginAt = DateTime.UtcNow;
        if (user.FailedLoginCount >= 5)
        {
            user.LockedUntil = DateTime.UtcNow.AddMinutes(30);
            await _auditLogger.LogAccountLockedAsync(user.Id);
        }
        await _userRepo.UpdateUserAsync(user);
    }

    private bool IsIpAllowed(string ipAddress, List<string> allowedRanges)
    {
        var ip = IPAddress.Parse(ipAddress);
        return allowedRanges.Any(range =>
            IPAddress.TryParse(range, out var allowed) &&
            ip.Equals(allowed));
    }
}

Data Residency

Data residency is a critical requirement for enterprise customers, particularly those subject to regulations like GDPR, which require that personal data be stored within specific geographic boundaries. Atlassian implements data residency by pinning each customer data to a specific AWS region. When a customer selects a data residency region during onboarding, all of their data � including issues, pages, repository content, and attachments � is stored exclusively in that region infrastructure. The platform enforces this constraint at the service level, ensuring that no data processing for a resident customer occurs outside their designated region.

Implementing data residency across multiple products is challenging because cross-product integrations naturally create data dependencies. For example, when a Jira issue links to a Confluence page, both the issue and the page must be in the same region. If a customer has users in multiple regions, the platform must handle scenarios where a user in one region needs to access data in another region, typically through read-only cross-region access with appropriate access controls.

Platform ComponentTechnologyScale Strategy
ComputeKubernetes (EKS)Horizontal pod autoscaling, cluster federation
Primary DatabasePostgreSQL (RDS)Read replicas, connection pooling (PgBouncer)
CacheRedis (ElastiCache)Cluster mode, automatic failover
SearchElasticsearch (OpenSearch)Index sharding, cross-cluster search
Object StorageAmazon S3Unlimited scale, lifecycle policies
Message QueueApache Kafka (MSK)Topic partitioning, consumer groups

10. Automation

Automation is a cross-cutting capability in the Atlassian platform that allows users to define rules that execute actions automatically when specific events occur. Automation rules are configured through a visual rule builder or through YAML definitions, and they span all Atlassian products � from Jira issue transitions and Confluence page updates to Bitbucket pipeline completions and Trello card movements. The automation system is designed to handle millions of rules across all tenants while maintaining low latency for the core product workflows that trigger automation events.

An automation rule consists of four components: a trigger that specifies when the rule should be evaluated, one or more conditions that filter when the rule should execute, one or more actions that define what the rule should do, and optional branches that implement conditional logic. Triggers can be event-based (fired when a specific event occurs in the system), scheduled (fired at a specific time or interval), or webhook-based (fired when an external system sends an HTTP request).

Rule Evaluation Pipeline

The rule evaluation pipeline processes incoming events through a series of stages to determine which rules should be executed. First, the event ingestion stage receives events from the product services through Kafka. Events are normalized into a common format that includes the event type, the affected resource, the user who triggered the event, and the event metadata. Next, the rule matching stage looks up all rules that are registered for the event type and filters them by tenant to avoid evaluating rules that belong to other organizations. Then, the condition evaluation stage evaluates each matching rule conditions against the event context to determine if the rule should execute. Finally, the action execution stage performs the actions defined in the matching rules.

graph LR Event[Product Event] --> Ingestion[Kafka Ingestion] Ingestion --> Normalize[Event Normalization] Normalize --> Match[Rule Matching by Tenant] Match --> Evaluate[Condition Evaluation] Evaluate --> Execute[Action Execution] Execute --> Audit[Audit Logging] Execute --> Notify[Notification Dispatch] Execute --> External[External Webhook] Evaluate --> Branch[Branch Evaluation] Branch --> Execute

Advanced Automation Features

The automation system supports advanced features that go beyond simple trigger-condition-action rules. Branching allows rules to execute different action sequences based on the evaluation of conditions, similar to if-else statements in programming. Looping allows rules to iterate over collections of resources, such as all issues in an epic or all members of a project, and execute actions for each item. Lookup tables allow rules to map input values to output values, enabling conditional logic without complex branching. Smart values are template expressions that extract and transform data from the event context, allowing rules to use dynamic values in their actions.

C#
public class AutomationRuleEvaluator
{
    private readonly IRuleRepository _ruleRepo;
    private readonly IConditionEvaluator _conditionEvaluator;
    private readonly IActionExecutor _actionExecutor;
    private readonly ISmartValueResolver _smartValueResolver;
    private readonly IRateLimiter _rateLimiter;
    private readonly IAuditLogger _auditLogger;

    public async Task<AutomationResult> ProcessEventAsync(
        AutomationEvent eventItem)
    {
        var rules = await _ruleRepo.GetRulesByTriggerAsync(
            eventItem.TenantId, eventItem.EventType);

        var rateLimitOk = await _rateLimiter
            .CheckRateLimitAsync(eventItem.TenantId);
        if (!rateLimitOk)
            return AutomationResult.RateLimited();

        var results = new List<RuleResult>();

        foreach (var rule in rules.Where(r => r.IsEnabled))
        {
            var ruleContext = await BuildRuleContextAsync(rule, eventItem);

            var conditionsPassed = await EvaluateConditionsAsync(
                rule.Conditions, ruleContext);

            if (!conditionsPassed)
            {
                results.Add(new RuleResult
                {
                    RuleId = rule.Id,
                    Status = RuleStatus.ConditionsNotMet
                });
                continue;
            }

            var actionResults = await ExecuteActionsAsync(
                rule.Actions, ruleContext, rule.BranchConfig);

            results.Add(new RuleResult
            {
                RuleId = rule.Id,
                Status = actionResults.All(a => a.Success)
                    ? RuleStatus.Success
                    : RuleStatus.PartialFailure,
                ActionResults = actionResults
            });

            await _auditLogger.LogRuleExecutionAsync(
                rule.Id, eventItem, actionResults);
        }

        return new AutomationResult { RuleResults = results };
    }

    private async Task<RuleContext> BuildRuleContextAsync(
        AutomationRule rule, AutomationEvent eventItem)
    {
        var context = new RuleContext
        {
            Event = eventItem,
            Rule = rule,
            Variables = new Dictionary<string, object>()
        };

        foreach (var varDef in rule.Variables)
        {
            var value = await _smartValueResolver
                .ResolveAsync(varDef.Expression, eventItem);
            context.Variables[varDef.Name] = value;
        }

        return context;
    }

    private async Task<bool> EvaluateConditionsAsync(
        List<AutomationCondition> conditions, RuleContext context)
    {
        if (!conditions.Any()) return true;
        return conditions.All(condition =>
            _conditionEvaluator.Evaluate(condition, context));
    }

    private async Task<List<ActionResult>> ExecuteActionsAsync(
        List<AutomationAction> actions,
        RuleContext context, BranchConfig branchConfig)
    {
        var results = new List<ActionResult>();

        if (branchConfig != null)
        {
            var branches = EvaluateBranches(branchConfig, context);
            foreach (var branch in branches)
            {
                var branchResults = await ExecuteActionsAsync(
                    branch.Actions, context, null);
                results.AddRange(branchResults);
            }
        }
        else
        {
            foreach (var action in actions)
            {
                var resolvedAction = await ResolveSmartValuesAsync(
                    action, context);
                var result = await _actionExecutor
                    .ExecuteAsync(resolvedAction, context);
                results.Add(result);
            }
        }

        return results;
    }

    private List<ActionBranch> EvaluateBranches(
        BranchConfig config, RuleContext context)
    {
        return config.Branches
            .Where(b => _conditionEvaluator.Evaluate(b.Condition, context))
            .ToList();
    }
}

Webhooks and External Integrations

The automation system supports outbound webhooks that allow rules to call external APIs when events occur. Webhooks are configured with a target URL, HTTP method, headers, and a body template that uses smart values to include event data. The webhook delivery system must handle retries for failed deliveries, track delivery status for each webhook invocation, and respect rate limits configured by the tenant. Webhooks are delivered with at-least-once semantics, meaning the external system may receive duplicate deliveries and must be idempotent.

Automation ComponentConfiguration OptionsLimit
TriggersEvent-based, scheduled, webhook50 triggers per rule
ConditionsField comparison, function, JQL, CQL10 conditions per rule
ActionsUpdate field, assign, notify, create, transition20 actions per rule
BranchesIf-else, for-each, related issues5 branch levels deep
Rules per ProjectActive automation rules500 rules per project
Executions per MonthTotal rule executionsPlan-dependent (1K to unlimited)

11. APIs

Atlassian provides comprehensive APIs that allow developers to build integrations, automate workflows, and extend the functionality of Atlassian products. The API ecosystem includes REST APIs for each product, a GraphQL API for flexible data querying, the Connect API for marketplace app development, and the Forge API for serverless app development. Each API is versioned independently, with backward compatibility maintained for at least two major versions, and all APIs are documented through interactive API explorers that allow developers to test requests directly in the browser.

The REST API follows standard HTTP conventions, using appropriate HTTP methods (GET for reading, POST for creating, PUT/PATCH for updating, DELETE for deleting), returning JSON responses with consistent error formats, and supporting pagination for list endpoints. Authentication is handled through OAuth 2.0 for marketplace apps and API tokens for direct integrations. Rate limiting is applied per-user for authenticated requests and per-IP for anonymous requests, with limits varying by endpoint complexity and tenant plan.

API Gateway Architecture

All API traffic enters through a centralized API gateway that provides authentication, rate limiting, request routing, and response caching. The gateway validates the caller identity using the identity service, applies the appropriate rate limits based on the caller plan and tenant, and routes the request to the appropriate product service. For requests that span multiple products, the gateway can compose responses from multiple services, reducing the number of round trips the client needs to make.

graph TB Client[API Client] --> Gateway[API Gateway] Gateway --> Auth[Authentication] Gateway --> RateLimit[Rate Limiter] Gateway --> Router[Request Router] Router --> JiraAPI[Jira REST API] Router --> ConfluenceAPI[Confluence REST API] Router --> BitbucketAPI[Bitbucket REST API] Router --> TrelloAPI[Trello REST API] Router --> GraphqlAPI[GraphQL API] JiraAPI --> JiraService[Jira Service] ConfluenceAPI --> ConfluenceService[Confluence Service] BitbucketAPI --> BitbucketService[Bitbucket Service] TrelloAPI --> TrelloService[Trello Service] GraphqlAPI --> DataLoader[Data Loader] DataLoader --> JiraService DataLoader --> ConfluenceService DataLoader --> BitbucketService

GraphQL API

The GraphQL API provides a unified query interface that spans multiple Atlassian products, allowing developers to fetch data from Jira, Confluence, and Bitbucket in a single request. The GraphQL schema is auto-generated from the product service schemas and is kept in sync through a schema registry that tracks changes and validates backward compatibility. The GraphQL resolver layer translates queries into the appropriate product API calls, applies authorization checks, and assembles the response according to the query selection set.

C#
public class AtlassianGraphQLController : ControllerBase
{
    private readonly ISchemaProvider _schemaProvider;
    private readonly IQueryExecutor _queryExecutor;
    private readonly IAuthorizationService _authService;
    private readonly IMetricsCollector _metrics;

    [HttpPost("graphql")]
    public async Task<IActionResult> ExecuteQuery(
        [FromBody] GraphQLRequest request)
    {
        var stopwatch = Stopwatch.StartNew();

        var user = await _authService.GetAuthenticatedUserAsync(HttpContext);
        if (user == null)
            return Unauthorized();

        var schema = await _schemaProvider.GetSchemaAsync();
        var validationErrors = ValidateQuery(schema, request.Query);
        if (validationErrors.Any())
            return BadRequest(new GraphQLResponse
            {
                Errors = validationErrors
            });

        var complexity = CalculateQueryComplexity(request.Query);
        if (complexity > 1000)
            return BadRequest(new GraphQLResponse
            {
                Errors = new List<GraphQLError>
                {
                    new GraphQLError
                    {
                        Message = "Query complexity exceeds maximum"
                    }
                }
            });

        var executionContext = new ExecutionContext
        {
            Schema = schema,
            Query = request.Query,
            Variables = request.Variables,
            User = user,
            DataLoaderContext = new DataLoaderContext()
        };

        var result = await _queryExecutor.ExecuteAsync(executionContext);

        stopwatch.Stop();
        _metrics.RecordQueryDuration(
            stopwatch.ElapsedMilliseconds, result.Errors.Any());

        return Ok(new GraphQLResponse
        {
            Data = result.Data,
            Errors = result.Errors
        });
    }

    private List<GraphQLError> ValidateQuery(ISchema schema, string query)
    {
        var validator = new QueryValidator(schema);
        return validator.Validate(query);
    }

    private int CalculateQueryComplexity(string query)
    {
        var analyzer = new ComplexityAnalyzer();
        return analyzer.Analyze(query);
    }
}

Webhook System

Atlassian products emit webhooks that notify external systems when events occur. The webhook system supports fine-grained filtering, allowing developers to register webhooks for specific event types (such as issue created, page updated, or pull request merged) and specific projects or repositories. Webhooks are delivered as HTTP POST requests with a JSON payload containing the event details, and include a signature header that allows the receiver to verify the webhook authenticity. The delivery system provides at-least-once delivery guarantees with configurable retry policies for failed deliveries.

API TypeAuthenticationRate LimitPrimary Use Case
Jira REST APIOAuth 2.0 / API Token100 requests/secondIssue CRUD, search, workflows
Confluence REST APIOAuth 2.0 / API Token100 requests/secondPage CRUD, space management
Bitbucket REST APIOAuth 2.0 / App Password1000 requests/minuteRepo management, PR operations
Trello REST APIOAuth 1.0 / API Key100 requests/10 secondsBoard, list, card operations
GraphQL APIOAuth 2.0Complexity-based (1000 max)Cross-product queries
WebhooksHMAC SignaturePer-tenant delivery queueEvent notifications

12. Intelligence

Atlassian Intelligence layer integrates artificial intelligence and machine learning capabilities across all products to help teams work more efficiently. Intelligence features include AI Assist for generating and summarizing content, Smart Suggestions for recommending relevant issues and pages, Analytics for providing insights into team productivity and project health, and Predictive Analytics for forecasting project timelines and identifying risks. These capabilities are powered by a combination of large language models (LLMs) for natural language understanding and traditional ML models for classification, prediction, and recommendation tasks.

The Intelligence architecture consists of three layers: the inference layer that runs ML models and LLM prompts, the feature store that provides pre-computed features for ML models, and the data pipeline that processes raw platform data into training data and features. The inference layer supports both real-time inference (for features like Smart Suggestions that must respond in milliseconds) and batch inference (for features like Analytics that can be computed asynchronously). The feature store uses a combination of online features (served from Redis for low-latency access) and offline features (stored in S3 for model training).

AI Assist

AI Assist is Atlassian generative AI feature that helps users create content, summarize information, and answer questions using the context of their work data. In Jira, AI Assist can generate issue descriptions from brief summaries, suggest labels and components based on issue content, and provide root cause analysis recommendations. In Confluence, AI Assist can summarize long pages, generate meeting notes from transcripts, and draft documents from bullet points. In Bitbucket, AI Assist can explain code changes in pull request descriptions, suggest commit messages, and identify potential bugs in code reviews.

graph TB User[User Input] --> Gateway[AI Gateway] Gateway --> Auth[Auth and Rate Limit] Gateway --> Context[Context Builder] Context --> JiraCtx[Jira Context] Context --> ConfluenceCtx[Confluence Context] Context --> BitbucketCtx[Bitbucket Context] JiraCtx --> PromptBuilder[Prompt Builder] ConfluenceCtx --> PromptBuilder BitbucketCtx --> PromptBuilder PromptBuilder --> LLMRouter[LLM Router] LLMRouter --> GPT[GPT-4 / Claude] LLMRouter --> Custom[Custom Fine-Tuned Model] GPT --> Response[AI Response] Custom --> Response Response --> Guardrails[Safety Guardrails] Guardrails --> Output[Filtered Output]

Analytics and Insights

The Analytics service processes event data from all Atlassian products to generate insights about team productivity, project health, and organizational trends. In Jira, analytics include velocity charts, burndown graphs, cumulative flow diagrams, and cycle time analysis. In Confluence, analytics include page views, edit frequency, and content freshness metrics. In Bitbucket, analytics include commit frequency, pull request cycle time, and code review metrics. These analytics are presented through dashboards that can be customized by administrators and shared with stakeholders.

C#
public class AtlassianAnalyticsService
{
    private readonly IEventStore _eventStore;
    private readonly IAnalyticsRepository _analyticsRepo;
    private readonly IFeatureStore _featureStore;
    private readonly IMetricsCollector _metrics;

    public async Task<SprintAnalytics> GetSprintAnalyticsAsync(
        string projectId, string sprintId)
    {
        var cacheKey = $"sprint-analytics:{projectId}:{sprintId}";
        var cached = await _analyticsRepo.GetCachedAsync<SprintAnalytics>(
            cacheKey);
        if (cached != null) return cached;

        var sprint = await GetSprintDetailsAsync(sprintId);
        var events = await _eventStore.GetEventsAsync(
            projectId, sprint.StartDate, sprint.EndDate);

        var analytics = new SprintAnalytics
        {
            SprintId = sprintId,
            TotalIssues = events.Count(e =>
                e.Type == EventType.IssueCreated),
            CompletedIssues = events.Count(e =>
                e.Type == EventType.StatusChanged &&
                e.ToStatus == "Done"),
            StoryPointsCommitted = events
                .Where(e => e.Type == EventType.IssueCreated)
                .Sum(e => e.StoryPoints),
            StoryPointsCompleted = events
                .Where(e => e.Type == EventType.StatusChanged &&
                    e.ToStatus == "Done")
                .Sum(e => e.StoryPoints),
            VelocityTrend = await CalculateVelocityTrendAsync(
                projectId, sprint.Index),
            CycleTimeDistribution =
                await CalculateCycleTimeAsync(events),
            BurndownData = await CalculateBurndownAsync(sprint, events),
            ScopeChanges = events.Count(e =>
                e.Type == EventType.IssueAddedToSprint ||
                e.Type == EventType.IssueRemovedFromSprint)
        };

        analytics.CompletionRate = analytics.TotalIssues > 0
            ? (double)analytics.CompletedIssues / analytics.TotalIssues
            : 0;

        analytics.AverageCycleTime =
            analytics.CycleTimeDistribution.Any()
            ? analytics.CycleTimeDistribution.Average()
            : TimeSpan.Zero;

        await _analyticsRepo.CacheAsync(
            cacheKey, analytics, TimeSpan.FromMinutes(5));

        return analytics;
    }

    public async Task<TeamHealthMetrics> GetTeamHealthAsync(
        string projectId, DateRange period)
    {
        var events = await _eventStore.GetEventsAsync(
            projectId, period.Start, period.End);

        var velocityData = await CalculateVelocityTrendAsync(projectId, 10);

        var metrics = new TeamHealthMetrics
        {
            ProjectId = projectId,
            Period = period,
            AverageVelocity = velocityData.Average(v => v.Points),
            VelocityStability = CalculateStandardDeviation(
                velocityData.Select(v => v.Points)),
            OnTimeDeliveryRate = await CalculateOnTimeRateAsync(
                events, projectId),
            WipAdherence = await CalculateWipAdherenceAsync(
                events, projectId),
            BacklogHealth = await AssessBacklogHealthAsync(projectId)
        };

        metrics.OverallHealthScore = CalculateHealthScore(metrics);
        return metrics;
    }

    private double CalculateHealthScore(TeamHealthMetrics metrics)
    {
        return (metrics.AverageVelocity * 0.25) +
               (metrics.OnTimeDeliveryRate * 0.30) +
               (metrics.WipAdherence * 0.20) +
               (metrics.BacklogHealth * 0.25);
    }

    private double CalculateStandardDeviation(IEnumerable<double> values)
    {
        var avg = values.Average();
        var sumSquares = values.Sum(v => Math.Pow(v - avg, 2));
        return Math.Sqrt(sumSquares / values.Count());
    }
}

Predictive Analytics

Predictive analytics in Atlassian use historical project data to forecast future outcomes. The sprint forecasting model analyzes past velocity trends, backlog composition, and team capacity to predict how much work a team can complete in upcoming sprints. The delivery date predictor uses Monte Carlo simulation based on historical cycle times to estimate the probability of completing a set of issues by a given date. The risk identification model analyzes issue dependencies, assignee workload, and historical delay patterns to flag issues at risk of missing their deadlines.

Intelligence FeatureML TechniqueData Source
AI Assist (Text Generation)LLM (GPT-4 / Claude) with RAGIssue content, page content, code
Smart SuggestionsEmbedding similarity + collaborative filteringUser behavior, content metadata
Sprint ForecastingTime series analysis + Monte CarloHistorical velocity, backlog data
Workload BalancingConstraint optimizationAssignee capacity, issue complexity
Risk IdentificationClassification model (gradient boosting)Dependency graph, delay history
Content SummarizationExtractive + abstractive summarizationPage content, comment threads

13. Enterprise Guard

Enterprise Guard is Atlassian suite of security, compliance, and governance features designed for large organizations with strict regulatory requirements. Enterprise Guard provides Data Loss Prevention (DLP) to prevent sensitive information from being shared inappropriately, encryption to protect data at rest and in transit, compliance certifications to meet regulatory requirements, and administrative controls to enforce organizational policies across all Atlassian products. These features are essential for enterprise customers in industries like finance, healthcare, and government, where data protection is not optional but legally mandated.

The DLP system monitors content creation and modification across all Atlassian products to detect and prevent the sharing of sensitive information. DLP policies define patterns to look for (such as credit card numbers, social security numbers, or proprietary code) and actions to take when those patterns are detected (such as blocking the content, alerting administrators, or quarantining the content for review). The DLP engine processes content in real-time using a combination of regex pattern matching, named entity recognition, and ML-based classification to identify sensitive content with high accuracy and low false positive rates.

Encryption Architecture

Atlassian Cloud provides encryption at rest using AES-256 encryption with AWS KMS-managed keys. For customers requiring additional control, Enterprise Guard supports customer-managed encryption keys (CMEK), where the customer provides and manages their own encryption keys through a cloud KMS integration. With CMEK, Atlassian never has access to the encryption key, and data is encrypted and decrypted using the customer key through a hardware security module (HSM). This architecture provides an additional layer of protection because even if Atlassian infrastructure is compromised, the attacker cannot decrypt customer data without the customer key.

C#
public class EnterpriseEncryptionService
{
    private readonly IKeyManagementService _kms;
    private readonly IEncryptionContextProvider _contextProvider;

    public async Task<EncryptedData> EncryptAsync(
        byte[] plaintext, EncryptionConfig config)
    {
        var keyId = await _kms.GetEncryptionKeyAsync(
            config.TenantId, config.KeyType);

        var encryptionContext = await _contextProvider
            .BuildContextAsync(config);

        var encryptedKey = await _kms.EncryptDataKeyAsync(
            keyId, encryptionContext);

        using var aes = Aes.Create();
        aes.Key = encryptedKey.DecryptedDataKey;
        aes.GenerateIV();

        byte[] ciphertext;
        using (var encryptor = aes.CreateEncryptor())
        using (var ms = new MemoryStream())
        {
            using (var cs = new CryptoStream(
                ms, encryptor, CryptoStreamMode.Write))
            {
                await cs.WriteAsync(plaintext);
            }
            ciphertext = ms.ToArray();
        }

        return new EncryptedData
        {
            Ciphertext = ciphertext,
            IV = aes.IV,
            EncryptedKey = encryptedKey.EncryptedDataKey,
            KeyId = keyId,
            EncryptionContext = encryptionContext,
            Algorithm = "AES-256-GCM",
            EncryptedAt = DateTime.UtcNow
        };
    }

    public async Task<byte[]> DecryptAsync(EncryptedData encryptedData)
    {
        var decryptedKey = await _kms.DecryptDataKeyAsync(
            encryptedData.KeyId,
            encryptedData.EncryptedKey,
            encryptedData.EncryptionContext);

        using var aes = Aes.Create();
        aes.Key = decryptedKey;
        aes.IV = encryptedData.IV;

        using var decryptor = aes.CreateDecryptor();
        using var ms = new MemoryStream(encryptedData.Ciphertext);
        using var cs = new CryptoStream(
            ms, decryptor, CryptoStreamMode.Read);
        using var result = new MemoryStream();
        await cs.CopyToAsync(result);

        return result.ToArray();
    }
}

public class DlpPolicyEngine
{
    private readonly IDlpRuleRepository _ruleRepo;
    private readonly IContentAnalyzer _contentAnalyzer;
    private readonly INotificationService _notifications;
    private readonly IAuditLogger _auditLogger;

    public async Task<DlpResult> EvaluateContentAsync(
        string tenantId, ContentItem content)
    {
        var policies = await _ruleRepo.GetActivePoliciesAsync(tenantId);
        var violations = new List<DlpViolation>();

        foreach (var policy in policies)
        {
            var matches = await _contentAnalyzer.AnalyzeAsync(
                content.Body, policy.DetectionRules);

            if (matches.Any())
            {
                var violation = new DlpViolation
                {
                    PolicyId = policy.Id,
                    PolicyName = policy.Name,
                    ContentId = content.Id,
                    ContentType = content.Type,
                    MatchedRules = matches,
                    DetectedAt = DateTime.UtcNow
                };

                violations.Add(violation);
                await ExecutePolicyActionAsync(
                    policy.Action, content, violation);
                await _auditLogger.LogDlpViolationAsync(violation);
            }
        }

        return new DlpResult
        {
            HasViolations = violations.Any(),
            Violations = violations
        };
    }

    private async Task ExecutePolicyActionAsync(
        DlpAction action, ContentItem content, DlpViolation violation)
    {
        switch (action.Type)
        {
            case DlpActionType.Block:
                content.IsBlocked = true;
                break;
            case DlpActionType.Quarantine:
                content.Status = ContentStatus.Quarantined;
                await _notifications.NotifyAdminsAsync(
                    "Content quarantined by DLP policy", violation);
                break;
            case DlpActionType.Notify:
                await _notifications.NotifyAdminsAsync(
                    "DLP policy violation detected", violation);
                break;
            case DlpActionType.Log:
                break;
        }
    }
}

Compliance and Audit

Enterprise Guard provides comprehensive audit logging that records every significant action performed across all Atlassian products. Audit logs capture who performed the action, what was changed, when it occurred, and from where (IP address and device). These logs are immutable, stored in a dedicated audit log service that prevents modification or deletion, and are retained for a configurable period (up to seven years for compliance with regulations like SEC Rule 17a-4). Audit logs can be exported in standard formats for analysis by external SIEM (Security Information and Event Management) systems.

Enterprise Guard FeatureDescriptionCompliance Standard
Data Loss PreventionReal-time content scanning for sensitive dataSOC 2, HIPAA, GDPR
Encryption at RestAES-256 with KMS-managed keysSOC 2, FedRAMP, ISO 27001
Customer-Managed KeysCustomer-controlled encryption keysHIPAA, PCI DSS
Audit LoggingImmutable, comprehensive activity logsSOC 2, FedRAMP, GDPR
IP AllowlistingRestrict access to approved IP rangesEnterprise security policies
Session ManagementConfigurable session timeouts and MFASOC 2, NIST 800-63

14. Administration

Atlassian administration model is hierarchical, with different levels of administrative control depending on the scope of the administration task. At the top level, the Organization Admin manages the Atlassian organization, which is the top-level entity that encompasses all sites, users, and billing for a customer. Organization admins can manage users across all sites, configure organization-wide security policies, manage billing and subscriptions, and control which products are enabled for the organization.

Below the organization level, Site Admins manage individual Atlassian Cloud sites. A site is a specific deployment of Atlassian products (for example, mycompany.atlassian.net), and site admins can configure which products are installed on the site, manage site-level settings like URL and branding, and install and configure Marketplace apps. Below the site level, Project Admins manage individual projects within a product, configuring workflows, permissions, notifications, and automation rules for their specific project.

Organization Management

The organization management console provides a centralized interface for organization admins to manage their Atlassian Cloud environment. Key capabilities include user management (adding users, assigning roles, managing groups), directory synchronization (connecting to external identity providers for automatic user provisioning via SCIM), billing management (viewing and modifying subscriptions, managing payment methods, downloading invoices), and security policy management (configuring MFA requirements, IP allowlists, and session policies).

Admin RoleScopeKey Responsibilities
Organization AdminAll sites and productsUser management, billing, security policies, directory sync
Site AdminSingle Atlassian siteProduct configuration, app installation, site settings
Project AdminSingle project within a productWorkflow config, permissions, automation, notifications
Product AdminSingle product (Jira, Confluence, etc.)Product-specific settings, global configurations
Billing AdminOrganization billingSubscription management, payment methods, invoices
Security AdminOrganization securitySecurity policies, compliance settings, audit logs

Audit and Compliance Reporting

The administration console provides comprehensive audit and compliance reporting tools that help administrators understand how their Atlassian environment is being used and ensure compliance with organizational policies. Audit dashboards display user activity trends, content access patterns, and administrative changes. Compliance reports generate pre-configured summaries for common frameworks like SOC 2, GDPR, and HIPAA, showing which controls are in place and which require attention. Custom reports can be built using the audit log API, which provides programmatic access to the full audit event stream.

C#
public class OrganizationAdminService
{
    private readonly IOrganisationRepository _orgRepo;
    private readonly IUserManagementService _userMgmt;
    private readonly IBillingService _billing;
    private readonly ISecurityPolicyService _security;
    private readonly IAuditReportService _auditReport;

    public async Task<OrgDashboard> GetOrgDashboardAsync(
        string orgId, User admin)
    {
        var org = await _orgRepo.GetOrganisationAsync(orgId);
        var users = await _userMgmt.GetUsersAsync(orgId);
        var recentActivity = await _auditReport
            .GetRecentActivityAsync(orgId, TimeSpan.FromDays(30));

        return new OrgDashboard
        {
            Organisation = org,
            TotalUsers = users.Count,
            ActiveUsers = users.Count(u =>
                u.LastActiveAt > DateTime.UtcNow.AddDays(-30)),
            SuspendedUsers = users.Count(u =>
                u.Status == UserStatus.Suspended),
            Products = await GetProductUsageAsync(orgId),
            Billing = await _billing.GetBillingSummaryAsync(orgId),
            SecurityPosture = await _security
                .GetSecurityPostureAsync(orgId),
            RecentAuditEvents = recentActivity,
            ComplianceStatus = await _auditReport
                .GetComplianceStatusAsync(orgId)
        };
    }

    public async Task<BulkActionResult> BulkProvisionUsersAsync(
        string orgId, List<UserProvisionRequest> requests,
        User performedBy)
    {
        var results = new List<UserProvisionResult>();

        foreach (var request in requests)
        {
            try
            {
                var user = await _userMgmt.CreateUserAsync(
                    new CreateUserRequest
                    {
                        Email = request.Email,
                        DisplayName = request.DisplayName,
                        OrganisationId = orgId,
                        Groups = request.Groups,
                        ProductAccess = request.ProductAccess
                    });

                results.Add(new UserProvisionResult
                {
                    Email = request.Email,
                    Status = ProvisionStatus.Success,
                    UserId = user.Id
                });
            }
            catch (Exception ex)
            {
                results.Add(new UserProvisionResult
                {
                    Email = request.Email,
                    Status = ProvisionStatus.Failed,
                    ErrorMessage = ex.Message
                });
            }
        }

        return new BulkActionResult
        {
            TotalRequested = requests.Count,
            Successful = results.Count(r =>
                r.Status == ProvisionStatus.Success),
            Failed = results.Count(r =>
                r.Status == ProvisionStatus.Failed),
            Results = results
        };
    }
}

Directory Synchronization

Directory synchronization allows organizations to connect their existing identity provider (such as Okta, Azure AD, or Ping Identity) to Atlassian Cloud for automatic user provisioning and deprovisioning. When a user is added to the identity provider directory, they are automatically provisioned in Atlassian Cloud with the appropriate product access and group memberships. When a user is removed from the directory, their Atlassian account is automatically suspended. This integration uses the SCIM 2.0 protocol for user provisioning and SAML 2.0 for authentication, ensuring that user lifecycle management is fully automated and consistent with the organization existing identity management processes.

15. Migration

Atlassian provides comprehensive migration tools and services to help customers move from Server or Data Center deployments to Atlassian Cloud. Cloud migration is one of the most significant transitions that Atlassian customers face, as it involves years of accumulated data � including issues, pages, repositories, workflows, and configurations � from a self-managed environment to a multi-tenant cloud platform. The migration process must preserve data integrity, maintain user history and permissions, and minimize disruption to ongoing work.

The migration journey typically follows four phases: assessment (evaluating the current environment and identifying migration requirements), preparation (cleaning up data, resolving compatibility issues, and configuring the cloud environment), execution (transferring data from the source to the target environment), and validation (verifying that all data was transferred correctly and that the cloud environment is functioning as expected). Atlassian provides the Cloud Migration Assistant, a tool that automates much of the execution phase by analyzing the source environment, identifying potential issues, and performing the data transfer.

Migration Architecture

The Cloud Migration Assistant uses a hybrid architecture that combines an on-premises agent (running in the customer Data Center environment) with cloud-side services. The on-premises agent reads data from the source databases, packages it into a transfer format, and uploads it to an S3 staging bucket in the target AWS region. The cloud-side services then process the staged data, transforming it into the cloud data model and loading it into the cloud databases. This architecture minimizes the data transfer over the network by compressing and deduplicating data before upload, and it provides resumable transfers that can be paused and restarted without losing progress.

graph LR subgraph Source DC JiraDC[Jira Data Center] ConfluenceDC[Confluence Data Center] BitbucketDC[Bitbucket Data Center] Agent[Migration Agent] end subgraph Transfer S3Staging[(S3 Staging)] Compress[Compression] end subgraph Target Cloud JiraCloud[Jira Cloud] ConfluenceCloud[Confluence Cloud] BitbucketCloud[Bitbucket Cloud] Transform[Data Transform] Validate[Validation] end JiraDC --> Agent ConfluenceDC --> Agent BitbucketDC --> Agent Agent --> Compress Compress --> S3Staging S3Staging --> Transform Transform --> JiraCloud Transform --> ConfluenceCloud Transform --> BitbucketCloud Validate --> JiraCloud Validate --> ConfluenceCloud Validate --> BitbucketCloud
Data TypeSource FormatTarget FormatKey Challenge
Jira IssuesRelational rows in PostgreSQLCloud data model with custom fieldsWorkflow mapping, custom field conversion
Confluence PagesStorage format XMLAtlassian Document Format (ADF)Macro conversion, attachment migration
Bitbucket ReposGit pack files on diskS3-backed Git storageLarge repository transfer, LFS objects
Users and GroupsInternal directory or LDAPAtlassian Cloud identityAccount matching, permission mapping
Automation RulesServer automation configurationCloud automation rulesRule compatibility, action mapping
Marketplace AppsServer/Data Center app versionsCloud app equivalentsFeature parity, data migration for apps

Migration Validation

After the data transfer is complete, the validation phase verifies that all data was migrated correctly. The validation process includes automated checks (comparing record counts between source and target, verifying that all issues have their correct statuses and fields, checking that all pages have their correct content and attachments) and manual checks (having users verify that their projects, pages, and repositories look and function correctly). The validation service generates a detailed report that identifies any discrepancies between the source and target environments, along with recommended remediation steps.

The validation process must handle the inherent differences between Server/Data Center and Cloud environments. Some features may not have direct equivalents in the Cloud platform, and some configurations may need to be adjusted to work in the multi-tenant Cloud environment. The validation report categorizes issues by severity � critical (data loss or corruption), warning (functional differences that may require attention), and informational (expected differences between environments) � to help administrators prioritize their remediation efforts.

16. Comparison with Monday.com, Azure DevOps, Linear

Understanding how Atlassian compares to its competitors is essential for appreciating its architectural choices and market position. The primary competitors in the software collaboration platform space include Monday.com, Azure DevOps, and Linear, each of which takes a different approach to solving the same fundamental problem of helping software teams plan, build, and deliver products. While Atlassian focuses on breadth of functionality and extensibility, each competitor has its own strengths and architectural philosophy that differentiate it in the market.

Monday.com positions itself as a work operating system that extends beyond software development to include marketing, operations, and other business functions. Its architecture emphasizes visual workflow customization and ease of use, with a column-based data model that allows users to define custom fields and views without traditional database schema constraints. Monday.com competitive advantage lies in its simplicity and cross-functional appeal, but it lacks the deep software development integrations (code review, CI/CD, deployment) that Atlassian provides through Bitbucket and Jira.

Azure DevOps is Microsoft integrated development platform that provides repositories (Azure Repos), CI/CD (Azure Pipelines), boards (Azure Boards), test plans (Azure Test Plans), and artifact management (Azure Artifacts). Azure DevOps is deeply integrated with the Microsoft ecosystem, including Visual Studio, GitHub (which Microsoft also owns), and Azure cloud services. Its architecture is built on Microsoft internal scaling infrastructure and benefits from the company massive investment in cloud computing. However, Azure DevOps is primarily focused on the Microsoft/.NET ecosystem and does not provide the knowledge management capabilities that Confluence offers.

Linear is a newer entrant that focuses on issue tracking for software teams with an emphasis on speed and minimalism. Linear architecture is built from scratch using modern technologies (GraphQL API, real-time sync via WebSockets, Rust-based backend for performance) and prioritizes keyboard-driven workflows and aesthetic design. Linear competitive advantage lies in its performance and user experience, but it provides a narrower feature set than Atlassian, lacking built-in knowledge management, IT service management, and the extensive marketplace ecosystem.

FeatureAtlassianMonday.comAzure DevOpsLinear
Issue TrackingJira SoftwareMonday Work OSAzure BoardsLinear Issues
Knowledge ManagementConfluenceMonday WorkdocsAzure WikiNot available
Code HostingBitbucketGitHub integrationAzure ReposGitHub integration
CI/CDBitbucket PipelinesThird-party integrationsAzure PipelinesGitHub Actions
ITSMJira Service ManagementMonday ServiceAzure DevOps + SCOMNot available
Marketplace5,000+ apps200+ integrationsAzure MarketplaceLimited integrations
ExtensibilityConnect, Forge, REST APIMonday AppsREST API, ExtensionsGraphQL API
Enterprise SecurityEnterprise Guard, SAML, SCIMEnterprise plan featuresAzure AD integrationSSO, basic compliance
AI FeaturesAI Assist, Smart SuggestionsMonday AIGitHub Copilot integrationAI-powered triage
Starting PriceFree tier, .15/user/month/seat/monthFree for 5 or fewer users/user/month

Architectural Differentiators

From an architectural perspective, the key differentiator of Atlassian platform is its multi-product integration layer. While competitors offer separate tools that can be connected through integrations, Atlassian builds its products on a shared platform foundation that enables deep, native integration between Jira, Confluence, Bitbucket, and other products. For example, a Jira issue can link directly to a Confluence page, a Bitbucket pull request, and a Trello card, with all of these connections maintained as first-class entities in the platform data model. This integration is not achieved through third-party connectors but through shared APIs and data stores that allow cross-product queries and workflows to execute efficiently.

Another architectural differentiator is the extensibility model. Atlassian Connect and Forge frameworks provide a well-defined sandbox for third-party developers to extend the platform without compromising security or performance. The Connect model webhook-based architecture allows marketplace apps to respond to platform events without polling, while the Forge model serverless execution environment eliminates the operational burden of hosting an app. Neither Monday.com, Azure DevOps, nor Linear provides an extensibility ecosystem of comparable scale and maturity.

17. Interview Q&A

The following questions and answers cover the most commonly asked system design interview questions related to building a software collaboration platform like Atlassian. These questions are designed to test your understanding of distributed systems architecture, data modeling, scalability, and the specific challenges of building a multi-tenant SaaS platform. Each answer addresses the key architectural decisions, tradeoffs, and implementation details that a senior engineer should be able to discuss fluently.

Q1: How would you design the real-time collaboration feature for Confluence pages?

Real-time collaboration requires maintaining a consistent document state across multiple concurrent editors. I would use Operational Transformation (OT) rather than CRDTs because the document structure in Confluence is rich (headings, tables, macros) and OT handles structured documents more naturally. Each client maintains a local copy of the document and sends operations (insert, delete, format) to the server. The server transforms incoming operations against concurrent operations from other clients, ensuring all clients converge to the same state. The server uses a version vector to track the state of each client view and to determine how to transform operations. WebSocket connections provide low-latency bidirectional communication, with the server broadcasting transformed operations to all connected clients within 50ms. The server also persists each version of the document for history and rollback capabilities, using a lazy write strategy where the document is persisted every 30 seconds or when the last editor disconnects, rather than on every operation.

Q2: How would you handle multi-tenant data isolation in the Atlassian Cloud Platform?

Multi-tenant isolation requires defense-in-depth across every layer. At the network layer, each tenant services run in isolated network segments with security groups that prevent cross-tenant traffic. At the application layer, every database query is scoped to the tenant data through a tenant ID filter that is injected at the data access layer and cannot be bypassed by application code. At the compute layer, each tenant has resource quotas (CPU, memory, storage) enforced by Kubernetes resource limits and monitored by a quota enforcement service. For critical data paths, I would use separate database schemas per tenant (rather than shared schemas with tenant ID columns) to provide stronger isolation guarantees. The tenant context is established during authentication by the identity service and propagated through all service calls using an encrypted context header, ensuring that every service in the call chain knows which tenant data it is operating on.

Q3: How would you design the Jira automation engine to handle millions of rules across all tenants?

The automation engine must evaluate millions of rules efficiently without adding latency to the core Jira workflow. I would use a rule indexing strategy that maps trigger types to the rules that listen for them, stored in an in-memory data structure (Redis or local cache). When an event occurs, only the rules registered for that event type are evaluated, reducing the evaluation scope from millions to potentially hundreds. Each rule conditions are compiled into an optimized evaluator at rule creation time, avoiding interpretation overhead during evaluation. Rules are organized in a tenant-scoped partition to ensure that rule evaluation for one tenant does not impact another. The action execution uses a worker queue (Kafka) to decouple rule evaluation from action execution, so the core Jira workflow is not blocked by automation actions. Rate limiting per tenant prevents runaway automations, and all rule executions are logged asynchronously to an audit service for debugging and compliance.

Q4: How would you design the Jira SLA management system to handle timezone-aware business hours?

SLA management with business hours requires a calendar service that maps each tenant business hours, holidays, and time zone configurations. The calendar service stores business hour definitions as a set of weekly time ranges (e.g., Monday-Friday 9:00 AM - 5:00 PM EST) and a list of holiday dates. When an SLA starts, the SLA engine calculates the deadline by adding the SLA goal to the start time using the calendar service AddBusinessTime method, which skips non-business hours and holidays. The SLA timer is not continuously running; instead, the remaining time is calculated on-demand when requested (lazy evaluation). When an agent asks for the SLA status of a request, the engine calculates the elapsed business time from start to now, and the remaining business time from now to the deadline. This approach avoids the complexity of maintaining real-time timers for thousands of concurrent SLAs and handles edge cases like timezone transitions and daylight saving time correctly. SLA breach notifications are generated by a scheduled job that scans for SLAs approaching their deadlines and sends alerts through the notification service.

Q5: How would you design the search system across Jira and Confluence?

Cross-product search requires a unified search index that aggregates content from both Jira and Confluence into a single Elasticsearch cluster. Each product service publishes change events to Kafka when content is created or modified, and a search indexing consumer processes these events to update the Elasticsearch index. The index schema includes a product field to distinguish between Jira issues and Confluence pages, along with common fields like title, body, author, lastModified, and tenantId. The search API accepts a query string and optional filters (product, project, space, date range, author) and returns ranked results from the unified index. Relevance scoring uses a combination of text match quality (BM25), recency (exponential decay based on last modified date), and popularity (view and edit counts). Tenant isolation is enforced by injecting a tenantId filter into every search query at the API gateway level, preventing cross-tenant data leakage. For large tenants with high write volumes, the indexing pipeline uses near-real-time refresh intervals (1-2 seconds) to keep the index reasonably current while avoiding excessive indexing load.

Q6: How would you design Bitbucket Pipelines to support parallel step execution with shared artifacts?

Parallel step execution in Bitbucket Pipelines requires a directed acyclic graph (DAG) scheduler that determines which steps can run in parallel and which must wait for dependencies. The pipeline YAML is parsed into a DAG where nodes represent steps and edges represent dependencies (explicit step dependencies and artifact dependencies). The scheduler traverses the DAG to identify steps with no unmet dependencies and dispatches them to the build infrastructure simultaneously. Each step runs in an isolated Docker container with access to a shared artifact volume (backed by EFS or S3). When a step produces artifacts, they are uploaded to the shared storage and indexed with metadata (step name, artifact paths). When a dependent step needs those artifacts, the build infrastructure mounts or downloads them before executing the step. The pipeline executor monitors all running steps and fails fast if any step in the critical path fails (when fail-fast is enabled), canceling dependent steps and cleaning up resources. The build infrastructure uses a container pool with pre-warmed containers to reduce cold start latency, and resources are allocated using a fair scheduler that ensures no single repository can monopolize the build infrastructure.

Q7: How would you handle the migration of Confluence pages from Server XML format to the cloud ADF format?

Confluence Server stores page content in a custom XML-based storage format, while Confluence Cloud uses Atlassian Document Format (ADF), a JSON-based structured document representation. The migration requires a bidirectional transformation engine that converts Server XML to ADF while preserving content fidelity. The transformation must handle several categories of content: simple text and formatting (straightforward mapping between XML elements and ADF nodes), macros (which have different implementations between Server and Cloud, requiring a macro compatibility layer), embedded images and attachments (which need to be re-hosted in cloud storage with updated references), and cross-page links (which must be rewritten to use cloud page IDs instead of Server page IDs). The transformation engine processes pages in dependency order (parent pages before child pages) to ensure that link targets exist before pages that reference them. Each transformation is logged with the original and transformed content, allowing manual review of pages where the transformation confidence is low. The migration tool provides a dry-run mode that performs the transformation without writing to the cloud environment, allowing administrators to review the results and identify issues before committing to the migration.

Q8: How would you design the notification system to handle millions of notifications across all Atlassian products?

The notification system must handle high-volume notification delivery across multiple channels (email, in-app, mobile push, Slack/Teams integrations) while respecting user preferences and rate limits. I would use a multi-stage notification pipeline: event ingestion (capturing notification events from all products through Kafka), preference resolution (checking each recipient notification settings to determine if and how they want to be notified), batching (grouping related notifications to reduce delivery volume � for example, batching all Jira issue updates into a single daily digest email), and delivery (sending notifications through the appropriate channel using dedicated delivery services for each channel). The batching stage is critical for preventing notification fatigue and reducing infrastructure costs. The system must also handle deduplication (preventing the same notification from being sent multiple times if the underlying event is processed more than once), throttling (limiting the rate of notifications per user to prevent flooding), and delivery tracking (recording when notifications are sent, delivered, and opened for analytics and debugging purposes).

Q9: How would you design the Bitbucket code review system to support inline comments and conversation threads?

The code review system must support commenting on specific lines of code, resolving conversations, and tracking the relationship between comments and code versions. I would model the review as a pull request entity that contains a collection of review threads, where each thread is anchored to a specific file path and line number in the diff. When new commits are pushed to the source branch, the diff changes and previously anchored comments may no longer be relevant to the current code. The system must detect when a comment anchor becomes stale (because the code around it has changed) and mark the thread accordingly, allowing reviewers to determine which feedback has been addressed and which still applies. The diff engine must handle three-way merges (comparing the base branch, the source branch at the time of the comment, and the current source branch) to determine whether a comment thread is still applicable. Inline comments are stored with their file path, line number, and the commit SHA they reference, allowing the system to reconstruct the review context at any point in the pull request history.

Q10: How would you design the Trello board to support 10,000+ cards while maintaining drag-and-drop responsiveness?

Rendering a board with 10,000+ cards requires a virtualized rendering approach where only the visible cards are rendered in the DOM. The board client maintains the complete card list in memory but only creates DOM elements for cards within or near the current viewport. As the user scrolls, cards entering the viewport are rendered and cards leaving the viewport are removed. This approach keeps the DOM size constant regardless of the total card count. The card data is fetched in pages from the server, with the initial page containing the first 100 cards and subsequent pages loaded on scroll. Drag-and-drop must work smoothly even with virtualized rendering, which requires careful coordination between the virtual list and the drag-and-drop library to ensure that drop targets are correctly identified even when cards are being dynamically added and removed from the DOM. The board state is managed using a immutable data structure on the client side, with WebSocket updates applied as patches to the local state. When a card is moved, the client optimistically applies the change locally and sends the move operation to the server, which validates the operation and broadcasts the change to other clients. Conflict resolution uses a vector clock approach to detect concurrent modifications and merge them deterministically.

Ayodhyya - System Design Blog Series | Atlassian Software Collaboration Platform - Senior+ Guide

Article #240 | Published: July 15, 2026