# Today I learned > Articles and learnings on Elixir, Rust, Ruby, Go, Javascript, Platform engineering, SRE. Public Ghost content for AI and LLM tooling. This file includes a bounded export of public pages first, then recent public posts. Append `.md` to any post or page URL to get the content in Markdown (for example, `/example-post.md`). ## Pages ### About this site URL: https://til.codes/about/ Last updated: 2024-07-13T20:54:24.000Z Today I learned is an independent publication launched in July 2024 by Manu Ajith. If you subscribe today, you'll get full access to the website as well as email newsletters about new content when it's available. Your subscription makes this site possible, and allows Today I learned to continue to exist. Thank you! ### Access all areas By signing up, you'll get access to the full archive of everything that's been published before and everything that's still to come. Your very own private library. ### Fresh content, delivered Stay up to date with new content sent straight to your inbox! No more worrying about whether you missed something because of a pesky algorithm or news feed. ### Meet people like you Join a community of other subscribers who share the same interests. --- ### Start your own thing Enjoying the experience? Get started for free and set up your very own subscription business using [Ghost](https://ghost.org/?ref=til.codes), the same platform that powers this website. ## Posts ### Gremlins After Midnight: How 794 Git Packs Hit the macOS 256 File Limit URL: https://til.codes/gremlins-after-midnight-how-794-git-packs-hit-the-macos-256-file-limit/ Last updated: 2026-06-02T21:39:40.000Z This was on Determinate Nix 3.15.2 (Nix 2.33.1). If you are on something newer, the specific failure below may not reproduce: Nix 2.34 and Determinate Nix 3.16 both started raising the soft descriptor limit at process startup, which defuses this exact class of crash. The mechanics are still worth understanding, because the underlying ingredients have not changed. I ran `darwin-rebuild switch` the way I have a thousand times. Same flake, same machine, nothing exotic in the diff. It had worked that morning. This time it fell over before it built a single derivation: ``` error: opening Git repository "~/.cache/nix/tarball-cache-v2": could not open '~/.cache/nix/tarball-cache-v2/config': Too many open files ``` Too many open files. On a rebuild that touches my own config. Nothing was leaking, nothing was in a loop. The machine had simply run out of something, and the something was file descriptors. ## What a file descriptor limit even is Every open file, socket, and pipe in a Unix process is a file descriptor: a small integer that the kernel hands back so you can refer to it later. There is a cap on how many a single process may hold open at once, and on macOS there are two of them. The soft limit is what a process gets by default, and it can be raised on its own up to the hard limit. The hard limit is the ceiling, and only the root can lift it. You can read both from a shell: ``` $ ulimit -Sn; ulimit -Hn 1048576 unlimited ``` So my interactive shell was fine: a million descriptors soft, no hard ceiling. That high number is not a macOS default, though. A stock login shell on macOS also starts at 256; something in my environment had raised it long ago, and I had forgotten. If the rebuild had run from this shell, it would have inherited the raised limit and never noticed. Here is the catch: not everything runs from your shell. macOS keeps a third number, the one launchd uses, and that is the one that bites. launchd is the system supervisor, and it seeds the limits for anything it starts: daemons, agents, and any process spawned by something that did not bother to raise its own soft limit. Ask launchd what it thinks: ```bash $ launchctl limit maxfiles maxfiles 256 unlimited ``` There it is. 256\. In 2026, a fresh macOS still imposes a soft limit of 256 open files on new processes, a number that would have felt generous on a workstation in 1995\. Whatever raised my shell's limit never touched launchd, so the low number sat out of sight. Anything launchd starts, rather than my shell, does not get the favor. ## Why libgit2 opens so many at once The path in the error is a clue most people skip past. `~/.cache/nix/tarball-cache-v2` is not a plain directory of downloaded tarballs. It is a Git repository. Nix keeps its flake and tarball inputs in a libgit2-backed store, which is why the failure is literally "opening Git repository" and the file it choked on is `config`, the first thing any Git repo reads on open. libgit2 is the C library Nix links against to talk to that store. A pack comes in pairs: a `.pack` with the compressed objects and a `.idx` with the lookup table. To resolve objects, libgit2 `mmap`s pack files and keeps them mapped, so the descriptors it holds scale with how many packs are in the object store. There is a configurable cap on this (`GIT_OPT_SET_MWINDOW_FILE_LIMIT`), but the point that matters here is the direction: more packs means more descriptors held open while the cache is in use, and opening or indexing a cache full of packs is exactly when that count spikes. Wait. Why would there be enough packs for that to matter? A healthy repo has a handful. ## Why the cache grows a forest of packs A Git pack is normally the product of garbage collection: many loose objects compacted into one tidy file. But Nix's tarball cache is not gc'd on a schedule the way a working repo is. As it fetches flake inputs and tarball revisions, it writes packs, and nothing ever runs maintenance to fold them back together. They just accumulate for as long as the cache lives. I counted mine: ```bash $ du -sh ~/.cache/nix/tarball-cache-v2 306M ~/.cache/nix/tarball-cache-v2 $ find ~/.cache/nix/tarball-cache-v2/objects/pack -type f | wc -l 1593 ``` 1593 files in the pack directory: `.idx` and `.pack` paired up, plus a few stragglers, so on the order of 790-odd packs. Against a soft limit of 256, a cache this size does not need anything exotic to go wrong; the descriptors libgit2 holds while working through the packs are enough to walk a 256-limit process off the edge. The cache had quietly grown a forest, and the rebuild was the first thing to walk into it from a process that inherited launchd's stingy 256 instead of my shell's million. Two correct decisions composed into one broken rebuild. Nix never compacts the cache because it is a cache, and compaction is not free. macOS ships a tiny soft limit because it always has, and your shell hides it. ## The immediate fix: collapse the packs The fastest way out is to give libgit2 fewer files to open. The instinct is to reach for `git gc --aggressive --prune=now`, and do not: this cache has no refs of its own (`git -C ~/.cache/nix/tarball-cache-v2 show-ref` comes back empty), so every object is unreachable from gc's point of view. `--prune=now` then happily discards the cached inputs you were trying to keep. You end up with a tidy repo and an empty cache. The safe way to compact in place is the multi-pack-index, which folds the packs without pruning objects out from under Nix: ``` git -C ~/.cache/nix/tarball-cache-v2 multi-pack-index write git -C ~/.cache/nix/tarball-cache-v2 multi-pack-index repack git -C ~/.cache/nix/tarball-cache-v2 multi-pack-index expire ``` That collapses the pack count, reducing the descriptors libgit2 needs to a handful while leaving the actual objects intact. The rebuild stops the limit from tripping because the limit is no longer the binding constraint. If you do not care about keeping the cached inputs, there is an even blunter option. It is a cache, so you are allowed to throw it away: ``` rm -rf ~/.cache/nix/tarball-cache-v2 ``` Nix rebuilds it clean on the next fetch. The repack is the nicer move because you keep everything already downloaded, but both clear the immediate error. I repacked. ## The durable fix: tell launchd to stop being from 1995 Repacking buys you time. It does not stop the packs from accumulating again, and it does nothing about the actual root cause, which is that 256 soft limit waiting to ambush the next process launchd spawns. The real fix is to raise launchd's `maxfiles` so it survives reboots and applies to invokers that never see your shell rc. For the running session, raise it directly: ``` sudo launchctl limit maxfiles 524288 524288 ``` That sets both the soft and hard limit to 524288 until the next reboot. To make it stick, drop a launch daemon at `/Library/LaunchDaemons/limit.maxfiles.plist` that re-applies the same limit every boot: ```xml Label limit.maxfiles ProgramArguments launchctl limit maxfiles 524288 524288 RunAtLoad ``` It runs one command at load, `launchctl limit maxfiles 524288 524288`, and exits. After a reboot, `launchctl limit maxfiles` reports `524288 524288` instead of the factory `256`, and every process launchd starts inherits the saner ceiling. It is worth a look at what the Nix daemon itself already does, because it solved this for its own process years ago. The `systems.determinate.nix-daemon` plist sets `SoftResourceLimits` and `HardResourceLimits` with `NumberOfFiles` at 1048576\. The daemon was never the thing hitting the wall. The thing hitting the wall was whatever spawned the `nix` invocation for the rebuild, inheriting 256 because nobody told launchd otherwise. ## Where it landed After the repack the rebuild went through on the first try. After the plist, `launchctl limit maxfiles` shows 524288 across the board, so a future pack forest will have room to breathe even from a non-shell invoker. I still expect to repack that cache again someday, because Nix will keep minting packs and never folding them, but it will not take the rebuild down with it. The takeaway I am keeping: when a tool dies with "Too many open files" on macOS, do not trust `ulimit` in your shell. Your shell may show a comfortable number that launchd never agreed to. Ask `launchctl limit maxfiles` what the process actually inherited, because that is the limit that was really in force. ### When the Nix Eval Cache Serves You a Ghost Derivation URL: https://til.codes/when-the-nix-eval-cache-serves-you-a-ghost-derivation/ Last updated: 2026-05-31T22:00:57.000Z The project config said `15432`. The process was on `15433`. Tests could not connect, because they were dialing the port the app expected and nothing was answering there. I opened `devenv.nix`. Port 15432\. I opened `.env`. Port 15432\. I grepped the whole repo for 15433\. Nothing. Not a single reference. And yet `devenv up` had booted Postgres on 15433, and it had done it confidently, with no warning, every single time. So the question was simple and infuriating. Neither `.env` nor `devenv.nix` mentions 15433 anywhere. Both say 15432\. Where is Postgres getting 15433 from? One caveat before blaming the cache: in devenv 2.x, the Postgres port is also the base for automatic port allocation, so 15433 can be legitimate if 15432 is already occupied. That did not make the mismatch less real. It just meant I needed to find the exact generated config the running process had loaded. ## What is actually listening First, confirm reality. The config is a claim; the running process is the fact. So I went and looked at the fact. The Postgres that was actually serving had its data directory at `.devenv/state/postgres`, and that directory had a `postgresql.conf` in it. Line 17: ``` port = 15433 ``` There it is. Not in any file I had edited. Sitting in the runtime state directory that devenv manages. So the next question writes itself. Who put 15433 in `.devenv/state/postgres/postgresql.conf`? ## Walking the process tree In this setup, devenv did not start Postgres directly. It started a process manager wrapper, which ran a chain of generated scripts. Following the running process back up: ``` devenv-processes-postgres -> /nix/store/vy3ndzy...-start-postgres/bin/start-postgres -> /nix/store/f7zr5mg...-setup-postgres/bin/setup-postgres ``` `setup-postgres` is the interesting one. It is the script that prepares `$PGDATA` before the server boots. And the way devenv ships a `postgresql.conf` is not by templating it into the state directory at runtime. It bakes the config into the Nix store at evaluation time and then copies it into place. The relevant line inside that setup script: ``` cp /nix/store/dasdnbj41...-postgresql.conf "$PGDATA/postgresql.conf" ``` So the `postgresql.conf` in my state directory was not authored by me and was not derived from the `devenv.nix` I had open. It was `cp`\-ed out of a specific store path. Read that store path: ``` $ cat /nix/store/dasdnbj41rjsw5hnp94vw2vfdllqvz20-postgresql.conf ... port = 15433 ``` The store object had 15433 baked in. The lie was now located. It lived in an immutable Nix store path, and `setup-postgres` faithfully stamped it into my data directory every time it ran. ## The part where it gets genuinely weird Here is the catch. My current shell's PATH did not point at that derivation. When I checked which `start-postgres` was on my PATH, it resolved to a *different* store hash: ``` /nix/store/90bj99hi3f1d1vffwl34qbag44f15043-start-postgres/bin/start-postgres ``` But the process actually running had been launched from: ``` /nix/store/vy3ndzyhaa961vd5swbahdlx0hzafi0w-start-postgres/bin/start-postgres ``` Two different `start-postgres` derivations. The one in my shell would copy a 15432 conf. The one that the live process had actually come from was an older build that copied the 15433 conf. Same name. Different content. My environment was internally inconsistent: the interactive shell knew about the new world, but the running daemon was still a fossil from the old one. That alone did not prove the eval cache was guilty; it proved the live process was not using the derivation I thought it was using. That is why grepping the repo found nothing. The 15433 was never in the repo. It was frozen inside a store object that the live process was still pointed at. Wait. If `devenv.nix` already says 15432, why was the old derivation still getting revived? ## The eval cache is the part to distrust devenv keeps an evaluation cache so it does not have to re-run Nix evaluation on every command. It lives here: ``` .devenv/nix-eval-cache.db .devenv/nix-eval-cache.db-shm .devenv/nix-eval-cache.db-wal ``` That cache maps evaluated attributes to the files, environment variables, and options they depended on. It is supposed to invalidate when a source file read during evaluation changes. Fast in the common case. A trap when that dependency tracking goes sideways. The cache should have noticed my edit and rebuilt the process config. But the result I kept getting was the stale `start-postgres` derivation, and forcing the cache to refresh was what finally broke the loop. That old derivation referenced the stale `postgresql.conf` store path. `setup-postgres` copied 15433 into my data directory. Postgres booted on 15433\. Every restart reproduced the exact same wrong port, which is precisely why it felt like gaslighting. A flaky bug you can dismiss. A bug that is perfectly consistent and contradicts the file in front of you is the one that makes you doubt your own eyes. ## The fix To get unblocked for tests immediately, the cheapest move is to stop arguing with the running process and just point the client at where Postgres actually is. Set the port in `.env` to match reality: ``` DB_PORT=15433 ``` Postgres was already up on 15433, so tests connect right away. One line, reversible, buys you time. But that is a workaround, not a fix. The actual repair is to force devenv to refresh its eval cache so it re-reads the file I edited: ```bash # stop the devenv processes first devenv up --refresh-eval-cache ``` If that still leaves the cache wedged, the sledgehammer version is deleting `.devenv/nix-eval-cache.db` and its SQLite sidecar files, if they exist. With the cache refreshed, devenv re-evaluates `devenv.nix`, produces a fresh `start-postgres` derivation, that derivation references a `postgresql.conf` store path with `port = 15432`, and `setup-postgres` copies the correct conf into `$PGDATA`. Postgres comes up on 15432\. The config and the process finally agree, and I reverted the `.env` line. There is a tempting third option I deliberately avoided: hand-editing `.devenv/state/postgres/postgresql.conf` from 15433 back to 15432\. It works until the next `devenv up`, at which point `setup-postgres` cheerfully `cp`s the stale store conf right back over your edit. Editing the copy is pointless when the source of the copy is the thing that is wrong. The Nix lesson is the one worth carrying out of this. There are three layers and you have to keep them straight: the config you declare in `devenv.nix`, the immutable artifact that config evaluates to in the store, and the eval cache that is supposed to decide when to re-derive that artifact. When the running process diverges from the file you are editing, the file is rarely the liar. Walk down to the store path the process actually loaded, account for port allocation, and if that path is older than your last edit, suspect the process state and the cache that handed it to you. ### The Filter in the Wrong Place: 76x Faster by Moving One WHERE Clause URL: https://til.codes/the-filter-in-the-wrong-place-76x-faster-by-moving-one-where-clause/ Last updated: 2026-05-29T17:29:26.000Z I was reviewing a PR that added a `GET /accounts` endpoint to a payments API. Each account has many transactions, and the endpoint returns each account's most recent transaction so the list can show last activity at a glance. It supports a partial name filter and paginates. The shape was sensible. The query looked reasonable until I saw where the name filter landed. The ranking was a textbook window function. For each account, rank that account's transactions newest first, and keep rank 1\. In Ecto it built up as a pipeline of subqueries: ```elixir defp ranked_transaction_query do from t in Transaction, select: %{ account_id: t.account_id, amount: t.amount, currency: t.currency, inserted_at: t.inserted_at, rank: over(row_number(), partition_by: t.account_id, order_by: [desc: t.inserted_at, desc: t.id] ) } end defp best_transaction_query(ranked) do from t in subquery(ranked), where: t.rank == 1 end ``` Then it joined accounts onto that best-transaction subquery, ordered by name, and only then, on the outer query, applied the name filter: ```elixir defp maybe_filter_by_name(query, name) do sanitized = "%" <> QueryUtils.sanitize_like(name) <> "%" where(query, [a, ...], ilike(a.name, ^sanitized)) end ``` Read top to bottom, it tells a clean story. Rank the transactions, take the newest per account, join the accounts, filter by name. Each step is correct in isolation. The results were right. The problem is the order those steps actually run in, and what the window function is forced to do because of it. ## How a window function actually evaluates Does Postgres push an outer `ILIKE` filter down into a window subquery so it only ranks matching accounts? Not in this case. Postgres can push an outer filter into a subquery that contains a window function, but only when the filter is on the partition key itself. Dropping a whole partition cannot change the ranking of the rows that survive, so that push is safe. The name filter is on `accounts.name`, which is not the partition key, and is not even a column the ranking subquery outputs. There is nothing for the planner to push down. So the window has to work from the unfiltered transaction input. `row_number() OVER (PARTITION BY account_id ORDER BY ...)` is defined relative to each whole account partition, so a filter that removes arbitrary rows from inside a partition would change the ranking. Postgres has executor optimizations and does not have to literally materialize and number every row, but the planner still cannot use `accounts.name` to shrink the input to this transactions-only window subquery. The name filter sat on the outer query, after the ranking subquery had already produced its output. By the time `ILIKE` ran, the window node had still been planned over the whole transactions table. The filter was not pruning the expensive input. It was discarding finished work. Searching for one account still paid the full cost of processing all \~40k transaction rows through that ranking plan, then kept the handful that matched. That was the inversion. The cheapest, most selective predicate in the query, a name match that usually returns a few rows, was the last thing to run. ## What the numbers said The endpoint logged the generated SQL. The unfiltered version shows the window running over the bare `transactions` table with no scoping at all: ```sql SELECT a0."id", a0."name", t1."amount", t1."currency", t1."inserted_at" FROM "accounts" AS a0 INNER JOIN ( SELECT tt0."account_id", tt0."amount", tt0."currency", tt0."inserted_at", tt0."rank" FROM ( SELECT ttt0."account_id", ttt0."amount", ttt0."currency", ttt0."inserted_at", row_number() OVER ( PARTITION BY ttt0."account_id" ORDER BY ttt0."inserted_at" DESC, ttt0."id" DESC ) AS "rank" FROM "transactions" AS ttt0 ) AS tt0 WHERE (tt0."rank" = 1) ) AS t1 ON t1."account_id" = a0."id" ORDER BY a0."name", a0."id" LIMIT $1 OFFSET $2 ``` The benchmark ran against 20k accounts and 40k transactions. The unfiltered listing came in around 66ms. With the filter applied the old way, ranking all 40k rows then matching the name, the filtered query measured about 61ms. Almost no savings, because the expensive part ran regardless of how few accounts matched. The filter was decoration on top of a full-table sort. ## Moving the filter into the subquery The fix is to make the name match happen first, so the window only ever sees transactions that belong to matching accounts. Resolve the matching account ids up front, then scope the ranking subquery to those ids: ```elixir defp matching_account_ids(name) do sanitized = "%" <> QueryUtils.sanitize_like(name) <> "%" from a in Account, where: ilike(a.name, ^sanitized), select: a.id end defp ranked_transaction_query(account_ids_query) do from t in Transaction, where: t.account_id in subquery(account_ids_query), select: %{ account_id: t.account_id, amount: t.amount, currency: t.currency, inserted_at: t.inserted_at, rank: over(row_number(), partition_by: t.account_id, order_by: [desc: t.inserted_at, desc: t.id] ) } end ``` Same window, same partition, same ordering. The only change is that the input to the window is now a few accounts' transactions instead of all of them. The `WHERE account_id IN (...)` runs at the same query level as the window, so it applies before the window is computed. Because it filters whole accounts in or out, every surviving account still gets its correct rank 1\. The `PARTITION BY account_id` work shrinks from 40k rows to whatever the name matched. A `pg_trgm` GIN index on `accounts.name` can make resolving those ids cheap, so the window starts from a tiny set. The filtered query dropped from about 61ms to roughly 0.9ms. On the full benchmark the name-filter path sat at 0.86ms median against the 66ms unfiltered listing, about 76x faster. The query went from ranking the whole table then looking for Acme, to finding Acme then ranking only Acme's transactions. ## Why the unfiltered case is still inherent The unfiltered listing is the honest baseline for this query shape, and it stays at \~66ms no matter what. When there is no name to filter on, there is nothing in this plan to scope the window down to. The window subquery has to consider the full transaction set to produce each account's most recent one, and offset pagination makes the outer query produce the ranked account rows before it can skip to a page in the middle. Walking to page 500 measured about 76ms versus 66ms for page 1, the extra \~10ms being Postgres skipping the \~9,980 rows ahead of the offset. That penalty grows linearly with page depth, and it is the price of offset pagination over this full-table window plan. Cursor-based pagination, a `LATERAL ... LIMIT 1` lookup, `DISTINCT ON` with the right index, or maintained latest-transaction state could attack the unfiltered path, but that is a separate problem. Moving the filter does not help the unfiltered path, and it was never supposed to. ## The escaping aside One more thing I checked while I was in there, because interpolating user input into an `ILIKE` pattern is its own small trap. The filter wraps the search term in `%...%`, but the term itself can contain LIKE metacharacters. Someone searching for `50%` or `a_b` would otherwise have the `%` and `_` interpreted as wildcards, quietly matching far more than they asked for. The helper escapes them before interpolation: ```elixir def sanitize_like(value) do value |> String.replace("\\", "\\\\") |> String.replace("%", "\\%") |> String.replace("_", "\\_") end ``` Order matters here. Escape the backslash first, then the wildcards, otherwise you double-escape the backslashes you just added. With PostgreSQL's default LIKE escape behavior, `%` matches a literal percent sign and `_` matches a literal underscore. If you want that contract to be visible at the SQL boundary, add an explicit `ESCAPE '\'` too. The wrapping `%...%` the query adds itself stays meaningful, and only the user's own metacharacters get neutralized. The takeaway from this review: a window-function subquery can block useful predicate pushdown unless the predicate safely removes whole partitions. Anything placed after the ranking can only discard rows the window plan already paid to produce. A predicate that filters on the partition key can be pushed down for free, but anything more selective, like a name match, has to live before the window, inside the subquery, or it buys you nothing. Correct results are not the same as correct order of operations, and with window functions the order is where the cost hides. ### Your Pin-to-Bottom Hook Is Missing the Users Who Need It Most URL: https://til.codes/your-pin-to-bottom-hook-is-missing-the-users-who-need-it-most/ Last updated: 2026-04-22T08:17:11.000Z If you're building a chat view, a log viewer, a live feed, or anything else that needs to stay pinned to the latest content, you'll eventually write a handler that recomputes scroll position when the layout changes. The instinct is to listen for the browser's `resize` event. That instinct is usually wrong, and the users it fails are the ones who need the feature to work most. Here's how to recognize the problem, and what to use instead. ## The Default Approach The code you might start with looks like this: ```javascript const { signal } = this.abortController window.addEventListener("resize", this.handlers.resize, { signal }) this.elements.container.addEventListener("scroll", this.handlers.scroll, { signal }) this._scrollToInitialPosition() ``` It handles the obvious cases. Drag the browser window larger, the handler fires and the view stays pinned. Rotate a tablet, same thing. Open DevTools, covered. This is what most people test, and it passes. But the viewport and the element you're trying to pin are not the same thing. The `resize` event fires for the former, not the latter. Your scroller is blind to almost every reason a layout actually changes. ## What `window.resize` Doesn't See - A user with low vision bumping browser zoom to 200%. - Firefox's "Zoom Text Only" mode, where layout reflows without the window reporting any new size. - A textarea auto-grows as the user types a long message. Less room for content above, same window dimensions. - A side panel or sidebar toggles open. The container got narrower; the window didn't move. - Images, attachments, or late-loading content pushing earlier content down after initial layout. - A soft keyboard appearing on mobile. Some browsers fire `resize`, many don't. Some of these are polish. Several are accessibility failures. The irony is that these are the events most likely to go unnoticed by a developer working at 100% zoom on a laptop. The users most likely to notice a pin drifting out of sync are the ones who rely on larger text, higher zoom, and wider layouts. ## The Right Primitive The fix is `ResizeObserver`, bound to the element you actually want to keep pinned: ```javascript const { signal } = this.abortController this.resizeObserver = new ResizeObserver(() => { this.handlers.resize() }) this.resizeObserver.observe(this.elements.container) this.elements.container.addEventListener("scroll", this.handlers.scroll, { signal }) ``` `ResizeObserver` doesn't care *why* the element resized. Browser zoom, text-only zoom, a flex sibling taking more space, a new font loading, a panel opening: if the observed element's box changes, the callback runs. The shift in mental model is subtle but important. Stop asking "did the viewport change?" and start asking "did the thing I'm trying to keep pinned change shape?" The second question is almost always the one you want. ## How the Observer Actually Works Switching to `ResizeObserver` means understanding a few details that differ from `resize` events, especially if you're leaving it running on a critical UI element. **The callback runs between layout and paint.** The event loop has a dedicated phase for resize observer notifications, after layout has settled but before anything gets painted. You can read layout geometry inside the callback without forcing a synchronous reflow, because the layout is already current. Writes that only affect paint, like` scrollTop`, happen in the same frame. **It receives entries, not just a signal.** Each call gets an array of `ResizeObserverEntry` objects, one per observed element that changed. The entry carries the new dimensions, so you rarely need to query the DOM again: ```javascript this.resizeObserver = new ResizeObserver((entries) => { for (const entry of entries) { const { blockSize } = entry.contentBoxSize[0] this.handlers.resize(blockSize) } }) ``` I prefer `contentBoxSize` and `borderBoxSize` over the older `contentRect`. The boxSize APIs are writing-mode aware (`blockSize` and `inlineSize` flip for vertical or RTL layouts, so your pin math keeps working in Japanese or Arabic) and they expose the border box directly. If your scroll math depends on padded or bordered geometry instead of content box, pass `{ box: "border-box" }` as the second argument to `observe()`. `contentRect` is still useful as a fallback for older browsers (Safari before 15.4). Its `width` and `height` are always content-box, so if you need border-box you have no choice but the newer API. One quirk: `contentRect.top` and `contentRect.left` are always `0`. The rect describes the content box relative to *itself*, not to the viewport. For on-screen position, use `getBoundingClientRect()`. ```javascript // Portable fallback. width/height are always content-box. new ResizeObserver((entries) => { for (const entry of entries) { const { width, height } = entry.contentRect this.handlers.resize(height) } }) ``` **It fires once immediately after `observe()`.** Unlike `window.resize`, which only fires on changes, `ResizeObserver` delivers an initial notification as soon as observation begins. That's your "starting layout" signal for free, and much of the initial-measurement plumbing you might otherwise hand-roll collapses into the same callback. **It batches to one fire per frame.** The browser coalesces notifications per animation frame, so you don't need to debounce or wrap the callback in `requestAnimationFrame`. A flurry of resizes during a CSS transition appears as a single batched call per frame. **It will complain if you cause a loop.** If your callback changes the observed element's size, or anything upstream that affects it, the browser skips remaining notifications and logs `ResizeObserver loop completed with undelivered notifications`. The canonical way to trip it is reading the entry's size and writing it straight back: ```javascript // Don't do this. new ResizeObserver((entries) => { const entry = entries[0] entry.target.style.height = `${entry.contentRect.height + 10}px` }).observe(container) ``` First notification: height is `H`. Callback sets it to `H + 10`. Browser relays out, fires callback with `H + 10`. Callback sets `H + 20`. The browser gives up and logs the error. Scroll pinning dodges this because `scrollTop` is paint-only; it doesn't affect layout: ```javascript // Safe: scrollTop does not resize the element. new ResizeObserver(() => { if (isPinned) { container.scrollTop = container.scrollHeight } }).observe(container) ``` Problems start when you do something more ambitious in the callback: toggling a "jump to latest" badge's visibility, swapping classes on a flex sibling, and resizing the composer. Any of those can round-trip into the observed box. If you need that kind of work, defer layout mutations behind `requestAnimationFrame` so they land in the next frame, or observe a different element (often the parent) whose size stays stable. ## Don't forget to Clean Up `ResizeObserver` doesn't accept `AbortSignal`, so you can't hand it to the `AbortController` that cleans up your scroll listener. It needs explicit teardown. Observing one element? Use `disconnect()`. Observing several and want to stop watching one? Use `unobserve(target)`. In a LiveView hook: ```javascript destroyed() { this.resizeObserver?.disconnect() this.abortController?.abort() }, ``` Skip this, and you leak observers across navigations. In a long session, that adds up to real memory and callbacks firing on detached nodes. ## The Accessibility Angle The deeper reason to reach for `ResizeObserver` isn't just that it catches more cases. It's that `window.resize` encodes the assumption that layout changes only when the window does, which was already wrong in 2016 and is completely false today. Users reshape their own layouts all the time: zoom, OS font preferences, reader mode, extensions, translation widgets, dynamic content, virtual keyboards. An accessible UI responds to *what the user actually changed*, not just to the coarse event the browser fires for a window drag. If any feature you build has "stay pinned," "recompute on resize," or "reflow when the layout changes," the default tool should be `ResizeObserver` on the element in question. `window.resize` is a fallback for the rare cases where the window itself genuinely matters. **Further reading:** - [ResizeObserver on MDN](https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver?ref=til.codes) - [ResizeObserverEntry on MDN](https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserverEntry?ref=til.codes) (covers `contentBoxSize`, `borderBoxSize`, `devicePixelContentBoxSize`) - [Resize Observer W3C specification](https://www.w3.org/TR/resize-observer/?ref=til.codes) (authoritative on loop-error behavior) - [WCAG 1.4.4: Resize Text](https://www.w3.org/WAI/WCAG22/Understanding/resize-text.html?ref=til.codes) - [Firefox: Font size and zoom, including Zoom Text Only](https://support.mozilla.org/en-US/kb/font-size-and-zoom-increase-size-of-web-pages?ref=til.codes) - [MutationObserver](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver?ref=til.codes) and [IntersectionObserver](https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver?ref=til.codes) (sibling APIs in the Observer family) ### Picking Where jj absorb Lands: Adding --into to jjui URL: https://til.codes/picking-where-jj-absorb-lands-adding-into-to-jjui/ Last updated: 2026-04-18T23:14:13.000Z I [wrote about jj absorb recently](https://til.codes/the-magic-of-jj-absorb-rewriting-history-without-the-pain), and how it takes your working copy changes and quietly redistributes them into the right ancestors. It's the command I miss most when I'm forced back to git. But there's a wall I kept hitting, and last week I finally did something about it. ## The Problem with "Every Mutable Ancestor" `jj absorb` defaults to `mutable()` for its destination set. Every mutable revision in your repo becomes a candidate for receiving your changes. Most of the time, this is what you want, since the algorithm only absorbs into ancestors of the source, so the list narrows itself naturally. But "every mutable ancestor" gets unwieldy fast. I work in stacks. A typical morning looks like this: ``` @ follow-up fixes touching backend and UI o Add UI module o Add backend handler o ... five other commits I'm still iterating on ``` Sometimes the working copy has a fix that belongs in `Add UI module` , but absolutely shouldn't touch `Add backend handler`. Maybe that backend commit is queued for a separate PR, and I'm being careful not to mix scopes. Or maybe one of the older commits is technically mutable, but I want to keep it frozen until I get review feedback. The CLI handles this with `--into`: ``` jj absorb --from @ --into pmmspwst ``` Pass it once or many times, and the destination set shrinks to exactly those revisions. But I use [jjui](https://github.com/idursun/jjui?ref=til.codes) for almost all my interactive `jj` work. Pressing `shift+a` ran a bare `jj absorb`, with no way to constrain. Every time I needed `--into`, I dropped to a terminal, navigated, typed out change IDs, and hoped I picked them right. ## Building the Picker I [opened an issue](https://github.com/idursun/jjui/issues/634?ref=til.codes). The maintainer came back with a clean design: convert absorb into an "operation" like `set_parents` or `abandon`. Mark the source revision with `<< absorb >>`. Mark each candidate with `<< into >>`. Bind `space` to toggle. Bind `enter` to apply. The crucial detail: if you make no selections, it should still run plain `jj absorb`. Preserve the existing `shift+a` then `enter` flow exactly. Just one extra keypress if you need it. Here's what shipped: ![](https://til.codes/content/images/2026/04/image.png) Three states visible. The source (working copy, `sptvnwlk`) shows `<< absorb >>` in cyan. Default candidates (`pmmspwst`, `lypnkxpn`) show `<< into >>` in red. Move the cursor, hit `space` on a candidate, and the marker dims to `<< default >>`, which excludes it from the destination set but keeps it visible. Hit `enter` and absorb runs with `--into` for whatever remains. The first version I wrote had a subtle bug: toggle every candidate off and press `enter`, and it would silently fall back to jj's default behavior. That's the opposite of what the user is asking for. If you explicitly deselected everything, you probably want to cancel, not run the unconstrained version. ## Why Ancestors-Only for the Candidate Set The maintainer suggested `mutable()` as the default candidate set, which is what jj's CLI does. I went with `mutable() & ::source` instead, narrowing to ancestors of the source. The absorb algorithm explains why. `jj absorb` only ever distributes hunks into ancestors of the source. Non-ancestors can't receive content because the file annotation walk never visits them. jj's own [docstring is explicit](https://github.com/jj-vcs/jj/blob/main/cli/src/commands/absorb.rs?ref=til.codes): *"Only ancestors of the source revision will be considered."* If the picker showed non-ancestors as candidates, users could toggle them on, but absorb would silently ignore them. That's misleading UX. Showing only ancestors keeps the picker honest about what's actually possible. ## The Trap of Tracked State My first iteration tracked a `userModified bool`. Toggle anything, set the flag. At apply time, if the flag was true, pass `--into`. Otherwise, run plain absorb. This sounds reasonable until you toggle a candidate off and then on again. You're back to the original set, but the flag is still true, so absorb runs with `--into a --into b`, which is functionally identical to no `--into`, just verbose and ugly in the command history. Worse: if defaults are empty (small repo, no mutable ancestors), pressing `space` on something then `space` again to undo leaves you with empty targets AND a true flag. The empty-targets close logic kicks in and the operation exits without running anything. The user did nothing, but jjui treated it as "I want to cancel." The fix was to drop the bool and compare sets at apply time. If `targets == defaults`, no `--into`. If `targets` is empty AND `defaults` wasn't, close. Otherwise, pass `--into` for each kept target. The state model is now derived, not tracked. This is the kind of bug unit tests don't catch easily, because the assertion you'd write has the same shape as the bug. I wrote a test for the toggle-on-then-off path asserting "userModified stays true" and it passed because that's exactly what the buggy code did. The test confirmed the bug, not the requirement. ## When the Lua API Collides `jjui` exposes actions to Lua so users can script workflows. The action that opens the absorb operation was registered as `revisions.absorb`. The new picker scope was also `revisions.absorb`. In jjui's Lua API, scopes are tables and actions are functions. Registering both means the scope table overwrites the action function. Anyone with a Lua script calling `jjui.revisions.absorb()` would suddenly find that function gone. The fix was renaming the entry action from `revisions.absorb` to `revisions.open_absorb`. This matches the convention every other operation uses: `open_abandon`, `open_squash`, `open_set_parents`. Honestly, it should have been named that way from the start, since the action opens an operation instead of running absorb directly. The Lua collision just made the rename non-optional. ## Closing This is a small feature. But when you live in stacks, being able to constrain absorb to a single ancestor turns "drop to terminal, type carefully" into "space twice, enter." The friction difference compounds. The PR is [open against idursun/jjui](https://github.com/idursun/jjui?ref=til.codes). If you use `jjui` and have thoughts on the picker UX, that's the place. **Resources:** - [jjui](https://github.com/idursun/jjui?ref=til.codes) - [GitHub issue #634](https://github.com/idursun/jjui/issues/634?ref=til.codes) - [jj absorb docs](https://docs.jj-vcs.dev/cli/?ref=til.codes#jj-absorb?ref=til.codes) ### Bridging git worktrees and jj workspaces for agentic workflows URL: https://til.codes/bridging-git-worktrees-and-jj-workspaces-for-agentic-workflows/ Last updated: 2026-04-07T17:14:09.000Z I run agentic workflows in jj workspaces: parallel feature development, code reviews, spikes, experiments, each isolated in its own working copy. The workflow is ideal for isolation. You can work on a feature, create stacked PRs, switch to another workspace for spikes or code reviews, or set up a scratch environment for debugging flaky tests. But when I opened a workspace, the editors showed no diff gutters, no change indicators, no blame annotations. As far as Zed, Neovim, and the others were concerned, I was editing unversioned directories. This sent me down a rabbit hole through libgit2's repository discovery, Git's internal storage model, and jj's colocated backend, and the fix turned out to be a single line. ## How Git Repository Discovery Actually Works When an editor opens a directory, it needs to find the Git repository. How it does this varies. Zed links against [libgit2](https://libgit2.org/?ref=til.codes), a C implementation of Git's object model, and performs repository discovery in-process. VS Code shells out to a bundled `git` binary, which means PATH-based shims can still affect it. Neovim plugins vary; some use libgit2 via FFI, others call `git` directly. But the discovery algorithm is the same regardless of who runs it. It starts with [git\_repository\_discover()](https://libgit2.org/libgit2/?ref=til.codes#HEAD/group/repository/git%5Frepository%5Fdiscover) (or the equivalent CLI invocation), walking up from the working directory: ``` ~/project/feature-auth/lib/accounts/ ~/project/feature-auth/lib/ ~/project/feature-auth/ ~/project/ ~/ ... ``` If `til_codes/.git` existed somewhere along this path, discovery would succeed. But the workspace is a sibling directory, `feature-auth/` sits next to `til_codes/`, not inside it. There's no `.git` anywhere in the walk. At each level, it looks for a `.git` entry. Here's where it gets interesting: `.git` can be one of two things. Case 1: A directory. The standard layout. Contains `objects/`, `refs/`, `HEAD`, `index`, and the rest of the repository internals. libgit2 opens it directly. Case 2: A file. A plain text file containing a single line: ``` gitdir: /path/to/actual/git/directory ``` libgit2 reads the file, resolves the path (relative paths are resolved against the file's parent directory), and opens the referenced directory as the repository. This isn't an extension or a hack; it's part of the [Git repository layout specification](https://git-scm.com/docs/gitrepository-layout?ref=til.codes). Git originally added it for submodule support and later adopted the same mechanism for `git worktree`. When you run `git worktree add ../feature-branch`, Git creates exactly this: a `.git` file in the new worktree that points back to `.git/worktrees/feature-branch/` inside the main repository. Every tool built on libgit2 follows these pointers transparently. A jj workspace has neither. It has a `.jj` directory, which libgit2 doesn't recognize. Discovery fails at the workspace root, bubbles up to the filesystem root, finds nothing, and the IDE concludes there's no repository. ## Inside jj's Storage Model To understand why a fix is even possible, you need to know how jj stores data. When you initialize a repository with `jj git init --colocate` and in current jj releases, colocation is the default, you get two version control systems sharing one directory: ``` til_codes/ .git/ # Standard Git repository HEAD objects/ # Git object store (commits, trees, blobs) refs/ # Branch refs, tags index # Working tree cache info/ exclude # Per-repo ignore patterns .jj/ # Jujutsu metadata repo/ store/ git_target # Points to ../.git (the colocated backend) op_store/ # Operation log (jj's undo history) op_heads/ # Current operation heads working_copy/ # Snapshot state for this working copy ``` The critical detail is in `.jj/repo/store/git_target`. In a colocated repository, this file contains the path to the `.git` directory. jj doesn't maintain its own object store; it writes directly to Git's. Every `jj describe`, `jj new`, or `jj squash` creates real Git commit objects in `.git/objects/`. The commit graph, the tree objects, the file blobs, they're all standard Git objects that any Git tool can read. When you create a workspace with `jj workspace add ../feature-auth`, the new directory gets a minimal `.jj`: ``` feature-auth/ .jj/ repo # Text file: path back to the main repo's .jj/repo/ working_copy/ # This workspace's snapshot state lib/ test/ mix.exs ... ``` The `repo` file is a pointer, not a copy. All workspaces in a jj repository share the same commit graph, operation log, and, because jj is colocated, the same Git object store. The Git objects representing your feature branch's commits are sitting in the main repo's `.git/objects/`. The workspace just has no way to tell libgit2 where to find them. ## Why the Shim Approach Can't Solve This Before arriving at the fix, I tried the obvious approach: intercepting `git` commands. [jj-worktree](https://github.com/kawaz/jj-worktree?ref=til.codes) is a Rust binary that gets symlinked as `git` early in `PATH`. When invoked inside a jj repository, it translates commands: ``` git status -> jj diff --summary (output converted to porcelain v1) git rev-parse HEAD -> jj log -r @ -T commit_id git worktree add -> jj workspace add git branch -d -> jj bookmark delete ``` The translation includes output format conversion. `jj diff --summary` produces `M file.txt`, but tools that parse `git status --porcelain` expect `M file.txt` (note the leading space indicating unstaged changes). jj-worktree handles this: ```rust // From jj-worktree's shim.rs - cmd_status() for line in diff.lines() { let trimmed = line.trim(); if let Some((status, path)) = trimmed.split_once(' ') { match status { "M" => println!(" M {path}"), "A" => println!("?? {path}"), "D" => println!(" D {path}"), "R" => println!(" M {path}"), _ => println!(" M {path}"), } } } ``` This works for CLI tools. Claude Code's git integration, `gh`pre-commit hooks, anything that spawns `git` as a subprocess sees the shim. VS Code would too, since its built-in Git extension shells out to a `git` binary. But Zed doesn't spawn `git`. It calls `git_repository_discover()` from libgit2, which is compiled into the editor binary. There's no PATH lookup, no process spawning, no opportunity for interception. Other tools that use libgit2 directly, including some Neovim plugins and GUI clients, have the same limitation. I verified this by adding debug logging to the shim and opening the workspace in Zed. The shim was never invoked. Zed's git integration operates entirely within its own process space. ## The Fix: One Line Since jj's colocated backend writes to Git's object store, and libgit2 follows `gitdir:` pointers, the fix is to connect the two: ```bash echo "gitdir: ~/project/til_codes/.git" > ~/project/feature-auth/.git ``` That's it. One file, one line. libgit2's discovery walk hits the workspace root, finds a `.git` file, reads the `gitdir:` pointer, opens the main repository's `.git` directory, and now has full access to the object store. Zed immediately shows diff gutters. Codex shows file changes. `git diff`, `git status`, and `git blame` become useful from the workspace directory. `git log` still follows the shared Git HEAD, it won't show your workspace's jj history unless you explicitly point it at exported refs or commit hashes. But to understand the limitations of this approach, you need to understand how Git actually computes diffs. ## Git's Three-Layer Diffing Model Git doesn't compute diffs by comparing files against a single reference point. It maintains three distinct representations of your project, and different commands compare different pairs: ``` +------------------+ | HEAD commit | (the tree object pointed to by HEAD) +------------------+ | git diff --cached | +------------------+ | Index | (.git/index - binary file, aka "staging area") +------------------+ | git diff | +------------------+ | Working tree | (actual files on disk) +------------------+ ``` - `git diff` compares the index against the working tree. - `git diff --cached` compares HEAD against the index. - `git status` does both comparisons and reports the union. The index is the key piece. It's a binary file at `.git/index` that caches metadata about every tracked file: path, file size, modification time, inode number, and the SHA-1 of the blob object representing the file's contents. When you run `git status`, Git stats every file in the working tree, compares the stat data against the index entries, and only reads file contents (to compute SHA-1) when the stat data doesn't match. This is what makes `git status` fast even in large repositories, it's a stat cache, not a content scan. Here's why this matters for our workspace setup. The `gitdir:` pointer shares the main repository's `.git` directory, which means it shares the main repository's index. That index was built by the main repository's working copy. It contains stat entries for files as they exist in the main repo, not in the workspace. This creates three distinct behaviors depending on the type of change: New files (files that exist in the workspace but not in the index) show as "untracked." This is correct, the IDE displays them with the "new file" indicator, which is exactly what you want. Modified files (files that exist in both the workspace and the index) are compared against the index's cached blob SHA-1\. If the shared index still reflects the tree state your workspace branched from, the diff is accurate, the cached content matches the file's pre-modification state. But if any Git operation or tool updates that shared index to reflect a different working copy (e.g., running `git checkout` or `git reset` in the main repo), diffs in the workspace will be computed against the wrong base. Deleted files (files in the index but not in the workspace) would show as deleted if the workspace doesn't contain them. In practice, jj workspaces start as copies of the full tree, so this only matters if you've explicitly removed files. For my workflow, feature branches off trunk, usually a handful of files changed, shared index untouched, and the diffs have been accurate. The index still reflects the tree state my workspace branched from, so the comparison base is correct. ## Contrast: How `git worktree` Does It Properly For context, here's what `git worktree add` creates that our approach doesn't. When you run `git worktree add ../feature-branch`, Git creates: ``` .git/worktrees/feature-branch/ HEAD # Separate HEAD for this worktree index # Separate index for this worktree commondir # Points back to the main .git gitdir # Path to the worktree's .git file ``` Each worktree gets its own HEAD and its own index. The object store and refs are shared (via `commondir`), but the working tree state is independent. This is why `git worktree` doesn't have the index-sharing problem, each worktree tracks its own file state. Our `gitdir:` hack skips this entirely. We point directly at the main `.git`, sharing everything, including the index and HEAD. It's a read-only window into the object store, not a proper worktree registration. This is fine for IDE diffing but means you should never run Git write operations (`git add`, `git commit`, `git checkout`) from the workspace, they would mutate the main repository's state. ## Hiding `.jj` From Git's View With the `gitdir:` pointer in place, `git status` now sees the `.jj` directory as untracked. Git has three layers of ignore rules, evaluated in this order: 1. `.gitignore` tracked, committed, and shared with the team. Not appropriate for this; `.jj` is a local concern. 2. `core.excludesFile` (defaults to `~/.config/git/ignore`) global, applies to every repository. Too broad. 3. `.git/info/exclude` per-repository, never committed, never shared. This is the right layer. The `info/exclude` file uses identical syntax to `.gitignore` but lives inside the `.git` directory: ```bash echo '.jj' >> ~/project/til_codes/.git/info/exclude ``` After adding this, `.jj` disappears from `git status` and from Zed's file tree. The exclude file doesn't need to exist beforehand; Git creates the `info/` directory during `git init`, but the `exclude` file may not be present. The `>>` append handles both cases (creates the file if missing, appends if it exists). ## Automating the Setup I manage jj workspaces with two shell functions: `jwa` (workspace add) and `jws` (workspace sync). `jwa` creates the workspace and calls `jws` to copy configuration files from the main repo. Adding the `gitdir:` setup to `jws` means every new workspace gets IDE support automatically: ```bash jws() { local target="$1" local root=$(jj root 2>/dev/null) if [[ -z "$root" ]]; then echo "Not in a jj repository" return 1 fi # Sync AI tool configs into the workspace local items=(".claude" "CLAUDE.md" "AGENTS.md" ".env" ".mcp.json") local copied=0 for item in "${items[@]}"; do if [[ -e "$root/$item" ]]; then cp -r "$root/$item" "$target/" ((copied++)) fi done # Set up gitdir pointer for IDE diff support if [[ -e "$root/.git" ]]; then local git_dir if [[ -d "$root/.git" ]]; then git_dir="$root/.git" elif [[ -f "$root/.git" ]]; then git_dir=$(cat "$root/.git" | sed 's/^gitdir: //') [[ "$git_dir" != /* ]] && git_dir="$root/$git_dir" fi if [[ -n "$git_dir" ]]; then echo "gitdir: $git_dir" > "$target/.git" mkdir -p "$git_dir/info" if ! grep -qx '.jj' "$git_dir/info/exclude" 2>/dev/null; then echo '.jj' >> "$git_dir/info/exclude" fi echo "Linked .git for IDE diff support" fi fi echo "Synced $copied items to $target" } ``` The `elif` branch handles the case where the main repo's `.git` is itself a `gitdir:` pointer (e.g., the main repo was created inside another git worktree). It reads the pointer, resolves relative paths, and chains through to the actual `.git` directory. The `jwa` function creates the workspace and calls `jws`: ```bash jwa() { local input="$1" local root=$(jj root 2>/dev/null) local workspace_path if [[ "$input" = /* ]]; then workspace_path="$input" elif [[ "$input" == */* ]]; then workspace_path="$PWD/$input" else workspace_path="$root/../$input" fi jj workspace add "$workspace_path" || return 1 workspace_path="$(cd "$workspace_path" && pwd)" jws "$workspace_path" cd "$workspace_path" } ``` The full workflow becomes: ```bash $ cd ~/project/til_codes $ jwa feature-auth Created workspace in "../feature-auth" Linked .git for IDE diff support Synced 3 items to ~/project/feature-auth $ cd ../feature-auth $ zed . # Diff gutters work immediately ``` ## The Rule: Read-Only Git, All Writes Through jj This bears repeating because violating it will corrupt your main repo's state. The `gitdir:` pointer gives IDEs read access to the object store. It does not register the workspace as a proper git worktree. Git commands that read from the working tree and object store (diff, status, blame) are safe and useful. `git log` works but follows the shared HEAD, not your workspace's jj history. Git commands that write (add, commit, checkout, reset, stash) will modify the main repo's index and HEAD. In the workspace, all version control goes through jj: ```bash jj describe -m "add user authentication" # set commit message jj new # start next change jj bookmark set feature-auth -r @- # create bookmark for push jj git push -b feature-auth # push for PR ``` Zed shows the diffs. jj manages the history. They operate on the same underlying objects without needing to agree on a protocol. ## When This Breaks There are specific scenarios where the diff accuracy degrades: Shared index gets mutated. If a Git operation in the main repo updates the shared index, `git checkout`, `git reset`, or even an IDE refreshing the main repo's working tree, diffs in the workspace will be computed against the wrong base. Note that jj operations alone don't touch the Git index; this only happens when something invokes Git directly against the main repo. The fix is simple: avoid Git write operations in the main repo while workspaces are active, or accept occasional noise in the diff gutter. Multiple workspaces sharing one `.git`. All workspaces point to the same index. If you open two workspaces in Zed simultaneously, both see diffs computed against the same index state. For new files (the most common case in feature branches), this is fine. For modified files, the workspace whose base commit matches the index will show correct diffs; the other may not. Binary files and large objects. Diff computation requires reading blob objects from the object store. If your workspace modifies large binaries, the IDE may be slower to compute diffs since it's resolving objects from the main repo's packfiles over the `gitdir:` pointer. ## Complementary Tools The `gitdir:` pointer and jj-worktree solve different problems and can coexist: | Concern | gitdir: pointer | jj-worktree | | --------------------------- | ----------------------------- | -------------------------- | | IDE diff gutters | Yes | No (IDEs bypass PATH) | | CLI git status/diff/log | Yes (read-only) | Yes (full translation) | | git commit/add/checkout | Dangerous (mutates main repo) | Translated to jj commands | | Dependencies | None | Rust binary in PATH | | Workspace metadata tracking | None | Bookmark and path tracking | For IDE-only support, the `gitdir:` pointer is sufficient. For CLI tools that need transparent git compatibility, install jj-worktree alongside it. They don't conflict, jj-worktree's shim detects whether it's in a jj repo and passes through to real git otherwise, and the `gitdir:` file is invisible to jj. ## The Long-Term Fix The real solution is native jj support in editors. Zed has an open issue for it. VS Code has community extensions in early development. Until those ship, the `gitdir:` pointer gets you most of the way there, accurate diffs for new and modified files (when the shared index still matches the expected base), blame annotations, and object resolution, with a single line of text and zero dependencies. The objects were always there. libgit2 just needed a signpost. --- Further reading: - [Git repository layout specification](https://git-scm.com/docs/gitrepository-layout?ref=til.codes) \-- the `gitdir:` format definition - [libgit2 repository discovery](https://libgit2.org/libgit2/?ref=til.codes#HEAD/group/repository/git%5Frepository%5Fdiscover) \-- the C API that IDEs call - [jj workspace documentation](https://jj-vcs.github.io/jj/latest/working-copy/?ref=til.codes) \-- how workspaces share the repo store - [jj-worktree](https://github.com/kawaz/jj-worktree?ref=til.codes) \-- Rust git shim for CLI compatibility - [Git index format](https://git-scm.com/docs/index-format?ref=til.codes) – the binary index specification ### LiveView Already Knows When Your Server Crashed URL: https://til.codes/liveview-already-knows-when-your-server-crashed/ Last updated: 2026-04-03T03:18:56.000Z This week, I reviewed a pull request that added error flash handling to a Phoenix app. The JavaScript implementation seemed straightforward: listening for `phx:page-loading-start`, checking the socket connection, showing a popover for server crashes, and hiding it during navigation. It included two helper functions, an event listener, and a manual `isConnected()` check to differentiate between server errors and network issues. Then I opened the LiveView documentation and realized we were reinventing functionality that already exists in the framework. ### What We Initially Built Here's the code we thought was complete: ```javascript window.addEventListener("phx:page-loading-start", (info) => { topbar.show(300) if (info.detail.kind === "error" && liveSocket.socket.isConnected()) { showErrorFlash() } else { clearErrorFlash() } }) function showErrorFlash() { const el = document.getElementById("client-error-flash") if (el && !el.matches(":popover-open")) { el.showPopover() } } function clearErrorFlash() { const el = document.getElementById("client-error-flash") if (el?.matches(":popover-open")) { el.hidePopover() } } ``` The key logic here is the `isConnected()` check. The `phx:page-loading-start` event triggers with `kind: "error"` for both server crashes and network disconnects. To show the error flash only for server crashes, we checked if the socket was connected at the time of the error, an indicator that the server process failed rather than the network. It worked, and initially, it made sense. But as I dug deeper into the docs, I realized LiveView already accounts for this distinction. ### LiveView's Built-in Classes LiveView uses [CSS classes on the root container](https://hexdocs.pm/phoenix%5Flive%5Fview/syncing-changes.html?ref=til.codes#navigation-classes) to represent connection states. The `data-phx-main` element gets the following classes: - `phx-connected` when the WebSocket is active. - `phx-disconnected` when the network connection drops. - `phx-error` when the server process crashes. That last class, `phx-error`, was exactly what we needed. LiveView distinguishes between server crashes and network disconnects, applying `phx-error` only to server issues. The manual `isConnected()` check and event logic we wrote were redundant. ### Observing Classes Instead of Events If we were just toggling a `div` CSS alone could handle this: `.phx-error #client-error-flash { display: block }`. But since we're using the [Popover API](https://developer.mozilla.org/en-US/docs/Web/API/Popover%5FAPI?ref=til.codes) with `popover="manual"`, CSS can't do it alone. The popover needs to be in the top layer to properly display above `` modals in our app. We simplified the JavaScript by replacing the event listener and helper functions with a `MutationObserver` to watch for class changes on the LiveView container: ```javascript const main = document.querySelector("[data-phx-main]") if (main) { new MutationObserver(() => { const flash = document.getElementById("client-error-flash") if (!flash) return if (main.classList.contains("phx-error")) { if (!flash.matches(":popover-open")) flash.showPopover() } else { if (flash.matches(":popover-open")) flash.hidePopover() } }).observe(main, { attributes: true, attributeFilter: ["class"] }) } ``` The observer reacts to LiveView's state transitions. When a server crash occurs, `phx-error` is added, triggering the popover to open. When LiveView reconnects, `phx-error` is replaced with `phx-connected`, closing the popover. For network disconnects, `phx-disconnected` is added instead, and the observer does nothing, exactly the behavior we wanted, but with much less code. ### Timing Considerations One issue we ran into: the observer must be set up **after** `liveSocket.connect()`. Initially, we placed it at the top of the script, but `querySelector` returned `null` because the `[data-phx-main]` element wasn't ready. Moving the setup after `liveSocket.connect()` resolved this: ```javascript liveSocket.connect() const main = document.querySelector("[data-phx-main]") if (main) { new MutationObserver(() => { // ... }).observe(main, { attributes: true, attributeFilter: ["class"] }) } ``` Alternatively, a `phx-hook` guarantees the DOM is ready, but for a simple observer like this, the standalone approach is cleaner. ### The Bigger Takeaway This reinforced a lesson I've learned before: check what LiveView already provides before writing custom JavaScript. Features like `phx:page-loading-start` events and navigation classes are well-documented, but it's easy to default to event-driven logic because that's what most JavaScript frameworks emphasize. LiveView often does more than you expect. Sometimes, the best solution is to rely on what's already built in. **Further reading:** - [LiveView navigation classes](https://hexdocs.pm/phoenix%5Flive%5Fview/syncing-changes.html?ref=til.codes#navigation-classes) - [Popover API](https://developer.mozilla.org/en-US/docs/Web/API/Popover%5FAPI?ref=til.codes) - [MutationObserver](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver?ref=til.codes) ### The Magic of `jj absorb`: Rewriting History Without the Pain URL: https://til.codes/the-magic-of-jj-absorb-rewriting-history-without-the-pain/ Last updated: 2025-11-29T00:14:27.000Z I was fixing a typo that spanned six commits With git, that meant an interactive rebase, editing each commit, resolving conflicts. With jj, it was one command: `jj absorb`. ## Why Interactive Rebases Are a Dead End for Cross-Cutting Changes I've been using git for many years, and I've performed thousands of interactive rebases. They're fine for small touch-ups, squashing a WIP commit, rewording a message, or dropping a debug statement you accidentally committed. But they break down when you need to make the same mechanical change across multiple commits. The problem isn't just the manual labor of editing each commit. It's the mental overhead of tracking state across a rebase session. Did I already fix the migration file? Which commit touched the context module? Why am I resolving a merge conflict in the middle of what should be a simple find-and-replace? Worse, if you realize mid-rebase that you missed something, your options are limited. You can abort and start over, or you can push forward and hope the remaining commits don't introduce more issues. The operation is stateful, and that state lives in your working directory, which means you can't easily context-switch to something else while it's in progress. Jujutsu (jj) approaches this differently. Its core insight is that local commits are mutable drafts, not immutable history. ## The Problem with Fixing Mistakes Across Commits Here's a scenario that happens more often than I'd like to admit. You've built a subscription feature over two days, six carefully structured commits. Migration, schema, context functions, tests - each commit stands on its own. The pull request is ready for review. Then the comment comes in: "Hey, we use American English. It's `canceled`, not `cancelled`." A quick grep reveals the problem is everywhere. The migration defines a `cancelled` enum. The schema references `:cancelled`. The context module has a `mark_as_cancelled/1` function. The tests assert on `:cancelled`. Four commits, all with the same mistake repeated. In Git, you have two options. You can tack on a "fix spelling" commit at the end, which pollutes your history with a cleanup patch that has nothing to do with the feature itself. Or you can fire up `git rebase -i`, mark four commits for editing, manually fix each one in sequence, resolve any conflicts that arise, and hope you don't make things worse. I've done both. The first feels sloppy. The second is tedious enough that I sometimes just live with the mistake. This is the exact problem `jj absorb` solves. ## What `jj absorb` Actually Does Jujutsu is a Git-compatible version control system that rethinks some core assumptions. The most important one: local commits are mutable drafts, not carved-in-stone history. This makes operations like history rewriting feel natural instead of dangerous. The `absorb` command takes your current working copy changes and automatically distributes them back into the commits that last modified those lines. You make the fix once, and jj figures out where each piece belongs. ## A Concrete Example Let's walk through the subscription spelling issue with real code. You have four commits in your branch: ``` @ f8a2b1c Add subscription cancellation tests | o e7d3c4a Add cancel_subscription/1 to context module | o d6b2a9f Add Subscription schema with status field | o c5a1e8d Add subscriptions migration with status enum ``` Each commit contains the British spelling. The migration defines an enum with `'cancelled'`. The schema has ```ruby field :subscription_status, Ecto.Enum, values: [:pending, :confirmed, :cancelled] ``` The context function is named `mark_as_cancelled/1`. The tests call this function and assert on `:cancelled`. With Git, fixing this means an interactive rebase where you edit each commit in turn, run your replacement, stage changes, continue the rebase, and repeat. If you've done this, you know the friction. It's not hard, but it's enough work that you think twice before doing it. With jj, you fix everything at once: ``` # Make all the changes in your working copy sed -i 's/cancelled/canceled/g' priv/repo/migrations/*_create_subscriptions.exs sed -i 's/cancelled/canceled/g' lib/my_app/subscriptions/subscription.ex sed -i 's/cancelled/canceled/g' lib/my_app/subscriptions.ex sed -i 's/cancelled/canceled/g' test/my_app/subscriptions_test.exs ``` Then you run a single command: ``` jj absorb ``` The output tells you what happened: ``` Absorbed changes into 4 revisions: f8a2b1c -> f8a2b1c' Add subscription cancellation tests e7d3c4a -> e7d3c4a' Add cancel_subscription/1 to context d6b2a9f -> d6b2a9f' Add Subscription schema with status field c5a1e8d -> c5a1e8d' Add subscriptions migration with status enum Rebased 0 descendant commits. ``` The command analyzed each changed line, found which commit last touched it, grouped the changes accordingly, applied them to the appropriate commits, and rebased descendants. Your history now shows that `canceled` was the spelling from the beginning. No fixup commits, no manual rebasing. ## When This Fits Your Workflow The sweet spot for `absorb` is when you have a mechanical change that needs to apply across multiple recent commits. Spelling inconsistencies are the obvious case. Renaming functions or variables that appear in several commits is another. Updating configuration URLs, standardizing on a code pattern after receiving review feedback, or fixing a consistent typo you made ten times, all of these are perfect candidates. It works less well for structural refactors where the change affects how code is organized, not just text replacement. And if the same line was modified differently in multiple commits, jj will detect the conflict and refuse to proceed, which is the right behavior. The other important constraint: don't use this on commits you've already pushed to a shared branch. Local history is yours to rewrite; public history is not. ## Some Useful Variations You can limit the scope if needed. To absorb only specific files: ``` jj absorb lib/my_app/subscriptions.ex test/my_app/subscriptions_test.exs ``` To restrict how far back jj looks: ``` jj absorb --into 'ancestors(@, 5)' # Only consider last 5 commits ``` And if you're unsure, preview the plan: ``` jj absorb --dry-run ``` If something goes wrong, jj's `undo` command is your safety net. It maintains an operation log of everything you've done. ## The Draft Mentality What makes `absorb` possible is jj's core philosophy: local commits are drafts. Traditional Git teaches that history is sacred, which makes rewriting feel dangerous and transgressive. jj says your local work is malleable and provides first-class tools to reshape it. When history rewriting is easy, you do it more often. When you do it more often, you keep your commits clean by default. Clean commits make code review smoother, and smooth code review makes better software. ## Getting Started You don't need to abandon Git entirely. Initialize jj in an existing repository: ``` cd your-repo jj git init --colocate ``` Your Git history remains intact, and you can use both git and jj commands. Start with `jj absorb` on a feature branch and see how it feels. The official documentation is solid, though light on this particular command, so I've also found Steve Klabnik's tutorial helpful for building intuition. `jj absorb` won't change how you think about version control overnight, but it might change what you consider reasonable effort for maintaining a clean history. For me, that was enough to make the switch. **Resources:** - [jj documentation](https://docs.jj-vcs.dev/?ref=til.codes) - [Steve Klabnik's jj tutorial](https://steveklabnik.github.io/jujutsu-tutorial/?ref=til.codes) - [git-absorb](https://github.com/tummychow/git-absorb?ref=til.codes) (Git approximation) ### Who Watches the Watcher? Debugging a Silent Langfuse Integration in Production URL: https://til.codes/who-watches-the-watcher-debugging-a-silent-langfuse-integration-in-production/ Last updated: 2025-11-14T00:51:33.000Z I spent a recent afternoon staring at an empty Langfuse dashboard, wondering if I'd lost my mind. I clicked over to the Langfuse UI: nothing. No traces, no generations, no sign that my Elixir app had ever spoken to it. That was not the way I left it the previous day. It's a particular kind of irony, isn't it? You deploy an observability platform to monitor your system, and then the very thing you set up for observability lacks it in return. Who watches the watcher? Turns out, I do. With `:dbg` and a stubborn refusal to redeploy every time I form a new hypothesis. The setup seemed straightforward enough. I was designing an AI email parser that processes emails daily, extracting structured data from messy HTML using self-hosted LLM models with VLLM. I needed to monitor latency, token usage, prompt effectiveness, and, importantly, which types of emails consumed the most GPU time. Langfuse made sense. A self-hosted option for data privacy, a proper OpenAPI spec, and they allow me to define custom costs for VLLM (since I'm paying for GPU hours, not tokens). I wired up `Data.Telemetry.LangfuseReporter`, deployed, everything was working fine, until the next day... and... silence. ## Reading the Code Like a Detective The first rule of production debugging is to understand what the code *should* do. I opened `Data.Telemetry.LangfuseReporter`: ```ruby defmodule Data.Telemetry.LangfuseReporter do @moduledoc """ Enhanced Langfuse reporter for VLLM observability with synchronous calls. """ @vllm_events [ [:data, :email, :llm_parse], [:data, :email, :vertex_parse], [:data, :vllm, :generation], [:data, :vllm, :tool_call] ] def attach_handlers do if enabled?() do Enum.each(@vllm_events, fn event -> :telemetry.attach( "langfuse-#{Enum.join(event, "-")}", event, &handle_event/4, %{} ) end) Logger.info("[Langfuse] Enhanced handlers attached for VLLM tracing") end :ok end defp handle_event(_event_name, _measurements, _metadata, _config) do :ok end end ``` First false red flag: The `handle_event/4` callback was a stub. Just `:ok` and nothing else. That explained why telemetry events weren't auto-capturing, but I knew the integration was also making direct calls from the LLM parser via `LangfuseReporter.create_trace/1` and `create_generation/1`. The `create_trace` function looked solid. It built a payload, checked for config, fired off a `Req.post`. Classic HTTP client works. But was it actually running in production? I could've added logs and waited 15 minutes for a redeploy. Instead, I did what any BEAM-powered masochist would do: I traced the live system. ## The Configuration Check That Wasn't Enough A quick `kubectl exec` got me into the pod. Environment variables looked fine, Langfuse endpoint, public key, secret key all present, using Kubernetes DNS to hit the service in-cluster. Good, I didn't mess up any env variable this time. ```bash kubectl exec -it data-574c77b48b-kzw6z -- /bin/bash env | grep LANGFUSE # LANGFUSE_ENDPOINT=http://langfuse-web.langfuse.svc.cluster.local:3000 # LANGFUSE_PUBLIC_KEY=pk-lf-[redacted] # LANGFUSE_SECRET_KEY=sk-lf-[redacted] ``` But I don't trust environment variables. I trust what the VM is actually doing. That's where Erlang's `:dbg` module becomes your best friend. You can watch every call, every argument, every return value, in real-time, without touching a line of code. So, I fired up a remote shell and started tracing: ```bash kubectl exec -it data-574c77b48b-kzw6z -- /app/bin/data remote ``` Inside the remote IEx session: ```ruby # Start the tracer :dbg.start() # Set up a custom tracer process that prints to stdout :dbg.tracer(:process, {fn msg, _n -> IO.puts(inspect(msg, limit: :infinity, pretty: true)) 0 end, 0}) # Trace all processes for call events :dbg.p(:all, :c) # Trace specific functions with return values :dbg.tpl(Data.Telemetry.LangfuseReporter, :send_to_langfuse, [{:_, [], [{:return_trace}]}]) :dbg.tpl(Data.Telemetry.LangfuseReporter, :create_trace, [{:_, [], [{:return_trace}]}]) :dbg.tpl(Req, :post, [{:_, [], [{:return_trace}]}]) ``` The pattern `[{:_, [], [{:return_trace}]}]` means: match any arguments, no guards, and include the return value. It's the trace pattern equivalent of "show me everything." Then I triggered a test event: ```ruby Data.Telemetry.LangfuseReporter.create_trace(%{ trace_id: UUID.uuid4(), user_id: 99999, email_id: 123, subject: "Test trace", sync_id: 456, gmail_auth_id: 789 }) ``` The trace output was revealing: ```ruby {:trace, #PID<0.1234.0>, :call, {Data.Telemetry.LangfuseReporter, :create_trace, [%{trace_id: "550e8400-e29b-41d4-a716-446655440000", ...}]}} {:trace, #PID<0.1234.0>, :call, {Data.Telemetry.LangfuseReporter, :send_to_langfuse, [%{id: "...", timestamp: "2025-11-14T10:30:00Z", type: "trace-create", ...}]}} {:trace, #PID<0.1234.0>, :return_from, {Data.Telemetry.LangfuseReporter, :send_to_langfuse, 1}, :ok} ``` Critical finding: `send_to_langfuse` was being called and returning `:ok`, but there was NO trace event for `Req.post/2`. The HTTP request wasn't happening. I added more granular tracing, this time for Req's internals: ```ruby :dbg.tpl(Req.Request, :run_request, [{:_, [], [{:return_trace}]}]) :dbg.tpl(Finch, :request, [{:_, [], [{:return_trace}]}]) ``` Triggered another test, and there it was: ```ruby {:trace, #PID<0.1234.0>, :exception_from, {Req.Request, :run_request, 1}, {ArgumentError, "unknown registry: Req.Finch"}} ``` ## The Missing Finch Pool and the Hot-Load Heroics Ah. Req was trying to use a Finch connection pool called `Req.Finch`, but my app only had `DataFinch` The request failed silently somewhere in the middleware stack, got converted to an error tuple, and my code treated it as missing config and swallowed it. The fix was trivial: tell Req to use the correct Finch pool ```ruby case Req.post(url, json: batch_payload, headers: headers, receive_timeout: 10_000, finch: DataFinch) do {:ok, %{status: status}} when status in 200..299 -> :ok {:ok, %{status: status, body: _body}} -> {:error, {:http_error, status}} {:error, reason} -> {:error, reason} end ``` But I wasn't about to wait 15 minutes to test it. The BEAM has a better way. Hot code loading is one of those BEAM features that feels like cheating. It was built for telecom systems that couldn't go down, but it's equally valuable for debugging. You can replace running code without stopping the system, test a fix immediately, and iterate in seconds. Here's the process I used: 1. Modified the code locally with `finch: DataFinch` 2. Compiled just that module: `mix compile --force` 3. Located the compiled .beam file: `_build/dev/lib/data/ebin/Elixir.Data.Telemetry.LangfuseReporter.beam` 4. Copied it to the pod: ```bash kubectl cp _build/dev/lib/data/ebin/Elixir.Data.Telemetry.LangfuseReporter.beam \ data-574c77b48b-kzw6z:/tmp/ ``` 1. Hot-loaded in the remote IEx session: ```ruby code_binary = File.read!("/tmp/Elixir.Data.Telemetry.LangfuseReporter.beam") :code.load_binary(Data.Telemetry.LangfuseReporter, 'Elixir.Data.Telemetry.LangfuseReporter.beam', code_binary) ``` Remote shell returned `{:module, Data.Telemetry.LangfuseReporter}`. I triggered another trace and saw it: `Req.post/2` was now being called, and returning `{:ok, %Req.Response{status: 207, ...}}`. Status 207 means "Multi-Status" - batch processed with mixed results. But the dashboard was still empty. ## Shot Into the Ether: Why the Server Wasn't Listening, or Was it? Time to check the server side. Well, turns out that Langfuse started receiving the events, but Langfuse's logs were full of validation errors: ```json { "level": "error", "message": "Error processing events", "timestamp": "2025-11-14T10:45:23.123Z", "errors": [ { "code": "invalid_type", "expected": "string", "received": "undefined", "path": ["batch", 0, "id"], "message": "Required" }, { "code": "invalid_type", "expected": "string", "received": "undefined", "path": ["batch", 0, "timestamp"], "message": "Required" } ] } ``` The payload structure was wrong. Langfuse was receiving requests but rejecting them due to schema validation errors. This is where many integrations fail: assuming you know the API structure without reading the specification. I fetched Langfuse's official OpenAPI spec: ```bash http GET https://cloud.langfuse.com/generated/api/openapi.yml \ > langfuse-openapi.yml ``` Searching for the ingestion endpoint's schema: ```yaml BaseEvent: type: object required: [id, timestamp, type, body] properties: id: type: string format: uuid description: Unique event identifier timestamp: type: string format: date-time type: type: string enum: [trace-create, generation-create, span-create, ...] body: type: object description: Event-specific payload ``` There we go - I was conflating the *event* ID with the *trace* ID. The outer payload needs a unique event ID, timestamp, and type, while the `body` contains the actual trace data. My payload: ```ruby %{ type: "trace-create", body: %{ id: trace_id, # WRONG - trace ID inside body timestamp: "...", # WRONG - missing at top level } } ``` Correct payload: ```ruby %{ id: UUID.uuid4(), # Unique EVENT ID timestamp: "...", type: "trace-create", body: %{ id: trace_id, # TRACE ID inside body # ... } } ``` This is a common API design pattern: the envelope vs. the payload. I fixed `create_trace/1`: ```ruby def create_trace(params) do payload = %{ id: UUID.uuid4(), # EVENT ID timestamp: format_datetime(params[:start_time] || DateTime.utc_now()), type: "trace-create", body: %{ id: params.trace_id, # TRACE ID sessionId: params[:session_id], userId: to_string(params[:user_id]), # ... } } send_to_langfuse(payload) end ``` ## Generations, Cost Tracking, and the Missing Context While in the spec, I found more issues with the generation payload. Token usage structure was wrong: ```ruby # My code body: %{ promptTokens: params[:prompt_tokens], completionTokens: params[:completion_tokens], totalTokens: params[:total_tokens], } # Spec requirement CreateGenerationBody: properties: usage: type: object properties: promptTokens: integer completionTokens: integer totalTokens: integer ``` Fixed: ```ruby body: %{ usage: %{ promptTokens: params[:prompt_tokens], completionTokens: params[:completion_tokens], totalTokens: params[:total_tokens] }, # ... } ``` Output field naming was wrong. I used `completion` (OpenAI style), but Langfuse uses `output`: ```ruby # Wrong body: %{completion: "The generated text..."} # Correct body: %{output: "The generated text..."} ``` Cost tracking structure needed work. For VLLM, costs are GPU-based: ```ruby defp calculate_vllm_cost(tokens, latency_ms) when is_number(tokens) and is_number(latency_ms) do # $3.50/hour for H200 GPU hours = latency_ms / (1000 * 60 * 60) hours * @hourly_cost |> Float.round(6) end body: %{ costDetails: %{ total: calculate_vllm_cost(params[:total_tokens], params[:latency_ms]), input: calculate_vllm_cost(params[:prompt_tokens], params[:latency_ms] / 2), output: calculate_vllm_cost(params[:completion_tokens], params[:latency_ms] / 2) } } ``` This apportions GPU cost between input/output based on token counts, a reasonable proxy for computational work. After hot-loading all the schema fixes, events finally appeared! But `gmail_id` was consistently `null`. This field tracks which email was synced. The data lived in the `gmails` table, accessible via an `sync` association. My `Emails.get/1` wasn't preloading it: ```ruby # Before def get(id) do Repo.get(Email, id) end # After def get(id) do Email |> Repo.get(id) |> Repo.preload(:sync) end ``` Then extract the ID: ```ruby gmail_id = email.sync && email.sync.gmail_id LangfuseReporter.create_trace(%{ gmail_id: gmail_id, # ... }) ``` After this final fix, full context flowed into Langfuse. ## Debugging Philosophy: What This Session Taught Me (Again) This wasn't my first rodeo with production debugging, but it reinforced some truths I've come to live by after a decade of building systems: **Observability beats speculation.** The urge is always "add logs and redeploy." But logs are limited. You must anticipate what to log, they add overhead and require redeployment to change. Instead, use VM-level tracing to see *exactly what executes*. `:dbg` Let me observe function calls, arguments, return values, and exceptions in real-time without modifying code. When to use `:dbg` vs logging: - **Use `:dbg` when:** Debugging unexpected behavior, tracing control flow, measuring precise timing - **Use logging when:** Recording business events, tracking user actions, aggregating metrics **Hot code loading is for debugging, not just deployments.** Being able to test a fix immediately, iterate in seconds instead of minutes, and validate on one pod before cluster-wide deploy changes how you debug. Just remember the limitations: - Can't change module attributes or app config - Requires restarting stateful processes (GenServers) to pick up state changes - Safe for pure functional code, risky for stateful code - Always follow up with proper deployment **Read the spec, not the docs.** API documentation is often incomplete. OpenAPI specs are machine-readable contracts that define required fields, exact data types, nested structures, and valid enum values. When integrating any external API: 1. Fetch the OpenAPI spec 2. Generate types/schemas from it (or validate against it) 3. Write tests that validate your payload against the spec For Elixir, consider using `ExJsonSchema` to validate payloads against OpenAPI schemas before sending them. **Configuration is code.** The missing Finch adapter came from an implicit assumption that `Req.post` would "just work." But configuration has dependencies, defaults, and failure modes. Best practices: - Make configuration explicit in function calls - Don't rely on global defaults when alternatives exist - Validate configuration at startup, not first use - Use typespecs to document configuration requirements **207 status codes hide problems.** Multi-Status responses mean "some succeeded, some failed." Don't treat them as blanket success. Always check the response body: ```ruby case Req.post(url, json: batch_payload, headers: headers, finch: DataFinch) do {:ok, %{status: 207, body: %{"successes" => s, "errors" => e}}} when length(e) > 0 -> Logger.warning("Langfuse partial failure", successes: length(s), errors: inspect(e)) {:error, {:partial_failure, e}} {:ok, %{status: status}} when status in 200..299 -> :ok {:error, reason} -> {:error, reason} end ``` **Distributed systems require multi-layer debugging.** The bug spanned four layers: application code, HTTP client, API schema, and data layer. Each was "correct" in isolation. The failure only emerged from their interaction. Debugging approach: 1. Start at boundaries (API requests/responses) 2. Work inward (application code, data layer) 3. Verify assumptions at each layer 4. Check both sides of network calls ## Performance Considerations and Production Safety **Cost of `:dbg` tracing:** While powerful, `:dbg` has overhead: - Each traced call generates a message to the tracer process - High-frequency functions (called millions of times/second) can overwhelm the tracer - Tracing `:all` processes captures everything, including BEAM internals Production safety guidelines: 1. Trace specific modules/functions, not `:all` modules 2. Use `:dbg.ctp/1` to clear trace patterns when done 3. Limit to short debugging sessions (minutes, not hours) 4. Monitor tracer process mailbox: `:erlang.process_info(pid, :message_queue_len)` For my use case (tracing HTTP requests that happen a few times per second), overhead was negligible—sub-millisecond per call. **Req vs Finch performance:** Specifying the Finch pool (`finch: DataFinch`) matters for performance: - Without a pool: new connection per request (TCP handshake, TLS negotiation) - With a pool: connection reuse (HTTP/1.1 keep-alive or HTTP/2 multiplexing) For my Langfuse integration (intra-cluster HTTP calls), this changed the latency from \~50ms to \~5ms per request. ## Kubernetes Debugging Techniques **Copy compiled .beam files for hot patches:** ```bash # Copy to pod kubectl cp local/file.beam namespace/pod:/tmp/ # In remote shell code_binary = File.read!("/tmp/file.beam") :code.load_binary(Module.Name, 'file.beam', code_binary) ``` **Check logs on both sides:** ```bash # Client (your app) kubectl logs -f data-574c77b48b-kzw6z # Server (Langfuse) kubectl logs -f -n langfuse langfuse-web-68cd7fb787-dhmd2 # Filter for errors kubectl logs -f data-574c77b48b-kzw6z | grep -i error ``` **Port-forward for direct testing:** ```bash # Forward Langfuse port to localhost kubectl port-forward -n langfuse svc/langfuse-web 3000:3000 # Test from local machine curl -X POST http://localhost:3000/api/public/ingestion \ -H "Authorization: Basic $(echo -n 'pk:sk' | base64)" \ -H "Content-Type: application/json" \ -d '{"batch": [...]}' ``` **Verify service discovery:** ```bash kubectl exec -it data-574c77b48b-kzw6z -- nslookup langfuse-web.langfuse.svc.cluster.local ``` ## Architectural Improvements for Next Time This session revealed several areas for improvement: **Schema validation at compile time:** ```ruby defmodule Data.Telemetry.LangfuseSchema do @external_resource "priv/langfuse_openapi.yml" @openapi_spec YamlElixir.read_from_file!("priv/langfuse_openapi.yml") def validate_trace(payload) do schema = get_in(@openapi_spec, ["components", "schemas", "CreateTraceEvent"]) ExJsonSchema.Validator.validate(schema, payload) end end # In create_trace/1 payload = build_trace_payload(params) if Mix.env() in [:dev, :test] do case LangfuseSchema.validate_trace(payload) do {:error, errors} -> raise "Invalid payload: #{inspect(errors)}" :ok -> :ok end end send_to_langfuse(payload) ``` **Circuit breaker for external APIs:** ```ruby defmodule Data.Telemetry.LangfuseCircuitBreaker do use GenServer @failure_threshold 10 @reset_timeout :timer.minutes(5) def record_failure do GenServer.call(__MODULE__, :record_failure) end def handle_call(:record_failure, _from, %{failures: f} = state) when f >= @failure_threshold do Logger.error("Langfuse circuit breaker OPEN") # Alert to Sentry/PagerDuty {:reply, :circuit_open, state} end # ... implementation end ``` **Integration tests against real schemas:** ```ruby defmodule Data.Telemetry.LangfuseReporterTest do use ExUnit.Case @langfuse_schema File.read!("priv/langfuse_openapi.yml") |> YamlElixir.read_from_string!() test "create_trace builds valid payload" do params = %{trace_id: UUID.uuid4(), user_id: 123, ...} payload = LangfuseReporter.build_trace_payload(params) schema = get_in(@langfuse_schema, [...]) assert :ok = ExJsonSchema.Validator.validate(schema, payload) end end ``` ## What I Learned (Again) This debugging journey, from silent failures to full observability, shows why I keep choosing Elixir for production systems. The BEAM gives you superpowers, sure, production introspection without redeployment, hot code loading for rapid hypothesis testing, process-level isolation that makes tracing safe, and built-in distribution that extends debugging across nodes. I've used all of them in this session, and they've saved me hours of redeploy cycles. But here's the thing: tools like `:dbg` and hot code loading are only effective when you combine them with solid engineering practices. You need to read the OpenAPI spec, not just the documentation. You need to validate schemas at compile time when you can. You need to make configuration explicit instead of relying on magic defaults. You need to check both sides of network calls - client logs and server logs. You need to test with real data against real schemas, not just mocked unit tests. And when you find yourself staring at a silent dashboard at 3 AM, remember this: resist the urge to add logging and redeploy. Instead, observe the running system with tracing. Form hypotheses based on what you actually see, not what you think should be happening. Test your fixes via hot code loading on a single pod. Verify on both client and server. Only deploy the validated fix cluster-wide. And if you're deploying an observability tool? Make sure you have a way to observe it. Because nobody else will. ### Flaky Playwright Tests and Phoenix: A Distributed Systems Problem URL: https://til.codes/flaky-playwright-tests-and-phoenix-a-distributed-systems-problem/ Last updated: 2025-11-08T15:50:16.000Z Ever run into that dreaded `DBConnection.OwnershipError` during your Playwright tests? The one that makes you think, "Great, another flaky test." Let me tell you, this isn’t about flakiness or some elusive race condition. What you’re seeing is the BEAM VM doing exactly what it’s designed to do: enforcing strict process isolation. Remember, this isn’t a monolith you’re testing. You’re working with three fully isolated processes, each with its own boundaries. One of them is stepping over the line, trying to grab a database connection that it’s not supposed to have access to. Here’s the kind of in-depth explanation I wish I’d had when I first ran into this issue. We’ll dig into how the Ecto SQL Sandbox operates, explore the process dictionary, walk through the ETS permission table lookups, and break down why `phoenix_test_playwright` it leverages the user agent string as a subtle communication channel. By the time we’re done, you won’t just have your tests running smoothly, you’ll also have a solid grasp of why the solution works, right down to the VM internals. ## The Problem: Three Processes, Zero Shared Context When you write what looks like a simple Playwright feature test: ```ruby # test/my_app_web/features/user_registration_test.exs defmodule MyAppWeb.UserRegistrationTest do use MyAppWeb.FeatureCase, async: true test "user can register with valid data", %{conn: conn} do conn |> visit("/register") |> fill_in("Email", with: "alice@example.com") |> fill_in("Password", with: "SecurePass123!") |> click_button("Create Account") |> assert_has(".alert-success", text: "Welcome!") end end ``` You're actually orchestrating **three distinct processes** that share nothing but the BEAM runtime: ### P1: The ExUnit Test Process (The Owner) This is the process that runs your test code. When you `use MyAppWeb.FeatureCase`, somewhere in the setup it calls: ```elixir # What phoenix_test_playwright does for you owner_pid = self() {:ok, _owner_sup} = Ecto.Adapters.SQL.Sandbox.start_owner!(MyApp.Repo, ownership_timeout: :infinity) ``` This call is deceptively simple. What's actually happening: 1. **ETS Table Creation**: The sandbox creates a public ETS table (by default named `$callers` , but this is misleading, it's not about call stacks) that will store permission tuples. 2. **Process Dictionary Injection**: The `DBConnection.Ownership` process stores the actual connection reference in **P1's process dictionary** under the key `:"$db_connection"`. This is crucial - **only P1 can see this**. 3. **Transaction Wrapping**: The connection is wrapped in a SQL `BEGIN` transaction with `ROLLBACK` queued for when the owner exits. The owner process (P1) now holds the database connection. But here's the kicker: **when P1 exits, that connection dies with it**. This is by design. It's a sandbox, transactions are supposed to be isolated and ephemeral. ### P2: The HTTP Request Handler (The Orphan) When Playwright's process hits your Phoenix endpoint with an HTTP request, Phoenix spawns a **new, short-lived process** to handle it. This process is supervised by your `Endpoint` supervisor, not your test. Its ancestry looks like: ``` P2 (HTTP Handler) ├── Parent: MyAppWeb.Endpoint.ProcessSupervisor ├── Grandparent: MyAppWeb.Endpoint └── No relation to P1 whatsoever ``` When your LiveView mounts or controller action runs, it executes in **P2's context**. If it tries to query the database: ```elixir # Inside your LiveView mount/3 (running in P2) def mount(_params, _session, socket) do user_count = MyApp.Repo.aggregate(MyApp.Accounts.User, :count) {:ok, assign(socket, user_count: user_count)} end ``` The `Repo.aggregate/3` call triggers a `DBConnection` lookup. Here's the exact sequence inside `lib/db_connection.ex`: ```elixir # Simplified from DBConnection source def checkout(conn, opts) do owner = DBConnection.Ownership.find_owner(self()) # ... end ``` `find_owner/1` does a three-step search: 1. **Check its own process dictionary**: `Process.get(:"$db_connection")` → `nil` 2. **Walk ancestry**: Recursively check parent processes → Not found 3. **Query ETS permission table**: Look for `{self(), :allowed, owner_pid}` → Not found Result: `{:error, :not_found}` → **OwnershipError**. ### P3: The LiveView GenServer (The Long-Lived Ghost) The real complexity starts after the initial HTTP request completes. When Playwright upgrades to a WebSocket connection, Phoenix spawns **yet another process**, a `Phoenix.LiveView.Socket` GenServer, supervised by the `Phoenix.Socket.Pool` supervisor: ``` P3 (LiveView Socket) ├── Parent: Phoenix.Socket.Pool.Supervisor ├── Grandparent: MyAppWeb.Endpoint └── Still no relation to P1 ``` This process is **long-lived**. It persists for the duration of the WebSocket connection, handling `handle_event/3`, `handle_info/2`, and `handle_params/3` callbacks. If your LiveView does any async work: ```elixir def handle_info(:delayed_query, socket) do # This runs AFTER your test might have passed data = MyApp.Repo.all(MyApp.Analytics.Event) {:noreply, assign(socket, data: data)} end ``` You're now in a race condition. The test process (P1) might finish, trigger a transaction rollback, and exit, **while P3 is still executing**. When P3's query hits the database, the connection is gone. ## How Ecto SQL Sandbox Actually Works ### The Process Dictionary: Connection Storage When `start_owner!/2` is called, it eventually reaches `DBConnection.Ownership.start_link/2`. Here's the critical code from `lib/db_connection/ownership.ex`: ```elixir def init({pool, owner, tag, timeout}) do # The connection ref is stored in THIS process's dictionary Process.put(:"$db_connection", {:owner, pool, tag}) # ... end ``` The process dictionary is a key-value store **local to each process**. It's not shared. It's not a global variable. It's not accessible from other processes. This is fundamental BEAM isolation. When your test process (P1) owns the connection, **only P1 can find it in its dictionary**. No amount of `send/2` or message passing will give P2 or P3 access. They need explicit permission. ### The ETS Table: The Permission Ledger The sandbox creates a public ETS table (by default: `:"$db_connection_owners"`). Let's examine its structure: ```elixir # When you call Ecto.Adapters.SQL.Sandbox.allow/3 :ets.insert_new(@ownership_table, {allowed_pid, :allowed, owner_pid, pool}) ``` The table schema is: - **Key**: `allowed_pid` (the process being granted access) - **Value**: `{allowed_pid, :allowed, owner_pid, pool}` - **Access**: Public (readable/writable from any process) When P2 or P3 attempts a database operation, `DBConnection.find_owner/1` queries this table: ```elixir # From lib/db_connection/ownership.ex def find_owner(caller_pid) do case :ets.lookup(@ownership_table, caller_pid) do [{^caller_pid, :allowed, owner_pid, _pool}] -> {:ok, owner_pid} [] -> find_owner_by_ancestry(caller_pid) end end ``` This is a **constant-time O(1) lookup,** fast, but it means the permission must be inserted before the query happens. ### The Permission Check Flow (Step-by-Step) Let's trace a `Repo.insert/1` call from within a LiveView `handle_event/3`: ```elixir # 1. User clicks button # 2. P3 (LiveView) receives websocket message # 3. handle_event/3 executes in P3 context def handle_event("create_user", params, socket) do # This line: user = MyApp.Repo.insert!(changeset) # Expands to: Ecto.Repo.insert!(MyApp.Repo, changeset) # Which calls: Ecto.Adapters.SQL.insert(adapter_meta, query, opts) # Which calls: DBConnection.execute(conn, query, opts) # Inside DBConnection.execute/4: ownership = DBConnection.Ownership.find_owner(self()) # self() is P3's PID # find_owner/1 does: # Step 1: Check own process dictionary Process.get(:"$db_connection") # => nil # Step 2: Check ETS table :ets.lookup(:'$db_connection_owners', self()) # => [] # Step 3: Check ancestry (simplified) find_ancestor_owner(self()) # => :error # Result: {:error, :not_found} # Which raises: DBConnection.OwnershipError end ``` The error message is telling you the truth: *"cannot find ownership process for #PID<0.x.y>"*. It searched the process dictionary, the ETS table, and the supervision tree. Nothing. ## The User Agent Pattern: A Covert Permission Channel ### Why Not Custom Headers? You might think: "I'll just pass the owner PID in a custom header!" Let's see why that fails: ![](https://til.codes/content/images/2025/11/image-33.png) The WebSocket upgrade handshake **does not include custom HTTP headers** from the original request. The spec only allows the `Cookie` header (for session) and a few others. Your `X-Test-Owner` header dies at the upgrade boundary. ### The User Agent Survives The User-Agent header is different. It's part of the browser's **persistent identity**, not the request. When you configure Playwright's browser context: ```javascript // What phoenix_test_playwright does automatically const browser = await chromium.launch(); const context = await browser.newContext({ userAgent: "Mozilla/5.0 ... Sandbox: {metadata}" }); ``` This user agent is sent on **every** HTTP request **and** is included in the WebSocket handshake's `User-Agent` field. It's the only piece of metadata that reliably crosses the HTTP/WebSocket boundary. ### How phoenix\_test\_playwright Encodes Metadata Let's look at the actual implementation (simplified from the library): ```elixir # In test setup def start_owner_and_encode_metadata(repo) do # 1. Start owner {:ok, owner_pid} = Ecto.Adapters.SQL.Sandbox.start_owner!(repo) # 2. Generate metadata map metadata = %{ repo: repo, owner: owner_pid, test_pid: self() } # 3. Encode with Phoenix's built-in encoder encoded = Phoenix.Ecto.SQL.Sandbox.encode_metadata(metadata) # Returns a base64-encoded, compressed string # 4. Inject into browser context set_browser_user_agent(encoded) encoded end ``` The `encode_metadata/1` function produces a string like: ``` "Phx-Ecto-Sandbox: eJxVjE0KwjAQRfdzin4B0hZc+QAuXLiC5iGppG1I2lSUDqXv7k1c3MzL m3kTt5T4W6QsQeDJH9JO3Kqt0BVmhXJXJK6VMK0rYVYXwjQvhHl5JO3LO/5O3B8AAAD//wMAJQBLJg==" ``` This is base64-encoded, zlib-compressed Erlang term. It's opaque, compact, and survives header parsing. ### Decoding and Permission Granting In your endpoint, the `Phoenix.Ecto.SQL.Sandbox` plug decodes this: ```elixir # lib/phoenix_ecto/sql_sandbox.ex def call(conn, _opts) do case get_req_header(conn, "user-agent") do [user_agent | _] -> case extract_metadata(user_agent) do {:ok, %{owner: owner_pid, repo: repo}} -> # CRITICAL: Grant permission to THIS process (P2) Ecto.Adapters.SQL.Sandbox.allow(repo, owner_pid, self()) :error -> :ok end [] -> :ok end conn end ``` The `allow/3` call inserts into the ETS table: ```elixir # From ecto_sql/lib/ecto/adapters/sql/sandbox.ex def allow(repo, owner_pid, allowed_pid) do pool = GenServer.whereis(repo) # Check owner actually owns a connection case :ets.lookup(@owner_table, owner_pid) do [{^owner_pid, :owner, _pool, _tag}] -> # Insert permission tuple :ets.insert(@ownership_table, {allowed_pid, :allowed, owner_pid, pool}) :ok [] -> {:error, :not_found} end end ``` Now, when P2 queries, the ETS lookup succeeds: ```elixir # P2's database query: :ets.lookup(:'$db_connection_owners', self()) # => [{#PID<0.3421.0>, :allowed, #PID<0.2261.0>, #PID<0.123.0>}] ``` ### LiveView Permission: The on\_mount Hook The HTTP handler (P2) is short-lived. After it renders the initial HTML, it terminates. But the WebSocket process (P3) is just starting. It needs the **same permission** granted again. The `get_connect_info/2` function extracts the user agent from the WebSocket handshake: ```elixir # lib/my_app_web/live_helpers.ex def on_mount(:default, _params, _session, socket) do if connected?(socket) do # Only for WebSocket-connected mount case get_connect_info(socket, :user_agent) do user_agent when is_binary(user_agent) -> # Same metadata, same permission grant Phoenix.Ecto.SQL.Sandbox.allow(user_agent, Ecto.Adapters.SQL.Sandbox) _ -> :ok end end {:cont, socket} end ``` **Critical detail**: `connected?(socket)` returns `false` on the initial HTTP render (P2's context) and `true` on the WebSocket mount (P3's context). This prevents double-granting permission to the same process. ## Mox: The Same Isolation, Same Solution Mox (Elixir's mocking library) has the **same problem**. Mock expectations are stored in the **defining process's dictionary**. Let's trace through: ```elixir # test file test "sends welcome email", %{conn: conn} do # Expectation stored in P1's dictionary expect(MyApp.MockMailer, :send, fn _email -> {:ok, %{id: "test-123"}} end) conn |> visit("/register") |> click_button("Create Account") # Triggers mailer in P3 # P3 cannot see P1's expectation! end # Inside the LiveView def handle_event("create_account", params, socket) do # This runs in P3 MyApp.MockMailer.send(email) # Mox looks in P3's dictionary → :error # Raises: Mox.UnexpectedCallError end ``` Mox stores expectations in an ETS table **private to the defining process**: ```elixir # From mox/lib/mox.ex def expect(mock, name, n \\ 1, code) do # Store in process dictionary of CURRENT process (P1) Process.put({mock, name}, %{n: n, code: code}) end ``` ### The Mox.allow/3 Solution Just like Ecto, Mox provides `allow/3` for cross-process expectations: ```elixir # test/support/live_helpers.ex def on_mount(:default, _params, _session, socket) do if connected?(socket) do case get_connect_info(socket, :user_agent) do user_agent when is_binary(user_agent) -> # Decode metadata to get test PID metadata = Phoenix.Ecto.SQL.Sandbox.decode_metadata(user_agent) # Allow Mox expectations Mox.allow(MyApp.MockMailer, metadata.test_pid, self()) Mox.allow(MyApp.MockRepo, metadata.test_pid, self()) Mox.allow(MyApp.MockHTTPClient, metadata.test_pid, self()) # Also allow Ecto sandbox Phoenix.Ecto.SQL.Sandbox.allow(user_agent, Ecto.Adapters.SQL.Sandbox) _ -> :ok end end {:cont, socket} end ``` ### Mox Patterns: Stubs vs. Expectations **For async-safe tests, prefer stubs**: ```elixir # test/support/feature_case.ex defmodule MyAppWeb.FeatureCase do use ExUnit.CaseTemplate, async: true setup _tags do # Stubs are GLOBAL - visible to all processes stub(MyApp.MockMailer, :send, fn _email -> {:ok, %{id: "stubbed-id"}} end) stub(MyApp.MockHTTPClient, :get, fn _url -> {:ok, %{status: 200, body: "ok"}} end) :ok end end ``` Stubs are stored in a shared ETS table (`:$mox_global`), making them visible to all processes. They don't require `allow/3`. **Use expectations only when you need to assert call count or arguments**: ```elixir # test file test "charges credit card exactly once", %{conn: conn} do expect(MyApp.MockPaymentGateway, :charge, 1, fn _amount -> {:ok, %{transaction_id: "tx-123"}} end) # ... test code ... # Verify called exactly once verify!(MyApp.MockPaymentGateway) end ``` **Never use `set_mox_global/1`**: ```elixir # DON'T - this forces async: false setup :set_mox_global # Creates race conditions between tests ``` `set_mox_global` makes expectations global, which means tests can interfere with each other. It's `async: false` by another name. ## The Race Condition: When Tests Finish Too Early ### The Problem: Async Work After Test Completion Here's a real-world scenario that will bite you: ```elixir defmodule MyAppWeb.DashboardLive do use MyAppWeb, :live_view def mount(_params, _session, socket) do if connected?(socket) do # Simulate delayed analytics loading Process.send_after(self(), :load_charts, 500) Process.send_after(self(), :load_metrics, 1000) end {:ok, assign(socket, page_state: :loading, charts: nil, metrics: nil)} end def handle_info(:load_charts, socket) do # This runs 500ms after mount charts = MyApp.Analytics.generate_charts() # DB queries here {:noreply, assign(socket, charts: charts)} end def handle_info(:load_metrics, socket) do # This runs 1000ms after mount metrics = MyApp.Analytics.get_metrics() # More DB queries {:noreply, assign(socket, metrics: metrics, page_state: :ready)} end end ``` Your test: ```elixir test "shows dashboard", %{conn: conn} do conn |> visit("/dashboard") |> assert_has(".chart-container") # Charts might not be loaded yet! # Test passes as soon as HTML renders... # ...but LiveView is still processing :load_charts message end ``` Timeline: ``` t=0ms: Test starts, visits /dashboard t=10ms: HTTP request (P2) renders initial HTML, shows loading state t=15ms: WebSocket connects (P3), mount/3 sends delayed messages t=20ms: Test asserts on HTML, passes t=25ms: Test process (P1) exits, transaction rolls back t=500ms: P3 handles :load_charts, tries to query DB → 💥 DBConnection.ConnectionError: owner exited ``` ### The Solution: assign\_async and Semantic State Tracking Phoenix LiveView has `assign_async/3` for exactly this problem: ```elixir def handle_info(:load_data, socket) do socket = socket |> assign(page_state: :loading) |> assign_async(:charts, fn -> # Runs in a Task process, but error handling is managed {:ok, %{charts: MyApp.Analytics.generate_charts()}} end) |> assign_async(:metrics, fn -> {:ok, %{metrics: MyApp.Analytics.get_metrics()}} end) {:noreply, socket} end def handle_async(:charts, {:ok, %{charts: charts}}, socket) do {:noreply, assign(socket, charts: charts) |> maybe_set_complete()} end def handle_async(:metrics, {:ok, %{metrics: metrics}}, socket) do {:noreply, assign(socket, metrics: metrics) |> maybe_set_complete()} end defp maybe_set_complete(%{assigns: assigns} = socket) do if assigns.charts != :loading && assigns.metrics != :loading do assign(socket, page_state: :complete) else socket end end ``` **How assign\_async works internally** (simplified): ```elixir # From phoenix_live_view/lib/phoenix_live_view.ex def assign_async(socket, key, func) do task_pid = Task.async(fn -> try do result = func.() send(self(), {:async_result, key, result}) catch kind, reason -> send(self(), {:async_result, key, {:exit, kind, reason}}) end end) put_in(socket.assigns[key], {:loading, task_pid}) end ``` The key insight: `assign_async` tracks the **state** of the async operation, not just the result. When you assert on `data-page-state="complete"`, you're waiting for a **deterministic state**, not racing against a timer. ### Testing with Semantic State ```elixir # Template
<%= if @page_state == :loading do %> <.spinner /> <% else %>
<%= @charts %>
<%= @metrics %>
<% end %>
# Test test "loads all analytics completely", %{conn: conn} do conn |> visit("/dashboard") # Wait for explicit state, not arbitrary timeout |> assert_has("[data-page-state='complete']", timeout: 5_000) # Now safe to assert on content |> assert_has(".charts") |> assert_has(".metrics") end ``` **Performance win**: The test only waits as long as needed, not a fixed `Process.sleep/1` duration. ## Advanced Patterns and Edge Cases ### Testing GenServer Calls from LiveView What if your LiveView calls a GenServer that queries the database? ```elixir defmodule MyApp.PriceCalculator do use GenServer def calculate(product_id) do GenServer.call(__MODULE__, {:calculate, product_id}) end def handle_call({:calculate, product_id}, _from, state) do # This runs in the GenServer process (P4) product = MyApp.Repo.get!(MyApp.Catalog.Product, product_id) price = compute_price(product) {:reply, price, state} end end defmodule MyAppWeb.ProductLive do def handle_event("calculate", %{"id" => id}, socket) do price = MyApp.PriceCalculator.calculate(id) # Calls P4 {:noreply, assign(socket, price: price)} end end ``` Now you have **four processes**: P1 (test), P3 (LiveView), and P4 (GenServer). You need to allow P4 too: ```elixir # In your LiveView on_mount def on_mount(:default, _params, _session, socket) do if connected?(socket) do case get_connect_info(socket, :user_agent) do user_agent when is_binary(user_agent) -> metadata = decode_metadata(user_agent) # Allow the GenServer too Ecto.Adapters.SQL.Sandbox.allow(MyApp.Repo, metadata.owner, MyApp.PriceCalculator) # Also need to allow Mox if GenServer uses mocks Mox.allow(MyApp.MockAPI, metadata.test_pid, MyApp.PriceCalculator) # Grant to current process (P3) Ecto.Adapters.SQL.Sandbox.allow(MyApp.Repo, metadata.owner, self()) _ -> :ok end end {:cont, socket} end ``` **Better approach**: Pass the caller's PID explicitly: ```elixir def handle_event("calculate", %{"id" => id}, socket) do # Tell the GenServer to use our permissions price = MyApp.PriceCalculator.calculate(id, caller_pid: self()) {:noreply, assign(socket, price: price)} end # GenServer def handle_call({:calculate, id, caller_pid}, _from, state) do # Allow this specific call Ecto.Adapters.SQL.Sandbox.allow(MyApp.Repo, caller_pid, self()) # ... query database ... end ``` ### Testing Oban Jobs Triggered by LiveView ```elixir def handle_event("bulk_import", %{"file" => file}, socket) do # Enqueues job that runs in separate process %{id: job_id} = Oban.insert!(MyApp.Workers.ImportJob.new(%{file: file})) # Track job in socket for testing {:noreply, assign(socket, import_job_id: job_id)} end ``` The Oban worker runs in **yet another process** (P5), outside the sandbox. You have three options: 1. **Disable Oban in tests** (simplest): ```elixir # config/test.exs config :my_app, Oban, testing: :inline # Runs synchronously in test process ``` 1. **Allow the worker** (complex): ```elixir defmodule MyApp.Workers.ImportJob do use Oban.Worker @impl true def perform(%Oban.Job{args: args}) do # Worker needs to decode user agent from args metadata = args["sandbox_metadata"] Ecto.Adapters.SQL.Sandbox.allow(metadata, Application.fetch_env!(:my_app, :sandbox_mod)) # ... perform work ... end end ``` 1. **Test the effect, not the job** (recommended): ```elixir test "bulk imports users", %{conn: conn} do conn |> visit("/import") |> upload_file("input[type=file]", "users.csv") |> click_button("Import") # Wait for LiveView to signal completion |> assert_has("[data-import-state='complete']") # Assert on final state, not job internals assert MyApp.Repo.aggregate(MyApp.Accounts.User, :count) == 100 end ``` ### Memory Implications Each sandbox connection holds a PostgreSQL backend process open. With `async: true`, you can have N connections simultaneously: ```elixir # config/test.exs config :my_app, MyApp.Repo, pool_size: 20, # Up to 20 parallel tests ownership_timeout: :infinity ``` Monitor with `:observer.start()`: - **P1 (test)**: \~2MB each - **PostgreSQL backend**: \~5MB each - **Total for 20 tests**: \~140MB On modern hardware, this is trivial. The parallelism gain far outweighs the memory cost. ## Debugging: When It Still Doesn't Work ### Tool 1: Trace the Permission Flow ```elixir # In iex -S mix test :dbg.tracer() :dbg.p(:all, [:call]) # Trace all Sandbox.allow calls :dbg.tp(Ecto.Adapters.SQL.Sandbox, :allow, 3, []) # Run test, watch output ``` You'll see exactly which processes are calling `allow/3` and when. ### Tool 2: Inspect the ETS Table ```elixir # In test setup or IEx def inspect_sandbox() do table = :"$db_connection_owners" # All permissions :ets.tab2list(table) |> Enum.each(fn {allowed, :allowed, owner, pool} -> IO.puts "#{inspect(allowed)} ← allowed by #{inspect(owner)}" end) # Owner connections owner_table = :"$db_connection_owner_table" :ets.tab2list(owner_table) |> Enum.each(fn {owner, :owner, pool, tag} -> IO.puts "Owner: #{inspect(owner)} → Pool: #{inspect(pool)}" end) end ``` ### Tool 3: Trace Process Exits ```elixir # In your test def test_with_exit_trace do # Monitor owner owner = self() ref = Process.monitor(owner) # Run test logic... receive do {:DOWN, ^ref, :process, ^owner, reason} -> IO.inspect(reason, label: "Owner exited") after 5000 -> :ok end end ``` ### Common Error #1: "owner exited" After Test Passes Symptom: Test passes, then you see a database error in the logs. Cause: LiveView is still processing async work. Fix: Use `assign_async` and assert on the semantic state, not just UI presence. ### Common Error #2: "cannot find ownership process" on Initial Page Load **Symptom**: OwnershipError on first HTTP request. **Cause**: `Phoenix.Ecto.SQL.Sandbox` plug not in endpoint, or placed before `Plug.Parsers`. **Fix**: Ensure plug is after parsers but before router: ```elixir plug Plug.Parsers, ... plug Phoenix.Ecto.SQL.Sandbox # Here! plug MyAppWeb.Router ``` ### Common Error #3: Mox.UnexpectedCallError in LiveView **Symptom**: Mock works in test but fails in LiveView. **Cause**: Didn't call `Mox.allow/3` in `on_mount`. **Fix**: Add `Mox.allow(mock, test_pid, self())` alongside sandbox allow. ### Common Error #4: Intermittent Failures with assign\_async **Symptom**: Tests pass locally, fail in CI. **Cause**: CI is slower, async work times out before `assert_has`. **Fix**: Increase `assert_has` timeout or optimize async functions. ```elixir # Increase timeout for CI |> assert_has("[data-page-state='complete']", timeout: 10_000) ``` ## The Internal Implementation: Reading the Source Let's examine the actual DBConnection source to understand the permission check: ```elixir # From hex.pm package db_connection 2.8.1, lib/db_connection/ownership.ex defmodule DBConnection.Ownership do @moduledoc """ DBConnection plugin for ownership. """ @ownership_table :"$db_connection_owners" def find_owner(pid) when is_pid(pid) do case Process.get(:"$db_connection") do {:owner, pool, tag} -> {:ok, {pool, tag}} _ -> case :ets.lookup(@ownership_table, pid) do [{^pid, :allowed, owner, pool}] -> # Recursively find the owner's connection case find_owner(owner) do {:ok, {pool, tag}} -> {:ok, {pool, tag}} error -> error end [] -> # Walk supervision tree find_owner_by_ancestry(pid) end end end defp find_owner_by_ancestry(pid) do case Process.info(pid, :dictionary) do {:dictionary, dict} -> case dict[:"$db_connection_parent"] do nil -> :error parent -> find_owner(parent) end _ -> :error end end end ``` **Key insights from source**: 1. **Transitive permissions are not automatic**: If A allows B, B cannot allow C. Only the **original owner** can grant permissions. 2. **Ancestry check uses `$db_connection_parent`**: This is how `allow/3` with `{:process, parent_pid}` works. 3. **ETS lookup is recursive**: It finds the owner, then finds the owner's connection. ## Alternative Approaches: Trade-offs ### Approach 1: Global Sandbox Mode ```elixir # config/test.exs config :my_app, MyApp.Repo, pool: Ecto.Adapters.SQL.Sandbox, ownership_mode: :global ``` **Pros**: No `allow/3` needed, all processes share one connection. **Cons**: - Forces `async: false` (global state) - Tests can interfere (uncommitted data visible across tests) - PostgreSQL deadlock risk with parallel tests **Verdict**: Only for legacy suites, you can't refactor. ### Approach 2: Transaction Isolation in Tests ```elixir # Don't use sandbox, manage transactions manually setup do :ok = Ecto.Adapters.SQL.begin_test_transaction(MyApp.Repo) on_exit(fn -> Ecto.Adapters.SQL.rollback_test_transaction(MyApp.Repo) end) end ``` **Pros**: No process isolation issues. **Cons**: - Still requires `async: false` (shared transaction) - Manual cleanup is error-prone - Doesn't work with LiveView (process dies before cleanup) **Verdict**: Pre-sandbox era pattern, don't use. ### Approach 3: Roll Your Own Permission Channel ```elixir # Pass PID through application environment setup do owner = self() Application.put_env(:my_app, :test_owner, owner) on_exit(fn -> Application.delete_env(:my_app, :test_owner) end) end # In LiveView def on_mount(_, _, _, socket) do if owner = Application.get_env(:my_app, :test_owner) do Ecto.Adapters.SQL.Sandbox.allow(MyApp.Repo, owner, self()) end {:cont, socket} end ``` **Pros**: Simple, no user-agent magic. **Cons**: - Global state (breaks async) - Race conditions between tests - Application env is a bottleneck (single process access) **Verdict**: Anti-pattern. Global state is death to async tests. ## The Correct Mental Model: Distributed Erlang At its core, the ownership system models **distributed Erlang**. Each test process is like a remote node that owns resources. The `allow/3` function is like granting RPC permissions. ![](https://til.codes/content/images/2025/11/image-34.png) When you think in these terms, the solution is obvious: **explicit, fine-grained permission grants,** exactly what you'd do in a real distributed system. ## Final Architecture: The Complete Picture ![](https://til.codes/content/images/2025/11/image-35.png) This is what you're building. It's not a simple test, it's a **microservices architecture** compressed into a single VM. ## TL;DR: Key Takeaways 1. **`DBConnection.OwnershipError` is correct behavior** \- The BEAM is protecting you from shared-state bugs. 2. **Three processes, three contexts** \- Test, HTTP, and WebSocket processes are isolated by design. 3. **User agent is the only reliable metadata channel** \- It survives HTTP → WebSocket upgrade. Headers don't. 4. **`allow/3` is a permission grant, not a connection transfer** \- The owner still controls the connection; others get temporary access. 5. **`async: false` is admitting defeat** \- Use `Phoenix.Ecto.SQL.Sandbox` plug and `on_mount` hooks instead. 6. **Race conditions are real** \- Use `assign_async` and assert on the semantic state like `data-page-state="complete"`. 7. **Mox needs the same treatment** \- Call `Mox.allow/3` alongside your sandbox allow. 8. **This is distributed systems 101** \- The same patterns apply to `Node.connect/2` and `:rpc.multicall/4`. The ownership system isn't a limitation to work around—it's a **correct design** for concurrent, isolated tests. Once you stop fighting it and start leveraging it, your tests become reliable, parallel, and fast. --- ## References - [phoenix\_test\_playwright](https://github.com/phoenixframework/phoenix%5Ftest%5Fplaywright?ref=til.codes) \- Library that implements this pattern - [Ecto.Adapters.SQL.Sandbox](https://hexdocs.pm/ecto%5Fsql/Ecto.Adapters.SQL.Sandbox.html?ref=til.codes) \- Official documentation - [Phoenix.Ecto.SQL.Sandbox](https://hexdocs.pm/phoenix%5Fecto/Phoenix.Ecto.SQL.Sandbox.html?ref=til.codes) \- Plug documentation - [Mox Multi-process Collaboration](https://hexdocs.pm/mox/Mox.html?ref=til.codes#module-multi-process-collaboration) \- Mox process isolation - [DBConnection Ownership Source](https://github.com/elixir-ecto/db%5Fconnection/blob/master/lib/db%5Fconnection/ownership.ex?ref=til.codes) \- Implementation details - [Phoenix LiveView assign\_async](https://hexdocs.pm/phoenix%5Flive%5Fview/Phoenix.LiveView.html?ref=til.codes#assign%5Fasync/3) \- Deterministic async handling ### Speed Racer Gone Wrong: When CUDA Graph Optimization Killed My Inference Server URL: https://til.codes/speed-racer-gone-wrong-when-cuda-graph-optimization-killed-my-inference-server/ Last updated: 2025-11-05T23:01:32.000Z # I had finally stabilized my vLLM deployment. Qwen3-30B was running smoothly on a single H200\. KV cache was under control. Memory utilization looked healthy at 75%. Everything was perfect. But seems like that was not the end of the story. The inference server was down. CUDA out of memory error. I checked the metrics. Memory usage had been at 65% all day. The OOM happened suddenly, under normal load. No spike in traffic. No unusual requests. Just... crash. I restarted the server. It ran fine for a while. Then crashed again. Different time, same error. As you have guessed by now, this is article is about of hunting down one of the most frustrating bugs I've encountered: non-deterministic CUDA OOM errors caused by graph capture. And why I eventually disabled one of vLLM's core optimizations with a single flag: `--enforce-eager`. ## The Pattern: Random OOMs Under Normal Load The crashes followed no predictable pattern: **Crash 1:** 8 active sequences, memory at 64% **Crash 2:** 12 active sequences, memory at 68% **Crash 3:** 6 active sequences, memory at 61% **Crash 4:** 10 active sequences, memory at 67% No correlation with load. No correlation with memory usage. The crashes seemed random. The error message was always the same: ``` RuntimeError: CUDA out of memory. Tried to allocate 3.25 GiB (GPU 0; 140.54 GiB total capacity; 138.92 GiB already allocated; 1.12 GiB free; 139.50 GiB reserved in total by PyTorch) ``` Wait. "139.50 GiB reserved in total by PyTorch"? My gpu-memory-utilization was 0.75, which should reserve around 106GB (75% of 141GB). Where did the extra 33GB come from? ## What Are CUDA Graphs? To understand what was happening, I needed to understand CUDA graphs. ### The Problem CUDA Graphs Solve When you run a neural network on a GPU, each operation (matrix multiply, activation function, normalization) is launched as a separate CUDA kernel. For a transformer forward pass, this means hundreds of kernel launches. Each kernel launch has overhead: 1. CPU submits work to GPU driver 2. Driver validates the operation 3. Driver schedules the kernel on GPU 4. GPU begins execution For small operations, this overhead can dominate execution time. A 0.1ms matrix multiply might have 0.05ms of launch overhead. That's 50% overhead! For a 60-layer transformer, you might have: - 60 attention layers × 10 operations = 600 operations - 60 feed-forward layers × 8 operations = 480 operations - Total: 1,080+ kernel launches per forward pass At 0.05ms overhead each: 1,080 × 0.05ms = 54ms of pure launch overhead. This is wasted time. The GPU could be computing, but instead it's waiting for the CPU to submit the next kernel. ### How CUDA Graphs Work CUDA graphs pre-record a sequence of operations into a graph data structure. Then you replay the entire graph with a single launch. Instead of: ``` for each operation: launch_kernel(operation) # 0.05ms overhead each ``` You do: ``` # One-time setup (graph capture) start_recording() for each operation: launch_kernel(operation) end_recording() create_graph() # Runtime (replay) launch_graph() # Single 0.05ms overhead for entire graph ``` The graph is captured once, then replayed many times. Instead of 1,080 kernel launches, you have 1 graph launch. The speedup is significant: 10-20% faster inference for typical workloads. ### The Memory Cost Here's the catch: graphs require memory. When you capture a graph, CUDA allocates buffers for: 1. **Intermediate tensors**: All temporary values between operations 2. **Parameter buffers**: Pointers to weights and activations 3. **Graph metadata**: Node structure, dependencies, scheduling info 4. **Memory pool**: Pre-allocated workspace for graph execution For a transformer forward pass, a single graph might allocate: - Intermediate attention scores: 2-4GB - Temporary buffers for normalization: 0.5-1GB - Workspace for matrix multiplies: 1-2GB - Graph metadata: 0.1-0.5GB - Total: 4-8GB per unique graph "Per unique graph" is the key phrase. ## The Problem: Graph Diversity in vLLM vLLM doesn't capture just one graph. It captures many graphs, because different request shapes require different execution patterns. ### What Makes Graphs Different A graph is "unique" based on: 1. **Batch size**: 1 sequence vs 8 sequences vs 32 sequences 2. **Sequence lengths**: Uniform lengths vs mixed lengths 3. **Number of tokens to generate**: 10 tokens vs 100 tokens vs 500 tokens 4. **Attention patterns**: Full attention vs causal attention 5. **KV cache state**: Empty cache vs partially filled cache In vLLM, every unique combination gets its own graph. Example scenarios: **Scenario A:** Batch of 8 sequences, all 2048 tokens long, generating 256 tokens each - Graph A captures: attention with 8 sequences, KV cache growing from 2048 to 2304 **Scenario B:** Batch of 4 sequences, lengths \[1024, 2048, 3072, 4096\], generating 128 tokens each - Graph B captures: different attention pattern, different KV cache sizes **Scenario C:** Batch of 16 sequences, all 512 tokens, generating 50 tokens each - Graph C captures: larger batch, smaller sequences Each scenario creates a new graph. Each graph allocates 4-8GB. ### The Graph Cache vLLM maintains a graph cache. Once a graph is captured, it's reused for matching patterns. The cache has a limit (typically 100-200 graphs). When you exceed this limit, old graphs are evicted. But here's the problem: **graphs aren't freed immediately**. CUDA graph memory is managed by PyTorch's caching allocator. When a graph is evicted from vLLM's cache, PyTorch doesn't necessarily free the memory. It keeps it in its memory pool, hoping to reuse it. This means graph memory accumulates over time, even as old graphs are evicted. ![](https://til.codes/content/images/2025/11/image-28.png) Over hours or days of operation, as request patterns vary, more and more graphs get captured. Memory usage creeps up. Eventually, you hit OOM. ## My Workload: Structured Output with Diverse Schemas My use case was uniquely bad for CUDA graphs. I was running structured output generation with vLLM's guided decoding (Outlines backend). Each request had: - A custom JSON schema (different per request) - Variable input lengths (500-10,000 tokens) - Variable output lengths (100-2,000 tokens) - Different complexity (nested objects, arrays, enums) Every request had a different execution pattern. For chatbots with fixed system prompts and similar response lengths, you might have 10-20 unique graphs. These get captured once, then reused thousands of times. Graph memory is stable. For structured output with diverse schemas, I was seeing 100+ unique graphs per hour. The graph cache was constantly churning. Memory kept growing. ### Measuring Graph Memory Growth I instrumented vLLM to log PyTorch memory stats: ```python import torch print(torch.cuda.memory_summary()) ``` Output after 1 hour of operation: ``` | | Reserved | Allocated | |-------------------|-----------|-----------| | Active | 106.2 GB | 98.4 GB | | Cached | 28.5 GB | 0.0 GB | |-------------------|-----------|-----------| | Total | 134.7 GB | 98.4 GB | ``` "Reserved" is what PyTorch has claimed from CUDA. "Allocated" is what's actually in use. 106GB reserved for active allocations (model weights + KV cache). But 28.5GB reserved and cached, not in use. This is graph memory. After 6 hours: ``` | | Reserved | Allocated | |-------------------|-----------|-----------| | Active | 108.1 GB | 99.2 GB | | Cached | 32.8 GB | 0.0 GB | |-------------------|-----------|-----------| | Total | 140.9 GB | 99.2 GB | ``` Cached memory grew to 32.8GB. Total reserved: 140.9GB out of 141GB available. I was on the edge of OOM. Any spike would crash the server. After a while, it crashed. ## Understanding PyTorch's Caching Allocator The root issue is how PyTorch manages GPU memory. ### Why PyTorch Caches Memory GPU memory allocation is slow. Calling `cudaMalloc()` takes 1-5ms. For operations that run every few milliseconds, this overhead is unacceptable. PyTorch's solution: allocate large blocks from CUDA once, then manage sub-allocations internally. When you free a tensor, PyTorch doesn't return memory to CUDA. It keeps it in a cache for future allocations. This is great for performance. But it means memory usage only goes up, never down. ### The Memory Lifecycle 1. **First allocation**: PyTorch requests 2GB from CUDA 2. **Usage**: You create tensors totaling 1.8GB 3. **Free**: You delete those tensors 4. **Cache**: PyTorch keeps the 2GB block, marks it as free 5. **Next allocation**: You create new tensors totaling 1.5GB 6. **Reuse**: PyTorch uses the cached 2GB block (no CUDA call needed) Memory usage from CUDA's perspective: always 2GB, even when only 1.5GB is in use. ### Graph Capture Amplifies This Graph capture allocates many temporary buffers. These buffers are freed after capture completes. But PyTorch caches them. For each new graph captured: 1. Allocate 5GB of temporary buffers during capture 2. Graph capture completes 3. Buffers are freed (from PyTorch's perspective) 4. PyTorch caches the 5GB (doesn't return to CUDA) 5. Next graph capture uses this cached memory (if shapes match) If next graph needs different shapes (different batch size, different sequence lengths), PyTorch can't reuse the cached memory. It allocates new memory. After 100 diverse graphs, you've accumulated 30-40GB of cached, unused memory. ![](https://til.codes/content/images/2025/11/image-29.png) This is why OOMs happened hours after startup, not immediately. Memory leaked slowly as graph diversity accumulated. ## The --enforce-eager Flag vLLM provides a flag to disable graph capture entirely: ```yaml args: - --enforce-eager ``` "Eager" mode means operations are launched one at a time, as they're encountered. No graph capture. No graph replay. This is the opposite of graph mode, which batches operations into pre-compiled graphs. With `--enforce-eager`: - No graph capture → no graph memory allocation - No cached graph memory → memory usage is predictable - No graph replay → 10-15% slower inference It's a trade-off: stability vs performance. ## Benchmarking: Eager vs Graph Mode I ran benchmarks to measure the actual performance impact. ### Test Setup - 100 concurrent requests - Input lengths: 2000 tokens (average) - Output lengths: 500 tokens (average) - Structured output with JSON schema constraints - Measured: Time to First Token (TTFT), inter-token latency, throughput ### Results: Graph Mode (Default) ``` Configuration: Default (graph mode enabled) TTFT: 520ms (p50), 780ms (p95) Inter-token latency: 24ms (p50), 38ms (p95) Throughput: 11.8 req/s Memory usage: 99GB (startup) → 137GB (after 6 hours) Crashes: 3 OOMs in 12 hours ``` Fast inference, but unstable. ### Results: Eager Mode (--enforce-eager) ``` Configuration: --enforce-eager TTFT: 595ms (p50), 890ms (p95) Inter-token latency: 28ms (p50), 43ms (p95) Throughput: 10.2 req/s Memory usage: 99GB (startup) → 102GB (stable) Crashes: 0 OOMs in 7 days ``` Slower inference, but completely stable. ### The Trade-Off Analysis **TTFT degradation**: 520ms → 595ms (14% slower) **Inter-token latency**: 24ms → 28ms (17% slower) **Throughput reduction**: 11.8 → 10.2 req/s (14% lower) **Stability improvement**: 3 crashes/12hr → 0 crashes/7d (infinite improvement) For my use case, stability won. Users don't care if responses take 595ms instead of 520ms. But they definitely care if the service crashes. ![](https://til.codes/content/images/2025/11/image-30.png) ## When CUDA Graphs Work Well I'm not saying CUDA graphs are bad. They're excellent for the right workload. ### Ideal Workload for Graph Mode Chatbots are the perfect use case: 1. **Fixed system prompt**: Every request starts with the same 500-token system message 2. **Similar response lengths**: Most responses are 100-300 tokens 3. **Predictable batching**: Batch sizes are usually 8, 16, or 32 4. **Uniform patterns**: Few unique execution paths With this workload: - 10-15 graphs capture 95% of requests - Graph cache is stable (no constant eviction) - Memory usage is predictable - You get the full 10-15% performance benefit Example: A customer support chatbot responding to product questions. The system prompt is fixed, responses are similar lengths, and the execution pattern is highly uniform. CUDA graphs give you free performance. ### Bad Workload for Graph Mode Structured output generation (my use case): 1. **Variable JSON schemas**: Every request has a different schema (hundreds to thousands of unique schemas) 2. **Diverse input lengths**: 500 to 10,000 token inputs 3. **Variable output lengths**: 100 to 2,000 tokens 4. **Complex constraints**: FSM-based guided decoding creates unique execution paths With this workload: - 100+ new graphs per hour - Graph cache constantly churns - Memory grows unpredictably - Frequent OOMs The performance benefit is negated by instability. ![](https://til.codes/content/images/2025/11/image-31.png) ## The Debugging Journey Finding this root cause took me a while. Here's the process I went through. ### Day 1: Is It the Model? Initial hypothesis: The quantized model has a bug that causes OOM under certain inputs. I tried: - Switching from Qwen3-30B to Llama-3.1-70B (different OOM, worse performance) - Reducing max-model-len from 60k to 32k (still crashed) - Reducing gpu-memory-utilization from 0.75 to 0.70 (delayed crash by 2 hours) None of these fixed it. The crash still happened, just at different times. ### Day 2: Is It the KV Cache? Hypothesis: KV cache is somehow growing beyond expected limits. I instrumented KV cache logging: ```python def log_kv_cache_stats(): print(f"KV blocks used: {num_blocks_used}") print(f"KV cache memory: {kv_cache_memory_gb:.2f} GB") ``` Output before crash: ``` KV blocks used: 3,872 KV cache memory: 60.2 GB ``` This was normal. KV cache wasn't the culprit. ### Day 3: PyTorch Memory Deep Dive I added detailed PyTorch memory logging: ```python import torch snapshot = torch.cuda.memory_snapshot() print(torch.cuda.memory_summary(device=0, abbreviated=False)) ``` This revealed the "Cached" memory that kept growing. Following the PyTorch docs, I learned about the caching allocator and how graph capture interacts with it. Then I found the vLLM issue on GitHub: "OOM with graph mode enabled for diverse workloads". Others had the same problem. The recommended solution: `--enforce-eager`. ### Validation I enabled `--enforce-eager` and ran a 48-hour stress test: - 10,000 requests with diverse JSON schemas - Memory usage stable at 99-103GB - No OOMs - No degradation over time Problem solved. ## Production Configuration: The Stable Setup Here's my final production configuration: ```yaml model: Qwen/Qwen3-30B-A3B-Instruct-2507 tensor-parallel-size: 1 max-model-len: 60000 gpu-memory-utilization: 0.75 max-num-seqs: 128 enable-chunked-prefill: true enforce-eager: true ``` The last line is critical for my workload. The 3-4GB variation is normal KV cache fluctuation based on request patterns. No memory leak, no growth, no crashes. ### The Performance Impact in Practice In production, the 14% throughput reduction is barely noticeable: **Before (graph mode):** - Throughput: 11.8 req/s - Multiple crashes per day, requiring restarts (15-20 minutes downtime) **After (eager mode):** - Throughput: 10.2 req/s - Crashes: 0 per month, no downtime The "slower" configuration is actually **faster** in wall-clock time, because it doesn't crash and restart. And for users, 595ms TTFT vs 520ms TTFT is imperceptible. Both feel instant. ## Understanding the Eager Mode Implementation What does `--enforce-eager` actually do in vLLM's code? ### Default Path: Graph Capture Without `--enforce-eager`, vLLM's forward pass looks like this: ```python def forward(self, input_ids, positions): # Check if we've seen this pattern before cache_key = (input_ids.shape, positions.shape) if cache_key in self.graph_cache: # Replay cached graph graph = self.graph_cache[cache_key] output = graph.replay(input_ids, positions) return output # New pattern: capture graph torch.cuda.synchronize() stream = torch.cuda.Stream() with torch.cuda.graph(stream) as g: output = self.model(input_ids, positions) # Store graph self.graph_cache[cache_key] = g return output ``` Every unique input shape triggers graph capture. The graph is cached for reuse. ### Eager Path: Direct Execution With `--enforce-eager`, vLLM's forward pass is simpler: ```python def forward(self, input_ids, positions): # Just run the model directly output = self.model(input_ids, positions) return output ``` No graph capture. No caching. Just execute operations as they're encountered. This is "eager" execution: compute results immediately, don't optimize or pre-compile. ### The Performance Difference Why is eager mode slower? 1. **Kernel launch overhead**: Each operation launches a kernel (\~0.03-0.05ms overhead per operation) 2. **No kernel fusion**: Operations run independently, can't be fused 3. **More CPU-GPU synchronization**: CPU waits for each operation to complete For a 60-layer transformer: - Graph mode: 1 graph launch + \~10ms execution = \~10.03ms - Eager mode: 1,080 kernel launches × 0.04ms + \~10ms execution = \~53ms Wait, that predicts eager should be 5x slower, not 14% slower! The reason it's only 14% slower in practice: most time is spent in large matrix multiplies, not launch overhead. A single attention operation might be 2-5ms of actual compute. Launch overhead is 0.04ms. That's only 1-2% overhead for large operations. The 14% slowdown comes from: - Small operations (normalization, activation functions) with high launch overhead - Missed optimization opportunities (kernel fusion in graphs) - Slightly worse memory access patterns (graphs can pre-allocate buffers optimally) But for my workload, 14% slower is acceptable for infinite stability improvement. ## Lessons Learned ### 1\. Optimization Can Become Liability CUDA graphs are a brilliant optimization. They make inferences 10-15% faster for typical workloads. But for diverse workloads, they introduce memory leaks and non-deterministic crashes. The optimization becomes a liability. Always benchmark optimizations for **your specific workload**. Don't assume defaults are optimal. ### 2\. Memory "Reserved" vs "Allocated" Matters PyTorch's caching allocator makes memory usage opaque. The GPU shows "138GB used", but PyTorch reports "99GB allocated" and "39GB cached". Understanding this distinction is critical for debugging memory issues. Use `torch.cuda.memory_summary()` to see the full picture, not just `nvidia-smi`. ### 3\. Workload Diversity Is a Hidden Cost Uniform workloads (chatbots) are easy to optimize. Diverse workloads (structured output) are hard. Graph caching assumes pattern reuse. If every request is unique, caching becomes counterproductive. For diverse workloads, eager execution can be faster (in wall-clock time) despite being slower (in per-request latency). ### 4\. Stability > Performance for Production A system that's 14% slower but never crashes is better than a system that's 14% faster but crashes daily. Downtime is expensive: - Lost revenue during outages - Engineering time investigating crashes - Customer trust erosion In production, prioritize predictability over peak performance. ### 5\. Disable Optimizations When Debugging When I was debugging the random OOMs, I should have disabled CUDA graphs earlier in the process. I spent 2 days assuming the problem was my configuration (KV cache size, GPU utilization, model quantization). Then I found it was an optimization I didn't even know was enabled. When debugging GPU memory issues, disable all automatic optimizations first. Then re-enable them one by one. ## Decision Tree: Graph Mode vs Eager Mode Here's how I think about this choice now: ![](https://til.codes/content/images/2025/11/image-32.png) For production systems, I default to eager mode unless I have strong evidence that graph mode will be stable. ## What's Next This completes the four-part series on running LLMs in production: 1. **Quantization**: Why 30B FP16 > 70B FP8 for structured output 2. **Tensor Parallelism**: Why 1 GPU > 4 GPUs for cloud inference 3. **KV Cache**: The invisible memory monster eating 60% of VRAM 4. **CUDA Graphs**: When optimization kills stability These four topics represent the hardest lessons I learned deploying vLLM at scale. Each one cost me days of debugging and multiple production outages. But now, with the final configuration, my inference server has been running fine without a single crash. Memory is stable, performance is acceptable. Sometimes the best optimization is disabling optimizations. --- **TL;DR**: I had random CUDA OOM crashes despite memory usage looking normal. The culprit: CUDA graph capture for diverse workloads (structured output with variable JSON schemas) created 100+ unique graphs that accumulated 30-40GB of cached memory. PyTorch's caching allocator never freed this memory, causing OOMs after hours of operation. Solution: `--enforce-eager` flag disables graph capture, trading 14% performance for complete stability. For uniform workloads (chatbots), graph mode is great. For diverse workloads, eager mode is safer. In production, stability beats micro-optimizations. ### Ghostbusters: Who You Gonna Call When KV Cache Eats Your GPU? URL: https://til.codes/ghostbusters-who-you-gonna-call-when-kv-cache-eats-your-gpu-2/ Last updated: 2025-11-05T23:13:31.000Z After settling on Qwen3-30B and ditching tensor parallelism, I thought my memory problems were solved. The model was 60GB, and my H200 had 141GB of VRAM. Simple math: I had 81GB to spare. Spoiler: I didn't. And I kept running into random OOM errors under load that made no sense. In this article, I am gonna dive into how discovering KV cache, the invisible memory consumer that nobody warns you about. It's not in your model config. It's not in your deployment YAML. But it can easily consume 60% of your GPU memory. ## The Mystery: Why Am I Running Out of Memory? My configuration looked perfectly reasonable: - `model`: Qwen/Qwen3-30B-A3B-Instruct-2507 - `max-model-len`: 16000 - `gpu-memory-utilization`: 0.90 - `max-num-seqs`: 128 The math seemed fine: - Model weights: 60GB (FP16) - GPU VRAM: 141GB - Utilization limit: 0.90 = 127GB usable - Headroom: 67GB for everything else With 67GB of spare memory, I should be able to handle 128 concurrent sequences easily, right? If only... Under load, vLLM would crash with CUDA OOM errors: `RuntimeError: CUDA out of memory. Tried to allocate 2.50 GiB` `(GPU 0; 140.54 GiB total capacity; 126.89 GiB already allocated)` Wait. 126.89 GB allocated? Where did the other 66GB go? ## What Is KV Cache, Anyway? To understand what was eating my memory, we need to understand how transformers actually work during inference. ### The Attention Mechanism (Simplified) In a transformer, each token attends to all previous tokens. This is the "self-attention" mechanism. For a sequence of length N, processing token N requires: 1. Compute Query vector for token N 2. Compute Key and Value vectors for token N 3. Look at Key and Value vectors for all previous tokens (1 through N-1) 4. Compute attention weights 5. Generate output Here's the problem: to process token N, you need the Key and Value vectors from all previous tokens. Without caching, you'd need to recompute Key and Value vectors for every token, for every new token you generate. This is insanely expensive. Solution: Cache the Key and Value vectors. This is the KV cache. ### What Gets Cached For each token in your sequence, at each layer in the model, you store: - Key vector - Value vector For Qwen3-30B: - **48 layers** - **4 Key/Value heads** (it uses Grouped-Query Attention, or GQA) - **128 dimensions per head** Per token, per layer: - Key: \[4 heads × 128 dim\] = FP16 = 1KB - Value: \[4 heads × 128 dim\] = FP16 = 1KB - **Total: 2KB per token per layer** For full sequence: - 48 layers × 2KB = **96KB per token** This means for a 16k token sequence: - 16,384 tokens × 96KB = 1,572,864 KB = **1.5 GiB per sequence** Wait. 1.5 GiB per sequence? And I was trying to run 128 concurrent sequences? - 128 × 1.5 GiB = **192 GiB** That's... still not going to fit in 141GB. My original math was wrong, but my conclusion was right. This was the memory hog. ## The KV Cache Memory Formula Here's the actual formula for KV cache memory (specifically for a GQA model): `KV_cache_memory = 2 × num_layers × num_key_value_heads × head_dim × seq_length × batch_size × sizeof(dtype)` Breaking it down: - `2` \= Key + Value - `num_layers` \= 48 (for Qwen3-30B) - `num_key_value_heads` \= 4 (this was my big mistake, I thought it was 40) - `head_dim` \= 128 - `seq_length` \= maximum sequence length (context window) - `batch_size` \= number of concurrent sequences - `sizeof(dtype)` \= 2 bytes for FP16 Let's plug in numbers for different scenarios: **Scenario 1: 16k context, 128 sequences** - KV cache = 2 × 48 × 4 × 128 × 16384 × 128 × 2 - \= 206,158,430,208 bytes - \= 192 GiB This is already more than my entire GPU. **Scenario 2: 16k context, 8 sequences** - KV cache = 2 × 48 × 4 × 128 × 16384 × 8 × 2 - \= 12,884,901,888 bytes - \= 12 GiB This fits! But 8 concurrent sequences isn't enough throughput. **Scenario 3: 16k context, 32 sequences** - KV cache = 2 × 48 × 4 × 128 × 16384 × 32 × 2 - \= 51,539,607,552 bytes - \= 48 GiB Now we're talking. 32 sequences with 16k context = 48 GiB KV cache. Total memory: - Model weights: 60GiB - KV cache: 48 GiB - Activations and overhead: \~10GiB - Total: \~118 GiB This fits in 127GB (90% of 141GB). The KV cache is the second-largest memory consumer, right after model weights. ![](https://til.codes/content/images/2025/11/image-22.png) ## My Context Window Journey: From 16k to 60k I didn't start with a 16k context window. I started much smaller. ### Phase 1: 8k Context (Conservative) Initial configuration: ```yaml max-model-len: 8192 max-num-seqs: 128 gpu-memory-utilization: 0.90 ``` KV cache at 8k with 32 sequences: ``` 2 × 48 × 4 × 128 × 8192 × 32 × 2 = 25,769,803,776 bytes = 24 GiB ``` This worked perfectly. No OOM errors. But then I started hitting the context limit. Prompts were getting truncated. Structured output with long JSON schemas wasn't working well. ### Phase 2: 16k Context (Comfortable) I doubled the context window: ```yaml max-model-len: 16384 max-num-seqs: 128 gpu-memory-utilization: 0.90 ``` KV cache at 16k with 32 sequences: 48 GiB (as calculated above). This also worked. But I had to reduce `max-num-seqs` to 32 to avoid OOMs. Wait. Why 32 and not 128? Because vLLM doesn't allocate KV cache for the maximum possible sequences. It allocates as needed. But it pre-allocates based on `gpu-memory-utilization`. If I set `max-num-seqs` to 128, vLLM reserves memory assuming 128 sequences *might* happen. But if only 32 are active, it still reserves the memory. This is a conservative design to avoid runtime OOMs ### Phase 3: 32k Context (Ambitious) I wanted longer context for RAG applications. I increased again: ```yaml max-model-len: 32768 max-num-seqs: 64 gpu-memory-utilization: 0.90 ``` KV cache at 32k with 16 sequences: ``` 2 × 48 × 4 × 128 × 32768 × 16 × 2 = 51,539,607,552 bytes = 48 GiB ``` This 48 GiB KV cache (same as 16k @ 32 seqs) also fit, but now with half the concurrent sequences and double the context. This is the fundamental trade-off: **context length vs batch size**. You can have long context OR high throughput. Not both. ![](https://til.codes/content/images/2025/11/image-23.png) ### Phase 4: 60k Context (Pushing Limits) For my use case (structured output generation with complex schemas), I wanted maximum context. ```yaml max-model-len: 60000 max-num-seqs: 128 gpu-memory-utilization: 0.75 enable-chunked-prefill: true ``` KV cache at 60k with 8 active sequences: ``` 2 × 48 × 4 × 128 × 60000 × 8 × 2 = 47,185,920,000 bytes = 44 GiB ``` Wait. 68.7GB just for KV cache? That's almost half my GPU! Total memory breakdown: - Model weights: 60GB - KV cache: 44 GiB (8 sequences) - Activations: 6GB - CUDA overhead: 4GB - Total: \~114GB With `gpu-memory-utilization=0.75`, I'm reserving 75% of my *available* 81GiB, which is \~60.75 GiB. My total *reserved* memory is 60 GiB (weights) + 60.75 GiB (for KV/activations) = \~121 GiB. My 114 GiB load fits, but doesn't leave much room. This is where I was hitting OOMs. But here's the problem: what if a user sends a 60k token prompt? ## The 60k Token Problem: Chunked Prefill Processing a 60k token prompt all at once requires: 1. Running 60k tokens through all 60 layers 2. Generating 60k Key and Value vectors 3. Storing them in KV cache The memory spike during this operation is massive. The activations alone (intermediate tensors) can be 20-30GB for a single 60k prompt. I tried processing a 60k prompt. Instant OOM. ``` RuntimeError: CUDA out of memory. Tried to allocate 18.50 GiB ``` The solution: **chunked prefill**. ### How Chunked Prefill Works Instead of processing all 60k tokens at once, vLLM processes them in chunks: 1. Process first 8k tokens → generate KV cache for tokens 0-8191 2. Process next 8k tokens → generate KV cache for tokens 8192-16383 3. Continue until all 60k tokens are processed This spreads the memory spike across multiple smaller operations. ![](https://til.codes/content/images/2025/11/image-24.png) With chunked prefill enabled: ```yaml enable-chunked-prefill: true ``` vLLM automatically chunks long prompts into 8k-16k token pieces (configurable). Memory during 60k prefill with chunking: - Model weights: 60GB (constant) - KV cache (accumulated): grows from 0 → 44GB - Activations for current chunk: \~4GB (for 8k tokens) - Peak memory: \~108GB This fits! Without chunking: - Activations for *full prompt*: \~28 GiB (for 60k tokens) - Peak memory: 60 GiB (weights) + 44 GiB (cache) + \~28 GiB (activations) = \~132 GiB This is cutting it dangerously close to my 141 GiB total, and easily exceeds my 127 GiB (90%) limit. This explains the 18.50 GiB OOM. ### The Performance Cost Chunked prefill is slower than processing the full prompt at once: - **Without chunking**: 60k tokens in one forward pass - **With chunking**: 8 forward passes of 8k tokens each Why is it slower? 1. Each chunk requires loading model weights from VRAM (cache misses) 2. GPU can't parallelize across chunks (sequential processing) 3. Memory bandwidth is used less efficiently Benchmark for 60k token prefill: - Without chunking: \~2.5 seconds (OOM risk) - With chunking (8k chunks): \~4.2 seconds (stable) I chose stability over speed. 4.2 seconds for a 60k prompt is acceptable. ## Understanding GPU Memory Utilization The `gpu-memory-utilization` parameter is confusing. It doesn't mean "use X% of GPU memory". It means "reserve X% for KV cache and activations". ### What vLLM Actually Does When vLLM starts: 1. Load model weights into VRAM (60GB for Qwen3-30B) 2. Calculate available memory: `total_vram - model_weights` 3. Apply utilization multiplier: `available × gpu-memory-utilization` 4. Reserve this amount for KV cache Example with gpu-memory-utilization=0.90: ``` Total VRAM: 141 GB Model weights: 60 GB Available for KV cache: 141 - 60 = 81 GB Reserved for KV cache: 81 × 0.90 = 72.9 GB Actual usable VRAM: 60 + 72.9 = 132.9 GB ``` With this setting, vLLM will allocate KV cache blocks until it reaches 72.9GB. Then it stops accepting new requests. ### Why I Reduced It to 0.75 With 0.90, I kept hitting OOMs under load. Why? Because the formula assumes **only KV cache** uses the remaining memory. But other things need memory too: 1. **Activations**: Intermediate tensors during forward pass (4-8GB) 2. **CUDA kernels**: Workspace for operations (2-4GB) 3. **Temporary buffers**: Attention scores, softmax outputs (1-3GB) 4. **Fragmentation**: Memory allocator overhead (2-5GB) With 0.90, there's no buffer for these extras. Any spike causes OOM. With 0.75: ``` Available for KV/Activations: 81 GiB Reserved for KV/Activations: 81 × 0.75 = 60.75 GiB Safety buffer: 81 - 60.75 = 20.25 GiB ``` 20GB buffer is enough for activations, CUDA overhead, and fragmentation. ![](https://til.codes/content/images/2025/11/image-25.png) Since switching to 0.75, I haven't had a single OOM error in production. ## The Batch Size vs Context Window Trade-Off This is the fundamental constraint of LLM inference: `KV_cache_memory = f(context_window, batch_size)` You can't maximize both. Given fixed GPU memory, increasing context window forces you to decrease batch size. Let me show the actual numbers for Qwen3-30B on H200 (with \~10GiB overhead added to the 60GiB model): ### Fixed Context: 16k, Variable Batch Size ```markdown | Batch Size | KV Cache | Total Memory (Approx) | Fits (in 127 GiB)? | |-------------|-----------|---------------------------|--------------------| | 8 | 12 GiB | 60 + 10 + 12 = 82 GiB | Yes | | 16 | 24 GiB | 60 + 10 + 24 = 94 GiB | Yes | | 32 | 48 GiB | 60 + 10 + 48 = 118 GiB | Yes | | 64 | 96 GiB | 60 + 10 + 96 = 166 GiB | No | | 128 | 192 GiB | 60 + 10 + 192 = 262 GiB | No | |-------------|-----------|---------------------------|--------------------| ``` With 16k context, max batch size is \~32 sequences. ### Fixed Batch: 16 sequences, Variable Context ``` | Context | KV Cache | Total Memory (Approx) | Fits (in 127 GiB)? | |----------|------------|-------------------------------|--------------------| | 8k | 12 GiB | 60 + 10 + 12 = 82 GiB | Yes | | 16k | 24 GiB | 60 + 10 + 24 = 94 GiB | Yes | | 32k | 48 GiB | 60 + 10 + 48 = 118 GiB | Yes | | 60k | 87.9 GiB | 60 + 10 + 87.9 = 157.9 GiB | No | | 80k | 117.2 GiB | 60 + 10 + 117.2 = 187.2 GiB | No | | 128k | 192 GiB | 60 + 10 + 192 = 262 GiB | No | ``` With 16 sequences, max context is \~60k tokens. ### The Sweet Spot For my use case (structured output with complex schemas): - Long context matters more than high batch size - Users send detailed prompts with examples - JSON schemas can be large (5-10k tokens) I chose: - **Context: 60k tokens** (for flexibility) - **Batch size: 8-16 sequences** (acceptable throughput) - **GPU utilization: 0.75** (stability) This gives me: - KV cache: \~44 GiB (for 8 full sequences) - Total memory: 60 (weights) + 44 (cache) + \~10 (overhead) = \~114 GiB - This fits within my 127 GiB (90%) limit, but shows why 16 sequences (at 88 GiB) would not. - Throughput: 8-12 req/s (acceptable for my load) Different use cases need different configurations. There's no universal "best" setting. ![](https://til.codes/content/images/2025/11/image-26.png) ## Prefix Caching: The Optimization I Didn't Implement While researching KV cache optimization, I discovered **prefix caching**. ### The Idea Many requests share common prefixes. For example: - System prompt: "You are a helpful assistant..." - Few-shot examples: 3-5 examples of desired output - Instructions: "Generate JSON matching this schema..." These can be 5k-10k tokens. If 90% of requests use the same system prompt, you're recomputing the same KV cache 90% of the time. Prefix caching stores the KV cache for common prefixes and reuses it across requests. ### How It Works 1. Compute KV cache for system prompt once: 5k tokens → **0.45 GiB** KV cache (5000 \* 96KB) 2. Store this KV cache with a hash of the prompt 3. New request arrives with same system prompt 4. Look up cached KV cache by hash 5. Skip computing KV cache for those 5k tokens 6. Only compute KV cache for the unique part This can save significant compute: - Prefill time for 5k tokens: \~500ms - Prefix cache lookup: \~5ms - Speedup: 100x for the cached portion ### Why I Didn't Implement It Prefix caching in vLLM requires: 1. `--enable-prefix-caching` flag 2. All requests must use the same prefix structure 3. Prefix must be at the start (not middle) My use case has **variable prefixes**: - Different JSON schemas per request (not reusable) - Few-shot examples change based on user's domain - System prompts are customized per user Prefix caching would help maybe 10-20% of requests. Not worth the complexity. But for chatbot use cases with fixed system prompts, this is a huge win. ## Memory Profiling: Seeing the Invisible To actually measure KV cache usage, I used NVIDIA's profiling tools. ### nvidia-smi: Basic Monitoring ```bash watch -n 1 nvidia-smi ``` Output during idle: ```markdown +---------------------------------------------------------------------------------------+ | NVIDIA-SMI 535.104.05 Driver Version: 535.104.05 CUDA Version: 12.2 | |---------------------------------+-----------------------+-----------------------------| | GPU Name Persistence-M | Bus-Id Disp.A | Volatile Uncorr. ECC | | Fan Temp Perf Pwr:Usage/Cap | Memory-Usage | GPU-Util Compute M. | |=================================+=======================+=============================| | 0 NVIDIA H200 On | 00000000:01:00.0 Off | 0 | | N/A 45C P0 98W / 700W | 62,145MiB / 143,845MiB | 0% Default | +---------------------------------+-----------------------+-----------------------------+ ``` Memory usage: 62GB (just model weights + overhead) Output during load (8 concurrent 10k-token sequences): ```markdown +---------------------------------------------------------------------------------------+ | NVIDIA-SMI 535.104.05 Driver Version: 535.104.05 CUDA Version: 12.2 | |---------------------------------+-----------------------+-----------------------------| | GPU Name Persistence-M | Bus-Id Disp.A | Volatile Uncorr. ECC | | Fan Temp Perf Pwr:Usage/Cap | Memory-Usage | GPU-Util Compute M. | |=================================+=======================+=============================| | 0 NVIDIA H200 On | 00000000:01:00.0 Off | 0 | | N/A 72C P0 456W / 700W | 127,891MiB / 143,845MiB | 89% Default | +---------------------------------+-----------------------+-----------------------------+ ``` Memory usage: 128GB KV cache: 128 - 62 = 66GB This matches the calculation: 8 sequences × 10k tokens × 1.03MB/token ≈ 66GB ### vLLM Metrics: Detailed KV Cache Stats vLLM exposes KV cache metrics at `/metrics`. `curl localhost:8000/metrics | grep kv_cache` Key metrics: - `vllm:num_requests_running 8` - `vllm:num_requests_waiting 0` - `vllm:gpu_cache_usage_perc 0.62` - `vllm:kv_cache_block_count 4096` - `vllm:kv_cache_block_size 16` - `gpu_cache_usage_perc`: 62% of reserved KV cache is in use - `kv_cache_block_count`: 4096 blocks allocated - `kv_cache_block_size`: 16 tokens per block - Total KV cache capacity: 4096 blocks × 16 tokens = 65,536 tokens - If 62% is in use: 65,536 × 0.62 = 40,632 tokens cached - With 8 concurrent requests: 40,632 / 8 = 5,079 tokens per request on average. This matches my traffic pattern: most requests are 3k-8k tokens. ## The Final Configuration After all the experimentation, here's my production config: ```yaml model: Qwen/Qwen3-30B-A3B-Instruct-2507 tensor-parallel-size: 1 max-model-len: 60000 gpu-memory-utilization: 0.75 max-num-seqs: 128 enable-chunked-prefill: true enforce-eager: true ``` Let me break down each parameter: **max-model-len: 60000** - Maximum context window: 60k tokens - Allows complex structured output prompts - Requires chunked prefill for stability **gpu-memory-utilization: 0.75** - Reserves 60GB for KV cache (out of 81GB available) - Leaves 21GB buffer for activations and CUDA overhead - Prioritizes stability over maximum throughput **max-num-seqs: 128** - Maximum concurrent sequences - In practice, averages 8-16 active sequences - vLLM only allocates KV cache for active sequences **enable-chunked-prefill: true** - Processes long prompts in 8k-16k chunks - Prevents OOM on 60k token prompts - Trades 40% slower prefill for stability This configuration gives me: - 99%+ uptime (no OOMs in 2 months) - 450ms TTFT for typical requests - 8-12 req/s throughput - 60k token context window for complex prompts ## Lessons Learned 1. **KV Cache *and Overhead* Are the Hidden Memory Consumers** My calculations showed the 60k KV cache for 8 sequences was \~44 GiB, *not* larger than the model. But my `nvidia-smi` test showed that the *total memory spike* (KV cache + activations + overhead) was 66GB, which *is* larger than the model. Always calculate KV cache, but be aware that activations and overhead can be just as large, if not larger. 2. **Context Window vs Batch Size Is a Hard Trade-Off** You cannot have both long context and high throughput on a single GPU. - Long context (60k) = Low batch size (8-16) - Short context (8k) = High batch size (32-64) Choose based on your use case. For structured output with complex schemas, I chose long context. 3. **`gpu-memory-utilization` Should Be Conservative** The parameter name is misleading. It doesn't mean "use 90% of GPU". It means "reserve 90% of *available* memory for cache/activations". Real memory usage includes: - Model weights (fixed) - KV cache (variable, controlled by this parameter) - Activations (variable, 5-10GB, or 18.5GB+!) - CUDA overhead (variable, 3-5GB) Setting it to 0.90 leaves no buffer. Use 0.75 for production. 4. **Chunked Prefill Is Essential for Long Context** Without chunked prefill, a single 60k token prompt will OOM your GPU due to the *activation* spike. With chunked prefill, you can handle 60k prompts at the cost of 40% slower prefill. This is a reasonable trade-off for stability. 5. **Monitor KV Cache in Production** Use vLLM's `/metrics` endpoint to track : - `gpu_cache_usage_perc`: How full is your KV cache? - `num_requests_running`: How many sequences active? - `num_requests_waiting`: Are you hitting limits? If cache usage is consistently near 100%, reduce `max-model-len` or increase GPU memory. 6. **Prefix Caching Is Powerful But Use-Case Specific** If your use case has: - Fixed system prompts - Repeated few-shot examples - Consistent prefix structure Prefix caching can give you 2-5x speedup on prefill. But if every request is unique (like structured output with custom schemas), it won't help. ## The Memory Breakdown (Final) ![](https://til.codes/content/images/2025/11/image-27.png) - Here's where all 141GB goes in my production setup: - **60 GiB:** Model weights (fixed) The KV cache is the largest *variable* memory consumer, but the activation spike is the most *dangerous*. Understanding this was the key to stable production deployments. - **\~44-50 GiB:** KV cache (variable, grows with 8-12 active sequences) - **\~20-30 GiB:** Activations, CUDA overhead, and fragmentation (the *other* big consumer) - **\~20 GiB:** Safety Buffer (my 0.75 setting) ## What's Next In the next article, I'll cover CUDA graphs and why I disabled them with `--enforce-eager`. CUDA graphs are a performance optimization that pre-compiles execution graphs. They can give you 10-15% speedup. But they also consume unpredictable amounts of memory. And for structured output with diverse JSON schemas, they caused random OOMs. Stay tuned for "[CUDA Graphs: When Optimization Becomes the Problem.](https://til.codes/speed-racer-gone-wrong-when-cuda-graph-optimization-killed-my-inference-server/)" --- **TL;DR**: KV cache can consume 60%+ of GPU memory, but isn't visible in your configuration. For Qwen3-30B with 60k context, KV cache uses 68GB (more than the 60GB model). The fundamental trade-off: long context = low batch size, short context = high batch size. I chose 60k context with 8-16 concurrent sequences, using gpu-memory-utilization=0.75 for stability and enable-chunked-prefill=true to handle long prompts without OOM. Monitor vLLM's KV cache metrics to avoid hitting memory limits in production. ### Fast & Furious Tensor Parallelism: GPU Heist Gone Wrong URL: https://til.codes/fast-furious-tensor-parallelism-gpu-heist-gone-wrong/ Last updated: 2025-11-02T22:56:22.000Z # After settling on a model (Qwen3-30B), I thought the next logical step was optimizing for throughput. The model fit on a single H200 GPU, but could I split it across 4 GPUs and serve 4x more requests? Spoiler: No. Tensor parallelism made everything slower. This is a deep dive into why adding more GPUs sometimes makes your inference worse, not better. ## The Promise of Tensor Parallelism Tensor parallelism splits a model across multiple GPUs by partitioning the weight tensors. Each GPU holds a slice of the model, and they work together to produce outputs. The idea is compelling: - Distribute the model's memory footprint across GPUs - Parallelize matrix multiplications across devices - Theoretically, 4x throughput with 4 GPUs For training, this works beautifully. But for inference? The math is different. ## What Are Tensors, Anyway? Before diving into `tensor parallelism` I want to explain what tensors actually are. A tensor is just a multi-dimensional array of numbers. - A scalar is a 0-dimensional tensor: `5` - A vector is a 1-dimensional tensor: `[1, 2, 3, 4]` - A matrix is a 2-dimensional tensor: `[[1, 2], [3, 4]]` - A 3D tensor: `[[[1, 2], [3, 4]], [[5, 6], [7, 8]]]` In a transformer model, weights are stored as tensors of various dimensions: ``` Query weight matrix: [hidden_size, hidden_size] Example: [4096, 4096] = 16.7 million parameters Attention multi-head weights: [num_heads, hidden_size, head_dim] Example: [32, 4096, 128] = 16.7 million parameters Feed-forward weights: [hidden_size, intermediate_size] Example: [4096, 11008] = 45 million parameters ``` For `Qwen3-30B`, there are \~60 layers, each with multiple weight matrices. The total: 30 billion parameters. When we talk about "tensor parallelism" we're talking about splitting these multi-dimensional arrays across GPUs. ## How Tensor Parallelism Actually Works Here's what happens when you split a single transformer layer across 4 GPUs. ### The Attention Mechanism Split In a transformer, the attention mechanism computes Query, Key, and Value matrices: ``` Input: [batch_size, sequence_length, hidden_size] Example: [1, 1024, 4096] Weight matrices: W_Q: [4096, 4096] # Query projection W_K: [4096, 4096] # Key projection W_V: [4096, 4096] # Value projection ``` **Without tensor parallelism (single GPU):** ``` Q = Input @ W_Q # Matrix multiply on one GPU K = Input @ W_K V = Input @ W_V ``` **With tensor parallelism across 4 GPUs:** The weight matrices are split column-wise across GPUs: ``` GPU 0: W_Q[:, 0:1024], W_K[:, 0:1024], W_V[:, 0:1024] GPU 1: W_Q[:, 1024:2048], W_K[:, 1024:2048], W_V[:, 1024:2048] GPU 2: W_Q[:, 2048:3072], W_K[:, 2048:3072], W_V[:, 2048:3072] GPU 3: W_Q[:, 3072:4096], W_K[:, 3072:4096], W_V[:, 3072:4096] ``` Each GPU computes its slice: ``` GPU 0: Q_0 = Input @ W_Q[:, 0:1024] # Output shape: [1, 1024, 1024] GPU 1: Q_1 = Input @ W_Q[:, 1024:2048] # Output shape: [1, 1024, 1024] GPU 2: Q_2 = Input @ W_Q[:, 2048:3072] # Output shape: [1, 1024, 1024] GPU 3: Q_3 = Input @ W_Q[:, 3072:4096] # Output shape: [1, 1024, 1024] ``` Then they concatenate the results: ``` Q = concatenate([Q_0, Q_1, Q_2, Q_3], dim=-1) # Shape: [1, 1024, 4096] ``` But here's the problem: **all GPUs need the full Input tensor** to do their computation. So the input must be broadcast to all GPUs. And the concatenation requires an **all-gather** operation across GPUs. ### Multi-Head Attention: Head Parallelism Transformers use multi-head attention. For example, Qwen3-30B has 40 attention heads. With 4 GPUs, you can split the heads: - GPU 0: Heads 0-9 (10 heads) - GPU 1: Heads 10-19 (10 heads) - GPU 2: Heads 20-29 (10 heads) - GPU 3: Heads 30-39 (10 heads) Each GPU computes attention independently for its heads. This is more efficient because: - No need to split within a head - Each head's computation is independent - Only need to gather the final outputs ![](https://til.codes/content/images/2025/11/image-6.png) Notice the **all-gather** operation (in red). This requires GPU-to-GPU communication. ### The Feed-Forward Network Split After attention, there's a feed-forward network (FFN): ``` FFN has two layers: 1. Linear up-projection: [4096, 11008] 2. Linear down-projection: [11008, 4096] ``` With tensor parallelism: ``` GPU 0: Computes columns 0-2752 of up-projection GPU 1: Computes columns 2752-5504 GPU 2: Computes columns 5504-8256 GPU 3: Computes columns 8256-11008 # After activation function (computed independently per GPU): GPU 0: Computes down-projection for its slice GPU 1: Computes down-projection for its slice GPU 2: Computes down-projection for its slice GPU 3: Computes down-projection for its slice ``` Then an **all-reduce** operation sums the results from all GPUs. ![](https://til.codes/content/images/2025/11/image-7.png) Again, the **all-reduce** (in red) requires expensive GPU communication. ### Communication Operations Per Layer For a single transformer layer with tensor parallelism: 1. **Broadcast** input to all GPUs 2. **All-gather** after QKV projection (if splitting by columns) 3. **All-gather** after attention computation (if splitting by heads) 4. **All-reduce** after feed-forward down-projection That's **3-4 collective communication operations per layer**. For Qwen3-30B with 60 layers: **180-240 communication operations per forward pass**. ## Training vs Inference: Why Tensor Parallelism Differs Tensor parallelism behaves differently during training vs inference. ### During Training **Forward pass**: Same as inference - requires all-gather and all-reduce operations. **Backward pass**: Gradients must be synchronized across GPUs. For each weight matrix split across GPUs, the gradient with respect to that weight is computed locally on each GPU. Then: 1. **All-reduce** gradients across GPUs to sum them 2. Update weights locally on each GPU Because training uses large batches (often 128-1024 examples), the compute time dominates: ``` Compute time: ~500ms per layer (large batch) Communication time: ~2ms per all-reduce Total per layer: ~502ms Communication overhead: 0.4% ``` The communication is negligible compared to compute. ### During Inference **Forward pass only**: No backward pass, no gradient synchronization. But with small batches (often 1-8 examples), compute time is tiny: ``` Compute time: ~5ms per layer (small batch) Communication time: ~2ms per all-reduce Total per layer: ~7ms Communication overhead: 28.6% ``` Suddenly, communication is 28% of your total time! With batch size = 1 (single request inference): ``` Compute time: ~1ms per layer Communication time: ~2ms per all-reduce Total per layer: ~3ms Communication overhead: 66.7% ``` **Communication dominates compute**. This is why tensor parallelism fails for low-latency inference. ![](https://til.codes/content/images/2025/11/image-8.png) ### Why Training Still Benefits Even though training requires more communication (forward + backward), it benefits from tensor parallelism because: 1. **Large batches amortize communication cost** 2. **Gradient accumulation** allows even larger effective batch sizes 3. **Memory savings** are critical (activations + gradients + optimizer states) 4. **Throughput matters more than latency** in training For inference: 1. **Small batches** make communication expensive 2. **No gradients** means less memory pressure 3. **Latency matters** \- every millisecond counts 4. **Memory is cheap** \- just buy a bigger GPU ## Deep Dive: NCCL and Collective Communication When GPUs communicate, they use NVIDIA's NCCL (NVIDIA Collective Communications Library). Understanding NCCL is key to understanding why tensor parallelism is slow on cloud GPUs. ### What Is NCCL? NCCL provides optimized implementations of collective communication operations: - **Broadcast**: One GPU sends data to all others - **All-gather**: Each GPU shares its data with all others - **All-reduce**: Each GPU contributes data, result is reduced (sum/max/min) and shared with all - **Reduce-scatter**: All-reduce but each GPU gets only its slice These operations are the foundation of distributed deep learning. ### The All-Reduce Algorithm: Ring-Reduce Let's break down how **all-reduce** works with 4 GPUs using the ring algorithm. Suppose each GPU has a value it wants to sum across all GPUs: ``` GPU 0: [1, 2, 3, 4] GPU 1: [5, 6, 7, 8] GPU 2: [9, 10, 11, 12] GPU 3: [13, 14, 15, 16] Goal: Each GPU should end up with [28, 32, 36, 40] (sum of all corresponding elements) ``` **Phase 1: Reduce-Scatter** (N-1 steps, where N=4 GPUs) GPUs arranged in a ring: GPU 0 → GPU 1 → GPU 2 → GPU 3 → GPU 0 Each GPU's data is divided into 4 chunks. Step 1: ``` GPU 0 sends chunk 0 to GPU 1 GPU 1 sends chunk 1 to GPU 2 GPU 2 sends chunk 2 to GPU 3 GPU 3 sends chunk 3 to GPU 0 After receiving and summing: GPU 0: [1+13, 2, 3, 4] GPU 1: [5, 6+1, 7, 8] GPU 2: [9, 10, 11+5, 12] GPU 3: [13, 14, 15, 16+9] ``` Step 2: ``` GPU 0 sends updated chunk 3 to GPU 1 GPU 1 sends updated chunk 0 to GPU 2 GPU 2 sends updated chunk 1 to GPU 3 GPU 3 sends updated chunk 2 to GPU 0 After receiving and summing: GPU 0: [1+13, 2, 3+11+5, 4] GPU 1: [5+6+1, 6+1, 7, 8+16+9] GPU 2: [9, 10+15, 11+5, 12] GPU 3: [13, 14, 15+7, 16+9] ``` Continue for N-1 = 3 steps total. After reduce-scatter, each GPU has the complete sum for 1/4 of the data. **Phase 2: All-Gather** (N-1 steps) Now each GPU shares its fully-reduced chunk with others. Step 1: ``` GPU 0 sends its complete chunk 3 to GPU 1 GPU 1 sends its complete chunk 0 to GPU 2 GPU 2 sends its complete chunk 1 to GPU 3 GPU 3 sends its complete chunk 2 to GPU 0 ``` Continue for N-1 = 3 steps. After all-gather, all GPUs have the complete result: `[28, 32, 36, 40]`. **Total communication:** - Each GPU sends N-1 messages in reduce-scatter - Each GPU sends N-1 messages in all-gather - Total: 2(N-1) messages per GPU - For 4 GPUs: 6 messages per GPU **Data transferred per GPU:** If total data size is S, each message is S/N in size. Total per GPU: 2(N-1) × S/N = 2S(N-1)/N For large N, this approaches 2S. Very efficient! ### Ring vs Tree Algorithms NCCL supports multiple algorithms: **Ring Algorithm:** - Best for bandwidth optimization - All GPUs participate equally - Latency: O(N) - depends on number of GPUs - Bandwidth: O(1) - optimal use of links **Tree Algorithm:** - Best for latency optimization - GPUs arranged in a binary tree - Latency: O(log N) - much faster for many GPUs - Bandwidth: O(log N) - less efficient NCCL automatically chooses based on message size and topology. For small messages (< 1MB): Tree algorithm (lower latency) For large messages (> 1MB): Ring algorithm (higher bandwidth) ### Network Topology Matters NCCL's performance depends heavily on how GPUs are connected. **Best: NVLink (GPU-to-GPU direct connection)** ``` [GPU 0] ←→ [GPU 1] ↕ ↕ [GPU 2] ←→ [GPU 3] ``` - Bandwidth: 900 GB/s (NVLink 4.0 on H200) - Latency: \~2 µs - Protocol: GPU-Direct (no CPU involvement) **Okay: PCIe within same NUMA node** ``` [CPU/NUMA Node] │ ───────┴─────── │ │ │ [GPU 0][GPU 1][GPU 2] ``` - Bandwidth: 64 GB/s (PCIe 4.0 x16) - Latency: \~20 µs - Protocol: PCIe transfers through CPU **Bad: PCIe across NUMA nodes** ``` [CPU/NUMA 0] [CPU/NUMA 1] │ │ [GPU 0] [GPU 1] ``` - Bandwidth: 32 GB/s (halved due to NUMA boundary) - Latency: \~40 µs - Protocol: PCIe + inter-socket communication **Worst: TCP/IP across machines** ``` [Machine 0] ←→ [Machine 1] [GPU 0] TCP [GPU 1] ``` - Bandwidth: 10-100 Gb/s (1.25-12.5 GB/s for 10-100 GbE) - Latency: \~50-200 µs - Protocol: TCP/IP network stack ### What I Saw in NCCL Logs When I enabled NCCL debugging: ```bash export NCCL_DEBUG=INFO export NCCL_DEBUG_SUBSYS=ALL ``` I saw this in the logs: ``` NCCL INFO [send] via NET/Socket ``` This means: **TCP sockets**. The slowest possible mode. Why? Because the H200 GPUs in DigitalOcean weren't connected via NVLink. They were separate instances, communicating over the network. What I wanted to see: ``` NCCL INFO [send] via P2P/IPC ``` This would mean: **Peer-to-peer over NVLink or fast PCIe**, orders of magnitude faster. Or for cross-machine with InfiniBand: ``` NCCL INFO [send] via NET/IB/GDRDMA ``` This would mean: **GPU-Direct RDMA over InfiniBand**, which bypasses the CPU and is much faster than TCP. But cloud providers don't give you NVLink or InfiniBand. You get PCIe at best, TCP sockets at worst. ## Understanding Tensor Parallelism vs Pipeline Parallelism There are two main ways to split a model across GPUs: ![](https://til.codes/content/images/2025/11/image-9.png) **Tensor Parallelism:** - Each layer is split across multiple GPUs - GPU 0 has the first 25% of each weight matrix - GPU 1 has the next 25%, and so on - All GPUs must synchronize after every layer **Pipeline Parallelism:** - Each GPU holds complete layers - GPU 0 has layers 1-10 - GPU 1 has layers 11-20, etc. - GPUs pass activations sequentially For inference with small batch sizes, pipeline parallelism seems better (less communication). But vLLM uses tensor parallelism by default. Why? Because pipeline parallelism has bubble time: while GPU 0 is processing token 5, GPUs 1-3 are idle waiting for the activation. For single-request inference, you can't fill the pipeline efficiently. Tensor parallelism keeps all GPUs busy, but at a cost: communication overhead. ## The Memory Math: Do I Even Need Multi-GPU? My model: Qwen3-30B - Weights: \~60GB FP16 - KV cache at 60k context: \~55GB - Total: \~115GB Single H200 GPU: - VRAM: 141GB - With 0.75 utilization: 106GB usable Wait. The model already fits on one GPU. Why am I even considering tensor parallelism? Because I thought splitting across GPUs would let me serve more requests simultaneously. More memory = bigger batch size = higher throughput. This was my first mistake. ## Experiment: Tensor Parallel Size = 4 I configured vLLM to split the model across 4 H200 GPUs: ```yaml model: Qwen/Qwen3-30B-A3B-Instruct-2507 tensor-parallel-size: 4 max-model-len: 8192 gpu-memory-utilization: 0.9 max-num-seqs: 512 ``` Key changes: - `tensor-parallel-size: 4` instead of `1` - Requested 4 GPUs instead of 1 - Increased max-num-seqs from 128 to 512 (hoping for higher throughput) - Reduced context window to 8192 (was 60k) ### The Hardware Setup I was initially on AWS g5.24xlarge instances: - 4x NVIDIA A10G GPUs (24GB VRAM each) - GPUs connected via PCIe 4.0 - No NVLink Later, I switched to DigitalOcean H200 GPU nodes: - 1x NVIDIA H200 GPU (141GB VRAM) - But I wanted to test multi-GPU, so I spun up a 4-GPU setup The H200s weren't connected via NVLink in the cloud setup. They communicated over PCIe 4.0, just like the A10Gs. This matters. A lot. ## Understanding GPU Interconnects When GPUs need to communicate (and with tensor parallelism, they communicate constantly), the interconnect bandwidth determines performance. ![](https://til.codes/content/images/2025/11/image-10.png) PCIe 4.0: 64 GB/s NVLink 4.0 (H200): 900 GB/s That's a **14x difference** in bandwidth. For tensor parallelism, every layer requires an all-reduce operation to synchronize gradients across GPUs. With PCIe, this is slow. With NVLink, it's fast. Cloud providers rarely give you NVLink. You get PCIe. ## The NCCL Communication Overhead vLLM uses NVIDIA's NCCL (NVIDIA Collective Communications Library) for GPU-to-GPU communication in tensor parallelism. I enabled NCCL debug logging to see what was happening: ```yaml env: - name: NCCL_DEBUG value: INFO - name: NCCL_DEBUG_SUBSYS value: ALL ``` In the logs, I saw: ``` [send] via NET/Socket ``` This means NCCL is using raw TCP sockets for cross-GPU communication. Not even RDMA. This is the slowest possible mode. What I wanted to see: ``` [send] via NET/IB/GDRDMA ``` That would mean InfiniBand with GPU-Direct RDMA, which is efficient for cross-node communication. Or better yet, for single-node multi-GPU: ``` [send] via P2P/IPC ``` Which means direct GPU-to-GPU memory access over NVLink or fast PCIe. But I was getting NET/Socket. Because the cloud GPUs weren't configured with proper interconnects. ## Benchmarking: Single GPU vs 4 GPUs I ran the same workload on both configurations and measured: - Time to first token (TTFT) - Inter-token latency - Throughput (requests per second) ### Test Setup - 100 concurrent requests - Each request: 1000 token prompt, 500 token generation - Structured output using Outlines (JSON schema constraints) ### Results: Single GPU (Baseline) ``` Configuration: tensor-parallel-size=1, max-model-len=60000 TTFT: 450ms (p50), 680ms (p95) Inter-token latency: 22ms (p50), 35ms (p95) Throughput: 12.4 requests/second GPU utilization: 85-90% ``` ### Results: 4 GPUs (Tensor Parallelism) ``` Configuration: tensor-parallel-size=4, max-model-len=8192 TTFT: 1250ms (p50), 1900ms (p95) Inter-token latency: 68ms (p50), 105ms (p95) Throughput: 8.1 requests/second GPU utilization per GPU: 40-50% ``` Wait. The 4-GPU setup was **slower**? - TTFT: 2.8x worse (1250ms vs 450ms) - Inter-token latency: 3.1x worse (68ms vs 22ms) - Throughput: 35% lower (8.1 vs 12.4 req/s) How is this possible? ## Why Tensor Parallelism Failed ### 1\. Communication Overhead Dominates With tensor parallelism, every transformer layer requires communication: ![](https://til.codes/content/images/2025/11/image-11.png) For a 30B model with \~60 layers, that's: - 3 all-reduce operations per layer - 180 all-reduce operations per forward pass - Each all-reduce with 4 GPUs over PCIe: \~1-2ms Total communication overhead: **180-360ms per forward pass**. On a single GPU, there's no communication. It's just compute. ### 2\. Small Batch Size Tensor parallelism amortizes communication overhead over batch size: - Large batch: compute time dominates, communication is a smaller percentage - Small batch: communication time dominates, compute is tiny For structured output generation, I can't use huge batches. Each request has different JSON schemas, different prompt lengths, and different generation lengths. Batching is limited to maybe 8-16 similar requests. With small batches, tensor parallelism overhead kills you. ![](https://til.codes/content/images/2025/11/image-12.png) ### 3\. Memory Isn't the Bottleneck I thought 4 GPUs = 4x memory = 4x throughput. But throughput isn't limited by memory. It's limited by compute and communication. With tensor parallelism: - Each GPU holds 1/4 of the model (15GB) - But KV cache is replicated across all GPUs - So memory savings aren't 4x The KV cache is duplicated because each GPU needs the full context to compute attention. You only split the model weights, not the KV cache. Memory per GPU with tensor-parallel-size=4: - Model weights: 60GB / 4 = 15GB - KV cache: 40GB (NOT divided by 4) - Total: 55GB per GPU Memory on single GPU: - Model weights: 60GB - KV cache: 40GB - Total: 100GB I'm only saving 45GB by using 4 GPUs instead of 1\. That's not enough to justify the communication overhead. ### 4\. Reduced Context Window To make the 4-GPU setup fit in memory, I had to reduce the context window from 60k to 8k tokens. This directly hurt structured output quality. Remember from the previous article: longer context windows allow for: - More detailed system prompts - More few-shot examples - Larger input documents Cutting context from 60k to 8k meant worse quality for the same throughput. ## The Breaking Point: AWS g5.24xlarge The g5.24xlarge has 4x A10G GPUs (24GB each). I initially tried to run Qwen3-30B with tensor parallelism on this instance. The memory math: - Model weights split across 4 GPUs: 60GB / 4 = 15GB per GPU (fits in 24GB) - KV cache replicated: 40GB per GPU (doesn't fit in 24GB) I had to: - Reduce context window to 4096 tokens (KV cache drops to \~10GB) - Reduce max-num-seqs to 64 - Use aggressive memory utilization (0.95) Even then, I hit OOM errors under load. The problem: with tensor parallelism, all GPUs must have enough memory for the **full KV cache** of all concurrent requests. If I want to serve 64 concurrent requests with 4k context each: - KV cache per request: \~2.5GB - Total KV cache: 64 x 2.5GB = 160GB - Per GPU: 160GB (not divided) This doesn't fit in 24GB per GPU, even with the model split. ## When Tensor Parallelism Works I'm not saying tensor parallelism is useless. It works well when: ### 1\. NVLink or InfiniBand Interconnects On DGX systems with NVLink, communication is 14x faster. The overhead becomes manageable. Example: DGX H100 with 8 GPUs connected via NVLink - All-reduce latency: \~0.1ms (vs 1-2ms on PCIe) - Total communication overhead: 18ms (vs 180-360ms) At that point, tensor parallelism makes sense. ### 2\. Large Batch Sizes If you can batch 128+ requests together, communication overhead is amortized: - Compute time dominates (600ms+) - Communication time becomes small percentage (300ms) But for structured output with diverse schemas, large batches are rare. ### 3\. Models That Don't Fit on One GPU If your model is 405B parameters (810GB in FP16), you have no choice. You need tensor parallelism. But for a 30B model on H200 (141GB VRAM), it's unnecessary. ### 4\. Throughput Over Latency If you care about requests per second over per-request latency, tensor parallelism with large batches can help. But I care about p50 and p95 latency. Users don't care about throughput; they care about response time. ## Alternative: Increase Replicas Instead Instead of 1 instance with 4 GPUs, what about 4 instances with 1 GPU each? This is called **data parallelism** or **model replicas**. ![](https://til.codes/content/images/2025/11/image-13.png) With 4 replicas: - Each instance handles 12.4 req/s - Total throughput: 49.6 req/s - No communication overhead - Better fault tolerance (one instance down = 75% capacity remaining) With tensor parallelism on 4 GPUs: - One instance handles 8.1 req/s - Total throughput: 8.1 req/s - Communication overhead dominates - No fault tolerance (one GPU fails = entire instance down) Model replicas are strictly better for my use case. ## The Cost Analysis Let's compare costs: ### Option A: Tensor Parallelism (4 GPUs on one instance) DigitalOcean H200 4-GPU instance: - Cost: \~$10,000/month - Throughput: 8.1 req/s - Latency p50: 1250ms Cost per 1000 requests: $10,000 / (8.1 req/s x 86400s x 30d) x 1000 = $0.047 ### Option B: Single GPU Replicas (4 separate instances) DigitalOcean H200 1-GPU instance: - Cost: $2,622/month per instance - 4 instances: $10,488/month - Throughput per instance: 12.4 req/s - Total throughput: 49.6 req/s - Latency p50: 450ms Cost per 1000 requests: $10,488 / (49.6 req/s x 86400s x 30d) x 1000 = $0.008 Option B is: - 6x cheaper per request - 2.8x lower latency - Better fault tolerance The choice is obvious. ## GPU Utilization: A Misleading Metric When I looked at GPU utilization with tensor parallelism, each GPU showed 40-50%. I initially thought: "The GPUs are underutilized! I need to increase batch size!" But that's the wrong interpretation. The GPUs were idle waiting for communication, not idle waiting for work. ![](https://til.codes/content/images/2025/11/image-14.png) Low GPU utilization in tensor parallelism doesn't mean you need more load. It means your GPUs are communication-bound. ## Pipeline Parallelism: The Road Not Taken I briefly considered pipeline parallelism as an alternative: - Split layers across GPUs instead of splitting each layer - GPU 0: Layers 1-15 - GPU 1: Layers 16-30 - GPU 2: Layers 31-45 - GPU 3: Layers 46-60 This reduces communication (only pass activations between layers, not within layers). But pipeline parallelism has bubble time: when GPU 0 is processing request 2, GPUs 1-3 are idle waiting. For single-request inference (common in structured output), pipeline parallelism is even worse than tensor parallelism. Pipeline parallelism works for training with large batches and micro-batching. Not for low-latency inference. ## The Final Configuration: Single H200 GPU After all the experiments, I went back to the simplest configuration: ```yaml model: Qwen/Qwen3-30B-A3B-Instruct-2507 tensor-parallel-size: 1 max-model-len: 60000 gpu-memory-utilization: 0.75 max-num-seqs: 128 enable-chunked-prefill: true enforce-eager: true resources: requests: nvidia.com/gpu: "1" ``` Single GPU. No parallelism. Simple. This gave me: - Best latency (450ms TTFT) - Largest context window (60k tokens) - Simplest deployment - Lowest cost per request When I need more throughput, I add replicas. Not GPUs per instance. ## Lessons Learned ### 1\. Communication Overhead Is Real Tensor parallelism sounds great in theory. In practice, GPUs spend more time synchronizing than computing when interconnects are slow. PCIe 4.0 is not fast enough for efficient tensor parallelism. You need NVLink or InfiniBand. ### 2\. Cloud GPUs Lack NVLink Most cloud providers (AWS, GCP, Azure, DigitalOcean) give you PCIe-connected GPUs, not NVLink. DGX systems with NVLink exist, but they're expensive and rare. For cloud inference, assume PCIe interconnects. ### 3\. Small Batches Kill Tensor Parallelism Communication overhead is amortized over batch size. With batch=1 or batch=8, tensor parallelism is pure overhead. Structured output generation has small, variable-sized batches. Tensor parallelism is a bad fit. ### 4\. Model Replicas > Tensor Parallelism For throughput scaling, horizontal scaling (more instances) beats tensor parallelism (more GPUs per instance). Unless your model doesn't fit on one GPU, don't use tensor parallelism. ### 5\. Memory Isn't Always the Bottleneck I thought 4 GPUs = 4x memory = 4x throughput. Wrong. KV cache is replicated across GPUs in tensor parallelism. You don't get 4x memory. And even if you did, throughput is limited by compute and communication, not memory. ### 6\. Simpler Is Better Single GPU, single model instance, simple deployment. Scale by adding replicas. This is easier to manage, easier to debug, and more cost-effective than complex multi-GPU setups. ## The Decision Tree for Parallelism Here's how I think about parallelism now: ![](https://til.codes/content/images/2025/11/image-15.png) ## When I Would Use Tensor Parallelism Despite all the negatives, tensor parallelism has its place: 1. **Model > GPU VRAM**: 405B model on H200? You need 3+ GPUs minimum. 2. **NVLink available**: DGX systems make tensor parallelism fast. 3. **Large, uniform batches**: Batch size 128+ with similar request lengths. 4. **Throughput > latency**: You optimize for requests/second, not milliseconds per request. But for structured output on cloud GPUs with a 30B model? Tensor parallelism is the wrong tool. ## What's Next In the next article, I'll dive into KV cache optimization. I experimented with: - Increasing context window from 16k to 60k tokens - Chunked prefill for long prompts - Prefix caching for repeated prompts - Memory profiling to understand KV cache growth The KV cache turned out to be the biggest consumer of GPU memory, and optimizing it made a bigger difference than any multi-GPU setup. Stay tuned for "[KV Cache: The Hidden Memory Monster in LLM Inference.](https://til.codes/ghostbusters-who-you-gonna-call-when-kv-cache-eats-your-gpu-2/)" --- **TL;DR**: I tried splitting Qwen3-30B across 4 GPUs using tensor parallelism to increase throughput. It was 2.8x slower than single GPU due to communication overhead over PCIe 4.0\. Without NVLink, tensor parallelism adds massive latency from all-reduce synchronization every layer. For small batch sizes (typical in structured output), communication dominates compute. Learned that scaling via model replicas (4 instances x 1 GPU) is 6x more cost-effective and 2.8x lower latency than tensor parallelism (1 instance x 4 GPUs). Unless your model doesn't fit on one GPU or you have NVLink, don't use tensor parallelism. ### Honey, I Shrunk the Model: When Quantizing 70B Parameters Broke Everything URL: https://til.codes/honey-i-shrunk-the-model-when-quantizing-70b-parameters-broke-everything/ Last updated: 2025-11-01T22:37:53.000Z # I spent the last few days testing different models, quantization formats, and vLLM setups, aiming to achieve structured output generation of acceptable quality. In this article, I will explore my process of moving from Llama-3.1-70B to a quantized FP8 version, experimenting with Llama 4 Scout's MoE architecture, trying Qwen2.5-72B, and finally settling on Qwen3-30B running in native FP16\. The experience taught me more about quantization trade-offs, instruction following, and vLLM's guided decoding than any documentation could. ## The Problem: Structured Output Generation at Scale I was running Llama-3.1-8B-Instruct on a single H200 GPU (141GB VRAM). For basic text generation, it worked fine. But my use case required something more complex: structured output generation using vLLM's guided decoding with the Outlines backend. Outlines uses finite-state machines (FSMs) to constrain LLM outputs to specific JSON schemas or regex patterns. This is critical when you need guaranteed valid JSON, not just "JSON-ish" text that might parse correctly. The problem? Smaller models struggle with instruction following when you add guided decoding constraints. The model needs to: 1. Understand complex system prompts 2. Follow JSON schema specifications precisely 3. Maintain coherent reasoning while the FSM filters out invalid tokens 4. Handle edge cases in structured data An 8B model just doesn't have enough capacity for this. I needed something bigger. ## Understanding the Memory Math Before diving into my experiments, let me explain the memory calculations because they're critical to understanding why quantization seemed necessary at first. Large language models store weights as floating-point numbers. FP16 (16-bit floating point) is standard: - Each parameter = 2 bytes - 70B parameters = 140GB of weights But GPU memory requirements are more than just the weights: ![](https://til.codes/content/images/2025/11/image-1.png) The KV cache is the killer. For each token in your context window, you store: - K (key) vectors: shape `[num_layers, num_heads, hidden_dim]` - V (value) vectors: same shape For a 70B model with 80 layers and a 32k context window, the KV cache alone can consume 40-60GB. This is where `--gpu-memory-utilization` comes in. It tells vLLM: "Reserve this percentage of GPU memory for the model and KV cache, and don't exceed it." ## What Is Quantization? Quantization reduces numerical precision to save memory: ![](https://til.codes/content/images/2025/11/image-2.png) The trade-off is precision loss. When you round weights from FP16 to FP8, you lose information. The question is: how much does this affect model quality? ## Experiment 1: Llama-3.1-70B (Baseline) First, I tried the obvious choice: `meta-llama/Llama-3.1-70B` without quantization. Configuration: ```yaml model: meta-llama/Llama-3.1-70B tensor-parallel-size: 1 max-model-len: 32768 gpu-memory-utilization: 0.85 ``` This barely fit on the H200: - Model weights: \~140GB FP16 - KV cache at 32k context: \~45GB - Total: \~185GB needed - Available with 0.85 utilization: 141GB x 0.85 = \~120GB It didn't work. I hit OOM (out of memory) errors immediately when trying to load the model. This is when I realized: even with 141GB of VRAM, a 70B model in FP16 doesn't fit comfortably when you factor in the KV cache. ## Experiment 2: Llama-3.3-70B with RedHat's W8A8 Quantization I found a pre-quantized model on HuggingFace: `RedHatAI/Llama-3.3-70B-Instruct-quantized.w8a8`. The naming convention w8a8 means: - w8 = 8-bit weights - a8 = 8-bit activations This should cut memory usage roughly in half: - Model weights: \~70GB (was 140GB) - KV cache: Still \~45GB for 32k context - Total: \~115GB (within H200 limits!) I configured vLLM: ```yaml model: RedHatAI/Llama-3.3-70B-Instruct-quantized.w8a8 tensor-parallel-size: 4 quantization: compressed-tensors max-model-len: 8192 gpu-memory-utilization: 0.9 ``` Error: ``` ValueError: Quantization method compressed-tensors is not supported in vLLM 0.11.0 ``` The compressed-tensors format was supported in vLLM 0.9.x, but somewhere between 0.9 and 0.11, the API changed and support was dropped. This is the first lesson about quantization in production: API stability is not guaranteed. Quantization formats and implementations evolve rapidly. A pre-quantized model from HuggingFace might not work with your version of vLLM. ## Experiment 3: Native FP8 Quantization Instead of using a pre-quantized model, I tried vLLM's native FP8 quantization on Llama-3.3-70B. The H200 GPU has native FP8 support through NVIDIA's Hopper Tensor Cores. This should be fast and memory-efficient. Configuration: ```yaml model: meta-llama/Llama-3.3-70B-Instruct quantization: fp8 tensor-parallel-size: 4 max-model-len: 4096 gpu-memory-utilization: 0.9 ``` This time, it loaded! The model started serving requests. ### The Instruction Following Problem I ran it through my structured output test cases using Outlines with JSON schema constraints. The quality degradation was immediate and obvious: **Test Case: Extract structured data from text** ```json { "name": "string", "age": "integer", "email": "string" } ``` FP16 Result: ```json { "name": "John Smith", "age": 35, "email": "john.smith@example.com" } ``` FP8 Result: ```json { "name": "John Smith", "age": 35, "email": "johnsmithexample.com" } ``` Notice the email is malformed. The FP8 model missed the `@` symbol. Worse, on complex nested schemas, the FP8 model would sometimes: 1. Generate incomplete JSON (missing closing braces) 2. Hallucinate extra fields not in the schema 3. Mix up types (strings where integers belong) 4. Lose coherence mid-generation The problem isn't that FP8 is broken. It's that instruction following is a delicate capability that degrades with reduced precision. ### Why FP8 Hurts Instruction Following Instruction following requires the model to: 1. Parse and understand system prompts 2. Maintain that understanding across many tokens 3. Apply constraints consistently When you quantize from FP16 to FP8, you're compressing the model's learned representations. The weights that encode "follow JSON schema precisely" get rounded. The activations that represent "I'm currently inside a string field" lose precision. For creative text generation, this might be fine. For structured output, it's fatal. ![](https://til.codes/content/images/2025/11/image-3.png) ## Experiment 4: Llama-4-Scout-17B-16E (MoE Architecture) At this point, I started questioning whether a dense 70B model was even the right approach. Llama 4 Scout is a Mixture-of-Experts (MoE) model: - 109B total parameters - Only 17B activated per token - 16 expert networks + 1 shared expert The idea: get 70B-class quality with 17B-class memory usage. Configuration: ```yaml model: meta-llama/Llama-4-Scout-17B-16E-Instruct tensor-parallel-size: 1 max-model-len: 32768 gpu-memory-utilization: 0.85 max-num-seqs: 256 guided-decoding-backend: outlines limit-mm-per-prompt: image=10 ``` ### Understanding MoE Memory Characteristics MoE models are weird for memory: - All 109B parameters must be loaded into VRAM - But only 17B are active per forward pass - So memory usage is high, but compute is lower Total memory for Llama 4 Scout: - Model weights: \~220GB in FP16 for all experts - Active computation: Only using 17B at a time Wait, 220GB? That's way more than 141GB on my H200. I needed to quantize the MoE model to fit it. But here's the problem with quantizing MoE: - The routing mechanism relies on precise weight values - Quantizing expert weights can break routing decisions - The shared expert is critical and can't tolerate much quantization I tried running it anyway, hoping vLLM would handle FP8 quantization gracefully for MoE. It didn't fit. OOM errors again. MoE models promise computational efficiency, not memory efficiency. They're great for throughput on massive GPU clusters, but terrible for single-GPU inference when you're memory-constrained. ## Experiment 5: Qwen2.5-72B-Instruct I pivoted to a different model family: Qwen2.5-72B-Instruct. Why Qwen? 1. Known for strong instruction following 2. Better structured output generation than Llama 3. Native support in vLLM Configuration: ```yaml model: Qwen/Qwen2.5-72B-Instruct quantization: fp8 tensor-parallel-size: 1 max-model-len: 32768 gpu-memory-utilization: 0.90 trust-remote-code: true ``` With FP8 quantization: - Model weights: \~72GB - KV cache at 32k: \~40GB - Total: \~112GB (fits!) ### Quality Comparison: Qwen2.5-72B FP8 vs Llama-3.3-70B FP8 I ran the same structured output benchmarks. Qwen2.5-72B in FP8 was noticeably better than Llama-3.3-70B in FP8 for instruction following. Fewer schema violations, better type consistency, less hallucination. But it still wasn't perfect and had subtle errors: - Occasional type mismatches - Rare schema violations - Inconsistent handling of optional fields For production use, 80-85% reliability isn't good enough when you need 99%+. ## Experiment 6: Qwen3-30B-A3B-Instruct (The Winner) Finally, I tried the newest Qwen model: Qwen3-30B-A3B-Instruct. This is a 30B parameter model, smaller than the 70B models I'd been testing. But it's the latest generation, trained with better data and techniques. Configuration: ```yaml model: Qwen/Qwen3-30B-A3B-Instruct-2507 tensor-parallel-size: 1 max-model-len: 60000 gpu-memory-utilization: 0.75 max-num-seqs: 128 enable-chunked-prefill: true enforce-eager: true ``` Key differences: - No quantization (native FP16) - Smaller model (30B vs 70B) - Larger context window (60k vs 32k) - More conservative GPU memory utilization (0.75 vs 0.90) Memory breakdown: - Model weights: \~60GB FP16 - KV cache at 60k context: \~55GB - Total: \~115GB - With 0.75 utilization: 141GB x 0.75 = 106GB (tight but workable) ### Quality Results Qwen3-30B in FP16 outperformed Qwen2.5-72B in FP8 for structured output generation. The instruction following was near-perfect: - 99%+ schema compliance - Consistent type handling - Reliable field extraction - No hallucinated fields How is a smaller model better than a larger quantized one? ![](https://til.codes/content/images/2025/11/image-4.png) The answer: precision matters more than parameter count for instruction following tasks. A 30B model in FP16 has: - Full numerical precision for all weights - Accurate activations throughout the forward pass - Reliable attention mechanisms - Consistent output distributions A 72B model in FP8 has: - 2.4x more parameters - But compressed representations - Accumulated quantization errors - Less reliable for constrained generation ## The Bigger Context Window Surprise One unexpected benefit of the smaller model: I could afford a much larger context window. With Llama-3.3-70B FP8, I was limited to 4096-8192 tokens to fit in memory. With Qwen3-30B FP16, I could run 60,000 tokens. For structured output generation with Outlines, this matters because: 1. Longer system prompts with detailed schemas 2. More few-shot examples in the prompt 3. Larger input documents to extract from 4. More room for reasoning chains The context window directly improves structured output quality. ## vLLM Configuration Deep Dive Let me explain the critical vLLM flags I landed on: ### \--gpu-memory-utilization 0.75 This reserves 75% of GPU memory for the model and KV cache. Why not 0.9? - The remaining 25% is for CUDA overhead, temporary buffers, and safety margin - At 0.9, you're one memory spike away from OOM - At 0.75, the system has breathing room I learned this the hard way after multiple CUDA OOM crashes at 0.9. ### \--max-num-seqs 128 Maximum number of sequences to batch together. Smaller is more stable: - Fewer sequences = less memory pressure - More predictable memory usage - Lower latency per request I originally had this at 512, which caused memory spikes during peak load. ### \--enable-chunked-prefill This enables processing long prompts in chunks rather than all at once. Critical for 60k context windows: - A 60k token prompt in one shot can OOM - Chunked prefill processes 4k-8k tokens at a time - Slower time-to-first-token, but doesn't crash ### \--enforce-eager This disables CUDA graph capture. CUDA graphs are a performance optimization where vLLM pre-compiles execution graphs. But they consume extra memory and can cause instability. With --enforce-eager: - Slower inference (no graph optimization) - Lower memory usage - More stable under varying loads For structured output generation, I prioritize stability over raw speed. ## Lessons Learned ### 1\. Quantization API Instability Pre-quantized models are risky. The format might not be supported by your vLLM version. Native quantization (FP8) is more reliable but still evolving. ### 2\. Instruction Following Degrades with Quantization For creative text generation, FP8 might be fine. For structured output, the precision loss shows up as schema violations and type errors. ### 3\. MoE Memory Characteristics MoE models don't save memory. They save compute. All expert weights must be loaded. This makes them unsuitable for memory-constrained single-GPU inference. ### 4\. Smaller + FP16 > Larger + FP8 A 30B model in FP16 can outperform a 72B model in FP8 for precision-critical tasks. Parameter count isn't everything. ### 5\. Context Window Trade-offs Smaller models leave more room for KV cache, enabling larger context windows. For structured output with complex schemas, context window size directly impacts quality. ### 6\. GPU Memory Utilization Conservative settings (0.75) are more stable than aggressive ones (0.9). The memory you save by being conservative prevents crashes under load. ## The Decision Tree I Wish I Had Here's the decision tree I follow now for model selection: ![](https://til.codes/content/images/2025/11/image-5.png) ## When Quantization Makes Sense I'm not saying quantization is useless. It's valuable for: 1. **Creative text generation** \- Where slight precision loss is tolerable 2. **Throughput-critical workloads** \- More requests per GPU by fitting more in memory 3. **Budget constraints** \- Can't afford H200-class GPUs 4. **Model sizes that don't fit in FP16** \- Truly massive models (405B+) However, for structured output generation with strict schemas, I'll opt for a smaller FP16 model over a larger quantized one every time. ## What's Next In the next article, I'll cover tensor parallelism. I experimented with splitting models across multiple GPUs using `--tensor-parallel-size 4`. Spoiler: for a 30B model on H200, a single GPU is faster than a multi-GPU. The communication overhead kills you. Stay tuned for "[Fast & Furious Tensor Parallelism: GPU Heist Gone Wrong](https://til.codes/fast-furious-tensor-parallelism-gpu-heist-gone-wrong/)" --- **TL;DR**: I attempted to run 70B models with quantization for structured output generation. Tested Llama-3.1-70B (OOM), Llama-3.3-70B + W8A8 (API incompatible), native FP8 (quality degraded), Llama-4-Scout MoE (too much memory), and Qwen2.5-72B FP8 (inconsistent). Finally landed on Qwen3-30B in FP16, which outperformed larger quantized models because precision matters more than parameter count for instruction following. Learned that quantization trades memory for accuracy, and that trade-off is fatal for structured output tasks. ### Beyond IO.inspect: The Holy Trinity of Elixir & Phoenix Debugging with Neovim and Nix URL: https://til.codes/beyond-io-inspect-the-holy-trinity-of-elixir-phoenix-debugging-with-neovim-and-nix/ Last updated: 2025-07-25T02:57:22.000Z Setting up a proper Elixir debugger in Neovim can feel like a dark art, especially when you throw Nix and `devenv` into the mix. I recently went down this rabbit hole, trying to get `nvim-dap` to play nicely with ElixirLS for a Phoenix project, and let me tell you, it was a journey. It was filled with cryptic errors, silent failures, and some moments of pure frustration. ## The First Hurdle: Talking to the Wrong Adapter Right out of the gate, my debugger failed to launch. My initial `nvim-dap` config was simple, but it was pointing to the generic `elixir-ls` command. It turns out ElixirLS ships with two different scripts: - `language_server.sh` for all that sweet LSP goodness. - `debug_adapter.sh` for... well, debugging. My setup was calling the wrong number. **The Fix:** I had to explicitly point DAP to the `debug_adapter.sh` script provided by Mason. A simple path change, and one problem down. ```lua dap.adapters.mix_task = { type = 'executable', -- Point directly to the debug adapter script! command = vim.fn.expand('~/.local/share/nvim/mason/packages/elixir-ls/debug_adapter.sh'), args = {} } ``` ## The Nix Saga: ElixirLS in a Cage My victory was short-lived. Since my Elixir installation is managed entirely by Nix/devenv (meaning it's not on my system's global `PATH`), the Mason-installed ElixirLS threw a fit. It couldn't find the `elixir` executable and crashed. ``` ** (FunctionClauseError) no function clause matching in IO.chardata_to_string/1 ``` I needed a way to make the configuration smart enough to use the Nix-provided environment when available. **The Solution:** Lua to rescue. I wrote a helper function that first checks if `elixir-ls` is in the `PATH` (which it will be inside a `devenv shell`). If found, it uses the `debug_adapter.sh` from that Nix-provided location. If not, it gracefully falls back to the default Mason path. ```lua local function get_elixir_ls_debug_adapter() -- Check if elixir-ls is in the shell's PATH (from Nix/devenv) local elixir_ls = vim.fn.exepath('elixir-ls') if elixir_ls ~= '' then local dir = vim.fn.fnamemodify(elixir_ls, ':h') local debug_adapter = dir .. '/debug_adapter.sh' if vim.fn.filereadable(debug_adapter) == 1 then vim.notify("Found Nix environment, using adapter: " .. debug_adapter) return debug_adapter end end -- Otherwise, fall back to the Mason-installed one local mason_adapter = vim.fn.expand("~/.local/share/nvim/mason/packages/elixir-ls/debug_adapter.sh") vim.notify("Using Mason debug adapter: " .. mason_adapter) return mason_adapter end ``` ## The Silent Server: Getting Phoenix to Cooperate With the adapter path sorted, I tried to launch a debug session for my Phoenix server... and nothing. The debugger would start, print a "Sleeping..." message, and then just sit there, mocking me. The server never actually booted up. After some time with the ElixirLS docs, I discovered that debugging Phoenix apps has some special rules. **The Fix:** The launch configuration needed a few key tweaks. You can't just `mix phx.server` and hope for the best. - You must use `debugInterpretModulesPatterns` to tell the debugger **only to interpret your own app's modules**. Otherwise, it tries to load everything and chokes. - Phoenix's live reload is **incompatible** with the debugger. - Don't set `startApps = true`. ```lua dap.configurations.elixir = { { type = "mix_task", name = "phoenix server", task = "phx.server", request = "launch", projectDir = "${workspaceFolder}", -- Tell the debugger which modules are yours! debugInterpretModulesPatterns = {"MyCoolApp*", "MyCoolAppWeb*"}, -- This is important for Phoenix exitAfterTaskReturns = false, }, } ``` ## The Mute REPL & Annoying Errors I was getting closer. I could launch the debugger and hit a breakpoint, but two smaller issues remained: 1. My DAP REPL was useless, screaming `No active session` whenever I tried to inspect a variable. 2. A constant warning about `unsupported exception breakpoints` was cluttering my screen. **The Fixes:** The first one was a classic user error. The REPL only works when execution is actually **paused at a breakpoint**. For the second, it's just an expected behavior, Elixir's debugger doesn't support exception breakpoints. A single line of config silenced it for good. ```lua dap.defaults.elixir.exception_breakpoints = {} ``` ## Complete, Working Config After all that troubleshooting, here is the complete, battle-tested configuration for your `nvim-dap` setup. It includes the adapter logic, correct Phoenix settings, a test runner, and some nice UI/keymap defaults. ```lua return { { "mfussenegger/nvim-dap", dependencies = { "rcarriga/nvim-dap-ui", "nvim-neotest/nvim-nio", "williamboman/mason.nvim", }, config = function() local dap = require "dap" local dapui = require "dapui" -- A nice, spacious UI layout dapui.setup({ layouts = { { elements = { { id = "scopes", size = 0.25 }, "breakpoints", "stacks", "watches" }, size = 40, position = "left", }, { elements = { "repl", "console" }, size = 0.25, position = "bottom", }, }, }) -- Smart adapter detection for Nix/devenv vs. Mason local function get_elixir_ls_debug_adapter() local elixir_ls = vim.fn.exepath('elixir-ls') if elixir_ls ~= '' then local dir = vim.fn.fnamemodify(elixir_ls, ':h') local debug_adapter = dir .. '/debug_adapter.sh' if vim.fn.filereadable(debug_adapter) == 1 then vim.notify("Found Nix environment, using adapter: " .. debug_adapter, vim.log.levels.INFO) return debug_adapter end end local mason_adapter = vim.fn.expand "~/.local/share/nvim/mason/packages/elixir-ls/debug_adapter.sh" vim.notify("Using Mason debug adapter: " .. mason_adapter, vim.log.levels.INFO) return mason_adapter end dap.adapters.mix_task = { type = "executable", command = get_elixir_ls_debug_adapter(), args = {}, } -- Silence the unsupported exception breakpoint warning dap.defaults.elixir.exception_breakpoints = {} dap.configurations.elixir = { -- Phoenix server config { type = "mix_task", name = "phoenix server", task = "phx.server", request = "launch", projectDir = "${workspaceFolder}", exitAfterTaskReturns = false, debugAutoInterpretAllModules = false, -- IMPORTANT: Change these patterns to match your app! debugInterpretModulesPatterns = {"MyCoolApp*", "MyCoolAppWeb*"}, env = { MIX_ENV = "dev" }, }, -- Mix test config { type = "mix_task", name = "mix test", task = "test", taskArgs = {"--trace"}, request = "launch", projectDir = "${workspaceFolder}", requireFiles = { "test/**/test_helper.exs", "test/**/*_test.exs" }, }, } -- Auto open/close DAP UI dap.listeners.after.event_initialized["dapui_config"] = function() dapui.open() end dap.listeners.before.event_terminated["dapui_config"] = function() dapui.close() end dap.listeners.before.event_exited["dapui_config"] = function() dapui.close() end -- Handy Keymaps vim.keymap.set("n", "db", dap.toggle_breakpoint, { desc = "Toggle Breakpoint" }) vim.keymap.set("n", "", dap.continue, { desc = "Continue (F5)" }) vim.keymap.set("n", "", dap.step_over, { desc = "Step Over (F10)" }) vim.keymap.set("n", "", dap.step_into, { desc = "Step Into (F11)" }) vim.keymap.set("n", "", dap.step_out, { desc = "Step Out (F12)" }) end, }, } ``` ## Final Tips & Gotchas - **Launch from Nix!** Always, always, *always* start Neovim from within your `devenv shell`. This is non-negotiable. - **Set Your Modules:** Remember to change `debugInterpretModulesPatterns` to match your application's module names (e.g., `MyApp*`, `MyAppWeb*`). - **REPL Usage:** The REPL only works when you're paused at a breakpoint. - **No Live Reload:** Phoenix's live reload feature is disabled during a debug session. You'll have to manually restart if you make changes. Getting Neovim, DAP, ElixirLS, and Nix to work together was a challenge, but the payoff is a powerful, integrated debugging experience right in your editor. No more `IO.inspect/1` spam Happy debugging! ### Remapping Keys on macOS with Nix-Darwin: My Battle with the EU Keyboard Layout URL: https://til.codes/remapping-keys-on-macos-with-nix-darwin-my-battle-with-the-eu-keyboard-layout/ Last updated: 2025-01-08T12:26:45.000Z If you’ve ever used an EU keyboard on macOS, you know the struggle. The tilde (`~`) is bizarrely placed next to the Z key, and getting to a backtick (\`) feels like solving a puzzle involving `Shift` and frustration. This post is about how I swapped the tilde and backtick keys using Nix-Darwin, `hidutil`, and some trial and error. If you’re also tired of your keyboard layout mocking you, read on. ### Why Bother Remapping? For anyone who spends time in the terminal, the tilde and backtick keys are essential. Yet, the EU keyboard layout seems designed to test your patience. So here is my plan - Swap the tilde (`~`) and backtick (\`) keys. - Make the change permanent, so I never have to think about it again. - Avoid third-party apps that might break with the next macOS update. ### Finding the Key Codes To remap keys, you need their codes. After digging through Apple documentation (and a lot of trial and error), I found the codes for the tilde and backtick: • Tilde (\~): 0x700000035 • Backtick (\`): 0x700000064 ### Using hidutil to Remap Keys Apple’s `hidutil` tool lets you remap keys at a low level. Here’s the command I used to swap the tilde and backtick: ```bash hidutil property --set '{ "UserKeyMapping":[ {"HIDKeyboardModifierMappingSrc":0x700000035,"HIDKeyboardModifierMappingDst":0x700000064}, {"HIDKeyboardModifierMappingSrc":0x700000064,"HIDKeyboardModifierMappingDst":0x700000035} ] }' ``` Running this command instantly swapped the two keys. Problem solved, right? Not quite. ### Making It Permanent with Nix-Darwin Typing the `hidutil` command every time I reboot isn’t practical. Since I use Nix-Darwin to manage my system configuration, I decided to make the remapping a permanent part of my setup. **Activation Script** ```bash { activation = { remapKeys = '' /usr/bin/hidutil property --set '{ "UserKeyMapping":[ {"HIDKeyboardModifierMappingSrc":0x700000035,"HIDKeyboardModifierMappingDst":0x700000064}, {"HIDKeyboardModifierMappingSrc":0x700000064,"HIDKeyboardModifierMappingDst":0x700000035} ] }' ''; }; } ``` **launchd Agent** For remapping that sticks across reboots, a launchd agent is the way to go. Here’s the configuration: ```bash { launchd.user.agents.remap-keys = { serviceConfig = { ProgramArguments = [ "/usr/bin/hidutil" "property" "--set" ''{ "UserKeyMapping":[ {"HIDKeyboardModifierMappingSrc":0x700000035,"HIDKeyboardModifierMappingDst":0x700000064}, {"HIDKeyboardModifierMappingSrc":0x700000064,"HIDKeyboardModifierMappingDst":0x700000035} ] }'' ]; RunAtLoad = true; }; }; } ``` This ensures the remapping is applied every time I log in. ```bash darwin-rebuild switch ``` And that was it. The tilde and backtick keys were swapped, and the change persisted across reboots. Mission accomplished. And that was it. The tilde and backtick keys were swapped, and the change persisted across reboots. Mission accomplished. ```bash hidutil property --set '{"UserKeyMapping":[]}' ``` Alternatively, just remove the script or launchd agent from your Nix-Darwin configuration and run `darwin-rebuild switch` ### Why this setup? - Clean and Declarative: The configuration lives in Nix, making it easy to manage and version control. - Reproducible: Got a new Mac? Just copy your Nix configuration, and you’re good to go. - Persistent: With a launchd agent, the remapping survives reboots. ## Closing thoughts Key remapping might seem like a small thing, but it’s one of those tweaks that makes using your computer just a little bit better. If you’re managing your macOS system with Nix-Darwin and you’ve got a keyboard layout that drives you nuts, give this a try. And hey, if you’ve got other Nix-Darwin tricks up your sleeve, let me know. I’m always on the lookout for new ways to make my setup smarter (or at least less frustrating). ### Advent of Code 2024: Day 6 - Guard Gallivant URL: https://til.codes/advent-of-code-2024-day-6-guard-gallivant/ Last updated: 2024-12-07T02:44:28.000Z # Today I dove into path finding and state machines. The problem seemed simple at first - just simulate a guard's patrol route - but it turned into a fascinating puzzle about cycle detection. I tackled it with my usual four languages - Rust, Elixir, Go, and Haskell - and each one showed me something new about handling state and detecting patterns. ## The Challenge: Predicting Patrol Patterns The puzzle dropped me into a 1518 laboratory with a guard following an intriguingly simple protocol: turn right when facing an obstacle, otherwise move forward. This deceptively straightforward rule set led to some complex emergent behavior, especially when I started looking for patrol loops in part 2. ## Four Languages, Four Philosophies ### Rust: Where Performance Meets Safety The heart of Rust's solution lies in its type system and zero-cost abstractions. Let's look at how it handles position and movement: ```rust #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] struct Position { row: i32, col: i32, } impl Position { const fn move_in_dir(self, dir: Direction) -> Self { match dir { Direction::North => Self::new(self.row - 1, self.col), Direction::East => Self::new(self.row, self.col + 1), Direction::South => Self::new(self.row + 1, self.col), Direction::West => Self::new(self.row, self.col - 1), } } } ``` What's fascinating here is how Rust's type system guides us toward correct code. The `#[derive]` attribute automatically implements crucial traits, while `const fn` ensures our movement calculations happen at compile time where possible. When part 2 demanded parallel processing, Rayon's parallel iterators slipped in seamlessly: ```rust candidates .into_par_iter() .filter(|&pos| grid.creates_loop(pos, start)) .count() ``` ### Haskell: Pure Functions and Strict Evaluation Haskell's approach to the problem is particularly interesting. The language's purity forced me to think carefully about state management: ```haskell data State = State { pos :: {-# UNPACK #-} !Position , dir :: !Direction } deriving (Eq, Ord, Show) hasLoop :: Grid -> Position -> State -> Bool hasLoop grid obstacle start = go Set.empty start where go !visited !current | isOutOfBounds grid nextPos = False | Set.member (pos current, dir current) visited = True | otherwise = go (Set.insert (pos current, dir current) visited) nextState ``` Those `UNPACK` pragmas and strict annotations (`!`) aren't just syntax noise - they're crucial performance optimizations. Without them, Haskell's lazy evaluation would build up during my cycle detection, leading to space leaks. The `where` clause creates a beautiful local scope for my helper function, making the code both elegant and efficient. ### Go: Simplicity in Motion Go's implementation shows its strength in handling concurrent operations without sacrificing readability: ```go func (g *Grid) createLoop(obstacle Position, start State) bool { visited := make(map[State]struct{}) current := start for steps := 0; steps < g.width * g.height * 4; steps++ { nextPos := current.pos.moveInDir(current.dir) if !g.isValid(nextPos) { return false } var nextState State if g.isBlocked(nextPos, obstacle) { nextState = State{current.pos, current.dir.turnRight()} } else { nextState = State{nextPos, current.dir} } if _, exists := visited[nextState]; exists { return true } visited[nextState] = struct{}{} current = nextState } return false } ``` The explicit state management and boundary checking reflect Go's philosophy of clarity over cleverness. When it came to parallelizing the search in part 2, Go's channels provided a natural way to distribute the work. ### Elixir: Pattern Matching Poetry Elixir's solution reads almost like the problem description itself: ```elixir defp process_instruction(grid, pos, dir, seen) do next_pos = move_forward(pos, dir) cond do out_of_bounds?(grid, next_pos) -> seen obstacle_at?(grid, next_pos) -> new_dir = turn_right(dir) new_state = {pos, new_dir} if MapSet.member?(seen, new_state), do: seen, else: process_instruction(grid, pos, new_dir, MapSet.put(seen, new_state)) true -> new_state = {next_pos, dir} if MapSet.member?(seen, new_state), do: seen, else: process_instruction(grid, next_pos, dir, MapSet.put(seen, new_state)) end end ``` The pattern matching and guard clauses make the state transitions crystal clear, while the recursive structure naturally maps to the problem's iterative nature. ## Performance: The Part 2 Challenge Part 2 transformed my simple path-finding problem into a search for cycle-inducing positions. This is where the performance characteristics of each language really came into play. Rust's Rayon made parallelization almost trivial, while still maintaining memory safety. Go's goroutines gave me fine-grained control over concurrent operations. Elixir's built-in parallelism through Task.async\_stream made it easy to distribute the work across cores. Haskell relied heavily on GHC's optimizations and strict evaluation to keep memory usage in check. ## Reflections on State and Cycles What fascinates me about this challenge is how it reveals each language's approach to state management. Rust's ownership system ensures we can't accidentally share state between threads. Haskell's purity forces us to be explicit about our state transitions. Go's simplicity makes the state machine's logic clear and maintainable. Elixir's pattern matching turns complex state transitions into readable code. ### Advent of Code 2024: Day 5 - Dependencies and Ordering URL: https://til.codes/advent-of-code-2024-day-5-dependencies-and-ordering/ Last updated: 2024-12-07T02:56:44.000Z # I found myself in the North Pole's printing department today, helping an elf with their safety manual updates. The printer had strict rules about page ordering - certain pages had to be printed before others. As I stared at the long list of rules like "47|53", I couldn't help but smile. This wasn't just about printing pages - this was a dependency resolution puzzle in disguise. ## The Challenge: A Printer's Dependency Graph The problem gave me two things: rules like "47|53" (meaning page 47 must be printed before page 53), and sequences of page numbers to validate. Part 1 asked me to identify sequences that already followed all the rules. Part 2 got more interesting - I needed to sort the invalid sequences according to the rules. ## Four Languages, Four Approaches ### Rust: Bidirectional Maps for Efficient Ordering In Rust, I modeled the rules using two HashMaps - one for forward dependencies and one for reverse: ```rust #[derive(Debug, Default)] struct RuleMaps { forward: HashMap>, reverse: HashMap>, } impl RuleMaps { fn compare(&self, a: i32, b: i32) -> std::cmp::Ordering { if self.forward.get(&a) .map_or(false, |targets| targets.contains(&b)) { Ordering::Less } else if self.forward.get(&b) .map_or(false, |targets| targets.contains(&a)) { Ordering::Greater } else { a.cmp(&b) } } } ``` This bidirectional approach made both validation and sorting efficient. The `compare` function integrates seamlessly with Rust's sorting mechanisms while respecting our dependency rules. ### Elixir: Pattern Matching for Clear Logic Elixir's pattern matching made the rule checking particularly elegant: ```elixir def should_come_before?(rules, a, b) do case Enum.find(rules, fn {source, target} -> (source == a and target == b) or (source == b and target == a) end) do {^a, ^b} -> true # Direct rule a|b exists {^b, ^a} -> false # Direct rule b|a exists nil -> false # No direct rule exists end end ``` The pin operator (`^`) creates a beautiful declarative way to express our ordering rules. The code reads almost like the problem description itself. ### Go: Pragmatic Sorting Go's approach shows its strength in straightforward, efficient code: ```go func shouldComeBefore(rules []rule, a, b int) bool { for _, r := range rules { if r.source == a && r.target == b { return true } if r.source == b && r.target == a { return false } } return false } func (s sequence) sort(rules []rule) sequence { sorted := make(sequence, len(s)) copy(sorted, s) sort.Slice(sorted, func(i, j int) bool { return shouldComeBefore(rules, sorted[i], sorted[j]) }) return sorted } ``` The explicit handling of comparisons and sorting reflects Go's preference for clarity over cleverness. ### Haskell: Pure Functional Dependencies Haskell's solution leverages efficient data structures from the containers package: ```haskell buildRuleMaps :: [Rule] -> (IntMap IntSet, IntMap IntSet) buildRuleMaps rules = foldl' insertRule (IM.empty, IM.empty) rules where insertRule (!fwd, !rev) (src, tgt) = ( IM.insertWith IS.union src (IS.singleton tgt) fwd , IM.insertWith IS.union tgt (IS.singleton src) rev ) ``` The use of `IntMap` and `IntSet` isn't just about performance - it's about expressing relationships between numbers in a purely functional way. The strict annotations (`!`) ensure our dependency maps are evaluated eagerly. ## Performance Through Data Structures Each language brought its own insights about efficient dependency handling. Rust's bidirectional maps made lookups O(1). Elixir's pattern matching compiler optimized our rule checks. Go's sort package provided an efficient implementation of our custom comparator. Haskell's specialized data structures gave us both performance and correctness guarantees. ## Reflections on Dependencies You know what's funny? I spend my days working with package managers and build systems, dealing with complex dependency trees. Yet here I was, getting excited about a printer queue doing essentially the same thing. Each of my languages handled it differently - Rust with its efficient maps, Elixir with elegant pattern matching, Go keeping it pragmatic, and Haskell showing off its pure functional approach. But they all had to solve the same core problem: making sure page 47 prints before page 53, just like making sure libssl installs before nginx. ### Advent of Code 2024: Day 4 - Pattern Matching in Multiple Dimensions URL: https://til.codes/advent-of-code-2024-day-4-pattern-matching-in-multiple-dimensions/ Last updated: 2024-12-07T02:47:52.000Z The elves handed me what looked like a simple word search puzzle today. Find "XMAS", they said. Easy enough - until I discovered I needed to find it in every possible direction, including diagonals and backwards. Then part 2 hit me with X-shaped patterns, and suddenly I was deep in geometric territory. Let me show you how I solved this with my four languages. ## The Challenge: Beyond Simple Word Search The first part asked me to find all occurrences of "XMAS" in any direction - horizontal, vertical, diagonal, and even backwards. But part 2 threw a curveball: find X-shaped patterns where each diagonal contains "MAS" (forwards or backwards). This shift from linear pattern matching to geometric pattern recognition opened up fascinating implementation choices. ## Four Languages, Four Approaches ### Rust: Type Safety Meets Performance In Rust, I started by modeling the directions and positions with zero-cost abstractions: ```rust #[derive(Debug, Clone, Copy, PartialEq, Eq)] struct Position { x: i32, y: i32, } const DIRECTIONS: [Direction; 8] = [ Direction::new(0, 1), // right Direction::new(1, 0), // down Direction::new(1, 1), // down-right Direction::new(1, -1), // down-left // ... other directions ]; ``` The beauty of Rust's implementation lies in its pattern matching for part 2: ```rust fn check_xmas_pattern(&self, pos: Position) -> bool { match ( self.get_diagonal(pos, UP_RIGHT), self.get_diagonal(pos, DOWN_RIGHT), ) { (Some(up), Some(down)) => Self::is_valid_pattern(up) && Self::is_valid_pattern(down), _ => false, } } ``` ### Elixir: Pattern Matching Paradise Elixir's pattern matching made the directional checks particularly elegant: ```elixir @directions [ {0, 1}, # right {1, 0}, # down {1, 1}, # down-right {1, -1}, # down-left {0, -1}, # left {-1, 0}, # up {-1, 1}, # up-right {-1, -1} # up-left ] defp valid_x_pattern?(diag1, diag2) do (is_mas?(diag1) and is_mas?(diag2)) or (is_mas?(diag1) and is_sam?(diag2)) or (is_sam?(diag1) and is_mas?(diag2)) or (is_sam?(diag1) and is_sam?(diag2)) end ``` ### Go: Pragmatic Grid Operations Go's approach shines in its straightforward handling of grid operations: ```go func (g *Grid) getDiagonal(i, j int, d Direction) []byte { result := make([]byte, 3) for step := -1; step <= 1; step++ { x, y := i+d.dx*step, j+d.dy*step result[step+1] = g.data[x][y] } return result } ``` ### Haskell: Pure Pattern Recognition Haskell's solution leverages its type system for clear pattern matching: ```haskell getDiagonal :: Grid -> Position -> Direction -> Maybe String getDiagonal grid (x, y) (dx, dy) = let positions = [(x + dx * i, y + dy * i) | i <- [-1..1]] in if all (inBounds grid) positions then Just [grid !! px !! py | (px, py) <- positions] else Nothing ``` ## Performance Optimization Through Geometry The geometric nature of the patterns led to some interesting optimizations. In part 2, I realized I could skip the grid edges entirely since an X-pattern needs space in all directions: ```rust fn solve_part2(&self) -> usize { (1..self.height.saturating_sub(1)) .flat_map(|x| { (1..self.width.saturating_sub(1)) .map(move |y| Position::new(x as i32, y as i32)) }) .filter(|&pos| self.get_char(pos) == Some('A')) .filter(|&pos| self.check_xmas_pattern(pos)) .count() } ``` ## Lessons in Pattern Recognition This challenge taught me valuable lessons about pattern recognition in grids. The transition from part 1's linear search to part 2's geometric patterns showed how different abstractions can make complex pattern matching more manageable. Rust's type system helped prevent coordinate confusion, Elixir's pattern matching made the logic crystal clear, Go's straightforward approach kept the code readable, and Haskell's pure functions made pattern composition natural. What I love about this problem is how it demonstrates that even seemingly simple pattern matching can reveal elegant geometric properties. Whether through type-safe coordinates, pattern matching, or list comprehensions, each language offered its own insights into handling multi-dimensional patterns. ### Advent of Code 2024: Day 3 - A Tale of State and Style URL: https://til.codes/advent-of-code-2024-day-3-a-tale-of-state-and-style/ Last updated: 2024-12-04T00:50:05.000Z # Its day three of Advent of Code. Today's challenge evolved from a straightforward exercise in multiplication into an elegant dance of state management and instruction parsing. Here I am again, with Rust, Elixir, Haskell, and Go to solve it. ## The Challenge: A State Machine in Disguise What started as a simple task - finding multiplication instructions and summing their products - transformed when part 2 introduced its `do()` and `don't()` instructions. The straightforward calculator evolved into a full-fledged state machine, alternating between enabled and disabled states, deciding when to multiply and when to stay quiet, a very interesting problem.! ### The Plot Twist ``` Simple multiplication: mul(2,3) -> 6 With state control: do() mul(2,3) -> 6 don't() mul(4,5) -> still 6! do() mul(1,2) -> now 8 ``` ## Four Languages, Four Philosophies Let me take you through how each language shaped my thinking about state management. This is where the real insights emerged. ### Rust: The Type System Whisperer ```rust const MUL_PATTERN: &str = r"mul\((\d{1,3}),(\d{1,3})\)"; const CONTROL_PATTERN: &str = r"mul\((\d{1,3}),(\d{1,3})\)|do\(\)|don't\(\)"; lazy_static! { static ref MUL_RE: Regex = Regex::new(MUL_PATTERN).unwrap(); static ref CONTROL_RE: Regex = Regex::new(CONTROL_PATTERN).unwrap(); } #[derive(Debug, Clone, PartialEq)] struct Multiplication { x: i32, y: i32, } #[derive(Debug, Clone, PartialEq)] enum Instruction { Multiply(Multiplication), Enable, Disable, } impl FromStr for Instruction { type Err = Error; fn from_str(s: &str) -> Result { if s == "do()" { Ok(Self::Enable) } else if s == "don't()" { Ok(Self::Disable) } else { Ok(Self::Multiply(s.parse()?)) } } } ``` Rust's type system naturally guided the solution's architecture. The `FromStr` trait implementation provides safe parsing, while the `derive` macros add useful functionality without boilerplate. The use of `lazy_static` for regex compilation shows Rust's attention to performance without sacrificing safety. ### Elixir: Pattern Matching Poetry ```elixir defmodule Solution do @type instruction :: [binary()] @type acc_state :: {integer(), boolean()} def solve(input) do ~r/(mul)\((\d{1,3}),(\d{1,3})\)|do\(\)|don't\(\)/ |> Regex.scan(input) |> process_instructions() end defp process_instruction(["do()"], {acc, _}), do: {acc, true} defp process_instruction(["don't()"], {acc, _}), do: {acc, false} defp process_instruction([_, "mul", x, y], {acc, true}), do: {acc + String.to_integer(x) * String.to_integer(y), true} defp process_instruction([_, "mul", _, _], {acc, false}), do: {acc, false} end ``` In Elixir, each instruction pattern became its own function clause, reading almost like English. The state flows through as a tuple of `{accumulator, enabled_flag}`, transformed immutably with each instruction. Notice how the type specifications (`@type`) make the code self-documenting while the regex pattern ensures precise instruction matching. ### Haskell: Pure and Proud ```haskell {-# LANGUAGE BangPatterns #-} {-# LANGUAGE LambdaCase #-} data Instruction = Multiply !Int !Int | Enable | Disable data State = State { stateSum :: {-# UNPACK #-} !Int , stateEnabled :: !Bool } processInstruction :: State -> Instruction -> State processInstruction !state@(State sum enabled) = \case Enable -> State sum True Disable -> State sum False Multiply x y | enabled -> State (sum + x * y) enabled | otherwise -> state solve :: String -> Int solve = stateSum . foldl' processInstruction initialState . findInstructions ``` Haskell's approach was characterized by its pure functional roots. The design uses language pragmas and strict annotations (`{-# UNPACK #-}`, `!`) for performance while maintaining mathematical elegance. The solution is remarkably concise, using function composition (`.`) to chain operations from parsing to state processing. The `LambdaCase` extension provides clean pattern matching syntax, while `BangPatterns` ensures strict evaluation for better performance. ### Go: Interface-Driven Design ```go type Instruction interface { Execute(state *State) } type State struct { Sum int Enabled bool } type Multiplication struct { X, Y int } func (m Multiplication) Execute(state *State) { if state.Enabled { state.Sum += m.X * m.Y } } type EnableInstruction struct{} func (EnableInstruction) Execute(state *State) { state.Enabled = true } type DisableInstruction struct{} func (DisableInstruction) Execute(state *State) { state.Enabled = false } ``` Go takes an object-oriented approach with interfaces, showing its strength in composing behavior through simple, clear abstractions. Each instruction type implements the `Execute` method, encapsulating its state-modifying behavior. This design demonstrates Go's pragmatic approach to polymorphism, using interfaces to define behavior while keeping the implementation straightforward and predictable. ## Parsing: The Unsung Hero The way each language approached parsing revealed deeper insights about their design philosophies. Rust's combination of `lazy_static` and regex shows how it achieves zero-cost abstractions, the regex patterns are compiled once and reused efficiently. Elixir's approach with a single powerful regex and named captures demonstrates its strength in text processing, while the pattern matching makes the code declarative. Haskell took a more fundamental approach with custom parsers, using its powerful type system and monadic composition to build complex parsers from simple pieces. Go's interface-based design allowed it to separate parsing concerns from execution logic, showing how interfaces can create clean abstractions without sacrificing simplicity. ## Reflections on State What started as a simple exercise in multiplication became a window into how different paradigms approach state and behavior composition. Rust's type system pushed me to think about states and instructions as distinct types, with the compiler ensuring correctness. Elixir showed how pattern matching combined with immutable state transformations can create clear, maintainable code. Haskell demonstrated that even with strict performance annotations (`UNPACK`, `BangPatterns`), we can maintain pure functional elegance. Go's solution was particularly interesting, using interfaces not just for polymorphism but as a way to encapsulate state transitions, showing that object-oriented principles can lead to clean, modular designs. As I continue this Advent of Code journey, these different perspectives are becoming as valuable as the solutions themselves. ### Advent of Code 2024: Day 2 - When Languages Shape Our Thinking URL: https://til.codes/advent-of-code-day-2-when-languages-shape-our-thinking/ Last updated: 2024-12-02T22:21:51.000Z # After yesterday's adventure with sorting columns (and exploring idiomatic solutions), today's challenge presented an intriguing twist. Validating reactor safety reports might sound straightforward, but it quickly reveals its complexity when you factor in all the edge cases and that clever "Problem Dampener" feature introduced in part 2. Here I am again, with my four trusty companions(or four horsemen?) `Elixir`, `Rust`, `Go`, and `Haskell`. But today's story isn't about which language has the prettiest syntax or the most elegant solution. It's about how each language made me think about the problem in completely different ways, and learning some fascinating things about sequence validation along the way! ## The Challenge: Reactor Safety Validation The problem today involves validating reactor safety sequences. A sequence is considered safe if: 1. It's either strictly increasing or strictly decreasing 2. Adjacent numbers must differ by at least 1 but no more than 3 3. The sequence must contain at least 2 numbers Part 2 introduces the "Problem Dampener" - a feature that allows removing exactly one number from the sequence to potentially make it safe. This adds an interesting twist to our validation logic, as we need to consider all possible single-number removals. ### Example: ``` Safe sequence: [1, 3, 6] (strictly increasing, differences: 2, 3) Unsafe sequence: [1, 3, 2, 4] (not strictly increasing/decreasing) Dampened sequence: [1, 3, 2, 4] -> [1, 3, 4] (becomes safe after removing 2) ``` ## The Core Algorithm: Four Ways to Skin a Cat Before we dive into the "Problem Dampener", let's talk about how each language influenced my approach to the basic validation. This is where things get really interesting. In Rust, I found myself thinking in terms of iterators and early returns: ```rust impl Report { fn is_safe(&self) -> bool { if self.levels.len() < 2 { return false; } let mut iter = self.levels.windows(2); let mut increasing = true; let mut decreasing = true; while let Some(&[a, b]) = iter.next() { let diff = b - a; match diff.signum() { -1 => increasing = false, 1 => decreasing = false, _ => return false, // Equal numbers aren't allowed } if diff.abs() < 1 || diff.abs() > 3 { return false; } } increasing || decreasing } } ``` Look at that `windows(2)` iterator - it's Rust's way of saying "here's a clean, zero-cost abstraction for looking at adjacent pairs." No allocations, no fuss, just pure efficiency. And those early returns? They're not just for show - they prevent unnecessary computation the moment we know our sequence is invalid. Elixir, on the other hand, had me thinking in transformations: ```elixir def is_safe?(levels) when length(levels) < 2, do: false def is_safe?(levels) do differences = levels |> Enum.zip(tl(levels)) |> Enum.map(fn {a, b} -> b - a end) valid_diffs? = Enum.all?(differences, &(&1 |> abs() |> Kernel.in(1..3))) increasing? = Enum.all?(differences, &(&1 > 0)) decreasing? = Enum.all?(differences, &(&1 < 0)) valid_diffs? and (increasing? or decreasing?) end ``` See how different this feels? Instead of checking conditions as we go, we're transforming the data in discrete steps: first create pairs, then compute differences, then check our conditions. It's more declarative - we're saying what we want, not how to do it. ### Go: Pragmatic Simplicity ```go type Report struct { levels []int } func (r Report) isSafe() bool { if len(r.levels) < 2 { return false } increasing, decreasing := true, true for i := 1; i < len(r.levels); i++ { diff := r.levels[i] - r.levels[i-1] absDiff := diff if diff < 0 { absDiff = -diff increasing = false } else { decreasing = false } if absDiff < 1 || absDiff > 3 { return false } } return increasing || decreasing } // Part 2: Problem Dampener func (r Report) canBeSafe() bool { if r.isSafe() { return true } for i := range r.levels { dampened := make([]int, 0, len(r.levels)-1) dampened = append(dampened, r.levels[:i]...) dampened = append(dampened, r.levels[i+1:]...) if Report{levels: dampened}.isSafe() { return true } } return false } ``` ### Haskell: Pure Functional Elegance ```haskell module Report where import Data.List (tails) isSafe :: [Int] -> Bool isSafe xs | length xs < 2 = False | otherwise = validDiffs && (increasing || decreasing) where pairs = zip xs (tail xs) diffs = map (uncurry subtract) pairs validDiffs = all (\d -> abs d >= 1 && abs d <= 3) diffs increasing = all (> 0) diffs decreasing = all (< 0) diffs -- Part 2: Problem Dampener canBeSafe :: [Int] -> Bool canBeSafe xs = isSafe xs || any isSafe (removals xs) where removals xs = [take i xs ++ drop (i + 1) xs | i <- [0..length xs - 1]] ``` ## Implementation Deep Dive Each language's implementation reveals its unique strengths: ### Rust: Zero-Cost Abstractions ```rust impl Report { fn is_safe(&self) -> bool { if self.levels.len() < 2 { return false; } let diffs: Vec<_> = self.levels.windows(2) .map(|w| w[1] - w[0]) .collect(); let valid_diffs = diffs.iter() .all(|&d| (1..=3).contains(&d.abs())); let monotonic = diffs.iter() .all(|&d| d > 0) || diffs.iter().all(|&d| d < 0); valid_diffs && monotonic } // Part 2: Problem Dampener fn can_be_safe(&self) -> bool { if self.is_safe() { return true; } // Try removing each number and check if sequence becomes safe (0..self.levels.len()).any(|i| { let mut dampened = self.levels.clone(); dampened.remove(i); Report { levels: dampened }.is_safe() }) } } ``` ### Elixir: Elegant Transformations ```elixir defmodule Report do def is_safe?(levels) when length(levels) < 2, do: false def is_safe?(levels) do differences = levels |> Enum.zip(tl(levels)) |> Enum.map(fn {a, b} -> b - a end) valid_diffs? = Enum.all?(differences, &(&1 |> abs() |> Kernel.in(1..3))) monotonic? = Enum.all?(differences, &(&1 > 0)) or Enum.all?(differences, &(&1 < 0)) valid_diffs? and monotonic? end # Part 2: Problem Dampener def can_be_safe?(levels) do is_safe?(levels) or 0..(length(levels) - 1) |> Enum.any?(fn i -> levels |> List.delete_at(i) |> is_safe?() end) end end ``` ## Error Handling: The Art of Failing Gracefully Error handling is the unsung hero of programming. It's easy to overlook, but each language has its own way of making us think about errors. Let's take a look. Rust's type system is like a safety net, catching errors before they even happen: ```rust #[derive(Debug, thiserror::Error)] enum Error { #[error("failed to parse number: {0}")] Parse(String), #[error("invalid sequence length")] InvalidLength, #[error("invalid difference between numbers")] InvalidDifference, } impl FromStr for Report { type Err = Error; fn from_str(s: &str) -> Result { let levels: Result, _> = s .split_whitespace() .map(str::parse::) .collect(); levels .map(|l| Report { levels: l }) .map_err(|e| Error::Parse(e.to_string())) .and_then(|r| { if r.levels.len() < 2 { Err(Error::InvalidLength) } else { Ok(r) } }) } } ``` Every possible error is accounted for, and the compiler ensures we don't forget any. It's like having a personal error handler, always watching our backs. Elixir's approach is more... conversational. We use the `with` expression to tell a story of how things should go, and handle errors as they arise: ```elixir def parse(input) when is_binary(input) do with {:ok, numbers} <- split_and_parse(input), {:ok, report} <- validate_length(numbers), {:ok, _} <- validate_differences(numbers) do {:ok, numbers} else {:error, :invalid_format} -> {:error, "Input must be space-separated numbers"} {:error, :invalid_length} -> {:error, "Sequence must have at least 2 numbers"} {:error, :invalid_difference} -> {:error, "Adjacent numbers must differ by 1-3"} end end ``` It's not just about handling errors; it's about expressing the happy path clearly and handling errors separately. We're telling a story of how things should go, and what to do when they don't. ## Language Features and Philosophies ### Elixir: Functional and Declarative Elixir encourages thinking in terms of data transformations and pipelines. Its functional nature shines through in how problems are broken down into small, composable functions: ```elixir def is_safe?(levels) when length(levels) < 2, do: false def is_safe?(levels) do differences = levels |> Enum.zip(tl(levels)) |> Enum.map(fn {a, b} -> b - a end) valid_diffs? = Enum.all?(differences, &(&1 |> abs() |> Kernel.in(1..3))) increasing? = Enum.all?(differences, &(&1 > 0)) decreasing? = Enum.all?(differences, &(&1 < 0)) valid_diffs? and (increasing? or decreasing?) end ``` The use of pipelines makes the code readable and expressive, focusing on the "what" rather than the "how." ### Go: Simple and Efficient Go's solution is straightforward, emphasizing simplicity and performance. It uses imperative loops and state management through boolean flags: ```go func (r Report) isSafe() bool { var increasing, decreasing bool = true, true prev := r.levels[0] for _, curr := range r.levels[1:] { diff := curr - prev if diff <= 0 { increasing = false } if diff >= 0 { decreasing = false } if absDiff := abs(diff); absDiff < 1 || absDiff > 3 { return false } prev = curr } return increasing || decreasing } ``` Go's pragmatism is evident in its focus on clear, efficient solutions. ### Haskell: Elegant and Concise Haskell leverages its functional nature to provide a concise and elegant solution. It uses high-level functions and list operations: ```haskell isSafe :: Report -> Bool isSafe (Report xs) | length xs < 2 = False | otherwise = (all (> 0) diffs || all (< 0) diffs) && all isValidDiff diffs where diffs = differences xs ``` Haskell's declarative style allows us to express the solution almost as a direct translation of the problem statement. ### Rust: Safe and Performant Rust combines safety and performance with its strong type system and ownership model. It uses iterators and window operations to achieve a clean solution: ```rust impl Report { fn is_safe(&self) -> bool { if self.levels.len() < 2 { return false; } let diffs: Vec<_> = self.levels.windows(2) .map(|w| w[1] - w[0]) .collect(); (diffs.iter().all(|&x| x > 0) || diffs.iter().all(|&x| x < 0)) && diffs.iter().all(|&x| (1..=3).contains(&x.abs())) } } ``` Rust's zero-cost abstractions and compile-time checks ensure both efficiency and safety. ## Performance and Implementation Analysis Each language's approach to data structures and algorithms reveals interesting trade-offs between clarity, performance, and memory usage. ### Data Structures and Memory Management #### Rust: Zero-Cost Abstractions ```rust impl Report { fn is_safe(&self) -> bool { if self.levels.len() < 2 { return false; } let diffs: Vec<_> = self.levels.windows(2) .map(|w| w[1] - w[0]) .collect(); (diffs.iter().all(|&x| x > 0) || diffs.iter().all(|&x| x < 0)) && diffs.iter().all(|&x| (1..=3).contains(&x.abs())) } } ``` Rust's implementation showcases its strength in memory management. The core of our data lives in a `Vec`, giving us the performance of contiguous memory. When we need to process pairs of numbers, the `windows()` iterator steps in, providing a view into our data without copying. While we do allocate a new vector for differences, it's a single, controlled allocation that Rust's ownership system will clean up the moment we're done with it. The processing happens in stack-efficient chunks, making the most of CPU cache lines. #### Go: Pragmatic Efficiency ```go func (r Report) isSafe() bool { var increasing, decreasing bool = true, true prev := r.levels[0] for _, curr := range r.levels[1:] { diff := curr - prev if diff <= 0 { increasing = false } if diff >= 0 { decreasing = false } if absDiff := abs(diff); absDiff < 1 || absDiff > 3 { return false } prev = curr } return increasing || decreasing } ``` Go's approach is straightforward. It works directly with a slice, which is just a window into an array with a length and capacity. There's no allocation during processing – we just keep track of our state with two boolean flags and a previous value. The slice header sits on the stack while the backing array lives on the heap, but Go's garbage collector handles this seamlessly. The early returns aren't just for readability; they help us avoid unnecessary computation the moment we know our answer. #### Elixir: Immutable Transformations ```elixir def is_safe?(levels) do differences = levels |> Enum.zip(tl(levels)) |> Enum.map(fn {a, b} -> b - a end) valid_diffs? = Enum.all?(differences, &(&1 |> abs() |> Kernel.in(1..3))) increasing? = Enum.all?(differences, &(&1 > 0)) decreasing? = Enum.all?(differences, &(&1 < 0)) valid_diffs? and (increasing? or decreasing?) end ``` Elixir embraces immutability, and it shows in how we handle data. Each transformation creates a new list, but this isn't as wasteful as it might sound. The BEAM VM (Erlang's virtual machine) is highly optimized for this kind of work. Pattern matching makes our code expressive, and the pipeline operator turns our solution into a clear sequence of transformations. While we make multiple passes over the data, the clarity of the code makes it a worthwhile trade-off. #### Haskell: Elegant Laziness ```haskell isSafe :: Report -> Bool isSafe (Report xs) | length xs < 2 = False | otherwise = (all (> 0) diffs || all (< 0) diffs) && all isValidDiff diffs where diffs = differences xs ``` Haskell's lazy evaluation transforms how we think about data processing. What looks like multiple list operations often compiles down to a single pass through the data. The `differences` function doesn't eagerly create a new list; it describes a computation that will happen only when needed. GHC's optimization passes, particularly stream fusion, often eliminate intermediate lists entirely. It's a beautiful example of how Haskell lets us write clear, mathematical solutions while the compiler handles the heavy lifting of making it efficient. ### Performance Characteristics Let's talk about how these different approaches play out in practice. It's a fascinating study in trade-offs and priorities. When it comes to memory usage, each language takes its own path. Rust gives us precise control – we know exactly when and where we're allocating memory for our differences vector. Go keeps things minimal, using only the memory it absolutely needs during processing. Elixir creates multiple immutable lists, but the BEAM VM is built to handle this efficiently. Haskell's lazy evaluation means we might not need those intermediate lists at all – they often exist only in the abstract, optimized away by GHC's fusion rules. Time complexity tells an interesting story too. While all our implementations are technically O(n), they get there differently. Go and Rust take the direct route with a single pass and early returns when possible. Elixir makes multiple passes, but gains clarity and maintainability in return. Haskell's approach looks like multiple passes on paper, but lazy evaluation and fusion often collapse these into a single efficient traversal. The way each language handles memory allocation reflects its broader philosophy. Rust gives us a single, controlled allocation that we can reason about precisely. Go avoids allocations during processing entirely, keeping things predictable. Elixir creates multiple immutable structures, prioritizing code clarity over memory efficiency. Haskell's lazy evaluation means allocations often disappear entirely after optimization. When it comes to optimization opportunities, each language plays to its strengths. Rust leverages powerful optimization pipeline and zero-cost abstractions. Go's simplicity makes it easy for the compiler to perform escape analysis and function inlining. Elixir's pattern matching and pipeline operators give the BEAM VM clear optimization boundaries. Haskell's purity and lazy evaluation enable aggressive optimizations like fusion and deforestation. ### Trade-offs and Considerations What fascinates me most is how each language's philosophy guides us toward different implementation choices, even for this seemingly straightforward problem. Take Rust, for instance. Its obsession with zero-cost abstractions isn't just a technical detail – it's a mindset. When writing the Rust solution, I found myself naturally thinking about memory control and efficiency. The `windows()` iterator isn't just convenient; it's a perfect example of Rust's promise that abstractions shouldn't cost us at runtime. You get the ergonomics of high-level iterators with the performance of manual pointer manipulation. Go takes a different stance. Its implementation might look almost too simple at first glance, but that's exactly the point. By using a straightforward slice and a single pass through the data, it achieves something remarkable: code that's both easy to understand and performant. There's no clever optimization here, just clean, predictable performance that's easy to reason about. Elixir's approach tells yet another story. When I wrote the Elixir solution, I wasn't thinking about memory allocations or iteration counts. Instead, I was focused on transforming data through clear, composable steps. Yes, this creates multiple intermediate lists, but it gives us something valuable in return: code that reads like a specification. The pipeline operator (`|>`) turns our solution into a story of data transformation. And then there's Haskell, quietly performing its lazy evaluation magic. At first glance, its solution might look inefficient – multiple passes over the list? But thanks to GHC's optimization capabilities, particularly fusion rules, those intermediate lists often disappear entirely. It's a beautiful example of how Haskell lets us write clear, mathematical solutions while the compiler handles the heavy lifting of making it efficient. ## Conclusion: Beyond the Problem What started as a simple sequence validation problem turned into an exploration of how different programming paradigms influence our problem-solving approaches. The "Problem Dampener" feature, in particular, revealed how each language's strengths can lead to distinct yet equally valid solutions. The most valuable lesson wasn't about finding the "best" solution, but understanding how different tools shape our thinking and approach to problem-solving. Each language offered unique insights that could be applied across the entire spectrum of programming challenges. > All code examples are available in my [Advent of Code 2024 repository](https://github.com/manusajith/adventofcode-2024/tree/main/day-02?ref=til.codes), including complete implementations, tests, and benchmarks. Happy coding, and see you tomorrow for Day 3! ### Advent of Code Day 1: A Deep Dive into Language Characteristics URL: https://til.codes/advent-of-code-day-1-a-deep-dive-into-language-characteristics/ Last updated: 2024-12-01T21:01:58.000Z # Every year when December rolls around, I tell myself "This year, I'm going to solve Advent of Code!" And every year, I end up sticking to my comfort zone (hello, procrastination, my old friend). But 2024 feels different. Instead of just picking one new language, I thought "Why not go all in?" So here I am, tackling the problem in four languages I've been working with over the past few years: Elixir, Rust, Go, and Haskell. A quick disclaimer though - I'm not here to showcase the most performant implementations or find the most elegant solutions. Instead, this is a learning journey about how different languages influence our problem-solving approach. It's fascinating to see how each language's philosophy and features guide us toward different solutions, even for the same simple problem. Let's dive into Day 1 and see how these languages make us approach problem-solving in their own unique ways. ## The Problem at Hand The challenge seems simple at first: we're given pairs of numbers, one pair per line. We need to sort each column of numbers independently and then sum up the absolute differences between corresponding pairs. For example, given: ``` 3 4 4 3 2 5 ``` We first sort each column to get `[2,3,4]` and `[3,4,5]`, then sum their differences: `|2-3| + |3-4| + |4-5| = 3`. Simple enough on paper, but as we'll see, each language brings its own perspective on how to handle this data transformation. ## Elixir: Where Pattern Matching Shines Let's start with Elixir. Pattern matching is often described as one of Elixir's awesome features, but its real power shows up in unexpected ways when we're handling data transformations: ```elixir defmodule Solution do def parse(input) do input |> String.trim() |> String.split("\n") |> Enum.map(&parse_line/1) |> Enum.unzip() end defp parse_line(line) do line |> String.split() |> Enum.map(&String.to_integer/1) |> then(fn [a, b] -> {a, b} end) end def solve({left, right}) do [left, right] |> Enum.map(&Enum.sort/1) |> then(&calculate_distance/1) end defp calculate_distance([sorted_left, sorted_right]) do Enum.zip_with(sorted_left, sorted_right, &abs(&1 - &2)) |> Enum.sum() end end ``` The beauty of Elixir's approach reveals itself in the details. Take the pattern matching in `parse_line/1` \- it's not just syntactic sugar for destructuring data. It's a declarative way of saying "this function only makes sense for pairs of numbers." If someone later modifies the input file to have three numbers per line, they'll get an immediate, clear failure message rather than silent incorrect behavior. The `then/1` function showcases another subtle but powerful aspect of Elixir's design. In many functional languages, you'd need to break your pipeline when the data doesn't fit perfectly. Before Elixir 1.12, we'd write something like: ```elixir |> (fn data -> calculate_distance(data) end).() ``` Now with `then/1`, the code reads like natural transformation steps, making it easier to understand the data flow at a glance. ## Rust: When Safety Meets Performance Rust's implementation reveals something fascinating about the relationship between ownership and optimization: ```rust use std::error::Error; use std::fs::File; use std::io::{BufRead, BufReader}; #[derive(Debug)] struct Lists { left: Vec, right: Vec, } impl Lists { fn from_file(path: &str) -> Result> { let file = File::open(path)?; let reader = BufReader::new(file); let mut left = Vec::new(); let mut right = Vec::new(); for line in reader.lines() { let line = line?; let nums: Vec = line .split_whitespace() .map(str::parse) .collect::>()?; if nums.len() != 2 { return Err("Each line must contain exactly two numbers".into()); } left.push(nums[0]); right.push(nums[1]); } Ok(Lists { left, right }) } fn solve(mut self) -> i32 { self.left.sort_unstable(); self.right.sort_unstable(); self.left.iter() .zip(self.right.iter()) .map(|(a, b)| (a - b).abs()) .sum() } } ``` Look at the `solve` method's signature. By taking `self` by value instead of reference, we're not just moving data around - we're enabling optimizations. Since Rust knows we own the data and won't need the original order again, it can use `sort_unstable()`, which is significantly faster than stable sorting but would be risky to use if other code might depend on the original order. The error handling through the `?` operator demonstrates Rust's philosophy of "zero-cost abstractions." While it looks like simple syntax sugar, it compiles down to the same efficient code you'd write by hand with explicit error checking. You get the ergonomics of exceptions with the performance of manual error handling. ## Go: Simplicity as a Feature Go takes a different approach that initially might seem verbose: ```go package main import ( "bufio" "fmt" "os" "sort" "strconv" "strings" ) type Lists struct { Left []int Right []int } func readLists(path string) (Lists, error) { file, err := os.Open(path) if err != nil { return Lists{}, fmt.Errorf("opening file: %w", err) } defer file.Close() var lists Lists scanner := bufio.NewScanner(file) for scanner.Scan() { nums := strings.Fields(scanner.Text()) if len(nums) != 2 { return Lists{}, fmt.Errorf("expected 2 numbers, got %d", len(nums)) } left, err := strconv.Atoi(nums[0]) if err != nil { return Lists{}, fmt.Errorf("parsing left number: %w", err) } right, err := strconv.Atoi(nums[1]) if err != nil { return Lists{}, fmt.Errorf("parsing right number: %w", err) } lists.Left = append(lists.Left, left) lists.Right = append(lists.Right, right) } return lists, scanner.Err() } func solve(lists Lists) int { left := make([]int, len(lists.Left)) right := make([]int, len(lists.Right)) copy(left, lists.Left) copy(right, lists.Right) sort.Ints(left) sort.Ints(right) total := 0 for i := range left { total += abs(left[i] - right[i]) } return total } func abs(x int) int { if x < 0 { return -x } return x } ``` The explicit error handling in Go is often criticized as repetitive, but it serves a crucial purpose in large codebases. Every error check is a decision point where you're forced to think about what could go wrong. This becomes invaluable when you're maintaining code months later and need to understand all the possible failure modes. Go's implementation also shows the value of predictability over cleverness. We could use interfaces or generics to make the code more "elegant," but Go pushes us toward simple, straightforward solutions that any team member can understand at a glance. It's a reminder that code is read far more often than it's written. ## Haskell: Types as Documentation Haskell brings a unique perspective where the type system becomes a powerful documentation tool: ```haskell {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE OverloadedStrings #-} import Data.List (sort) import qualified Data.Text as T import qualified Data.Text.IO as TIO data Lists = Lists { leftList :: [Int] , rightList :: [Int] } deriving Show parseLine :: T.Text -> Either String (Int, Int) parseLine line = case map readInt $ T.words line of [Right x, Right y] -> Right (x, y) _ -> Left "Each line must contain exactly two numbers" where readInt = either Left Right . T.decimal parseFile :: FilePath -> IO (Either String Lists) parseFile path = do content <- TIO.readFile path return $ do pairs <- traverse parseLine $ T.lines content let (lefts, rights) = unzip pairs return Lists { leftList = lefts, rightList = rights } solve :: Lists -> Int solve Lists{..} = sum $ zipWith distance (sort leftList) (sort rightList) where distance x y = abs (x - y) ``` The type signatures in Haskell tell a complete story. When you see: ```haskell parseLine :: T.Text -> Either String (Int, Int) ``` You immediately know three things: 1. This function might fail (`Either`) 2. If it fails, you'll get a `String` explaining why 3. If it succeeds, you'll get exactly two integers The use of `traverse` here isn't just about handling errors - it's about composing effects. In other languages, we'd write nested loops or use intermediate collections to handle the possibility of failure at each step. Haskell lets us express this as a single transformation: "try to parse each line, collecting all successes or stopping at the first failure." The type system ensures we can't accidentally ignore a failure case. ## Closing Thoughts What makes this comparison fascinating isn't just how each language solves the problem, but how each solution reveals the core values of its language: - Elixir shows us how pattern matching and data transformation can make code both safe and readable - Rust demonstrates how we can write high-level abstractions without sacrificing performance - Go reminds us that sometimes the simplest solution is the best solution - Haskell shows us how a powerful type system can make code self-documenting These aren't just different ways to solve the same problem - they're different ways of thinking about programming itself. And that's what makes polyglot programming so valuable - it expands our mental models and makes us better programmers, regardless of which language we're using. ### My First Adventure with Astro: A Journey from Next.js URL: https://til.codes/my-first-adventure-with-astro-a-journey-from-next-js/ Last updated: 2024-12-01T18:14:31.000Z # My First Adventure with Astro: A Journey from Next.js I recently took a dive into Astro, it has been on my radar for a while now. Coming from a Next.js background, I was excited to try something new, especially for a single page static site. Next.js is awesome, but it's a bit overkill for a simple static site. I wanted something that was simple to set up, fast to deploy, and easy to maintain. Of course, I could still go the old-fashioned way of just writing things in HTML and CSS, but at the same time this was a good opportunity to learn something new. So why not? ## Why Astro? Astro's approach to JavaScript is what initially caught my attention. We often throw JavaScript at every problem, whereas Astro takes a different stance - "Zero JavaScript by default." What does this mean in practice? Well, imagine you're building a single page landing page. Most of your content is static - it doesn't need interactivity. With Next.js, you'd still be shipping a JavaScript bundle to handle routing and hydration, even for completely static pages. Astro, on the other hand, only ships HTML and CSS by default. But don't get confused with this approach - Astro isn't anti-JavaScript. It's just smarter about it. If we need an interactive contact form or a dynamic chart? Astro lets you add JavaScript components exactly where you need them using "islands architecture." Each interactive component is isolated, meaning the rest of your page stays lean and fast. This selective hydration approach means: - Your initial page loads are blazing fast - Your server costs might be lower (less bandwidth used) - Better performance on mobile devices (less JS to parse and execute) - Improved SEO (search engines love fast, lightweight pages) ### Next.js vs Astro: My Take Let's talk about how Astro compares to Next.js from my experience. Having used Next.js extensively, switching to Astro was an interesting journey. The first thing you'll notice is the different philosophy. Next.js is built around JavaScript - it's in its DNA. You get all the React goodness, client-side routing, and a ton of built-in features. It's like having a Swiss Army knife for web development. Great for complex applications, but sometimes it feels like bringing a cannon to a knife fight. Astro, on the other hand, feels more like traditional web development on steroids. It's refreshingly simple. You write your components in `.astro` files, which took me a hot minute to get used to, but then it clicked. You can still use React components when you need them. It's not about choosing one or the other - it's about using the right tool for the job. When it comes to development experience, both have their strengths. Next.js has this incredible hot reloading and TypeScript integration that just works. The ecosystem is massive, and you can find a solution for pretty much anything. But Astro? It's simpler. Less boilerplate, faster builds, and you're not locked into React. You can use Vue, Svelte, or anything else you want. Performance is where things get interesting. Next.js does a great job optimizing your JavaScript, but you're still shipping the React runtime to your users. For a complex web app, that's fine - you need that interactivity. But for a simple landing page or a blog? That's where Astro shines. Your pages load instantly because they're just HTML and CSS. No JavaScript runtime, no hydration, no nothing - unless you explicitly ask for it. The learning curve coming from Next.js is surprisingly gentle. Yes, you'll need to shift your mindset a bit. You'll start thinking more about what actually needs JavaScript and what doesn't. But in a way, it feels like going back to basics - in a good way. ## Setting Up with devenv/nix Since I use devenv/nix for my development environment, setting up an Astro project was an easy process. The devenv/nix setup is simple and minimal: ```nix { pkgs, ... }: { packages = with pkgs; [ nodejs_20 nodePackages.pnpm nodePackages.wrangler ]; scripts = { dev.exec = "pnpm dev"; build.exec = "pnpm build"; preview.exec = "pnpm preview"; }; } ``` The same setup works on MacOS, Linux, local env or server. Just need to run these two commands: ```bash direnv allow devenv up ``` ![Works on my machine](https://i.imgur.com/3eTKEZp.jpg) No more "it works on my machine" problems! ## Tailwind CSS Integration Adding Tailwind to Astro was surprisingly simple. Just install the integration: ```bash pnpm astro add tailwind ``` You get all the Tailwind goodness with zero config. The integration with Astro's templating feels natural, and the CSS output is optimized automatically. ## Cloudflare Pages and Workers ### Deployment to Cloudflare Pages Deploying to Cloudflare Pages was pretty easy as well. The `astro.config.mjs` setup is minimal: ```javascript export default defineConfig({ output: "server", adapter: cloudflare(), integrations: [tailwind()] }); ``` ### Setting Up Email with Cloudflare Workers I've been using Cloudflare Workers for a while now, so integrating it with Astro was pretty straightforward. Workers are pretty good for handling any server-side logic - they're fast, reliable, and the free tier covers most small projects easily. I wanted to test out how Astro handles dynamic features, so I decided to add a contact form to the site. It's a good way to see how Astro works with external services and client-side interactions. Since I already had Workers set up for the API endpoints, adding the email functionality was simple enough. Workers let you handle both the API endpoints and the email sending in one place, which keeps things clean and simple. ## A Few Gotchas Along the Way The transition to Astro wasn't all smooth sailing. Partial hydration is probably the biggest mental shift - you need to explicitly tell Astro which components need JavaScript. At first, I kept forgetting to add the `client:load` directive to my interactive components and wondering why they weren't working. But once you get into the habit, it actually makes you think more carefully about where you really need that JavaScript. ## Understanding Astro's Islands One last note about Astro's most interesting features - the island architecture. Think of your webpage as a static ocean of HTML, and your interactive components as islands of JavaScript. Each island is independent, which means you can have a React island here, a Vue island there, and the rest of your page stays static and fast. In practice, this means you can do something like this: your navigation menu could be a React component (because you need that smooth mobile menu animation), your contact form could be a Vue component (because that's what your team prefers for forms), and everything else - your blog posts, your about page, your footer - can be just plain HTML. No JavaScript overhead, no framework tax. The way Astro handles this is pretty clever. You just add a directive like `client:load` or `client:visible` to your component, and Astro takes care of loading the JavaScript only when it's needed. Want that heavy chart component to load only when it's scrolled into view? Just use `client:visible`. Need that contact form to be interactive immediately? Use `client:load`. It's like having a dimmer switch for JavaScript instead of just an on/off button. ## Final Thoughts Would I use Astro again? Absolutely! The combination of Astro + Tailwind + Cloudflare feels like a winning stack for static sites, especially for landing pages. While Next.js is still great for complex applications, Astro hits a sweet spot for content-focused sites. ### Writing Rust NIF code to convert encryption code from Elixir to Rust URL: https://til.codes/converting-encryption-code-from-elixir-to-rust-using-nif/ Last updated: 2022-12-27T05:29:55.000Z In this article, we will go through what a NIF is, how to write safe NIF code using Rust and Rustler ## What's a NIF? A NIF of Native Implemented Function, is a function usually implemented in C, which can be called from Elixir. NIFs are usually used to run a small piece of native code for faster performance. ## Writing a NIF using Rust and Rustler From [Rustler's](https://github.com/rusterlium/rustler?ref=til.codes) documentation: > Rustler is a library for writing Erlang NIFs in safe Rust code. That means there should be no ways to crash the BEAM (Erlang VM). The library provides facilities for generating the boilerplate for interacting with the BEAM, handles encoding and decoding of Erlang terms, and catches rust panics before they unwind into C. One of the big caveats of using NIF in an Elixir code base, is that it could potentially bring down the entire BEAM VM, if the NIF code panic. Thus writing NIF code in Rust gives the advantage that Rust code can catch any panic before. ## Getting started with Rustler The first step is adding `rustler` as a dependency to the project. ```elixir {:rustler, "~> 0.26.0"} ``` and running `mix deps.get` Once the dependencies are installed, a new rustler project can be created by running: ```elixir mix rustler.new ``` ## Writing your first NIF The following code, takes an encrypted string, decrypts it using AES-256-GCM mode. ```elixir defmodule Encryption do @moduledoc """ Decrypts a given encrypted string using AES-256-GCM mode decryption. """ @key_size 32 @iv_size 16 @mode :aes_256_gcm @tag_length 16 @doc """ Decrypts a given encrypted string using AES-256-GCM mode decryption. """ @spec decrypt(binary) :: binary def decrypt(content) do data_bin = Base.url_decode64!(content) size = byte_size(data_bin) data_size = size - @key_size - 2 <<_version::binary-size(2), data::binary-size(data_size), key::binary-size(@key_size)>> = data_bin <> = payload :crypto.crypto_one_time_aead(@mode, key, iv, cipher, aad, tag, false) end end ``` Now, lets take the above code and convert that to a NIF using Rust. Let's start with writing an elixir module ```elixir defmodule NIFEncryption do @moduledoc """ NIF module decrypting data. """ use Rustler, otp_app: :otp_app_name, crate: "encryption" @doc """ Decodes an encrypted token. """ @spec decrypt(binary()) :: {:ok, binary()} | {:error, binary()} def decrypt(_token), do: error() defp error, do: :erlang.nif_error(:nif_not_loaded) end ``` The code will allow us to utilise the Rust crate `encryption` Now, lets write the Rust code to implement our `decrypt` function. ```rust // file: native/src/encryption/lib.rs use openssl::symm::Cipher; const KEY_SIZE: usize = 32; const IV_SIZE: usize = 16; const TAG_LENGTH: usize = 16; #[rustler::nif] pub fn decrypt(token: &str) -> String { let data_bin: Vec = base64_url::decode(token).unwrap(); let size: usize = data_bin.len(); let (iv, tag_and_aad): (&[u8], &[u8]) = data.split_at(IV_SIZE); let (tag, aad_and_cipher): (&[u8], &[u8]) = tag_and_aad.split_at(TAG_LENGTH); let (aad, cipher): (&[u8], &[u8]) = aad_and_cipher.split_at(IV_SIZE); let content: Vec = openssl::symm::decrypt_aead(Cipher::aes_256_gcm(), key, Some(iv), aad, cipher, tag) .unwrap(); return content.iter().map(|e| *e as char).collect::(); } rustler::init!("Elixir.Encryption", [decode]); ``` the `[rustler::nif]` macro exposes the `decrypt` function as a nif, and we initialize the NIF using `rustler::init!` macro which exports the function as `Elixir.Encryption.decrypt/1` ## Benchmarks While its cool to port over the Elixir code to Rust code, its also important to see the performance comparison of the Elixir implementation over the Rust implementation. Most often synthetic benchmarks does not bring in a lot of value add to the code, but it gives good insights into some of the stats. Running a synthetic benchmark using `benchee` gives the following results. #### TL;DR version Elixir implementation is - `9.51x slower` - `uses 2.05x more memory` ```markdown Operating System: macOS CPU Information: Apple M1 Max Number of Available Cores: 10 Available memory: 64 GB Elixir 1.14.2 Erlang 25.2 Benchmark suite executing with the following configuration: warmup: 2 s time: 10 s memory time: 2 s reduction time: 0 ns parallel: 10 inputs: none specified Estimated total run time: 28 s Benchmarking Elixir ... Benchmarking Rust ... Name ips average deviation median 99th % Rust 22.77 K 43.93 μs ±223.31% 35.33 μs 173.80 μs Elixir 2.39 K 417.70 μs ±90.41% 327.21 μs 2004.40 μs Comparison: Rust 22.77 K Elixir 2.39 K - 9.51x slower +373.78 μs Memory usage statistics: Name Memory usage Rust 0.59 KB Elixir 1.20 KB - 2.05x memory usage +0.62 KB ``` ## Closing thoughts While NIFs are cool and often gives better performance, there are caveats to consider. - A panic in NIF can bring down the entire BEAM VM. - Learning curve to implement native code in C or Rust. - Maintenance overhead of adding another language to the stack. - NIF is generically recommended to be used for short running operations - usually under 1s. If the operation takes more time, look into dirty schedulers. Hope this helps someone. ### Add latency tracking to phoenix live view apps. URL: https://til.codes/add-latency-tracking-to-phoenix-live-view-apps/ Last updated: 2022-10-16T07:50:25.000Z Adding latency tracker to a phoenix application provides some valuable insights for UX. Latency tracking can be added to any phoenix live view application using a few lines of codes as below: Adding the hook ```javascript // assets/js/app.js Ping: { mounted() { this.timer = setInterval(() => { let beforeTime = (new Date().getTime()) this.pushEvent("ping", {}, resp => { let rtt = (new Date().getTime()) - beforeTime this.el.innerText = `Ping: ${rtt}ms` }) }, 1000) }, destroyed() { clearInterval(this.timer) } } ``` Followed by adding the `handle_event/3` to the liveview ```elixir def handle_event("ping", _, socket) do {:reply, %{}, socket} end ``` And finally, adding a few lines of html to render the latency info on the template. ```html # templates/layout/live.html.heex
``` Copied from LiveBeats app Reference: [https://github.com/fly-apps/live\_beats/commit/65f307b1fae3c41879a5ef69ee51ea53a968e645](https://github.com/fly-apps/live%5Fbeats/commit/65f307b1fae3c41879a5ef69ee51ea53a968e645?ref=til.codes) ### Running scheduled cron jobs in Rust using tokio URL: https://til.codes/running-scheduled-cron-jobs-in-rust/ Last updated: 2022-10-16T07:55:12.000Z One common use case when writing backend services is to have the feature to perform some task on a periodic/scheduled manner. The following code can be used to perform some task on a period manner, using `tokio` and `async/await` ```rust let mut interval = time::interval(std::time::Duration::from_secs(60)); loop { interval.tick().await; tokio::spawn(async { perform_task().await; }); } ``` ### Making periodic http requests in Rust URL: https://til.codes/making-periodic-http-requests-in-rust/ Last updated: 2022-10-15T07:59:30.000Z In this post, we will look into how we can write. a a simple program to make periodic http requests using `request` in Rust. Lets start with creating a client using `request` ```rust let client = reqwest::Client::builder() ``` Now, lets add basic http headers and some cookies, to the client, ```rust let user_agent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36"; let cookies = format!("{}", std::env::var("COOKIES")?); let mut headers = header::HeaderMap::new(); headers.insert("accept", header::HeaderValue::from_static("*/*")); headers.insert("cookie", header::HeaderValue::from_str(&cookies).unwrap()); let client = reqwest::Client::builder() .user_agent(user_agent) .default_headers(headers) ``` And then add our http proxy to the client with: ```rust let proxy_url = format!("{}", std::env::var("PROXY_URL")?); let http_proxy = reqwest::Proxy::http(&proxy_url)?; let https_proxy = reqwest::Proxy::https(&proxy_url)?; let client = reqwest::Client::builder() .user_agent(user_agent) .default_headers(headers) .danger_accept_invalid_certs(true) .gzip(true) .proxy(http_proxy) .proxy(https_proxy) .connect_timeout(std::time::Duration::from_secs(60)) .timeout(std::time::Duration::from_secs(600)) .build() .unwrap(); ``` Now let's wrap everything together with ```rust use dotenv::dotenv; use reqwest::header; use reqwest::Client; use tokio::time; #[tokio::main] async fn main() -> Result<(), Box> { dotenv().ok(); let proxy_url = format!("{}", std::env::var("PROXY_URL")?); let cookies = format!("{}", std::env::var("COOKIES")?); let ipify_url = format!("https://api.ipify.org?format=json"); let user_agent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36"; let http_proxy = reqwest::Proxy::http(&proxy_url)?; let https_proxy = reqwest::Proxy::https(&proxy_url)?; let mut headers = header::HeaderMap::new(); headers.insert("accept", header::HeaderValue::from_static("*/*")); headers.insert("cookie", header::HeaderValue::from_str(&cookies).unwrap()); let client = reqwest::Client::builder() .user_agent(user_agent) .default_headers(headers) .danger_accept_invalid_certs(true) .gzip(true) .proxy(http_proxy) .proxy(https_proxy) .connect_timeout(std::time::Duration::from_secs(60)) .timeout(std::time::Duration::from_secs(600)) .build() .unwrap(); tokio::join!(fetch(&client, &ipify_url), fetch(&client, &ipify_url)); Ok(()) } async fn fetch(client: &Client, url: &str) -> Result<(), Box> { let mut interval = time::interval(std::time::Duration::from_secs(60)); loop { interval.tick().await; let res = client.get(url).send().await.unwrap(); match res.status() { reqwest::StatusCode::OK => { println!("Status: OK, url:{:?}", res.url().path()); println!("URL: {}", url); } status => println!("status: {}, path: {}", status, res.url().path()), } } } ``` ### Configure max_connections for PostgreSQL using nix URL: https://til.codes/configure-max_connections-postgresql-nix/ Last updated: 2022-05-30T06:59:17.000Z The default value of `max_connections` is 100, and if you ever wanted to configure it for a higher limit, it can be done so by updating the following: ```diff { pkgs, ... }: { services.postgresql = { enable = true; package = pkgs.postgresql_14; dataDir = "/usr/local/var/postgres"; + extraConfig = '' + max_connections = 1000 + ''; }; } ``` ### using elixir master branch using nix and nix-shell URL: https://til.codes/using-elixir-master-branch-using-nix/ Last updated: 2021-08-28T01:45:51.000Z There were some noteable changes that landed on the master branch in elixir today, related to improving the compilation, and I wanted to try out the latest version of elixir that was on the master branch. Since I use nix for setting up my projects, the latest version of elixir was not yet available as nix package. So I was looking into options on how I can use the latest master branch with nix-shell. One option was to use override the package derivation and pin it to master branch, but after a bit more googling I went ahead with another option of using `overlays` Here is how my nix-shell looks like: ```nix { sources ? import ./nix/sources.nix }: with sources; let project_name = "til_codes"; pkgs = import sources.nixpkgs { overlays = [ (import ./nix/elixir.nix) ]; config = { }; }; inherit (pkgs.lib) optional optionals; in pkgs.mkShell { buildInputs = with pkgs; [ elixir ]; } ``` and `nix/elixir.nix` as follows: ```nix self: super: { elixir = (super.beam.packagesWith super.erlang).elixir.override { src = builtins.fetchGit { url = "https://github.com/elixir-lang/elixir"; ref = "master"; }; minimumOTPVersion = "22"; }; } ``` The advantage of using overlays over overrides is that overlays can be further customised if needed. Hope that helps someone ### Using variables inside binary pattern matching URL: https://til.codes/binary-pattern-matching-in-elixir-and-using-variables-as-pattern/ Last updated: 2020-12-10T22:48:36.000Z I was refactoring a piece of code that I inherited in a codebase, which was parsing date from an external source in various formats into `Date` in elixir. The initial version of `parse/1` function look something like the following: ```ruby defmodule DateParser do def parse( <<_day_value::binary-size(1), space_or_comma::binary-size(1), _rest::binary>> = purchase_date ) when space_or_comma in [" ", ", "] do # 4 Nov 2020 12:12:50 +0000 or 4, Nov 2020 12:12:50 +0000 s = purchase_date |> String.replace(",", "") |> String.split() |> Enum.take(5) |> Enum.join(" ") # pad 0 on day 4 => 04 format = "%d %b %Y %T %z" case Timex.parse("0#{s}", format, :strftime) do {:ok, date} -> date |> Timex.to_naive_datetime() |> NaiveDateTime.to_date() _ -> nil end end def parse( <<_day_value::binary-size(2), space_or_comma::binary-size(1), _rest::binary>> = purchase_date ) when space_or_comma in [" ", ","] do # 14 Nov 2020 12:12:50 +0000 or 14, Nov 2020 12:12:50 +0000 s = purchase_date |> String.replace(",", "") |> String.split() |> Enum.take(5) |> Enum.join(" ") format = "%d %b %Y %T %z" case Timex.parse(s, format, :strftime) do {:ok, date} -> date |> Timex.to_naive_datetime() |> NaiveDateTime.to_date() _ -> nil end end end ``` While it worked perfectly fine for the usecase, I saw an opportunity to refactor that to make it slighly more readable. A few things that I had in mind was, extract the common piece of code for splitting the string, and `Timex.parse` to a function so that it can be re-used. The first iteration for the refactor looked something like the following: ```ruby defmodule DateParser do def parse( <<_day_value::binary-size(1), space_or_comma::binary-size(1), _rest::binary>> = purchase_date ) when space_or_comma in [" ", ", "] do purchase_date |> do_split(5) |> do_parse end def parse( <<_day_value::binary-size(2), space_or_comma::binary-size(1), _rest::binary>> = purchase_date ) when space_or_comma in [" ", ","] do purchase_date |> do_split(5) |> do_parse() end defp do_split(date, take_count) do date |> String.split() |> Enum.take(take_count) |> Enum.join(" ") end defp do_parse(date, format \\ "%d %b %Y %T %z") do case Timex.parse(date, format) do {:ok, date} -> NaiveDateTime.to_date(date) {:error, _reason} -> nil end end end ``` While that became more readable, I thought, I could take it one more step further, by extracting the pattern to a module tag, so that the function head is more easy to read. ```ruby defmodule DateParser do @single_digit_date quote do: <<_day_value::binary-size(1), var!(delimiter)::binary-size(1), _rest::binary>> @double_digit_date quote do: <<_day_value::binary-size(2), var!(delimiter)::binary-size(1), _rest::binary>> def parse(unquote(@single_digit_date) = purchase_date) when delimiter in [" ", ", "] do purchase_date |> do_split(5) |> do_parse end def parse(unquote(@double_digit_date) =purchase_date) when delimiter in [" ", ","] do purchase_date |> do_split(5) |> do_parse() end defp do_split(date, take_count) do date |> String.split() |> Enum.take(take_count) |> Enum.join(" ") end defp do_parse(date, format \\ "%d %b %Y %T %z") do case Timex.parse(date, format) do {:ok, date} -> NaiveDateTime.to_date(date) {:error, _reason} -> nil end end end ``` That looks more readable, atleast IMO :) Interesting thing that I learned was the use of `var!(delimiter)` inside the pattern and then being able to use that inside the guard clause. Hopefully that helps someone in the future ### Tracing in Elixir/Erlang using :erlang.trace and GenServer URL: https://til.codes/tracing-in-elixir/ Last updated: 2020-12-05T01:31:25.000Z Recently, I wanted to trace a running Elixir system and see the messages that a function received. I was looking to inspect the params that it received and even manipulate the params to fiddle with some edge cases in the system. I knew that I could use `dbg` to start a trace on the function and play with the arguments and get more info on the results. I wrote about some tips on using `dbg` here before [Tracing in Elixir/Erlang using dbg trips and tricks.Tips and tricks for tracing in elixir/erlang using dbg module and some helper functions.![](https://til.codes/favicon.ico)Today I learnedManu S Ajith![](https://www.gravatar.com/avatar/966eb8b0d931ad79a0a031b50d1f5feb?s=250&d=mm&r=x)](https://til.codes/tracing-in-elixir-erlang-using-dbg-trips-and-tricks/) After a bit of googling, I came across [this amazing answer in StackOverflow](https://stackoverflow.com/a/28914950/1598425?ref=til.codes) by Saša Jurić, where he provided an example where he wrapped the `:erlang.trace` around a GenServer which can be hooked to the module. Copying the code that he shared: ```ruby defmodule Tracer do use GenServer def start(modules), do: GenServer.start(__MODULE__, modules) def init(modules) do :erlang.trace(:all, true, [:call]) for module <- modules do :erlang.trace_pattern({module, :_, :_}, [{:_, [], [{:return_trace}]}]) end {:ok, nil} end def handle_info({:trace, _, :call, {mod, fun, args}}, state) do IO.puts "called #{mod}.#{fun}(#{Enum.join(Enum.map(args, &inspect/1), ",")})" {:noreply, state} end def handle_info({:trace, _, :return_from, {mod, fun, arity}, res}, state) do IO.puts "#{mod}.#{fun}/#{arity} returned #{res}" {:noreply, state} end def handle_info(_, state), do: {:noreply, state} end ``` And then you can trace any module like: ```elixir Tracer.start([YourModule]) ``` Pretty neat stuff.! Just reminds me again how powerful tracing in Elixir/Erlang is. Reference: [Elixir compile-time code injection / AOPI’ve previously used AOP-style code to separate Logic from Logging, and been very pleased with the results. I recognize that opinions on AOP vary, but I’d like to figure out a solution in Elixir, ...![](https://cdn.sstatic.net/Sites/stackoverflow/Img/apple-touch-icon.png?v=c78bd457575a)Stack OverflowChris Meyer![](https://cdn.sstatic.net/Sites/stackoverflow/Img/apple-touch-icon@2.png?v=73d79a89bded)](https://stackoverflow.com/a/28914950/1598425?ref=til.codes) ### Deleting stale Postgres wal files URL: https://til.codes/deleting-stale-postgres-wal-files/ Last updated: 2020-10-27T22:48:22.000Z I was debugging a system earlier today, where one of the postgres instances ran out of disk space, and taking a closer look at the system, I realised that the backup command was failing silently, but the postgres archive files was still being retained in the wal folder. Checking a bit at the document, found that they can be deleted using: ```bash pg_archivecleanup -d /var/lib/postgresql/wal/pg_wal 000000010000003700000010.00000020 ``` Hope it helps someone or even me in future :) ### Finding long running SQL queries in PostgreSQL URL: https://til.codes/finding-long-running-sql-queries-in-postgresql/ Last updated: 2020-10-18T14:31:31.000Z Recently in one of the projects that I was working on, I came across a situation where the SQL query times was getting slower and slower per day, and I had to figure out what was happening to the system. One of the things that I checked immediately was whether there were any long running queries in the system, that is affecting the other queries, and whoila, there it was - the system had a bunch of rogue queries that had been running for over a day or so. Running the following command gave me queries that were running for more than 5 seconds. ```sql SELECT pid, now() - pg_stat_activity.query_start AS duration, state, query FROM pg_stat_activity WHERE state ='active' AND query NOT ILIKE '%pg_stat_activity%' AND (now() - pg_stat_activity.query_start) > interval '5 seconds' ORDER BY duration desc; ``` This would give you a result like: ```SQL -[ RECORD 1 ]------------------------------------------------------------ pid | 6473 duration | 00:00:01.011715 state | active query | .......... ``` Now you have the `pid` of the query, lets try to get some more information on the query. ```bash strace -f -s2048 -p 6473 ``` Running a `strace` on the query gives you more information on what the query is doing. In my case, it was waiting indefinitely for acquiring a lock, which would never succeed. Solution for me was to terminate the query as it had already gone rogue. ```sql SELECT pg_cancel_backend(6473) ``` the `pg_cancel_backend` command kills the running query. ### Using custom/older versions of libraries and packages using nix. URL: https://til.codes/using-custom-versions-of-libraries-and-packages-using-nix/ Last updated: 2020-05-02T22:22:35.000Z Ever had the need to work on a legacy project having an old version of a language or framework or library and went through the pain of installing those dependencies? - yea we all have. I recently had to setup an old project and was facing the same exact situation. ruby version: 2.2.2 I was trying to setup the project using nix, but unfortunately nix does not support ruby 2.2.2 now. The least supported version is 2.5. ```bash $ nix-env -qaP ruby nixpkgs.ruby_2_5 ruby-2.5.8 nixpkgs.ruby ruby-2.6.6 nixpkgs.rubyMinimal ruby-2.6.6 nixpkgs.ruby_2_7 ruby-2.7.1 ``` So I need to find a way to install ruby-2.2.2 using nix. One [recommended way](https://discourse.nixos.org/t/legacy-ruby-version/3683/2?ref=til.codes) of doing that is using pinned versions. Finding out the old sha/rev from hydra and pinning it. But unfortunately this did not work out for me. The old version that was built in hydra was using an old version of nix, but is no longer compatible with the mac os version that I am using now. So that was out of the equation. Next option is to override the package derivation using `overrideAttrs` ```nix ruby_2_2_2 = pkgs.ruby.overrideAttrs (oldAttrs: rec { version = "2.2.2"; src = fetchurl { url = "https://cache.ruby-lang.org/pub/ruby/2.2/ruby-2.2.2.tar.gz"; sha256 = "0i4v7l8pnam0by2cza12zldlhrffqchwb2m9shlnp7j2gqqhzz2z"; }; # nixpkgs does not have 2.2.2 related patches anymore. patches = []; postPatch = ""; }); ``` Notice the override of `patches = []`This was necessary as nix would try to patch the ruby version from a list of available `patchsets`. But then again, ruby 2.2.2 is outdated, and nixpkgs does not maintain those patches any more. After a bit of tweaking and skipping the patches it worked, and I did not have to try installing ruby 2.2.2 on host or setting up a docker image for that. Hope that helps someone. ### nix-shell for elixir projects URL: https://til.codes/nix-shell-for-elixir-projects/ Last updated: 2020-04-13T07:43:16.000Z I have been using `nix` as my default development environment for my projects for some time, and have found it quite useful, especially when working with different languages and frameworks and having different version requirements. Just wanted to share my default nix-shell that I use/modify in the projects. ``` { pkgs ? import {} }: with pkgs; let inherit (lib) optional optionals; erlang = beam.interpreters.erlangR22; elixir = beam.packages.erlangR22.elixir_1_10; nodejs = nodejs-12_x; in mkShell { buildInputs = [cacert git erlang elixir cargo nodejs] ++ optional stdenv.isLinux inotify-tools ++ optionals stdenv.isDarwin (with darwin.apple_sdk.frameworks; [ CoreFoundation CoreServices ]); shellHook = '' alias mdg="mix deps.get" alias mps="mix phx.server" alias test="mix test" alias c="iex -S mix" ''; } ``` Hope that helps someone.! Cheers. ### Tracing in Elixir/Erlang using dbg trips and tricks. URL: https://til.codes/tracing-in-elixir-erlang-using-dbg-trips-and-tricks/ Last updated: 2020-04-11T13:39:52.000Z For context, I was trying to debug/understand a new codebase and wanted to do some tracing so that I could better understand the flow, so I started tracing these functions using the `dbg` module from Erlang. But one of the common cons of the dbg module is the documentation of the functions, and odd function names ``` iex(1)> :dbg. c/3 c/4 cn/1 ctp/0 ctp/1 ctp/2 ctp/3 ctpe/1 ctpg/0 ctpg/1 ctpg/2 ctpg/3 ctpl/0 ctpl/1 ctpl/2 ctpl/3 deliver_and_flush/1 dhandler/2 dtp/0 dtp/1 erlang_trace/3 flush_trace_port/0 flush_trace_port/1 fun2ms/1 get_info/0 get_tracer/0 get_tracer/1 h/0 h/1 i/0 ln/0 ltp/0 match_0_9/1 match_front/2 match_rear/2 n/1 p/1 p/2 rtp/1 start/0 stop/0 stop_clear/0 stop_trace_client/1 tp/2 tp/3 tp/4 tpe/2 tpl/2 tpl/3 tpl/4 trace_client/2 trace_client/3 trace_port/2 trace_port_control/1 trace_port_control/2 tracer/0 tracer/2 tracer/3 transform_flags/1 wrap_postsort/1 wrap_presort/2 wrap_sort/2 wrap_sortfix/2 wtp/1 ``` And without having the documentation in one hand, it would be impossible to remember all these function names and their arguments and options. I always wondered whether there would be a simpler way to do this. Of-course there are thin elixir wrappers around it which provide a much more friendly DSL, but it would mean adding another dependency. There are also other libraries like `recon`, `redbug`, `xprof` etc, but all has the same caveats. So after searching for a while, I came across this stack overflow [post](https://stackoverflow.com/questions/1954894/using-trace-and-dbg-in-erlang/2831375?ref=til.codes#2831375) where it provides a simple Erlang wrapper module with a more friendly function names. Source code available here: [saleyn/utilErlang utility modules. Contribute to saleyn/util development by creating an account on GitHub.![](https://github.githubassets.com/favicons/favicon.svg)GitHubsaleyn![](https://avatars1.githubusercontent.com/u/272543?s=400&v=4)](https://github.com/saleyn/util/blob/master/src/user%5Fdefault.erl?ref=til.codes) That was exactly the thing that I was looking for: ``` iex(5)> :dbg_tracer.help ** shell internal commands ** b() -- display all variable bindings e(N) -- repeat the expression in query f() -- forget all variable bindings f(X) -- forget the binding of variable X h() -- history history(N) -- set how many previous commands to keep results(N) -- set how many previous command results to keep catch_exception(B) -- how exceptions are handled v(N) -- use the value of query rd(R,D) -- define a record rf() -- remove all record information rf(R) -- remove record information about R rl() -- display all record information rl(R) -- display record information about R rp(Term) -- display Term using the shell's record information rr(File) -- read record information from File (wildcards allowed) rr(F,R) -- read selected record information from file(s) rr(F,R,O) -- read selected record information with options ** commands in module c ** bt(Pid) -- stack backtrace for a process c(Mod) -- compile and load module or file cd(Dir) -- change working directory flush() -- flush any messages sent to the shell help() -- help info i() -- information about the system ni() -- information about the networked system i(X,Y,Z) -- information about pid l(Module) -- load or reload module lm() -- load all modified modules lc([File]) -- compile a list of Erlang modules ls() -- list files in the current directory ls(Dir) -- list files in directory m() -- which modules are loaded m(Mod) -- information about module mm() -- list all modified modules memory() -- memory allocation information memory(T) -- memory allocation information of type nc(File) -- compile and load code in on all nodes nl(Module) -- load module on all nodes pid(X,Y,Z) -- convert X,Y,Z to a Pid pwd() -- print working directory q() -- quit - shorthand for init:stop() regs() -- information about registered processes nregs() -- information about all registered processes uptime() -- print node uptime xm(M) -- cross reference check a module y(File) -- generate a Yecc parser ** commands in module i (interpreter interface) ** ih() -- print help for the i module ** user extended commands ** dbgtc(File) -- use dbg:trace_client() to read data from File dbgon(M) -- enable dbg tracer on all funs in module M dbgon(M,Fun) -- enable dbg tracer for module M and function F dbgon(M,File) -- enable dbg tracer for module M and log to File dbgadd(M) -- enable call tracer for module M dbgadd(M,F) -- enable call tracer for function M:F dbgdel(M) -- disable call tracer for module M dbgdel(M,F) -- disable call tracer for function M:F dbgoff() -- disable dbg tracer (calls dbg:stop/0) l() -- load all changed modules la() -- load all modules mm() -- list modified modules ``` Pro tip: You can add it to your `.iex.exs` file if you need to be using the module quite often and in multiple projects. Copy the code into `dbg_tracer.erl` and in your home directory, and add the following to `.iex.exs` so that it compiles the erlang module and make it available. ``` c("dbg_tracer.erl") ``` Hope that helps someone. Cheers. References: [Using trace and dbg in ErlangI am trying to start using erlang:trace/3 and the dbg module to trace the behaviour of a live production system without taking the server down. The documentation is opaque (to put it mildly) and t...![](https://cdn.sstatic.net/Sites/stackoverflow/img/apple-touch-icon.png?v=c78bd457575a)Stack OverflowGordon Guthrie![](https://cdn.sstatic.net/Sites/stackoverflow/img/apple-touch-icon@2.png?v=73d79a89bded)](https://stackoverflow.com/questions/1954894/using-trace-and-dbg-in-erlang/2831375?ref=til.codes#2831375) [saleyn/utilErlang utility modules. Contribute to saleyn/util development by creating an account on GitHub.![](https://github.githubassets.com/favicons/favicon.svg)GitHubsaleyn![](https://avatars1.githubusercontent.com/u/272543?s=400&v=4)](https://github.com/saleyn/util/blob/master/src/user%5Fdefault.erl?ref=til.codes) ### Custom types using dry-logic and predicates URL: https://til.codes/custom-types-with-dry-rb-gems/ Last updated: 2020-01-20T17:38:59.000Z I wanted to have a custom type in my application for IP addresses - IPv4 and IPv6 types. My first idea was build the custom type based on top of `Dry::Types::Strict::String.constrained` Having a quick look at the constrained type, I realised that its dry-logic that dictates the [predicate](https://github.com/dry-rb/dry-logic/blob/master/lib/dry/logic/predicates.rb?ref=til.codes) logic. Another quick search over the Github issues, and I see that I am not the only one who wanted something similar. I found a similar [issue](https://github.com/dry-rb/dry-types/issues/179?ref=til.codes) that I was trying to solve. #### Solution use the `case?` predicate. From the Github issue: > > Anything that responds to `===` can be used, including strings, numbers, classes, ranges, regexes, and even procs. So the `case?` predicate responds to `===`, You can pass a pattern and the predicate will check if the pattern matches your input, just what I wanted. ```ruby module Types include Dry.Types ipv4 = ->(input) { IpValidator.valid_ipv4?(input) } IPv4 = Types::String.constrained(case: ipv4) ipv6 = ->(input) { IpValidator.valid_ipv6?(input) } IPv6 = Types::String.constrained(case: ipv6) end ``` In the above snipped, I have an `ipv4` and `ipv6` lambda which takes an input and checks whether its a valid `ipv4` or `ipv6` using `IpValidator` class, which is a wrapper to check the IP address. With these 2 lambdas, I can now build my custom types by passing them to the `case?` predicate which will check if the input and the pattern matches. Simple usage: ```ruby Types::IPv4.call('127.0.0.1') Types::IPv6.call('2001:0db8:85a3:0000:0000:8a2e:0370:7334') ``` Hope that helps someone. Enjoy.! ### Upgrading ghost to 3.X from 2.x URL: https://til.codes/upgrading-ghost-to-3-x-from-2-x/ Last updated: 2019-12-16T23:34:22.000Z I was upgrading my blog(ghost) from `v2.x` to `v3.x` and encountered a strange error with the ghost-cli command. TL;DR Check compatibility of ghost-cli with nodejs version As usual, I fired up the terminal and ran the command: ``` ghost update ``` and got the strange error. ``` ghost update /usr/lib/node_modules/ghost-cli/lib/command.js:108 static async _run(commandName, argv = {}, extensions) { ^^^^ SyntaxError: Unexpected identifier at createScript (vm.js:56:10) at Object.runInThisContext (vm.js:97:10) at Module._compile (module.js:549:28) at Object.Module._extensions..js (module.js:586:10) at Module.load (module.js:494:32) at tryModuleLoad (module.js:453:12) at Function.Module._load (module.js:445:3) at Module.require (module.js:504:17) at require (internal/module.js:20:19) at Object. (/usr/lib/node_modules/ghost-cli/lib/bootstrap.js:6:17) ``` That seemed an odd error and was giving nothing for me to debug. I checked the Ghost Github issue tracker for any clues, but to my dismay it did not give me any clues either. But after looking through the documentation, I noticed the the supported nodejs versions document [here](https://ghost.org/faq/node-versions/?ref=til.codes) And to my surprise, I was running nodejs v6, which is not supported by ghost anymore. So that was the clue that I needed, and I went ahead with upgrading my nodejs version to `10.x` and 🎉 it fixed the issue with the ghost-cli. ###### Commands to upgrade nodejs version(taken from the faq page above) To upgrade the steps are: ``` # update your source list with the version of Node.js you want to upgrade to. curl -sL https://deb.nodesource.com/setup_10.x | sudo -E bash # upgrade Node/ks sudo apt-get install -y nodejs # force Ghost to update even if there are no new versions of Ghost, # to trigger a re-install of dependencies. ghost update --force ``` That was smooth. Enjoy.! ### Escaping special characters like & in rails Html views URL: https://til.codes/escaping-special-characters-like-in-rails-html-views/ Last updated: 2019-12-16T23:50:56.000Z In this article we will explore how to safely escape and render html characters while preserving special characters like ampersand(&) using `strip_tags` and `Loofah`. Recently, I was working on a legacy project, displaying some old data stored in the db in some new views. The data stored in the db came from a legacy WYSIWYG editor, and had not been sanitised before saving. It had a mixture of sanitised html tags as well as raw tags. To render the content in a safe way, I had to come up with a custom Sanitizer on top of `ActionView` helpers and `Loofah`. To give an example, the following text: ``` text = "

Someone hacked Terms & Conditions with & <script>alert('hi')<script>

``` If we use strip tags from `ActionView` we get the following: ``` strip_tags(text) # => "Someone hacked Terms & Conditions with & <script>alert('hi')<script>" ``` But notice that the `&` also got escaped here, and I needed a way to render & without being escaped. #### Loofah to the rescue After doing some research, I figured out that Loofah can be used. So for the same example: ``` text = "

Someone hacked Terms & Conditions with & <script>alert('hi')<script>

" Loofah.fragment(text).text(encode_special_chars: false) => "Someone hacked Terms & Conditions with alert('hello') & & <script>alert('hi')<script>

" # text = "

Someone hacked Terms & Conditions with & <script>alert('hi')<script>

" # text = strip_tags(text) # => "Someone hacked Terms & Conditions with & <script>alert('hi')<script>" # text = to_text(text) # => "Someone hacked Terms & Conditions with & ``` That's it and now you have syntax highlighting enabled for your blog. ### Tweaking the layout: list instead of cards and bigger fonts. I did not quite like the new layout of the Casper theme. Cards instead of a list. Also, the fonts were smaller. But the good thing is that you can always tweak the theme and make it the way you like. So I tweaked a few css classes to make the layout the way I liked. The following are the changes that I made: ``` // bigger cover image .site-header { height: 50vh } // a little bit wider lists .inner { max-width: 1200px; } // bigger fonts .post-template .kg-card-markdown>p { font-size: 1.25em; line-height: 1.5em } .post-full-content pre code { font-size: 1.55em; line-height: 1.5em } .post-card-excerpt p { font-size: 1.55em; line-height: 1.5em } .post-card-title { font-size: 2em } // tweaks for the code/syntax highlights pre { word-wrap: normal; -moz-hyphens: none; -ms-hyphens: none; -webkit-hyphens: none; hyphens: none; font-size: 1em; line-height: 1.3em } pre code, pre tt { white-space: pre } // don't use the margin for the lists .post-card { min-height: 0 } // Change cards to table .read-next-feed .post-card, .post-feed { display: flex; } // keep the card layout for similar posts .post-card, .post-feed { display: table } // smaller subscribers form .subscribe-form { padding: 3.0vw; } ``` So that's it, looks much better now. Let me know how you feel the new ghost and Casper theme and any nice tweaks that you have made. Sharing is caring.! 🍻 ### How to disable Adobe Flash Player update notification URL: https://til.codes/how-to-disable-adobe-flash-player-update-notification-2/ Last updated: 2017-11-18T16:16:32.000Z Well, lets not talk about the history/legacy of Flash/HTML5 etc here. Flash has its place but its really annoying when the Adobe Flash Player updater pops in when you are working on something important and distracts you. And even worse, you are presented with only option to **Remind Me Later** or **Download**. And if you select Remind Later option, the annoying popup returns after 60mins again. ###### So how do I get rid of this popup? Two options: Either let adobe install the flash player updates automatically or disable the updates forever. ###### Disabling/Auto Install updates To customise your settings for disabling updates or to enable auto installs on a Mac Goto: **System Preferences -> Flash Player -> Updates** You now have option to either: 1. Allow Adobe to install updates(auto install updates) 2. Notify me to install updates(the annoying per 60min reminder when update is available) 3. Never check for updates(I don't care with it) Select the one which is convenient for you.! Hope this helps someone.! Thanks for reading. ### Disable pry and exit debugger without killing the main program in Ruby URL: https://til.codes/disable-pry-and-exit-debugger-without-killing-the-main-program-in-ruby/ Last updated: 2017-11-19T16:02:31.000Z You ever ran into a situation where you had a huge loop running and you put a `binding.pry` within that loop for debugging and wondered how the hell you would exit the debugger? The answer is simple `disable-pry` is your saviour.! [via GIPHY](https://giphy.com/gifs/loki-saviour-l2QEjYgve6wmqLKog?ref=til.codes) This has got its downside though, this will disable any further invocations of the `pry` again. Under the hood its disabling the whole pry and setting `ENV['DISABLE_PRY'] = true` To re-enable pry for any further debugging you will have to clear the ENV var by `ENV['DISABLE_PRY'] = nil` I have defined a helper method `enable_pry` with the following inside my `.pryrc` for my convenience. ``` def enable_pry ENV['DISABLE_PRY'] = nil end ``` Credits where its due: [https://stackoverflow.com/questions/8015531/how-do-i-step-out-of-a-loop-with-ruby-pry](https://stackoverflow.com/questions/8015531/how-do-i-step-out-of-a-loop-with-ruby-pry?ref=til.codes) Happy coding.!! ### Don't forget to update the sequence in PostgreSQL after a COPY command URL: https://til.codes/dont-forget-to-update-the-sequence-in-postgresql-after-a-copy-command/ Last updated: 2017-11-19T19:53:40.000Z You might have noticed that after bulk inserting records using the `COPY` statement in PostgreSQL the sequence IDs are not getting updated for any further inserts later on, and it would throw duplicate sequence ID errors. So you would be wondering what makes this `COPY` statement different that it does not update the sequences. Well the copy statement is not entirely the culprit here. ###### Internal Details/Reason When you perform a normal `INSERT`, you often don't specify the value for the SEQUENCE-backed primary key explicitly. But if for some reason you did, you would run in to the same problems as you have with `COPY`. So as you see, `COPY` statement is not the real culprit here. The sequence only increments when a value is evaluated by the database itself during the INSERT statements( it internally uses the nextval function underneath). If you provide values for your ID, the sequence is not used, thus it doesn't get incremented. ###### Fix So now you know why it does not increment the sequence, but how do you fix the sequence after a bulk insert? The answer is simple: you need to call the `nextval` function to reset the sequence generator. ``` select setval('seqname',select max(id) from tablename)); ``` so lets say you have a `users` table with `users_id_seq` as the sequence name then: ``` select setval('users_id_seq',select max(id) from users)); ``` ###### Further reading: PostgreSQL provides a lot more configuration options for your sequence generator. You can read them over here [Postgres docs](https://www.postgresql.org/docs/9.1/static/sql-createsequence.html?ref=til.codes) Thanks for reading, and hope it helps someone.! ### Copy data from one postgres instance to another. remote copy options explored: Copy, CSV and STDIN URL: https://til.codes/using-postgres-copy-command-to-copy-data-from-one-server-to-another/ Last updated: 2017-11-19T21:18:59.000Z Its very common use case to copy data from one database instance to another be it from local to staging, staging to production etc. For copying data from one PostgreSQL instance to another, there are a couple of techniques. You can take a data dump as an SQL file or as a CSV from your PostgreSQL instance and do a restore. But what happens when you are dealing with Gigs of data? It might not be a good idea to do a dump of 100s of Gigs to your local system and then copy to the remote host and restore. Well, PostgreSQL also has a `\copy` statement which can be quite handy in this case. What if I say that you can even do a remote copy of data from one database to another over ssh. Interesting isn't it. Let's explore various options for copying data to from local/remote servers. I am not going to cover the `pg_dump` and `pg_restore` methods here as there are a lot of resources out there explaining those in detail. ### Understanding `COPY` and `\copy` statements `COPY` command is to input and output data between database and file only in the database server. If we connect to the database remotely (E.g. from another computer), we cannot use `COPY` command from the remote computer. In other words, input file or output file has to be in the database server. This is because SQL statements that are entered at the `psql` prompt are sent to the server before they are executed. This means that any file paths included in SQL statements are interpreted by the server. Since the server doesn't know what directory a user is in when they execute a statement, this means that all file paths have to be absolute. On the other hand, `\COPY`, the meta command provided by PostgreSQL, is to input or output file in the client computer. If we connect to database server remotely by utilizing `psql` command, we can input or use a file on the client computer. This meta command initiates copies from the client (which is the `psql` process in this case), and this allows it to interpret paths that are relative to the user's current directory. ### Copy data from a CSV file to local database. The simplest solution to copy data from one database to another is to save to a local file and then restore it ``` \COPY users TO 'users.csv' CSV HEADER ``` The above command selects the data that you want and then writes it to a CSV file using the `\copy` command. Now its time to restore the CSV file to the second database. Loading the data is also pretty straightforward with the same `\copy` command. ``` \COPY users FROM 'users.csv' WITH CSV HEADER; ``` ### Copy data using STDIN to a local database. While it's useful to save the data to a local CSV file, it's not always needed. You can even copy the data from one database or table using the STDOUT-> STDIN technique. ``` psql -h localhost \ -d your_primary_db \ -U postgres -c \ "\copy users (id, email, first_name, last_name) from STDIN with delimiter as ','" \ < /tmp/users.csv ``` Note that the above statement also leverages SQL statements inside the `\copy` statement thereby you can choose what data you need to copy. ### Copy data from a CSV file to remote database. The following command copies data from a local CSV file to a remote PostgreSQL database ``` psql \ -h remotehost \ -d your_primary_db \ -U postgres \ -c "\copy users (id, email, first_name, last_name) from '/tmp/users.csv' with delimiter as ','" ``` ### Copy data using STDIN to a remote database. Now, lets use STDIN for reading the CSV file and copying data to a remote host. ``` psql -h remotehost \ -d your_primary_db \ -U postgres -c \ "\copy users (id, email, first_name, last_name) from STDIN with delimiter as ','" \ < /tmp/users.csv ``` ### Copy data from one server to another server ``` psql \ -U user_name \ -h production_server \ -d database_name \ -c "\\copy users to stdout" | \ psql -U user_name \ -h staging_server \ -d database_name \ -c "\\copy users from stdin" ``` The above command STDOUTs the data from the production server and copies the same data over STDIN on the staging server. ### Other techniques There are also a few other techniques that can be used to copy data from one server to another like setting up replication between databases or doing a full snapshot replication of the db. Reference: [https://www.postgresql.org/docs/current/static/sql-copy.html](https://www.postgresql.org/docs/current/static/sql-copy.html?ref=til.codes) Hope that someone finds it useful. 🖖🏻 ### Escape character sequence "E" in PostgreSQL explained URL: https://til.codes/escape-character-sequence-e-in-postgresql-explained/ Last updated: 2017-11-19T19:35:37.000Z You might have encountered situations like where you have to insert some data into your PostgreSQL table that has special characters. eg: `this is a very large sentence \n and hence this is broken down into two sentence` and when you try to do that without the escape sequence postgreSQL starts to behave wieirdly with you, and might start throwing errors like: `nonstandard use of escape in a string literal` Most often this is handle in the code where the programming language takes care of this for you or the ORM do the magic for you. If you are interested in knowing a bit more about the escape character sequences in PostgreSQL, keep reading. # Escape String Constants. PostgreSQL also has the C-styled escape characters as it closely follows the SQL standard. In postgreSQL you can specify the escape character by prefixing the letter `E` From the PostgreSQL [docs](https://www.postgresql.org/docs/10/static/sql-syntax-lexical.html?ref=til.codes) > PostgreSQL also accepts "escape" string constants, which are an extension to the SQL standard. An escape string constant is specified by writing the letter E (upper or lower case) just before the opening single quote, e.g., E'foo'. (When continuing an escape string constant across lines, write E only before the first opening quote.) Within an escape string, a backslash character () begins a C-like backslash escape sequence, in which the combination of backslash and following character(s) represent a special byte value So you can write the above statement using the escape sequence as: `E'this is a very large sentence \n and hence this is broken down into two sentence'` This is particularly useful when you are using the PostgreSQL `\copy` statement and trying to load the data from a CSV file. ### Passing multiple options/argument with default options in rake URL: https://til.codes/passing-multiple-options-argument-with-default-options-in-rake/ Last updated: 2017-04-12T09:48:05.000Z Rake task allows you to accept multiple arguments which can be used within the task, and also allows us to specify default options for these arguments. ###### Accept multiple arguments/options Lets say we have a simple rake task to sync data from/across our servers to different enviroments. We can accept the `server` and `host` as arguments here. ``` namespace :db do desc "Restores the database dump to the given environment" task :sync, [:server, :host] => [:environment, 'db:drop', 'db:create'] do |_t, args| ... end end ``` The above rake task can be called using: ``` bin/rake 'db:sync[staging, local]' ``` ###### With default values Sometimes it makes sense to have default values for the arguments. An eg use case would be if no value is specified for the host, we are expecting the DB to be synced to our local database. In such situations, we can use `args.with_defaults` ``` namespace :db do desc "Restores the database dump to the given environment" task :sync, [:server, :host] => [:environment, 'db:drop', 'db:create'] do |_t, args| args.with_defaults(:server => 'staging', :host => 'local') .... end end ``` and the rake task can be executed using: ``` bin/rake 'db:sync[staging]' ``` ###### Note: **if you are using zsh**, you will need to wrap the task inside **'** **'**. this is because zsh doesn't play too nicely with the arguments/commands. ### How to fix the Index name too long error in rails migrations URL: https://til.codes/how-to-fix-the-index-name-too-long-error-in-rails-migrations/ Last updated: 2017-03-16T13:06:39.000Z Today, I was reviewing some legacy code, trying to isolate the bottlenecks and pinpoint the optimisation strategies for scaling and performance optimisations of a rails app. As the usual procedure, I wanted to check the database indexes and see if something was missing here. And my guess was in the right direction. Some of the indexes were missing. So the next obvious thing to do: Create a migration for adding the missing indexes. I thought it was gonna be a breeze, I generated the migration, filled the migration file with the correct table name and fields etc, but when I tried to run the migration, I stumbled upon an issue. ``` ArgumentError: Index name 'index_loan_application_status_message_template_groups_on_message_template_group_id' on table 'loan_application_status_message_template_groups' is too long; the limit is 63 characters ``` Problem: **The index name is too long** So here is what my migration looked like: ``` class IndexForeignKeysInLoanApplicationStatusMessageTemplateGroups < ActiveRecord::Migration def change add_index :loan_application_status_message_template_groups, :message_template_group_id end end ``` After a bit of googling I learned that PostgreSQL has a limit of 63 char for naming, as can be found [here](https://www.postgresql.org/docs/current/static/sql-syntax-lexical.html?ref=til.codes#SQL-SYNTAX-IDENTIFIERS). So I need to keep my index identified under 63 chars. And our nice folks who build rails/active record have given us a nice way to accomplish this. **[Solution](solution)**: You can specify the name of the index and keep it under the 63 chars mark by passing values to the `name` argument in your migration. So I modified my migration to the following: ``` class IndexForeignKeysInLoanApplicationStatusMessageTemplateGroups < ActiveRecord::Migration def change add_index :loan_application_status_message_template_groups, :message_template_group_id, name: 'loan_application_status_message_template_group_index' end end ``` Hope that helps someone. :) ### Find out which part of your code is triggering ActiveRecord or SQL queries. URL: https://til.codes/find-out-which-part-of-your-code-is-triggering-activerecord-or-sql-queries/ Last updated: 2017-03-16T13:21:08.000Z Today, I got a new pet project to play with and my job was to identify the bottlenecks and performance improvements we can make in the application. So I wanted to jump in and wanted to see what SQL queries were being triggered when someone requests data. Going through the entire code was not a feasible option for me. So I was more like looking for a silver bullet, which would give me a list of queries that were triggered and the code snippets or areas which triggered them, so that I can see if it can be optimised any further. What I had mind was to log all SQL queries and the caller or method which triggered them. Looking a bit deep into the rails documentation, and some google and stack overflow links, I finally came with this helper script. You can add this inside `config/intializers` directory and see the magic. ``` module LogQuerySource def debug(*args, &block) return unless super backtrace = Rails.backtrace_cleaner.clean caller relevant_caller_line = backtrace.detect do |caller_line| !caller_line.include?('/initializers/') end if relevant_caller_line logger.debug(" ↳ #{relevant_caller_line.sub("#{Rails.root}/", '')}") end end end ActiveRecord::LogSubscriber.send :prepend, LogQuerySource ``` So how does it shows its magic: Here is an extract from my logs. As you can see the SQL query is bieng triggered by the block in `processed_repayments_changes` in `app/services/lead_services/timeline.rb`. ``` CACHE (0.1ms) SELECT "admin_users".* FROM "admin_users" WHERE "admin_users"."id" = $1 LIMIT 1 [["id", "2"]] ↳ app/services/lead_services/timeline.rb:175:in `block in processed_repayments_changes' ``` Super useful yea ?? Spread the word and let it help someone else too :) ### Bypassing ssh firewall by overriding Type of Service headers for TCP packets in routers URL: https://til.codes/bypassing-ssh-firewall-by-overriding-type-of-service-headers-for-tcp-packets-in-routers/ Last updated: 2017-02-16T13:37:48.000Z I was recently having issues when trying to ssh into one of my servers. The normal debugging techniques and solutions didn't work for me and I had to debug this in depth to figure out the issue was actually with my router blocking the packets ###### The actual problem.! When you ask for a regular ssh terminal, ssh sets the TCP packet type of service (ToS) to "interactive". I was working from a cafe using a public wifi hotspot and the public wifi router that I was using was blocking those packet types! ###### Solution So I now need to way to figure out and bypass those headers that were being set. I found that using `netcat`, the tunnelled TCP packets get no type of service directives. Thus, if you tunnel all your ssh traffic through `netcat`, you reset the ToS of the TCP packets to the default ones. you can just add this to your ssh config file (either `~/.ssh/config` or `/etc/ssh/ssh_config`): ``` Host *.example.com ProxyCommand nc %h %p ``` ###### What is Type of Service(ToS) The type of service (ToS) field in the IPv4 header. It had various definitions over time and the modern redefinition of the ToS field is a six-bit Differentiated Services Code Point (DSCP) field and a two-bit Explicit Congestion Notification (ECN) field. The ToS field could specify a datagram's priority and request a route for low-delay, high-throughput, or highly-reliable service. Based on these ToS values, a packet would be placed in a prioritized outgoing queue, or take a route with appropriate latency, throughput, or reliability. ### Debugging Git network connection issues using GIT_TRACE URL: https://til.codes/debugging-git-network-connection-issues-using-git_trace/ Last updated: 2017-02-16T13:17:06.000Z I have been travelling a lot this month and working remotely from cafes and public places during my stay in Europe. And most often I was connected to the internet using the public hot spots or free Wifi provided by the cafes or restaurants. The quality of the connections were really good I must admit, but one common trouble I had with these connections was when using git. Most of these connections had some restrictions with using git. #### Problem In one of the instances, I was able to push to github but was not able to fetch/clone/pull from the repo. As I issued the command ``` git fetch origin ``` it hangs there forever and didn't respond at all. #### Debugging So let's open our troubleshooting guide and go to Rule 1: - Check your connectivity :P ``` ping google.com PING google.com (216.58.205.238): 56 data bytes 64 bytes from 216.58.205.238: icmp_seq=0 ttl=46 time=124.577 ms ``` Okay, so I am connected.!! - Check if you have access to the repo Since I am able to push to repo, I am pretty sure that I have access to the repo, but still :check: - Check if ssh was disabled. ``` $ ssh git@github.com PTY allocation request failed on channel 0 Hi manusajith! You've successfully authenticated, but GitHub does not provide shell access. Connection to github.com closed. ``` Okay.!! so ssh is not blocked on the router for sure now. - Replace `git://` protocol with `http/https` I was pretty sure that this was not the issue, but still lets give it a try.!! - Try changing your DNS to `8.8.8.8` I already had that :P Since all of the above failed, I realised that it needs a bit more serious debugging. My immediate thoughts was to troubleshoot the network connections and to log the connection attempts to GitHub in a more verbose manner so that I can debug it in depth. #### GIT\_TRACE Welcome to `GIT_TRACE`. This configuration option gives us a more verbose trace to the git network connections and all the internal commands it goes through. This environment variable can accept the following values: - 1, 2, or true If this variable is set to `1`, `2` or `true` ( the comparison is case insensitive), git will print trace: messages on stderr telling about alias expansion, built-in command execution and external command execution. - greater than 1 and less than 10 If this variable is set to an integer value greater than 1 and lower than 10 (strictly) then git will interpret this value as an open file descriptor and will try to write the trace messages into this file descriptor. - absolute path Alternatively, if this variable is set to an absolute path (starting with a / character), git will interpret this as a file path and will try to write the trace messages into it. ``` $ GIT_TRACE=1 git fetch origin 13:44:47.299097 git.c:350 trace: built-in: git 'fetch' 'origin' 13:44:47.403611 run-command.c:336 trace: run_command: 'ssh' 'git@github.com' 'git-upload-pack '\''woumedia/naturalblender-api.git'\''' ``` With the more in-depth trace, I was able to figure out that it hanged forever at when trying to upload the pack. Since I know where exactly the problem was I am now able to find an fix for the same, further research and debugging gave me clues as to why it was stuck there forever: It was a type of service issue with TCP packets handling on my router, which unfortunately couldn't do much about. ### Script/runner vz Rake tasks URL: https://til.codes/script-runner-vz-rake-in-cron-job-rails/ Last updated: 2017-02-16T14:02:42.000Z When it comes to running Rails tasks the common question that comes to mind is whether to use `script/runner` or to call a `rake` task ``` script/runner long_running_task VZ rake long_running_task ``` To understand it better we need to understand how both works under the hood. Whenever `script/runner` is called it boots the entire Rails But on the other hand Rake task doesn't load the entire Rails app unless you tell it to by making the task depend on `:environment`, like this: ``` task :some_useful_task => :environment do # do some useful task end ``` Since booting your Rails app is expensive each time, it might be worth skipping if you can avoid it. ### Elegant way to silently ignore a Ruby exception URL: https://til.codes/which-is-the-shortest-way-to-silently-ignore-a-ruby-exception/ Last updated: 2017-02-16T13:47:55.000Z I was working on a small hobby project and wanted to ignore some of the Exceptions that were raised. The first version of the code that I wrote was something similar to: ``` def ignore_exception begin yield rescue Exception end end ``` and using ``` ignore_exception { puts "Ignoring Exception"; raise Exception; puts "This is Ignored" } ``` After a bit of more googling and research, I came across another clean solution provided by `ActiveSupport` ``` suppress(Exception) do # dangerous code here end ``` **PS: Don't ask me why I wanted to do this, as I mentioned this was just a hobby project and will/should never go out into a production code.** Ref: [http://api.rubyonrails.org/classes/Kernel.html#method-i-suppress](http://api.rubyonrails.org/classes/Kernel.html?ref=til.codes#method-i-suppress) ### brew cask: Error: Unknown command: cask URL: https://til.codes/brew-cask-error-unknown-command-cask/ Last updated: 2016-09-24T14:45:08.000Z After upgrading my OS to Sierra I was having an issue with homebrew. It failed to recognize the `cask` command and was throwing an error: ``` brew cask install haskell-platform Error: Unknown command: cask ``` Brew wasn't able to find the correct path and was causing this issue. ### Fix: ``` brew update brew cleanup brew cask cleanup ``` ### Manage sidekiq workers using deployment setup and Capistrano URL: https://til.codes/manage-sidekiq-workers-using-deployment-setup-and-capistrano/ Last updated: 2016-09-09T06:17:33.000Z In real world, most of the Rails applications would be having a Sidekiq instance as its companion in production systems. And it's a necessary thing to have some sort of mechanism to manage your Sidekiq instance with your deployment setup. In this post, I am going to throw light on some of the options that you have with managing Sidekiq workers with the deployment setup capistrano. 1. using `capistrano-sidekiq` gem. 2. using custom tasks ###### Option1: `capistrano-sidekiq` The first one is using `capistrano-sidekiq` gem which takes care of the hassle for you First add `capistrano-sidekiq` to your Gemfile: ``` gem 'capistrano-sidekiq' ``` After you do a: ``` bundle install ``` Require `capistrano-sidekiq` in your Capfile: ``` require 'capistrano/sidekiq' ``` The gem comes with a lot of options to customize your Sidekiq instance, you can see the various available options from the [github page](https://github.com/seuros/capistrano-sidekiq?ref=til.codes#usage) ###### sidekiq default hooks `capistrano-sidekiq` adds some default hooks when `capistrano-rails` is installed. ``` task :add_default_hooks do after 'deploy:starting', 'sidekiq:quiet' after 'deploy:updated', 'sidekiq:stop' after 'deploy:reverted', 'sidekiq:stop' after 'deploy:published', 'sidekiq:start' end ``` \== Sidekiq will start or stop automatically during Rails deployments.== Just set `sidekiq_default_hooks` to false if you don't want this to happen. You also can start/stop Sidekiq instances manually anytime using: ``` cap production sidekiq:start ``` **Note**: This option is prefered if you need advanced control of your sidekiq workers. ###### Option:2: Your own capistrano task If you dont want to manage your sidekiq instances using `capistrano-sidekiq` gem you can simply add a custom task in your code like: ``` task :restart_sidekiq do on roles(:worker) do execute :service, "sidekiq restart" end end after "deploy:published", "restart_sidekiq" ``` This will restart your sidekiq instance whenever capistrano publishes the artifacts to your server after a deployment. ### How to turn on SQL debug logging for ActiveRecord URL: https://til.codes/how-to-turn-on-sql-debug-logging-for-activerecord/ Last updated: 2016-09-09T06:33:43.000Z If you want to see the SQL queries that your apps are running and want o optimize them or tune them, then it is a nice idea to log them. You can either log it to your log file or even to STDOUT. You can set the log level in your environment file to `debug` so that you can get maximum data out of your application and can use the same for tuning. ``` config.log_level = :debug ``` To log it to STDOUT, you can add the following snippet to your specific environment file. ``` ActiveRecord::Base.logger = Logger.new(STDOUT) ``` References: - Rails [Guide](http://guides.rubyonrails.org/debugging%5Frails%5Fapplications.html?ref=til.codes) about debugging - ActiveRecord logger [class](http://apidock.com/rails/ActiveRecord/Base/logger/class?ref=til.codes) ### How to fix Npm install failed with "cannot run in wd" URL: https://til.codes/npm-install-failed-with-cannot-run-in-wd-2/ Last updated: 2018-02-21T06:35:10.000Z I was trying to upgrade my blogs ghost version to 0.9(which btw has some cool features) and I stumbled upon an issue when trying to upgrade the packages. Every time I run ``` sudo npm install --production ``` on my server, I get the following error: ``` npm WARN cannot run in wd ghost@0.9.0 node core/server/utils/npm/preinstall.js (wd=/var/www/ghost) ``` On digging a bit deep, I found that NPM tries to downgrade its privileges when it runs scripts. That downgrading the privileges causes this error. **TL;DR:** So the workarounds are: 1. Run `sudo npm run postinstall` manually. OR 2. Run `npm install --unsafe-perm` OR 3. Run `sudo chown -R my_name /usr/local` References: 1.Fixing npm permissions:[https://docs.npmjs.com/getting-started/fixing-npm-permissions](https://docs.npmjs.com/getting-started/fixing-npm-permissions?ref=til.codes) 2.[https://docs.npmjs.com/misc/scripts#user](https://docs.npmjs.com/misc/scripts?ref=til.codes#user) ### Copy file from host machine to docker container URL: https://til.codes/copy-file-from-host-machine-to-docker-container/ Last updated: 2016-12-16T06:14:05.000Z The `docker cp` utility copies the contents of `SRC_PATH` to the `DEST_PATH`. You can copy from the container’s file system to the local machine or the reverse, from the local filesystem to the container. If `-` is specified for either the `SRC_PATH` or `DEST_PATH`, you can also stream a tar archive from STDIN or to STDOUT. The CONTAINER can be a running or stopped container. The SRC\_PATH or DEST\_PATH can be a file or directory. ``` Usage: docker cp [OPTIONS] CONTAINER:SRC_PATH DEST_PATH | - docker cp [OPTIONS] SRC_PATH | - CONTAINER:DEST_PATH Copy files/folders between a container and the local filesystem -L, --follow-link Always follow symbol link in SRC_PATH --help Print usage ``` For example: ``` docker cp foo.txt mycontainer:/foo.txt docker cp mycontainer:/foo.txt foo.txt ``` Reference: [Docker CLI docs for cp](https://docs.docker.com/engine/reference/commandline/cp/?ref=til.codes) ### How to load rake task from a custom file or directory URL: https://til.codes/load-rake-files-and-run-tasks-from-other-files/ Last updated: 2016-07-12T12:58:12.000Z If you are wondering how you can load a rake task from a custom file, then this is guide will help you to accomplish your task. Lets assume you have a rake task named `hello.rake` under your project directory's lib folder like: ``` /lib/tasks/hello.rake ``` Then you can create a simple `Makefile` in your directory to load it like this: ``` Dir.glob('lib/tasks/*.rake').each { |r| load r} ``` Of course, this will load all files ending with the rake extension. You can simply load `hello.rake` like this: ``` load './lib/tasks/hello.rake' ``` To see all the tasks that have been loaded use ``` rake -T ``` Note that we've used lib/tasks since that's the standard approach taken by Rails applications. You could use assets or whatever you prefer, though I prefer lib/tasks even in non-Rails projects. Alternatively, you can either put your tasks into `rakelib/` folder which rake loads by default or add a specific folder in your Rakefile via: ``` Rake.add_rakelib 'lib/tasks' ``` ### docker-compose up is slow on docker for mac os beta URL: https://til.codes/docker-compose-slow-on-docker-for-mac-os-beta/ Last updated: 2016-12-16T06:12:18.000Z I have been trying with Docker beta on Mac OS and was facing an issue with docker-compose. It was taking ages for the docker-compose commands to get executed. After digging into a while I figured out that the dns lookup was causing the issues. docker-compose is trying to resolve `localunixsocket.local`. You can get insight into the dns lookup by running ``` sudo tcpdump -A -s0 -nni en0 port 53 ``` For now I've pointed `localunixsocket.local` to localhost in my `/etc/hosts`. Now everything is speedy again. To do the same, you need to edit `/etc/hosts` file and add the contents: ``` 127.0.0.1 localunixsocket.local ``` Hope someone finds this useful ### Injecting auth-headers into angular.js application using http.config vz using interceptors. URL: https://til.codes/injecting-auth-headers-into-angular-js-application-using-http-config-vz-using-interceptors/ Last updated: 2016-12-16T06:11:50.000Z We have been building a client app in Angular.js and decided on using token auth with JWTs, pretty much the normal choice. We got everything set up on the server side and am receiving the token on the client side, but now we just need to send the token with every request. At this point, our front-end developer bought an interesting point to debate. Should we be using an interceptor or should we add headers using $httpProvider config Most resources out there say to use an $http interceptor to transform every outgoing request, but doesn't specify the reason as to why the approach is better than the other. I thought of writing it to give some insight on the internals of the same: ###### default headers vz $http interceptors. The big difference between the two is that the providers are only executed once, during the config phase of the module `( .config() )` Interceptors are called on the fly and are much more flexible with what you can do with them. ###### When to use providers Use providers to set global config options where these options are static, never changing during the lifecycle of your app. An example use case would be in a situation where you could use $httpProvider to set a token header, say for example if you're using a third-party API and they require an API\_KEY on every request. Generally, API\_KEY's don't change, so it would make sense that your application config phase would set the API\_KEY in the provider, again abiding by the "static only settings" idea. ###### When to use interceptors Use interceptors, to apply values that may dynamically change through the life cycle of your application. The best illustration of interceptors would be to use and set Authentication headers or tokens that need to be sent along with each request. These tokens can change, and also might expire after a certain time. An example code for injecting an interceptor for sending an authentication token along with each request is as follows: ``` module.factory('authInjector', ['AuthService', function(AuthService) { var authInjector = { request: function(config) { if (!AuthService.isAnonymus) { config.headers['Authentication-token'] = AuthService.token; } return config; } }; return authInjector; }]); module.config(['$httpProvider', function($httpProvider) { $httpProvider.interceptors.push('authInjector'); }]); ``` ###### TL;DR The best way to do it is through the interceptor. Doing the setup in the `.config()` or `.run()` should be preferred for values that don't change over the lifecycle of the app. ### How to enable support for CORS with custom headers like authentication in Rails URL: https://til.codes/how-to-enable-support-for-cors-with-custom-headers-like-authentication-in-rails/ Last updated: 2016-12-16T06:11:17.000Z I was building a client side application for my API that I built using Rails/Grape. So in order to access the resources using the API from the client application the first thing that I need to do was to enable CORS support in the API. ### Enabling CORS using rack-cors gem I did everything as was said in [README](https://github.com/cyu/rack-cors?ref=til.codes), that is updated Gemfile accordingly and updated application.rb like this: ``` module YourApp class Application < Rails::Application # ... config.middleware.use Rack::Cors do allow do origins '*' resource '*', :headers => :any, :methods => [:get, :post, :options] end end end end ``` It was pretty much straight forward, and now I have the CORS support enabled. My client side app should be able to access the API easily. ### Problem: Custom Headers like Authorization. Now that I had the CORS setup, I tried to authenticate the user using JWT, and the authorization header. I was stumped to see that the server is not returning the authorization headers. After some dabbling with the code, I figured out that the authorization headers were not accessible only from another domain - Something wrong with the CORS setup. ###### Reason: Custom headers were not exposed by default with the rack-cors gem. By default, the rack-cors gem only exposes certain values like `Content-Type`, `Last-Modified` etc. So the authorization header that I was setting wasn't being exposed over to the cross-origin requests. ###### Expose custom headers. To expose any custom headers, we need to explicitly specify the keys in our configuration. ``` config.middleware.use Rack::Cors do allow do origins '*' resource '*', :headers => :any, :expose => ['access-token', 'expiry', 'token-type', 'Authorization'], :methods => [:get, :post, :options, :delete, :put] end end ``` So adding the `expose` key to the rack-cors configuration now exposes any custom headers like Authorization header ### How to install a specific package version in Alpine and Docker? URL: https://til.codes/how-to-install-a-specific-package-version-in-alpine-and-docker/ Last updated: 2016-12-16T06:10:20.000Z I was building a docker image for a backend API application that I was working on, so that my colleague, who is a front-end guy can easily setup the docker container and get it up and running. I chose Alpine Linux to build the Docker image bcos its light weight. I started adding dependencies to the Dockerfile and I came across a situation where I needed to use a specific version of the package. So this was what I had in my Dockerfile: ``` RUN apk update && \ apk upgrade && \ apk add ruby ``` And I needed Ruby 2.2.4 version to be installed in the image. With the above code, it installs the latest version of the Ruby which is 2.3. After some googling, I figured out that we can specify the version and lock it down so that the package manager will use that specific version of the package. So here is the same code now: ``` RUN apk update && \ apk upgrade && \ apk add ruby=2.2.4 ``` Exploring the options, we can set a minimum or maximum version to any package using ``` apk add 'packagename<1.2.3-suffix' ``` or ``` apk add 'packagename>1.2.3-suffix' ``` Hope this helps someone or even me in future. :) ### TDD in Elixir with ExUnit and Doctest URL: https://til.codes/tdd-in-elixir-with-exdoc-and-doctests/ Last updated: 2016-12-16T06:09:25.000Z ###### ExUnit & DocTest Elixir has it's own test framework built in natively, called ExUnit. ExUnit is a core component of Elixir itself, as much as the task runner and dependency manager mix. When you start a new project with mix, everything is directly set up for you, including basic unit tests for your first module and preconfigured tasks to do TDD right away. In addition to traditional test suites, there is also another (often overlooked) feature, called DocTests. This means that you can copy a sample IEx call of your function, including the result, and paste it directly into a documentation block above the function in your code. When you run your test suite, these snippets are picked up by ExUnit and executed along with your tests! Using these DocTests you can ensure that comments will say the truth, even if someone changes the code of the function (because a test error will pop up that wants to be fixed). ###### When not to use doctest In general, doctests are not recommended when your code examples contain side effects. For example, if a doctest prints to standard output, doctest will not try to capture the output. Similarly, doctests do not run in any kind of sandbox. So any module defined in a code example is going to linger throughout the whole test suite run. Example: An example app can be found [here](https://github.com/manusajith/schizo%5Fexample%5Felixir?ref=til.codes): ``` defmodule SchizoTest do use ExUnit.Case doctest Schizo test "uppercase doesnt change the first word" do assert(Schizo.uppercase("foo") === "foo") end test "uppercase converts the second word to uppercase" do assert(Schizo.uppercase("foo bar") == "foo BAR") end test "uppercase converts every other word to uppercase" do assert(Schizo.uppercase("foo bar baz whee") == "foo BAR BAZ WHEE") end test "unvowel doesnt change the first word" do assert(Schizo.unvowel("foo") === "foo") end test "unvowel removes the second words vowels" do assert(Schizo.unvowel("foo bar") == "foo br") end test "unvowel removes every other words vowels" do assert(Schizo.unvowel("foo bar baz whee") == "foo br bz wh") end end ``` and the accompanying code for the above test: ``` defmodule Schizo do @moduledoc """ A nice module that lets you upcase or unvowel from every other word in a sentence """ @doc """ Uppercase every other word in a sentence, Example: iex> Schizo.uppercase("you are awesome") "you ARE AWESOME" """ def uppercase(string) do transformer(string, &upcaser/1) end @doc """ Removes vowels from every other word in a sentence. Example: iex> Schizo.unvowel("you are silly") "you r slly" """ def unvowel(string) do transformer(string, &unvoweler/1) end defp transformer(string, transformation) do string |> String.split() |> Stream.with_index |> Enum.map(transformation) |> Enum.join(" ") end defp upcaser(input) do transform(input, &String.upcase/1) end defp unvoweler(input) do transform(input, fn (word) -> Regex.replace(~r/[aeiou]/, word, "") end) end defp transform({word, index}, transformation) do case {word, index} do {word, 0} -> word _ -> transformation.(word) end end end ``` ### How to fix Incomplete response received from application from nginx / passenger in a rails application URL: https://til.codes/how-to-fix-incomplete-response-received-from-application-from-nginx-passenger-in-a-rails-application/ Last updated: 2016-12-16T06:08:06.000Z I was trying to deploy a simple rails application( stack is Ruby 2.2.4, Rails 4.2.5, with passenger/Nginx as application/web server) to AWS EC2 and I was getting a weird error. ``` Incomplete response received from application ``` And looking at the Nginx/passenger logs I wasn't able to figure out the issue first hand. Then == I tried to debug in detail and changed the `passenger_app_env` to development == so that I can see the debug logs in details and voila.!! I figured out how stupid I was. Checking the logs, I see: ``` app error: Missing `secret_token` and `secret_key_base` for 'production' environment ``` \== Yep, you got it right, I forgot to set the `secret_token` in my env, and it caused the error. == After generating the `secret_token` using ``` RAILS_ENV=production rake secret ``` and exporting it to the bash env using: ``` export SECRET_KEY_BASE=secret_token ``` and restarting Nginx using ``` touch tmp/restart.txt ``` I was able to bring my rails app online. As they say: > Making mistakes simply mean you are learning faster. \~Weston H. Agor Hope I save someone's time in future. :) ### Tail Call Optimisation URL: https://til.codes/tail-call-optimisation-eplained/ Last updated: 2016-05-27T19:31:53.000Z Tail Call Optimization is the process by which a smart compiler can make a call to a function and take no additional stack space. The only situation in which this happens is if the last instruction executed in a function is a call to another function or the function itself. The most common use is tail-recursion, where a recursive function written to take advantage of tail-call optimization can use constant stack space. Normally during a recursion, the runtime needs to keep track of all the recursive calls, so that when one returns it can resume at the previous call and so on. Keeping track of all the calls takes up space, which gets significant when the function calls itself a lot. But with tail call optimization, it can just say "go back to the beginning, only this time change the parameter values to these new ones." It can do that because nothing after the recursive call refers to those values. Note first of all that not all languages support it. Scheme is one of the few programming languages that guarantee in the spec that any implementation must provide this optimization (JavaScript will also, once ES6 is finalized), so here are two examples of the factorial function in Scheme: In plain terms, TCO applies to a special case of recursion. If the last thing you do in a function is call itself (e.g. it is calling itself from the "tail" position), this can be optimized by the compiler to act like iteration instead of standard recursion. The following example of calculating Factorial in elixir explain how Tail Call Optimization can be done. ``` defmodule Factorial do def of(0), do: 1 def of(n) when n > 0 do # Not tail call optimized # because recursion needs to # occur before multiplication n * of(n - 1) end end ``` ``` defmodule Factorial do def of(0), do: 1 def of(n), do: of(n, 1) def of(1, acc), do: acc def of(n, acc) when n > 1 do # Tail call optimized # because recursion is the # last calculation of(n - 1, acc * n) end end ``` ### Active Model Serializer vz Jbuilder vz Rabl vz Grape-entity for rendering JSON URL: https://til.codes/active-model-serializer-vz-jbuilder-vz-rabl-vz-grape-entity-vz-roar/ Last updated: 2016-05-25T13:14:08.000Z When architecting an API only application one the of the most important points of discussion among the team members is which framework to use ? - Should we be using Rails and render JSON ? - Should we be using Grape ? - Should we be using Rails-API ? Also, how are we gonna format the JSON responses? Options that usually pop up include: - Jbuilder - ActiveModelSerializer - Rabl - Grape-Entity Anyways I am not gonna favour one over the other in this article, the choice of these options entirely depend on the use case, but instead, I will run via each of the options quickly to give a basic idea of each. ###### Jbuilder Jbuilder is bundled with Rails, and so it's one of the most popular choices. Jbuilder provides a nice DSL to format and structure your JSON response. It provide us with an easy way to define exactly what attributes are included in and how the response is formatted and nested. For generating json responses we need to create correspon corresponding `/app/views/` directories just like you would with view templates but with the extension `.json.jbuilder` ``` # app/views/article/show.json.jbuilder json.content format_content(@article.content) json.(@article, :created_at, :updated_at) json.author do json.name @article.author.name.familiar json.url url_for(@article.author, format: :json) end ``` ###### RABL Another alternative is RABL, it also provides a custom DSL for generating JSON responses. It works by creating a view with the extension `.rabl`, and defining which attributes, nodes, and relations you wish to include in the JSON response. ``` # app/views/posts/index.rabl collection @posts attributes :id, :title, :subject child(:user) { attributes :full_name } node(:read) { |post| post.read_by?(@user) } ``` ###### Active Model Serializer Active Model Serializer is a great way to build JSON responses using an object oriented approach. It separates the serialization concern into its own folder /app/serializers, comes with its own Rails generator, and it behaves more like ActiveRecord in that you can define associations in the serializer. It also allows you to choose your adapter-to decide what type of JSON structure is produced-or to build your own. Popular supported formats are JSON-API and JSON-HAL. It can also act as a presenter where you can define custom methods to display extra information or override how it’s displayed in your JSON. ###### ActiveModelSerializer and Rails 5 - API mode With Rails 5, rails-api comes in bundled and when you generate a API only Rails app with ActiveModelSerializer is the default choice. ``` class PostSerializer < ActiveModel::Serializer attributes :title, :body has_many :comments url :post end ``` ###### Grape Entity Grape Entity was extracted from Grape, which is a popular gem used for building RESTful APIs. Similarly to RABL and Jbuilder, it provides a DSL for defining entities which are the structure of your JSON response. ``` module API module Entities class Article < Grape::Entity expose :title expose :content, documentation: { type: "Text", desc: "Blog post." } expose :author_info do expose :email expose :full_name end end end end ``` ###### ROAR ROAR allows you to build presenter classes to represent your data. It also supports JSON and XML responses. ``` require 'roar/json' module PostRepresenter include Roar::JSON property :title end ``` ###### ActiveModel or Plain Ruby Hash This may seem like a strange thing to point out, but for very simple cases, you can simply call the to\_json method on either an ActiveModel object or a native Ruby Hash. ``` # Using an @organization model respond_to do |format| format.json do render json: @organization.to_json end end # Using a plain Ruby Hash respond_to do |format| format.json do render json: { name: @user.name, email: @user.email }.to_json end end ``` ###### Benchmarks [Kirill Platonov](https://twitter.com/platonov%5Fkd?ref=til.codes) has an interesting benchmark with ActiveModelSerializer vz Jbuilder. The following is an excerpt from his [article](http://kirillplatonov.com/2014/11/04/active%5Fmodel%5Fserializer%5Fvs%5Fjbuilder/?ref=til.codes) > You can see everything by yourself. Jbuilder wins ActiveModel::Serializers only in the standalone variant of usage and only when use transform only single AR object, not an array. But the difference between Jbuilder and AMS in this case only 1.06x, it's very close to calculating error. > And from opposite you can see, that in all other cases Jbuilder is much slower than ActiveModel::Serializers. And it's the real world cases because Jbuilder is mostly used with render method. And the results with render method are extremely bad for Jbuilder: > 11.95x slower than AMS with a single object > 8.94x slower than AMS with array of objects > I think for now there is really no reason to use Jbuilder. It's very slow, too verbose and too complex for such simple task as transforming objects to JSON. You can use ActiveModel::Serializers and it will be handy and will cover all of your usage cases. ###### Conclusion Now that you have various options at hand, I leave you to decide what is the best suited for your app. > Already know you that which you need. > \-- Yoda ### Error handling with Grape, Rails and ActiveRecord CanCan URL: https://til.codes/error-handling-with-grape-rails-and-activerecord-cancan/ Last updated: 2016-05-05T07:33:52.000Z I have been working on a project developing the backend API using Rails and Grape. One fine morning I pulled the latest code from the git repo that my colleague wrote, and was going to review the code. As usual, the first thing I did was to run the Integrations specs and to my surprise, some of the specs were failing. The failing specs were mostly around the Sad path, and looking deeper I realized that the exceptions were not being handled properly in the code and was causing the specs to fail. So, given the scenario, I wanted to handle the exceptions generated by the Grape API in a much clean and DRY way as these exceptions are ought to happen in the API any time. So I decided to write a concern, by extending the ActiveSupport::Concern to handle all the exceptions that the app generates. So here is an abstract of the code that I came up with. ``` module API module V1 module ExceptionsHandler extend ActiveSupport::Concern included do rescue_from :all do |e| # When required params are missing or validation fails if e.class.name == 'Grape::Exceptions::ValidationErrors' error!(e.message, status: 406) # Bad token elsif e.class.name == 'RuntimeError' && e.message == 'Invalid base64 string' error!('401 Unauthorized', status: 401) # AccessDenied - Authorization failure elsif e.class.name == 'CanCan::AccessDenied' error!('You don\'t have permissions.', status: 403) # Record not found elsif e.class.name == ActiveRecord::RecordNotFound do |e| error!(e.message, status: 404) # When all hell broke loose else Rails.logger.error "\n#{e.class.name} (#{e.message}):" e.backtrace.each { |line| Rails.logger.error line } Rack::Response.new({ error: '400 Bad Request', errors: e.errors }.to_json, 400) end end end end end end ``` You can add your own custom exceptions to the case. Some of the exceptions that Grape supports can be found [here](http://www.rubydoc.info/github/intridea/grape/Grape/Exceptions/ValidationErrors?ref=til.codes) Hope someone finds this helpful. ### Using helper methods and helper modules in Rails Grape to keep the code DRY URL: https://til.codes/using-helper-methods-and-helper-modules-in-rails-grape-to-keep-the-code-dry/ Last updated: 2016-05-05T08:42:49.000Z I was working with a Grape API and got into a situation where I was kind of reimplementing some methods on more than one mounted API. So naturally, I wanted to refactor the code, keep it DRY and extract it as helper methods and eventually into helper module. So here is what my base API class looks like: ``` # /app/api/api.rb class API < Grape::API format :json version 'v1', using: :path mount V1::A mount V1::B end ``` and here goes my helper module, nothing fancy, just pretty basic stuff: ``` # /app/api/v1/helpers/authentication_helpers.rb module V1 module Helpers module AuthenticationHelpers extend Grape::API::Helpers def current_operator @current_operator ||= authenticate! end def authenticate! # Handle the authentication stuff end end end end ``` So now we have our AuthenticationHelpers available, we can just go ahead and include them in any or every API that is mounted. ###### Loading helpers for every mounted API We can include and load our AuthenticationHelper across all the mounted APIs by just including them in our base class and then making all other classes inherit from the base class API. ``` # /app/api/api.rb class API < Grape::API include V1::Helpers format :json version 'v1', using: :path mount V1::A mount V1::B end class A < API # ... end class B < API # ... end ``` ###### Loading helpers for a single mounted API If we just want to include our AuthenticationHelper for one or two APIs only, we can go ahead and just include them on the individual classes. ``` # /app/api/v1/a.rb module V1 class A < Grape::API helpers V1::Helpers # ... end end # /app/api/v1/B.rb module V1 class B < Grape::API helpers V1::Helpers #... end end ``` ### JSON Web Tokens explained and how to use JWT in authentication with APIs URL: https://til.codes/json-web-tokens-explained-and-how-to-use-jwt-in-authentication-with-apis/ Last updated: 2016-05-25T16:18:58.000Z ### Introduction Over the last few years web application development has seen drastic changes, with the front-end frameworks coming to the scene, the popularity of hybrid applications, mobile first strategy, rising demand for SPAs, Microservices etc. In older times, both the frontend and the backend would be in a single place, but this has changed over the years now. Using separate client-side applications is a common choice now. It definitely has a lot of advantages, but it also has brought significant changes on the architecture as well. The RESTful API service model has been used a great amount recently in applications. Nowadays our back-end is more about complex business logic and data while presentation logic is moved exclusively to the front-end or mobile applications using frameworks like Angular/React/Ember etc Also in most cases the application would need to interface with 3rd party services for fetching data. Interfacing with the 3rd party apps are often done using the APIs. With these changes in the architecture, sharing data securely has also changed a lot. Previously cookies and server-based authentication was the easiest and go to solution. However, with the client side frameworks in place handling authentication in modern Mobile and Single Page Applications can be tricky and needs to be addressed carefully. The best-known approaches for implementing authenticated endpoints for the APIS are either using OAuth 2.0 or the token based authentication using JSON Web Token (JWT). ### What are JSON Web Tokens? The official draft of the JSON Web Tokens (JWT) is as below: ###### Abstract from the draft specification: > JSON Web Token (JWT) is a compact, URL-safe means of representing claims to be transferred between two parties. The claims in a JWT are encoded as a JSON object that is used as the payload of a JSON Web Signature (JWS) structure or as the plaintext of a JSON Web Encryption (JWE) structure, enabling the claims to be digitally signed or integrity protected with a Message Authentication Code (MAC) and/or encrypted. ###### Or in plain terms: A JSON Web Token, or JWT, is a cryptographically signed token which can be used to send information that can be verified and trusted by means of a digital signature. This token can be verified against the signature to verify its authenticity. Since the token is encrypted we can include sensitive information like authorization headers in the payload, ###### Characteristics of JWT: JWTs are self-contained: A JWT mainly has 3 parts, basic information about the token itself, a payload which can be sensitive information, and a signature. All these information are passed along with the JWT itself. JWTs can be passed around easily: Since JWTs are self-contained, and encrypted they can be easily transmitted inside a HTTP header. ### What is the JSON Web Token structure? JSON Web Tokens are self-contained and consist of three parts separated by dots (.), which are: - Header - Payload - Signature Therefore, a typical JWT looks like the following. ``` header.payload.signature ``` Let's break down the different parts. ##### Header The header part of a JWT mainly consists of information related to the token itself. It has two parts: - the type of the token, which is JWT, - the hashing algorithm being used, such as HMAC SHA256 or RSA. For example: ``` { "alg": "HS256", "typ": "JWT" } ``` The above details are Base64Url encoded to form the header of a JWT ##### Payload Payload is the second part of the JWT. It may include confidential information like user details, any other additional metadata etc. These are also called claims. There are three types of claims: reserved, public, and private claims. ###### Reserved claims: The standard defines a set of predefined claims. These are are not mandatory but hightly recommended. Some of them are: ``` - iss: The issuer of the token - sub: The subject of the token - aud: The audience of the token - exp: This will probably be the registered claim most often used. This will define the expiration in NumericDate value. The expiration MUST be after the current date/time. - nbf: Defines the time before which the JWT MUST NOT be accepted for processing - iat: The time the JWT was issued. Can be used to determine the age of the JWT - jti: Unique identifier for the JWT. Can be used to prevent the JWT from being replayed. This is helpful for a one time use token. ``` ###### Public claims: Any custom claims can be defined in the public claims section. ###### Private claims: These are the custom claims created to share information between parties that agree on using them. Example Payload Our example payload has two registered claims (iss, and exp) and two public claims (name, admin). ``` { "iss": "til.codes", "exp": 1464178005, "name": "Manu S Ajith", "admin": true } ``` Payloads are also Base64Url encoded to form the second part of the JWT. ##### Signature To create the signature for a JWT, we first need to take the encoded header, the encoded payload, a secret, the algorithm specified in the header, and using that sign that. The commonly used algorithm is HMAC SHA256. We can create the signature using the above header, payload, secret, and HMAC SHA256 algorithm in the following way: ``` HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret) ``` This signature is used to verify the authenticity of the sender, and also makes sure that the token hasn't tampered. ##### Combining all the parts: We can get our final JWT by combining the three Base64 strings separated by dots The following shows a JWT that has the previous header and payload encoded, and it is signed with a secret. ``` eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJ0aWwuY29kZXMiLCJleHAiOjE0NjQxNzgwMDUsIm5hbWUiOiJNYW51IFMgQWppdGgiLCJhZG1pbiI6dHJ1ZX0.fr2Vf8CzYzZ6swM0GRgmh3M_6VPYDnSBwlIJS99Bkf8 ``` ### When should you use JSON Web Tokens? Some of the most common use case of JWT are authentication and information exchange. Authentication: This is the most common use case for JWT. The authentication would be done using token based strategy, and with each subsequent request a JWT is passed back to the server. This JWT is used to permit or deny access to routes, services, and resources for that user. Applications using the Single Sign On strategy heavily leverges JWT because of its small overhead and its ability to be easily used across different domains. Information Exchange: JWT are a good way of securely exchanging confidential information between applications because they are signed and ecrypted. Additionally, as the signature is calculated using the header and the payload, you can also verify that the content hasn't been tampered with. ### Advantages of Token-Based Authentication and JWT **Stateless, easier to scale:** The JWT contains all the necessary information to identify a particular user. It can be sent to any server among a cluster of servers under a load balancer, not necessarily to the server that issued the token, or the server that the user logged in initially with. **Reusability:** We can reuse the token generated across multiple servers and multiple platforms and domains. Applications using Single Sign On (SSO) are typical examples. **Security:** Since the tokens are signed we can transmit the information securely over HTTP. Additionally, we can encrypt the tokens using JWE and also use an SSL and transmit over HTTPS **Performance:** We can completely avoid deserializing the sessions back on the server on each request. We can use any of the servers to authenticate the token, validate it and parse the contents ### How do JSON Web Tokens work with authentication ? Upon successfully authenticating a user using his credentials(email/password combinication), the server issues a JWT. This token is used to authenticate the user in all further requests. This can be saved on the client side either in local storage, or cookies. Whenever the user wants to access a authenticated/authorized route or resource, the client app should send the JWT that was issued previously, typically in the Authorization header using the Bearer schema. The content of the header should look like the following: ``` Authorization: Bearer ``` Since token based authenticate is stateless the user state is never saved in server memory. Each time the server gets a request to a protected route, it will check the validity of the JWT and the Authorization Header. Upon successfully decoding the token it will grant access to the resource if the user has permission to the same. As JWTs are self-contained, it can even be used to store access levels for a particular user, reducing the need to query the database multiple times. ### Conclusion The JSON Web Token standard can be used across multiple languages, frameworks and is quickly and easily interchangeable. You can use the token in a URL, POST parameter, or an HTTP header. The versatility of the JSON Web Token lets us authenticate an API quickly and easily by passing information through the token. ### Docker difference between run, cmd, entrypoint commands URL: https://til.codes/docker-run-vs-cmd-vs-entrypoint/ Last updated: 2016-11-09T10:33:22.000Z If you have built a docker image, you would be familiar with the commands RUN, CMD, ENTRYPOINT. While some of you know what these means, where to use those and when to use those, there are some who might not know the exact difference between those commands. These commands are quite similar and can cause confusion to the newbies. So I thought of scribbling it down to give a clear idea of what these commands are and when to use what. ### TL;DR: In short - RUN executes the command(s) that you give in a new layer and creates a new image. This is mainly used for installing a new package. - CMD sets default command and/or parameters, however, we can overwrite those commands or pass in and bypass the default parameters from the command line when docker runs - ENTRYPOINT is used when yo want to run a container as an executable. If you are interested in the longer version, describing the details in detail, please read on. Before I explain the commands in detail, it would be good to mention a few basics about docker. ### Docker images and layers When Docker runs a container, it runs an image inside it. This image is usually built by executing a series of Docker instructions, which add layers on top of existing image or OS distribution. OS distribution is the initial image and every package is added as a new layer on top of that. ### Shell and Exec forms Commands in docker can be specified either in shell form or the Exec form. All three instructions (RUN, CMD, and ENTRYPOINT) too can be specified in shell form or exec form. ###### Shell form ``` ``` Examples: ``` RUN apk --update add install ruby CMD echo "Hello world" ENTRYPOINT echo "Hello world" ``` When an instruction is executed in shell form it calls `/bin/sh -c ` under the hood and normal shell processing happens. For example, the following snippet in Dockerfile ``` ENV name Manu ENTRYPOINT echo "Hello, $name" ``` when container runs as `docker run -it ` will produce output ``` Hello, Manu ``` Note that variable name is replaced with its value. ###### Exec form This is the preferred form for CMD and ENTRYPOINT instructions. ``` ["executable", "param1", "param2", ...] ``` Examples: ``` RUN ["apk", "add", "ruby"] CMD ["/bin/echo", "Hello world"] ENTRYPOINT ["/bin/echo", "Hello world"] ``` When an instruction is executed in exec form it calls executable directly, and shell processing does not happen. For example, the following snippet in Dockerfile ``` ENV name Manu ENTRYPOINT ["/bin/echo", "Hello, $name"] ``` when container runs as `docker run -it ` will produce output ``` Hello, $name ``` Note that variable name is not substituted. ###### How to run bash/zsh? If you need to run bash (or any other interpreter like zsh but sh), use exec form with `/bin/bash` as executable. In this case, normal shell processing will take place. For example, the following snippet in Dockerfile ``` ENV name Manu ENTRYPOINT ["/bin/bash", "-c", "echo Hello, $name"] ``` when container runs as `docker run -it ` will produce output ``` Hello, Manu ``` So hope you now have a clear idea of the 2 forms that can be used for specifying our commands. Now let us see the different commands and how to specify those using the 2 forms shell and exec. #### RUN As mentioned above, the RUN command is mainly used to install a new package on top of the main OS distribution. When you use the RUN command, it will execute the instruction and will create a new layer. RUN command can be used in two forms: ``` 1. Shell form RUN 2. Exec form RUN ["executable", "param1", "param2"] ``` A good example demonstrating the RUN instruction would be to install multiple system packages that are needed for your image. ``` RUN apk --update && \ apt add ruby \ ruby-json \ ruby-nokogiri \ git ``` Note that `apk --update` and `apt add` are executed in a single RUN instruction. This is done to make sure that the latest packages will be installed. If `apk add` were in a separate RUN instruction, then it would reuse a layer added by `apt --update`, which could had been created a long time ago. #### CMD CMD instruction allows you to set a default command and default parameters which will be executed when docker is run. But these commands and parameters can be overwritten by passing the values over the command line. CMD can be specified in three forms: ``` 1. exec form, preferred way CMD ["executable","param1","param2"] 2. (sets additional default parameters for ENTRYPOINT in exec form) CMD ["param1","param2"] 3. Shell form CMD command param1 param2 ``` Again, the first and third forms should look familar to you as they were already covered above. The second one is used together with ENTRYPOINT instruction in exec form. It sets default parameters that will be added after ENTRYPOINT parameters if container runs without command line arguments. Let's have a look how CMD instruction works. The following snippet in Dockerfile ``` CMD echo "Hello world" ``` when container runs as `docker run -it ` will produce output ``` Hello world ``` but when container runs with a command, e.g., `docker run -it /bin/bash`, CMD is ignored and bash interpreter runs instead: ``` root@7de4bed89922:/# ``` #### ENTRYPOINT ENTRYPOINT instruction should be used when you need your container to be run as an executable. I might look similar to CMD, but in fact, it is different and should be used in a different context The difference is ENTRYPOINT is that unlike CMD, the command and parameters are not ignored when Docker container runs with command line parameters. (There is a way to ignore ENTTRYPOINT, but it is unlikely that you will do it.) ENTRYPOINT instructions too can be written in two forms: ``` 1. Executable form preferred way ENTRYPOINT ["executable", "param1", "param2"] 2. Shell form ENTRYPOINT command param1 param2 ``` ###### Exec form Exec form of ENTRYPOINT allows you to set commands and parameters and then use either form of CMD to set additional parameters that are more likely to be changed. ENTRYPOINT arguments are always used while CMD ones can be overwritten by command line arguments provided when Docker container runs. For example, the following snippet in Dockerfile ``` ENTRYPOINT ["/bin/echo", "Hello"] CMD ["world"] ``` when container runs as `docker run -it ` will produce output ``` Hello world ``` but when container runs as `docker run -it ` Manu will result in ``` Hello Manu ``` ###### Shell form Shell form of ENTRYPOINT ignores any CMD or docker run command line arguments. ### Conclusion Use RUN instructions to build your image by adding layers on top of the initial image. Prefer ENTRYPOINT to CMD when building executable Docker image and you need a command always to be executed, and use CMD if you need to provide extra default arguments that could be overwritten from the command line when docker container runs. Choose CMD if you need to provide a default command and/or arguments that can be overwritten from the command line when docker container runs. ### Behaviours in Elixir explained URL: https://til.codes/behaviours-in-elixir-explained/ Last updated: 2016-05-17T19:24:23.000Z Behaviours provide a way to define an interface which a module can implement. A module declares that it implements the Behaviour with the `@behaviour` annotation. The functions in the modules implementing the behaviour will be checked at compile time to see if they match the function specifications in the behaviour and if it doesn't it throws an error. Behaviours provide a way to: - define a set of functions that have to be implemented by a module; - ensure that a module implements all the functions in that set. ###### Defining behaviours If a module adopts the behaviour of another module, then it will have to define all the functions defined in the module that is said to behave. So in order to define the function specifications that a module needs to implement the `@callback` directive is used ``` defmodule Greeter do @callback shen_says_hello(String.t) :: any @callback po_replies(String.t) :: any end ``` ###### Adopting a behaviour from another module Modules adopting a behaviour will have to implement all the functions defined with the `@callback` directive. ``` # A module uses the @behaviour annotation to indicate that it implements a behaviour defmodule LordShenGreetsPo do @behaviour Greeter def shen_says_hello(name) do IO.puts "Greetings, #{name}, we meet at last !!" end def po_replies(name) do IO.puts "Hey, how ya doin'?, #{name}" end end # Since the following module does not implement po_replies/1 a compile time warning will occur: "warning: undefined behaviour function po_replies/1 (for behaviour Greeter)" defmodule LordShenGreetsPo do @behaviour Greeter def shen_says_hello(name) do IO.puts "Greetings, #{name}, we meet at last !!" end end ``` ### require vz import vz use vz alias directives in Elixir URL: https://til.codes/require-vz-import-vz-use-vz-alias-directives-in-elixir/ Last updated: 2016-05-17T18:41:12.000Z One of an essential feature of a language when developing software is to reuse the code or the modules. ###### What are directives ? So directives are a way via which we can reuse our code, import it and use it with other modules. ###### What are the available directives in Elixir When it comes to elixir, we are given 4 options or directives: - require - import - alias - use ###### Require directive We will be using macros across our code and there is a high chance that we would need to share these macros too. So let's say we have a module named `Foo` and we have a macro `a_macro` Now, in order to use that macro, we need to guarantee its module and implementation are available during compilation. This is done with the require directive. ``` iex> Foo.a_macro(1) ** (CompileError) iex:1: you must require Foo before invoking the macro Foo.a_macro/1 iex> require Foo nil iex> Foo.a_macro(1) true ``` An attempt to call a macro that was not loaded will raise an error. ###### Import directive import directive is used for easily accessing functions or macros from other modules without using the fully-qualified name. For instance, if we want to use the `duplicate/2` function from the List module several times, we can simply import it: ``` iex> import List, only: [duplicate: 2] nil iex> duplicate :ok, 3 [:ok, :ok, :ok] ``` In this case, we are importing only the function duplicate (with arity 2) from List. Although `:only` is optional, its usage is recommended in order to avoid importing all the functions of a given module inside the namespace. `:except` could also be given as an option in order to import everything in a module except a list of functions. import also supports `:macros` and `:functions` to be given to `:only`. For example, to import all macros, one could write: ``` import Integer, only: :macros ``` Or to import all functions, you could write: ``` import Integer, only: :functions ``` Note that import is lexically scoped too. This means that we can import specific macros or functions inside function definitions: ``` defmodule Math do def some_function do import List, only: [duplicate: 2] duplicate(:ok, 10) end end ``` In the example above, the imported List.duplicate/2 is only visible within that specific function. duplicate/2 won’t be available in any other function in that module (or any other module for that matter). Note that importing a module automatically requires it. ###### use directive Although not a directive, use is a macro tightly related to require that allows you to use a module in the current context. The use macro is frequently used by developers to bring external functionality into the current lexical scope, often modules. For example, in order to write tests using the ExUnit framework, a developer should use the ExUnit.Case module: ``` defmodule AssertionTest do use ExUnit.Case, async: true test "always pass" do assert true end end ``` Behind the scenes, use requires the given module and then calls the **using**/1 callback on it allowing the module to inject some code into the current context. Generally speaking, the following module: ``` defmodule Example do use Feature, option: :value end is compiled into defmodule Example do require Feature Feature.__using__(option: :value) end ``` With this we are almost finishing our tour about Elixir modules. The last topic to cover is module attributes. ###### Aliases directive At this point you may be wondering: what exactly an Elixir alias is and how is it represented? An alias in Elixir is a capitalized identifier (like String, Keyword, etc) which is converted to an atom during compilation. For instance, the String alias translates by default to the atom `:"Elixir.String":` ``` iex> is_atom(String) true iex> to_string(String) "Elixir.String" iex> :"Elixir.String" == String true ``` By using the `alias/2` directive, we are simply changing the atom the alias expands to. Aliases expand to atoms because in the Erlang VM (and consequently Elixir) modules are always represented by atoms. For example, that’s the mechanism we use to call Erlang modules: ``` iex> :lists.flatten([1, [2], 3]) [1, 2, 3] ``` This is also the mechanism that allows us to dynamically call a given function in a module: ``` iex> mod = :lists :lists iex> mod.flatten([1, [2], 3]) [1, 2, 3] ``` We are simply calling the function flatten on the atom `:lists`. ###### Summary alias to more conveniently reference modules ``` #Alias the module so it can be called as Bar instead of Foo.Bar alias Foo.Bar, as: Bar ``` require to ensure macros are available and compiled ``` #Ensure the module is compiled and available (usually for macros) require Foo ``` import to more conveniently reference functions ``` # Import functions from Foo so they can be called without the `Foo.` prefix import Foo ``` use to give module opportunity to "hook" into your code ``` # Invokes the custom code defined in Foo as an extension point use Foo ``` and all directives are lexically scoped. ### Install, configure and use git hooks to automate your workflow with distributed team URL: https://til.codes/install-configure-and-use-git-hooks-to-automate-your-workflow-with-distributed-team/ Last updated: 2016-05-05T14:03:08.000Z [My team](https://redpanthers.co/?ref=til.codes) uses Git as the center of our development process, leveraging all those cool features git provides us - git workflow, feature branching, switching branches for code reviews, Pull Requests and all such cool stuff. Using git is an integral part of our workflow, which means all the development happens over git branches, creating a Pull Request when you need a review, merging them into staging branch so that our CI servers can pick them up and get it deployed and so on. Tl;DR: I am not here to explain the benefits of git driven workflow(you already are aware of that). This article explains some use cases of git hooks to automate some of those redundant tasks that you just hate doing or completely forget about. ##### So where does git hooks comes in ? On any typical day, we work on our branch, switch to another branch for code review when someone in the team says - Hey can you hop in and see if this implementation can be optimized ? By using git, it helps us to do this branch switching. But switching git branches in between your work also brings in some downsides. This means we switch branches a lot, either to see someone’s code and review it or to kick off a new branch for a new feature. Over time this scenario seemed to happen a quite a few times: ###### Use case 1: Installing newly added dependencies, Running migrations, Generating new assets. > Switch to master and pull in updates > > Start working on a new feature in a new branch > > Write something and run tests > OMG!! the tests are failing..!! > Turn to the team in slack: "Guys, I’m getting an error on the stuff you added, did you get it?" > The team says: "No. Did you install the Gems and ran the migration ?" > Me: "Ah my bad! you got me there, did not run `bundle install`" or "Ah dammit, I forgot to migrate the Db" Developers often need to perform a set of tasks or operations when they pull a branch or checkout to a new branch, and if by any case these steps are missed you might end up having unexpected behavior. For the experienced pals, it is easy to debug, but for the inexperienced guys, it might take some time trying to figure out that they missed some basic steps that should have been done. ###### Use case 2: Prevent the developers from accidentally committing code which were used for testing or debugging. Developers often have the habit of leaving behind some of the debug statements that they wrote while developing or testing the code in the final branch, not intentionally!. Some of the debug statements just puts/emits some information like `console.log(), Rails.logger.info(), IO.puts` etc while some others can expose confidential information regarding the stack itself `puts caller`, and in worse case it might even have a full REPL code like `debugger, binding.pry, IEx.pry ` . And imagine the worse case when one of your developers leaves behind a `binding.pry` REPL, just like the one below to the final code and your CI is trying to run those tests.!! ``` # ... it 'should return status 422' do binding.pry expect(response.status).to eq(422) end # ... ``` BOOM!! - Your CI servers end up in a limbo waiting for someone to get itself out of that debug statement. ##### Git hooks to the rescue.! While there are many approaches preventing the above scenarios from happening, I chose to use a bunch of git hooks to achieve the objective. The benefit of using git hooks is that it can distribute to your teammates and can be reused. ###### What is a git hook? A git hook is basically a bash script that runs on certain occasions in the execution process when you are working with git. They will take out some of those redundant tasks that you just hate doing or completely forget about. They can do a bunch of tasks like check code style guide violations, security audits, check if debugger statements are present and a whole lot of useful stuff. ###### Different types of Git hooks. Git supports custom script triggers through hooks. These hooks give you a chance to inject functionality at particular points in the standard pipeline: Here are the basic available git hooks: - Before committing ("pre-commit") - Before writing a commit message ("prepare-commit-msg") - After writing a commit message ("commit-msg") - After committing ("post-commit") - Before a rebase ("pre-rebase") - After a checkout ("post-checkout") - After a merge ("post-merge") - Before receiving a push ("pre-receive") - After receiving a push ("post-receive") - Before receiving a push, run once per branch ("update") ###### Creating a Git Hook Git comes with a lot of pre-available hooks. These default available hooks are included in every repo that you create by default and these can be found inside your `.git/hooks` dir. To create a new hook, all we need to do is to create a file in the .git/hooks folder with the name of the hook you want to attach to, in my case `pre-commit`, since I want this to happen every time I am about to commit something. So here is what one of my `pre-commit` hooks looks like for a typical Rails project ``` #!/bin/bash # Grep through modified files for forbidden words and reject commit if found # Separate more file types with pipes here FILE_PATTERN='\.(js|html|rb|yml)(\..+)?$' # Separate more forbidden strings with spaces here FORBIDDEN=( console.log puts logger debugger binding.pry ) for i in "${FORBIDDEN[@]}" do git diff --cached --name-only | \ grep -E $FILE_PATTERN | \ GREP_COLOR='4;5;37;41' xargs grep --color --with-filename -n $i && \ echo 'Debugger code found:' $i 'Please remove them before commiting' && exit 1 done exit 0 ``` ###### Gotchas Note: Permissions of Hooks to Executable When you’ve written a git hook, don’t forget to make it executable. Git will not tell you why it’s skipped and go ahead and skip the hook altogether. Solution: ``` chmod ug+x .git/hooks/* ``` ###### Wrap up Git Hooks are pretty cool if your workflow is built around git like lots of teams do nowadays the hooks are a cool place to tie in your day to day stuff that tends to be forgotten and generate unneeded debugging and such. Take a look at available hooks and think about your routine, I’m pretty sure you will find something you can delegate out to git and stop worrying about it every day. As they say: > Anything that can be automated should be automated.!! ###### Further Reading: 1. [Git-scm Book](https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks?ref=til.codes) 2. [Git-scm docs](https://git-scm.com/docs/githooks?ref=til.codes) 3. [Atlassian Tutorial](https://www.atlassian.com/git/tutorials/git-hooks?ref=til.codes) ### Rails : Devise : Send different Emails for Confirmation based on the presence of attribute or parameter URL: https://til.codes/rails-devise-send-different-emails-for-confirmation-based-on-the-presence-of-attribute-or-parameter/ Last updated: 2016-05-05T09:51:07.000Z So what am I up to today? - I thought of scribbling down some pieces of code that I wrote to help one of my interns in my company to help complete a task. I was juggling with my daily work and I suddenly got a ping in our slack team > Hey can you please advise me on how to use separate mailer templates for devise confirmation instructions just based on the presence/absence of a parameter/attribute value ? I thought for a second and said, TL;DR: you need to tell devise to your custom mailer, override the `confirmation_instructions` method in the same and you can pass in the template you want to use as options to that method. ##### Now the long story for who needs step by step instructions. If you have a requirement that demands to send email when every time a user signup by his own or a user is invited into the system by an existing user and you may also want to send two different email template for two scenarios. By default Devise do not support multiple email template so you need to define your new Mailer inherited from `Devise::Mailer` and configure Devise to use the Mailer you defined. ###### Configuring devise to use custom Mailer ``` # in config/initializers/devise.rb # Configure the class responsible to send e-mails. # config.mailer = 'Devise::Mailer' config.mailer = 'CustomDeviseMailer' ``` and go ahead and create your `CustomMailer` inherited from `Devise::Mailer` ``` # in app/mailers/custom_devise_mailer.rb class CustomDeviseMailer < Devise::Mailer layout 'mailers' # To make sure that your mailer uses the devise views default template_path: 'devise/mailer' def confirmation_instructions(record, token, options={}) # Use different e-mail templates for normal signup e-mail confirmation # and for when a user is invited into the system by an existing user. if record.invited? options[:template_name] = 'invited_confirmation_instructions' else options[:template_name] = 'confirmation_instructions' end super end end ``` I hope someone finds this useful. ### Share button not working in Safari on OSX El-Capitan URL: https://til.codes/safari-share-button-not-working-on-osx-el-capitan/ Last updated: 2016-04-26T07:42:26.000Z ## Share button not working in Safari on OSX El-Capitan Since upgrading to the new El Capitain I have not been able to use the share button, one of the features that I liked the most with Safari. The strange thing was that it was working on my mac mini, while only my Macbook pro had the issue. So, I did the next obvious thing, Google to rescue! And here are some of the suggestions that I came across: > `Might be corrupted Safari preferences after the upgrade to El Capitan`. > > Quit Safari if it's open. > > Open a Finder window. From the Finder menu bar click Go > Go to Folder > > Type or copy paste the following: > > `~/Library/Preferences/com.apple.Safari.plist` > > Click Go then move the com.apple.Safari.plist file to the trash. > > Relaunch Safari then try the Share button. Another one: > As for the spinning beach ball when trying to load a web page. Try troubleshooting Safari plugins and extensions. `You may have third party software installed that's not compatible with Safari `. From the Safari menu bar `click Safari > Preferences` then select the `Extensions` tab. `Turn that OFF`, quit and relaunch Safari to test. If that helped, turn one extension on then quit and relaunch Safari to test until you find the incompatible extension then click uninstall. `If it's not an extensions issue, try troubleshooting third party plug-ins`. Back to Safari > Preferences. This time select the Security tab. Deselect: Allow all other plug-ins. Quit and relaunch Safari to test. If that made a difference, instructions for troubleshooting plugins here. None of the above were working for me and finally, I came across a thread: > `The bug is caused by System Integrity Protection, a new feature of El Capitan`. Just disable SIP, restart, check your system (everything should be back to normal), and then enable it again. And in-case you are wondering how to get that done: > Restart your machine and hold Cmd + R until you hear the chime. You'll enter recovery mode. Once there open a terminal and type the following: `csrutil disable` This will disable the System Integrity Protection. After that just restart the machine. All the widgets should be back. Repeat the process again but this time type in the Terminal: `csrutil enable` And restart. You should be good to go now. And finally I have the sharing option back in my Safari. And if you were wondering why the sharing feature was working on my mac mini - I was running the Apple Developer Program Beta Testing Mac OSx on my mac mini. Hope someone finds this helpful. ### Testing carrierwave file uploads with RSpec and FactoryGirl. URL: https://til.codes/testing-carrierwave-file-uploads-with-rspec-and-factorygirl/ Last updated: 2016-04-26T10:10:16.000Z TLDR; In this blog post, I am gonna focus only on testing carrierwave. So if you are looking for instruction on installing/setting up carrierwave in your project I would suggest you looking at the carrierwave's wiki over [here](https://github.com/carrierwaveuploader/carrierwave/wiki?ref=til.codes). So let's assume we have everything setup, in our rails application, We have a model named `Attachment` which uses the Carrierwave uploader(`FileUploader`) and the following code within it: ``` # app/models/attachment.rb class Attachment < ActiveRecord::Base mount_uploader :file, FileUploader end ``` So to get started with testing, we need to create records which belong to the Attachment. So let's go ahead and create/update our factory to include the file. ``` # spec/factories/attachments.rb FactoryGirl.define do factory :attachment do photo Rack::Test::UploadedFile.new(File.open(File.join(Rails.root, '/spec/fixtures/myfiles/myfile.jpg'))) end end ``` So here I am attaching a file located in my `/spec/fixtures/myfiles/` folder as a photo. The above code just attaches the photo lazily to the factory when we build a new one. If you are using `create` method and creating records that are actually persisted in the DB, you want to update the above code to: ``` FactoryGirl.define do factory :attachment do after :create do |b| b.update_column(:photo, "foo/bar/baz.png") end end end ``` With the above code in our factory, we can use the same for testing with RSpec. While the above code is enough to get to get started with the specs, I would suggest doing the following things to speed up and optimize your test suites. 1. Set storage to local file system in test environment. 2. Disable file process in test environment. 3. Separate out the upload folders for test environment. 4. Clean uploaded files after each request. ###### Setup Carrierwave to use local storage and disable file processing in test env We can do that by adding following piece of code to `Carrierwave initializer`: ``` if Rails.env.test? || Rails.env.cucumber? CarrierWave.configure do |config| config.storage = :file config.enable_processing = false end end ``` ###### Separate out the upload folders for test environment. Next, we should separate test uploads from any other uploads. We can do that by modifying `cache_dir` and `store_dir` methods for all Carrierwave models (i.e. all models that are descendants of CarrierWave::Uploader::Base). ``` # config/initializers/carrierwave.rb CarrierWave::Uploader::Base.descendants.each do |klass| next if klass.anonymous? klass.class_eval do def cache_dir "#{Rails.root}/spec/support/uploads/tmp" end def store_dir "#{Rails.root}/spec/support/uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}" end end end ``` ###### Clean uploaded files after each request. Adding the following code to our `spec_helper.rb` will make sure that we don't have stale images hanging around there after each request. ``` # spec_helper.rb RSpec.configure do |config| config.after(:each) do if Rails.env.test? || Rails.env.cucumber? FileUtils.rm_rf(Dir["#{Rails.root}/spec/support/uploads"]) end end end ``` ###### Settting asset\_host You also would want to set the asset\_host option for the carrierwave. For that add the following lines to the initializer. ``` CarrierWave.configure do |config| config.asset_host = ActionController::Base.asset_host end ``` So this is how your carrierwave initializer would like finally: ``` if Rails.env.test? || Rails.env.cucumber? CarrierWave.configure do |config| config.storage = :file config.enable_processing = false end # make sure uploader is auto-loaded FileUploader CarrierWave::Uploader::Base.descendants.each do |klass| next if klass.anonymous? klass.class_eval do def cache_dir "#{Rails.root}/spec/support/uploads/tmp" end def store_dir "#{Rails.root}/spec/support/uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}" end end end end CarrierWave.configure do |config| config.asset_host = ActionController::Base.asset_host end ``` Hope I have detailed enough pointers here to get started with carrierwave testing. ### fizzbuzz in elixir using OTP and GenServer URL: https://til.codes/fizzbuzz-in-elixir-using-otp-and-genserver/ Last updated: 2016-04-25T17:57:57.000Z Speaking of Erlang/Elixir the first thing that comes to mind is OTP. The erlang veterans would definitely playing around with it on a daily basis, but to the novice, it would sound a relatively strange term, and would be wondering what this is. So I thought of scribbling down something around this topic, to give a general idea on OTP and GenServer. ### What is OTP? So if we go by the official words, OTP — the Open Telecom Platform can be defined as: > “a complete development environment for concurrent programming”, usually consisting of an Erlang compiler and interpreter, a database server (Mnesia), an analysis tool (Dyalizer). ### Behaviours One of the central design principles of Erlang/OTP are application patterns, or `behaviours` as the grey-beards calls them. A set of common tasks would be defined as a generic implementation, and we just need to add implementation specific code into a callback module which exports a set of specific functions. With Elixir most of these implementations comes in built in, which makes the developers life easy. so we just need to override only those which matters for us. ### A FizzBuzz server using GenServer FizzBuzz is an easy piece of code to get started with something, and I am gonna use the same here. Elixir comes with a really cool build tool named `mix` that provides tasks for creating, compiling, testing your application, managing its dependencies. Lets use `mix` to create a new project. ``` $ mix new fizzbuzz --module FizzBuzz ``` Running mix new creates a whole application using the default template for us. So the next thing to do is, use GenServer in our application. So add the following to your `lib/fizzbuzz.ex` file. I am also gonna use the `Logger` in later parts for debugging the code, so lets just add that too. use GenServer require Logger So once we have everything in place, the next thing we would want is to have an API for our clients via which they can communicate to the server. So let's go ahead and write our APIs Next we define the API for our clients. ``` def start_link do GenServer.start_link(__MODULE__, :ok, name: __MODULE__) end def get(n) do GenServer.call(__MODULE__, {:print, n}) end def print(n) do GenServer.cast(__MODULE__, {:print, n}) end ``` ###### Explanation: `FizzBuzz.start_link/0` is just a wrapper around `GenServer.start_link/3` which starts the server as a linked process. Usually, the other functions refer to our server using the PID, but it's a bit odd to use the PID in every case. Alternatively we can start our server by passing the name option. In our case we will pass the name of the module `(__MODULE__)` Our server mainly has 2 interfaces, namely, `FizzBuzz.get/1` and `FizzBuzz.print/1`. The input for these is a number and would return the value of the FizzBuzz as the output. A GenServer mainly supports 2 request types: `calls` and `casts`. `calls` is synchronous, which means it is supposed to send something back to the client (our get function), on the other hand `casts` is asynchronous and doesn’t necessarily return anything to the client (our print function). It's usually considered as a best practice to wrap the GenServer interface in our own client APIs. So moving on, we now need callbacks for the GenServer. ``` def init(:ok) do Logger.debug "FizzBuzz server started" {:ok, %{}} end def handle_call({:print, n}, _from, state) do {:ok, fb, state} = fetch_or_calculate(n, state) {:reply, fb, state} end def handle_cast({:print, n}, state) do {:ok, fb, state} = fetch_or_calculate(n, state) IO.puts fb {:noreply, state} end ``` `init/1` gets called by `GenSever.start_link/3` and returns a tuple of the form `{:ok, state}`. In our specific case the state is a simple Elixir map `(%{})`. `handle_call/2` and `handle_cast/2` are the core of our application. We use pattern matching on the first argument to specify that we handle messages of the form {:print, n}. The private function fetch\_or\_calculate/2 retrieves or calculates the value, and we then return the appropriate response. In the case of a call it will have the form {:reply, response, state}, whereas for a cast it will be {:noreply, state}. These are GenServer conventions, the documentation lists all possible answer types for every request type. This is what the private helper looks like: ``` defp fetch_or_calculate(n, state) do if Dict.has_key?(state, n) do Logger.debug "Fetching #{n}" {:ok, fb} = Dict.fetch(state, n) else Logger.debug "Calculating #{n}" fb = fizzbuzz(n) state = Dict.put(state, n, fb) end {:ok, fb , state} end ``` `fetch_or_calculate/2` checks if the FizzBuzz value for n has been calculated before. If it has, we fetch it from the dictionary. If the value hasn’t been computed before, we do so and update the state by adding the newly computed value to it `(Dict.put(state, n, fb))`. Finally, we return a tuple of the form `{:ok, value, state}` which we pattern match against in the handler functions. That’s it, our FizzBuzz server is now ready for action! Here’s the complete code: ``` defmodule FizzBuzz do use GenServer require Logger ## Client API def start_link do GenServer.start_link(__MODULE__, :ok, name: __MODULE__) end def get(n) do GenServer.call(__MODULE__, {:print, n}) end def print(n) do GenServer.cast(__MODULE__, {:print, n}) end ## Server Callbacks def init(:ok) do Logger.debug "FizzBuzz server started" {:ok, %{}} end def handle_call({:print, n}, _from, state) do {:ok, fb, state} = fetch_or_calculate(n, state) {:reply, fb, state} end def handle_cast({:print, n}, state) do {:ok, fb, state} = fetch_or_calculate(n, state) IO.puts fb {:noreply, state} end defp fetch_or_calculate(n, state) do if Dict.has_key?(state, n) do Logger.debug "Fetching #{n}" {:ok, fb} = Dict.fetch(state, n) else Logger.debug "Calculating #{n}" fb = fizzbuzz(n) state = Dict.put(state, n, fb) end {:ok, fb , state} end defp fizzbuzz(n) do case {rem(n, 3), rem(n, 5)} do {0, 0} -> :FizzBuzz {0, _} -> :Fizz {_, 0} -> :Buzz _ -> n end end end ``` Time to use our code. In the project directory, fire up an `iex` session in the context of our application with the following command: ``` iex -S mix ``` Now we can start a server and compute some values: ``` FizzBuzz.start_link 1..100 |> Enum.map(&FizzBuzz.print/1) ``` Here’s the output generated by that command. Due to the asynchronous nature of the requests the outout, log messages and the function’s return value are interleaved: ``` 4 10:54:58.026 [debug] Calculating 4 Buzz [:ok, :ok, :ok] 10:54:58.028 [debug] Calculating 5 Fizz ``` Note how despite running in a separate process, the output of `IO.puts` happened in our iex session, since that’s the current group leader. If we now try to fetch an already computed value `(FizzBuzz.get(5))`, we can see that it’s actually retrieved from the cache: ``` 10:58:56.774 [debug] Fetching 5 :Buzz ``` ### Summary This blog post explores the concepts of OTP, and how to leverage those concepts and build server applications. Now our FizzBuzz application can be extended offering features like supervision and hot code swapping or can be deployed as part of a bigger Elixir application ### Some best practices while developing meteor.js apps URL: https://til.codes/meteor-js-best-practices-and-techniques/ Last updated: 2016-12-16T06:04:15.000Z Recently I was working as a consultant for a meteor.js app, reviewing the entire codebase, and came across some of the common mistakes the developer makes when writing his/her first meteor.js app. Though some of them are trivial, some others can have bigger consequences on the security of the application itself, and some others can affect the performance. So I thought of scribbling down some of the best practices, do's and dont's in writing a metoer.js. Some of the points are just best-practices(maybe opinionated), and your app will work totally fine even if you don't follow those. - Don't re-invent the wheel; use packages. - Choice of router? - iron-router vz flow-router. iron-router was once the de facto choice, but time has changed and flow-router has taken over the reins now. - Data modeling: Choosing between a normalized schema or embedding everything into the same document possibly creating duplicates. The choice depends on how often you want to read/write the data and how often the collection would be updated. - Schema for collections and validation: Use collection2 package for attaching schema and validating the inputs. - Use collection-hooks package for managing the before-*, after-* hooks in your code. - Use meteor-autoform package to keep your forms clean. - Use check package for in your app for advanced security. - Try to cache the data that is subscribed, so that you don’t need to hit the database each time. - Use pagination, won’t be an issue in the initial stages, but when the data grows, you wouldn’t want to load all the data in the initial load. - Try to avoid the usage of Sessions whenever possible; Query the data from the server instead unless you really want the data to be there in multiple sessions or hot reloads. - Have control of what data you expose and who can access what. Publish only those data that is needed. - Never trust the user, and don't allow the client to update the data. - Components - This can be debated - Blaze vz React, turns out that Blaze is now meteor's ex and React is its new crush. - Remove auto publish and insecure from your packages before you deploy to production. - Organize your code into directories, so that when the codebase grows, it can be maintained. Hope the above best practices turns out to be helpful for you. Let me know of any best practice that you follow as comments. ### RVM error when starting zsh __rvm_cleanse_variables: function definition file not found URL: https://til.codes/rvm-error-when-starting-zsh-rvm_cleanse_variables-function-definition-file-not-found/ Last updated: 2016-12-16T06:04:35.000Z I was getting this error when opening new zshell: ``` __rvm_cleanse_variables: function definition file not found command not found: rvm_error ``` Running the following solved the problem: ``` rm -f ~/.zcompdump* ``` *Note: The \* is incase there are multiple .zcompdump files.* ###### So what goes behind the scenes: ZSH uses `compinit` to initialize completion for the current session. To speed up the running of compinit, it can be made to produce a dumped configuration which will be read in on future invocations. This dumped file is `.zcompdump` in the same directory as the startup files (i.e. `$ZDOTDIR` or `$HOME`) ### Run the last shell command with sudo URL: https://til.codes/run-the-last-shell-command-with-sudo/ Last updated: 2016-02-16T09:59:15.000Z I often an into the situation where I type in a long command, hit enter and then figure out that I needed to run that command as root user. Instead of typing the same command with sudo prefixed we can do the same thing using a shortcut. To run the last command 'as is', you can just do ``` $ !! ``` If you want to run it as root, with sudo; just do ``` $ sudo !! ``` Hope you find it useful. ### How do I get a process to run in the background? URL: https://til.codes/how-do-i-get-a-process-to-run-in-the-background/ Last updated: 2016-02-09T09:30:38.000Z ###### Simple / Usable things first If you want a start script without much effort, you could use the upstart service. See the corresponding manual page and `/etc/init/*.conf` for examples. After creating such a process you can start your server by calling ``` service my server start ``` If you want more features, like specific limitations or permission management, you could try `xinetd`. ###### Using the shell You could start your process like this: ``` nohup ./myexecutable & ``` The `&` tells the shell to start the command in the background, keeping it in the job list. On some shells, the job is killed if the parent shell exits using the `HANGUP` signal. To prevent this, you can launch your command using the nohup command, which discards the HANGUP signal. However, this does not work, if the called process reconnects the HANGUP signal. To be really sure, you need to remove the process from the shell's joblist. For two well known shells this can be achieved as follows: ``` bash: ./myexecutable & disown zsh: ./myexecutable &! ``` ###### Killing your background job Normally, the shell prints the PID of the process, which then can be killed using the kill command, to stop the server. If your shell does not print the PID, you can get it using ``` echo $! ``` directly after execution. This prints the PID of the forked process. *Curated from [this](http://stackoverflow.com/questions/12486691/how-do-i-get-my-golang-web-server-to-run-in-the-background?ref=til.codes) answer in SO.* ### Remove Untagged Images From Docker URL: https://til.codes/remove-untagged-images-from-docker/ Last updated: 2016-01-29T07:00:41.000Z To delete all untagged images: ``` docker rmi $(docker images -q --filter "dangling=true") ``` ### Docker: Remove all images and containers URL: https://til.codes/docker-remove-all-images-and-containers/ Last updated: 2016-01-18T08:31:54.000Z To delete all the containers we have created so far: ``` docker rm $(docker ps -a -q) ``` To delete all the images in docker: ``` docker rmi $(docker images -q) ``` NB: Deleting docker containers and images is not reversible. Make sure you know what you are upto. Ref: [Docker Issue](https://github.com/docker/docker/issues/928?ref=til.codes#issuecomment-23538307) ### Restricting users signup to a particular domain in meteor.js URL: https://til.codes/restricting-users-signup-to-a-particular-domain-in-meteor-js/ Last updated: 2016-01-08T19:10:01.000Z If you want to restrict the user registration to a particular domain only, say for e.g.: `codingarena.in`, then you can do the same by adding the following code: ``` Accounts.config({restrictCreationByEmailDomain:'codingarena.in'}); ``` ### Git fatal: remote origin already exists URL: https://til.codes/git-fatal-remote-origin-already-exists/ Last updated: 2016-01-07T19:01:52.000Z One of my colleagues was facing an issue with git, trying to add a remote to the git repo. ``` ➜ repo_name git:(master) git remote add origin git@github.com:username/repo_name.git fatal: remote origin already exists. ``` As the error message indicates, there is already a remote configured with the name `origin`. So you can either add the new remote with a different name say `github` or update the existing one if you don't need it: To add a new remote with an alternate name, called for example github instead of origin (which obviously already exists in your system), do the following: ``` $ git remote add github git@github.com:username/repo_name.git ``` Alternatively, you can update the existing remote `origin` using the following command: ``` $ git remote set-url origin git@github.com:username/repo_name.git ``` If you want to list the current remotes, you can check the same using ``` git remote -v ``` ### How to Fix [ERROR] Unknown/unsupported storage engine: InnoDB URL: https://til.codes/how-to-fix-error-unknownunsupported-storage-engine-innodb/ Last updated: 2016-01-07T18:47:57.000Z After playing around with my MySQL configuration file in my docker container, MySQL was throwing a weird error, and was failing to start. I checked the error logs and found an entry in the error log. ``` [ERROR] Plugin ‘InnoDB’ init function returned error. [ERROR] Plugin ‘InnoDB’ registration as a STORAGE ENGINE failed. [ERROR] Unknown/unsupported storage engine: InnoDB [ERROR] Aborting ``` After googling and searching SO for a while, I realised that the solution is to rename or delete some of the log files of InnoDB. In case you are concerned about the data, it would be wise to have a backup of those files, in my case I encountered the error on a fresh docker MariaDB container. Hence I chose to remove the files. ``` rm /var/lib/mysql/ib_logfile0 rm /var/lib/mysql/ib_logfile1 ``` and then restart MySQL. ``` root@docker# /etc/init.d/mysql start Starting MySQL database server: mysqld . .. Checking for tables which need an upgrade, are corrupt or were not closed cleanly.. root@docker# ``` And thats it, I now have my MySQL server up and running once again. ### Format time using moment.js in meteor URL: https://til.codes/format-time-using-moment-js-in-meteor/ Last updated: 2016-01-07T17:18:06.000Z If you want to format a time field from you database, say for e.g.: createdAt, the following helper can be used. ``` UI.registerHelper('formatTime', function(context, options) { if(context) return moment(context).format('MM/DD/YYYY'); }); ``` An alternate way would be to write a helper for one particular view, but creating a global helper would help us to re-use the helper elsewhere too and keep the code DRY. ### Masonry in meteor.js URL: https://til.codes/masonry-in-meteor-js/ Last updated: 2016-01-07T17:04:22.000Z Code snippet to make masonry work with meteor.js ``` Template.activity_feed.rendered = function () { $('.masonry-container').isotope({ itemSelector: '.item', layoutMode: 'masonry', masonry: { columnWidth: 200, gutterWidth: 5 } }) }; ``` ### Visualise your Gemfile dependencies URL: https://til.codes/visualize-your-gemfile-dependencies/ Last updated: 2016-01-03T20:34:00.000Z If you ever wanted to visualise the gem dependencies of in your ruby/rails app, then bundler is there for rescue. Type in the following from your terminal: ``` bundle viz ``` Viz generates a PNG file of the current Gemfile as a dependency graph. Viz requires the `ruby-graphviz` gem (and its dependencies) to be installed on the system. ### Connecting Ruby & Active Record Without Rails URL: https://til.codes/connecting-a-ruby-app-to-active-record-without-rails/ Last updated: 2016-01-01T17:06:41.000Z ``` require 'active_record' require 'mysql2' # or 'pg' or 'sqlite3' # Change the following to reflect your database settings ActiveRecord::Base.establish_connection( adapter: 'mysql2', # or 'postgresql' or 'sqlite3' host: 'localhost', database: 'your_database', username: 'your_username', password: 'your_password' ) # Define your classes based on the database, as always class YourModel < ActiveRecord::Base end ``` ### Best way to make a shell script daemon? URL: https://til.codes/best-way-to-make-a-shell-script-daemon/ Last updated: 2015-12-31T17:55:33.000Z I wanted to run a shell script as a daemon, and I was exploring SO as usual, and one of the answers that I found interesting and that fits my use case was: ``` (./install.sh &) & ``` Ref: to the original question [here](http://stackoverflow.com/questions/3430330/best-way-to-make-a-shell-script-daemon?ref=til.codes). ### Postgresql: How to find pg_hba.conf file using Mac OS X URL: https://til.codes/postgresql-how-to-find-pg_hba-conf-file-using-mac-os-x/ Last updated: 2015-12-29T18:39:04.000Z To find the postgresql config file we can use the `locate` command. Type the following in your terminal: ``` locate pg_hba.conf ``` Or else: If you have a postgres server running on your machine, you can find the config directory using the following command ``` ps aux | grep postgres ``` which would show something similar to: ``` user 3391 0.0 0.0 2615032 804 ?? S 18Dec15 1:01.04 /usr/local/opt/postgresql/bin/postgres -D /usr/local/var/postgres -r /usr/local/var/postgres/server.log ``` The `-D` option in the above command show the directory of the postgres, where you would be able to locate the `pg_hba.conf` file or else: if you can connect to your postgres server, then the following command show the config files. ``` SHOW hba_file; SHOW config_file; ``` Hope that helps. ### Dump PostgreSQL without owner and privileges URL: https://til.codes/dump-postgresql-without-owner-and-privileges/ Last updated: 2015-12-29T16:29:34.000Z If you ever wanted to dump your postgres development database without the owner and the privileges, then you just need to run the following command: ``` pg_dump database_name -O -x > output_file ``` ### Start Phoenix app with cowboy server on different port URL: https://til.codes/start-phoenix-app-with-cowboy-server-on-different-port/ Last updated: 2015-11-23T04:12:47.000Z To run phoenix app on a custom port other than port 4000, tweak your config file for respective environment(dev/prod/test) to the following: ``` config :my_app, MyApp.Endpoint, http: [port: {:system, "PORT"}], ``` Then from the terminal: ``` $ PORT=4001 mix phoenix.server $ PORT=4002 mix phoenix.server ``` Enjoy.! ### Problem installing puma on OS X with openssl URL: https://til.codes/problem-installing-puma-on-os-x-with-openssl/ Last updated: 2015-11-17T19:12:22.000Z When installing puma on OSX 10.11 El Capitan, if you are facing issues compiling mini\_ssl.c ``` fatal error: 'openssl/bio.h' file not found. ``` all you need to do is to specify ``` --with-opt-dir ``` You can make this applicable for all your Gemfiles: ``` bundle config build.puma --with-opt-dir=/usr/local/opt/openssl ``` Thanks to [Jeremy](https://github.com/jeremy?ref=til.codes) for [this](https://github.com/puma/puma/issues/718?ref=til.codes#issuecomment-139624081) ### Run Multiple Skype clients on Mac OS X? URL: https://til.codes/run-multiple-skype-clients-on-mac-os-x/ Last updated: 2015-11-16T18:45:33.000Z Just run the following command in terminal: ``` open -na /Applications/Skype.app --args -DataPath /Users/$(whoami)/Library/Application\ Support/Skype2 ``` ### Creating tables and problems with primary key in Rails URL: https://til.codes/creating-tables-and-problems-with-primary-key-in-rails/ Last updated: 2015-11-13T01:22:53.000Z If you run into the following error when running a migration in your legacy Rails app with MySQL 5.7 or above ``` rake aborted! "Mysql2::Error: All parts of a PRIMARY KEY must be NOT NULL:" ``` the fix is by monkey patching the MySQL Adapter using the following code. ``` # lib/patches/abastract_mysql_adapter.rb class ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter NATIVE_DATABASE_TYPES[:primary_key] = 'int(11) auto_increment PRIMARY KEY' end ``` and then require it from your `environment.rb` ``` require File.expand_path('../../lib/patches/abstract_mysql_adapter', __FILE__) ``` This issue is fixed in the latest version of Rails, but your legacy apps would need to be patched manually. If you are interested in learning more about change that caused this issue, you can refer [this](http://dev.mysql.com/doc/relnotes/mysql/5.7/en/news-5-7-3.html?ref=til.codes) link ### Easy way pull latest of all git-submodules URL: https://til.codes/easy-way-pull-latest-of-all-git-submodules/ Last updated: 2015-11-08T14:20:43.000Z Often large projects have dependencies on many other 3rd party libraries. Each library can be a separate repo that can be brought into the dependant project as a submodule. During development, if ever you want to just go grab the latest version of every dependant submodule, type in the following command: ``` git pull origin master --recurse-submodules ``` ### Hibernate not working 15.04 URL: https://til.codes/hibernate-not-working-15-04/ Last updated: 2015-10-30T01:33:21.000Z To know your swap partition, run `swapon -s`. Let's say it reports `/dev/sda5`. Open `/etc/default/grub` and find the line with `GRUB_CMDLINE_LINUX_DEFAULT=`. This is the list of kernel command line options GRUB will pass to Linux. Add `resume=/dev/sda5`, so the line `GRUB_CMDLINE_LINUX_DEFAULT="nosplash enable_mtrr_cleanup=1"` will now look like this ``` GRUB_CMDLINE_LINUX_DEFAULT="nosplash enable_mtrr_cleanup=1 resume=/dev/sda6" ``` Save the file and run this to enable the new configuration: ``` sudo update-grub2 ``` And that's it..!! ### Pundit for authorization with Rspec Rails URL: https://til.codes/pundit-for-authorization-with-rspec-rails/ Last updated: 2015-10-21T01:02:28.000Z Authorization is one of the important feature of any web app. With rails you can leverage the power of all those wonderful open source gems that are available to you or you can code your own authorization module. The most commonly used gems for authorization are [Pundit](http://github.com/elabs/pundit?ref=til.codes) and `Cancan` (now [cancancan](https://github.com/CanCanCommunity/cancancan?ref=til.codes), since the community took over the development of the gem. One reason for which I love the Ruby community the most :)) Now since we have those 2 popular gem, the next big question is which to chose from ### Pundit or Cancan ? #### Cancan - Popular among the two. - More star gazers in github. - Now being maintained by the community. - Custom DSL #### Pundit - Plain ruby classes and Object Oriented code design patterns. - No DSL to master - PORO. ### Basic usage The setup and install instructions are documented in detailed in the gem's wiki itself. The main principle is to extract the authorization rules into policy files, which are POROs: For eg:, an Article plolicy might look like: ``` class ArticlePolicy attr_reader :user, :article def initialize(user, article) @user = user @article = article end def new? user.has_roles?('author') end alias_method :create?, :new? def edit? user.has_roles?('author') && article.is_draft? end alias_method :update?, :edit? end ``` And your corresponding controller will be something like : ``` class ArticleController < ApplicationController include Pundit rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized ... def edit @article = Article.find(params[:id]) authorize @article ... end ... private def user_not_authorized flash[:error] = 'You are not authorized to perform this action.' redirect_to(request.referrer || root_path) end end ``` ### Testing with Rspec. Pundit works well with rspec. Thunderboltlabs has a nice [article](http://thunderboltlabs.com/blog/2013/03/27/testing-pundit-policies-with-rspec/?ref=til.codes) written on testing pundit policies with rspec. Going ahead and adding the matchers for pundit: ``` RSpec::Matchers.define :permit do |action| match do |policy| policy.public_send("#{action}?") end failure_message_for_should do |policy| "#{policy.class} does not permit #{action} on #{policy.record} for #{policy.user.inspect}." end failure_message_for_should_not do |policy| "#{policy.class} does not forbid #{action} on #{policy.record} for #{policy.user.inspect}." end end ``` to `spec/pundit_matcher.rb` and including that in our `spec_helper` using `Dir[Rails.root.join("spec/support/**/*.rb")].each {|f| require f}`. Once we have the custom matchers and the support files loaded into spec helper we can go ahead and write our test cases our article policy. So our rspec test will be having a following pattern: ``` require 'spec_helper' describe ArticlePolicy do subject { ArticlePolicy.new(user, article) } let(:article) { FactoryGirl.create(:article) } context 'for a visitor' do let(:user) { nil } it { should_not permit(:create) } it { should_not permit(:new) } it { should_not permit(:update) } it { should_not permit(:edit) } end context "for an author" do let(:user) { FactoryGirl.create(:user, role: 'author') } it { should permit(:create) } it { should permit(:new) } it { should permit(:update) } it { should permit(:edit) } end end ``` ### Tweaks with shoulda matchers. In case your application is using shoulda matchers there is a chance that the above wont work, as a result of conflicting namespace. You can read more about the same [here](https://github.com/elabs/pundit/issues/142?ref=til.codes). So the work around would be to tweak your support file. Instead of using the conflicting `permit` we just rename it to `permitted` a little bit to the following: ``` RSpec::Matchers.define :permitted_to do |action| match do |policy| policy.public_send("#{action}?") end failure_message_for_should do |policy| "#{policy.class} does not permit #{action} on #{policy.record} for #{policy.user.inspect}." end failure_message_for_should_not do |policy| "#{policy.class} does not forbid #{action} on #{policy.record} for #{policy.user.inspect}." end end ``` Updating our test case for the above workaround, ``` require 'spec_helper' describe ArticlePolicy do subject { ArticlePolicy.new(user, article) } let(:article) { FactoryGirl.create(:article) } context 'for a visitor' do let(:user) { nil } it { should_not permitted_to(:create) } it { should_not permitted_to(:new) } it { should_not permitted_to(:update) } it { should_not permitted_to(:edit) } end context "for an author" do let(:user) { FactoryGirl.create(:user, role: 'author') } it { should permitted_to(:create) } it { should permitted_to(:new) } it { should permitted_to(:update) } it { should permitted_to(:edit) } end end ``` Another possible work around is not to load the shoulda matchers in your policies specs. You can restrict this by loading them only to your models, controllers etc. Hope you find this helpful. ### Manually insert users from seed file with accounts-password URL: https://til.codes/seeding-users-in-meteor-js-and-account-password/ Last updated: 2015-09-30T01:05:30.000Z One of the easiest way to seed users/admin-user into the system is by running the following on the server when it starts: ``` Meteor.startup(function() { if (Meteor.users.find().count() === 0) { Accounts.createUser({ username: 'test', email: 'me@til.codes', password: 'password' }); } }); ``` ### Postgres could not connect to server URL: https://til.codes/postgres-could-not-connect-to-server/ Last updated: 2015-09-22T13:50:52.000Z ``` $ psql psql: could not connect to server: No such file or directory Is the server running locally and accepting connections on Unix domain socket "/tmp/.s.PGSQL.5432"? ``` The reason for the issue is because a pid file was blocking postgres from starting up. To fix it: ``` rm /usr/local/var/postgres/postmaster.pid ``` and then all is well. ### Ruby: delete multiple hash keys URL: https://til.codes/ruby-delete-multiple-hash-keys/ Last updated: 2015-07-24T09:16:19.000Z Returns a hash that includes everything but the given keys. ``` hash = { a: true, b: false, c: nil} hash.except(:c) # => { a: true, b: false} hash # => { a: true, b: false, c: nil} ``` This is useful for limiting a set of parameters to everything but a few known toggles: ``` @person.update(params[:person].except(:admin)) ``` ### “bin/rails: No such file or directory” w/ Ruby 2 & Rails 4 on Heroku URL: https://til.codes/binrails-no-such-file-or-directory-w-ruby-2-rails-4-on-heroku/ Last updated: 2015-07-20T06:30:34.000Z Rails apps with version 4 or above has some files under the `bin` folder namely `bundle`, `rails`, `rake`, `setup`, `spring`. But since I had `bin` folder gitignored, these files dint make it to heroku. To fix this: Remove bin from \~/.gitignore Run `bundle install` or `rake rails:update:bin` Commit your changes with `git add .` and `git commit -m "Add bin back"` Push your changes to Heroku with git push heroku master Heroku has a detailed article on the same which can be found [here](https://devcenter.heroku.com/articles/rails4?ref=til.codes). ### SET GOPATH environment variable on Ubuntu? URL: https://til.codes/how-do-i-set-the-gopath-environment-variable-on-ubuntu/ Last updated: 2015-07-17T15:41:40.000Z Just add the following lines to `~/.bashrc` ``` export GOROOT=/usr/lib/go export GOPATH=$HOME/go export PATH=$PATH:$GOROOT/bin:$GOPATH/bin ``` ### Calling View helper methods from Controller / Model in Rails URL: https://til.codes/calling-view-helper-methods-from-controller-model-in-rails/ Last updated: 2015-07-17T11:55:20.000Z Recently I was researching a way to use `link_to` inside my Rails 3 controller, and learned about the method `view_context`. So if you ever want to access view helper methods from controller / models in rails 3 you have to use view\_context method. Example ``` view_context.link_to 'Link', link_path(@link)}. ``` PS: Just a note that you should avoid calling these functions within the controllers to follow mvc pattern conventions. ### Email notification when someone logs in via SSH URL: https://til.codes/email-notification-when-someone-logs-in-via-ssh/ Last updated: 2015-07-13T19:06:22.000Z To setup email notification, login to your server as root. Edit `.bashrc` ``` vim .bashrc ``` add the following line at the end, changing `"ServerName"` to the host-name of your server and `"me@til.codes"` to your own email address. ``` echo 'ALERT - Root Shell Access (Server-name) on:' `date` `who` | mail -s "Alert: Root Access from `who | cut -d"(" -f2 | cut -d")" -f1`" me@til.codes ``` Save and exit. Next time someone (hopefully you) logs on as root, you will get an email about this. ### How do I make --no-ri --no-rdoc the default for gem install? URL: https://til.codes/how-do-i-make-no-ri-no-rdoc-the-default-for-gem-install/ Last updated: 2015-07-13T15:43:15.000Z If you are deploying to a server, or you do not want to wait around for rdoc and ri to install for each gem, you can disable them for gem installs and updates. Just add the following line to your `~/.gemrc` or `/etc/gemrc`: ``` gem: --no-rdoc --no-ri ``` or ``` gem: --no-document ``` or if want to be more specific, ``` install: --no-document update: --no-document ``` ### How to update git remotes URL: https://til.codes/how-to-update-git-remote/ Last updated: 2015-07-13T10:26:42.000Z To list the existing remotes, we can use the following command: ``` git remote -v #View existing remotes origin https://github.com/user/repo.git (fetch) origin https://github.com/user/repo.git (push) ``` Use the following command to set the remote to a new end point. ``` git remote set-url origin https://github.com/user/repo2.git # Change the 'origin' remote's URL ``` Making sure that worked ;) ``` git remote -v # Verify new remote URL # origin https://github.com/user/repo2.git (fetch) # origin https://github.com/user/repo2.git (push) ``` Reference: [Github](https://help.github.com/articles/changing-a-remote-s-url/?ref=til.codes)