<?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: Molly Struve (she/her)</title>
    <description>The latest articles on DEV Community by Molly Struve (she/her) (@molly).</description>
    <link>https://dev.to/molly</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%2F119473%2F4fe2a414-c5d4-4cfe-b9da-8b9da90fb5e6.jpg</url>
      <title>DEV Community: Molly Struve (she/her)</title>
      <link>https://dev.to/molly</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/molly"/>
    <language>en</language>
    <item>
      <title>Interview Prep Questions</title>
      <dc:creator>Molly Struve (she/her)</dc:creator>
      <pubDate>Wed, 18 Aug 2021 20:12:12 +0000</pubDate>
      <link>https://dev.to/molly/interview-prep-questions-5dha</link>
      <guid>https://dev.to/molly/interview-prep-questions-5dha</guid>
      <description>&lt;p&gt;&lt;em&gt;Followup post for &lt;a href="https://dev.to/molly/setting-yourself-up-for-interview-success-15b7"&gt;Setting Yourself Up for Interview Success&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Below is a list of questions that I found extremely helpful when preparing for my interviews. Most of them came directly from the interviews I had and some were general ones I found online. &lt;/p&gt;

&lt;p&gt;When I used these to prep I put them on flash cards. I wrote the question on one side and my answer (or answers) on the other. Then I would go through them at the beginning of each week I was interviewing to prepare for my interviews. &lt;/p&gt;

&lt;p&gt;For reference, I was interviewing for Senior, Staff, and Engineering Manager positions with a focus on SRE (Site Reliability Engineering) and Reliability roles. I broke up the questions into categories to make them easier to sort through. They are in no particular order. I only numbered them to make them easy to reference. Using my answers to these questions I was able to answer every question that was thrown at me during my interviews. &lt;/p&gt;

&lt;h2&gt;
  
  
  General Questions
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;What do you want out of your next role?&lt;/li&gt;
&lt;li&gt;Tell me about a time you disagreed with someone. How did you handle it? What was the outcome?

&lt;ul&gt;
&lt;li&gt;The follow up questions might not get asked but get in the habit of answering those even when giving the initial answer. &lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;What are your greatest strengths?&lt;/li&gt;
&lt;li&gt;What are your greatest weaknesses?

&lt;ul&gt;
&lt;li&gt;You likely will not directly get asked the above questions, but if you are aware of what your strengths are then you can work them into your answers for other questions. If you know what your weaknesses are you can also mention those and how you are working to improve them. The key is to not have any blindspots.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;What is a time when something took a lot out of you and what did you learn? &lt;/li&gt;
&lt;li&gt;List the major achievements you are most proud of at your current job. &lt;/li&gt;
&lt;li&gt;Tell me about someone you look up to and why?&lt;/li&gt;
&lt;li&gt;Tell me about a time you made a mistake.&lt;/li&gt;
&lt;li&gt;What was a piece of feedback that was hard to hear but helped you improve?&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Sr./Staff Level IC Questions
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Tell me about a complex system you designed.&lt;/li&gt;
&lt;li&gt;Tell me about a large project you had to get buy in for. What was it and how did you get the buy in you needed?&lt;/li&gt;
&lt;li&gt;Tell me about a time you failed and what you learned from it.

&lt;ul&gt;
&lt;li&gt;Even if they don't ask for what you learned you always include that. It shows that you are humble and have the ability to grow. &lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;What do you value highly when working with software? (technical values)

&lt;ul&gt;
&lt;li&gt;Example answer: "I highly value simplicity bc I have found it leads to more reliable software. I also value automation bc humans doing things manually usually increases the chances of error." Expand more on your answer, but that gives you an idea of the kinds of answers you would give. &lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Tell me about a time you had to make a decision that involved short-term sacrifices for long-term gains.&lt;/li&gt;
&lt;li&gt;Tell me about a time when you solved a complex problem and how you went about it.

&lt;ul&gt;
&lt;li&gt;Note this is different than #1 which asks about designing a complex system. I choose a &lt;a href="https://dev.to/molly/10-tips-for-debugging-in-production-ko1"&gt;story about a gnarly bug&lt;/a&gt; I had to fix. &lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;What is the most challenging part of being in an engineering leader for you?&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  SRE/Reliability IC Questions
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Tell me about an incident that you ran?&lt;/li&gt;
&lt;li&gt;How do you define observability? How have you improved observability on past systems you have worked with? &lt;/li&gt;
&lt;li&gt;What tools have you built that were able to aid developers and/or fellow SREs?&lt;/li&gt;
&lt;li&gt;Give me an example of a time you made something more efficient.&lt;/li&gt;
&lt;li&gt;Give me an example of a time you improved a process. (could be team process or technical process) 

&lt;ul&gt;
&lt;li&gt;I talked about improving an on-call rotation for these types of questions.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;What does reliability mean to you? &lt;/li&gt;
&lt;li&gt;What is your preferred testing strategy?

&lt;ul&gt;
&lt;li&gt;Here you want to talk high level like unit vs end-to-end, smoke tests, canary deployments, etc. Give examples of what has worked well or not for you in the past. &lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Give me an example of a calculated risk that you took where speed was critical.&lt;/li&gt;
&lt;li&gt;What is your definition of SRE?&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Manager Questions
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;What is your management style?&lt;/li&gt;
&lt;li&gt;Tell me about the person you are most proud of empowering and growing. &lt;/li&gt;
&lt;li&gt;Tell me about a time when you had to give someone hard feedback.&lt;/li&gt;
&lt;li&gt;How do you deal with competing priorities?

&lt;ul&gt;
&lt;li&gt;When you get "how do you..." questions, answering with your strategy is good, but having a quick story to follow it up as an example is even better. &lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;How do you gain trust from your team?&lt;/li&gt;
&lt;li&gt;What do you look for when you are hiring?&lt;/li&gt;
&lt;li&gt;How do you determine if someone is technically competent for a job?&lt;/li&gt;
&lt;li&gt;How do you keep the jerks out?&lt;/li&gt;
&lt;li&gt;Have you ever had a large project killed?&lt;/li&gt;
&lt;li&gt;How do you broach the topic of technical debt when having to explain it to leadership and other stakeholders?&lt;/li&gt;
&lt;li&gt;What does the diversity of your current team look like?&lt;/li&gt;
&lt;li&gt;Tell me about a time you missed a deadline and how you handled it.&lt;/li&gt;
&lt;li&gt;When was a time you failed as a manager and how did you handle it and make it right?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Depending on your experience, you may not have an answer for all of the "Tell me about a time..." questions. That is OK! Be truthful about it and then explain how you would handle the situation if you found yourself faced with it. For example, I never have had a large project killed but I talked about how I would be objective and make sure my team fully understood why it had happened.&lt;/p&gt;

&lt;p&gt;If you have any other questions you would like to add to this list, feel free to drop them in the comments below 👇 &lt;/p&gt;

</description>
      <category>interviewing</category>
      <category>job</category>
      <category>career</category>
    </item>
    <item>
      <title>Setting Yourself Up for Interview Success</title>
      <dc:creator>Molly Struve (she/her)</dc:creator>
      <pubDate>Wed, 18 Aug 2021 20:11:53 +0000</pubDate>
      <link>https://dev.to/molly/setting-yourself-up-for-interview-success-15b7</link>
      <guid>https://dev.to/molly/setting-yourself-up-for-interview-success-15b7</guid>
      <description>&lt;p&gt;When it comes to interviewing, everyone has their own style and way they like to approach it. When I started looking for my next opportunity, I was not quite sure what to expect. I read a few blog posts to prep myself. These were incredibly helpful, but I found a few things even beyond their recommendations that helped me to succeed and that is what I want to share with you all. My approach may not be ideal for everyone but it worked very well for myself and using it I was able to land five job offers. &lt;/p&gt;

&lt;h2&gt;
  
  
  Interview Often
&lt;/h2&gt;

&lt;p&gt;Many folks will tell you that interviewing is a skill and you should make sure to get in some practice rounds before aiming towards a company you really want. I 100% found this to be the case. The first couple of phone screens I did I had to pause and think about the answers to the questions. As things went along, I was asked similar questions over and over and it got much easier. &lt;/p&gt;

&lt;p&gt;However, even with a couple of practice rounds, I still felt a bit stressed and anxious going into interviews. Then, as my interviewing frequency picked up, I noticed my anxiety nearly went away. When I had an interview scheduled almost every day my anxiety was almost non-existent because it felt like just one more meeting on my schedule. &lt;/p&gt;

&lt;p&gt;Once I had this realization I started to schedule interviews on a consistent cadence each week. This not only lowered my anxiety, but did wonders for my ability to solve the technical problems. Instead of getting nervous before a technical session, I would get focused. Interviewing on a consistent basis got me into a nice groove when it came to tackling technical interviews such as coding or systems design problems. &lt;/p&gt;

&lt;p&gt;I even went so far as to schedule a set of interviews with a company solely for the reason of getting me in the groove and prepped for one of the final companies I interviewed with. Not only did that extra company help prep me, but it turned out to be a fabulous interview and I got an offer that I seriously considered in the end.  &lt;/p&gt;

&lt;h2&gt;
  
  
  Flash Cards
&lt;/h2&gt;

&lt;p&gt;The first couple of roles I did hiring screens for determined I was not a great fit. One of the roles I actually thought I was a great fit for but realized I had not presented my skills as well as I could have during the phone screen.&lt;/p&gt;

&lt;p&gt;Immediately after those phone screens I decided it was time to get organized and take interviewing more seriously. I sat down and wrote out the questions they asked that, at the time, I didn't think I gave good answers to. Then I took time to come up with much better answers. I combed through my mind to find examples that would better highlight my skillset. In addition to writing down answers to the questions I was asked in the phone screens, I collected some more generic technical interview questions from various websites and wrote down answers for those as well. &lt;/p&gt;

&lt;p&gt;I initially put all of this into a Google doc. The Google doc was great for getting my thoughts written down, but I wanted something that I could use repeatedly to prep. To achieve this I went back to my college days and used the tried and true study method of flash cards! I made flash cards with the questions on one side and my answers on the other. Then, like in school, I would run through the flash cards at the beginning of each week to make sure I had a quick recall of all the stories and highlights of my career. &lt;/p&gt;

&lt;p&gt;Even though the interview questions didn't always exactly match those on my flash cards, the flash cards helped remind me about the key points throughout my career. Those key points and experiences were easily transferable to many types of questions. For folks that are interested, I wrote a second post listing out all of the questions I used to prepare. &lt;/p&gt;


&lt;div class="ltag__link"&gt;
  &lt;a href="/molly" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&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%2Fuser%2Fprofile_image%2F119473%2F4fe2a414-c5d4-4cfe-b9da-8b9da90fb5e6.jpg" alt="molly"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="https://dev.to/molly/interview-prep-questions-5dha" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;Interview Prep Questions&lt;/h2&gt;
      &lt;h3&gt;Molly Struve (she/her) ・ Aug 18 '21&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#interviewing&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#job&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#career&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  Professional Interview Prep
&lt;/h2&gt;

&lt;p&gt;Before starting this process I looked into companies and websites that I could use for interview prep. I felt pretty confident in my coding ability, but wanted to learn more about the systems design portion that many interviews have. To fill this void I signed up for &lt;a href="https://www.tryexponent.com/" rel="noopener noreferrer"&gt;Exponent&lt;/a&gt;. Exponent is a bit pricey, but it was completely worth it for me. One nice aspect is that they have some free content available so you can see if it is right for you and what you want to accomplish. I only ended up subscribing to it for two months. &lt;/p&gt;

&lt;p&gt;I found their guide to SWE interviewing to have a lot of great information in it. One part that was crucial in helping me was the systems design section. I had never done a systems design interview before so I had no clue about how to approach one.&lt;/p&gt;

&lt;p&gt;Exponent had many examples of folks answering system design questions which gave me an idea of what a good answer included. They also had lots of literature about how to break down the system design questions and what to cover when answering them. Once again, I ended up making flash cards around system design question elements and ran through those periodically to keep myself sharp on that front. &lt;/p&gt;

&lt;p&gt;For more details on how to I tackled my system design interviews stay tuned for a follow up blog post. 😎  &lt;/p&gt;

&lt;h2&gt;
  
  
  Take Notes
&lt;/h2&gt;

&lt;p&gt;During your interviews take notes! Especially if you are interviewing at multiple companies, it can be very hard to keep them all straight. I have Google docs for each company I interviewed with that have extensive notes from each process. &lt;/p&gt;

&lt;p&gt;These docs contain everything from the questions I asked, to the background of each interviewer, to any feelings I felt during the interview. These notes were invaluable at the end of the process when it came to decision time. &lt;/p&gt;

&lt;h2&gt;
  
  
  Give yourself 30 min before each interview
&lt;/h2&gt;

&lt;p&gt;Scheduling interviews around work can be challenging, but one thing I found is that scheduling in downtime right before is crucial to get yourself in the mental state to interview. My routine involved the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Gather up my notes already compiled for the company and write down questions I wanted to ask&lt;/li&gt;
&lt;li&gt;Research the interviewers if I had their names&lt;/li&gt;
&lt;li&gt;Turn off all notifications and close Slack&lt;/li&gt;
&lt;li&gt;Open any necessary tools (code editor, whiteboard tool, etc)&lt;/li&gt;
&lt;li&gt;Go to the bathroom and grab some water &lt;/li&gt;
&lt;li&gt;
&lt;a href="https://blog.ted.com/10-examples-of-how-power-posing-can-work-to-boost-your-confidence/" rel="noopener noreferrer"&gt;Power pose&lt;/a&gt; for a min and then have a quick dance party to get rid of any last min nerves and pump myself up.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last point is not for everyone, but for me, listening to some pump up music while dancing makes me feel really good about myself and gets me in a good headspace. I also listen to music before I compete with my horses to get in the zone which may be why it worked well in this scenario. &lt;/p&gt;

&lt;h2&gt;
  
  
  Enjoy it!
&lt;/h2&gt;

&lt;p&gt;Once I got into that interview groove I started to really enjoy them. I got to meet some incredible engineers and people while solving some fun and interesting problems. A lot of the code interviews I did felt like I was working with colleagues. Yes, I was responsible for coming up with the solution, but during the process I shared so many good laughs with interviewers as we commiserated over a typo or trying to put together a regex. &lt;/p&gt;

&lt;p&gt;Maybe I got incredibly lucky and the 30+ people I met from the 6 companies I considered were all just really nice. Or, maybe some of it had to do with the mindset I went into these interviews with. &lt;/p&gt;

&lt;p&gt;Once I had settled into interviewing, I approached each interview with a collaborative mindset. I was there to learn about the company as much as they wanted to learn about me. This made me much more relaxed and open during the interview and I think my interviewers ended up mirroring that feeling. If you go into an interview like you are heading into battle, then it likely will be a battle for both sides. &lt;/p&gt;

&lt;p&gt;Even though I had to say no to many companies, they are still places I would have loved to work. I would recommend them to others in a heartbeat because of how much I enjoyed their interview process. &lt;/p&gt;

&lt;h2&gt;
  
  
  Good Luck!
&lt;/h2&gt;

&lt;p&gt;Interviewing is not easy. It is a full on side hustle when you decide to take it on. But it is worth it in the end! Your perfect next opportunity is out there, now you just have to find it.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Anything else you all want to know about my interviewing process? Drop any additional questions you may have in the comments 👇&lt;/em&gt;&lt;/p&gt;

</description>
      <category>interviewing</category>
      <category>job</category>
      <category>career</category>
    </item>
    <item>
      <title>Incident Retro: Failing Comment Creation + Erroneous Push Notifications</title>
      <dc:creator>Molly Struve (she/her)</dc:creator>
      <pubDate>Wed, 14 Jul 2021 20:53:41 +0000</pubDate>
      <link>https://dev.to/devteam/incident-retro-failing-comment-creation-erroneous-push-notifications-55dj</link>
      <guid>https://dev.to/devteam/incident-retro-failing-comment-creation-erroneous-push-notifications-55dj</guid>
      <description>&lt;p&gt;&lt;em&gt;This incident retro was tougher than most to share because, despite the seriousness of the issue, it affected only a very small percentage of our user base. However, we learned some incredibly valuable lessons and I think it's only right that I give others the chance to learn from our mistake as well.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Context
&lt;/h2&gt;

&lt;p&gt;On Thursday July 8th, we merged a &lt;a href="https://github.com/forem/forem/pull/14121" rel="noopener noreferrer"&gt;very large PR&lt;/a&gt; that updated our code to start using our new User Settings instead of the deprecated fields on users. The goal of moving these fields to user settings is to lighten up the user model and make these types of settings more configurable on a per-Forem basis.  &lt;/p&gt;

&lt;h2&gt;
  
  
  Problem
&lt;/h2&gt;

&lt;p&gt;On July 9th around midday, it was brought to our attention &lt;a href="https://github.com/forem/forem/issues/14183" rel="noopener noreferrer"&gt;through a bug report&lt;/a&gt; that the comment creation experience on the frontend seemed to be broken. At this point folks on the team started looking into the problem. Using &lt;a href="https://www.honeycomb.io/" rel="noopener noreferrer"&gt;Honeycomb&lt;/a&gt;, we were able to confirm that the problem started when the User Settings PR was deployed which made it the prime suspect. &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%2Fforem.team%2Fremoteimages%2Fuploads%2Farticles%2Facuvsc0l8y4135nois0f.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%2Fforem.team%2Fremoteimages%2Fuploads%2Farticles%2Facuvsc0l8y4135nois0f.png" alt="Picture of a graph where the line is steady under 100ms and jumps to thousands" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I immediately dove into the PR and quickly found &lt;a href="https://github.com/forem/forem/pull/14121/files#diff-56d67da7087534bc7d4cffd953e00dc2e3eb41047f1e82f3124cd6d69c509873R38" rel="noopener noreferrer"&gt;the line of code that had been broken&lt;/a&gt;.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fforem.team%2Fremoteimages%2Fuploads%2Farticles%2Fjh78q3nsi71vrdyekkk0.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%2Fforem.team%2Fremoteimages%2Fuploads%2Farticles%2Fjh78q3nsi71vrdyekkk0.png" alt="Github PR dif" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We had incorrectly removed the &lt;code&gt;user_ids&lt;/code&gt; scope from the filtering used to collect user IDs for sending mobile push notifications. This was causing us to attempt to grab notification settings and IDs for all users with mobile notifications turned on which was often timing out. These timeouts would occur after a comment was created so the comment in most cases still existed but we never returned a success response to the frontend. This caused the frontend to freeze creating a bad experience for the user. &lt;/p&gt;
&lt;h2&gt;
  
  
  Fix
&lt;/h2&gt;

&lt;p&gt;In order to remedy this situation, we quickly added back the scope and deployed. Immediately the errors and timeouts went away and things returned to normal for comment creation. &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%2Fforem.team%2Fremoteimages%2Fuploads%2Farticles%2Fvpicn144aagfpsrbnnhs.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%2Fforem.team%2Fremoteimages%2Fuploads%2Farticles%2Fvpicn144aagfpsrbnnhs.png" alt="Graph of increased duration returning to normal" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At this point, we concluded that possibly some extra notifications had been sent but that the majority of these requests had timed out so we assumed we were in a good state now. I looked for some sort of push notification model in our app where we might have stored unsent notifications but upon not finding one figured we were clear. &lt;/p&gt;
&lt;h2&gt;
  
  
  More Problems
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;July 9th ~12pm EDT&lt;/strong&gt;: Unfortunately, the above assumption was incorrect. Just before midnight eastern, a team member reported she was getting random notifications on her phone. At this point, our mobile engineer jumped in to investigate and found that we had a ton of push notifications enqueued in Redis.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;July 9th ~2am EDT&lt;/strong&gt;: Mobile engineer posts about the issue in our #emergency Slack channel.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;July 9th ~7am EDT&lt;/strong&gt;: On-call engineer wakes up and sees the #emergency Slack message and jumps in to help investigate and try to resolve the issue. Shortly after, I also jumped in. It was at this point that I learned all about how our push notification system worked. We use &lt;a href="https://github.com/rpush/rpush" rel="noopener noreferrer"&gt;RPush&lt;/a&gt; for communicating with various push-notification services. RPush stores the data about the push notifications in Redis for quick insertion and removal times. We quickly assessed that we had a large number of push notifications enqueued in Redis waiting to be sent, despite the solution deployed the day before. &lt;/p&gt;
&lt;h2&gt;
  
  
  Fix 2
&lt;/h2&gt;

&lt;p&gt;Upon realizing we had all of these records in Redis, we took the following steps to resolve the issue:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Removal of the iOS cert from Heroku to prevent sending any notifications. Without this cert we could ensure we would not send anymore bad notifications and that would give us time to fix the data in Redis.&lt;/li&gt;
&lt;li&gt;To be safe, we removed all of the &lt;code&gt;PushNotifications::DeliveryWorker&lt;/code&gt; jobs from Sidekiq so as not to be pinging Redis for the keys causing our key collection to slow down. 

&lt;ul&gt;
&lt;li&gt;At the same time, we looped through all of the keys in Redis and collected all of the rpush ones. These included all delivered and undelivered keys. There were 1.2 million rpush keys so this process took about 30 mins. We used the below script:
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;redis&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;redis&lt;/span&gt;
&lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="n"&gt;key_collection&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;keys&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;scan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s2"&gt;"0"&lt;/span&gt;
  &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;
  &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;
  &lt;span class="n"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;key_collection&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;include?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'rpush:notifications'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;keys&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;scan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;Once we had the keys collected we double checked that they were the keys we wanted and then we deleted them ALL in batches of 10 using this code:
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;key_collection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each_slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;del&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;Once the keys were gone, we double checked the count and then added back the iOS cert to Heroku. &lt;/li&gt;
&lt;li&gt;After Heroku restarted, we ran a test to confirm that the notifications were back to sending and that we were recording delivered notifications correctly again in Redis. &lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Impact
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Comment Creation
&lt;/h3&gt;

&lt;p&gt;The comment creation flow on the frontend was broken for 30 hours after the UserSettings PR was deployed on July 8th. However, comment creation levels remained steady.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;irb(main):001:0&amp;gt; Comment.where(created_at: 36.hours.ago..Time.now).count
=&amp;gt; 762
irb(main):002:0&amp;gt; Comment.where(created_at: 72.hours.ago..36.hours.ago).count
=&amp;gt; 857
irb(main):003:0&amp;gt; Comment.where(created_at: 108.hours.ago..72.hours.ago).count
=&amp;gt; 852
irb(main):004:0&amp;gt; Comment.where(created_at: 144.hours.ago..108.hours.ago).count
=&amp;gt; 756
irb(main):005:0&amp;gt; Comment.where(created_at: 180.hours.ago..144.hours.ago).count
=&amp;gt; 587
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In total, there were 934 comments that were affected by the broken frontend interface. &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%2F3csl8linhu5fzipww4gq.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%2F3csl8linhu5fzipww4gq.png" alt="Honeycomb.io graph showing a total count of 934 comments over a 30 hour period" width="800" height="563"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Push Notifications
&lt;/h3&gt;

&lt;p&gt;Given DEV is early on in its mobile journey, only 0.2% of our users have registered devices that are able to receive push notifications. This means that only 0.2% of our users were affected by this incident. We consider ourselves lucky that we were able to expose some of these issues and have this incident remain very contained. &lt;/p&gt;

&lt;p&gt;Since the numbers of users affected was small, we decide to proactively reach out to all of those folks via email to apologize and explain why they &lt;em&gt;might&lt;/em&gt; have received erroneous notifications. &lt;/p&gt;

&lt;h2&gt;
  
  
  Learnings
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Large PRs
&lt;/h3&gt;

&lt;p&gt;Large PRs naturally come with more risk. The bug that kicked off this stream of events was a small change in a very large PR that got missed by multiple folks. For starters, the diff for the line was not particularly helpful in recognizing the change that caused the issue. In addition, there were so many lines that it was easy to overlook. Breaking down PRs is one way to prevent this. &lt;/p&gt;

&lt;p&gt;However, there are times when large, wide spread changes need to be made and in those cases you have to rely on your test suite. &lt;/p&gt;

&lt;h3&gt;
  
  
  Missing tests
&lt;/h3&gt;

&lt;p&gt;The feature that broke was completely untested. Had we been testing that feature properly I think there is a good chance the bug could have been caught. Immediately after pushing the hotfix &lt;a href="https://github.com/forem/forem/pull/14187" rel="noopener noreferrer"&gt;I added a test&lt;/a&gt; to ensure this never happens again. &lt;/p&gt;

&lt;h3&gt;
  
  
  Technical feature education
&lt;/h3&gt;

&lt;p&gt;After fixing the bug, none of us working on the problem were well educated about how our Push Notification systems worked. Being late on a Friday, we skimmed the code and concluded we would be good to go. This ended up being the wrong assumption as we later learned Saturday that push notifications, unlike app ones, are stored and enqueued via Redis. I think it's more important than ever that we are somehow sharing and educating each other about some of these larger features as we roll them out. &lt;/p&gt;

&lt;p&gt;Thankfully, we do &lt;a href="https://docs.forem.com/backend/push-notifications/" rel="noopener noreferrer"&gt;have some great Push Notification documentation&lt;/a&gt; but it was never sought out during the incident. Is there a way we can make docs like this more visible? During the incident we were all heads down in the code, should we maybe have some sort of URL link in the code to the docs?&lt;/p&gt;

&lt;h3&gt;
  
  
  Being more intentional
&lt;/h3&gt;

&lt;p&gt;I could have very easily reached out to our mobile team on Friday evening to double check that we were in a good state for Push Notifications. Once again, the whole Friday evening and wanting to be done caused me to accept my assumptions rather than challenge and check them. &lt;/p&gt;

&lt;p&gt;One easy way to prevent something like this is to use a checklist. A checklist (heavily used in aviation because it has been shown to be key in preventing incidents,) is an easy way to make sure you never miss something and are deliberate and intentional with your decisions. We have great checklists for handling Heroku incidents in our internal Gitbook. However, we don't have a general incident on-call checklist which we plan to add.  &lt;/p&gt;

&lt;h2&gt;
  
  
  Incident Response
&lt;/h2&gt;

&lt;p&gt;Before I dive into specifics, I want to point out that this is the first larger incident we have had in a while. Incidents being rare is GREAT! But, it also means that our incident response was a bit rusty and our documented incident processes were a bit out of date to handle this situation. Going forward we will be taking a closer look at all of these things to ensure we are keeping them relevant and useful based on the current state of our application and team. &lt;/p&gt;

&lt;h3&gt;
  
  
  Broken Comment Creation Reporting Flow
&lt;/h3&gt;

&lt;p&gt;The initial comment creation problem was reported by a DEV user via a GitHub issue and seen about 11 hours later by our internal engineering team. GitHub Issues are naturally not a very urgent form of reporting for us since they are handled during working hours. One way we could improve this flow would be to further communicate that urgent support issues need to be emailed to &lt;code&gt;yo@forem.com&lt;/code&gt;. Our customer success team is always monitoring these channels diligently and could more quickly triage and escalate an issue to the team. &lt;/p&gt;

&lt;p&gt;Manual reporting aside, ideally, we should have caught this programmatically with our monitoring by alerting on the increased HTTP request errors that were detected.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fforem.team%2Fremoteimages%2Fuploads%2Farticles%2Fianj96377wcnqd10q0l7.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%2Fforem.team%2Fremoteimages%2Fuploads%2Farticles%2Fianj96377wcnqd10q0l7.png" alt="Datadog graphs showing elevated error rates" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Going forward, we plan to set up a monitor to alert us for these changes so that we are the first to find out, rather than relying on users reporting the issue to us. With the proper monitor in place, we could have caught this within a couple of hours instead of over a day. &lt;/p&gt;

&lt;h3&gt;
  
  
  Broken Push Notifications Reporting Flow
&lt;/h3&gt;

&lt;p&gt;We responded to the broken comment creation issue immediately when it was surfaced, however, the followup push notification issues were not. Our engineer correctly pinged the #emergency channel when he realized that we were having problems with notifications but did not escalate the issue and wake up the on-call dev via PagerDuty. &lt;/p&gt;

&lt;p&gt;One way we could streamline this process would be to see if we could set up our #emergency Slack channel to automatically ping PageDuty when a message is posted. Another option is address some of our incident response rustiness by better educating the team on our desired incident response flows. This probably needs to be a reoccurring training that happens periodically so as to prevent processes and knowledge from getting stale as our systems evolve. &lt;/p&gt;

&lt;h3&gt;
  
  
  Technical Remediation
&lt;/h3&gt;

&lt;p&gt;One delay we encountered when trying to fix this issue was that the Rpush keys were stored in the same Redis instance as our Rails cache. Given our Rails cache is massive and contains 4+ million keys, looping through all of them to find only the Rpush keys was tedious and took a while. That cache is also very active which caused us to hit some timeouts when we were trying to perform heavier operations on it. &lt;/p&gt;

&lt;p&gt;We could have mitigated this friction by having either a separate Redis instance for Rpush OR by having those keys in a separate database away from our Rails cache keys. Going forward, as we grow our mobile platforms, I think we should make one of the above changes to ensure that we have easy access to these keys and records. It will also ensure that as we grow and scale push notifications we don't have to worry about impacting other systems like our core Rails app. &lt;/p&gt;

&lt;h2&gt;
  
  
  Thanks Everyone 🤗
&lt;/h2&gt;

&lt;p&gt;A lot of people gave up their personal time to come together on Friday and Saturday to mitigate and fix these issues. I truly appreciate the amazing team we have at Forem and their dedication to this software. We learned a lot about our systems in the process which ensures that this incident will not go to waste. I hope others can learn from this as well.&lt;/p&gt;

</description>
      <category>incident</category>
      <category>retro</category>
      <category>postmortem</category>
    </item>
    <item>
      <title>Goodbye Offline Page</title>
      <dc:creator>Molly Struve (she/her)</dc:creator>
      <pubDate>Wed, 17 Mar 2021 17:53:35 +0000</pubDate>
      <link>https://dev.to/devteam/goodbye-offline-page-5d98</link>
      <guid>https://dev.to/devteam/goodbye-offline-page-5d98</guid>
      <description>&lt;p&gt;Dear DEV Community Members, &lt;/p&gt;

&lt;p&gt;I wanted to bring you all up to speed on a decision the Forem team recently made to remove our &lt;a href="https://dev.to/devteam/instant-webpages-and-terabytes-of-data-savings-through-the-magic-of-service-workers-1mkc"&gt;Service Worker functionality&lt;/a&gt;, and thus, our offline page. This was not a decision we came to lightly.&lt;/p&gt;

&lt;p&gt;Like many of you, we all thoroughly enjoyed drawing in our browsers using the DEV offline page whenever our internet fritzed on us. Recently however, the cost of maintaining the Service Worker became exceedingly high for our team. Over the past couple of months, we've been having to put out one emergency fire after another, all because of our Service Worker functionality. &lt;/p&gt;

&lt;p&gt;A couple of weeks ago, I took a step back and assessed the situation. Given the fact that we lean heavily into edge caching, I figured that losing the Service Worker would not have a huge impact on performance and could save the Forem engineering team a lot of headaches. However, there still remained the question of the offline page. After chatting with &lt;a class="mentioned-user" href="https://dev.to/ben"&gt;@ben&lt;/a&gt; about our options, we decided to strip the Service Worker down so that its only function was to serve the offline page.&lt;/p&gt;


&lt;div class="ltag_github-liquid-tag"&gt;
  &lt;h1&gt;
    &lt;a href="https://github.com/forem/forem/pull/12834" rel="noopener noreferrer"&gt;
      &lt;img class="github-logo" alt="GitHub logo" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.dev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg"&gt;
      &lt;span class="issue-title"&gt;
        Reduce service worker functionality to minimal offline page
      &lt;/span&gt;
      &lt;span class="issue-number"&gt;#12834&lt;/span&gt;
    &lt;/a&gt;
  &lt;/h1&gt;
  &lt;div class="github-thread"&gt;
    &lt;div class="timeline-comment-header"&gt;
      &lt;a href="https://github.com/benhalpern" rel="noopener noreferrer"&gt;
        &lt;img class="github-liquid-tag-img" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Favatars.githubusercontent.com%2Fu%2F3102842%3Fv%3D4" alt="benhalpern avatar"&gt;
      &lt;/a&gt;
      &lt;div class="timeline-comment-header-text"&gt;
        &lt;strong&gt;
          &lt;a href="https://github.com/benhalpern" rel="noopener noreferrer"&gt;benhalpern&lt;/a&gt;
        &lt;/strong&gt; posted on &lt;a href="https://github.com/forem/forem/pull/12834" rel="noopener noreferrer"&gt;&lt;time&gt;Feb 26, 2021&lt;/time&gt;&lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="ltag-github-body"&gt;
      
&lt;h2&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;What type of PR is this? (check all applicable)&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;[x] Refactor&lt;/li&gt;
&lt;li&gt;[ ] Feature&lt;/li&gt;
&lt;li&gt;[x] Bug Fix&lt;/li&gt;
&lt;li&gt;[ ] Optimization&lt;/li&gt;
&lt;li&gt;[ ] Documentation Update&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;Description&lt;/h2&gt;
&lt;p&gt;This PR strips out some of our &lt;em&gt;very cool&lt;/em&gt; &lt;a href="https://dev.to/devteam/instant-webpages-and-terabytes-of-data-savings-through-the-magic-of-service-workers-1mkc" rel="nofollow"&gt;ReadableStream&lt;/a&gt; service worker functionality because it has been causing too many bugs related to the deployment of new code.&lt;/p&gt;
&lt;p&gt;The upside of this functionality is &lt;em&gt;very instant&lt;/em&gt; initial page loads, but the downside to the current implementation is too many instances of difficult-to-predict deployment cache issues. These were always painful to deal with, but are exceptionally difficult in a Forem world where our devops cannot act quickly to fix production issues the same way we can with a single deploy.&lt;/p&gt;
&lt;p&gt;The fundamental issue with our current implementation is just that it is &lt;em&gt;not exhaustive&lt;/em&gt; in terms of dealing with certain scenarios, and dealing with edge cases creates a lot of complexity.&lt;/p&gt;
&lt;p&gt;We suspect there are still UX benefits and SEO benefits to handling basic offline functionality...&lt;/p&gt;
&lt;p&gt;&lt;a href="https://user-images.githubusercontent.com/3102842/109359353-56dee900-7853-11eb-92e7-5eacc074f3f2.png" rel="nofollow noopener noreferrer"&gt;&lt;img width="408" alt="Screen Shot 2021-02-26 at 4 47 54 PM" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fuser-images.githubusercontent.com%2F3102842%2F109359353-56dee900-7853-11eb-92e7-5eacc074f3f2.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;And we still have our service worker lifecycle on top of which we may be able to methodically add back certain functionality that this PR removes, but we can do so with much more of an emphasis on ensuring all possible scenarios are accounted for so that we do not allow users to get caught in buggy situations.&lt;/p&gt;
&lt;p&gt;One additional adjustment I made was just removing the "DEV" portion of the image on the offline page (and some code cleanup there)... We may want to offer a new fun way for Forem admins to customize this page, but I figured this small adjustment to make for a more minimal and generalized offline page made sense here.&lt;/p&gt;
&lt;h2&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;Follow up&lt;/h2&gt;
&lt;p&gt;There is additional code that can be removed with this new approach, but due to the nature of how service workers run code already downloaded to user browsers (i.e. the cause of this complexity in the first place), it makes sense to wait at least a few weeks to remove some of the other code that is not needed anymore in case it could break old installs.&lt;/p&gt;
&lt;h2&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;Related Tickets &amp;amp; Documents&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://github.com/forem/internalEngineering/issues/333" rel="noopener noreferrer"&gt;https://github.com/forem/internalEngineering/issues/333&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;QA Instructions, Screenshots, Recordings&lt;/h2&gt;
&lt;p&gt;This is mostly an adoption of this functionality: &lt;a href="https://web.dev/offline-fallback-page/" rel="nofollow noopener noreferrer"&gt;https://web.dev/offline-fallback-page/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Eyeball the functionality and test different network conditions to ensure this does not cause new problems.&lt;/p&gt;
&lt;h3&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;UI accessibility concerns?&lt;/h3&gt;
&lt;p&gt;This should not cause new a11y issues.&lt;/p&gt;
&lt;h2&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;Added tests?&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;[ ] Yes&lt;/li&gt;
&lt;li&gt;[x] No, and this is why: I'm not really sure how to test this.&lt;/li&gt;
&lt;li&gt;[ ] I need help with writing tests&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;[Forem core team only] How will this change be communicated?&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;Will this PR introduce a change that impacts Forem members or creators, the
development process, or any of our internal teams? If so, please note how you
will share this change with the people who need to know about it.&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;[x] I've updated the [Developer Docs]&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;[optional] What gif best describes this PR or how it makes you feel?&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://camo.githubusercontent.com/443fd4bceb2cf0dd09a5186c0c81fe19bb02e1691c54a077a8a56769256a213b/68747470733a2f2f6d656469612e67697068792e636f6d2f6d656469612f3236753462343562384b6c67414237694d2f67697068792e676966" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/443fd4bceb2cf0dd09a5186c0c81fe19bb02e1691c54a077a8a56769256a213b/68747470733a2f2f6d656469612e67697068792e636f6d2f6d656469612f3236753462343562384b6c67414237694d2f67697068792e676966" alt="goodbye"&gt;&lt;/a&gt;&lt;/p&gt;

    &lt;/div&gt;
    &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/forem/forem/pull/12834" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;We deployed and rejoiced thinking our woes were over. &lt;/p&gt;

&lt;h3&gt;
  
  
  We were wrong
&lt;/h3&gt;

&lt;p&gt;A few days after this deploy, our emergency Slack channel was once again lit up with users who could not log in from Safari. We put together &lt;a href="https://github.com/forem/forem/pull/12907" rel="noopener noreferrer"&gt;a fix&lt;/a&gt; (and then &lt;a href="https://github.com/forem/forem/pull/12909" rel="noopener noreferrer"&gt;a follow up fix&lt;/a&gt; 🙈) and shipped it. A few days after that, the emergency channel again popped up with a flood of log in problems being reported from users. Once again &lt;a href="https://github.com/forem/forem/pull/12953" rel="noopener noreferrer"&gt;we shipped a fix&lt;/a&gt;. After the &lt;a href="https://github.com/forem/forem/pull/12971" rel="noopener noreferrer"&gt;third day of this happening&lt;/a&gt;, I decided to turn to my engineering leaders to get their thoughts on the whole situation.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What are folks' thoughts on the continuing issues we are having with the Service Worker? Ben greatly reduced its functionality to only the offline page bc of all the issues it has been causing and the giant sink it has been for developer time but it doesn't feel like things have gotten much better. My heart can't take many more days of coming back to my Slack with #emergency lit up and a 100 reply thread.&lt;/p&gt;

&lt;p&gt;Do we think things are going to get better once we have identified all the exception paths? OR do we still think it's going to be a landmine that will only continue to bite us? The cost of this offline page feature is getting pretty high and I am wondering if it's worth the cost. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;My incredible team and &lt;a class="mentioned-user" href="https://dev.to/ben"&gt;@ben&lt;/a&gt; all jumped in with their thoughts.&lt;/p&gt;

&lt;p&gt;But there was another complication thrown into the mix: our offline page was supposed to help our SEO rankings! However, we were not sure &lt;em&gt;how much&lt;/em&gt; it was supposed to help. In the end, we weighed all our options and determined that the best course of action was to remove the Service Worker completely and monitor our incoming traffic using Google's Search Console.&lt;/p&gt;

&lt;p&gt;Making engineering decisions is all about tradeoffs. In this case, the Service Worker feature was disruptive to both our users and to us as developers, which seemed to outweigh any benefits that it had provided. While removing the offline page is a bit disappointing, in the big scheme of things, it will make the Forem platform much more reliable and stable going forward.  &lt;/p&gt;

&lt;p&gt;For more details, check out the removal PR below. 👇&lt;/p&gt;


&lt;div class="ltag_github-liquid-tag"&gt;
  &lt;h1&gt;
    &lt;a href="https://github.com/forem/forem/pull/12974" rel="noopener noreferrer"&gt;
      &lt;img class="github-logo" alt="GitHub logo" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.dev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg"&gt;
      &lt;span class="issue-title"&gt;
        Removed service worker
      &lt;/span&gt;
      &lt;span class="issue-number"&gt;#12974&lt;/span&gt;
    &lt;/a&gt;
  &lt;/h1&gt;
  &lt;div class="github-thread"&gt;
    &lt;div class="timeline-comment-header"&gt;
      &lt;a href="https://github.com/nickytonline" rel="noopener noreferrer"&gt;
        &lt;img class="github-liquid-tag-img" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Favatars.githubusercontent.com%2Fu%2F833231%3Fv%3D4" alt="nickytonline avatar"&gt;
      &lt;/a&gt;
      &lt;div class="timeline-comment-header-text"&gt;
        &lt;strong&gt;
          &lt;a href="https://github.com/nickytonline" rel="noopener noreferrer"&gt;nickytonline&lt;/a&gt;
        &lt;/strong&gt; posted on &lt;a href="https://github.com/forem/forem/pull/12974" rel="noopener noreferrer"&gt;&lt;time&gt;Mar 11, 2021&lt;/time&gt;&lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="ltag-github-body"&gt;
      
&lt;h2&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;What type of PR is this? (check all applicable)&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;[ ] Refactor&lt;/li&gt;
&lt;li&gt;[ ] Feature&lt;/li&gt;
&lt;li&gt;[x] Bug Fix&lt;/li&gt;
&lt;li&gt;[ ] Optimization&lt;/li&gt;
&lt;li&gt;[ ] Documentation Update&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;Description&lt;/h2&gt;
&lt;p&gt;This removes all of our service worker functionality with a self-destructing service worker which appears to be the best approach for unregistering a service worker. See &lt;a href="https://github.com/NekR/self-destroying-sw#how-to-use" rel="noopener noreferrer"&gt;https://github.com/NekR/self-destroying-sw#how-to-use&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;Related Tickets &amp;amp; Documents&lt;/h2&gt;

&lt;h2&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;QA Instructions, Screenshots, Recordings&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Test this in Chrome, Firefox, and Safari. Note in Safari, there may be no service worker registered due to the work done with the service-companion.js file. In that case, ensure that no errors are thrown in the console of the web dev tools in Safari.&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Pull down latest of the main branch and deploy the site locally. Navigate to the home page and validate that the service worker is running by opening the developer tools and navigating to the developer tools.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Chrome&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://user-images.githubusercontent.com/833231/110863755-25bcda80-828f-11eb-98dd-38e9c1a12ce9.png" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fuser-images.githubusercontent.com%2F833231%2F110863755-25bcda80-828f-11eb-98dd-38e9c1a12ce9.png" alt="image"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Firefox&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://user-images.githubusercontent.com/833231/110955507-ade6c280-8317-11eb-8e3b-d306f4226a0c.png" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fuser-images.githubusercontent.com%2F833231%2F110955507-ade6c280-8317-11eb-8e3b-d306f4226a0c.png" alt="image"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Safari&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://user-images.githubusercontent.com/833231/110955743-e7b7c900-8317-11eb-95e7-91935d25cf9d.png" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fuser-images.githubusercontent.com%2F833231%2F110955743-e7b7c900-8317-11eb-95e7-91935d25cf9d.png" alt="image"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol start="2"&gt;
&lt;li&gt;Pull down the branch for this PR and deploy it locally. Refresh the browser window that is currently open and validate that the service worker gets unregistered.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Chrome&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://user-images.githubusercontent.com/833231/110863832-42591280-828f-11eb-8563-bb959a81d101.png" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fuser-images.githubusercontent.com%2F833231%2F110863832-42591280-828f-11eb-8563-bb959a81d101.png" alt="image"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Firefox&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://user-images.githubusercontent.com/833231/110955675-d66ebc80-8317-11eb-99cc-7c20cafcb94c.png" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fuser-images.githubusercontent.com%2F833231%2F110955675-d66ebc80-8317-11eb-99cc-7c20cafcb94c.png" alt="image"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Safari&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://user-images.githubusercontent.com/833231/110955754-e9818c80-8317-11eb-9dc8-095c7f6a2eb9.png" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fuser-images.githubusercontent.com%2F833231%2F110955754-e9818c80-8317-11eb-9dc8-095c7f6a2eb9.png" alt="image"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;UI accessibility concerns?&lt;/h3&gt;

&lt;p&gt;N/A this is front-end infrastructure&lt;/p&gt;

&lt;h2&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;Added tests?&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Yes&lt;/li&gt;
&lt;li&gt;[x] No, and this is why: I've removed tests related to the service worker, so existing tests should continue to pass.&lt;/li&gt;
&lt;li&gt;[ ] I need help with writing tests&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;[Forem core team only] How will this change be communicated?&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Will this PR introduce a change that impacts Forem members or creators, the
development process, or any of our internal teams? If so, please note how you
will share this change with the people who need to know about it.&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] I've updated the &lt;a href="https://docs.forem.com" rel="nofollow noopener noreferrer"&gt;Developer Docs&lt;/a&gt; and/or
&lt;a href="https://forem.gitbook.io/forem-admin-guide/" rel="nofollow noopener noreferrer"&gt;Admin Guide&lt;/a&gt;, or
&lt;a href="https://storybook.forem.com/" rel="nofollow noopener noreferrer"&gt;Storybook&lt;/a&gt; (for Crayons components)&lt;/li&gt;
&lt;li&gt;[ ] I've updated the README or added inline documentation&lt;/li&gt;
&lt;li&gt;[x] I will share this change in a &lt;a href="https://forem.dev/t/changelog" rel="nofollow noopener noreferrer"&gt;Changelog&lt;/a&gt;
or in a &lt;a href="http://forem.dev" rel="nofollow noopener noreferrer"&gt;forem.dev&lt;/a&gt; post&lt;/li&gt;
&lt;li&gt;[ ] I will share this change internally with the appropriate teams&lt;/li&gt;
&lt;li&gt;[ ] I'm not sure how best to communicate this change and need help&lt;/li&gt;
&lt;li&gt;[ ] This change does not need to be communicated, and this is why not: &lt;em&gt;please
replace this line with details on why this change doesn't need to be
shared&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;[optional] Are there any post deployment tasks we need to perform?&lt;/h2&gt;

&lt;h2&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;[optional] What gif best describes this PR or how it makes you feel?&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://camo.githubusercontent.com/5f75067069df05a1c71b11deef9162bc80e1c107c66a7e21b98f39241a78fde9/68747470733a2f2f6d656469612e67697068792e636f6d2f6d656469612f325a336c675a4f6849536b59552f67697068792e676966" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/5f75067069df05a1c71b11deef9162bc80e1c107c66a7e21b98f39241a78fde9/68747470733a2f2f6d656469612e67697068792e636f6d2f6d656469612f325a336c675a4f6849536b59552f67697068792e676966" alt='Samuel L. Jackson in Jurassic Park saying "Hold on to your butts"'&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;/div&amp;gt;
&amp;lt;div class="gh-btn-container"&amp;gt;&amp;lt;a class="gh-btn" href="https://github.com/forem/forem/pull/12974"&amp;gt;View on GitHub&amp;lt;/a&amp;gt;&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;
 
&lt;/div&gt;

</description>
      <category>changelog</category>
    </item>
    <item>
      <title>Duplicate Digest Email Incident Retro From January</title>
      <dc:creator>Molly Struve (she/her)</dc:creator>
      <pubDate>Tue, 16 Mar 2021 18:13:20 +0000</pubDate>
      <link>https://dev.to/molly/duplicate-digest-email-incident-retro-from-january-2on9</link>
      <guid>https://dev.to/molly/duplicate-digest-email-incident-retro-from-january-2on9</guid>
      <description>&lt;p&gt;&lt;em&gt;Sorry all that this incident retro is a couple of months late. We did the retro internally immediately after the incident occurred and then I put it on my TODO list to make it public on DEV and that's where it stayed for far too long. 😬&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Background Context
&lt;/h2&gt;

&lt;p&gt;Over the past few months, we have been working hard to make the Forem setup more configurable for our creators. In an effort to do this we recently updated how we create a Forem's community name. This was done by merging the following PR that appended the &lt;code&gt;collective_noun&lt;/code&gt;, if it was not disabled for the particular Forem, to the &lt;code&gt;SiteConfig.community_name&lt;/code&gt; through a DataUpdateScript:&lt;/p&gt;


&lt;div class="ltag_github-liquid-tag"&gt;
  &lt;h1&gt;
    &lt;a href="https://github.com/forem/forem/pull/12055" rel="noopener noreferrer"&gt;
      &lt;img class="github-logo" alt="GitHub logo" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.dev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg"&gt;
      &lt;span class="issue-title"&gt;
        Adds collective_noun Fields Back to SiteConfig Model
      &lt;/span&gt;
      &lt;span class="issue-number"&gt;#12055&lt;/span&gt;
    &lt;/a&gt;
  &lt;/h1&gt;
  &lt;div class="github-thread"&gt;
    &lt;div class="timeline-comment-header"&gt;
      &lt;a href="https://github.com/juliannatetreault" rel="noopener noreferrer"&gt;
        &lt;img class="github-liquid-tag-img" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Favatars1.githubusercontent.com%2Fu%2F32834804%3Fv%3D4" alt="juliannatetreault avatar"&gt;
      &lt;/a&gt;
      &lt;div class="timeline-comment-header-text"&gt;
        &lt;strong&gt;
          &lt;a href="https://github.com/juliannatetreault" rel="noopener noreferrer"&gt;juliannatetreault&lt;/a&gt;
        &lt;/strong&gt; posted on &lt;a href="https://github.com/forem/forem/pull/12055" rel="noopener noreferrer"&gt;&lt;time&gt;Dec 28, 2020&lt;/time&gt;&lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="ltag-github-body"&gt;
      &lt;h2&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;What type of PR is this? (check all applicable)&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;[ ] Refactor&lt;/li&gt;
&lt;li&gt;[ ] Feature&lt;/li&gt;
&lt;li&gt;[x] Bug Fix&lt;/li&gt;
&lt;li&gt;[ ] Optimization&lt;/li&gt;
&lt;li&gt;[ ] Documentation Update&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;Description&lt;/h2&gt;
&lt;p&gt;This PR adds the &lt;code&gt;collective_noun&lt;/code&gt; and &lt;code&gt;collective_noun_disabled&lt;/code&gt; columns back to the &lt;code&gt;SiteConfig&lt;/code&gt; model after running into a &lt;code&gt;data_update&lt;/code&gt; script failure in another PR. This is necessary so that when running the &lt;code&gt;data_update&lt;/code&gt; script responsible for updating the &lt;code&gt;community_name&lt;/code&gt;, we have access to the &lt;code&gt;collective_noun&lt;/code&gt; and &lt;code&gt;collective_noun_disabled&lt;/code&gt; methods. Additionally, this PR adds a &lt;code&gt;data_update&lt;/code&gt; script that conditionally updates &lt;code&gt;SiteConfig.community_name&lt;/code&gt; &lt;em&gt;if&lt;/em&gt; &lt;code&gt;SiteConfig.collective_noun_disabled&lt;/code&gt; returns &lt;code&gt;false&lt;/code&gt;. If, however, the forem has &lt;code&gt;collective_noun_disabled&lt;/code&gt; set to &lt;code&gt;true&lt;/code&gt;, then the &lt;code&gt;community_name&lt;/code&gt; will remain as-is and untouched.&lt;/p&gt;
&lt;h2&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;Related Tickets &amp;amp; Documents&lt;/h2&gt;
&lt;p&gt;Relates to PRs #11846 , PR #11973 , and PR #12054&lt;/p&gt;
&lt;h2&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;QA Instructions, Screenshots, Recordings&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;To QA these changes, first ensure that all checks pass and the Travis build is green, then&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Run the &lt;code&gt;data_update&lt;/code&gt; script manually and ensure that your Community's name updates accordingly&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;UI accessibility concerns?&lt;/h3&gt;
&lt;p&gt;N/A&lt;/p&gt;
&lt;h2&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;Added tests?&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;[x] Yes&lt;/li&gt;
&lt;li&gt;[ ] No, and this is why: &lt;em&gt;please replace this line with details on why tests
have not been included&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;[ ] I need help with writing tests&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;Added to documentation?&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;[ ] &lt;a href="https://docs.forem.com" rel="nofollow noopener noreferrer"&gt;Developer Docs&lt;/a&gt; and/or
&lt;a href="https://forem.gitbook.io/forem-admin-guide/" rel="nofollow noopener noreferrer"&gt;Admin Guide&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;[ ] README&lt;/li&gt;
&lt;li&gt;[x] No documentation needed&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;[optional] Are there any post-deployment tasks we need to perform?&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;data_update&lt;/code&gt; script included in this PR needs to successfully run!&lt;/p&gt;
&lt;h2&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;[optional] What gif best describes this PR or how it makes you feel?&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://camo.githubusercontent.com/d6f88f5a61196e38d0908a1e7f75a7d3462c7b744d0589c9f78aab12a51b6e92/68747470733a2f2f6d656469612e67697068792e636f6d2f6d656469612f45356d6b6369544561424c4e4b2f67697068792e676966" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/d6f88f5a61196e38d0908a1e7f75a7d3462c7b744d0589c9f78aab12a51b6e92/68747470733a2f2f6d656469612e67697068792e636f6d2f6d656469612f45356d6b6369544561424c4e4b2f67697068792e676966" alt="The Office: Days of Nonsense"&gt;&lt;/a&gt;&lt;/p&gt;

    &lt;/div&gt;
    &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/forem/forem/pull/12055" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Data Update Script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;DataUpdateScripts&lt;/span&gt;
  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AppendCollectiveNounToCommunityName&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;run&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="no"&gt;SiteConfig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;collective_noun_disabled&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="no"&gt;SiteConfig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;collective_noun&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;blank?&lt;/span&gt;

      &lt;span class="no"&gt;SiteConfig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;community_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="no"&gt;SiteConfig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;community_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="no"&gt;SiteConfig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;collective_noun&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The PR nicely addresses and updates all places in the code where we were using &lt;code&gt;community_name&lt;/code&gt; and therefore went out without any problems. &lt;/p&gt;

&lt;p&gt;However, 4 days later on Wednesday, December, 30th, we began to get reports of folks receiving duplicate digest emails. &lt;/p&gt;

&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-1344320876741861377-288" src="https://platform.twitter.com/embed/Tweet.html?id=1344320876741861377"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-1344320876741861377-288');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1344320876741861377&amp;amp;theme=dark"
  }



&lt;/p&gt;

&lt;p&gt;I quickly jumped in and determined the problem to be an &lt;code&gt;Emails::EnqueueDigestWorker&lt;/code&gt; that was continuously being restarted by deploys and sending the duplicate digest emails. &lt;/p&gt;
&lt;h2&gt;
  
  
  Bug Trigger*
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;em&gt;NOTE: I did not label this section "Cause" because I believe the real, root cause of this problem came much earlier in the year. Scroll down for more details.&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;code&gt;Emails::EnqueueDigestWorker&lt;/code&gt; is a worker that we have "turned off" for DEV because it takes too long to complete given the high number of users we have. We know it will keep restarting on deploys so we choose to use a rake task to send digest emails. To keep the worker from running we have a guard clause at the beginning of the worker that tells it to exit early if the environment is DEV. The guard clause at the time of the incident looked like this:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="no"&gt;SiteConfig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;community_name&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"DEV"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The problem was, when the above PR was deployed we changed &lt;code&gt;community_name&lt;/code&gt; from "DEV" to "DEV Community" causing this guard clause to no longer work which allowed the worker to start running.&lt;/p&gt;
&lt;h2&gt;
  
  
  Mitigation
&lt;/h2&gt;

&lt;p&gt;The first thing I did to mitigate the number of those affected by this problem was to clear all of the existing digest worker jobs out of Sidekiq. Once that was done, I pushed out a quick fix to update the hardcoded string to the current community name by adding "Community" to it.&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_github-liquid-tag"&gt;
  &lt;h1&gt;
    &lt;a href="https://github.com/forem/forem/pull/12082" rel="noopener noreferrer"&gt;
      &lt;img class="github-logo" alt="GitHub logo" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.dev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg"&gt;
      &lt;span class="issue-title"&gt;
        Bug Fix:Update dev.to check to use DEV Community
      &lt;/span&gt;
      &lt;span class="issue-number"&gt;#12082&lt;/span&gt;
    &lt;/a&gt;
  &lt;/h1&gt;
  &lt;div class="github-thread"&gt;
    &lt;div class="timeline-comment-header"&gt;
      &lt;a href="https://github.com/mstruve" rel="noopener noreferrer"&gt;
        &lt;img class="github-liquid-tag-img" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Favatars3.githubusercontent.com%2Fu%2F1813380%3Fv%3D4" alt="mstruve avatar"&gt;
      &lt;/a&gt;
      &lt;div class="timeline-comment-header-text"&gt;
        &lt;strong&gt;
          &lt;a href="https://github.com/mstruve" rel="noopener noreferrer"&gt;mstruve&lt;/a&gt;
        &lt;/strong&gt; posted on &lt;a href="https://github.com/forem/forem/pull/12082" rel="noopener noreferrer"&gt;&lt;time&gt;Dec 30, 2020&lt;/time&gt;&lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="ltag-github-body"&gt;
      &lt;p&gt;Recently we changed our site name on dev.to from DEV to DEV Community. This is used in our code in a couple of places and needs to be updated. From a prod console&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;irb(main):002:0&amp;gt; SiteConfig.community_name == "DEV Community"
=&amp;gt; true
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Without the guard clauses working properly we enqueued our Email Digest worker which takes hours to complete for DEV bc of all the users. Since we restart Sidekiq on every deployment, this worker was continuously restarting which is why the duplicate emails were getting sent.&lt;/p&gt;

    &lt;/div&gt;
    &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/forem/forem/pull/12082" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;



&lt;p&gt;While this fixed the immediate issue, the hardcoded string is what got us into this jam in the first place. To prevent future problems I quickly followed up with a second, more resilient PR, that used a more robust method to check for the DEV environment. &lt;/p&gt;


&lt;div class="ltag_github-liquid-tag"&gt;
  &lt;h1&gt;
    &lt;a href="https://github.com/forem/forem/pull/12083" rel="noopener noreferrer"&gt;
      &lt;img class="github-logo" alt="GitHub logo" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.dev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg"&gt;
      &lt;span class="issue-title"&gt;
        Refactor:Use dev_to? to Check for DEV
      &lt;/span&gt;
      &lt;span class="issue-number"&gt;#12083&lt;/span&gt;
    &lt;/a&gt;
  &lt;/h1&gt;
  &lt;div class="github-thread"&gt;
    &lt;div class="timeline-comment-header"&gt;
      &lt;a href="https://github.com/mstruve" rel="noopener noreferrer"&gt;
        &lt;img class="github-liquid-tag-img" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Favatars3.githubusercontent.com%2Fu%2F1813380%3Fv%3D4" alt="mstruve avatar"&gt;
      &lt;/a&gt;
      &lt;div class="timeline-comment-header-text"&gt;
        &lt;strong&gt;
          &lt;a href="https://github.com/mstruve" rel="noopener noreferrer"&gt;mstruve&lt;/a&gt;
        &lt;/strong&gt; posted on &lt;a href="https://github.com/forem/forem/pull/12083" rel="noopener noreferrer"&gt;&lt;time&gt;Dec 30, 2020&lt;/time&gt;&lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="ltag-github-body"&gt;
      &lt;h2&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;What type of PR is this? (check all applicable)&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;[x] Refactor&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;Description&lt;/h2&gt;
&lt;p&gt;Rather than checking the community name which we just found out can change easily, lets check the domain like we do in other places since that is far less likely to change.&lt;/p&gt;
&lt;h2&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;Related Tickets &amp;amp; Documents&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://github.com/forem/forem/pull/12082" rel="noopener noreferrer"&gt;https://github.com/forem/forem/pull/12082&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://camo.githubusercontent.com/26ba707f419f9a08a474960e67d5ffd036c3482d72586f32dda76b7ae3cc47b8/68747470733a2f2f6d656469612e74656e6f722e636f6d2f696d616765732f35623861393238343439666338303962303333643936633536633736326436662f74656e6f722e676966" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/26ba707f419f9a08a474960e67d5ffd036c3482d72586f32dda76b7ae3cc47b8/68747470733a2f2f6d656469612e74656e6f722e636f6d2f696d616765732f35623861393238343439666338303962303333643936633536633736326436662f74656e6f722e676966" alt="alt_text"&gt;&lt;/a&gt;&lt;/p&gt;

    &lt;/div&gt;
    &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/forem/forem/pull/12083" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  Root Cause and Learnings
&lt;/h2&gt;

&lt;p&gt;Even though the &lt;a href="https://github.com/forem/forem/pull/11846" rel="noopener noreferrer"&gt;the community name PR&lt;/a&gt; was the trigger for this issue, it was far from the root cause. This issue was really caused by the poor decision by myself to hard code this string in the first place.&lt;/p&gt;


&lt;div class="ltag_github-liquid-tag"&gt;
  &lt;h1&gt;
    &lt;a href="https://github.com/forem/forem/pull/10070" rel="noopener noreferrer"&gt;
      &lt;img class="github-logo" alt="GitHub logo" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.dev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg"&gt;
      &lt;span class="issue-title"&gt;
        Bug Fix:Run Individual Digest Workers inline for DEV
      &lt;/span&gt;
      &lt;span class="issue-number"&gt;#10070&lt;/span&gt;
    &lt;/a&gt;
  &lt;/h1&gt;
  &lt;div class="github-thread"&gt;
    &lt;div class="timeline-comment-header"&gt;
      &lt;a href="https://github.com/mstruve" rel="noopener noreferrer"&gt;
        &lt;img class="github-liquid-tag-img" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Favatars3.githubusercontent.com%2Fu%2F1813380%3Fv%3D4" alt="mstruve avatar"&gt;
      &lt;/a&gt;
      &lt;div class="timeline-comment-header-text"&gt;
        &lt;strong&gt;
          &lt;a href="https://github.com/mstruve" rel="noopener noreferrer"&gt;mstruve&lt;/a&gt;
        &lt;/strong&gt; posted on &lt;a href="https://github.com/forem/forem/pull/10070" rel="noopener noreferrer"&gt;&lt;time&gt;Aug 29, 2020&lt;/time&gt;&lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="ltag-github-body"&gt;
      &lt;h2&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;What type of PR is this? (check all applicable)&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;[x] Bug Fix&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;Description&lt;/h2&gt;
&lt;p&gt;Followup to &lt;a href="https://github.com/forem/forem/pull/10065" rel="noopener noreferrer"&gt;https://github.com/forem/forem/pull/10065&lt;/a&gt;, we need to also ensure the individual workers run inline and are not piling up in Sidekiq since running them in parallel is what is stressing the database.&lt;/p&gt;
&lt;h2&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;Added tests?&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;[x] yes&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href="https://camo.githubusercontent.com/805fd3d99329d52f09bec0c304bbbc7a1d5403f2cfb0ecd3f184f56672be6d6f/68747470733a2f2f6d656469612e74656e6f722e636f6d2f696d616765732f30663235336135396364386531633164633438316132656266316330376131342f74656e6f722e676966" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/805fd3d99329d52f09bec0c304bbbc7a1d5403f2cfb0ecd3f184f56672be6d6f/68747470733a2f2f6d656469612e74656e6f722e636f6d2f696d616765732f30663235336135396364386531633164633438316132656266316330376131342f74656e6f722e676966" alt="alt_text"&gt;&lt;/a&gt;&lt;/p&gt;

    &lt;/div&gt;
    &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/forem/forem/pull/10070" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Hardcoded strings, especially those for variables like this that can change, should be avoided at all costs when writing code.&lt;/strong&gt; At the very least you should be writing a method or using a CONSTANT to represent the string you want to check. This allows you to easily Find/Replace that method or CONSTANT in the future so it doesn't get lost in the shuffle as it did here. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The second problem was that I deployed that fix without a PR review.&lt;/strong&gt; I announced the problem in Slack and linked the PR, but based on the Slack message our database was in a dire state which is why I chose to expedite the fix. In the future, trying to get at least 1 PR review should always be the goal even if it means waiting an extra minute or two. Even if you don't immediately take the advice of the PR review, at least you can go back and update things later. &lt;/p&gt;

&lt;p&gt;The first fix that I pushed out in order to quickly update the digest worker worked but was un-ideal. This was pointed on in the PR review. I was still able to quickly push the hotfix but then immediately went back and wrote more thoughtful and robust code after. &lt;strong&gt;That flow is how the initial bug should have been handled.&lt;/strong&gt; Our recently renewed commitment to sticking more to our processes now is what led to us fixing this problem correctly the second time around, while the first time we left the door open for future problems.&lt;/p&gt;

&lt;p&gt;Finally, the more we can move away from this "DEV is a special snowflake" concept the better. If every environment is treated the same then you don't have to worry about edge cases popping up unexpectedly in production for specific environments. The final goal is to remove that guard clause altogether so every Forem sends its email digests the same way. Once again, this eliminates another special case solution that lowers the risk of future surprises. &lt;/p&gt;

&lt;h2&gt;
  
  
  Handling Hotfixes
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Always try to get a PR review even for hotfixes!&lt;/strong&gt; Most of us are not running life-saving software. If your site remains down for an extra min or two because you did the responsible thing and got a code review, it's not the end of the world. Having a good code review will decrease your chances of shipping unreliable code that could cause future issues. &lt;/p&gt;

&lt;p&gt;If a fix MUST go out immediately and you cannot get a review beforehand(maybe it's super late and no one is online), then &lt;strong&gt;get a follow-up review.&lt;/strong&gt; Ask someone to review the code after the fact to make sure it is sound and is not so hacky as to lead to future problems.&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;Don't hard code strings and get a PR review! And if all else fails, learn from your mistakes and strive not to make them again. Happy coding!  &lt;/p&gt;

</description>
      <category>incident</category>
      <category>ruby</category>
      <category>postmortem</category>
    </item>
    <item>
      <title>Welcoming Katie Davis and Suzanne Aitchison to the Forem Team!</title>
      <dc:creator>Molly Struve (she/her)</dc:creator>
      <pubDate>Thu, 14 Jan 2021 22:47:27 +0000</pubDate>
      <link>https://dev.to/devteam/welcoming-katie-davis-and-suzanne-aitchison-to-the-forem-team-4f7j</link>
      <guid>https://dev.to/devteam/welcoming-katie-davis-and-suzanne-aitchison-to-the-forem-team-4f7j</guid>
      <description>&lt;p&gt;We are very excited to start off 2021 with the addition of two new staff members who will focus on Forem's front end development. Please meet Katie Davis (Senior Software Engineer) and Suzanne Aitchison (Software Engineer).&lt;/p&gt;

&lt;p&gt;Katie and Suzanne will work closely with Senior Software Engineer, &lt;a class="mentioned-user" href="https://dev.to/nickytonline"&gt;@nickytonline&lt;/a&gt; and Head of Engineering, &lt;a class="mentioned-user" href="https://dev.to/molly"&gt;@molly&lt;/a&gt; to support the ongoing development and maintenance of dev.to, Forem, as well as other apps and projects. &lt;/p&gt;

&lt;p&gt;Our Forem rollout is off to a fast-moving start, and we are thrilled to have such highly-skilled additions to our tight-knit team. &lt;/p&gt;

&lt;p&gt;Please give a special welcome to Katie and Suzanne and check out their introductory posts.&lt;/p&gt;


&lt;div class="ltag__link"&gt;
  &lt;a href="/katiedavis" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&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%2Fuser%2Fprofile_image%2F494542%2F0e8c942b-c9fa-4fe6-be70-6fd685744566.jpeg" alt="katiedavis"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="https://dev.to/katiedavis/hi-i-m-joining-the-forem-team-1ja6" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;Hi! I'm Joining the Forem Team 🎉&lt;/h2&gt;
      &lt;h3&gt;Katie Davis ・ Jan 14 '21&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#meta&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#career&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#forem&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;



&lt;div class="ltag__link"&gt;
  &lt;a href="/s_aitchison" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&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%2Fuser%2Fprofile_image%2F197075%2Fac841cbd-abbb-4760-be69-6909cef48656.jpg" alt="s_aitchison"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="https://dev.to/s_aitchison/i-joined-the-forem-team-1dh6" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;I joined the Forem team!&lt;/h2&gt;
      &lt;h3&gt;Suzanne Aitchison ・ Jan 14 '21&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#meta&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#career&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#forem&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&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%2Fdmju8ucf4bhidfrcvbg7.gif" 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%2Fdmju8ucf4bhidfrcvbg7.gif" alt="Alt text of image" width="384" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>meta</category>
    </item>
    <item>
      <title>What's the best or worst recruitment line you have heard?</title>
      <dc:creator>Molly Struve (she/her)</dc:creator>
      <pubDate>Fri, 18 Sep 2020 22:37:17 +0000</pubDate>
      <link>https://dev.to/molly/what-s-the-best-or-worst-recruitment-line-you-have-heard-3o9j</link>
      <guid>https://dev.to/molly/what-s-the-best-or-worst-recruitment-line-you-have-heard-3o9j</guid>
      <description>&lt;p&gt;After reading yet another pesky, eye-rolling recruiter email, I started wondering, what kinds of emails others get. Share the best or worst lines you have ever received from a recruiter! &lt;/p&gt;

</description>
      <category>discuss</category>
      <category>watercooler</category>
    </item>
    <item>
      <title>Rake::Task .enhance() Method Explained</title>
      <dc:creator>Molly Struve (she/her)</dc:creator>
      <pubDate>Wed, 05 Aug 2020 18:33:09 +0000</pubDate>
      <link>https://dev.to/molly/rake-task-enhance-method-explained-3bo0</link>
      <guid>https://dev.to/molly/rake-task-enhance-method-explained-3bo0</guid>
      <description>&lt;p&gt;I recently fixed a bug in our &lt;a href="https://github.com/forem/forem" rel="noopener noreferrer"&gt;Forem software&lt;/a&gt; that led to some serious Ruby learnings that I have to share.&lt;/p&gt;

&lt;h1&gt;
  
  
  The Bug
&lt;/h1&gt;

&lt;p&gt;The problem we were having was that a Postgres view we use to expose data to our team, &lt;a href="https://github.com/ankane/hypershield" rel="noopener noreferrer"&gt;called &lt;code&gt;hypershield&lt;/code&gt;&lt;/a&gt;, was not getting refreshed correctly. Ideally, we want this view refreshed AFTER we run migrations so that it is up to date with the database. However, it came to my attention when looking at our logs that the &lt;code&gt;hypershield&lt;/code&gt; view was being refreshed BEFORE we ran migrations.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[hypershield] Refreshing schemas
[hypershield] Success!
== 20200726215928 ChangeTagIdsToBigints: migrating ============================
== 20200726215928 ChangeTagIdsToBigints: migrated (4.9509s) ===================
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This led to our &lt;code&gt;hypershield&lt;/code&gt; views being out of date with our actual database. &lt;/p&gt;

&lt;h1&gt;
  
  
  .enhance()
&lt;/h1&gt;

&lt;p&gt;To ensure that the &lt;code&gt;hypershield&lt;/code&gt; view is refreshed when we migrate our database we use the &lt;a href="https://www.rubydoc.info/gems/rake/Rake/Task#enhance-instance_method" rel="noopener noreferrer"&gt;Rake::Task method &lt;code&gt;.enhance()&lt;/code&gt;&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Rake&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"db:prepare"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;enhance&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s2"&gt;"hypershield:refresh"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the past when I used the method &lt;code&gt;enhance&lt;/code&gt; it always ran the additional rake task AFTER the task I was "enhancing". This got me really confused as to why the behavior was suddenly different, so I went digging. &lt;/p&gt;

&lt;p&gt;During my digging, I came across the Rake::Task docs. Here, I opened up the source code for the method.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;enhance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;deps&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kp"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="vi"&gt;@prerequisites&lt;/span&gt; &lt;span class="o"&gt;|=&lt;/span&gt; &lt;span class="n"&gt;deps&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;deps&lt;/span&gt;
  &lt;span class="vi"&gt;@actions&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;block&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;block_given?&lt;/span&gt;
  &lt;span class="nb"&gt;self&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first thing that struck me was that the behavior was different if you passed in an argument versus a block. &lt;/p&gt;

&lt;p&gt;When passed an argument, that argument became one of the &lt;code&gt;@prerequisites&lt;/code&gt; for the task, meaning it was run BEFORE. When passed a block, the block was added to a list of &lt;code&gt;@actions&lt;/code&gt;. According to the docs, &lt;code&gt;@actions&lt;/code&gt; are "attached to a task", meaning they run AFTER the task. &lt;/p&gt;

&lt;h1&gt;
  
  
  The Fix
&lt;/h1&gt;

&lt;p&gt;If I want to refresh our &lt;code&gt;hypershield&lt;/code&gt; view after we run migrations I need to pass that refresh task to &lt;code&gt;enhance&lt;/code&gt; in a block and not as an argument. The &lt;a href="https://github.com/forem/forem/pull/9546" rel="noopener noreferrer"&gt;final fix&lt;/a&gt; looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Rake&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"db:prepare"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;enhance&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="no"&gt;Rake&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"hypershield:refresh"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, the &lt;code&gt;hypershield&lt;/code&gt; view is updated AFTER migrations are run. This ensures the view is always up to date with our database. &lt;strong&gt;NOTE&lt;/strong&gt; the &lt;code&gt;.execute&lt;/code&gt; that we have to use to invoke our task. This is not needed when you pass the task as an argument, but it is needed when you are using a block. &lt;/p&gt;

&lt;h1&gt;
  
  
  TL;DR
&lt;/h1&gt;

&lt;p&gt;Passing a task as an argument to &lt;code&gt;enhance&lt;/code&gt; causes it to run BEFORE the task you are "enhancing".&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Rake&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"task_A"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;enhance&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s2"&gt;"task_B"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="c1"&gt;# Runs task_B&lt;/span&gt;
&lt;span class="c1"&gt;# Runs task_A&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Passing a task to &lt;code&gt;enhance&lt;/code&gt; in a block causes it to run AFTER the task you are "enhancing".&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Rake&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"task_A"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;enhance&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="no"&gt;Rake&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"task_B"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="c1"&gt;# Runs task_A&lt;/span&gt;
&lt;span class="c1"&gt;# Runs task_B&lt;/span&gt;
&lt;span class="sb"&gt;```



Now go and enhance away!!!

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/6tuyrapc49w9prajk3va.gif)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
      <category>rake</category>
    </item>
    <item>
      <title>Share Your Best Typo Story!</title>
      <dc:creator>Molly Struve (she/her)</dc:creator>
      <pubDate>Thu, 25 Jun 2020 23:11:56 +0000</pubDate>
      <link>https://dev.to/molly/share-your-best-typo-story-4961</link>
      <guid>https://dev.to/molly/share-your-best-typo-story-4961</guid>
      <description>&lt;p&gt;I recently posted a tweet about fixing a bug that turned out to be a misspelled word. This has happened to me more times than I care to admit 😅&lt;/p&gt;

&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-1276195170292322311-566" src="https://platform.twitter.com/embed/Tweet.html?id=1276195170292322311"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-1276195170292322311-566');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1276195170292322311&amp;amp;theme=dark"
  }



&lt;/p&gt;

&lt;p&gt;Anyways, all of the replies I can totally relate to, and now I want to hear more! What is your best typo story? Is there a word you can never get right? Did you spend hours debugging something that turned out to be a typo? Let's hear what ya got because we have all been there! &lt;/p&gt;

</description>
      <category>watercooler</category>
      <category>discuss</category>
      <category>justforfun</category>
    </item>
    <item>
      <title>Using KnapsackPro to Parallelize Your Tests</title>
      <dc:creator>Molly Struve (she/her)</dc:creator>
      <pubDate>Fri, 19 Jun 2020 14:20:37 +0000</pubDate>
      <link>https://dev.to/molly/using-knapsackpro-to-parallelize-your-tests-1lbe</link>
      <guid>https://dev.to/molly/using-knapsackpro-to-parallelize-your-tests-1lbe</guid>
      <description>&lt;p&gt;Recently, DEV was struggling with CI build times. It got to the point where &lt;a href="https://travis-ci.com/" rel="noopener noreferrer"&gt;Travis&lt;/a&gt; builds were taking up to 30 mins to complete. On top of that, we had a few flaky specs in our build and the result was some very frustrated developers. &lt;/p&gt;

&lt;p&gt;When I began trying to figure out how to solve this issue, the first strategy that came to mind was parallelizing the build process. This means splitting up our test suite into chunks and running each of those chunks at the same time. The challenge with this is figuring out how to split the chunks up so that each chunk runs in the same amount of time.&lt;/p&gt;

&lt;p&gt;For example, if you have a 30 min build and you want to split it into 3 parallel builds, ideally you want each build to run for 10 min. In order to do this, you have to figure out how long each of your tests takes to run. While grappling with this problem, one of my coworkers suggested I checkout &lt;a href="https://knapsackpro.com/dashboard/organizations/1142/projects/1022/test_suites/1434/builds" rel="noopener noreferrer"&gt;KnapsackPro&lt;/a&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  KnapsackPro
&lt;/h1&gt;

&lt;p&gt;&lt;em&gt;NOTE: KnapsackPro has two modes, &lt;a href="https://github.com/KnapsackPro/knapsack_pro-ruby#queue-mode" rel="noopener noreferrer"&gt;Queue Mode&lt;/a&gt; and Regular Mode. Currently, DEV is using Regular Mode and that is what I will be covering in the rest of this blog post.&lt;/em&gt; &lt;/p&gt;

&lt;p&gt;&lt;a href="https://knapsackpro.com/dashboard/organizations/1142/projects/1022/test_suites/1434/builds" rel="noopener noreferrer"&gt;KnapsackPro&lt;/a&gt; allows you to evenly split up your tests between parallel CI builds so that they run in the most optimal way to save you time. KnapsackPro does this by recording the time each test takes to run. It then uses that timing data to split your tests up equally in terms of runtime into however many groups you choose. This sounded like a great solution so I started digging into the docs trying to figure out how to get it all setup&lt;/p&gt;

&lt;h1&gt;
  
  
  Getting Started: KnapsackPro and Ruby
&lt;/h1&gt;

&lt;p&gt;Before I go any further, I have to say that the &lt;a href="https://docs.knapsackpro.com/integration/" rel="noopener noreferrer"&gt;KnapsackPro docs&lt;/a&gt; are some of the best I have worked with. They offer a thorough step by step setup plan for whatever language or testing framework you are using. In addition, their FAQ docs cover just about every possible buggy scenario you might run into. All of this made setting up KnapsackPro a straight forward process. &lt;/p&gt;

&lt;p&gt;Here are the steps to setup KnapsackPro with a Rails project. I pulled these straight out of the &lt;a href="https://docs.knapsackpro.com/knapsack_pro-ruby/guide/" rel="noopener noreferrer"&gt;KnapsackPro gem installation guide&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Install the Gem
&lt;/h2&gt;

&lt;p&gt;Add these lines to your application's Gemfile and then run &lt;code&gt;bundle install&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;group&lt;/span&gt; &lt;span class="ss"&gt;:test&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:development&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s1"&gt;'knapsack_pro'&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  Step 2: Customize Setup Based on Tests
&lt;/h2&gt;

&lt;p&gt;Once you have the gem installed then its time to set up your configuration based on what kind of testing framework you use. To figure out how to configure the gem, KnapsackPro gives you this handy installation guide. &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%2Fi%2Fma1ij7q6dd4bv11mii6z.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%2Fi%2Fma1ij7q6dd4bv11mii6z.png" alt="Alt Text" width="800" height="679"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Simply select your testing frameworks and gems and it will tell you what to add to your configuration. In our case, we use Rspec, Webmock, and TravisCI which gave us these additional steps to perform. &lt;/p&gt;
&lt;h3&gt;
  
  
  RSpec Setup
&lt;/h3&gt;

&lt;p&gt;Add the following at the beginning of your &lt;code&gt;spec/rails_helper.rb&lt;/code&gt; or &lt;code&gt;spec/spec_helper.rb&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'knapsack_pro'&lt;/span&gt;
&lt;span class="no"&gt;KnapsackPro&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Adapters&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;RSpecAdapter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bind&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  VCR and Webmock Configuration
&lt;/h3&gt;

&lt;p&gt;We use VCR and Webmock so we needed to add the Knapsack Pro API subdomain to ignore hosts for those configurations.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'vcr'&lt;/span&gt;
&lt;span class="no"&gt;VCR&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configure&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hook_into&lt;/span&gt; &lt;span class="ss"&gt;:webmock&lt;/span&gt; &lt;span class="c1"&gt;# or :fakeweb&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ignore_hosts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'localhost'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'127.0.0.1'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'0.0.0.0'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'api.knapsackpro.com'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# add below when you hook into webmock&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'webmock/rspec'&lt;/span&gt;
&lt;span class="no"&gt;WebMock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;disable_net_connect!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;allow_localhost: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;allow: &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'api.knapsackpro.com'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;To make sure everything loads properly, ensure you have require false for your webmock gem when VCR is hooked into it.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;group&lt;/span&gt; &lt;span class="ss"&gt;:test&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s1"&gt;'vcr'&lt;/span&gt;
  &lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s1"&gt;'webmock'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;require: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The docs also state:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you happen to see your tests failing due to WebMock not allowing requests to Knapsack Pro API it means you probably reconfigure WebMock in some of your tests. For instance, you may use &lt;code&gt;WebMock.reset!&lt;/code&gt; or it's called automatically in the after(:each) block, if you require 'webmock/rspec'. These setups will remove api.knapsackpro.com from allowed domains. Please try below to fix this issue:&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;RSpec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configure&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;after&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:suite&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="no"&gt;WebMock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;disable_net_connect!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="ss"&gt;allow_localhost: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;allow: &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s1"&gt;'api.knapsackpro.com'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Travis CI Setup
&lt;/h3&gt;

&lt;p&gt;Using the &lt;a href="https://docs.travis-ci.com/user/speeding-up-the-build/#parallelizing-your-builds-across-virtual-machines" rel="noopener noreferrer"&gt;Travis matrix feature&lt;/a&gt; we were able to parallelize our builds on Travis with the following updates to our &lt;code&gt;.travis.yml&lt;/code&gt; file.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bundle&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;exec&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;rake&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;knapsack_pro:rspec"&lt;/span&gt;
&lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;global&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# tokens should be set in travis settings in web interface to avoid expose tokens in build logs&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;KNAPSACK_PRO_TEST_SUITE_TOKEN_RSPEC=rspec-token&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;KNAPSACK_PRO_CI_NODE_TOTAL=3&lt;/span&gt;
  &lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;KNAPSACK_PRO_CI_NODE_INDEX=0&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;KNAPSACK_PRO_CI_NODE_INDEX=1&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;KNAPSACK_PRO_CI_NODE_INDEX=2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Below is the DEV Pull Request that made all of these changes in our repository. &lt;/p&gt;


&lt;div class="ltag_github-liquid-tag"&gt;
  &lt;h1&gt;
    &lt;a href="https://github.com/thepracticaldev/dev.to/pull/8390"&gt;
      &lt;img class="github-logo" alt="GitHub logo" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.dev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg"&gt;
      &lt;span class="issue-title"&gt;
        Spec Speedup: Use Knapsack to Run Parallel Builds
      &lt;/span&gt;
      &lt;span class="issue-number"&gt;#8390&lt;/span&gt;
    &lt;/a&gt;
  &lt;/h1&gt;
  &lt;div class="github-thread"&gt;
    &lt;div class="timeline-comment-header"&gt;
      &lt;a href="https://github.com/mstruve" rel="noopener noreferrer"&gt;
        &lt;img class="github-liquid-tag-img" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Favatars3.githubusercontent.com%2Fu%2F1813380%3Fv%3D4" alt="mstruve avatar"&gt;
      &lt;/a&gt;
      &lt;div class="timeline-comment-header-text"&gt;
        &lt;strong&gt;
          &lt;a href="https://github.com/mstruve" rel="noopener noreferrer"&gt;mstruve&lt;/a&gt;
        &lt;/strong&gt; posted on &lt;a href="https://github.com/thepracticaldev/dev.to/pull/8390"&gt;&lt;time&gt;Jun 10, 2020&lt;/time&gt;&lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="ltag-github-body"&gt;
      &lt;h2&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;What type of PR is this? (check all applicable)&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;[x] Optimization&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;Description&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://user-images.githubusercontent.com/1813380/84311282-a4ca8a00-ab28-11ea-8b8b-d605a1dee817.png" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fuser-images.githubusercontent.com%2F1813380%2F84311282-a4ca8a00-ab28-11ea-8b8b-d605a1dee817.png" alt="Screen Shot 2020-06-10 at 2 42 30 PM"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This PR introduces a service called &lt;a href="https://knapsackpro.com/" rel="nofollow noopener noreferrer"&gt;Knapsack&lt;/a&gt; to help us parallelize our spec suite as evenly as possible. The first time I ran Knapsack in our build it ran every test separately &lt;a href="https://docs.google.com/spreadsheets/d/1CaoqVcytPFW27VDziqwn-1nTsN4cohdz-Zl6BXINP9Q/edit#gid=0" rel="nofollow noopener noreferrer"&gt;and recorded the time it took to run the test.&lt;/a&gt; Using this information Knapsack then splits the tests up for us into 3 equally timed groups to run in parallel each time we run our test suite. This is how regular mode works.&lt;/p&gt;
&lt;p&gt;The changes in this PR are introducing the gem and setting it up.  All of them were made with the help of the &lt;a href="https://docs.knapsackpro.com/knapsack_pro-ruby/guide/" rel="nofollow noopener noreferrer"&gt;Knapsack installation guide&lt;/a&gt; which walks you through all the changes you should make to get it working properly.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;NOTE&lt;/strong&gt; - There is currently a bug with the parallelization in Travis that causes the &lt;code&gt;--local&lt;/code&gt; flag for our bundler command to be ignored. This means on your first build, since there is no travis cache, the jobs will likely take 13min. I am in contact with Travis support to get this resolved.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://user-images.githubusercontent.com/1813380/84309864-5d42fe80-ab26-11ea-86cd-6d2610c4c619.png" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fuser-images.githubusercontent.com%2F1813380%2F84309864-5d42fe80-ab26-11ea-86cd-6d2610c4c619.png" alt="Screen Shot 2020-06-10 at 2 25 58 PM"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Why aren't we using Queue Mode? Ideally, we want to use queue mode. In Queue Mode Knapsack sends us groups of 3-5 specs at once and then when they finish, sends another group of specs. It keeps doing this until all specs have been run. This is obviously the fastest approach but we ran into some errors with the jobs hanging. My goal is to get the regular version out then try to debug that hanging issue so we can use queue mode.&lt;/p&gt;
&lt;p&gt;How much does Knapsack cost? FREE bc we are open source and I must say the founder has been extremely helpful in getting us going and holding my hand through the integration process.&lt;/p&gt;
&lt;p&gt;@snackattas&lt;/p&gt;
&lt;p&gt;&lt;a href="https://camo.githubusercontent.com/715fe1f0f43a35749c3490e0c6b6e20e5fa6af1e/68747470733a2f2f6d656469612e67697068792e636f6d2f6d656469612f356d5970613669416330706f654c3452726d2f3230302e676966" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/715fe1f0f43a35749c3490e0c6b6e20e5fa6af1e/68747470733a2f2f6d656469612e67697068792e636f6d2f6d656469612f356d5970613669416330706f654c3452726d2f3230302e676966" alt="alt_text"&gt;&lt;/a&gt;&lt;/p&gt;

    &lt;/div&gt;
    &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/thepracticaldev/dev.to/pull/8390"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;



&lt;p&gt;If you click through to the pull request, you will notice we also made some additional changes to ensure our code coverage checks and other CI steps ran efficiently and correctly with our new parallel builds.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using the Gem
&lt;/h2&gt;

&lt;p&gt;With all of those changes in place, you will then want to push your branch up and let the KnapsackPro API do its thing. Keep in mind, the first run will NOT be optimal because the knapsack_pro gem will record the execution time of every one of your tests. &lt;/p&gt;

&lt;p&gt;To make sure everything was recorded successfully, you can check the &lt;a href="https://knapsackpro.com/dashboard/organizations/1142/projects/1022/test_suites/1434/builds/a722ab1b-3bd8-491c-b260-66c4e9b2aa62" rel="noopener noreferrer"&gt;build metrics on your KnapsackPro API dashboard&lt;/a&gt;. &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%2Fi%2F1vlwdtsxknhm7qypjawu.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%2Fi%2F1vlwdtsxknhm7qypjawu.png" alt="Alt Text" width="800" height="407"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here you can find everything from node build times to a breakdown of how long each test took to run. Once KnapsackPro has that data, then it can strategically split your tests up as evenly as possible for all future builds. Your second test suite run on your CI provider will be parallelized with the optimal test suite split if the first run was recorded correctly.&lt;/p&gt;

&lt;h1&gt;
  
  
  Gotchas
&lt;/h1&gt;

&lt;h3&gt;
  
  
  Setting Up KnapsackPro for Forks
&lt;/h3&gt;

&lt;p&gt;One hiccup we ran into when implementing KnapsackPro was that it would not work for forks because forks do not have access to our KnapsackPro tokens in Travis. We ended up seeing this error&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Missing environment variable KNAPSACK_PRO_TEST_SUITE_TOKEN. You should set environment variable like KNAPSACK_PRO_TEST_SUITE_TOKEN_RSPEC (note there is suffix _RSPEC at the end). knapsack_pro gem will set KNAPSACK_PRO_TEST_SUITE_TOKEN based on KNAPSACK_PRO_TEST_SUITE_TOKEN_RSPEC value. If you use other test runner than RSpec then use proper suffix.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Once again, the great KnapsackPro docs came to the rescue with a &lt;a href="https://knapsackpro.com/faq/question/how-to-make-knapsack_pro-works-for-forked-repositories-of-my-project" rel="noopener noreferrer"&gt;section in the FAQ that explained how to get KnapsackPro to work with forked branches&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The TL;DR of the solution is that we had to create an executable file &lt;code&gt;bin/knapsack_pro_rspec&lt;/code&gt; in our main project repository to handle the missing tokens.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$KNAPSACK_PRO_TEST_SUITE_TOKEN_RSPEC&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nv"&gt;KNAPSACK_PRO_ENDPOINT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https://api-disabled-for-fork.knapsackpro.com &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nv"&gt;KNAPSACK_PRO_TEST_SUITE_TOKEN_RSPEC&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;disabled-for-fork &lt;span class="se"&gt;\&lt;/span&gt;
    bundle &lt;span class="nb"&gt;exec &lt;/span&gt;rake knapsack_pro:rspec 
&lt;span class="k"&gt;else&lt;/span&gt;
    &lt;span class="c"&gt;# Regular Mode&lt;/span&gt;
    bundle &lt;span class="nb"&gt;exec &lt;/span&gt;rake knapsack_pro:rspec
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Then, in our &lt;code&gt;.tavis.yml&lt;/code&gt; file, we replaced &lt;code&gt;bundle exec rake knapsack_pro:rspec&lt;/code&gt; with &lt;code&gt;bin/knapsack_pro_rspec&lt;/code&gt;. You can see the changes in this PR:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_github-liquid-tag"&gt;
  &lt;h1&gt;
    &lt;a href="https://github.com/thepracticaldev/dev.to/pull/8413"&gt;
      &lt;img class="github-logo" alt="GitHub logo" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.dev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg"&gt;
      &lt;span class="issue-title"&gt;
        Allow Knapsack to work for Forks
      &lt;/span&gt;
      &lt;span class="issue-number"&gt;#8413&lt;/span&gt;
    &lt;/a&gt;
  &lt;/h1&gt;
  &lt;div class="github-thread"&gt;
    &lt;div class="timeline-comment-header"&gt;
      &lt;a href="https://github.com/mstruve" rel="noopener noreferrer"&gt;
        &lt;img class="github-liquid-tag-img" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Favatars3.githubusercontent.com%2Fu%2F1813380%3Fv%3D4" alt="mstruve avatar"&gt;
      &lt;/a&gt;
      &lt;div class="timeline-comment-header-text"&gt;
        &lt;strong&gt;
          &lt;a href="https://github.com/mstruve" rel="noopener noreferrer"&gt;mstruve&lt;/a&gt;
        &lt;/strong&gt; posted on &lt;a href="https://github.com/thepracticaldev/dev.to/pull/8413"&gt;&lt;time&gt;Jun 11, 2020&lt;/time&gt;&lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="ltag-github-body"&gt;
      &lt;p&gt;In order to get KnapsackPro to work on forks we need &lt;a href="https://knapsackpro.com/faq/question/how-to-make-knapsack_pro-works-for-forked-repositories-of-my-project" rel="nofollow noopener noreferrer"&gt;to use this workaround from the Knapsack docs&lt;/a&gt;&lt;/p&gt;

    &lt;/div&gt;
    &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/thepracticaldev/dev.to/pull/8413"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;



&lt;p&gt;The new script tries to hit the KnapsackPro API, but without the token, it fails. Upon failure, it will fallback on grouping tests by directory names and you will see an output that looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;W, [2020-06-17T08:45:14.412458 #8343]  WARN -- : [knapsack_pro] Next request in 2s...
W, [2020-06-17T08:45:16.577365 #8343]  WARN -- : [knapsack_pro] #&amp;lt;SocketError: Failed to open TCP connection to api-disabled-for-fork.knapsackpro.com:443 (getaddrinfo: Name or service not known)&amp;gt;
W, [2020-06-17T08:45:16.577552 #8343]  WARN -- : [knapsack_pro] Fallback mode started. We could not connect with Knapsack Pro API. Your tests will be executed based on directory names. Read more about fallback mode at https://github.com/KnapsackPro/knapsack_pro-ruby#what-happens-when-knapsack-pro-api-is-not-availablenot-reachable-temporarily
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Grouping by directory name is not quite as ideal as grouping by timing, but it beats not parallelizing things at all.&lt;/p&gt;

&lt;h1&gt;
  
  
  Why KnapsackPro is AWESOME!
&lt;/h1&gt;

&lt;h3&gt;
  
  
  Great Documentation
&lt;/h3&gt;

&lt;p&gt;I said it above and I will say it again, KnapsackPro is extremely well documented which makes getting started with it very straight forward. There is literally a doc for just about every question or scenario you can run into.&lt;/p&gt;

&lt;h3&gt;
  
  
  Public Dashboard
&lt;/h3&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%2Fi%2F5plq3m1vkkyvk164jfsz.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%2Fi%2F5plq3m1vkkyvk164jfsz.png" alt="Alt Text" width="800" height="509"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One of the big benefits of KnapsackPro is that they give you the option to make your &lt;a href="https://knapsackpro.com/dashboard/organizations/1142/projects/1022/test_suites/1434/builds" rel="noopener noreferrer"&gt;dashboard and test stats public&lt;/a&gt;. This is a huge deal for us at DEV because we have so many external contributors. It is amazing when those contributors can access the same data that the core team can. &lt;/p&gt;

&lt;h3&gt;
  
  
  Customer Service is Beyond Good
&lt;/h3&gt;

&lt;p&gt;KnapsackPro is a small company, which means when you send them a support email it goes straight to a real person! No automated response, no bouncing around between different support people with canned responses. You go straight to someone who will be able to help you.&lt;/p&gt;

&lt;p&gt;At the start of this, DEV's test suite was in pretty rough shape in terms of flakiness and reliability. It's also worth mentioning that I am a Site Reliability Engineer, not a QA engineer, so I struggled quite a bit getting everything setup. What got me through was the support and help I received from KnapsackPro along the way. Email responses were quick(within 24 hrs) and not only would they answer my questions, but they also offered me tips about how to set things up even more efficiently than I was.&lt;/p&gt;

&lt;h1&gt;
  
  
  End Result
&lt;/h1&gt;

&lt;p&gt;The end result of all this work is that we now have a test suite that runs in about 10 min! In addition, when we come across a new flaky spec, we can simply retry the one job that failed, instead of having to run the entire suite. &lt;/p&gt;

&lt;p&gt;The move also forced us to separate our testing process and our deploy process. Now when a deploy fails for some external reason, we can simply retry the deploy step. Before, we would have to restart the entire build and run the whole test suite again before we could deploy. It was not fun.&lt;/p&gt;

&lt;p&gt;Devs are always looking for a better and faster CI and KnapsackPro is a great tool that can help you accomplish that.  &lt;/p&gt;

&lt;p&gt;&lt;em&gt;I was not enticed or asked to write this blog post by anyone from KnapsackPro. I know many companies struggle with slow test suites and I wanted to share how we tackled that problem at DEV so hopefully, others might be able to do what we did to solve their own challenges.&lt;/em&gt; &lt;/p&gt;

</description>
      <category>testing</category>
      <category>ruby</category>
      <category>tutorial</category>
      <category>rspec</category>
    </item>
    <item>
      <title>Tips for Running Scripts in Production</title>
      <dc:creator>Molly Struve (she/her)</dc:creator>
      <pubDate>Tue, 16 Jun 2020 16:32:49 +0000</pubDate>
      <link>https://dev.to/molly/tips-for-running-scripts-in-production-4c72</link>
      <guid>https://dev.to/molly/tips-for-running-scripts-in-production-4c72</guid>
      <description>&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%2Fi%2Ffdncjl2tm3jw7k3epr70.gif" 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%2Fi%2Ffdncjl2tm3jw7k3epr70.gif" alt="Alt Text" width="498" height="281"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I know, how dare I suggest running a script in production. I am a Site Reliability Engineer, I should never condone such craziness. But truth is, there will likely come a time when you need to run a script in production to update or cleanup some data. In this post I am going to give you some tips about how to write and execute a script in production as safely as possible. &lt;/p&gt;

&lt;h1&gt;
  
  
  1) Track Your Progress
&lt;/h1&gt;

&lt;p&gt;Nothing is worse than writing a giant block of code, pasting it into a console, then hitting enter and watching it sit there. You have no idea where the code is in the script or what it is doing and that, at least for me, is terrifying. &lt;/p&gt;

&lt;p&gt;For this reason, you always want to make sure you output some sort of progress meter from your scripts. This allows you to follow along and know where you are in your process. In the event you are using Ruby, consider some well placed puts statements. Below is a script that we recently used at DEV to clean up some incorrectly cached data. Notice the puts statements throughout the script that allow us to follow along as it does its work.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;invalid_articles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="no"&gt;Tag&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;id: &lt;/span&gt;&lt;span class="n"&gt;tag_ids&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;find_each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;taggings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;

  &lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;taggings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each_with_index&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;tagging&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="n"&gt;article&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tagging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;taggable&lt;/span&gt;
    &lt;span class="k"&gt;next&lt;/span&gt; &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;article&lt;/span&gt;

    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;article&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;cached_tag_list: &lt;/span&gt;&lt;span class="n"&gt;article&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pluck&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:name&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;", "&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;
      &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"Artcle update success &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;article&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
      &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"Artcle update failure &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;article&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
      &lt;span class="n"&gt;invalid_articles&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;article&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Also notice that we are keeping track of any invalid articles that we might find while running this script. Especially when you are cleaning up bad data, always assume you might stumble across more of it and prepare for that in your script. Here we use an &lt;code&gt;if/else&lt;/code&gt; statement to catch any invalid articles. You could also use a &lt;code&gt;begin/rescue&lt;/code&gt; block. &lt;/p&gt;
&lt;h1&gt;
  
  
  2) Record Before and After States
&lt;/h1&gt;

&lt;p&gt;When you are updating records there is always a chance something will go off the rails. In order to have the ability to "roll back" track your before state as you are making the updates. If we update our script above to do this, here is what it would look like.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;invalid_articles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="n"&gt;before_update_tag_lists&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt; 
&lt;span class="no"&gt;Tag&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;id: &lt;/span&gt;&lt;span class="n"&gt;tag_ids&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;find_each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;taggings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each_with_index&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;tagging&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="n"&gt;article&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tagging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;taggable&lt;/span&gt;
    &lt;span class="k"&gt;next&lt;/span&gt; &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;article&lt;/span&gt;
    &lt;span class="c1"&gt;# Record the current cached tag list for every article&lt;/span&gt;
    &lt;span class="n"&gt;before_update_tag_lists&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;article&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;article&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cached_tag_list&lt;/span&gt;

    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;article&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;cached_tag_list: &lt;/span&gt;&lt;span class="n"&gt;article&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pluck&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:name&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;", "&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;
      &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"Artcle update success &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;article&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
      &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"Artcle update failure &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;article&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
      &lt;span class="n"&gt;invalid_articles&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;article&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;If anything goes wrong while this script is running, the &lt;code&gt;before_update_tag_lists&lt;/code&gt; hash has all of our original data in it. Using this original data we can loop back through the articles and reupdate them with the old lists if necessary. &lt;/p&gt;
&lt;h1&gt;
  
  
  3) Write Production Quality Code
&lt;/h1&gt;

&lt;p&gt;It can be tempting when you are writing a script to use as little syntax as possible. Usually, this means throwing in single letter variables everywhere. You probably won't ever use this code again, so why waste time making it look pretty and readable? The reason you want to make it pretty and readable is because then the script is easier to understand and follow. Having a script that is easy to understand will help you avoid writing bugs.&lt;/p&gt;

&lt;p&gt;In my script example above I clearly write out what each object is that I am working with. This allows nearly anyone to look at the script and be able to understand what it is doing. This leads me to my next script writing tip. &lt;/p&gt;
&lt;h1&gt;
  
  
  4) Have Your Script Reviewed
&lt;/h1&gt;

&lt;p&gt;The same way you never want to push code out to production without a code review, you shouldn't run a script in production without a code review. This is another reason why you want to make sure your script is understandable and readable, because you want someone else to be able to also figure out what it is doing. &lt;/p&gt;

&lt;p&gt;We all know the value a second set of eyes on our code brings. Even if you find yourself in a situation where time is tight and you need to run a script ASAP, try as hard as you can to get a second set of eyes on it. I can't tell you the number of times a fresh set of eyes has kept me from botching a script update. &lt;/p&gt;
&lt;h1&gt;
  
  
  5) Use &lt;a href="https://www.gnu.org/software/screen/" rel="noopener noreferrer"&gt;Screen&lt;/a&gt; or &lt;a href="https://github.com/tmux/tmux/wiki" rel="noopener noreferrer"&gt;Tmux&lt;/a&gt; for Long Running Scripts
&lt;/h1&gt;

&lt;p&gt;Tmux and Screen allow you to start an ssh session in a shell and keep that shell active even through network disruptions. This ensures that if you lose connection while your script is running, the script run will not be interrupted. Thanks &lt;a class="mentioned-user" href="https://dev.to/kinduff"&gt;@kinduff&lt;/a&gt; for the reminder! &lt;/p&gt;


&lt;div class="liquid-comment"&gt;
    &lt;div class="details"&gt;
      &lt;a href="/kinduff"&gt;
        &lt;img class="profile-pic" 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%2Fuser%2Fprofile_image%2F18683%2F01f06170-274c-4e62-b4f4-1cd36c823ab7.jpg" alt="kinduff profile image"&gt;
      &lt;/a&gt;
      &lt;a href="/kinduff"&gt;
        &lt;span class="comment-username"&gt;Alejandro AR&lt;/span&gt;
      &lt;/a&gt;
      &lt;span class="color-base-30 px-2 m:pl-0"&gt;•&lt;/span&gt;

&lt;a href="https://dev.to/kinduff/comment/10f64" class="comment-date crayons-link crayons-link--secondary fs-s"&gt;
  &lt;time class="date-short-year"&gt;
    Jun 17 '20
  &lt;/time&gt;

&lt;/a&gt;

    &lt;/div&gt;
    &lt;div class="body"&gt;
      &lt;p&gt;If the task takes a good amount of time, you have risks of being disconnected from either the SSH session, your internet provider, etc.&lt;/p&gt;

&lt;p&gt;To avoid this, I recommended running these scripts (although, I do not recommend running scripts like this at all) using screen. It's super easy to use:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;SSH into the desired instance&lt;/li&gt;
&lt;li&gt;Start a new screen session using &lt;code&gt;screen&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Run the long running script&lt;/li&gt;
&lt;li&gt;Press &lt;code&gt;Ctrl&lt;/code&gt; + &lt;code&gt;a&lt;/code&gt; followed by &lt;code&gt;d&lt;/code&gt; to detach the session&lt;/li&gt;
&lt;li&gt;You can now close everything, even the SSH session&lt;/li&gt;
&lt;li&gt;You can reattach to the screen session using &lt;code&gt;screen -r&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Screen has a lot of awesome things, make sure to check out the &lt;a href="https://linux.die.net/man/1/screen" rel="nofollow noopener noreferrer"&gt;man page&lt;/a&gt;.&lt;/p&gt;


    &lt;/div&gt;
&lt;/div&gt;



&lt;p&gt;Running a script in production is never ideal, but if you use these tips when you do it, it can make the experience much less daunting. &lt;/p&gt;

&lt;p&gt;Happy scripting!  &lt;/p&gt;

</description>
      <category>beginners</category>
      <category>ruby</category>
      <category>devops</category>
    </item>
    <item>
      <title>Optimizing Code as You Go</title>
      <dc:creator>Molly Struve (she/her)</dc:creator>
      <pubDate>Tue, 02 Jun 2020 16:25:55 +0000</pubDate>
      <link>https://dev.to/molly/optimizing-code-as-you-go-3b7e</link>
      <guid>https://dev.to/molly/optimizing-code-as-you-go-3b7e</guid>
      <description>&lt;p&gt;I am a big proponent of always thinking about scale and optimization when you are writing code, no matter how big or small of a product you are working on. A lot of people like to argue that there is no reason to optimize when you don't have to and that is wastes time. Yes, sometimes it does take a little bit of extra time, but down the road that little bit of time can save you a huge headache. &lt;/p&gt;

&lt;p&gt;I'm not talking about rearchitecting a whole feature, I'm talking about the little things you can do while you are coding that I guarantee will make a difference in the future. In this post, I will walk through an example of a small optimization I made recently at DEV &lt;a href="https://github.com/thepracticaldev/dev.to/pull/8148"&gt;with this pull request&lt;/a&gt; and how we could of avoided creating that pull request altogether.&lt;/p&gt;

&lt;h1&gt;
  
  
  The Most Popular DEV endpoint
&lt;/h1&gt;

&lt;p&gt;Every time a DEV page is loaded we make an asynchronous call to get some user data to help us populate variables in the view. This user data comes from the endpoint &lt;code&gt;async_info/base_data&lt;/code&gt;. Because this call is made for every page load it is above and beyond our most hit endpoint. The top purple line shows the number of calls made to our &lt;code&gt;async_info/base_data&lt;/code&gt; endpoint.&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%2F5962hij0iwizdr33gh0o.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%2F5962hij0iwizdr33gh0o.png" alt="Graph of endpoint usage at DEV over time and the top usage by double is the async user_data endpoint" width="800" height="441"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Given we call this endpoint a lot I decided to take a closer look at it to ensure it was as lean and mean as it could be. The first thing I did was checkout how we were building the hash we were returning. Here is what the hash looked like before my change:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="ss"&gt;id: &lt;/span&gt;&lt;span class="vi"&gt;@user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="vi"&gt;@user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;username: &lt;/span&gt;&lt;span class="vi"&gt;@user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;profile_image_90: &lt;/span&gt;&lt;span class="no"&gt;ProfileImage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@user&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;width: &lt;/span&gt;&lt;span class="mi"&gt;90&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;

  &lt;span class="ss"&gt;followed_tag_names: &lt;/span&gt;&lt;span class="vi"&gt;@user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cached_followed_tag_names&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;followed_tags: &lt;/span&gt;&lt;span class="vi"&gt;@user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cached_followed_tags&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;only: &lt;/span&gt;&lt;span class="sx"&gt;%i[id name bg_color_hex text_color_hex hotness_score]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;methods: &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:points&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;

  &lt;span class="ss"&gt;followed_user_ids: &lt;/span&gt;&lt;span class="vi"&gt;@user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cached_following_users_ids&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;followed_organization_ids: &lt;/span&gt;&lt;span class="vi"&gt;@user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cached_following_organizations_ids&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice we have two different tag fields returning the same data. I do not know which one field was added first, but for the sake of this example lets say that &lt;code&gt;followed_tag_names&lt;/code&gt; was their first. &lt;/p&gt;

&lt;h1&gt;
  
  
  Let's pretend...
&lt;/h1&gt;

&lt;p&gt;Let's pretend you are the past developer adding the &lt;code&gt;followed_tags&lt;/code&gt; value to the hash. You need all of the tag information in the view and not just the names, so you add the new field &lt;code&gt;followed_tags&lt;/code&gt; to the hash. Here, is where many developers stop. They have the information they need so their work is done. &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%2Fi%2F5hrf982pw1gkg6e9f6tc.gif" 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%2Fi%2F5hrf982pw1gkg6e9f6tc.gif" alt="DONT STOP, keep going gif" width="480" height="269"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is when I say, don't stop! Take a moment and look at your hash. You are now making &lt;strong&gt;TWO&lt;/strong&gt; calls to get the same Tag information. While this may be fine when the hash is not created often, it is far from ideal when your site starts to take off and this hash is created millions of times a day. That extra call will add up. If instead of stopping, you take a few extra minutes to address this unoptimal code, you can easily solve this issue and save your future self a lot of headache. &lt;/p&gt;

&lt;h1&gt;
  
  
  Optimization Options
&lt;/h1&gt;

&lt;p&gt;Here are the two options I considered when I was optimizing this code. &lt;/p&gt;

&lt;p&gt;1) We could memoize the tags in a variable and then reference that variable in our hash like below. This will save us that extra database hit and ensure we don't waste resources on redundant calls.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;tags&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="vi"&gt;@user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cached_followed_tags&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="ss"&gt;id: &lt;/span&gt;&lt;span class="vi"&gt;@user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="vi"&gt;@user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;username: &lt;/span&gt;&lt;span class="vi"&gt;@user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;profile_image_90: &lt;/span&gt;&lt;span class="no"&gt;ProfileImage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@user&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;width: &lt;/span&gt;&lt;span class="mi"&gt;90&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="ss"&gt;followed_tag_names: &lt;/span&gt;&lt;span class="n"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="ss"&gt;:name&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="ss"&gt;followed_tags: &lt;/span&gt;&lt;span class="n"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;only: &lt;/span&gt;&lt;span class="sx"&gt;%i[id name bg_color_hex text_color_hex hotness_score]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;methods: &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:points&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
  &lt;span class="ss"&gt;followed_user_ids: &lt;/span&gt;&lt;span class="vi"&gt;@user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cached_following_users_ids&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;followed_organization_ids: &lt;/span&gt;&lt;span class="vi"&gt;@user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cached_following_organizations_ids&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;2) If we don't want to make the extra database call AND we want to eliminate sending duplicate information, we can solve it the way I did in the &lt;a href="https://github.com/thepracticaldev/dev.to/pull/8148"&gt;pull request&lt;/a&gt;. In the pull request I removed the &lt;code&gt;followed_tag_names&lt;/code&gt; field and updated our view code to parse the JSON from the &lt;code&gt;followed_tags&lt;/code&gt; field. I choose this because it saves us a database hit AND decreases the amount of information we are sending to and from the server.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;followedTagNames&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;currentUser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;followed_tags&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ReadingList&lt;/span&gt;
  &lt;span class="nx"&gt;availableTags&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;followedTagNames&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nx"&gt;statusView&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;view&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="sr"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;,
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  TL;DR
&lt;/h1&gt;

&lt;p&gt;By spending an extra couple of minutes looking at your code and thinking about scale, at the very least, you can save your future self some extra work. At the most, you could save your app from a big slow down if/when it gets hit with a large amount of growth.&lt;/p&gt;

&lt;p&gt;HAPPY SCALING! &lt;/p&gt;

</description>
      <category>beginners</category>
      <category>optimization</category>
      <category>ruby</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
