<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: pry0rity</title>
    <description>The latest articles on DEV Community by pry0rity (@pry0rity).</description>
    <link>https://dev.to/pry0rity</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2226919%2Fa31e1c52-5732-4384-8075-bafb9be591e0.jpeg</url>
      <title>DEV Community: pry0rity</title>
      <link>https://dev.to/pry0rity</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/pry0rity"/>
    <language>en</language>
    <item>
      <title>App store rankings hate slow apps - mobile vitals can help you fix them</title>
      <dc:creator>pry0rity</dc:creator>
      <pubDate>Tue, 04 Mar 2025 20:27:42 +0000</pubDate>
      <link>https://dev.to/sentry/app-store-rankings-hate-slow-apps-mobile-vitals-can-help-you-fix-them-321p</link>
      <guid>https://dev.to/sentry/app-store-rankings-hate-slow-apps-mobile-vitals-can-help-you-fix-them-321p</guid>
      <description>&lt;p&gt;Mobile devs know the struggle. Small regressions can cause big issues in production, and fixing them isn't as easy as pushing a quick patch. Unlike a web app, shipping fixes for apps means navigating app store approvals, and often hopping on meetings with customers to debug because mobile issues can be so challenging to recreate.&lt;/p&gt;

&lt;p&gt;Catching these issues before the 1-star reviews roll in is crucial. Luckily, Sentry just made it easier than ever.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mobile Vitals&lt;/strong&gt; - our latest addition to the Insights feature - highlights your slowest app starts, screen loads, and renders, so you can quickly tackle frozen or laggy interactions before users rage-quit. Whether you're building with React Native, Flutter, Android, or iOS, let's dive into what you can do with it.&lt;/p&gt;

&lt;p&gt;(P.S. We first wrote about mobile vitals years ago—if you're curious about how this new tool works under the hood, it's a &lt;a href="https://blog.sentry.io/mobile-vitals-four-metrics-every-mobile-developer-should-care-about/" rel="noopener noreferrer"&gt;good read&lt;/a&gt;.)&lt;/p&gt;

&lt;h2&gt;
  
  
  What does Mobile Vitals track?
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1: App Start Performance
&lt;/h3&gt;

&lt;p&gt;You’re doom-scrolling Instagram recipes and suddenly a great ad for a calorie counting app pops up. You download it out of curiosity, and for some reason, it takes 20 seconds to boot up the first time, and 10 seconds every time after that. What are you gonna do - keep forcing yourself to go through that every time you pop another Oreo, or just go back to the scale? &lt;/p&gt;

&lt;p&gt;Sentry tracks App Start performance to help you prevent your users from burning unnecessary calories uninstalling your apps out of frustration. We consider two key metrics here: cold starts &amp;amp; warm starts. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cold starts: You never get a second chance at a first impression. Sentry tracks cold starts independently so you can make sure your latest patch doesn’t crush your first impressions.&lt;/li&gt;
&lt;li&gt;Warm starts: Memory management on iOS &amp;amp; Android is complicated and often suspends some tasks while keeping your application ‘warm’ in the background. Pulling up your application from the recent apps selector should be lightning-fast, too. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It’s not just the top-level metric that matters. For warm starts, Sentry auto-instruments &lt;a href="https://docs.sentry.io/platforms/apple/guides/ios/tracing/instrumentation/automatic-instrumentation/#app-start-tracing" rel="noopener noreferrer"&gt;iOS&lt;/a&gt; and &lt;a href="https://docs.sentry.io/platforms/android/tracing/instrumentation/automatic-instrumentation/#app-start-instrumentation" rel="noopener noreferrer"&gt;Android&lt;/a&gt; apps with great granular detail. With iOS for instance, you can see pre-runtime, UIKit, frame renders and a bunch of &lt;a href="https://docs.sentry.io/platforms/apple/guides/ios/tracing/instrumentation/automatic-instrumentation/#app-start-tracing" rel="noopener noreferrer"&gt;other native tasks&lt;/a&gt; independently and work to fix what’s bottlenecking your app load performance. &lt;/p&gt;

&lt;p&gt;Clicking into a screen shows you an overview for all your most recent app loads leading up to that screen, which you can then drill into and see what sorts of tasks may be bottlenecking.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftthpdq7oqzxcow7o8p16.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftthpdq7oqzxcow7o8p16.png" alt="A Sentry UI screenshot showing App Cold Starts and App Warm Starts highlighted with " width="777" height="396"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  2: Screen Load Performance
&lt;/h3&gt;

&lt;p&gt;If it takes 5 seconds every time you want to load the comments page, nobody’s going to leave comments. It’s really that simple. &lt;/p&gt;

&lt;p&gt;Sentry helps you cut down on UX bloat by tracking &lt;a href="https://docs.sentry.io/product/insights/mobile/screen-loads/" rel="noopener noreferrer"&gt;TTID and TTFD&lt;/a&gt; for every screen load:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Time to Initial Display (TTID):&lt;/strong&gt; This tracks the time it takes for a screen to produce its first frame. Often, that’s just a background or a wall of text, but for a user, this is an important metric as it makes the app ‘feel’ fast. Sentry tracks this automatically by default.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Time to Full Display (TTFD):&lt;/strong&gt; More importantly is the time it takes for a screen to be fully loaded and functional. This is similar to the LCP core web vital on browser loads. TTFD can include content that’s lazy- or async-loaded after getting its first content, making it a more useful metric for a lot of your mobile screens. This isn’t enabled by default, but it’s super easy to set up. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Just like with App Starts, you can drill into Screen Loads by clicking into a screen and drilling into the full trace of the user session, or even dive into a mobile CPU profile. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://app.arcade.software/share/tYVh6rDXcj9UQNOESjOY" rel="noopener noreferrer"&gt;Check out a 10-second interactive video here. &lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  3: Screen Rendering Performance
&lt;/h3&gt;

&lt;p&gt;Your favorite solitaire clone ships a new patch, and suddenly you find your framerate dropping like a brick. You just got your phone a few years ago… is it time to upgrade? Nah, you’ll just throw out a 1-star review, uninstall, and go right back to Wordle.&lt;/p&gt;

&lt;p&gt;We surface 3 key &lt;a href="https://docs.sentry.io/product/insights/mobile/#app-start" rel="noopener noreferrer"&gt;screen rendering performance metrics&lt;/a&gt; by default: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Slow frames:&lt;/strong&gt; any frame that takes longer than a framecycle to render (16.7ms, or 1/60th of a second at 60hz) is considered ‘slow’, causing laggy UI responses and jittery animations. Sentry calculates the % of slow frames for each screen, so you can quickly find the conditions that lead to laggy or dropped frames. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Frozen frames:&lt;/strong&gt; Even worse is a frame that’s stuck for a longer period of time. . Sentry considered any frame render that takes &amp;gt;700ms ‘frozen’. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Frame delay:&lt;/strong&gt; This is the total ‘hang’ time accumulated on a screen due to slow &amp;amp; frozen frames. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In our own testing, we found these 3 metrics super useful for debugging the clunkiest-feeling parts of our mobile application. By combining these with mobile session replays, we were able to quickly recreate conditions that led to clunkiness for our users and get down to the brass tax. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://demo.arcade.software/O1ly5WbccGBhqKMS2fxq" rel="noopener noreferrer"&gt;Check out a 10-second interactive video here.&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why does this matter?
&lt;/h2&gt;

&lt;p&gt;We’re not narcissistic enough to claim that this is as impactful as Core Web Vitals was for the internet - but it’s a start. For mobile apps, it’s extremely difficult to know what to track, let alone how to track it. So, our mobile SDK team took the step to create an awesome new way to get your mobile application performance on track by monitoring a few vital metrics simply &amp;amp; scalably. &lt;/p&gt;

&lt;p&gt;More than just monitoring, Mobile Vitals gives you the why behind slow screens—not just the when. Every insight is backed by a trace, so you can see if the culprit is slow code on your main thread or a deeper issue like sluggish database queries or too complex layouts. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frluzzs943tt5w7fpy3dv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frluzzs943tt5w7fpy3dv.png" alt=" " width="800" height="469"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A few of our own, Lazar &amp;amp; Salma, recorded an awesome workshop about how to use Distributed Tracing to debug frontend issues with backend solutions. If you have slow screen loads on your mobile devices but aren’t able to find a root cause on the device itself, this is worth a watch. &lt;/p&gt;

&lt;p&gt;In short, Mobile Vitals is a tool to: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Monitor mobile top level performance metrics&lt;/li&gt;
&lt;li&gt;Easily identify key areas to improve&lt;/li&gt;
&lt;li&gt;Deep dive from a top level metric, to a specific performance issue on a particular screen, to a concrete &lt;a href="https://docs.sentry.io/platforms/apple/tracing/trace-propagation/" rel="noopener noreferrer"&gt;distributed trace&lt;/a&gt;, allowing you to debug and understand an issue across the full-stack&lt;/li&gt;
&lt;li&gt;Connect your metrics with &lt;a href="https://docs.sentry.io/product/explore/session-replay/mobile/" rel="noopener noreferrer"&gt;session replay&lt;/a&gt; and &lt;a href="https://docs.sentry.io/product/explore/profiling/" rel="noopener noreferrer"&gt;profiling&lt;/a&gt;, giving you full insights on what the user was experiencing and which code was running&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What numbers do I need to shoot for?
&lt;/h2&gt;

&lt;p&gt;Goodhart’s law - &lt;em&gt;when a metric becomes a target, it ceases to be a good metric&lt;/em&gt; - still rings true. That said, Google and Apple both openly state that their app store algorithms prioritize app quality, user engagement, app uninstalls and other key factors that are directly related to app performance. &lt;a href="https://x.com/LongTimeHistory/status/1892600734488617138" rel="noopener noreferrer"&gt;Google&lt;/a&gt; in particular recommends cold starts of &amp;lt; 5s, and warm starts of &amp;lt; 2s, but these are just starting points. &lt;/p&gt;

&lt;p&gt;Rather than just setting a target, mobile vitals monitoring is useful for objectively assessing the current state of your mobile app, deciding whether you want to try and improve it, and continuously evaluating your vitals metrics to make sure you’re not accidentally shipping a major slowdown. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdsqifc1um87h7kvdt386.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdsqifc1um87h7kvdt386.png" alt=" " width="295" height="321"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Credit: &lt;a href="https://xkcd.com/2899/" rel="noopener noreferrer"&gt;xkcd&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I get started monitoring my mobile vitals?
&lt;/h3&gt;

&lt;p&gt;Most of the Mobile Vitals metrics are instrumented automatically, so as long as you’ve set up the Sentry SDK in your mobile app, you should be good to go! TTFD is the only exception - you’ll need to call the API to let Sentry know when a screen is fully rendered. Here’s the setup for our most popular SDK’s:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.sentry.io/platforms/apple/guides/ios/tracing/instrumentation/automatic-instrumentation/" rel="noopener noreferrer"&gt;iOS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.sentry.io/platforms/android/tracing/instrumentation/automatic-instrumentation/" rel="noopener noreferrer"&gt;Android&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.sentry.io/platforms/react-native/tracing/instrumentation/automatic-instrumentation/" rel="noopener noreferrer"&gt;React Native&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.sentry.io/platforms/flutter/tracing/instrumentation/automatic-instrumentation/" rel="noopener noreferrer"&gt;Flutter&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you want to go the extra mile, you can also track custom &lt;a href="https://docs.sentry.io/platforms/apple/tracing/instrumentation/performance-metrics/" rel="noopener noreferrer"&gt;performance metrics&lt;/a&gt;, add &lt;a href="https://docs.sentry.io/platforms/apple/tracing/instrumentation/custom-instrumentation/" rel="noopener noreferrer"&gt;custom spans&lt;/a&gt;, and attach span attributes that you can use to calculate and monitor &lt;a href="https://blog.sentry.io/find-and-fix-performance-bottlenecks-with-sentrys-trace-explorer/" rel="noopener noreferrer"&gt;span metrics&lt;/a&gt; for your most critical user experiences. &lt;/p&gt;

&lt;p&gt;If you’re a mobile developer who cares about your user experiences, tracking Mobile Vitals is a great way to gauge how your mobile app releases impact device performance beyond just the top level metrics. Mobile Vitals is available to all paying Sentry customers - just turn it on in your SDK and give it a shot.&lt;/p&gt;

&lt;p&gt;Questions? Join our passionate community of mobile devs in the &lt;a href="https://discord.com/invite/sentry" rel="noopener noreferrer"&gt;Sentry Discord&lt;/a&gt;. &lt;/p&gt;

</description>
    </item>
    <item>
      <title>App store rankings love fast apps - mobile vitals can help you get there</title>
      <dc:creator>pry0rity</dc:creator>
      <pubDate>Fri, 28 Feb 2025 06:06:51 +0000</pubDate>
      <link>https://dev.to/pry0rity/app-store-rankings-love-fast-apps-mobile-vitals-can-help-you-get-there-5hkh</link>
      <guid>https://dev.to/pry0rity/app-store-rankings-love-fast-apps-mobile-vitals-can-help-you-get-there-5hkh</guid>
      <description>&lt;p&gt;Mobile devs know the struggle. Small regressions can cause big issues in production, and fixing them isn't as easy as pushing a quick patch. Unlike a web app, shipping fixes for apps means navigating app store approvals, and often hopping on meetings with customers to debug because mobile issues can be so challenging to recreate.&lt;/p&gt;

&lt;p&gt;Catching these issues before the 1-star reviews roll in is crucial. Luckily, Sentry just made it easier than ever.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mobile Vitals&lt;/strong&gt; - our latest addition to the Insights feature - highlights your slowest app starts, screen loads, and renders, so you can quickly tackle frozen or laggy interactions before users rage-quit. Whether you're building with React Native, Flutter, Android, or iOS, let's dive into what you can do with it.&lt;/p&gt;

&lt;p&gt;(P.S. We first wrote about mobile vitals years ago—if you're curious about how this new tool works under the hood, it's a &lt;a href="https://blog.sentry.io/mobile-vitals-four-metrics-every-mobile-developer-should-care-about/" rel="noopener noreferrer"&gt;good read&lt;/a&gt;.)&lt;/p&gt;

&lt;h2&gt;
  
  
  What does Mobile Vitals track?
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1: App Start Performance
&lt;/h3&gt;

&lt;p&gt;You’re doom-scrolling Instagram recipes and suddenly a great ad for a calorie counting app pops up. You download it out of curiosity, and for some reason, it takes 20 seconds to boot up the first time, and 10 seconds every time after that. What are you gonna do - keep forcing yourself to go through that every time you pop another Oreo, or just go back to the scale? &lt;/p&gt;

&lt;p&gt;Sentry tracks App Start performance to help you prevent your users from burning unnecessary calories uninstalling your apps out of frustration. We consider two key metrics here: cold starts &amp;amp; warm starts. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cold starts: You never get a second chance at a first impression. Sentry tracks cold starts independently so you can make sure your latest patch doesn’t crush your first impressions.&lt;/li&gt;
&lt;li&gt;Warm starts: Memory management on iOS &amp;amp; Android is complicated and often suspends some tasks while keeping your application ‘warm’ in the background. Pulling up your application from the recent apps selector should be lightning-fast, too. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It’s not just the top-level metric that matters. For warm starts, Sentry auto-instruments &lt;a href="https://docs.sentry.io/platforms/apple/guides/ios/tracing/instrumentation/automatic-instrumentation/#app-start-tracing" rel="noopener noreferrer"&gt;iOS&lt;/a&gt; and &lt;a href="https://docs.sentry.io/platforms/android/tracing/instrumentation/automatic-instrumentation/#app-start-instrumentation" rel="noopener noreferrer"&gt;Android&lt;/a&gt; apps with great granular detail. With iOS for instance, you can see pre-runtime, UIKit, frame renders and a bunch of &lt;a href="https://docs.sentry.io/platforms/apple/guides/ios/tracing/instrumentation/automatic-instrumentation/#app-start-tracing" rel="noopener noreferrer"&gt;other native tasks&lt;/a&gt; independently and work to fix what’s bottlenecking your app load performance. &lt;/p&gt;

&lt;p&gt;Clicking into a screen shows you an overview for all your most recent app loads leading up to that screen, which you can then drill into and see what sorts of tasks may be bottlenecking.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftthpdq7oqzxcow7o8p16.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftthpdq7oqzxcow7o8p16.png" alt="A Sentry UI screenshot showing App Cold Starts and App Warm Starts highlighted with " width="777" height="396"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  2: Screen Load Performance
&lt;/h3&gt;

&lt;p&gt;If it takes 5 seconds every time you want to load the comments page, nobody’s going to leave comments. It’s really that simple. &lt;/p&gt;

&lt;p&gt;Sentry helps you cut down on UX bloat by tracking &lt;a href="https://docs.sentry.io/product/insights/mobile/screen-loads/" rel="noopener noreferrer"&gt;TTID and TTFD&lt;/a&gt; for every screen load:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Time to Initial Display (TTID):&lt;/strong&gt; This tracks the time it takes for a screen to produce its first frame. Often, that’s just a background or a wall of text, but for a user, this is an important metric as it makes the app ‘feel’ fast. Sentry tracks this automatically by default.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Time to Full Display (TTFD):&lt;/strong&gt; More importantly is the time it takes for a screen to be fully loaded and functional. This is similar to the LCP core web vital on browser loads. TTFD can include content that’s lazy- or async-loaded after getting its first content, making it a more useful metric for a lot of your mobile screens. This isn’t enabled by default, but it’s super easy to set up. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Just like with App Starts, you can drill into Screen Loads by clicking into a screen and drilling into the full trace of the user session, or even dive into a mobile CPU profile. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://app.arcade.software/share/tYVh6rDXcj9UQNOESjOY" rel="noopener noreferrer"&gt;Check out a 10-second interactive video here. &lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  3: Screen Rendering Performance
&lt;/h3&gt;

&lt;p&gt;Your favorite solitaire clone ships a new patch, and suddenly you find your framerate dropping like a brick. You just got your phone a few years ago… is it time to upgrade? Nah, you’ll just throw out a 1-star review, uninstall, and go right back to Wordle.&lt;/p&gt;

&lt;p&gt;We surface 3 key &lt;a href="https://docs.sentry.io/product/insights/mobile/#app-start" rel="noopener noreferrer"&gt;screen rendering performance metrics&lt;/a&gt; by default: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Slow frames:&lt;/strong&gt; any frame that takes longer than a framecycle to render (16.7ms, or 1/60th of a second at 60hz) is considered ‘slow’, causing laggy UI responses and jittery animations. Sentry calculates the % of slow frames for each screen, so you can quickly find the conditions that lead to laggy or dropped frames. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Frozen frames:&lt;/strong&gt; Even worse is a frame that’s stuck for a longer period of time. . Sentry considered any frame render that takes &amp;gt;700ms ‘frozen’. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Frame delay:&lt;/strong&gt; This is the total ‘hang’ time accumulated on a screen due to slow &amp;amp; frozen frames. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In our own testing, we found these 3 metrics super useful for debugging the clunkiest-feeling parts of our mobile application. By combining these with mobile session replays, we were able to quickly recreate conditions that led to clunkiness for our users and get down to the brass tax. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://demo.arcade.software/O1ly5WbccGBhqKMS2fxq" rel="noopener noreferrer"&gt;Check out a 10-second interactive video here.&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why does this matter?
&lt;/h2&gt;

&lt;p&gt;We’re not narcissistic enough to claim that this is as impactful as Core Web Vitals was for the internet - but it’s a start. For mobile apps, it’s extremely difficult to know what to track, let alone how to track it. So, our mobile SDK team took the step to create an awesome new way to get your mobile application performance on track by monitoring a few vital metrics simply &amp;amp; scalably. &lt;/p&gt;

&lt;p&gt;More than just monitoring, Mobile Vitals gives you the why behind slow screens—not just the when. Every insight is backed by a trace, so you can see if the culprit is slow code on your main thread or a deeper issue like sluggish database queries or too complex layouts. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frluzzs943tt5w7fpy3dv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frluzzs943tt5w7fpy3dv.png" alt=" " width="800" height="469"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A few of our own, Lazar &amp;amp; Salma, recorded an awesome workshop about how to use Distributed Tracing to debug frontend issues with backend solutions. If you have slow screen loads on your mobile devices but aren’t able to find a root cause on the device itself, this is worth a watch. &lt;/p&gt;

&lt;p&gt;In short, Mobile Vitals is a tool to: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Monitor mobile top level performance metrics&lt;/li&gt;
&lt;li&gt;Easily identify key areas to improve&lt;/li&gt;
&lt;li&gt;Deep dive from a top level metric, to a specific performance issue on a particular screen, to a concrete &lt;a href="https://docs.sentry.io/platforms/apple/tracing/trace-propagation/" rel="noopener noreferrer"&gt;distributed trace&lt;/a&gt;, allowing you to debug and understand an issue across the full-stack&lt;/li&gt;
&lt;li&gt;Connect your metrics with &lt;a href="https://docs.sentry.io/product/explore/session-replay/mobile/" rel="noopener noreferrer"&gt;session replay&lt;/a&gt; and &lt;a href="https://docs.sentry.io/product/explore/profiling/" rel="noopener noreferrer"&gt;profiling&lt;/a&gt;, giving you full insights on what the user was experiencing and which code was running&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What numbers do I need to shoot for?
&lt;/h2&gt;

&lt;p&gt;Goodhart’s law - &lt;em&gt;when a metric becomes a target, it ceases to be a good metric&lt;/em&gt; - still rings true. That said, Google and Apple both openly state that their app store algorithms prioritize app quality, user engagement, app uninstalls and other key factors that are directly related to app performance. &lt;a href="https://x.com/LongTimeHistory/status/1892600734488617138" rel="noopener noreferrer"&gt;Google&lt;/a&gt; in particular recommends cold starts of &amp;lt; 5s, and warm starts of &amp;lt; 2s, but these are just starting points. &lt;/p&gt;

&lt;p&gt;Rather than just setting a target, mobile vitals monitoring is useful for objectively assessing the current state of your mobile app, deciding whether you want to try and improve it, and continuously evaluating your vitals metrics to make sure you’re not accidentally shipping a major slowdown. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdsqifc1um87h7kvdt386.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdsqifc1um87h7kvdt386.png" alt=" " width="295" height="321"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Credit: &lt;a href="https://xkcd.com/2899/" rel="noopener noreferrer"&gt;xkcd&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I get started monitoring my mobile vitals?
&lt;/h3&gt;

&lt;p&gt;Most of the Mobile Vitals metrics are instrumented automatically, so as long as you’ve set up the Sentry SDK in your mobile app, you should be good to go! TTFD is the only exception - you’ll need to call the API to let Sentry know when a screen is fully rendered. Here’s the setup for our most popular SDK’s:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.sentry.io/platforms/apple/guides/ios/tracing/instrumentation/automatic-instrumentation/" rel="noopener noreferrer"&gt;iOS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.sentry.io/platforms/android/tracing/instrumentation/automatic-instrumentation/" rel="noopener noreferrer"&gt;Android&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.sentry.io/platforms/react-native/tracing/instrumentation/automatic-instrumentation/" rel="noopener noreferrer"&gt;React Native&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.sentry.io/platforms/flutter/tracing/instrumentation/automatic-instrumentation/" rel="noopener noreferrer"&gt;Flutter&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you want to go the extra mile, you can also track custom &lt;a href="https://docs.sentry.io/platforms/apple/tracing/instrumentation/performance-metrics/" rel="noopener noreferrer"&gt;performance metrics&lt;/a&gt;, add &lt;a href="https://docs.sentry.io/platforms/apple/tracing/instrumentation/custom-instrumentation/" rel="noopener noreferrer"&gt;custom spans&lt;/a&gt;, and attach span attributes that you can use to calculate and monitor &lt;a href="https://blog.sentry.io/find-and-fix-performance-bottlenecks-with-sentrys-trace-explorer/" rel="noopener noreferrer"&gt;span metrics&lt;/a&gt; for your most critical user experiences. &lt;/p&gt;

&lt;p&gt;If you’re a mobile developer who cares about your user experiences, tracking Mobile Vitals is a great way to gauge how your mobile app releases impact device performance beyond just the top level metrics. Mobile Vitals is available to all paying Sentry customers - just turn it on in your SDK and give it a shot.&lt;/p&gt;

&lt;p&gt;Questions? Join our passionate community of mobile devs in the &lt;a href="https://discord.com/invite/sentry" rel="noopener noreferrer"&gt;Sentry Discord&lt;/a&gt;. &lt;/p&gt;

</description>
      <category>mobile</category>
      <category>ios</category>
      <category>reactnative</category>
      <category>android</category>
    </item>
    <item>
      <title>Checking your 6: how to use tracing to pinpoint and fix missing indexes in your app</title>
      <dc:creator>pry0rity</dc:creator>
      <pubDate>Thu, 12 Dec 2024 22:20:31 +0000</pubDate>
      <link>https://dev.to/pry0rity/checking-your-6-how-i-used-tracing-to-pinpoint-and-fix-missing-indexes-in-my-app-3ho1</link>
      <guid>https://dev.to/pry0rity/checking-your-6-how-i-used-tracing-to-pinpoint-and-fix-missing-indexes-in-my-app-3ho1</guid>
      <description>&lt;p&gt;Your app’s humming along, and then &lt;em&gt;boom&lt;/em&gt;—someone pulls up a massive table, and your database slows to a crawl. Your API hangs. Logs are screaming. Users are... not happy. Don’t panic. Way, way way too often, the culprit is missing indexes. Here’s how to find and fix them with a little help by tracing &amp;amp; monitoring your backend.&lt;br&gt;
&lt;em&gt;Beware: you might actually need caps lock for this.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  what even is an index?
&lt;/h2&gt;

&lt;p&gt;Think of an index as those tiny sticky notes the star student used in their textbook. Without an index, your query digs through every single row, like flipping through a giant unorganized binder. Add an index? Now it’s flipping straight to the right page.&lt;/p&gt;

&lt;p&gt;Here’s the classic example. Fetching emails from a users table:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'dev@example.com'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without an index, that query scans the whole table. Add this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;idx_users_email&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Boom. Your query just went from a cross-country road trip to first-class airfare.&lt;/p&gt;

&lt;h2&gt;
  
  
  finding slow queries (or: how I learned to stop guessing and love &lt;code&gt;EXPLAIN&lt;/code&gt;)
&lt;/h2&gt;

&lt;p&gt;Slow queries are like ghosts—you know they’re there, but where? Running SQL without checking EXPLAIN is like debugging without logs.&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;EXPLAIN&lt;/span&gt; &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'dev@example.com'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;------&lt;/span&gt;
&lt;span class="n"&gt;Seq&lt;/span&gt; &lt;span class="n"&gt;Scan&lt;/span&gt; &lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cost&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt;&lt;span class="p"&gt;..&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt; &lt;span class="k"&gt;rows&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;Filter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'dev@example.com'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If it says &lt;code&gt;Seq Scan&lt;/code&gt;, congrats—you’ve got a full table scan. That means your database is reading every single row. It’s screaming for help.&lt;/p&gt;

&lt;h2&gt;
  
  
  the index fix
&lt;/h2&gt;

&lt;p&gt;Let’s fix that nonsense. Add an index:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;idx_users_email&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Re-run &lt;code&gt;EXPLAIN&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;Index&lt;/span&gt; &lt;span class="n"&gt;Scan&lt;/span&gt; &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="n"&gt;idx_users_email&lt;/span&gt; &lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cost&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;29&lt;/span&gt;&lt;span class="p"&gt;..&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;44&lt;/span&gt; &lt;span class="k"&gt;rows&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;Index&lt;/span&gt; &lt;span class="n"&gt;Cond&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'dev@example.com'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now it should say &lt;code&gt;Index Scan&lt;/code&gt; or &lt;code&gt;Bitmap Heap Scan&lt;/code&gt; - the type of idx is up to the optimizer. Translation: Your query is finally taking the shortcut. &lt;/p&gt;

&lt;h2&gt;
  
  
  when to index (and when to chill)
&lt;/h2&gt;

&lt;p&gt;Indexes are powerful, but they’re not free. Too many can backfire, like over-optimizing code until it’s unreadable.&lt;/p&gt;

&lt;p&gt;Index these:&lt;/p&gt;

&lt;p&gt;1: Columns you filter on a lot:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'shipped'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;idx_orders_status&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;2: Columns used in &lt;code&gt;JOIN&lt;/code&gt;s:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;customers&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;idx_orders_customer_id&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;3: Columns you sort by:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;products&lt;/span&gt; &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;price&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;idx_products_price&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;products&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Skip these:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Low-selectivity columns (e.g., a status column with only active/inactive).&lt;/li&gt;
&lt;li&gt;Frequently updated columns (indexes need to be rebuilt for every write op).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  real-world debugging: how I found a missing index with sentry
&lt;/h2&gt;

&lt;p&gt;Here’s a recent example: I was working on a habit tracker app that ran slow as molasses on the homepage. The top query was this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;habits_daily&lt;/span&gt; 
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="nb"&gt;date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I already had my backend traced with &lt;a href="https://docs.sentry.io/product/performance/" rel="noopener noreferrer"&gt;Sentry&lt;/a&gt;, so the culprit jumped out. This one query was 20x slower than the next slowest... and took 10x more time in total because it was being called so frequently (Shoutout to the Backend Insights tab for making it so obvious.)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9va053de17y1pauwxahj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9va053de17y1pauwxahj.png" alt="Sentry table showing the slowest DB queries" width="800" height="180"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I ran &lt;code&gt;EXPLAIN&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;EXPLAIN&lt;/span&gt; &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;habits_daily&lt;/span&gt; 
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="nb"&gt;date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'2024-12-01'&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'123'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sure enough, it was doing a &lt;code&gt;Seq Scan&lt;/code&gt;. No index. No surprise.&lt;/p&gt;

&lt;p&gt;So, I added an index...&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;idx_habits_date_user&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;habits_daily&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;...and re-checked:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;EXPLAIN&lt;/span&gt; &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;habits_daily&lt;/span&gt; 
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="nb"&gt;date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'2024-12-01'&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'123'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;------&lt;/span&gt;
&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;Bitmap&lt;/span&gt; &lt;span class="n"&gt;Heap&lt;/span&gt; &lt;span class="n"&gt;Scan&lt;/span&gt; &lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="n"&gt;habits_daily&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;cost&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;35&lt;/span&gt;&lt;span class="p"&gt;..&lt;/span&gt;&lt;span class="mi"&gt;31&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;52&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="k"&gt;rows&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;143&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Boom. &lt;code&gt;Bitmap Heap Scan&lt;/code&gt;. Looks like it's running right, at least for now. &lt;br&gt;
The result? Query cost dropped by 1000x. My homepage stopped lagging. And yeah, seeing it all happen in Sentry made it ridiculously easy to catch and confirm the fix.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbqgagqsonc66zzfduhzj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbqgagqsonc66zzfduhzj.png" alt="Sentry dashboard showing a 20x reduction in query latency" width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  the tl;dr
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Use &lt;code&gt;EXPLAIN&lt;/code&gt; to find slow queries.&lt;/li&gt;
&lt;li&gt;Add indexes for high-impact filters, joins, and sort fields.&lt;/li&gt;
&lt;li&gt;Keep an eye on performance monitoring tools to zero in on bottlenecks fast.
Tools like &lt;a href="https://docs.sentry.io/product/insights/backend/" rel="noopener noreferrer"&gt;Sentry&lt;/a&gt; help you spot these issues before your users do. Seriously, if you’re not using something like that, you’re making life harder than it needs to be. Now go optimize your queries—you’ll thank yourself later.&lt;/li&gt;
&lt;/ul&gt;

</description>
    </item>
  </channel>
</rss>
