<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Al Mokhtar Bekkour — Writing</title>
    <link>https://almokhtar.dev/blog</link>
    <atom:link href="https://almokhtar.dev/rss.xml" rel="self" type="application/rss+xml" />
    <description>Notes on building and scaling multi-tenant SaaS in Rails and Go.</description>
    <language>en-ca</language>
    <item>
      <title>Vendor the small algorithm, don't add the dependency</title>
      <link>https://almokhtar.dev/blog/vendor-the-algorithm-not-the-dependency/</link>
      <guid isPermaLink="true">https://almokhtar.dev/blog/vendor-the-algorithm-not-the-dependency/</guid>
      <pubDate>Fri, 10 Jul 2026 00:00:00 GMT</pubDate>
      <description>The reflex to reach for a package for everything is wrong for small, stable, well-specified code. Why I vendored the FSRS-5 spaced-repetition algorithm into my iOS app instead of adding a dependency — and where vendoring is the wrong call.</description>
    </item>
    <item>
      <title>gRPC vs REST: when I actually reach for each (and when gRPC is a mistake)</title>
      <link>https://almokhtar.dev/blog/grpc-vs-rest-when-to-use/</link>
      <guid isPermaLink="true">https://almokhtar.dev/blog/grpc-vs-rest-when-to-use/</guid>
      <pubDate>Mon, 15 Jun 2026 00:00:00 GMT</pubDate>
      <description>gRPC vs REST gets argued in the abstract. I've shipped both — a Rails↔Go deploy engine on gRPC, browser uploads on plain WebSockets. Here's my actual rule for choosing, and the cases where gRPC is the wrong call.</description>
    </item>
    <item>
      <title>Streaming a deploy from a Go agent to Rails over gRPC</title>
      <link>https://almokhtar.dev/blog/streaming-deploys-go-grpc-rails/</link>
      <guid isPermaLink="true">https://almokhtar.dev/blog/streaming-deploys-go-grpc-rails/</guid>
      <pubDate>Wed, 06 May 2026 00:00:00 GMT</pubDate>
      <description>One-click deploy means running git clone, docker build and docker run on a remote box and showing every log line live. How I stream that: a Go agent that pipes each command's output over a gRPC server-stream to a Rails control plane, which turns it into a live deploy log.</description>
    </item>
    <item>
      <title>Capturing a bug in the browser: wrapping fetch, XHR and console (and redacting PII first)</title>
      <link>https://almokhtar.dev/blog/capturing-bugs-in-the-browser/</link>
      <guid isPermaLink="true">https://almokhtar.dev/blog/capturing-bugs-in-the-browser/</guid>
      <pubDate>Thu, 19 Feb 2026 00:00:00 GMT</pubDate>
      <description>A screen recording shows what broke, not the console error or failed request behind it. How to capture those by wrapping fetch/XHR/console without breaking the page — and strip passwords, tokens and card numbers client-side before anything leaves the browser.</description>
    </item>
    <item>
      <title>Streaming uploads over WebSockets in Go with io.Pipe backpressure</title>
      <link>https://almokhtar.dev/blog/streaming-uploads-websockets-go-iopipe/</link>
      <guid isPermaLink="true">https://almokhtar.dev/blog/streaming-uploads-websockets-go-iopipe/</guid>
      <pubDate>Tue, 27 Jan 2026 00:00:00 GMT</pubDate>
      <description>A browser MediaRecorder emits video chunks for minutes on end. Buffering each recording in RAM doesn't scale. How I stream chunks straight to storage over a WebSocket in Go, using io.Pipe so the socket buffer becomes the backpressure.</description>
    </item>
    <item>
      <title>Low-latency on-device captions in Swift with SpeechAnalyzer</title>
      <link>https://almokhtar.dev/blog/on-device-streaming-captions-ios-speechanalyzer/</link>
      <guid isPermaLink="true">https://almokhtar.dev/blog/on-device-streaming-captions-ios-speechanalyzer/</guid>
      <pubDate>Tue, 18 Nov 2025 00:00:00 GMT</pubDate>
      <description>Transcribing a 50-minute audiobook chapter before showing the first caption is unusable. How I get the first line in ~1–2s: a playhead-windowed AsyncStream feeding iOS 26's SpeechAnalyzer, with the feeder and results consumer racing on separate tasks — all on-device.</description>
    </item>
    <item>
      <title>Vendoring FSRS spaced repetition as a pure Swift function</title>
      <link>https://almokhtar.dev/blog/fsrs-spaced-repetition-in-swift/</link>
      <guid isPermaLink="true">https://almokhtar.dev/blog/fsrs-spaced-repetition-in-swift/</guid>
      <pubDate>Tue, 22 Apr 2025 00:00:00 GMT</pubDate>
      <description>My audiobook app turns highlights into flashcards scheduled by FSRS-5 — the algorithm Anki uses. Why I vendored the math as a pure, dependency-free, unit-testable Swift function instead of adding a package, and how the scheduler actually works.</description>
    </item>
    <item>
      <title>Syncing invoices to QuickBooks when the other side keeps changing under you</title>
      <link>https://almokhtar.dev/blog/quickbooks-resilient-integration-rails/</link>
      <guid isPermaLink="true">https://almokhtar.dev/blog/quickbooks-resilient-integration-rails/</guid>
      <pubDate>Mon, 14 Aug 2023 00:00:00 GMT</pubDate>
      <description>Third-party accounting APIs don't fail cleanly. Customers get deleted in QuickBooks behind your back, tokens expire mid-request, and retries duplicate records. Here's how one Rails integration stayed idempotent and learned to heal itself.</description>
    </item>
    <item>
      <title>The Postgres features I reach for before I reach for a cache</title>
      <link>https://almokhtar.dev/blog/postgres-performance-multi-tenant-rails/</link>
      <guid isPermaLink="true">https://almokhtar.dev/blog/postgres-performance-multi-tenant-rails/</guid>
      <pubDate>Thu, 09 Mar 2023 00:00:00 GMT</pubDate>
      <description>A slow multi-tenant Rails app usually isn't slow because Postgres is slow. It's slow because the work is being done in Ruby that Postgres would do better. DISTINCT ON, array overlap, and full-text search — with the honest limits.</description>
    </item>
    <item>
      <title>Roles that contain roles: an RBAC resolved by a recursive Postgres query</title>
      <link>https://almokhtar.dev/blog/rbac-recursive-postgres-cte/</link>
      <guid isPermaLink="true">https://almokhtar.dev/blog/rbac-recursive-postgres-cte/</guid>
      <pubDate>Tue, 11 Oct 2022 00:00:00 GMT</pubDate>
      <description>When a role can inherit other roles, permissions stop being a list and become a graph — and graphs are where the ORM taps out. Here's a role system resolved in one WITH RECURSIVE query, cached per tenant, and enforced through method_missing.</description>
    </item>
    <item>
      <title>Schema-per-tenant Rails, and the bug that only shows up in a background job</title>
      <link>https://almokhtar.dev/blog/schema-per-tenant-rails-sqs/</link>
      <guid isPermaLink="true">https://almokhtar.dev/blog/schema-per-tenant-rails-sqs/</guid>
      <pubDate>Tue, 17 May 2022 00:00:00 GMT</pubDate>
      <description>Isolating tenants in separate Postgres schemas is the easy 90%. The 10% that bites is keeping that isolation when work leaves the web request and runs on an SQS worker. Here's how we carried the tenant across the queue.</description>
    </item>
    <item>
      <title>Wiring up an open-banking feed in Rails without leaking or getting locked in</title>
      <link>https://almokhtar.dev/blog/open-banking-integration-rails/</link>
      <guid isPermaLink="true">https://almokhtar.dev/blog/open-banking-integration-rails/</guid>
      <pubDate>Tue, 09 Nov 2021 00:00:00 GMT</pubDate>
      <description>Bank aggregators are slow, asynchronous, and full of PII you must never log. Here's how I integrated one: a swappable provider, encrypted credentials, a recursively-redacting request logger, and a poll-until-done connection job.</description>
    </item>
    <item>
      <title>Auto-categorizing bank transactions with Postgres fuzzy matching (no ML)</title>
      <link>https://almokhtar.dev/blog/bank-transaction-matching-postgres-levenshtein/</link>
      <guid isPermaLink="true">https://almokhtar.dev/blog/bank-transaction-matching-postgres-levenshtein/</guid>
      <pubDate>Mon, 30 Aug 2021 00:00:00 GMT</pubDate>
      <description>'UBER *EATS 8829' and 'UBER* EATS' are the same recurring expense, but the strings don't match. How I auto-categorize bank feeds with Postgres levenshtein() and soundex() against a user's own history — no machine learning.</description>
    </item>
    <item>
      <title>Charging a card once in Rails: the guard I shipped, and the gaps I didn't close</title>
      <link>https://almokhtar.dev/blog/exactly-once-payments-rails/</link>
      <guid isPermaLink="true">https://almokhtar.dev/blog/exactly-once-payments-rails/</guid>
      <pubDate>Mon, 24 May 2021 00:00:00 GMT</pubDate>
      <description>A payments flow is 'at least once' at every layer — retries, double-clicks, replayed webhooks. Here's the guard that stops the common cases, an honest accounting of the ones it doesn't, and how I'd close them.</description>
    </item>
    <item>
      <title>Building an invoice tax engine in Rails that accountants trust</title>
      <link>https://almokhtar.dev/blog/rails-invoice-tax-engine-bigdecimal/</link>
      <guid isPermaLink="true">https://almokhtar.dev/blog/rails-invoice-tax-engine-bigdecimal/</guid>
      <pubDate>Tue, 16 Mar 2021 00:00:00 GMT</pubDate>
      <description>Tax-inclusive and tax-exclusive pricing round differently, and one cent of drift makes an invoice look broken. How I built a tax engine with the Strategy pattern, BigDecimal, and spreadsheet-verified golden-master tests.</description>
    </item>
    <item>
      <title>Polymorphic models in Laravel: one table, two different records (Eloquent morphs)</title>
      <link>https://almokhtar.dev/blog/polymorphic-claims-model-laravel/</link>
      <guid isPermaLink="true">https://almokhtar.dev/blog/polymorphic-claims-model-laravel/</guid>
      <pubDate>Mon, 20 Jan 2020 00:00:00 GMT</pubDate>
      <description>A CRM tracked two kinds of record that shared almost no fields but flowed through the same statuses, teams, and dashboards. I modeled it as one table with a polymorphic child — and hung the whole framework off the shared root. Mostly it worked; a couple of seams are worth showing.</description>
    </item>
    <item>
      <title>Aggregating in SQL vs in memory: a Laravel dashboard performance lesson</title>
      <link>https://almokhtar.dev/blog/dashboards-sql-vs-in-memory-laravel/</link>
      <guid isPermaLink="true">https://almokhtar.dev/blog/dashboards-sql-vs-in-memory-laravel/</guid>
      <pubDate>Tue, 15 Oct 2019 00:00:00 GMT</pubDate>
      <description>Two chart methods in an old Laravel CRM — one aggregates counts in SQL, the one right below it loads every record into memory and sums them in a PHP closure. A dashboard performance lesson at millions of rows, and the junior-to-mid growth arc behind it.</description>
    </item>
    <item>
      <title>Strangling a legacy database into Laravel (my first big migration, mistakes included)</title>
      <link>https://almokhtar.dev/blog/strangling-a-legacy-database-laravel/</link>
      <guid isPermaLink="true">https://almokhtar.dev/blog/strangling-a-legacy-database-laravel/</guid>
      <pubDate>Mon, 22 Jul 2019 00:00:00 GMT</pubDate>
      <description>My first serious Laravel app wasn't greenfield — it had to swallow years of data from a legacy call-center system with tables named `lead` and `hotkey`. Here's the anti-corruption layer and the chunked migration engine I built, and the parts I'd do differently now.</description>
    </item>
    <item>
      <title>Splitting money in a marketplace: Stripe Connect, destination charges, and getting hosts paid</title>
      <link>https://almokhtar.dev/blog/stripe-connect-marketplace-payments/</link>
      <guid isPermaLink="true">https://almokhtar.dev/blog/stripe-connect-marketplace-payments/</guid>
      <pubDate>Mon, 18 Feb 2019 00:00:00 GMT</pubDate>
      <description>A marketplace can't just charge a card — the money has to split between the platform and the host, and the host has to be onboarded for payouts. The real Stripe Connect wiring: Express accounts, hosted onboarding, and a destination charge that skims the platform fee.</description>
    </item>
    <item>
      <title>The availability search that loaded every booking into memory — a Rails date-range fix</title>
      <link>https://almokhtar.dev/blog/marketplace-availability-search-nplus1/</link>
      <guid isPermaLink="true">https://almokhtar.dev/blog/marketplace-availability-search-nplus1/</guid>
      <pubDate>Thu, 04 Oct 2018 00:00:00 GMT</pubDate>
      <description>A marketplace 'search by dates' feature quietly became O(bookings × listings): it pulled every booking in the database into Ruby and eliminated listings one query at a time. The fix was a single overlap query the codebase already had — and never used.</description>
    </item>
  </channel>
</rss>