Full Width [alt+shift+f] Shortcuts [alt+shift+k]
Sign Up [alt+shift+s] Log In [alt+shift+l]
1

Warming up the Puma master before it forks

from 37signals Dev [alt+shift+b] in programming

Basecamp 5 runs on Puma in cluster mode: one master process with preload_app! and 63 single-threaded workers per host, deployed as a Docker container with Kamal. We serve Basecamp from several sites. Each site has its own web hosts and a read replica of the database, and writes go to a single primary database in one of them. On our busiest hosts, each deploy left up to 2,000 requests waiting while the new workers warmed up. We reduced those queues by running signed-in requests through the app in the Puma master, before it forked the workers. Why 63 single-threaded workers? Basecamp has always served web requests from processes rather than threads. It ran on Unicorn, which only does processes, until we moved to Puma in January 2025, and we kept the same setup: workers (Concurrent.physical_processor_count * 1.3).ceil threads 1, 1 preload_app! On a 48-core host that’s 63 workers, each handling one request at a time. We chose 1.3 after benchmarking HEY in 2023, when we moved our apps out of the cloud and onto our own hardware. We tested several combinations of workers and threads with a mix of GET and POST requests on a 32-vCPU VM. Every multithreaded configuration we tested was slower and handled fewer requests than single-threaded workers. Adding workers beyond about 1.2 to 1.3 per vCPU brought little benefit. The threaded workers spent a lot of their time waiting for Ruby’s global VM lock. That made single-threaded workers a good fit for this workload, and we use the same setup for Basecamp. An app that spends more time waiting on its database or other services may benefit from more threads, so benchmark your own app. The other reason is the app itself. Basecamp has class-level state in places and has never needed to be thread-safe. With one request per process, it still doesn’t. Processes do use more memory than threads, and preload_app! reduces the difference. The master loads the app once and the workers share its memory through copy-on-write until they write...
yesterday

Stay updated

Get a weekly newsletter with the top 5 articles worth reading every week.

More from 37signals Dev

Lexxy 1.0 is here

Today we are releasing the version 1.0 of Lexxy. Lexxy is a rich text editor for Rails built on Lexical. It already powers Basecamp, Fizzy and many others, and it will become the default editor in Rails. I recently presented it in Rails World (slides, video coming soon). This is the article version of my talk. Trix hit a wall Trix has been our editor since 2015, and every Rails app’s editor since Action Text shipped in Rails 6. It’s small and reliable, and it has served millions of people for a decade. But in the last few years our customers kept asking for features like tables or code highlighting, and we kept struggling to deliver them. The reason is the Trix document model. A Trix document is a flat list of blocks. A block is a line of text with some labels attached, like quote, bullet list, bullet. There is no tree, and a block can never contain another block. Nesting is an illusion: at render time, adjacent blocks with the same labels get wrapped together. A flat list of blocks with very limited extensibility options That design bought a lot of simplicity, but you can’t express something like a table with it. Two cells next to each other would carry exactly the same labels, so Trix would merge them into one. The model can say “this bullet is one level deeper”. It cannot say “this cell is different from the cell next to it”. A tables issue has been opened since 2015! The model just can’t do it. The second problem was maintenance. An editor built on contenteditable behaves differently in every browser and even changes from time with operating system releases. In 2024, three iOS releases in a row broke typing, dictation or the caret in Trix, and each one cost us real effort to work around. Check this one as an example. Why Lexical This was a conversation we had at 37signals for years: 2022. We started a project to add tables to Trix. We gave up after a week. The document model can’t represent two-dimensional things. 2023. I built a proof of concept with Tiptap inside HEY. We liked it, but we never started a serious project with it. 2024. We built House, our own Markdown editor, for Writebook. Not WYSIWYG, but WYSIWYM: what you see is what you mean. A wonderful editor for long-form writing like books or technical documentation. 2025. We tried House in another product, and it didn’t fit. For most apps, WYSIWYG was just the right answer. 2025. We had the discussion again, and this time we looked at the whole field. Four years of the same conversation David ruled out Tiptap, CKEditor and the other commercial editors: an open source core with features kept proprietary, and a sales team behind them. We didn’t want our editor to depend on somebody else’s licensing decisions. Then we found Lexical: MIT, from Meta, and very powerful. I spent two weeks evaluating it, and in May we made the call to go with it. A tiny core. Pick the rest. Lexical’s core has zero dependencies and weighs forty-two kilobytes. In a way, it validates the approach that Trix pioneered. The document is an immutable state you never mutate directly, you just get new snapshots when performing updates; contenteditable is an input device and a rendering surface, never the source of truth. It has a DOM reconciler to update the actual DOM very efficiently, and other primitives to deal with handling commands and node transformations. Everything else, from lists to tables to markdown, is a package in the orbit. Lexxy uses thirteen of them. Lexical solved the maintenance problem too. Meta’s products like Facebook or Instagram use Lexical and their user count is in the hundreds of millions. This means that even small issues with new keyboards and devices are fixed fast by the dedicated Meta team that maintains it. Furthermore, Meta’s business is the products, not the editor. We much rather liked this structure of incentives for the long-term investment an editor represents. The iceberg The plan was simple. Pick Lexical, wire it up to Action Text, add a toolbar and ship it. Well, it didn’t go exactly like that. What you envision, and what's under the water Lexxy today is thirteen thousand lines of vanilla JavaScript on top of Lexical. A great editing experience is very hard to get right. An editor is a machine where the user can change the state in a thousand different ways. For example. you have two images one after the other and want to put the cursor between them, but there is nothing there to put a cursor in. Or somebody pastes from Google Docs, and you have to turn a pile of inline styles and empty spans into clean markup. And then Safari, and Android keyboards, and the clipboard, and undo, and… From the first pull request to Basecamp 5 The first pull request landed in May 2025. Fizzy launched with Lexxy in December, and Basecamp 5 in May this year. Basecamp was the real test: twenty years of content written with Trix, and people who use the editor all day, every day. Zoltán Hosszú and Samuel Péchèr were the key people who made this happen. The took a very green version of Lexxy, added a ton of features (including Tables) and polish, and they fixed countless bugs. They also pulled off a remarkable milestone: seamlessly switching millions of Basecamp users from Trix to Lexxy. What’s included? We didn’t want a to build Trix with tables. We had Lexical and we had agents to help, so we wanted to be ambitious here. We went for the whole package. Features In terms of major features: A color highlighter, built in instead of this being a custom Basecamp extension, as it was with Trix. Tables, with an interface we worked hard to keep simple and accessible. Markdown. You type it, you get rich text. Code blocks with syntax highlighting as you type, in more than twenty languages. Image galleries you can navigate and reorder with the keyboard. Prompts. Type a character, get a menu: mentions, emoji, or whatever your app needs. Links by pasting a URL over selected text. Previews of attachments like videos and PDFs, rendered as your app renders them. Action Text Native Lexxy is also Action Text native. Action Text stores attachments in a canonical format that Trix doesn’t speak, so it translates on save and again on render. We taught Lexxy to emit exactly that markup. What you see in the editor is what gets saved, and what gets saved is what your app renders. Your existing content, attachments and views keep working. That opened another door. Action Text now talks to an editor adapter, with an implementation for Trix and one for Lexxy, so switching is one line: config.action_text.editor = :lexxy. Here the credit goes to Sean Doyle, who started that pull request before Lexxy existed and took it to the finish line with our input. It ships with Rails 8.2, and we hope other editors will use it too. Extensibility And you can extend Lexxy. Extensions are built on Lexical’s own mechanism, and this is not a second-class API: Lexxy itself is thirteen extensions, tables included, and Basecamp has nine more. class MyExtension extends Lexxy.Extension { get enabled() { … } get allowedElements() { … } get lexicalExtension() { return this.defineExtension({ name: "my-extension", nodes: [ … ], register(editor) { … } }) } initializeToolbar(toolbar) { … } dispose() { … } } Lexxy.configure({ global: { extensions: [ MyExtension ] } }) My favorite of how extensible is Lexxy are voice notes in Basecamp: you record, you see the waveform while you talk, and it becomes a player inside the document. About a thousand lines, without forking or patching anything. Performance Lexxy is fast, because Lexical is fast. In a ten thousand word document, Trix takes thirty-eight milliseconds to process a keystroke. Lexxy takes four. Above fifty milliseconds, the editor starts feeling sluggish. Compared to Trix, Lexxy brought a whole new performance regime. Milliseconds per keystroke by document size Accessibility Accessibility in Trix was not great. In general, building accessible experiences for rich text editors built on top of contenteditable is quite hard. We had a dream team to help with Lexxy accessibility. Bruno Prieto worked with Michael Berger, our accessibility champion at 37signals, to bring the bar to where we wanted it to be. Bruno is an outstanding programmer who happens to be blind, so he knows one thing or two about accessibility, and he delivered. As a result, in Lexxy everything is reachable with the keyboard. The editor announces itself properly to screen readers, and it gets a thousand details right so that the editing experience using a screen reader is fantastic. You can learn more about accessibility in our docs. Security The latest AI models have resulted in an unprecedented explosion of vulnerabilities found, and we took this thread quite seriously. Lexxy counted with programmers of the caliber of Jeremy Daer and Mike Dalessio helping to make it more secure. We have put a lot of attention to sanitizing the editor contents, validating attachment URLs and making sure that the types of attachments and nodes the editor support are allow-listed. Lexxy also comes with preliminary Trusted Types support, to offer CSP-level control over certain DOM manipulation APIs. The trusted types policy is there, but we are not enforcing it everywhere yet. Agents We started Lexxy using Claude since day one. A main lesson was that an agent needs to drive the editor like a user does. The best decision we made in this project was moving the system tests from Capybara to Playwright: three browsers instead of one, a suite that runs in seconds, and a much more faithful clipboard, keyboard and focus. This represented a tremendous improvement in how agents could close the loop by themselves. Write a test, see it fail, fix it, see it pass. We have more than six hundred browser tests today. Moving the suite to Playwright changed how fast we could write tests With a solid testing foundation in place, we could start fixing bugs in large batches. As mentioned, getting a text editor right implies a ton of work, and the kind of backlog we got at some point would have have buried us in pre-agent times. We also used agents to validate the fixes: an agent reproduces the bug in the public Lexxy sandbox, checks that it’s gone with the branch applied, and labels the pull request. Agents were essential to get Lexxy done with the people and the deadlines we had: we are a small company, and the same people were shipping two products in parallel. 275 cards closed The new Rails default We believe Lexxy is the best rich text editor out there right now, and we are going to make it the default editor in Rails next. If you’re starting a Rails application today, use Lexxy. If you’re using Action Text with a standard configuration, switch. It’s one line, and we’ve worked hard to make it seamless.

4 days ago • 1 votes
Lexxy: A new rich text editor for Rails

Today, we are introducing Lexxy, a new rich text editor for Action Text. It’s based on Lexical — Meta’s text editing framework — and it brings a much better text editing experience to Rails: Good HTML semantics. Paragraphs are real <p> tags, as they should be. Markdown support: shortcuts, auto-formatting on paste. Real-time code syntax highlighting. Create links by pasting URLs on selected text. Configurable prompts. Support for mentions and other interactive prompts with multiple loading and filtering strategies. Preview attachments like PDFs and Videos in the editor. Works seamlessly with Action Text and Active Storage. We created Lexxy because Trix was falling short of expectations in certain areas, and we encountered technical barriers when attempting to offer the experience we wanted. Lexxy comes with a bunch of juicy improvements, but more than that, we now have a fantastic foundation to build on top of. Lexxy will also bring a great improvement to Action Text: we will let you configure the editor in Action Text just like you configure the database in Active Record. This will open the door to integrating other editors in Rails. Text editing is central to our products. We believe Lexxy will let us deliver the editing experience we want. We are launching an early beta today, give it a try!

4th Sep 2025 • 1 votes
Introducing Action Push Native

Note: Shortly after releasing this gem, we renamed it from Action Native Push to Action Push Native, in case you arrived here looking for the gem of the former name. More details here. We’ve open-sourced Action Push Native, a Rails gem for sending push notifications to mobile platforms. It supports both Apple and Google push notification services. Why did we build it? We created it to migrate off Amazon SNS and Pinpoint, as part of our broader cloud exit. We’re using it in Basecamp and HEY to send more than 10 million push notifications per day without a hitch. Action Push Native relies on HTTP/2 persistent connections to the Apple Push Notification service, which significantly reduced job duration compared to our previous HTTP/1 setup with AWS Pinpoint: AWS Pinpoint jobs duration Action Push Native jobs duration How does it work? The gem connects directly to the Apple (APNs) and Google (FCM) push notification services. It handles retries, rate-limiting, and deleting dead devices automatically. Configure each platform with your credentials, and you can start sending notifications like this: device = ApplicationPushDevice.create! \ name: "iPhone 16", token: "6c267f26b173cd9595ae2f6702b1ab560371a60e7c8a9e27419bd0fa4a42e58f", platform: "apple" notification = ApplicationPushNotification.new \ title: "Hello world!", body: "Welcome to Action Push Native" notification.deliver_later_to(device) Version 0.1.0 is available now. You can read more on GitHub. We hope you find it useful!

18th Aug 2025 • 1 votes
Announcing Hotwire Native 1.2

We’ve just launched Hotwire Native v1.2 and it’s the biggest update since the initial launch last year. The update has several key improvements, bug fixes, and more API consistency between platforms. And we’ve created all new iOS and Android demo apps to show it off! A web-first framework for building native mobile apps Improvements There are a few significant changes in v1.2 that are worth specifically highlighting. Route decision handlers Hotwire Native apps route internal urls to screens in your app, and route external urls to the device’s browser. Historically, though, it wasn’t straightforward to customize the default behavior for unique app needs. In v1.2, we’ve introduced the RouteDecisionHandler concept to iOS (formerly only on Android). Route decisions handlers offer a flexible way to decide how to route urls in your app. Out-of-the-box, Hotwire Native registers these route decision handlers to control how urls are routed: AppNavigationRouteDecisionHandler: Routes all internal urls on your app’s domain through your app. SafariViewControllerRouteDecisionHandler: (iOS Only) Routes all external http/https urls to a SFSafariViewController in your app. BrowserTabRouteDecisionHandler: (Android Only) Routes all external http/https urls to a Custom Tab in your app. SystemNavigationRouteDecisionHandler: Routes all remaining external urls (such as sms: or mailto:) through device’s system navigation. If you’d like to customize this behavior you can register your own RouteDecisionHandler implementations in your app. See the documentation for details. Server-driven historical location urls If you’re using Ruby on Rails, the turbo-rails gem provides the following historical location routes. You can use these to manipulate the navigation stack in Hotwire Native apps. recede_or_redirect_to(url, **options) — Pops the visible screen off of the navigation stack. refresh_or_redirect_to(url, **options) — Refreshes the visible screen on the navigation stack. resume_or_redirect_to(url, **options) — Resumes the visible screen on the navigation stack with no further action. In v1.2 there is now built-in support to handle these “command” urls with no additional path configuration setup necessary. We’ve also made improvements so they handle dismissing modal screens automatically. See the documentation for details. Bottom tabs When starting with Hotwire Native, one of the most common questions developers ask is how to support native bottom tab navigation in their apps. We finally have an official answer! We’ve introduced a HotwireTabBarController for iOS and a HotwireBottomNavigationController for Android. And we’ve updated the demo apps for both platforms to show you exactly how to set them up. New demo apps To better show off all the features in Hotwire Native, we’ve created new demo apps for iOS and Android. And there’s a brand new Rails web app for the native apps to leverage. Hotwire Native demo app Clone the GitHub repos to build and run the demo apps to try them out: iOS repo Android repo Rails app Huge thanks to Joe Masilotti for all the demo app improvements. If you’re looking for more resources, Joe even wrote a Hotwire Native for Rails Developers book! Release notes v1.2 contains dozens of other improvements and bug fixes across both platforms. See the full release notes to learn about all the additional changes: iOS release notes Android release notes Take a look If you’ve been curious about using Hotwire Native for your mobile apps, now is a great time to take a look. We have documentation and guides available on native.hotwired.dev and we’ve created really great demo apps for iOS and Android to help you get started.

23rd Apr 2025 • 71 votes

More in programming

Clip of me singing Despard in Ruddigore in 2013

A clip of me singing a funny song from Gilbert and Sullivan’s Ruddigore back in 2013

7 hours ago • 1 votes
How and Why fork() Uses Copy-on-Write

In this video, we look at why fork() needs copy-on-write, how it works inside the kernel, and a memory usage problem that Instagram encountered with Python.

13 hours ago • 1 votes
What we lost when we lost comments

Comments require commitment, but they’re worth it.

19 hours ago • 1 votes
Lighthouse map

Lovely global map with animated lights sweeping the waters

21 hours ago • 1 votes
📚 BoredReading

You seem to be enjoying this.

Join free to unlock everything.

Create free account

Already have an account? Sign in