Skip to main content

Optimising Beacon for large accounts

If you have more than 100,000 records, we recommend reading the following to help ensure your Beacon account runs as smoothly as possible

snow capped mountains with valley and forest

Beacon lets you filter on anything you want, including across record types. When your database is small, this is fast.

On a larger database, some queries can be slower than expected, and some can time out. A filter that cannot finish within 30 seconds fails rather than running indefinitely. Timeouts come from broad joins, unaccelerated ranges, and huge counts — not from ordinary lookups.


How filtering works, under the hood

Before the best practices, it helps to understand how Beacon’s filter engine works.

For each record type, Beacon stores an optimised index of your data, built specifically for filtering. The index is updated in the background after a record is saved.

A record written in the last few seconds can be missing from a filter until that update finishes. After a large import, allow a short delay before relying on lists and counts.

Lookups

This allows for “lookups”, for example:

Lookups will always be extremely fast - Beacon does not scan every person. It knows where to look in the index.

Listing & sorting

When filtering for a list of records, the principle is the same. Beacon knows where to look in the filter index.

Finding a page of donations is extremely fast. The list also has to come back in a useful order.

Lists default to newest record first. That order is built into the filter index (record number, which tracks creation order), so Beacon can stop after the first page. Sorting by created date or by record ID, newest first, uses this path. A filtered list of the 100 newest donations is the same kind of lookup.

Filtering on created date is separate from sorting by it. A condition such as “created this year” is a real date comparison, and created date is accelerated on every record type.

Created date vs custom sorting

Take two similar cases. You want three donations, sorted differently:

  1. Sort by created date (3 most recent donations)

  2. Sort by payment amount (3 largest donations)

At a small number of records, both are quick. At scale, they diverge.

Most recent first is extremely fast. Beacon reads the first matching records from the top of the index and stops.

3 largest donations has no index order to walk. Beacon gathers the matching records and sorts them. That stays quick when the filter is already narrow (for example, donations this month). If the filter matches more than 100,000 records, the list is refused, with a message to sort by created date descending or to narrow the filter first. Beacon does not scan the whole record type to return the top three.

The same cap applies to every sort other than created date or record ID, newest first. That includes last modified, locked date, payment date, and amount — including fields that are accelerated for filtering. Acceleration speeds up the filter. It does not speed up the sort.

Created date ascending is a different, slower plan once a filter is applied. Prefer descending.

The below makes this point well:

Range scans (dates, number, currency fields)

Exact matches (email is X, type is Y) are what the filter index is built for, and they stay fast at any size.

The core fields in the appendix, plus created date, last modified date, and locked date on every record type (including custom record types), are accelerated for ranges. Inequalities and calendar windows on those fields are direct index lookups. Examples:

  • Amount > £10

  • Payment date less than 45 days ago

  • Date of birth is more than 6,575 days ago (over 18)

  • Created this year, or last modified this month

Currency ranges compare the amount in the account’s base currency.

Custom fields and core fields that are not in the appendix are not accelerated. On those fields, calendar buckets such as “this year” and “last month” stay fast. Open-ended ranges (“before this date”, “more than N days ago”) scan candidates and get slow on large record types. Prefer a calendar bucket for those fields.

Each record type has at most three accelerated fields. See the appendix for the full list, and for fields that are deliberately left off it.

Joins

CRMs are about relationships and how records link together. Common filters:

  • List all payments, where Campaign Type is “General fundraising”

  • List all people, where “Related payments > 0” (people who’ve made a payment)

  • List all people, where email contains “beaconcrm.org”

These leave the single-index lookup. Beacon cannot just read the top records from the index. It has to combine results from more than one place, which is slower.

Beacon runs each side and then keeps the records that satisfy the combination. After that, it still has to sort the combined set. This is a lot of work when:

  • The number of records matched by each filter set is very large

  • Complex nested AND/OR conditions are used between sets

  • The combined filters match a large number of results that then need to be sorted

It’s important to mention that these are not Beacon restrictions - all databases will struggle with more complex joins and conditions.

Which filter conditions cause joins?

  • Meets criteria

  • Related X

  • Unique / not unique

  • Contains / starts with / ends with

    • Note: field values are stored in a separate “search index”, which the filter engine needs to join against.

  • Rules-based access, when the user cannot see every record of that type

Joins & OR filters

OR filters with joins are particularly heavy.

With OR, both conditions match, and Beacon combines the two lists. A broad OR is heavier than a broad AND:

Aggregation filters: Related X and unique

These are different, and they need different rules:

Related X (count, sum, or average of related records) is answered directly while the related set stays under about 100,000 rows. Above that, Beacon computes the answer in the analytics warehouse and returns the exact figure. That is slower than a list, and a record saved in the last few seconds may not be included yet. A Related X condition that runs often — “people with any payment”, “total donations this year” — should be a rollup field instead. The stored number is then an ordinary filter.

Unique / not unique is always answered on the operational database. There is no warehouse fallback. Avoid these on high-volume record types.

Counting

A list stops after one page. A count cannot: every matching record has to be included.

Beacon counts on the operational database up to 100,000 matches. Past that, the count is computed in the analytics warehouse, and the number you get back is the real total. It is slower than the list, and a record saved in the last few seconds may not be included yet.

Counts are much faster when the filter is specific, because there are fewer rows to count. A dashboard card that counts an entire multi-million record type takes the slow path every time it loads. Exports and whole-type reports belong on that warehouse path. Interactive lists should stay selective.


Best practices

  • Sort by created date descending (most recent first) wherever you can.

  • Narrow the filter before sorting by any other field. Amount, payment date, last modified, and custom fields are all capped at 100,000 matching records.

  • Minimise joins. Store the answer you filter on:

    • Rollup fields, instead of Related X conditions. Example: total donations this year.

    • Smart fields that copy a value from a record you point at, instead of Meets criteria. Example: store Fund > Code on the Payment record type.

  • When you do use a join:

    • Make each sub-filtered set as specific as possible. A smaller set is less work.

    • Beacon consolidates multiple Meets criteria conditions on the same hop. Adding conditions there narrows the set.

    • Make the final matched list as specific as possible, so there is less to sort at the end.

  • Minimise OR filters. A broad OR combines both sides.

  • On date, number, and currency fields:

    • Filter on the accelerated core fields in the appendix when you have a choice.

    • On any field that is not accelerated, filter by a calendar bucket (“this year”, “last month”) rather than “more than N days ago” or “before this date”.

    • On an accelerated field, those open ranges are fine. Date of birth “more than 6,575 days ago” is a direct lookup.

  • Prefer “starts with” or “ends with” over “contains” when you know the start or end of the value.

  • Avoid unique / not unique on large record types.

  • After a large import, wait for the filter index to catch up before treating a list or count as complete.


Limitations

  • Sorting by any column other than created date or record ID, newest first, is refused when the filter matches more than 100,000 records. Narrow the filter, or sort by created date descending.

  • Created date ascending is slower than descending once a filter is applied.

  • Custom fields and custom record types are never accelerated for ranges. Each core record type has at most three accelerated fields (appendix).

  • Related X over a related set larger than about 100,000 rows, and counts over 100,000 matches, run in the analytics warehouse. They return the exact number, they are slower, and they can lag new writes by a few seconds.

  • Unique / not unique has no warehouse fallback. Avoid it on high-volume record types.

  • “Contains” joins a separate search index. “Starts with” and “ends with” do not.

  • The filter index updates in the background. A just-saved record can be absent from filters for a few seconds, longer after a bulk import.

  • A filter that runs longer than 30 seconds fails.


Appendix: core fields accelerated for range scans

These core fields are accelerated for fast range scans. Created date, last modified date, and locked date are accelerated on every record type, including custom record types, and are not repeated in the table.

Custom fields are not accelerated. Currency amounts are compared in the account’s base currency.

Record type

Fields

person

date_of_birth

payment

amount, amount_net, payment_date

gift_aid_declaration

start_date, end_date, declaration_date

activity

date

alumnus

start_date, leave_date

campaign

start_date, end_date

case

date_opened, date_closed

collection_tin_placement

checkout_date, return_date

education_history

completion_date

event_attendee

signup_date

event

start_date, end_date

extra_curricular_history

start_date, leave_date

fundraising_page

start_date, target, total_payments

grant_application

application_date, amount_requested, approval_date

grant_evaluation

due_date, date_completed

grant_payment

amount, payment_date

grant

amount, close_date, deadline_date

legacy

pledge_date, date_of_death, estimated_value

major_gift

amount, start_date, end_date

membership

start_date, expiry_date

payout

banked_date, total, net_amount

subscription

amount, first_payment_date, cancelled_date

task

due_date

ticket

face_value

volunteer_application

application_date, possible_start_date

Deliberately not accelerated (the three-field limit is already used, or the record type is low volume):

  • Subscription → next payment date

  • Person → volunteer start date

  • Event → total donations

  • Campaign → financial goal, financial total

  • Fund, Grant programme, and Gift Aid claim — no accelerated fields. These types are typically small, so an unaccelerated range stays cheap.

Did this answer your question?