Instead of writer pinning, we'll track the last transaction ID of each
write in the session. Then on each read we'll wait for the replica to
report that this transaction is available.
If it doesn't become available within a reasonable timeout, we'll
proceed anyway, and accept the possibility of a stale read.
The hope here is that most of the time, the replica is caught up in the
time between a write request and the following read request. If it's
not, we now have a little tolerance to wait for it, which hopefully
proves enough to stale reads are not encountered in normal use.
We also disable the writer affinity opt-out mechanism that we had
before, since we will no longer be using writer affinity at the load
balancer.
User flows when editing a card look like:
- Click "Edit" → the closure buttons are replaced by "Save changes"
- Submit card form → Saves and restores closure buttons
- Press ESC while editing → Cancels and restores closure buttons
Also, renamed for clarity:
- _title.html.erb → _content.html.erb
Co-Authored-By: Claude <noreply@anthropic.com>
- redirect avatar image requests to the rails_blob_url, instead of
streaming them through the web app
- use a thumbnail variant for avatar images
- only put avatar initials behind the stale? check (not the image
redirect, which would result in browsers rendering broken images when
an avatar is changed, until max-age expires)
Locally, having stale_while_revalidate works great, but in production when we are behind CloudFlare, this results in an old image being shown after you upload a new one
See: https://app.fizzy.do/5986089/cards/2978
The check-then-act pattern in `register_activity_spike` has been
replaced with `find_or_create_by!` to eliminate the race condition
that could lead to creating multiple activity spikes for a card. To
support this change, the `card_id` index on `cards_activity_spikes`
has been made unique.
ref: https://app.fizzy.do/5986089/cards/3063
Primarily this is in tests (which were caught by temporarily
introducing acts_as_tenant and enabling safety checks), but notably
action cable connections were not working properly, and that's now
fixed.
Lean on ActiveRecord models for searching and strip out the raw SQL.
Replaces the search_index_* tables with sharded search_records_* tables
as that allows us to use a Search::Record model name.
A Class is dynamically created for each record table shard so that we
and we can access it via the Search::Record.for_account(account_id)
method.