message-queues

2 posts

discord

Osprey: Open Sourcing our Rule Engine (opens in new tab)

Discord is open-sourcing Osprey, a rule engine designed to help platforms detect and respond to emerging safety threats in real time. Built with ROOST and internet.dev, it processes platform events, evaluates configurable rules, and produces actionable verdicts with minimal engineering effort. Osprey emphasizes scale, rapid rule deployment, transparency, extensibility, and continuous improvement. ## Goals for a Modern Rule Engine Osprey was designed around several requirements: - Process thousands of events per second in real time. - Let teams create and deploy expressive rules within minutes. - Return clear verdicts indicating whether activity is safe, suspicious, or malicious. - Explain how rules were executed and expose errors for investigation and debugging. - Support feedback loops that improve future detection rules. - Remain extensible enough to address new attack patterns. ## Osprey’s Processing Model Osprey accepts platform events called **Actions** through either: - Synchronous gRPC requests. - Asynchronous message queues. The engine evaluates these actions using rules written in SML, a Python-based rule language. Rules can use Python UDFs, Features, and Effects, while synchronous requests can return Verdict effects directly to callers. Outputs are sent to Apache Druid, which powers investigation and analysis tools. ## Actions Actions are JSON-like events submitted to Osprey. - Each action type has a unique name and schema. - Callers can customize the payload with relevant platform data. - Example data includes login attempts, user IDs, usernames, email addresses, and IP addresses. - Rules extract and evaluate values from these action payloads. ## Rules and SML Rules are the central mechanism for detecting suspicious behavior. - SML uses a Python-inspired syntax intended to be accessible to less-technical rule authors. - Rules can reference other rules and extracted data. - Static validation enforces consistent rule-writing practices. - Validation can be extended with Python, from naming conventions to more complex domain-specific checks. - Example rules identify a known spammer by email and apply a `spammer` label to the associated user entity. ## User-Defined Functions UDFs are regular Python functions that extend Osprey’s rule language and standard library. - Built-in capabilities such as `Rule`, `WhenRules`, and `JsonData` are implemented as UDFs. - Teams can add their own UDFs when integrating Osprey into other products. - UDFs can retrieve information from external services, including machine-learning models. - They can be configured for asynchronous execution and access external-service providers through the execution context. - A sample UDF obtains a link-spam score from an external prediction service. ## Features and Entities Features are globally named variables produced during Osprey executions. - Features are exported to Apache Druid for later querying and investigation. - Prefixing a variable name with `_` keeps it local instead of exporting it. - Examples include `UserId` and `UserEmail`, extracted from JSON action data. - Entities are a specialized type of Feature representing persistent objects such as users, servers, or email addresses. - Entities can receive effects such as labels, classifications, and signals. - Entity types determine which effects are valid through static validation. - The Osprey interface provides dedicated Entity Views for examining an entity’s history. ## Effects Effects are outcomes triggered when rules evaluate as true. - They are validated and processed in aggregate after execution. - Effects can modify or annotate entities with labels, classifications, or signals. - Verdict effects can be returned synchronously to inform the requesting service of a safety determination. Osprey’s open-source release gives platforms a reusable foundation for real-time trust and safety enforcement. Teams interested in adopting it can explore the repository at [github.com/roostorg/osprey](https://github.com/roostorg/osprey).

discord

How Discord Indexes Trillions of Messages (opens in new tab)

Discord’s original Elasticsearch-based search system worked well for billions of messages but became fragile as message volume and cluster size grew. Redis queues could drop messages, bulk operations failed too broadly, large clusters were difficult to operate, and individual indices could hit Lucene’s roughly two-billion-document limit. Discord’s response was to modernize the platform with Kubernetes, the Elastic Kubernetes Operator, and a multi-cluster “cell” architecture built from smaller clusters. ## The Original Search Architecture - Messages were stored in Elasticsearch indices distributed across two clusters. - Data was sharded by Discord server (guild) or direct message, keeping each guild’s messages together for efficient queries. - Messages were indexed lazily because not every message is searched. - Redis-backed queues supplied workers with message batches for Elasticsearch bulk indexing. ## Problems with the Existing System ### Redis Queue Message Loss - The realtime indexing queue relied on Redis. - When Elasticsearch failures caused the queue to back up, Redis CPU usage could reach its limit. - Once overloaded, Redis began dropping messages, making the indexing pipeline unreliable. ### Fault-Intolerant Bulk Indexing - A batch could contain messages belonging to many different Elasticsearch indices and nodes. - A batch of 50 messages might fan out to dozens of nodes. - If one message failed because its target node was unavailable, Elasticsearch treated the entire bulk request as failed. - All messages were then re-enqueued, increasing queue pressure. - In a 100-node cluster with batches of 50 messages, a single failed node gave each batch roughly a 40% chance of encountering a failure. ### Large-Cluster Overhead - Adding nodes and indices enabled horizontal scaling but increased coordination overhead. - Bulk operations fanned out across more nodes, slowing indexing. - Larger clusters also had a higher probability that some node would fail. ### Difficult Upgrades and Restarts - The system lacked sufficient resilience to individual node outages, making rolling restarts unsafe. - Clusters exceeding 200 nodes and containing terabytes of data would have taken too long to drain gracefully. - Discord therefore remained on outdated operating-system and Elasticsearch versions. - Addressing the Log4Shell vulnerability required taking the entire search system offline while every node was restarted. ### Oversized Indices - Some indices accumulated messages from extremely large guilds. - Each Elasticsearch index is backed by a Lucene index with a limit of approximately two billion documents. - Once that limit was reached, all further indexing failed. - Discord temporarily recovered by identifying and deleting guilds created primarily for message spam, but this was not viable for legitimate high-volume communities. ## Moving Elasticsearch to Kubernetes - Discord chose Kubernetes to improve operational flexibility and resource efficiency. - The Elastic Cloud on Kubernetes (ECK) Operator could define cluster topology and configuration declaratively. - Kubernetes would automate operating-system upgrades. - ECK provided tools for safer rolling restarts and Elasticsearch upgrades. - This marked Discord’s first move toward managing stateful Elasticsearch infrastructure on Kubernetes. ## Smaller Multi-Cluster Cells - Discord planned to replace very large clusters with a larger number of smaller Elasticsearch clusters. - Smaller clusters reduce coordination overhead and limit the impact of individual node failures. - A cell-based design also provides a more manageable scaling and operational boundary than clusters with hundreds of nodes. Discord’s experience demonstrates that scaling Elasticsearch is not only a matter of adding nodes. Reliable operation requires isolating failures, avoiding oversized indices and fan-out-heavy batches, and designing deployment infrastructure that supports upgrades without taking search offline.